From 6bf20c78f5b69d40bcc4931df93d29198435ab67 Mon Sep 17 00:00:00 2001 From: zakk Date: Fri, 26 Aug 2005 17:39:27 +0000 Subject: newlines fixed git-svn-id: svn://svn.icculus.org/quake3/trunk@6 edf5b092-35ff-0310-97b2-ce42778d08ea --- code/Construct | 862 +- code/Makefile | 6 +- code/botlib/aasfile.h | 534 +- code/botlib/be_aas_bsp.h | 178 +- code/botlib/be_aas_bspq3.c | 974 +- code/botlib/be_aas_cluster.c | 3090 +-- code/botlib/be_aas_cluster.h | 76 +- code/botlib/be_aas_debug.c | 1554 +- code/botlib/be_aas_debug.h | 124 +- code/botlib/be_aas_def.h | 612 +- code/botlib/be_aas_entity.c | 874 +- code/botlib/be_aas_entity.h | 126 +- code/botlib/be_aas_file.c | 1164 +- code/botlib/be_aas_file.h | 84 +- code/botlib/be_aas_funcs.h | 94 +- code/botlib/be_aas_main.c | 858 +- code/botlib/be_aas_main.h | 122 +- code/botlib/be_aas_move.c | 2202 +- code/botlib/be_aas_move.h | 142 +- code/botlib/be_aas_optimize.c | 624 +- code/botlib/be_aas_optimize.h | 66 +- code/botlib/be_aas_reach.c | 9094 +++---- code/botlib/be_aas_reach.h | 136 +- code/botlib/be_aas_route.c | 4418 ++-- code/botlib/be_aas_route.h | 134 +- code/botlib/be_aas_routealt.c | 480 +- code/botlib/be_aas_routealt.h | 80 +- code/botlib/be_aas_sample.c | 2788 +-- code/botlib/be_aas_sample.h | 138 +- code/botlib/be_ai_char.c | 1580 +- code/botlib/be_ai_chat.c | 6034 ++--- code/botlib/be_ai_gen.c | 268 +- code/botlib/be_ai_goal.c | 3642 +-- code/botlib/be_ai_move.c | 7220 +++--- code/botlib/be_ai_weap.c | 1086 +- code/botlib/be_ai_weight.c | 1824 +- code/botlib/be_ai_weight.h | 166 +- code/botlib/be_ea.c | 1016 +- code/botlib/be_interface.c | 1762 +- code/botlib/be_interface.h | 114 +- code/botlib/botlib.vcproj | 3118 +-- code/botlib/l_crc.c | 302 +- code/botlib/l_crc.h | 58 +- code/botlib/l_libvar.c | 588 +- code/botlib/l_libvar.h | 126 +- code/botlib/l_log.c | 338 +- code/botlib/l_log.h | 92 +- code/botlib/l_memory.c | 926 +- code/botlib/l_memory.h | 152 +- code/botlib/l_precomp.c | 6456 ++--- code/botlib/l_precomp.h | 360 +- code/botlib/l_script.c | 2866 +-- code/botlib/l_script.h | 494 +- code/botlib/l_struct.c | 924 +- code/botlib/l_struct.h | 150 +- code/botlib/l_utils.h | 70 +- code/botlib/lcc.mak | 110 +- code/botlib/linux-i386.mak | 184 +- code/bspc/Conscript | 150 +- code/bspc/Makefile | 228 +- code/bspc/aas_areamerging.c | 780 +- code/bspc/aas_areamerging.h | 48 +- code/bspc/aas_cfg.c | 504 +- code/bspc/aas_cfg.h | 146 +- code/bspc/aas_create.c | 2284 +- code/bspc/aas_create.h | 272 +- code/bspc/aas_edgemelting.c | 216 +- code/bspc/aas_edgemelting.h | 48 +- code/bspc/aas_facemerging.c | 564 +- code/bspc/aas_facemerging.h | 48 +- code/bspc/aas_file.c | 1098 +- code/bspc/aas_file.h | 50 +- code/bspc/aas_gsubdiv.c | 1312 +- code/bspc/aas_gsubdiv.h | 50 +- code/bspc/aas_map.c | 1698 +- code/bspc/aas_map.h | 46 +- code/bspc/aas_prunenodes.c | 178 +- code/bspc/aas_prunenodes.h | 48 +- code/bspc/aas_store.c | 2164 +- code/bspc/aas_store.h | 214 +- code/bspc/aasfile.h | 504 +- code/bspc/be_aas_bspc.c | 584 +- code/bspc/be_aas_bspc.h | 46 +- code/bspc/brushbsp.c | 3742 +-- code/bspc/bspc.c | 1982 +- code/bspc/bspc.sln | 56 +- code/bspc/bspc.vcproj | 2762 +-- code/bspc/cfgq3.c | 168 +- code/bspc/csg.c | 2010 +- code/bspc/faces.c | 1956 +- code/bspc/gldraw.c | 464 +- code/bspc/glfile.c | 298 +- code/bspc/l_bsp_ent.c | 360 +- code/bspc/l_bsp_ent.h | 116 +- code/bspc/l_bsp_hl.c | 1776 +- code/bspc/l_bsp_hl.h | 628 +- code/bspc/l_bsp_q1.c | 1240 +- code/bspc/l_bsp_q1.h | 550 +- code/bspc/l_bsp_q2.c | 2268 +- code/bspc/l_bsp_q2.h | 196 +- code/bspc/l_bsp_q3.c | 1648 +- code/bspc/l_bsp_q3.h | 162 +- code/bspc/l_bsp_sin.c | 2372 +- code/bspc/l_bsp_sin.h | 212 +- code/bspc/l_cmd.c | 2460 +- code/bspc/l_cmd.h | 314 +- code/bspc/l_log.c | 430 +- code/bspc/l_log.h | 84 +- code/bspc/l_math.c | 578 +- code/bspc/l_math.h | 186 +- code/bspc/l_mem.c | 882 +- code/bspc/l_mem.h | 102 +- code/bspc/l_poly.c | 2822 +-- code/bspc/l_poly.h | 240 +- code/bspc/l_qfiles.c | 1326 +- code/bspc/l_qfiles.h | 182 +- code/bspc/l_threads.c | 3020 +-- code/bspc/l_threads.h | 90 +- code/bspc/l_utils.c | 518 +- code/bspc/l_utils.h | 158 +- code/bspc/lcc.mak | 122 +- code/bspc/leakfile.c | 202 +- code/bspc/linux-i386.mak | 218 +- code/bspc/map.c | 2534 +- code/bspc/map_hl.c | 2228 +- code/bspc/map_q1.c | 2348 +- code/bspc/map_q2.c | 2324 +- code/bspc/map_q3.c | 1362 +- code/bspc/map_sin.c | 2422 +- code/bspc/nodraw.c | 94 +- code/bspc/portals.c | 2594 +- code/bspc/prtfile.c | 574 +- code/bspc/q2files.h | 974 +- code/bspc/q3files.h | 748 +- code/bspc/qbsp.h | 954 +- code/bspc/qfiles.h | 974 +- code/bspc/sinfiles.h | 730 +- code/bspc/tetrahedron.c | 2778 +-- code/bspc/tetrahedron.h | 48 +- code/bspc/textures.c | 456 +- code/bspc/tree.c | 566 +- code/bspc/writebsp.c | 1190 +- code/cgame.lnt | 50 +- code/cgame/Conscript | 276 +- code/cgame/cg_consolecmds.c | 1156 +- code/cgame/cg_draw.c | 5318 ++--- code/cgame/cg_drawtools.c | 1648 +- code/cgame/cg_effects.c | 1436 +- code/cgame/cg_ents.c | 2074 +- code/cgame/cg_event.c | 2410 +- code/cgame/cg_info.c | 594 +- code/cgame/cg_local.h | 3338 +-- code/cgame/cg_localents.c | 1772 +- code/cgame/cg_main.c | 3994 ++-- code/cgame/cg_marks.c | 4548 ++-- code/cgame/cg_newdraw.c | 3704 +-- code/cgame/cg_particles.c | 4036 ++-- code/cgame/cg_players.c | 5238 ++-- code/cgame/cg_playerstate.c | 1052 +- code/cgame/cg_predict.c | 1256 +- code/cgame/cg_public.h | 476 +- code/cgame/cg_scoreboard.c | 1068 +- code/cgame/cg_servercmds.c | 2196 +- code/cgame/cg_snapshot.c | 806 +- code/cgame/cg_syscalls.asm | 212 +- code/cgame/cg_syscalls.c | 890 +- code/cgame/cg_view.c | 1752 +- code/cgame/cg_weapons.c | 4554 ++-- code/cgame/cgame.bat | 126 +- code/cgame/cgame.def | 6 +- code/cgame/cgame.plg | 248 +- code/cgame/cgame.q3asm | 52 +- code/cgame/cgame.sh | 72 +- code/cgame/cgame.vcproj | 2442 +- code/cgame/cgame_ta.bat | 130 +- code/cgame/cgame_ta.q3asm | 56 +- code/cgame/cgame_ta.sh | 76 +- code/cgame/tr_types.h | 458 +- code/clean.bat | 122 +- code/client/cl_cgame.c | 2058 +- code/client/cl_cin.c | 3480 +-- code/client/cl_console.c | 1572 +- code/client/cl_input.c | 1802 +- code/client/cl_keys.c | 2504 +- code/client/cl_main.c | 6648 +++--- code/client/cl_net_chan.c | 334 +- code/client/cl_parse.c | 1310 +- code/client/cl_scrn.c | 1094 +- code/client/cl_ui.c | 2400 +- code/client/client.h | 1038 +- code/client/keys.h | 114 +- code/client/snd_adpcm.c | 660 +- code/client/snd_dma.c | 3272 +-- code/client/snd_local.h | 408 +- code/client/snd_mem.c | 808 +- code/client/snd_mix.c | 1362 +- code/client/snd_public.h | 144 +- code/client/snd_wavelet.c | 506 +- code/game.lnt | 70 +- code/game/Conscript | 280 +- code/game/ai_chat.c | 2452 +- code/game/ai_chat.h | 122 +- code/game/ai_cmd.c | 3984 ++-- code/game/ai_cmd.h | 74 +- code/game/ai_dmnet.c | 5220 ++-- code/game/ai_dmnet.h | 122 +- code/game/ai_dmq3.c | 10922 ++++----- code/game/ai_dmq3.h | 412 +- code/game/ai_main.c | 3390 +-- code/game/ai_main.h | 598 +- code/game/ai_team.c | 4160 ++-- code/game/ai_team.h | 78 +- code/game/ai_vcmd.c | 1100 +- code/game/ai_vcmd.h | 72 +- code/game/be_aas.h | 442 +- code/game/be_ai_char.h | 96 +- code/game/be_ai_chat.h | 226 +- code/game/be_ai_gen.h | 66 +- code/game/be_ai_goal.h | 236 +- code/game/be_ai_move.h | 284 +- code/game/be_ai_weap.h | 208 +- code/game/be_ea.h | 132 +- code/game/bg_lib.c | 2648 +-- code/game/bg_lib.h | 182 +- code/game/bg_local.h | 166 +- code/game/bg_misc.c | 3208 +-- code/game/bg_pmove.c | 4138 ++-- code/game/bg_public.h | 1476 +- code/game/bg_slidemove.c | 650 +- code/game/botlib.h | 1032 +- code/game/chars.h | 268 +- code/game/g_active.c | 2382 +- code/game/g_arenas.c | 752 +- code/game/g_bot.c | 2034 +- code/game/g_client.c | 2688 +-- code/game/g_cmds.c | 3402 +-- code/game/g_combat.c | 2390 +- code/game/g_items.c | 2020 +- code/game/g_local.h | 1942 +- code/game/g_main.c | 3664 +-- code/game/g_mem.c | 122 +- code/game/g_misc.c | 964 +- code/game/g_missile.c | 1616 +- code/game/g_mover.c | 3224 +-- code/game/g_public.h | 858 +- code/game/g_rankings.c | 2270 +- code/game/g_rankings.h | 792 +- code/game/g_session.c | 386 +- code/game/g_spawn.c | 1286 +- code/game/g_svcmds.c | 1016 +- code/game/g_syscalls.asm | 450 +- code/game/g_syscalls.c | 1580 +- code/game/g_target.c | 934 +- code/game/g_team.c | 2966 +-- code/game/g_team.h | 176 +- code/game/g_trigger.c | 930 +- code/game/g_utils.c | 1332 +- code/game/g_weapon.c | 2290 +- code/game/game.bat | 170 +- code/game/game.def | 6 +- code/game/game.q3asm | 70 +- code/game/game.sh | 96 +- code/game/game.vcproj | 4264 ++-- code/game/game_ta.bat | 172 +- code/game/game_ta.q3asm | 70 +- code/game/game_ta.sh | 96 +- code/game/inv.h | 332 +- code/game/match.h | 268 +- code/game/q_math.c | 2616 +- code/game/q_shared.c | 2516 +- code/game/q_shared.h | 2864 +-- code/game/surfaceflags.h | 160 +- code/game/syn.h | 68 +- code/installdebug.bat | 12 +- code/installrelease.bat | 26 +- code/installvms.bat | 14 +- code/jpeg-6/jcapimin.c | 456 +- code/jpeg-6/jcapistd.c | 322 +- code/jpeg-6/jccoefct.c | 896 +- code/jpeg-6/jccolor.c | 918 +- code/jpeg-6/jcdctmgr.c | 782 +- code/jpeg-6/jchuff.c | 1692 +- code/jpeg-6/jchuff.h | 68 +- code/jpeg-6/jcinit.c | 144 +- code/jpeg-6/jcmainct.c | 592 +- code/jpeg-6/jcmarker.c | 1278 +- code/jpeg-6/jcmaster.c | 1156 +- code/jpeg-6/jcomapi.c | 188 +- code/jpeg-6/jconfig.h | 82 +- code/jpeg-6/jcparam.c | 1150 +- code/jpeg-6/jcphuff.c | 1658 +- code/jpeg-6/jcprepct.c | 742 +- code/jpeg-6/jcsample.c | 1038 +- code/jpeg-6/jctrans.c | 742 +- code/jpeg-6/jdapimin.c | 796 +- code/jpeg-6/jdapistd.c | 550 +- code/jpeg-6/jdatadst.c | 302 +- code/jpeg-6/jdatasrc.c | 408 +- code/jpeg-6/jdcoefct.c | 1450 +- code/jpeg-6/jdcolor.c | 734 +- code/jpeg-6/jdct.h | 352 +- code/jpeg-6/jddctmgr.c | 540 +- code/jpeg-6/jdhuff.c | 1148 +- code/jpeg-6/jdhuff.h | 404 +- code/jpeg-6/jdinput.c | 762 +- code/jpeg-6/jdmainct.c | 1040 +- code/jpeg-6/jdmarker.c | 2104 +- code/jpeg-6/jdmaster.c | 1114 +- code/jpeg-6/jdmerge.c | 800 +- code/jpeg-6/jdphuff.c | 1284 +- code/jpeg-6/jdpostct.c | 580 +- code/jpeg-6/jdsample.c | 956 +- code/jpeg-6/jdtrans.c | 244 +- code/jpeg-6/jerror.c | 464 +- code/jpeg-6/jerror.h | 546 +- code/jpeg-6/jfdctflt.c | 336 +- code/jpeg-6/jfdctfst.c | 448 +- code/jpeg-6/jfdctint.c | 566 +- code/jpeg-6/jidctflt.c | 482 +- code/jpeg-6/jidctfst.c | 734 +- code/jpeg-6/jidctint.c | 776 +- code/jpeg-6/jidctred.c | 794 +- code/jpeg-6/jinclude.h | 232 +- code/jpeg-6/jload.c | 290 +- code/jpeg-6/jmemansi.c | 334 +- code/jpeg-6/jmemdos.c | 1268 +- code/jpeg-6/jmemmgr.c | 2230 +- code/jpeg-6/jmemname.c | 542 +- code/jpeg-6/jmemnobs.c | 210 +- code/jpeg-6/jmemsys.h | 364 +- code/jpeg-6/jmorecfg.h | 696 +- code/jpeg-6/jpegint.h | 776 +- code/jpeg-6/jpeglib.h | 2102 +- code/jpeg-6/jpegtran.c | 740 +- code/jpeg-6/jquant1.c | 1712 +- code/jpeg-6/jquant2.c | 2620 +- code/jpeg-6/jutils.c | 350 +- code/jpeg-6/jversion.h | 28 +- code/macosx/BuildRelease | 34 +- code/macosx/CGMouseDeltaFix.h | 54 +- code/macosx/CGMouseDeltaFix.m | 262 +- code/macosx/CGPrivateAPI.h | 370 +- code/macosx/GenerateQGL.pl | 292 +- code/macosx/Q3Controller.h | 80 +- code/macosx/Q3Controller.m | 870 +- code/macosx/Quake3.pbproj/apple.pbxuser | 1126 +- code/macosx/Quake3.pbproj/project.pbxproj | 23604 +++++++++---------- code/macosx/RecordDemo.zsh | 18 +- code/macosx/botlib.log | 392 +- code/macosx/macosx_display.h | 76 +- code/macosx/macosx_display.m | 746 +- code/macosx/macosx_glimp.h | 74 +- code/macosx/macosx_glimp.m | 2228 +- code/macosx/macosx_glsmp_mutex.m | 352 +- code/macosx/macosx_glsmp_null.m | 92 +- code/macosx/macosx_glsmp_ports.m | 850 +- code/macosx/macosx_input.m | 1832 +- code/macosx/macosx_local.h | 258 +- code/macosx/macosx_qgl.h | 10190 ++++---- code/macosx/macosx_sndcore.m | 650 +- code/macosx/macosx_snddma.m | 410 +- code/macosx/macosx_sys.m | 1074 +- code/macosx/macosx_timers.h | 112 +- code/macosx/macosx_timers.m | 150 +- code/macosx/timedemo.zsh | 52 +- code/null/mac_net.c | 130 +- code/null/null_client.c | 182 +- code/null/null_glimp.c | 112 +- code/null/null_input.c | 70 +- code/null/null_main.c | 232 +- code/null/null_net.c | 128 +- code/null/null_snddma.c | 120 +- code/opts.lnt | 52 +- code/q3_ui/Conscript | 186 +- code/q3_ui/compile.bat | 4 +- code/q3_ui/keycodes.h | 326 +- code/q3_ui/q3_ui.bat | 200 +- code/q3_ui/q3_ui.q3asm | 86 +- code/q3_ui/q3_ui.sh | 110 +- code/q3_ui/q3_ui.vcproj | 4016 ++-- code/q3_ui/ui.def | 6 +- code/q3_ui/ui.q3asm | 90 +- code/q3_ui/ui_addbots.c | 824 +- code/q3_ui/ui_atoms.c | 2534 +- code/q3_ui/ui_cdkey.c | 582 +- code/q3_ui/ui_cinematics.c | 700 +- code/q3_ui/ui_confirm.c | 586 +- code/q3_ui/ui_connect.c | 564 +- code/q3_ui/ui_controls2.c | 3334 +-- code/q3_ui/ui_credits.c | 258 +- code/q3_ui/ui_demo2.c | 582 +- code/q3_ui/ui_display.c | 530 +- code/q3_ui/ui_gameinfo.c | 1640 +- code/q3_ui/ui_ingame.c | 698 +- code/q3_ui/ui_loadconfig.c | 548 +- code/q3_ui/ui_local.h | 1600 +- code/q3_ui/ui_login.c | 416 +- code/q3_ui/ui_main.c | 498 +- code/q3_ui/ui_menu.c | 838 +- code/q3_ui/ui_mfield.c | 878 +- code/q3_ui/ui_mods.c | 566 +- code/q3_ui/ui_network.c | 562 +- code/q3_ui/ui_options.c | 458 +- code/q3_ui/ui_playermodel.c | 1462 +- code/q3_ui/ui_players.c | 2496 +- code/q3_ui/ui_playersettings.c | 1026 +- code/q3_ui/ui_preferences.c | 838 +- code/q3_ui/ui_qmenu.c | 3492 +-- code/q3_ui/ui_rankings.c | 840 +- code/q3_ui/ui_rankstatus.c | 418 +- code/q3_ui/ui_removebots.c | 684 +- code/q3_ui/ui_saveconfig.c | 424 +- code/q3_ui/ui_serverinfo.c | 544 +- code/q3_ui/ui_servers2.c | 3280 +-- code/q3_ui/ui_setup.c | 654 +- code/q3_ui/ui_signup.c | 572 +- code/q3_ui/ui_sound.c | 632 +- code/q3_ui/ui_sparena.c | 100 +- code/q3_ui/ui_specifyleague.c | 666 +- code/q3_ui/ui_specifyserver.c | 426 +- code/q3_ui/ui_splevel.c | 2016 +- code/q3_ui/ui_sppostgame.c | 1288 +- code/q3_ui/ui_spreset.c | 388 +- code/q3_ui/ui_spskill.c | 658 +- code/q3_ui/ui_startserver.c | 3936 ++-- code/q3_ui/ui_team.c | 420 +- code/q3_ui/ui_teamorders.c | 898 +- code/q3_ui/ui_video.c | 2140 +- code/qcommon/cm_load.c | 1678 +- code/qcommon/cm_local.h | 388 +- code/qcommon/cm_patch.c | 3542 +-- code/qcommon/cm_patch.h | 206 +- code/qcommon/cm_polylib.c | 1474 +- code/qcommon/cm_polylib.h | 136 +- code/qcommon/cm_public.h | 152 +- code/qcommon/cm_test.c | 956 +- code/qcommon/cm_trace.c | 2942 +-- code/qcommon/cmd.c | 1430 +- code/qcommon/cmd.c.save | 1272 +- code/qcommon/common.c | 6634 +++--- code/qcommon/cvar.c | 1812 +- code/qcommon/files.c | 6838 +++--- code/qcommon/huffman.c | 874 +- code/qcommon/md4.c | 598 +- code/qcommon/msg.c | 3514 +-- code/qcommon/net_chan.c | 1484 +- code/qcommon/qcommon.h | 2134 +- code/qcommon/qfiles.h | 976 +- code/qcommon/unzip.c | 8598 +++---- code/qcommon/unzip.h | 672 +- code/qcommon/vm.c | 1670 +- code/qcommon/vm_interpreted.c | 1778 +- code/qcommon/vm_local.h | 360 +- code/qcommon/vm_ppc.c | 2958 +-- code/qcommon/vm_ppc_new.c | 4238 ++-- code/qcommon/vm_x86.c | 2392 +- code/quake3.sln | 290 +- code/quake3.vcproj | 6926 +++--- code/renderer.lnt | 54 +- code/renderer/qgl.h | 1144 +- code/renderer/qgl_linked.h | 714 +- code/renderer/ref_trin.def | 4 +- code/renderer/renderer.vcproj | 12224 +++++----- code/renderer/tr_animation.c | 342 +- code/renderer/tr_backend.c | 2286 +- code/renderer/tr_bsp.c | 3724 +-- code/renderer/tr_cmds.c | 894 +- code/renderer/tr_curve.c | 1248 +- code/renderer/tr_flares.c | 894 +- code/renderer/tr_font.c | 1084 +- code/renderer/tr_image.c | 5040 ++-- code/renderer/tr_init.c | 2434 +- code/renderer/tr_light.c | 790 +- code/renderer/tr_local.h | 3218 +-- code/renderer/tr_main.c | 2970 +-- code/renderer/tr_marks.c | 886 +- code/renderer/tr_mesh.c | 794 +- code/renderer/tr_model.c | 1400 +- code/renderer/tr_noise.c | 190 +- code/renderer/tr_public.h | 334 +- code/renderer/tr_scene.c | 818 +- code/renderer/tr_shade.c | 2722 +-- code/renderer/tr_shade_calc.c | 2410 +- code/renderer/tr_shader.c | 6026 ++--- code/renderer/tr_shadows.c | 682 +- code/renderer/tr_sky.c | 1690 +- code/renderer/tr_surface.c | 2430 +- code/renderer/tr_world.c | 1336 +- code/run.bat | 2 +- code/runrelease.bat | 2 +- code/server/server.h | 808 +- code/server/sv_bot.c | 1270 +- code/server/sv_ccmds.c | 1524 +- code/server/sv_client.c | 3062 +-- code/server/sv_game.c | 1962 +- code/server/sv_init.c | 1390 +- code/server/sv_main.c | 1710 +- code/server/sv_net_chan.c | 414 +- code/server/sv_rankings.c | 3074 +-- code/server/sv_snapshot.c | 1364 +- code/server/sv_world.c | 1382 +- code/splines/Splines.vcproj | 592 +- code/splines/math_angles.cpp | 300 +- code/splines/math_angles.h | 390 +- code/splines/math_matrix.cpp | 268 +- code/splines/math_matrix.h | 446 +- code/splines/math_quaternion.cpp | 156 +- code/splines/math_quaternion.h | 380 +- code/splines/math_vector.cpp | 288 +- code/splines/math_vector.h | 1148 +- code/splines/q_parse.cpp | 1070 +- code/splines/q_shared.cpp | 1952 +- code/splines/q_shared.h | 1620 +- code/splines/q_shared.hpp | 1620 +- code/splines/splines.cpp | 2500 +- code/splines/splines.h | 2150 +- code/splines/util_list.h | 692 +- code/splines/util_str.cpp | 1240 +- code/splines/util_str.h | 1634 +- code/ui/Conscript | 124 +- code/ui/compile.bat | 4 +- code/ui/keycodes.h | 326 +- code/ui/ui.bat | 66 +- code/ui/ui.def | 6 +- code/ui/ui.q3asm | 24 +- code/ui/ui.vcproj | 2016 +- code/ui/ui_atoms.c | 1040 +- code/ui/ui_gameinfo.c | 648 +- code/ui/ui_local.h | 2272 +- code/ui/ui_main.c | 11994 +++++----- code/ui/ui_players.c | 2756 +-- code/ui/ui_public.h | 382 +- code/ui/ui_shared.c | 11570 ++++----- code/ui/ui_shared.h | 900 +- code/ui/ui_syscalls.asm | 202 +- code/ui/ui_syscalls.c | 802 +- code/ui/ui_util.c | 58 +- code/unix/ChangeLog | 5962 ++--- code/unix/Cons_gcc.pm | 94 +- code/unix/Conscript-client | 530 +- code/unix/Conscript-dedicated | 230 +- code/unix/Conscript-pk3 | 270 +- code/unix/Conscript-sdk | 244 +- code/unix/Conscript-setup | 66 +- code/unix/LinuxSupport/CHANGES-1.32.txt | 300 +- code/unix/LinuxSupport/INSTALL | 104 +- code/unix/LinuxSupport/index.html | 566 +- code/unix/LinuxSupport/udp_wide_README.txt | 40 +- code/unix/LinuxSupport/udp_wide_broadcast.patch | 114 +- code/unix/Makefile | 4238 ++-- code/unix/Makefile.Game | 570 +- code/unix/Quake3.kdelnk | 24 +- code/unix/README.EULA | 368 +- code/unix/README.Linux | 704 +- code/unix/README.Q3Test | 612 +- code/unix/build_setup.sh | 250 +- code/unix/cons | 13656 +++++------ code/unix/extract_ver.pl | 18 +- code/unix/ftol.nasm | 302 +- code/unix/linux_common.c | 688 +- code/unix/linux_glimp.c | 3560 +-- code/unix/linux_joystick.c | 414 +- code/unix/linux_local.h | 98 +- code/unix/linux_qgl.c | 8306 +++---- code/unix/linux_signals.c | 122 +- code/unix/linux_snd.c | 586 +- code/unix/matha.s | 850 +- code/unix/pcons-2.3.1 | 15822 ++++++------- code/unix/q3test.spec.sh | 104 +- code/unix/qasm.h | 960 +- code/unix/run-target.sh | 24 +- code/unix/snapvector.nasm | 188 +- code/unix/snd_mixa.s | 434 +- code/unix/sys_dosa.s | 230 +- code/unix/unix_glw.h | 76 +- code/unix/unix_main.c | 2546 +- code/unix/unix_net.c | 1232 +- code/unix/unix_shared.c | 870 +- code/unix/vm_x86.c | 58 +- code/unix/vm_x86a.s | 924 +- code/win32/glw_win.h | 102 +- code/win32/mod-sdk-setup/GameSource.VCT | Bin 628735 -> 628569 bytes .../mod-sdk-setup/QIIIA Game Source License.doc | Bin 34304 -> 34266 bytes code/win32/resource.h | 88 +- code/win32/win_gamma.c | 428 +- code/win32/win_glimp.c | 3316 +-- code/win32/win_input.c | 2298 +- code/win32/win_local.h | 190 +- code/win32/win_main.c | 2506 +- code/win32/win_net.c | 2062 +- code/win32/win_qgl.c | 8748 +++---- code/win32/win_shared.c | 612 +- code/win32/win_snd.c | 776 +- code/win32/win_syscon.c | 1192 +- code/win32/win_wndproc.c | 912 +- code/win32/winquake.rc | 142 +- 596 files changed, 396314 insertions(+), 396314 deletions(-) (limited to 'code') diff --git a/code/Construct b/code/Construct index ad9bc3c..82e6e0d 100755 --- a/code/Construct +++ b/code/Construct @@ -1,431 +1,431 @@ -# -*- mode: perl -*- -# cons script for cgame game q3_ui ui .so and .qvm builds -# -# Oct. 2001 TTimo -# - -# the top directory is -# --- -# where: -# is "debug" or "release" -# is "x86" or "ppc" -# is "Linux" "BSD" "IRIX" etc. -# is major.minor of libc config - -# source the compiler version utility -BEGIN { - push @INC, './unix'; -} -use Cons_gcc; - -# defaults -$config = 'debug'; -$do_smp = 1; -$do_masterserver = 0; -$do_authserver = 0; -$do_authport = 0; -$do_setup = 0; -$do_bspc = 0; -$do_sdk = 0; -$do_pk3 = 0; -# those are exported -$DO_WIN32 = 0; -$NO_VM = 0; -$NO_SO = 0; -$CC='gcc'; -$CXX='g++'; - -# detect an sdk build (don't attempt client build and other things) -if ( -r 'unix/Conscript-client' ) -{ - $no_core = 0; -} -else -{ - $no_core = 1; -} - -# detection of CPU type -$cpu = `uname -m`; -chop ($cpu); -if ($cpu +~ /i?86/) -{ - $cpu = 'x86'; -} -# OS -$OS = `uname`; -chop ($OS); -# hacky win32 detection and win32 specifics code -if ($OS =~ CYGWIN) -{ - $DO_WIN32 = 1; -} -else -{ - # libc .. do the little magic! - $libc_cmd = '/lib/libc.so.6 |grep "GNU C "|grep version|awk -F "version " \'{ print $2 }\'|cut -b -3'; - $libc = `$libc_cmd`; - chop ($libc); -} - -if ($DO_WIN32 eq 1) -{ - print("Win32 build\n"); - - $config = $ARGV[0]; - - # TODO: option to override $Q3BASE from command line - $Q3BASE = $ENV{Q3BASE}; # FIXME: this doesn't play nice with cygwin path syntax - print("\$Q3BASE: $Q3BASE\n"); - - if($config eq 'debug') - { - $DIR = 'Debug'; - system("cp -v $DIR/quake3.exe \$Q3BASE"); - system("cp -v $DIR/cgamex86.dll $DIR/qagamex86.dll $DIR/uix86.dll \$Q3BASE/baseq3"); - } - elsif ($config eq 'debug-TA') - { - $DIR = 'Debug_TA'; - system("cp -v $DIR/quake3.exe \$Q3BASE"); - system("cp -v $DIR/cgamex86.dll $DIR/qagamex86.dll $DIR/uix86.dll \$Q3BASE/missionpack"); - } - elsif($config eq 'release-TA') - { - $DIR = 'Release_TA'; - # spank! - system("./spank.sh"); - system("cp -v $DIR/quake3.exe \$Q3BASE"); - } - else - { - printf("ERROR: no config option (debug debug-TA release-TA)"); - exit; - } - - # copy selected stuff to shared media - $DESTDIR='/cygdrive/e/incoming/Id/q3-1.32'; - system("cp -v $DIR/quake3.exe $DESTDIR"); - system("cp -v /cygdrive/e/Q3SetupMedia/quake3/CHANGES-1.32.txt $DESTDIR"); - - exit; -} - -if(@ARGV gt 0) -{ - foreach $cmdopt (@ARGV) - { - if(lc($cmdopt) eq 'release') - { - $config = 'release'; - next; - } - elsif(lc($cmdopt) eq 'debug') - { - $config = 'debug'; - next; - } - elsif(lc($cmdopt) eq 'novm') - { - $NO_VM = 1; - next; - } - elsif(lc($cmdopt) eq 'noso') - { - $NO_SO = 1; - next; - } - elsif(lc($cmdopt) eq 'nosmp') - { - $do_smp = 0; - next; - } - elsif(lc($cmdopt) =~ 'master_server=.*') - { - $do_masterserver = 1; - $master_server = lc($cmdopt); - $master_server =~ s/master_server=(.*)/\1/; - next; - } - elsif(lc($cmdopt) =~ 'auth_server=.*') - { - $do_authserver = 1; - $auth_server = lc($cmdopt); - $auth_server =~ s/auth_server=(.*)/\1/; - next; - } - elsif(lc($cmdopt) =~ 'auth_port=.*') - { - $do_authport = 1; - $auth_port = lc($cmdopt); - $auth_port =~ s/auth_port=(.*)/\1/; - next; - } - elsif(lc($cmdopt) =~ 'setup') - { - $do_setup = 1; - next; - } - elsif(lc($cmdopt) =~ 'bspc') - { - $do_bspc = 1; - next; - } - elsif(lc($cmdopt) =~ 'sdk') - { - $do_sdk = 1; - next; - } - elsif(lc($cmdopt) =~ 'pk3') - { - $do_pk3 = 1; - next; - } - elsif(lc($cmdopt) =~ 'gcc=.*') - { - $CC=lc($cmdopt); - $CC =~ s/gcc=(.*)/\1/; - next; - } - elsif(lc($cmdopt) =~ 'g\+\+=.*') - { - $CXX=lc($cmdopt); - $CXX=~s/g\+\+=(.*)/\1/; - next; - } - else - { - # output an error & exit - print("Error\n $0: Unknown command line option: [ $cmdopt ]\n"); - system("cons -h"); - exit; - } - } -} - -if (($do_setup eq 1) && ($config ne 'release')) -{ - print("Error\n $0: 'setup' requires 'release'\n"); - exit; -} - -# sdk -if ($do_sdk eq 1) -{ - # extract the Q3 version from q_shared.h - $line = `cat game/q_shared.h | grep Q3_VERSION`; - chomp $line; - $line =~ s/.*Q3\ (.*)\"/$1/; - $Q3_VER = $line; - $SDK_NAME = "linuxq3a-sdk-$Q3_VER.x86.run"; - Default "unix/$SDK_NAME"; - Export qw( SDK_NAME Q3_VER ); - Build 'unix/Conscript-sdk'; - return; -} - -# build the config directory -$CONFIG_DIR = $config . '-' . $cpu . '-' . $OS . '-' . $libc; - -$COMMON_CFLAGS = '-pipe -fsigned-char '; - -if ($config eq 'debug') -{ - # use -Werror for better QA - $BASE_CFLAGS = $COMMON_CFLAGS . '-g -Wall -Werror -O '; - $BSPC_BASE_CFLAGS = $COMMON_CFLAGS . '-g -O -DLINUX -DBSPC -Dstricmp=strcasecmp '; -} -else -{ - $BASE_CFLAGS = $COMMON_CFLAGS . '-DNDEBUG -O6 -mcpu=pentiumpro -march=pentium -fomit-frame-pointer -ffast-math -malign-loops=2 -malign-jumps=2 -malign-functions=2 -fno-strict-aliasing -fstrength-reduce '; - $BSPC_BASE_CFLAGS = $BASE_CFLAGS . '-DLINUX -DBSPC -Dstricmp=strcasecmp '; -} - -if ($do_masterserver eq 1) -{ - $BASE_CFLAGS .= "-DMASTER_SERVER_NAME=\\\"$master_server\\\" "; -} - -if ($do_authserver eq 1) -{ - $BASE_CFLAGS .= "-DAUTHORIZE_SERVER_NAME=\\\"$auth_server\\\" "; -} - -if ($do_authport eq 1) -{ - $BASE_CFLAGS .= "-DPORT_AUTHORIZE=$auth_port "; -} - -my @gcc_version = Cons_gcc::get_gcc_version($CC); -print("GCC version: $gcc_version[1] - $gcc_version[2]\n"); -# with 2.95 you can link with gcc, this avoids nasty useless libstdc++ dependency -if ($gcc_version[0] eq '2') -{ - $LINK = $CC; -} else { - $LINK = $CXX; -} - -my @ccache = Cons_gcc::get_ccache(); -if ($ccache[0] eq '1') -{ - $CC = $ccache[1] . " " . $CC; - $CXX = $ccache[1] . " " . $CXX; -} - -print 'cpu : ' . $cpu . "\nOS : " . $OS . "\n"; -print "libc: " . $libc . "\n"; -print "configured for " . $config . " build\n"; -print 'CFLAGS: ' . $BASE_CFLAGS . "\n"; - -# install config -$INSTALL_BASEDIR='#install'; - -Default $INSTALL_BASEDIR; - -sub build_tools { - system("mkdir qvmtools 2>/dev/null"); - if (@_[0] eq 'q3lcc') - { - system("cd ../lcc ; make all ; cp /tmp/lcc ../code/qvmtools/q3lcc ; cp /tmp/rcc ../code/qvmtools/q3rcc ; cp /tmp/cpp ../code/qvmtools/q3cpp"); - } - elsif (@_[0] eq 'q3asm') - { - system("cd ../q3asm ; make ; cp q3asm ../code/qvmtools"); - } - else - { - printf("build_tools: @_[0] unrecognized command\n"); - die; - } - return 1; -} - -# build tools -$env_tools = new cons(); -Command $env_tools 'qvmtools/q3lcc', '[perl] &build_tools(\'q3lcc\')'; -Command $env_tools 'qvmtools/q3asm', '[perl] &build_tools(\'q3asm\')'; - -if ($do_bspc eq 1) -{ - # build bspc - $BUILD_DIR = $CONFIG_DIR . '/bspc'; - Link $BUILD_DIR => '.'; - $INSTALL_DIR = $INSTALL_BASEDIR . '/utils'; - Export qw( BSPC_BASE_CFLAGS BUILD_DIR INSTALL_DIR CC CXX LINK ); - Build $BUILD_DIR . '/bspc/Conscript'; -} - -# build vanilla Q3 -$TARGET_DIR='Q3'; - -$INSTALL_DIR = $INSTALL_BASEDIR . '/baseq3'; - -$BUILD_DIR = $CONFIG_DIR . '/' . $TARGET_DIR . '/cgame'; -Link $BUILD_DIR => '.'; -Export qw( BASE_CFLAGS TARGET_DIR INSTALL_DIR NO_VM NO_SO CC CXX LINK ); -Build $BUILD_DIR . '/cgame/Conscript'; - -$BUILD_DIR = $CONFIG_DIR . '/' . $TARGET_DIR . '/game'; -Link $BUILD_DIR => '.'; -Export qw( BASE_CFLAGS TARGET_DIR INSTALL_DIR NO_VM NO_SO CC CXX LINK ); -Build $BUILD_DIR . '/game/Conscript'; - -$BUILD_DIR = $CONFIG_DIR . '/' . $TARGET_DIR . '/q3_ui'; -Link $BUILD_DIR => '.'; -Export qw( BASE_CFLAGS TARGET_DIR INSTALL_DIR NO_VM NO_SO CC CXX LINK ); -Build $BUILD_DIR . '/q3_ui/Conscript'; - -# build TA -$TARGET_DIR='TA'; - -$INSTALL_DIR = $INSTALL_BASEDIR . '/missionpack'; - -$BUILD_DIR = $CONFIG_DIR . "/" . $TARGET_DIR . '/cgame'; -Link $BUILD_DIR => '.'; -Export qw( BASE_CFLAGS TARGET_DIR INSTALL_DIR NO_VM NO_SO CC CXX LINK ); -Build $BUILD_DIR . '/cgame/Conscript'; - -$BUILD_DIR = $CONFIG_DIR . "/" . $TARGET_DIR . '/game'; -Link $BUILD_DIR => '.'; -Export qw( BASE_CFLAGS TARGET_DIR INSTALL_DIR NO_VM NO_SO CC CXX LINK ); -Build $BUILD_DIR . '/game/Conscript'; - -$BUILD_DIR = $CONFIG_DIR . '/' . $TARGET_DIR . '/ui'; -Link $BUILD_DIR => '.'; -Export qw( BASE_CFLAGS TARGET_DIR INSTALL_DIR NO_VM NO_SO CC CXX LINK ); -Build $BUILD_DIR . '/ui/Conscript'; - -# core -if ($no_core eq 1) -{ - return; -} - -$INSTALL_DIR = $INSTALL_BASEDIR; -$BUILD_DIR = $CONFIG_DIR . '/core/dedicated'; -Link $BUILD_DIR => '.'; -$hack = $BASE_CFLAGS; # hit me! -$BASE_CFLAGS .= '-DDEDICATED '; -Export qw( BASE_CFLAGS BUILD_DIR INSTALL_DIR CC CXX LINK ); -Build $BUILD_DIR . '/unix/Conscript-dedicated'; -$BASE_CFLAGS = $hack; - - - -$TARGETNAME = 'linuxquake3'; -$BUILD_DIR = $CONFIG_DIR . '/core/client'; -$BASE_LDFLAGS = ''; - -Link $BUILD_DIR => '.'; -Export qw( BASE_CFLAGS BASE_LDFLAGS BUILD_DIR INSTALL_DIR TARGETNAME CC CXX LINK ); -Build $BUILD_DIR . '/unix/Conscript-client'; - -if ($do_smp eq 1) -{ - $TARGETNAME = 'linuxquake3-smp'; - $BUILD_DIR = $CONFIG_DIR . '/core/client-smp'; - $BASE_CFLAGS .= '-DSMP '; - $BASE_LDFLAGS = '-lpthread '; - - Link $BUILD_DIR => '.'; - Export qw( BASE_CFLAGS BASE_LDFLAGS BUILD_DIR INSTALL_DIR TARGETNAME CC CXX LINK ); - Build $BUILD_DIR . '/unix/Conscript-client'; -} - -if ($NO_VM eq 0 && $do_pk3 eq 1) -{ - # build the PK3s - $INSTALL_DIR = $INSTALL_BASEDIR; - $BUILD_DIR = $CONFIG_DIR . '/pk3-builder'; - Link $BUILD_DIR => 'unix'; - Export qw( INSTALL_DIR BUILD_DIR CONFIG_DIR CC CXX LINK ); - Build $BUILD_DIR . '/Conscript-pk3'; -} - -if ($do_setup eq 1) -{ - Link $CONFIG_DIR => '.'; - Export qw( INSTALL_BASEDIR ); - Build $CONFIG_DIR . '/unix/Conscript-setup'; -} - -Help -" -Usage: cons [-h] [ -- [release|debug] [novm] [noso] [nosmp] [master_server=] [auth_server=] [auth_port=] [pk3] [bspc] [setup] [sdk]] -Default build type is Debug, specifying '-- release' on the -command line builds a Release version (NOTE that this option -only affects the native libraries). - -novm: will not build the VMs -noso: will not build the so - -below are for core builds only: - -nosmp : do not build the SMP-enabled version of the renderer -pk3 : generate the pk3s on the fly (defined in unix/Conscript-pk3) -bspc : build bspc -setup : build setup -sdk : build the mod sdk -" -; +# -*- mode: perl -*- +# cons script for cgame game q3_ui ui .so and .qvm builds +# +# Oct. 2001 TTimo +# + +# the top directory is +# --- +# where: +# is "debug" or "release" +# is "x86" or "ppc" +# is "Linux" "BSD" "IRIX" etc. +# is major.minor of libc config + +# source the compiler version utility +BEGIN { + push @INC, './unix'; +} +use Cons_gcc; + +# defaults +$config = 'debug'; +$do_smp = 1; +$do_masterserver = 0; +$do_authserver = 0; +$do_authport = 0; +$do_setup = 0; +$do_bspc = 0; +$do_sdk = 0; +$do_pk3 = 0; +# those are exported +$DO_WIN32 = 0; +$NO_VM = 0; +$NO_SO = 0; +$CC='gcc'; +$CXX='g++'; + +# detect an sdk build (don't attempt client build and other things) +if ( -r 'unix/Conscript-client' ) +{ + $no_core = 0; +} +else +{ + $no_core = 1; +} + +# detection of CPU type +$cpu = `uname -m`; +chop ($cpu); +if ($cpu +~ /i?86/) +{ + $cpu = 'x86'; +} +# OS +$OS = `uname`; +chop ($OS); +# hacky win32 detection and win32 specifics code +if ($OS =~ CYGWIN) +{ + $DO_WIN32 = 1; +} +else +{ + # libc .. do the little magic! + $libc_cmd = '/lib/libc.so.6 |grep "GNU C "|grep version|awk -F "version " \'{ print $2 }\'|cut -b -3'; + $libc = `$libc_cmd`; + chop ($libc); +} + +if ($DO_WIN32 eq 1) +{ + print("Win32 build\n"); + + $config = $ARGV[0]; + + # TODO: option to override $Q3BASE from command line + $Q3BASE = $ENV{Q3BASE}; # FIXME: this doesn't play nice with cygwin path syntax + print("\$Q3BASE: $Q3BASE\n"); + + if($config eq 'debug') + { + $DIR = 'Debug'; + system("cp -v $DIR/quake3.exe \$Q3BASE"); + system("cp -v $DIR/cgamex86.dll $DIR/qagamex86.dll $DIR/uix86.dll \$Q3BASE/baseq3"); + } + elsif ($config eq 'debug-TA') + { + $DIR = 'Debug_TA'; + system("cp -v $DIR/quake3.exe \$Q3BASE"); + system("cp -v $DIR/cgamex86.dll $DIR/qagamex86.dll $DIR/uix86.dll \$Q3BASE/missionpack"); + } + elsif($config eq 'release-TA') + { + $DIR = 'Release_TA'; + # spank! + system("./spank.sh"); + system("cp -v $DIR/quake3.exe \$Q3BASE"); + } + else + { + printf("ERROR: no config option (debug debug-TA release-TA)"); + exit; + } + + # copy selected stuff to shared media + $DESTDIR='/cygdrive/e/incoming/Id/q3-1.32'; + system("cp -v $DIR/quake3.exe $DESTDIR"); + system("cp -v /cygdrive/e/Q3SetupMedia/quake3/CHANGES-1.32.txt $DESTDIR"); + + exit; +} + +if(@ARGV gt 0) +{ + foreach $cmdopt (@ARGV) + { + if(lc($cmdopt) eq 'release') + { + $config = 'release'; + next; + } + elsif(lc($cmdopt) eq 'debug') + { + $config = 'debug'; + next; + } + elsif(lc($cmdopt) eq 'novm') + { + $NO_VM = 1; + next; + } + elsif(lc($cmdopt) eq 'noso') + { + $NO_SO = 1; + next; + } + elsif(lc($cmdopt) eq 'nosmp') + { + $do_smp = 0; + next; + } + elsif(lc($cmdopt) =~ 'master_server=.*') + { + $do_masterserver = 1; + $master_server = lc($cmdopt); + $master_server =~ s/master_server=(.*)/\1/; + next; + } + elsif(lc($cmdopt) =~ 'auth_server=.*') + { + $do_authserver = 1; + $auth_server = lc($cmdopt); + $auth_server =~ s/auth_server=(.*)/\1/; + next; + } + elsif(lc($cmdopt) =~ 'auth_port=.*') + { + $do_authport = 1; + $auth_port = lc($cmdopt); + $auth_port =~ s/auth_port=(.*)/\1/; + next; + } + elsif(lc($cmdopt) =~ 'setup') + { + $do_setup = 1; + next; + } + elsif(lc($cmdopt) =~ 'bspc') + { + $do_bspc = 1; + next; + } + elsif(lc($cmdopt) =~ 'sdk') + { + $do_sdk = 1; + next; + } + elsif(lc($cmdopt) =~ 'pk3') + { + $do_pk3 = 1; + next; + } + elsif(lc($cmdopt) =~ 'gcc=.*') + { + $CC=lc($cmdopt); + $CC =~ s/gcc=(.*)/\1/; + next; + } + elsif(lc($cmdopt) =~ 'g\+\+=.*') + { + $CXX=lc($cmdopt); + $CXX=~s/g\+\+=(.*)/\1/; + next; + } + else + { + # output an error & exit + print("Error\n $0: Unknown command line option: [ $cmdopt ]\n"); + system("cons -h"); + exit; + } + } +} + +if (($do_setup eq 1) && ($config ne 'release')) +{ + print("Error\n $0: 'setup' requires 'release'\n"); + exit; +} + +# sdk +if ($do_sdk eq 1) +{ + # extract the Q3 version from q_shared.h + $line = `cat game/q_shared.h | grep Q3_VERSION`; + chomp $line; + $line =~ s/.*Q3\ (.*)\"/$1/; + $Q3_VER = $line; + $SDK_NAME = "linuxq3a-sdk-$Q3_VER.x86.run"; + Default "unix/$SDK_NAME"; + Export qw( SDK_NAME Q3_VER ); + Build 'unix/Conscript-sdk'; + return; +} + +# build the config directory +$CONFIG_DIR = $config . '-' . $cpu . '-' . $OS . '-' . $libc; + +$COMMON_CFLAGS = '-pipe -fsigned-char '; + +if ($config eq 'debug') +{ + # use -Werror for better QA + $BASE_CFLAGS = $COMMON_CFLAGS . '-g -Wall -Werror -O '; + $BSPC_BASE_CFLAGS = $COMMON_CFLAGS . '-g -O -DLINUX -DBSPC -Dstricmp=strcasecmp '; +} +else +{ + $BASE_CFLAGS = $COMMON_CFLAGS . '-DNDEBUG -O6 -mcpu=pentiumpro -march=pentium -fomit-frame-pointer -ffast-math -malign-loops=2 -malign-jumps=2 -malign-functions=2 -fno-strict-aliasing -fstrength-reduce '; + $BSPC_BASE_CFLAGS = $BASE_CFLAGS . '-DLINUX -DBSPC -Dstricmp=strcasecmp '; +} + +if ($do_masterserver eq 1) +{ + $BASE_CFLAGS .= "-DMASTER_SERVER_NAME=\\\"$master_server\\\" "; +} + +if ($do_authserver eq 1) +{ + $BASE_CFLAGS .= "-DAUTHORIZE_SERVER_NAME=\\\"$auth_server\\\" "; +} + +if ($do_authport eq 1) +{ + $BASE_CFLAGS .= "-DPORT_AUTHORIZE=$auth_port "; +} + +my @gcc_version = Cons_gcc::get_gcc_version($CC); +print("GCC version: $gcc_version[1] - $gcc_version[2]\n"); +# with 2.95 you can link with gcc, this avoids nasty useless libstdc++ dependency +if ($gcc_version[0] eq '2') +{ + $LINK = $CC; +} else { + $LINK = $CXX; +} + +my @ccache = Cons_gcc::get_ccache(); +if ($ccache[0] eq '1') +{ + $CC = $ccache[1] . " " . $CC; + $CXX = $ccache[1] . " " . $CXX; +} + +print 'cpu : ' . $cpu . "\nOS : " . $OS . "\n"; +print "libc: " . $libc . "\n"; +print "configured for " . $config . " build\n"; +print 'CFLAGS: ' . $BASE_CFLAGS . "\n"; + +# install config +$INSTALL_BASEDIR='#install'; + +Default $INSTALL_BASEDIR; + +sub build_tools { + system("mkdir qvmtools 2>/dev/null"); + if (@_[0] eq 'q3lcc') + { + system("cd ../lcc ; make all ; cp /tmp/lcc ../code/qvmtools/q3lcc ; cp /tmp/rcc ../code/qvmtools/q3rcc ; cp /tmp/cpp ../code/qvmtools/q3cpp"); + } + elsif (@_[0] eq 'q3asm') + { + system("cd ../q3asm ; make ; cp q3asm ../code/qvmtools"); + } + else + { + printf("build_tools: @_[0] unrecognized command\n"); + die; + } + return 1; +} + +# build tools +$env_tools = new cons(); +Command $env_tools 'qvmtools/q3lcc', '[perl] &build_tools(\'q3lcc\')'; +Command $env_tools 'qvmtools/q3asm', '[perl] &build_tools(\'q3asm\')'; + +if ($do_bspc eq 1) +{ + # build bspc + $BUILD_DIR = $CONFIG_DIR . '/bspc'; + Link $BUILD_DIR => '.'; + $INSTALL_DIR = $INSTALL_BASEDIR . '/utils'; + Export qw( BSPC_BASE_CFLAGS BUILD_DIR INSTALL_DIR CC CXX LINK ); + Build $BUILD_DIR . '/bspc/Conscript'; +} + +# build vanilla Q3 +$TARGET_DIR='Q3'; + +$INSTALL_DIR = $INSTALL_BASEDIR . '/baseq3'; + +$BUILD_DIR = $CONFIG_DIR . '/' . $TARGET_DIR . '/cgame'; +Link $BUILD_DIR => '.'; +Export qw( BASE_CFLAGS TARGET_DIR INSTALL_DIR NO_VM NO_SO CC CXX LINK ); +Build $BUILD_DIR . '/cgame/Conscript'; + +$BUILD_DIR = $CONFIG_DIR . '/' . $TARGET_DIR . '/game'; +Link $BUILD_DIR => '.'; +Export qw( BASE_CFLAGS TARGET_DIR INSTALL_DIR NO_VM NO_SO CC CXX LINK ); +Build $BUILD_DIR . '/game/Conscript'; + +$BUILD_DIR = $CONFIG_DIR . '/' . $TARGET_DIR . '/q3_ui'; +Link $BUILD_DIR => '.'; +Export qw( BASE_CFLAGS TARGET_DIR INSTALL_DIR NO_VM NO_SO CC CXX LINK ); +Build $BUILD_DIR . '/q3_ui/Conscript'; + +# build TA +$TARGET_DIR='TA'; + +$INSTALL_DIR = $INSTALL_BASEDIR . '/missionpack'; + +$BUILD_DIR = $CONFIG_DIR . "/" . $TARGET_DIR . '/cgame'; +Link $BUILD_DIR => '.'; +Export qw( BASE_CFLAGS TARGET_DIR INSTALL_DIR NO_VM NO_SO CC CXX LINK ); +Build $BUILD_DIR . '/cgame/Conscript'; + +$BUILD_DIR = $CONFIG_DIR . "/" . $TARGET_DIR . '/game'; +Link $BUILD_DIR => '.'; +Export qw( BASE_CFLAGS TARGET_DIR INSTALL_DIR NO_VM NO_SO CC CXX LINK ); +Build $BUILD_DIR . '/game/Conscript'; + +$BUILD_DIR = $CONFIG_DIR . '/' . $TARGET_DIR . '/ui'; +Link $BUILD_DIR => '.'; +Export qw( BASE_CFLAGS TARGET_DIR INSTALL_DIR NO_VM NO_SO CC CXX LINK ); +Build $BUILD_DIR . '/ui/Conscript'; + +# core +if ($no_core eq 1) +{ + return; +} + +$INSTALL_DIR = $INSTALL_BASEDIR; +$BUILD_DIR = $CONFIG_DIR . '/core/dedicated'; +Link $BUILD_DIR => '.'; +$hack = $BASE_CFLAGS; # hit me! +$BASE_CFLAGS .= '-DDEDICATED '; +Export qw( BASE_CFLAGS BUILD_DIR INSTALL_DIR CC CXX LINK ); +Build $BUILD_DIR . '/unix/Conscript-dedicated'; +$BASE_CFLAGS = $hack; + + + +$TARGETNAME = 'linuxquake3'; +$BUILD_DIR = $CONFIG_DIR . '/core/client'; +$BASE_LDFLAGS = ''; + +Link $BUILD_DIR => '.'; +Export qw( BASE_CFLAGS BASE_LDFLAGS BUILD_DIR INSTALL_DIR TARGETNAME CC CXX LINK ); +Build $BUILD_DIR . '/unix/Conscript-client'; + +if ($do_smp eq 1) +{ + $TARGETNAME = 'linuxquake3-smp'; + $BUILD_DIR = $CONFIG_DIR . '/core/client-smp'; + $BASE_CFLAGS .= '-DSMP '; + $BASE_LDFLAGS = '-lpthread '; + + Link $BUILD_DIR => '.'; + Export qw( BASE_CFLAGS BASE_LDFLAGS BUILD_DIR INSTALL_DIR TARGETNAME CC CXX LINK ); + Build $BUILD_DIR . '/unix/Conscript-client'; +} + +if ($NO_VM eq 0 && $do_pk3 eq 1) +{ + # build the PK3s + $INSTALL_DIR = $INSTALL_BASEDIR; + $BUILD_DIR = $CONFIG_DIR . '/pk3-builder'; + Link $BUILD_DIR => 'unix'; + Export qw( INSTALL_DIR BUILD_DIR CONFIG_DIR CC CXX LINK ); + Build $BUILD_DIR . '/Conscript-pk3'; +} + +if ($do_setup eq 1) +{ + Link $CONFIG_DIR => '.'; + Export qw( INSTALL_BASEDIR ); + Build $CONFIG_DIR . '/unix/Conscript-setup'; +} + +Help +" +Usage: cons [-h] [ -- [release|debug] [novm] [noso] [nosmp] [master_server=] [auth_server=] [auth_port=] [pk3] [bspc] [setup] [sdk]] +Default build type is Debug, specifying '-- release' on the +command line builds a Release version (NOTE that this option +only affects the native libraries). + +novm: will not build the VMs +noso: will not build the so + +below are for core builds only: + +nosmp : do not build the SMP-enabled version of the renderer +pk3 : generate the pk3s on the fly (defined in unix/Conscript-pk3) +bspc : build bspc +setup : build setup +sdk : build the mod sdk +" +; diff --git a/code/Makefile b/code/Makefile index 673b3ae..5e403ae 100755 --- a/code/Makefile +++ b/code/Makefile @@ -1,3 +1,3 @@ -# nasty ugly to get build system working from Anjuta -all: - if [ `hostname` == sparkle ] ; then ./unix/pcons-2.3.1 -j4 -- novm noso ; else ./unix/cons ; fi +# nasty ugly to get build system working from Anjuta +all: + if [ `hostname` == sparkle ] ; then ./unix/pcons-2.3.1 -j4 -- novm noso ; else ./unix/cons ; fi diff --git a/code/botlib/aasfile.h b/code/botlib/aasfile.h index 5b37fbe..7fdc26a 100755 --- a/code/botlib/aasfile.h +++ b/code/botlib/aasfile.h @@ -1,267 +1,267 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - - -//NOTE: int = default signed -// default long - -#define AASID (('S'<<24)+('A'<<16)+('A'<<8)+'E') -#define AASVERSION_OLD 4 -#define AASVERSION 5 - -//presence types -#define PRESENCE_NONE 1 -#define PRESENCE_NORMAL 2 -#define PRESENCE_CROUCH 4 - -//travel types -#define MAX_TRAVELTYPES 32 -#define TRAVEL_INVALID 1 //temporary not possible -#define TRAVEL_WALK 2 //walking -#define TRAVEL_CROUCH 3 //crouching -#define TRAVEL_BARRIERJUMP 4 //jumping onto a barrier -#define TRAVEL_JUMP 5 //jumping -#define TRAVEL_LADDER 6 //climbing a ladder -#define TRAVEL_WALKOFFLEDGE 7 //walking of a ledge -#define TRAVEL_SWIM 8 //swimming -#define TRAVEL_WATERJUMP 9 //jump out of the water -#define TRAVEL_TELEPORT 10 //teleportation -#define TRAVEL_ELEVATOR 11 //travel by elevator -#define TRAVEL_ROCKETJUMP 12 //rocket jumping required for travel -#define TRAVEL_BFGJUMP 13 //bfg jumping required for travel -#define TRAVEL_GRAPPLEHOOK 14 //grappling hook required for travel -#define TRAVEL_DOUBLEJUMP 15 //double jump -#define TRAVEL_RAMPJUMP 16 //ramp jump -#define TRAVEL_STRAFEJUMP 17 //strafe jump -#define TRAVEL_JUMPPAD 18 //jump pad -#define TRAVEL_FUNCBOB 19 //func bob - -//additional travel flags -#define TRAVELTYPE_MASK 0xFFFFFF -#define TRAVELFLAG_NOTTEAM1 (1 << 24) -#define TRAVELFLAG_NOTTEAM2 (2 << 24) - -//face flags -#define FACE_SOLID 1 //just solid at the other side -#define FACE_LADDER 2 //ladder -#define FACE_GROUND 4 //standing on ground when in this face -#define FACE_GAP 8 //gap in the ground -#define FACE_LIQUID 16 //face seperating two areas with liquid -#define FACE_LIQUIDSURFACE 32 //face seperating liquid and air -#define FACE_BRIDGE 64 //can walk over this face if bridge is closed - -//area contents -#define AREACONTENTS_WATER 1 -#define AREACONTENTS_LAVA 2 -#define AREACONTENTS_SLIME 4 -#define AREACONTENTS_CLUSTERPORTAL 8 -#define AREACONTENTS_TELEPORTAL 16 -#define AREACONTENTS_ROUTEPORTAL 32 -#define AREACONTENTS_TELEPORTER 64 -#define AREACONTENTS_JUMPPAD 128 -#define AREACONTENTS_DONOTENTER 256 -#define AREACONTENTS_VIEWPORTAL 512 -#define AREACONTENTS_MOVER 1024 -#define AREACONTENTS_NOTTEAM1 2048 -#define AREACONTENTS_NOTTEAM2 4096 -//number of model of the mover inside this area -#define AREACONTENTS_MODELNUMSHIFT 24 -#define AREACONTENTS_MAXMODELNUM 0xFF -#define AREACONTENTS_MODELNUM (AREACONTENTS_MAXMODELNUM << AREACONTENTS_MODELNUMSHIFT) - -//area flags -#define AREA_GROUNDED 1 //bot can stand on the ground -#define AREA_LADDER 2 //area contains one or more ladder faces -#define AREA_LIQUID 4 //area contains a liquid -#define AREA_DISABLED 8 //area is disabled for routing when set -#define AREA_BRIDGE 16 //area ontop of a bridge - -//aas file header lumps -#define AAS_LUMPS 14 -#define AASLUMP_BBOXES 0 -#define AASLUMP_VERTEXES 1 -#define AASLUMP_PLANES 2 -#define AASLUMP_EDGES 3 -#define AASLUMP_EDGEINDEX 4 -#define AASLUMP_FACES 5 -#define AASLUMP_FACEINDEX 6 -#define AASLUMP_AREAS 7 -#define AASLUMP_AREASETTINGS 8 -#define AASLUMP_REACHABILITY 9 -#define AASLUMP_NODES 10 -#define AASLUMP_PORTALS 11 -#define AASLUMP_PORTALINDEX 12 -#define AASLUMP_CLUSTERS 13 - -//========== bounding box ========= - -//bounding box -typedef struct aas_bbox_s -{ - int presencetype; - int flags; - vec3_t mins, maxs; -} aas_bbox_t; - -//============ settings =========== - -//reachability to another area -typedef struct aas_reachability_s -{ - int areanum; //number of the reachable area - int facenum; //number of the face towards the other area - int edgenum; //number of the edge towards the other area - vec3_t start; //start point of inter area movement - vec3_t end; //end point of inter area movement - int traveltype; //type of travel required to get to the area - unsigned short int traveltime;//travel time of the inter area movement -} aas_reachability_t; - -//area settings -typedef struct aas_areasettings_s -{ - //could also add all kind of statistic fields - int contents; //contents of the area - int areaflags; //several area flags - int presencetype; //how a bot can be present in this area - int cluster; //cluster the area belongs to, if negative it's a portal - int clusterareanum; //number of the area in the cluster - int numreachableareas; //number of reachable areas from this one - int firstreachablearea; //first reachable area in the reachable area index -} aas_areasettings_t; - -//cluster portal -typedef struct aas_portal_s -{ - int areanum; //area that is the actual portal - int frontcluster; //cluster at front of portal - int backcluster; //cluster at back of portal - int clusterareanum[2]; //number of the area in the front and back cluster -} aas_portal_t; - -//cluster portal index -typedef int aas_portalindex_t; - -//cluster -typedef struct aas_cluster_s -{ - int numareas; //number of areas in the cluster - int numreachabilityareas; //number of areas with reachabilities - int numportals; //number of cluster portals - int firstportal; //first cluster portal in the index -} aas_cluster_t; - -//============ 3d definition ============ - -typedef vec3_t aas_vertex_t; - -//just a plane in the third dimension -typedef struct aas_plane_s -{ - vec3_t normal; //normal vector of the plane - float dist; //distance of the plane (normal vector * distance = point in plane) - int type; -} aas_plane_t; - -//edge -typedef struct aas_edge_s -{ - int v[2]; //numbers of the vertexes of this edge -} aas_edge_t; - -//edge index, negative if vertexes are reversed -typedef int aas_edgeindex_t; - -//a face bounds an area, often it will also seperate two areas -typedef struct aas_face_s -{ - int planenum; //number of the plane this face is in - int faceflags; //face flags (no use to create face settings for just this field) - int numedges; //number of edges in the boundary of the face - int firstedge; //first edge in the edge index - int frontarea; //area at the front of this face - int backarea; //area at the back of this face -} aas_face_t; - -//face index, stores a negative index if backside of face -typedef int aas_faceindex_t; - -//area with a boundary of faces -typedef struct aas_area_s -{ - int areanum; //number of this area - //3d definition - int numfaces; //number of faces used for the boundary of the area - int firstface; //first face in the face index used for the boundary of the area - vec3_t mins; //mins of the area - vec3_t maxs; //maxs of the area - vec3_t center; //'center' of the area -} aas_area_t; - -//nodes of the bsp tree -typedef struct aas_node_s -{ - int planenum; - int children[2]; //child nodes of this node, or areas as leaves when negative - //when a child is zero it's a solid leaf -} aas_node_t; - -//=========== aas file =============== - -//header lump -typedef struct -{ - int fileofs; - int filelen; -} aas_lump_t; - -//aas file header -typedef struct aas_header_s -{ - int ident; - int version; - int bspchecksum; - //data entries - aas_lump_t lumps[AAS_LUMPS]; -} aas_header_t; - - -//====== additional information ====== -/* - -- when a node child is a solid leaf the node child number is zero -- two adjacent areas (sharing a plane at opposite sides) share a face - this face is a portal between the areas -- when an area uses a face from the faceindex with a positive index - then the face plane normal points into the area -- the face edges are stored counter clockwise using the edgeindex -- two adjacent convex areas (sharing a face) only share One face - this is a simple result of the areas being convex -- the areas can't have a mixture of ground and gap faces - other mixtures of faces in one area are allowed -- areas with the AREACONTENTS_CLUSTERPORTAL in the settings have - the cluster number set to the negative portal number -- edge zero is a dummy -- face zero is a dummy -- area zero is a dummy -- node zero is a dummy -*/ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + + +//NOTE: int = default signed +// default long + +#define AASID (('S'<<24)+('A'<<16)+('A'<<8)+'E') +#define AASVERSION_OLD 4 +#define AASVERSION 5 + +//presence types +#define PRESENCE_NONE 1 +#define PRESENCE_NORMAL 2 +#define PRESENCE_CROUCH 4 + +//travel types +#define MAX_TRAVELTYPES 32 +#define TRAVEL_INVALID 1 //temporary not possible +#define TRAVEL_WALK 2 //walking +#define TRAVEL_CROUCH 3 //crouching +#define TRAVEL_BARRIERJUMP 4 //jumping onto a barrier +#define TRAVEL_JUMP 5 //jumping +#define TRAVEL_LADDER 6 //climbing a ladder +#define TRAVEL_WALKOFFLEDGE 7 //walking of a ledge +#define TRAVEL_SWIM 8 //swimming +#define TRAVEL_WATERJUMP 9 //jump out of the water +#define TRAVEL_TELEPORT 10 //teleportation +#define TRAVEL_ELEVATOR 11 //travel by elevator +#define TRAVEL_ROCKETJUMP 12 //rocket jumping required for travel +#define TRAVEL_BFGJUMP 13 //bfg jumping required for travel +#define TRAVEL_GRAPPLEHOOK 14 //grappling hook required for travel +#define TRAVEL_DOUBLEJUMP 15 //double jump +#define TRAVEL_RAMPJUMP 16 //ramp jump +#define TRAVEL_STRAFEJUMP 17 //strafe jump +#define TRAVEL_JUMPPAD 18 //jump pad +#define TRAVEL_FUNCBOB 19 //func bob + +//additional travel flags +#define TRAVELTYPE_MASK 0xFFFFFF +#define TRAVELFLAG_NOTTEAM1 (1 << 24) +#define TRAVELFLAG_NOTTEAM2 (2 << 24) + +//face flags +#define FACE_SOLID 1 //just solid at the other side +#define FACE_LADDER 2 //ladder +#define FACE_GROUND 4 //standing on ground when in this face +#define FACE_GAP 8 //gap in the ground +#define FACE_LIQUID 16 //face seperating two areas with liquid +#define FACE_LIQUIDSURFACE 32 //face seperating liquid and air +#define FACE_BRIDGE 64 //can walk over this face if bridge is closed + +//area contents +#define AREACONTENTS_WATER 1 +#define AREACONTENTS_LAVA 2 +#define AREACONTENTS_SLIME 4 +#define AREACONTENTS_CLUSTERPORTAL 8 +#define AREACONTENTS_TELEPORTAL 16 +#define AREACONTENTS_ROUTEPORTAL 32 +#define AREACONTENTS_TELEPORTER 64 +#define AREACONTENTS_JUMPPAD 128 +#define AREACONTENTS_DONOTENTER 256 +#define AREACONTENTS_VIEWPORTAL 512 +#define AREACONTENTS_MOVER 1024 +#define AREACONTENTS_NOTTEAM1 2048 +#define AREACONTENTS_NOTTEAM2 4096 +//number of model of the mover inside this area +#define AREACONTENTS_MODELNUMSHIFT 24 +#define AREACONTENTS_MAXMODELNUM 0xFF +#define AREACONTENTS_MODELNUM (AREACONTENTS_MAXMODELNUM << AREACONTENTS_MODELNUMSHIFT) + +//area flags +#define AREA_GROUNDED 1 //bot can stand on the ground +#define AREA_LADDER 2 //area contains one or more ladder faces +#define AREA_LIQUID 4 //area contains a liquid +#define AREA_DISABLED 8 //area is disabled for routing when set +#define AREA_BRIDGE 16 //area ontop of a bridge + +//aas file header lumps +#define AAS_LUMPS 14 +#define AASLUMP_BBOXES 0 +#define AASLUMP_VERTEXES 1 +#define AASLUMP_PLANES 2 +#define AASLUMP_EDGES 3 +#define AASLUMP_EDGEINDEX 4 +#define AASLUMP_FACES 5 +#define AASLUMP_FACEINDEX 6 +#define AASLUMP_AREAS 7 +#define AASLUMP_AREASETTINGS 8 +#define AASLUMP_REACHABILITY 9 +#define AASLUMP_NODES 10 +#define AASLUMP_PORTALS 11 +#define AASLUMP_PORTALINDEX 12 +#define AASLUMP_CLUSTERS 13 + +//========== bounding box ========= + +//bounding box +typedef struct aas_bbox_s +{ + int presencetype; + int flags; + vec3_t mins, maxs; +} aas_bbox_t; + +//============ settings =========== + +//reachability to another area +typedef struct aas_reachability_s +{ + int areanum; //number of the reachable area + int facenum; //number of the face towards the other area + int edgenum; //number of the edge towards the other area + vec3_t start; //start point of inter area movement + vec3_t end; //end point of inter area movement + int traveltype; //type of travel required to get to the area + unsigned short int traveltime;//travel time of the inter area movement +} aas_reachability_t; + +//area settings +typedef struct aas_areasettings_s +{ + //could also add all kind of statistic fields + int contents; //contents of the area + int areaflags; //several area flags + int presencetype; //how a bot can be present in this area + int cluster; //cluster the area belongs to, if negative it's a portal + int clusterareanum; //number of the area in the cluster + int numreachableareas; //number of reachable areas from this one + int firstreachablearea; //first reachable area in the reachable area index +} aas_areasettings_t; + +//cluster portal +typedef struct aas_portal_s +{ + int areanum; //area that is the actual portal + int frontcluster; //cluster at front of portal + int backcluster; //cluster at back of portal + int clusterareanum[2]; //number of the area in the front and back cluster +} aas_portal_t; + +//cluster portal index +typedef int aas_portalindex_t; + +//cluster +typedef struct aas_cluster_s +{ + int numareas; //number of areas in the cluster + int numreachabilityareas; //number of areas with reachabilities + int numportals; //number of cluster portals + int firstportal; //first cluster portal in the index +} aas_cluster_t; + +//============ 3d definition ============ + +typedef vec3_t aas_vertex_t; + +//just a plane in the third dimension +typedef struct aas_plane_s +{ + vec3_t normal; //normal vector of the plane + float dist; //distance of the plane (normal vector * distance = point in plane) + int type; +} aas_plane_t; + +//edge +typedef struct aas_edge_s +{ + int v[2]; //numbers of the vertexes of this edge +} aas_edge_t; + +//edge index, negative if vertexes are reversed +typedef int aas_edgeindex_t; + +//a face bounds an area, often it will also seperate two areas +typedef struct aas_face_s +{ + int planenum; //number of the plane this face is in + int faceflags; //face flags (no use to create face settings for just this field) + int numedges; //number of edges in the boundary of the face + int firstedge; //first edge in the edge index + int frontarea; //area at the front of this face + int backarea; //area at the back of this face +} aas_face_t; + +//face index, stores a negative index if backside of face +typedef int aas_faceindex_t; + +//area with a boundary of faces +typedef struct aas_area_s +{ + int areanum; //number of this area + //3d definition + int numfaces; //number of faces used for the boundary of the area + int firstface; //first face in the face index used for the boundary of the area + vec3_t mins; //mins of the area + vec3_t maxs; //maxs of the area + vec3_t center; //'center' of the area +} aas_area_t; + +//nodes of the bsp tree +typedef struct aas_node_s +{ + int planenum; + int children[2]; //child nodes of this node, or areas as leaves when negative + //when a child is zero it's a solid leaf +} aas_node_t; + +//=========== aas file =============== + +//header lump +typedef struct +{ + int fileofs; + int filelen; +} aas_lump_t; + +//aas file header +typedef struct aas_header_s +{ + int ident; + int version; + int bspchecksum; + //data entries + aas_lump_t lumps[AAS_LUMPS]; +} aas_header_t; + + +//====== additional information ====== +/* + +- when a node child is a solid leaf the node child number is zero +- two adjacent areas (sharing a plane at opposite sides) share a face + this face is a portal between the areas +- when an area uses a face from the faceindex with a positive index + then the face plane normal points into the area +- the face edges are stored counter clockwise using the edgeindex +- two adjacent convex areas (sharing a face) only share One face + this is a simple result of the areas being convex +- the areas can't have a mixture of ground and gap faces + other mixtures of faces in one area are allowed +- areas with the AREACONTENTS_CLUSTERPORTAL in the settings have + the cluster number set to the negative portal number +- edge zero is a dummy +- face zero is a dummy +- area zero is a dummy +- node zero is a dummy +*/ diff --git a/code/botlib/be_aas_bsp.h b/code/botlib/be_aas_bsp.h index d55353c..aa05e4b 100755 --- a/code/botlib/be_aas_bsp.h +++ b/code/botlib/be_aas_bsp.h @@ -1,89 +1,89 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: be_aas_bsp.h - * - * desc: AAS - * - * $Archive: /source/code/botlib/be_aas_bsp.h $ - * - *****************************************************************************/ - -#ifdef AASINTERN -//loads the given BSP file -int AAS_LoadBSPFile(void); -//dump the loaded BSP data -void AAS_DumpBSPData(void); -//unlink the given entity from the bsp tree leaves -void AAS_UnlinkFromBSPLeaves(bsp_link_t *leaves); -//link the given entity to the bsp tree leaves of the given model -bsp_link_t *AAS_BSPLinkEntity(vec3_t absmins, - vec3_t absmaxs, - int entnum, - int modelnum); - -//calculates collision with given entity -qboolean AAS_EntityCollision(int entnum, - vec3_t start, - vec3_t boxmins, - vec3_t boxmaxs, - vec3_t end, - int contentmask, - bsp_trace_t *trace); -//for debugging -void AAS_PrintFreeBSPLinks(char *str); -// -#endif //AASINTERN - -#define MAX_EPAIRKEY 128 - -//trace through the world -bsp_trace_t AAS_Trace( vec3_t start, - vec3_t mins, - vec3_t maxs, - vec3_t end, - int passent, - int contentmask); -//returns the contents at the given point -int AAS_PointContents(vec3_t point); -//returns true when p2 is in the PVS of p1 -qboolean AAS_inPVS(vec3_t p1, vec3_t p2); -//returns true when p2 is in the PHS of p1 -qboolean AAS_inPHS(vec3_t p1, vec3_t p2); -//returns true if the given areas are connected -qboolean AAS_AreasConnected(int area1, int area2); -//creates a list with entities totally or partly within the given box -int AAS_BoxEntities(vec3_t absmins, vec3_t absmaxs, int *list, int maxcount); -//gets the mins, maxs and origin of a BSP model -void AAS_BSPModelMinsMaxsOrigin(int modelnum, vec3_t angles, vec3_t mins, vec3_t maxs, vec3_t origin); -//handle to the next bsp entity -int AAS_NextBSPEntity(int ent); -//return the value of the BSP epair key -int AAS_ValueForBSPEpairKey(int ent, char *key, char *value, int size); -//get a vector for the BSP epair key -int AAS_VectorForBSPEpairKey(int ent, char *key, vec3_t v); -//get a float for the BSP epair key -int AAS_FloatForBSPEpairKey(int ent, char *key, float *value); -//get an integer for the BSP epair key -int AAS_IntForBSPEpairKey(int ent, char *key, int *value); - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_bsp.h + * + * desc: AAS + * + * $Archive: /source/code/botlib/be_aas_bsp.h $ + * + *****************************************************************************/ + +#ifdef AASINTERN +//loads the given BSP file +int AAS_LoadBSPFile(void); +//dump the loaded BSP data +void AAS_DumpBSPData(void); +//unlink the given entity from the bsp tree leaves +void AAS_UnlinkFromBSPLeaves(bsp_link_t *leaves); +//link the given entity to the bsp tree leaves of the given model +bsp_link_t *AAS_BSPLinkEntity(vec3_t absmins, + vec3_t absmaxs, + int entnum, + int modelnum); + +//calculates collision with given entity +qboolean AAS_EntityCollision(int entnum, + vec3_t start, + vec3_t boxmins, + vec3_t boxmaxs, + vec3_t end, + int contentmask, + bsp_trace_t *trace); +//for debugging +void AAS_PrintFreeBSPLinks(char *str); +// +#endif //AASINTERN + +#define MAX_EPAIRKEY 128 + +//trace through the world +bsp_trace_t AAS_Trace( vec3_t start, + vec3_t mins, + vec3_t maxs, + vec3_t end, + int passent, + int contentmask); +//returns the contents at the given point +int AAS_PointContents(vec3_t point); +//returns true when p2 is in the PVS of p1 +qboolean AAS_inPVS(vec3_t p1, vec3_t p2); +//returns true when p2 is in the PHS of p1 +qboolean AAS_inPHS(vec3_t p1, vec3_t p2); +//returns true if the given areas are connected +qboolean AAS_AreasConnected(int area1, int area2); +//creates a list with entities totally or partly within the given box +int AAS_BoxEntities(vec3_t absmins, vec3_t absmaxs, int *list, int maxcount); +//gets the mins, maxs and origin of a BSP model +void AAS_BSPModelMinsMaxsOrigin(int modelnum, vec3_t angles, vec3_t mins, vec3_t maxs, vec3_t origin); +//handle to the next bsp entity +int AAS_NextBSPEntity(int ent); +//return the value of the BSP epair key +int AAS_ValueForBSPEpairKey(int ent, char *key, char *value, int size); +//get a vector for the BSP epair key +int AAS_VectorForBSPEpairKey(int ent, char *key, vec3_t v); +//get a float for the BSP epair key +int AAS_FloatForBSPEpairKey(int ent, char *key, float *value); +//get an integer for the BSP epair key +int AAS_IntForBSPEpairKey(int ent, char *key, int *value); + diff --git a/code/botlib/be_aas_bspq3.c b/code/botlib/be_aas_bspq3.c index a5e0d44..97c286d 100755 --- a/code/botlib/be_aas_bspq3.c +++ b/code/botlib/be_aas_bspq3.c @@ -1,487 +1,487 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: be_aas_bspq3.c - * - * desc: BSP, Environment Sampling - * - * $Archive: /MissionPack/code/botlib/be_aas_bspq3.c $ - * - *****************************************************************************/ - -#include "../game/q_shared.h" -#include "l_memory.h" -#include "l_script.h" -#include "l_precomp.h" -#include "l_struct.h" -#include "aasfile.h" -#include "../game/botlib.h" -#include "../game/be_aas.h" -#include "be_aas_funcs.h" -#include "be_aas_def.h" - -extern botlib_import_t botimport; - -//#define TRACE_DEBUG - -#define ON_EPSILON 0.005 -//#define DEG2RAD( a ) (( a * M_PI ) / 180.0F) - -#define MAX_BSPENTITIES 2048 - -typedef struct rgb_s -{ - int red; - int green; - int blue; -} rgb_t; - -//bsp entity epair -typedef struct bsp_epair_s -{ - char *key; - char *value; - struct bsp_epair_s *next; -} bsp_epair_t; - -//bsp data entity -typedef struct bsp_entity_s -{ - bsp_epair_t *epairs; -} bsp_entity_t; - -//id Sofware BSP data -typedef struct bsp_s -{ - //true when bsp file is loaded - int loaded; - //entity data - int entdatasize; - char *dentdata; - //bsp entities - int numentities; - bsp_entity_t entities[MAX_BSPENTITIES]; -} bsp_t; - -//global bsp -bsp_t bspworld; - - -#ifdef BSP_DEBUG -typedef struct cname_s -{ - int value; - char *name; -} cname_t; - -cname_t contentnames[] = -{ - {CONTENTS_SOLID,"CONTENTS_SOLID"}, - {CONTENTS_WINDOW,"CONTENTS_WINDOW"}, - {CONTENTS_AUX,"CONTENTS_AUX"}, - {CONTENTS_LAVA,"CONTENTS_LAVA"}, - {CONTENTS_SLIME,"CONTENTS_SLIME"}, - {CONTENTS_WATER,"CONTENTS_WATER"}, - {CONTENTS_MIST,"CONTENTS_MIST"}, - {LAST_VISIBLE_CONTENTS,"LAST_VISIBLE_CONTENTS"}, - - {CONTENTS_AREAPORTAL,"CONTENTS_AREAPORTAL"}, - {CONTENTS_PLAYERCLIP,"CONTENTS_PLAYERCLIP"}, - {CONTENTS_MONSTERCLIP,"CONTENTS_MONSTERCLIP"}, - {CONTENTS_CURRENT_0,"CONTENTS_CURRENT_0"}, - {CONTENTS_CURRENT_90,"CONTENTS_CURRENT_90"}, - {CONTENTS_CURRENT_180,"CONTENTS_CURRENT_180"}, - {CONTENTS_CURRENT_270,"CONTENTS_CURRENT_270"}, - {CONTENTS_CURRENT_UP,"CONTENTS_CURRENT_UP"}, - {CONTENTS_CURRENT_DOWN,"CONTENTS_CURRENT_DOWN"}, - {CONTENTS_ORIGIN,"CONTENTS_ORIGIN"}, - {CONTENTS_MONSTER,"CONTENTS_MONSTER"}, - {CONTENTS_DEADMONSTER,"CONTENTS_DEADMONSTER"}, - {CONTENTS_DETAIL,"CONTENTS_DETAIL"}, - {CONTENTS_TRANSLUCENT,"CONTENTS_TRANSLUCENT"}, - {CONTENTS_LADDER,"CONTENTS_LADDER"}, - {0, 0} -}; - -void PrintContents(int contents) -{ - int i; - - for (i = 0; contentnames[i].value; i++) - { - if (contents & contentnames[i].value) - { - botimport.Print(PRT_MESSAGE, "%s\n", contentnames[i].name); - } //end if - } //end for -} //end of the function PrintContents - -#endif // BSP_DEBUG -//=========================================================================== -// traces axial boxes of any size through the world -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bsp_trace_t AAS_Trace(vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask) -{ - bsp_trace_t bsptrace; - botimport.Trace(&bsptrace, start, mins, maxs, end, passent, contentmask); - return bsptrace; -} //end of the function AAS_Trace -//=========================================================================== -// returns the contents at the given point -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_PointContents(vec3_t point) -{ - return botimport.PointContents(point); -} //end of the function AAS_PointContents -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean AAS_EntityCollision(int entnum, - vec3_t start, vec3_t boxmins, vec3_t boxmaxs, vec3_t end, - int contentmask, bsp_trace_t *trace) -{ - bsp_trace_t enttrace; - - botimport.EntityTrace(&enttrace, start, boxmins, boxmaxs, end, entnum, contentmask); - if (enttrace.fraction < trace->fraction) - { - Com_Memcpy(trace, &enttrace, sizeof(bsp_trace_t)); - return qtrue; - } //end if - return qfalse; -} //end of the function AAS_EntityCollision -//=========================================================================== -// returns true if in Potentially Hearable Set -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean AAS_inPVS(vec3_t p1, vec3_t p2) -{ - return botimport.inPVS(p1, p2); -} //end of the function AAS_InPVS -//=========================================================================== -// returns true if in Potentially Visible Set -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean AAS_inPHS(vec3_t p1, vec3_t p2) -{ - return qtrue; -} //end of the function AAS_inPHS -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_BSPModelMinsMaxsOrigin(int modelnum, vec3_t angles, vec3_t mins, vec3_t maxs, vec3_t origin) -{ - botimport.BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, origin); -} //end of the function AAS_BSPModelMinsMaxs -//=========================================================================== -// unlinks the entity from all leaves -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_UnlinkFromBSPLeaves(bsp_link_t *leaves) -{ -} //end of the function AAS_UnlinkFromBSPLeaves -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bsp_link_t *AAS_BSPLinkEntity(vec3_t absmins, vec3_t absmaxs, int entnum, int modelnum) -{ - return NULL; -} //end of the function AAS_BSPLinkEntity -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_BoxEntities(vec3_t absmins, vec3_t absmaxs, int *list, int maxcount) -{ - return 0; -} //end of the function AAS_BoxEntities -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_NextBSPEntity(int ent) -{ - ent++; - if (ent >= 1 && ent < bspworld.numentities) return ent; - return 0; -} //end of the function AAS_NextBSPEntity -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_BSPEntityInRange(int ent) -{ - if (ent <= 0 || ent >= bspworld.numentities) - { - botimport.Print(PRT_MESSAGE, "bsp entity out of range\n"); - return qfalse; - } //end if - return qtrue; -} //end of the function AAS_BSPEntityInRange -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_ValueForBSPEpairKey(int ent, char *key, char *value, int size) -{ - bsp_epair_t *epair; - - value[0] = '\0'; - if (!AAS_BSPEntityInRange(ent)) return qfalse; - for (epair = bspworld.entities[ent].epairs; epair; epair = epair->next) - { - if (!strcmp(epair->key, key)) - { - strncpy(value, epair->value, size-1); - value[size-1] = '\0'; - return qtrue; - } //end if - } //end for - return qfalse; -} //end of the function AAS_FindBSPEpair -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_VectorForBSPEpairKey(int ent, char *key, vec3_t v) -{ - char buf[MAX_EPAIRKEY]; - double v1, v2, v3; - - VectorClear(v); - if (!AAS_ValueForBSPEpairKey(ent, key, buf, MAX_EPAIRKEY)) return qfalse; - //scanf into doubles, then assign, so it is vec_t size independent - v1 = v2 = v3 = 0; - sscanf(buf, "%lf %lf %lf", &v1, &v2, &v3); - v[0] = v1; - v[1] = v2; - v[2] = v3; - return qtrue; -} //end of the function AAS_VectorForBSPEpairKey -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_FloatForBSPEpairKey(int ent, char *key, float *value) -{ - char buf[MAX_EPAIRKEY]; - - *value = 0; - if (!AAS_ValueForBSPEpairKey(ent, key, buf, MAX_EPAIRKEY)) return qfalse; - *value = atof(buf); - return qtrue; -} //end of the function AAS_FloatForBSPEpairKey -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_IntForBSPEpairKey(int ent, char *key, int *value) -{ - char buf[MAX_EPAIRKEY]; - - *value = 0; - if (!AAS_ValueForBSPEpairKey(ent, key, buf, MAX_EPAIRKEY)) return qfalse; - *value = atoi(buf); - return qtrue; -} //end of the function AAS_IntForBSPEpairKey -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_FreeBSPEntities(void) -{ - int i; - bsp_entity_t *ent; - bsp_epair_t *epair, *nextepair; - - for (i = 1; i < bspworld.numentities; i++) - { - ent = &bspworld.entities[i]; - for (epair = ent->epairs; epair; epair = nextepair) - { - nextepair = epair->next; - // - if (epair->key) FreeMemory(epair->key); - if (epair->value) FreeMemory(epair->value); - FreeMemory(epair); - } //end for - } //end for - bspworld.numentities = 0; -} //end of the function AAS_FreeBSPEntities -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_ParseBSPEntities(void) -{ - script_t *script; - token_t token; - bsp_entity_t *ent; - bsp_epair_t *epair; - - script = LoadScriptMemory(bspworld.dentdata, bspworld.entdatasize, "entdata"); - SetScriptFlags(script, SCFL_NOSTRINGWHITESPACES|SCFL_NOSTRINGESCAPECHARS);//SCFL_PRIMITIVE); - - bspworld.numentities = 1; - - while(PS_ReadToken(script, &token)) - { - if (strcmp(token.string, "{")) - { - ScriptError(script, "invalid %s\n", token.string); - AAS_FreeBSPEntities(); - FreeScript(script); - return; - } //end if - if (bspworld.numentities >= MAX_BSPENTITIES) - { - botimport.Print(PRT_MESSAGE, "too many entities in BSP file\n"); - break; - } //end if - ent = &bspworld.entities[bspworld.numentities]; - bspworld.numentities++; - ent->epairs = NULL; - while(PS_ReadToken(script, &token)) - { - if (!strcmp(token.string, "}")) break; - epair = (bsp_epair_t *) GetClearedHunkMemory(sizeof(bsp_epair_t)); - epair->next = ent->epairs; - ent->epairs = epair; - if (token.type != TT_STRING) - { - ScriptError(script, "invalid %s\n", token.string); - AAS_FreeBSPEntities(); - FreeScript(script); - return; - } //end if - StripDoubleQuotes(token.string); - epair->key = (char *) GetHunkMemory(strlen(token.string) + 1); - strcpy(epair->key, token.string); - if (!PS_ExpectTokenType(script, TT_STRING, 0, &token)) - { - AAS_FreeBSPEntities(); - FreeScript(script); - return; - } //end if - StripDoubleQuotes(token.string); - epair->value = (char *) GetHunkMemory(strlen(token.string) + 1); - strcpy(epair->value, token.string); - } //end while - if (strcmp(token.string, "}")) - { - ScriptError(script, "missing }\n"); - AAS_FreeBSPEntities(); - FreeScript(script); - return; - } //end if - } //end while - FreeScript(script); -} //end of the function AAS_ParseBSPEntities -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_BSPTraceLight(vec3_t start, vec3_t end, vec3_t endpos, int *red, int *green, int *blue) -{ - return 0; -} //end of the function AAS_BSPTraceLight -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_DumpBSPData(void) -{ - AAS_FreeBSPEntities(); - - if (bspworld.dentdata) FreeMemory(bspworld.dentdata); - bspworld.dentdata = NULL; - bspworld.entdatasize = 0; - // - bspworld.loaded = qfalse; - Com_Memset( &bspworld, 0, sizeof(bspworld) ); -} //end of the function AAS_DumpBSPData -//=========================================================================== -// load an bsp file -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_LoadBSPFile(void) -{ - AAS_DumpBSPData(); - bspworld.entdatasize = strlen(botimport.BSPEntityData()) + 1; - bspworld.dentdata = (char *) GetClearedHunkMemory(bspworld.entdatasize); - Com_Memcpy(bspworld.dentdata, botimport.BSPEntityData(), bspworld.entdatasize); - AAS_ParseBSPEntities(); - bspworld.loaded = qtrue; - return BLERR_NOERROR; -} //end of the function AAS_LoadBSPFile +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_bspq3.c + * + * desc: BSP, Environment Sampling + * + * $Archive: /MissionPack/code/botlib/be_aas_bspq3.c $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_aas_def.h" + +extern botlib_import_t botimport; + +//#define TRACE_DEBUG + +#define ON_EPSILON 0.005 +//#define DEG2RAD( a ) (( a * M_PI ) / 180.0F) + +#define MAX_BSPENTITIES 2048 + +typedef struct rgb_s +{ + int red; + int green; + int blue; +} rgb_t; + +//bsp entity epair +typedef struct bsp_epair_s +{ + char *key; + char *value; + struct bsp_epair_s *next; +} bsp_epair_t; + +//bsp data entity +typedef struct bsp_entity_s +{ + bsp_epair_t *epairs; +} bsp_entity_t; + +//id Sofware BSP data +typedef struct bsp_s +{ + //true when bsp file is loaded + int loaded; + //entity data + int entdatasize; + char *dentdata; + //bsp entities + int numentities; + bsp_entity_t entities[MAX_BSPENTITIES]; +} bsp_t; + +//global bsp +bsp_t bspworld; + + +#ifdef BSP_DEBUG +typedef struct cname_s +{ + int value; + char *name; +} cname_t; + +cname_t contentnames[] = +{ + {CONTENTS_SOLID,"CONTENTS_SOLID"}, + {CONTENTS_WINDOW,"CONTENTS_WINDOW"}, + {CONTENTS_AUX,"CONTENTS_AUX"}, + {CONTENTS_LAVA,"CONTENTS_LAVA"}, + {CONTENTS_SLIME,"CONTENTS_SLIME"}, + {CONTENTS_WATER,"CONTENTS_WATER"}, + {CONTENTS_MIST,"CONTENTS_MIST"}, + {LAST_VISIBLE_CONTENTS,"LAST_VISIBLE_CONTENTS"}, + + {CONTENTS_AREAPORTAL,"CONTENTS_AREAPORTAL"}, + {CONTENTS_PLAYERCLIP,"CONTENTS_PLAYERCLIP"}, + {CONTENTS_MONSTERCLIP,"CONTENTS_MONSTERCLIP"}, + {CONTENTS_CURRENT_0,"CONTENTS_CURRENT_0"}, + {CONTENTS_CURRENT_90,"CONTENTS_CURRENT_90"}, + {CONTENTS_CURRENT_180,"CONTENTS_CURRENT_180"}, + {CONTENTS_CURRENT_270,"CONTENTS_CURRENT_270"}, + {CONTENTS_CURRENT_UP,"CONTENTS_CURRENT_UP"}, + {CONTENTS_CURRENT_DOWN,"CONTENTS_CURRENT_DOWN"}, + {CONTENTS_ORIGIN,"CONTENTS_ORIGIN"}, + {CONTENTS_MONSTER,"CONTENTS_MONSTER"}, + {CONTENTS_DEADMONSTER,"CONTENTS_DEADMONSTER"}, + {CONTENTS_DETAIL,"CONTENTS_DETAIL"}, + {CONTENTS_TRANSLUCENT,"CONTENTS_TRANSLUCENT"}, + {CONTENTS_LADDER,"CONTENTS_LADDER"}, + {0, 0} +}; + +void PrintContents(int contents) +{ + int i; + + for (i = 0; contentnames[i].value; i++) + { + if (contents & contentnames[i].value) + { + botimport.Print(PRT_MESSAGE, "%s\n", contentnames[i].name); + } //end if + } //end for +} //end of the function PrintContents + +#endif // BSP_DEBUG +//=========================================================================== +// traces axial boxes of any size through the world +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bsp_trace_t AAS_Trace(vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask) +{ + bsp_trace_t bsptrace; + botimport.Trace(&bsptrace, start, mins, maxs, end, passent, contentmask); + return bsptrace; +} //end of the function AAS_Trace +//=========================================================================== +// returns the contents at the given point +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_PointContents(vec3_t point) +{ + return botimport.PointContents(point); +} //end of the function AAS_PointContents +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_EntityCollision(int entnum, + vec3_t start, vec3_t boxmins, vec3_t boxmaxs, vec3_t end, + int contentmask, bsp_trace_t *trace) +{ + bsp_trace_t enttrace; + + botimport.EntityTrace(&enttrace, start, boxmins, boxmaxs, end, entnum, contentmask); + if (enttrace.fraction < trace->fraction) + { + Com_Memcpy(trace, &enttrace, sizeof(bsp_trace_t)); + return qtrue; + } //end if + return qfalse; +} //end of the function AAS_EntityCollision +//=========================================================================== +// returns true if in Potentially Hearable Set +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_inPVS(vec3_t p1, vec3_t p2) +{ + return botimport.inPVS(p1, p2); +} //end of the function AAS_InPVS +//=========================================================================== +// returns true if in Potentially Visible Set +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_inPHS(vec3_t p1, vec3_t p2) +{ + return qtrue; +} //end of the function AAS_inPHS +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_BSPModelMinsMaxsOrigin(int modelnum, vec3_t angles, vec3_t mins, vec3_t maxs, vec3_t origin) +{ + botimport.BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, origin); +} //end of the function AAS_BSPModelMinsMaxs +//=========================================================================== +// unlinks the entity from all leaves +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_UnlinkFromBSPLeaves(bsp_link_t *leaves) +{ +} //end of the function AAS_UnlinkFromBSPLeaves +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bsp_link_t *AAS_BSPLinkEntity(vec3_t absmins, vec3_t absmaxs, int entnum, int modelnum) +{ + return NULL; +} //end of the function AAS_BSPLinkEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BoxEntities(vec3_t absmins, vec3_t absmaxs, int *list, int maxcount) +{ + return 0; +} //end of the function AAS_BoxEntities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_NextBSPEntity(int ent) +{ + ent++; + if (ent >= 1 && ent < bspworld.numentities) return ent; + return 0; +} //end of the function AAS_NextBSPEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BSPEntityInRange(int ent) +{ + if (ent <= 0 || ent >= bspworld.numentities) + { + botimport.Print(PRT_MESSAGE, "bsp entity out of range\n"); + return qfalse; + } //end if + return qtrue; +} //end of the function AAS_BSPEntityInRange +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_ValueForBSPEpairKey(int ent, char *key, char *value, int size) +{ + bsp_epair_t *epair; + + value[0] = '\0'; + if (!AAS_BSPEntityInRange(ent)) return qfalse; + for (epair = bspworld.entities[ent].epairs; epair; epair = epair->next) + { + if (!strcmp(epair->key, key)) + { + strncpy(value, epair->value, size-1); + value[size-1] = '\0'; + return qtrue; + } //end if + } //end for + return qfalse; +} //end of the function AAS_FindBSPEpair +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_VectorForBSPEpairKey(int ent, char *key, vec3_t v) +{ + char buf[MAX_EPAIRKEY]; + double v1, v2, v3; + + VectorClear(v); + if (!AAS_ValueForBSPEpairKey(ent, key, buf, MAX_EPAIRKEY)) return qfalse; + //scanf into doubles, then assign, so it is vec_t size independent + v1 = v2 = v3 = 0; + sscanf(buf, "%lf %lf %lf", &v1, &v2, &v3); + v[0] = v1; + v[1] = v2; + v[2] = v3; + return qtrue; +} //end of the function AAS_VectorForBSPEpairKey +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_FloatForBSPEpairKey(int ent, char *key, float *value) +{ + char buf[MAX_EPAIRKEY]; + + *value = 0; + if (!AAS_ValueForBSPEpairKey(ent, key, buf, MAX_EPAIRKEY)) return qfalse; + *value = atof(buf); + return qtrue; +} //end of the function AAS_FloatForBSPEpairKey +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_IntForBSPEpairKey(int ent, char *key, int *value) +{ + char buf[MAX_EPAIRKEY]; + + *value = 0; + if (!AAS_ValueForBSPEpairKey(ent, key, buf, MAX_EPAIRKEY)) return qfalse; + *value = atoi(buf); + return qtrue; +} //end of the function AAS_IntForBSPEpairKey +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeBSPEntities(void) +{ + int i; + bsp_entity_t *ent; + bsp_epair_t *epair, *nextepair; + + for (i = 1; i < bspworld.numentities; i++) + { + ent = &bspworld.entities[i]; + for (epair = ent->epairs; epair; epair = nextepair) + { + nextepair = epair->next; + // + if (epair->key) FreeMemory(epair->key); + if (epair->value) FreeMemory(epair->value); + FreeMemory(epair); + } //end for + } //end for + bspworld.numentities = 0; +} //end of the function AAS_FreeBSPEntities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ParseBSPEntities(void) +{ + script_t *script; + token_t token; + bsp_entity_t *ent; + bsp_epair_t *epair; + + script = LoadScriptMemory(bspworld.dentdata, bspworld.entdatasize, "entdata"); + SetScriptFlags(script, SCFL_NOSTRINGWHITESPACES|SCFL_NOSTRINGESCAPECHARS);//SCFL_PRIMITIVE); + + bspworld.numentities = 1; + + while(PS_ReadToken(script, &token)) + { + if (strcmp(token.string, "{")) + { + ScriptError(script, "invalid %s\n", token.string); + AAS_FreeBSPEntities(); + FreeScript(script); + return; + } //end if + if (bspworld.numentities >= MAX_BSPENTITIES) + { + botimport.Print(PRT_MESSAGE, "too many entities in BSP file\n"); + break; + } //end if + ent = &bspworld.entities[bspworld.numentities]; + bspworld.numentities++; + ent->epairs = NULL; + while(PS_ReadToken(script, &token)) + { + if (!strcmp(token.string, "}")) break; + epair = (bsp_epair_t *) GetClearedHunkMemory(sizeof(bsp_epair_t)); + epair->next = ent->epairs; + ent->epairs = epair; + if (token.type != TT_STRING) + { + ScriptError(script, "invalid %s\n", token.string); + AAS_FreeBSPEntities(); + FreeScript(script); + return; + } //end if + StripDoubleQuotes(token.string); + epair->key = (char *) GetHunkMemory(strlen(token.string) + 1); + strcpy(epair->key, token.string); + if (!PS_ExpectTokenType(script, TT_STRING, 0, &token)) + { + AAS_FreeBSPEntities(); + FreeScript(script); + return; + } //end if + StripDoubleQuotes(token.string); + epair->value = (char *) GetHunkMemory(strlen(token.string) + 1); + strcpy(epair->value, token.string); + } //end while + if (strcmp(token.string, "}")) + { + ScriptError(script, "missing }\n"); + AAS_FreeBSPEntities(); + FreeScript(script); + return; + } //end if + } //end while + FreeScript(script); +} //end of the function AAS_ParseBSPEntities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BSPTraceLight(vec3_t start, vec3_t end, vec3_t endpos, int *red, int *green, int *blue) +{ + return 0; +} //end of the function AAS_BSPTraceLight +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DumpBSPData(void) +{ + AAS_FreeBSPEntities(); + + if (bspworld.dentdata) FreeMemory(bspworld.dentdata); + bspworld.dentdata = NULL; + bspworld.entdatasize = 0; + // + bspworld.loaded = qfalse; + Com_Memset( &bspworld, 0, sizeof(bspworld) ); +} //end of the function AAS_DumpBSPData +//=========================================================================== +// load an bsp file +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_LoadBSPFile(void) +{ + AAS_DumpBSPData(); + bspworld.entdatasize = strlen(botimport.BSPEntityData()) + 1; + bspworld.dentdata = (char *) GetClearedHunkMemory(bspworld.entdatasize); + Com_Memcpy(bspworld.dentdata, botimport.BSPEntityData(), bspworld.entdatasize); + AAS_ParseBSPEntities(); + bspworld.loaded = qtrue; + return BLERR_NOERROR; +} //end of the function AAS_LoadBSPFile diff --git a/code/botlib/be_aas_cluster.c b/code/botlib/be_aas_cluster.c index 2fd569b..118dd0a 100755 --- a/code/botlib/be_aas_cluster.c +++ b/code/botlib/be_aas_cluster.c @@ -1,1545 +1,1545 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: be_aas_cluster.c - * - * desc: area clustering - * - * $Archive: /MissionPack/code/botlib/be_aas_cluster.c $ - * - *****************************************************************************/ - -#include "../game/q_shared.h" -#include "l_memory.h" -#include "l_script.h" -#include "l_precomp.h" -#include "l_struct.h" -#include "l_log.h" -#include "l_memory.h" -#include "l_libvar.h" -#include "aasfile.h" -#include "../game/botlib.h" -#include "../game/be_aas.h" -#include "be_aas_funcs.h" -#include "be_aas_def.h" - -extern botlib_import_t botimport; - -#define AAS_MAX_PORTALS 65536 -#define AAS_MAX_PORTALINDEXSIZE 65536 -#define AAS_MAX_CLUSTERS 65536 -// -#define MAX_PORTALAREAS 1024 - -// do not flood through area faces, only use reachabilities -int nofaceflood = qtrue; - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_RemoveClusterAreas(void) -{ - int i; - - for (i = 1; i < aasworld.numareas; i++) - { - aasworld.areasettings[i].cluster = 0; - } //end for -} //end of the function AAS_RemoveClusterAreas -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_ClearCluster(int clusternum) -{ - int i; - - for (i = 1; i < aasworld.numareas; i++) - { - if (aasworld.areasettings[i].cluster == clusternum) - { - aasworld.areasettings[i].cluster = 0; - } //end if - } //end for -} //end of the function AAS_ClearCluster -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_RemovePortalsClusterReference(int clusternum) -{ - int portalnum; - - for (portalnum = 1; portalnum < aasworld.numportals; portalnum++) - { - if (aasworld.portals[portalnum].frontcluster == clusternum) - { - aasworld.portals[portalnum].frontcluster = 0; - } //end if - if (aasworld.portals[portalnum].backcluster == clusternum) - { - aasworld.portals[portalnum].backcluster = 0; - } //end if - } //end for -} //end of the function AAS_RemovePortalsClusterReference -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_UpdatePortal(int areanum, int clusternum) -{ - int portalnum; - aas_portal_t *portal; - aas_cluster_t *cluster; - - //find the portal of the area - for (portalnum = 1; portalnum < aasworld.numportals; portalnum++) - { - if (aasworld.portals[portalnum].areanum == areanum) break; - } //end for - // - if (portalnum == aasworld.numportals) - { - AAS_Error("no portal of area %d", areanum); - return qtrue; - } //end if - // - portal = &aasworld.portals[portalnum]; - //if the portal is already fully updated - if (portal->frontcluster == clusternum) return qtrue; - if (portal->backcluster == clusternum) return qtrue; - //if the portal has no front cluster yet - if (!portal->frontcluster) - { - portal->frontcluster = clusternum; - } //end if - //if the portal has no back cluster yet - else if (!portal->backcluster) - { - portal->backcluster = clusternum; - } //end else if - else - { - //remove the cluster portal flag contents - aasworld.areasettings[areanum].contents &= ~AREACONTENTS_CLUSTERPORTAL; - Log_Write("portal area %d is seperating more than two clusters\r\n", areanum); - return qfalse; - } //end else - if (aasworld.portalindexsize >= AAS_MAX_PORTALINDEXSIZE) - { - AAS_Error("AAS_MAX_PORTALINDEXSIZE"); - return qtrue; - } //end if - //set the area cluster number to the negative portal number - aasworld.areasettings[areanum].cluster = -portalnum; - //add the portal to the cluster using the portal index - cluster = &aasworld.clusters[clusternum]; - aasworld.portalindex[cluster->firstportal + cluster->numportals] = portalnum; - aasworld.portalindexsize++; - cluster->numportals++; - return qtrue; -} //end of the function AAS_UpdatePortal -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_FloodClusterAreas_r(int areanum, int clusternum) -{ - aas_area_t *area; - aas_face_t *face; - int facenum, i; - - // - if (areanum <= 0 || areanum >= aasworld.numareas) - { - AAS_Error("AAS_FloodClusterAreas_r: areanum out of range"); - return qfalse; - } //end if - //if the area is already part of a cluster - if (aasworld.areasettings[areanum].cluster > 0) - { - if (aasworld.areasettings[areanum].cluster == clusternum) return qtrue; - // - //there's a reachability going from one cluster to another only in one direction - // - AAS_Error("cluster %d touched cluster %d at area %d\r\n", - clusternum, aasworld.areasettings[areanum].cluster, areanum); - return qfalse; - } //end if - //don't add the cluster portal areas to the clusters - if (aasworld.areasettings[areanum].contents & AREACONTENTS_CLUSTERPORTAL) - { - return AAS_UpdatePortal(areanum, clusternum); - } //end if - //set the area cluster number - aasworld.areasettings[areanum].cluster = clusternum; - aasworld.areasettings[areanum].clusterareanum = - aasworld.clusters[clusternum].numareas; - //the cluster has an extra area - aasworld.clusters[clusternum].numareas++; - - area = &aasworld.areas[areanum]; - //use area faces to flood into adjacent areas - if (!nofaceflood) - { - for (i = 0; i < area->numfaces; i++) - { - facenum = abs(aasworld.faceindex[area->firstface + i]); - face = &aasworld.faces[facenum]; - if (face->frontarea == areanum) - { - if (face->backarea) if (!AAS_FloodClusterAreas_r(face->backarea, clusternum)) return qfalse; - } //end if - else - { - if (face->frontarea) if (!AAS_FloodClusterAreas_r(face->frontarea, clusternum)) return qfalse; - } //end else - } //end for - } //end if - //use the reachabilities to flood into other areas - for (i = 0; i < aasworld.areasettings[areanum].numreachableareas; i++) - { - if (!aasworld.reachability[ - aasworld.areasettings[areanum].firstreachablearea + i].areanum) - { - continue; - } //end if - if (!AAS_FloodClusterAreas_r(aasworld.reachability[ - aasworld.areasettings[areanum].firstreachablearea + i].areanum, clusternum)) return qfalse; - } //end for - return qtrue; -} //end of the function AAS_FloodClusterAreas_r -//=========================================================================== -// try to flood from all areas without cluster into areas with a cluster set -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_FloodClusterAreasUsingReachabilities(int clusternum) -{ - int i, j, areanum; - - for (i = 1; i < aasworld.numareas; i++) - { - //if this area already has a cluster set - if (aasworld.areasettings[i].cluster) - continue; - //if this area is a cluster portal - if (aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL) - continue; - //loop over the reachable areas from this area - for (j = 0; j < aasworld.areasettings[i].numreachableareas; j++) - { - //the reachable area - areanum = aasworld.reachability[aasworld.areasettings[i].firstreachablearea + j].areanum; - //if this area is a cluster portal - if (aasworld.areasettings[areanum].contents & AREACONTENTS_CLUSTERPORTAL) - continue; - //if this area has a cluster set - if (aasworld.areasettings[areanum].cluster) - { - if (!AAS_FloodClusterAreas_r(i, clusternum)) - return qfalse; - i = 0; - break; - } //end if - } //end for - } //end for - return qtrue; -} //end of the function AAS_FloodClusterAreasUsingReachabilities -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_NumberClusterPortals(int clusternum) -{ - int i, portalnum; - aas_cluster_t *cluster; - aas_portal_t *portal; - - cluster = &aasworld.clusters[clusternum]; - for (i = 0; i < cluster->numportals; i++) - { - portalnum = aasworld.portalindex[cluster->firstportal + i]; - portal = &aasworld.portals[portalnum]; - if (portal->frontcluster == clusternum) - { - portal->clusterareanum[0] = cluster->numareas++; - } //end if - else - { - portal->clusterareanum[1] = cluster->numareas++; - } //end else - } //end for -} //end of the function AAS_NumberClusterPortals -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_NumberClusterAreas(int clusternum) -{ - int i, portalnum; - aas_cluster_t *cluster; - aas_portal_t *portal; - - aasworld.clusters[clusternum].numareas = 0; - aasworld.clusters[clusternum].numreachabilityareas = 0; - //number all areas in this cluster WITH reachabilities - for (i = 1; i < aasworld.numareas; i++) - { - // - if (aasworld.areasettings[i].cluster != clusternum) continue; - // - if (!AAS_AreaReachability(i)) continue; - // - aasworld.areasettings[i].clusterareanum = aasworld.clusters[clusternum].numareas; - //the cluster has an extra area - aasworld.clusters[clusternum].numareas++; - aasworld.clusters[clusternum].numreachabilityareas++; - } //end for - //number all portals in this cluster WITH reachabilities - cluster = &aasworld.clusters[clusternum]; - for (i = 0; i < cluster->numportals; i++) - { - portalnum = aasworld.portalindex[cluster->firstportal + i]; - portal = &aasworld.portals[portalnum]; - if (!AAS_AreaReachability(portal->areanum)) continue; - if (portal->frontcluster == clusternum) - { - portal->clusterareanum[0] = cluster->numareas++; - aasworld.clusters[clusternum].numreachabilityareas++; - } //end if - else - { - portal->clusterareanum[1] = cluster->numareas++; - aasworld.clusters[clusternum].numreachabilityareas++; - } //end else - } //end for - //number all areas in this cluster WITHOUT reachabilities - for (i = 1; i < aasworld.numareas; i++) - { - // - if (aasworld.areasettings[i].cluster != clusternum) continue; - // - if (AAS_AreaReachability(i)) continue; - // - aasworld.areasettings[i].clusterareanum = aasworld.clusters[clusternum].numareas; - //the cluster has an extra area - aasworld.clusters[clusternum].numareas++; - } //end for - //number all portals in this cluster WITHOUT reachabilities - cluster = &aasworld.clusters[clusternum]; - for (i = 0; i < cluster->numportals; i++) - { - portalnum = aasworld.portalindex[cluster->firstportal + i]; - portal = &aasworld.portals[portalnum]; - if (AAS_AreaReachability(portal->areanum)) continue; - if (portal->frontcluster == clusternum) - { - portal->clusterareanum[0] = cluster->numareas++; - } //end if - else - { - portal->clusterareanum[1] = cluster->numareas++; - } //end else - } //end for -} //end of the function AAS_NumberClusterAreas -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_FindClusters(void) -{ - int i; - aas_cluster_t *cluster; - - AAS_RemoveClusterAreas(); - // - for (i = 1; i < aasworld.numareas; i++) - { - //if the area is already part of a cluster - if (aasworld.areasettings[i].cluster) - continue; - // if not flooding through faces only use areas that have reachabilities - if (nofaceflood) - { - if (!aasworld.areasettings[i].numreachableareas) - continue; - } //end if - //if the area is a cluster portal - if (aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL) - continue; - if (aasworld.numclusters >= AAS_MAX_CLUSTERS) - { - AAS_Error("AAS_MAX_CLUSTERS"); - return qfalse; - } //end if - cluster = &aasworld.clusters[aasworld.numclusters]; - cluster->numareas = 0; - cluster->numreachabilityareas = 0; - cluster->firstportal = aasworld.portalindexsize; - cluster->numportals = 0; - //flood the areas in this cluster - if (!AAS_FloodClusterAreas_r(i, aasworld.numclusters)) - return qfalse; - if (!AAS_FloodClusterAreasUsingReachabilities(aasworld.numclusters)) - return qfalse; - //number the cluster areas - //AAS_NumberClusterPortals(aasworld.numclusters); - AAS_NumberClusterAreas(aasworld.numclusters); - //Log_Write("cluster %d has %d areas\r\n", aasworld.numclusters, cluster->numareas); - aasworld.numclusters++; - } //end for - return qtrue; -} //end of the function AAS_FindClusters -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_CreatePortals(void) -{ - int i; - aas_portal_t *portal; - - for (i = 1; i < aasworld.numareas; i++) - { - //if the area is a cluster portal - if (aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL) - { - if (aasworld.numportals >= AAS_MAX_PORTALS) - { - AAS_Error("AAS_MAX_PORTALS"); - return; - } //end if - portal = &aasworld.portals[aasworld.numportals]; - portal->areanum = i; - portal->frontcluster = 0; - portal->backcluster = 0; - aasworld.numportals++; - } //end if - } //end for -} //end of the function AAS_CreatePortals -/* -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_MapContainsTeleporters(void) -{ - bsp_entity_t *entities, *ent; - char *classname; - - entities = AAS_ParseBSPEntities(); - - for (ent = entities; ent; ent = ent->next) - { - classname = AAS_ValueForBSPEpairKey(ent, "classname"); - if (classname && !strcmp(classname, "misc_teleporter")) - { - AAS_FreeBSPEntities(entities); - return qtrue; - } //end if - } //end for - return qfalse; -} //end of the function AAS_MapContainsTeleporters -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_NonConvexFaces(aas_face_t *face1, aas_face_t *face2, int side1, int side2) -{ - int i, j, edgenum; - aas_plane_t *plane1, *plane2; - aas_edge_t *edge; - - - plane1 = &aasworld.planes[face1->planenum ^ side1]; - plane2 = &aasworld.planes[face2->planenum ^ side2]; - - //check if one of the points of face1 is at the back of the plane of face2 - for (i = 0; i < face1->numedges; i++) - { - edgenum = abs(aasworld.edgeindex[face1->firstedge + i]); - edge = &aasworld.edges[edgenum]; - for (j = 0; j < 2; j++) - { - if (DotProduct(plane2->normal, aasworld.vertexes[edge->v[j]]) - - plane2->dist < -0.01) return qtrue; - } //end for - } //end for - for (i = 0; i < face2->numedges; i++) - { - edgenum = abs(aasworld.edgeindex[face2->firstedge + i]); - edge = &aasworld.edges[edgenum]; - for (j = 0; j < 2; j++) - { - if (DotProduct(plane1->normal, aasworld.vertexes[edge->v[j]]) - - plane1->dist < -0.01) return qtrue; - } //end for - } //end for - - return qfalse; -} //end of the function AAS_NonConvexFaces -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean AAS_CanMergeAreas(int *areanums, int numareas) -{ - int i, j, s, face1num, face2num, side1, side2, fn1, fn2; - aas_face_t *face1, *face2; - aas_area_t *area1, *area2; - - for (i = 0; i < numareas; i++) - { - area1 = &aasworld.areas[areanums[i]]; - for (fn1 = 0; fn1 < area1->numfaces; fn1++) - { - face1num = abs(aasworld.faceindex[area1->firstface + fn1]); - face1 = &aasworld.faces[face1num]; - side1 = face1->frontarea != areanums[i]; - //check if the face isn't a shared one with one of the other areas - for (s = 0; s < numareas; s++) - { - if (s == i) continue; - if (face1->frontarea == s || face1->backarea == s) break; - } //end for - //if the face was a shared one - if (s != numareas) continue; - // - for (j = 0; j < numareas; j++) - { - if (j == i) continue; - area2 = &aasworld.areas[areanums[j]]; - for (fn2 = 0; fn2 < area2->numfaces; fn2++) - { - face2num = abs(aasworld.faceindex[area2->firstface + fn2]); - face2 = &aasworld.faces[face2num]; - side2 = face2->frontarea != areanums[j]; - //check if the face isn't a shared one with one of the other areas - for (s = 0; s < numareas; s++) - { - if (s == j) continue; - if (face2->frontarea == s || face2->backarea == s) break; - } //end for - //if the face was a shared one - if (s != numareas) continue; - // - if (AAS_NonConvexFaces(face1, face2, side1, side2)) return qfalse; - } //end for - } //end for - } //end for - } //end for - return qtrue; -} //end of the function AAS_CanMergeAreas -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean AAS_NonConvexEdges(aas_edge_t *edge1, aas_edge_t *edge2, int side1, int side2, int planenum) -{ - int i; - vec3_t edgevec1, edgevec2, normal1, normal2; - float dist1, dist2; - aas_plane_t *plane; - - plane = &aasworld.planes[planenum]; - VectorSubtract(aasworld.vertexes[edge1->v[1]], aasworld.vertexes[edge1->v[0]], edgevec1); - VectorSubtract(aasworld.vertexes[edge2->v[1]], aasworld.vertexes[edge2->v[0]], edgevec2); - if (side1) VectorInverse(edgevec1); - if (side2) VectorInverse(edgevec2); - // - CrossProduct(edgevec1, plane->normal, normal1); - dist1 = DotProduct(normal1, aasworld.vertexes[edge1->v[0]]); - CrossProduct(edgevec2, plane->normal, normal2); - dist2 = DotProduct(normal2, aasworld.vertexes[edge2->v[0]]); - - for (i = 0; i < 2; i++) - { - if (DotProduct(aasworld.vertexes[edge1->v[i]], normal2) - dist2 < -0.01) return qfalse; - } //end for - for (i = 0; i < 2; i++) - { - if (DotProduct(aasworld.vertexes[edge2->v[i]], normal1) - dist1 < -0.01) return qfalse; - } //end for - return qtrue; -} //end of the function AAS_NonConvexEdges -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean AAS_CanMergeFaces(int *facenums, int numfaces, int planenum) -{ - int i, j, s, edgenum1, edgenum2, side1, side2, en1, en2, ens; - aas_face_t *face1, *face2, *otherface; - aas_edge_t *edge1, *edge2; - - for (i = 0; i < numfaces; i++) - { - face1 = &aasworld.faces[facenums[i]]; - for (en1 = 0; en1 < face1->numedges; en1++) - { - edgenum1 = aasworld.edgeindex[face1->firstedge + en1]; - side1 = (edgenum1 < 0) ^ (face1->planenum != planenum); - edgenum1 = abs(edgenum1); - edge1 = &aasworld.edges[edgenum1]; - //check if the edge is shared with another face - for (s = 0; s < numfaces; s++) - { - if (s == i) continue; - otherface = &aasworld.faces[facenums[s]]; - for (ens = 0; ens < otherface->numedges; ens++) - { - if (edgenum1 == abs(aasworld.edgeindex[otherface->firstedge + ens])) break; - } //end for - if (ens != otherface->numedges) break; - } //end for - //if the edge was shared - if (s != numfaces) continue; - // - for (j = 0; j < numfaces; j++) - { - if (j == i) continue; - face2 = &aasworld.faces[facenums[j]]; - for (en2 = 0; en2 < face2->numedges; en2++) - { - edgenum2 = aasworld.edgeindex[face2->firstedge + en2]; - side2 = (edgenum2 < 0) ^ (face2->planenum != planenum); - edgenum2 = abs(edgenum2); - edge2 = &aasworld.edges[edgenum2]; - //check if the edge is shared with another face - for (s = 0; s < numfaces; s++) - { - if (s == i) continue; - otherface = &aasworld.faces[facenums[s]]; - for (ens = 0; ens < otherface->numedges; ens++) - { - if (edgenum2 == abs(aasworld.edgeindex[otherface->firstedge + ens])) break; - } //end for - if (ens != otherface->numedges) break; - } //end for - //if the edge was shared - if (s != numfaces) continue; - // - if (AAS_NonConvexEdges(edge1, edge2, side1, side2, planenum)) return qfalse; - } //end for - } //end for - } //end for - } //end for - return qtrue; -} //end of the function AAS_CanMergeFaces*/ -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_ConnectedAreas_r(int *areanums, int numareas, int *connectedareas, int curarea) -{ - int i, j, otherareanum, facenum; - aas_area_t *area; - aas_face_t *face; - - connectedareas[curarea] = qtrue; - area = &aasworld.areas[areanums[curarea]]; - for (i = 0; i < area->numfaces; i++) - { - facenum = abs(aasworld.faceindex[area->firstface + i]); - face = &aasworld.faces[facenum]; - //if the face is solid - if (face->faceflags & FACE_SOLID) continue; - //get the area at the other side of the face - if (face->frontarea != areanums[curarea]) otherareanum = face->frontarea; - else otherareanum = face->backarea; - //check if the face is leading to one of the other areas - for (j = 0; j < numareas; j++) - { - if (areanums[j] == otherareanum) break; - } //end for - //if the face isn't leading to one of the other areas - if (j == numareas) continue; - //if the other area is already connected - if (connectedareas[j]) continue; - //recursively proceed with the other area - AAS_ConnectedAreas_r(areanums, numareas, connectedareas, j); - } //end for -} //end of the function AAS_ConnectedAreas_r -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean AAS_ConnectedAreas(int *areanums, int numareas) -{ - int connectedareas[MAX_PORTALAREAS], i; - - Com_Memset(connectedareas, 0, sizeof(connectedareas)); - if (numareas < 1) return qfalse; - if (numareas == 1) return qtrue; - AAS_ConnectedAreas_r(areanums, numareas, connectedareas, 0); - for (i = 0; i < numareas; i++) - { - if (!connectedareas[i]) return qfalse; - } //end for - return qtrue; -} //end of the function AAS_ConnectedAreas -//=========================================================================== -// gets adjacent areas with less presence types recursively -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_GetAdjacentAreasWithLessPresenceTypes_r(int *areanums, int numareas, int curareanum) -{ - int i, j, presencetype, otherpresencetype, otherareanum, facenum; - aas_area_t *area; - aas_face_t *face; - - areanums[numareas++] = curareanum; - area = &aasworld.areas[curareanum]; - presencetype = aasworld.areasettings[curareanum].presencetype; - for (i = 0; i < area->numfaces; i++) - { - facenum = abs(aasworld.faceindex[area->firstface + i]); - face = &aasworld.faces[facenum]; - //if the face is solid - if (face->faceflags & FACE_SOLID) continue; - //the area at the other side of the face - if (face->frontarea != curareanum) otherareanum = face->frontarea; - else otherareanum = face->backarea; - // - otherpresencetype = aasworld.areasettings[otherareanum].presencetype; - //if the other area has less presence types - if ((presencetype & ~otherpresencetype) && - !(otherpresencetype & ~presencetype)) - { - //check if the other area isn't already in the list - for (j = 0; j < numareas; j++) - { - if (otherareanum == areanums[j]) break; - } //end for - //if the other area isn't already in the list - if (j == numareas) - { - if (numareas >= MAX_PORTALAREAS) - { - AAS_Error("MAX_PORTALAREAS"); - return numareas; - } //end if - numareas = AAS_GetAdjacentAreasWithLessPresenceTypes_r(areanums, numareas, otherareanum); - } //end if - } //end if - } //end for - return numareas; -} //end of the function AAS_GetAdjacentAreasWithLessPresenceTypes_r -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_CheckAreaForPossiblePortals(int areanum) -{ - int i, j, k, fen, ben, frontedgenum, backedgenum, facenum; - int areanums[MAX_PORTALAREAS], numareas, otherareanum; - int numareafrontfaces[MAX_PORTALAREAS], numareabackfaces[MAX_PORTALAREAS]; - int frontfacenums[MAX_PORTALAREAS], backfacenums[MAX_PORTALAREAS]; - int numfrontfaces, numbackfaces; - int frontareanums[MAX_PORTALAREAS], backareanums[MAX_PORTALAREAS]; - int numfrontareas, numbackareas; - int frontplanenum, backplanenum, faceplanenum; - aas_area_t *area; - aas_face_t *frontface, *backface, *face; - - //if it isn't already a portal - if (aasworld.areasettings[areanum].contents & AREACONTENTS_CLUSTERPORTAL) return 0; - //it must be a grounded area - if (!(aasworld.areasettings[areanum].areaflags & AREA_GROUNDED)) return 0; - // - Com_Memset(numareafrontfaces, 0, sizeof(numareafrontfaces)); - Com_Memset(numareabackfaces, 0, sizeof(numareabackfaces)); - numareas = numfrontfaces = numbackfaces = 0; - numfrontareas = numbackareas = 0; - frontplanenum = backplanenum = -1; - //add any adjacent areas with less presence types - numareas = AAS_GetAdjacentAreasWithLessPresenceTypes_r(areanums, 0, areanum); - // - for (i = 0; i < numareas; i++) - { - area = &aasworld.areas[areanums[i]]; - for (j = 0; j < area->numfaces; j++) - { - facenum = abs(aasworld.faceindex[area->firstface + j]); - face = &aasworld.faces[facenum]; - //if the face is solid - if (face->faceflags & FACE_SOLID) continue; - //check if the face is shared with one of the other areas - for (k = 0; k < numareas; k++) - { - if (k == i) continue; - if (face->frontarea == areanums[k] || face->backarea == areanums[k]) break; - } //end for - //if the face is shared - if (k != numareas) continue; - //the number of the area at the other side of the face - if (face->frontarea == areanums[i]) otherareanum = face->backarea; - else otherareanum = face->frontarea; - //if the other area already is a cluter portal - if (aasworld.areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) return 0; - //number of the plane of the area - faceplanenum = face->planenum & ~1; - // - if (frontplanenum < 0 || faceplanenum == frontplanenum) - { - frontplanenum = faceplanenum; - frontfacenums[numfrontfaces++] = facenum; - for (k = 0; k < numfrontareas; k++) - { - if (frontareanums[k] == otherareanum) break; - } //end for - if (k == numfrontareas) frontareanums[numfrontareas++] = otherareanum; - numareafrontfaces[i]++; - } //end if - else if (backplanenum < 0 || faceplanenum == backplanenum) - { - backplanenum = faceplanenum; - backfacenums[numbackfaces++] = facenum; - for (k = 0; k < numbackareas; k++) - { - if (backareanums[k] == otherareanum) break; - } //end for - if (k == numbackareas) backareanums[numbackareas++] = otherareanum; - numareabackfaces[i]++; - } //end else - else - { - return 0; - } //end else - } //end for - } //end for - //every area should have at least one front face and one back face - for (i = 0; i < numareas; i++) - { - if (!numareafrontfaces[i] || !numareabackfaces[i]) return 0; - } //end for - //the front areas should all be connected - if (!AAS_ConnectedAreas(frontareanums, numfrontareas)) return 0; - //the back areas should all be connected - if (!AAS_ConnectedAreas(backareanums, numbackareas)) return 0; - //none of the front faces should have a shared edge with a back face - for (i = 0; i < numfrontfaces; i++) - { - frontface = &aasworld.faces[frontfacenums[i]]; - for (fen = 0; fen < frontface->numedges; fen++) - { - frontedgenum = abs(aasworld.edgeindex[frontface->firstedge + fen]); - for (j = 0; j < numbackfaces; j++) - { - backface = &aasworld.faces[backfacenums[j]]; - for (ben = 0; ben < backface->numedges; ben++) - { - backedgenum = abs(aasworld.edgeindex[backface->firstedge + ben]); - if (frontedgenum == backedgenum) break; - } //end for - if (ben != backface->numedges) break; - } //end for - if (j != numbackfaces) break; - } //end for - if (fen != frontface->numedges) break; - } //end for - if (i != numfrontfaces) return 0; - //set the cluster portal contents - for (i = 0; i < numareas; i++) - { - aasworld.areasettings[areanums[i]].contents |= AREACONTENTS_CLUSTERPORTAL; - //this area can be used as a route portal - aasworld.areasettings[areanums[i]].contents |= AREACONTENTS_ROUTEPORTAL; - Log_Write("possible portal: %d\r\n", areanums[i]); - } //end for - // - return numareas; -} //end of the function AAS_CheckAreaForPossiblePortals -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_FindPossiblePortals(void) -{ - int i, numpossibleportals; - - numpossibleportals = 0; - for (i = 1; i < aasworld.numareas; i++) - { - numpossibleportals += AAS_CheckAreaForPossiblePortals(i); - } //end for - botimport.Print(PRT_MESSAGE, "\r%6d possible portal areas\n", numpossibleportals); -} //end of the function AAS_FindPossiblePortals -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_RemoveAllPortals(void) -{ - int i; - - for (i = 1; i < aasworld.numareas; i++) - { - aasworld.areasettings[i].contents &= ~AREACONTENTS_CLUSTERPORTAL; - } //end for -} //end of the function AAS_RemoveAllPortals - -#if 0 -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_FloodCluster_r(int areanum, int clusternum) -{ - int i, otherareanum; - aas_face_t *face; - aas_area_t *area; - - //set cluster mark - aasworld.areasettings[areanum].cluster = clusternum; - //if the area is a portal - //if (aasworld.areasettings[areanum].contents & AREACONTENTS_CLUSTERPORTAL) return; - // - area = &aasworld.areas[areanum]; - //use area faces to flood into adjacent areas - for (i = 0; i < area->numfaces; i++) - { - face = &aasworld.faces[abs(aasworld.faceindex[area->firstface + i])]; - // - if (face->frontarea != areanum) otherareanum = face->frontarea; - else otherareanum = face->backarea; - //if there's no area at the other side - if (!otherareanum) continue; - //if the area is a portal - if (aasworld.areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) continue; - //if the area is already marked - if (aasworld.areasettings[otherareanum].cluster) continue; - // - AAS_FloodCluster_r(otherareanum, clusternum); - } //end for - //use the reachabilities to flood into other areas - for (i = 0; i < aasworld.areasettings[areanum].numreachableareas; i++) - { - otherareanum = aasworld.reachability[ - aasworld.areasettings[areanum].firstreachablearea + i].areanum; - if (!otherareanum) - { - continue; - AAS_Error("reachability %d has zero area\n", aasworld.areasettings[areanum].firstreachablearea + i); - } //end if - //if the area is a portal - if (aasworld.areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) continue; - //if the area is already marked - if (aasworld.areasettings[otherareanum].cluster) continue; - // - AAS_FloodCluster_r(otherareanum, clusternum); - } //end for -} //end of the function AAS_FloodCluster_r -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_RemoveTeleporterPortals(void) -{ - int i, j, areanum; - - for (i = 1; i < aasworld.numareas; i++) - { - for (j = 0; j < aasworld.areasettings[i].numreachableareas; j++) - { - areanum = aasworld.reachability[aasworld.areasettings[i].firstreachablearea + j].areanum; - if (aasworld.reachability[aasworld.areasettings[i].firstreachablearea + j].traveltype == TRAVEL_TELEPORT) - { - aasworld.areasettings[i].contents &= ~AREACONTENTS_CLUSTERPORTAL; - aasworld.areasettings[areanum].contents &= ~AREACONTENTS_CLUSTERPORTAL; - break; - } //end if - } //end for - } //end for -} //end of the function AAS_RemoveTeleporterPortals -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_FloodClusterReachabilities(int clusternum) -{ - int i, j, areanum; - - for (i = 1; i < aasworld.numareas; i++) - { - //if this area already has a cluster set - if (aasworld.areasettings[i].cluster) continue; - //if this area is a cluster portal - if (aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL) continue; - //loop over the reachable areas from this area - for (j = 0; j < aasworld.areasettings[i].numreachableareas; j++) - { - //the reachable area - areanum = aasworld.reachability[aasworld.areasettings[i].firstreachablearea + j].areanum; - //if this area is a cluster portal - if (aasworld.areasettings[areanum].contents & AREACONTENTS_CLUSTERPORTAL) continue; - //if this area has a cluster set - if (aasworld.areasettings[areanum].cluster == clusternum) - { - AAS_FloodCluster_r(i, clusternum); - i = 0; - break; - } //end if - } //end for - } //end for -} //end of the function AAS_FloodClusterReachabilities - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_RemoveNotClusterClosingPortals(void) -{ - int i, j, k, facenum, otherareanum, nonclosingportals; - aas_area_t *area; - aas_face_t *face; - - AAS_RemoveTeleporterPortals(); - // - nonclosingportals = 0; - for (i = 1; i < aasworld.numareas; i++) - { - if (!(aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL)) continue; - //find a non-portal area adjacent to the portal area and flood - //the cluster from there - area = &aasworld.areas[i]; - for (j = 0; j < area->numfaces; j++) - { - facenum = abs(aasworld.faceindex[area->firstface + j]); - face = &aasworld.faces[facenum]; - // - if (face->frontarea != i) otherareanum = face->frontarea; - else otherareanum = face->backarea; - // - if (!otherareanum) continue; - // - if (aasworld.areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) - { - continue; - } //end if - //reset all cluster fields - AAS_RemoveClusterAreas(); - // - AAS_FloodCluster_r(otherareanum, 1); - AAS_FloodClusterReachabilities(1); - //check if all adjacent non-portal areas have a cluster set - for (k = 0; k < area->numfaces; k++) - { - facenum = abs(aasworld.faceindex[area->firstface + k]); - face = &aasworld.faces[facenum]; - // - if (face->frontarea != i) otherareanum = face->frontarea; - else otherareanum = face->backarea; - // - if (!otherareanum) continue; - // - if (aasworld.areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) - { - continue; - } //end if - // - if (!aasworld.areasettings[otherareanum].cluster) break; - } //end for - //if all adjacent non-portal areas have a cluster set then the portal - //didn't seal a cluster - if (k >= area->numfaces) - { - aasworld.areasettings[i].contents &= ~AREACONTENTS_CLUSTERPORTAL; - nonclosingportals++; - //recheck all the other portals again - i = 0; - break; - } //end if - } //end for - } //end for - botimport.Print(PRT_MESSAGE, "\r%6d non closing portals removed\n", nonclosingportals); -} //end of the function AAS_RemoveNotClusterClosingPortals - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== - -void AAS_RemoveNotClusterClosingPortals(void) -{ - int i, j, facenum, otherareanum, nonclosingportals, numseperatedclusters; - aas_area_t *area; - aas_face_t *face; - - AAS_RemoveTeleporterPortals(); - // - nonclosingportals = 0; - for (i = 1; i < aasworld.numareas; i++) - { - if (!(aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL)) continue; - // - numseperatedclusters = 0; - //reset all cluster fields - AAS_RemoveClusterAreas(); - //find a non-portal area adjacent to the portal area and flood - //the cluster from there - area = &aasworld.areas[i]; - for (j = 0; j < area->numfaces; j++) - { - facenum = abs(aasworld.faceindex[area->firstface + j]); - face = &aasworld.faces[facenum]; - // - if (face->frontarea != i) otherareanum = face->frontarea; - else otherareanum = face->backarea; - //if not solid at the other side of the face - if (!otherareanum) continue; - //don't flood into other portals - if (aasworld.areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) continue; - //if the area already has a cluster set - if (aasworld.areasettings[otherareanum].cluster) continue; - //another cluster is seperated by this portal - numseperatedclusters++; - //flood the cluster - AAS_FloodCluster_r(otherareanum, numseperatedclusters); - AAS_FloodClusterReachabilities(numseperatedclusters); - } //end for - //use the reachabilities to flood into other areas - for (j = 0; j < aasworld.areasettings[i].numreachableareas; j++) - { - otherareanum = aasworld.reachability[ - aasworld.areasettings[i].firstreachablearea + j].areanum; - //this should never be qtrue but we check anyway - if (!otherareanum) continue; - //don't flood into other portals - if (aasworld.areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) continue; - //if the area already has a cluster set - if (aasworld.areasettings[otherareanum].cluster) continue; - //another cluster is seperated by this portal - numseperatedclusters++; - //flood the cluster - AAS_FloodCluster_r(otherareanum, numseperatedclusters); - AAS_FloodClusterReachabilities(numseperatedclusters); - } //end for - //a portal must seperate no more and no less than 2 clusters - if (numseperatedclusters != 2) - { - aasworld.areasettings[i].contents &= ~AREACONTENTS_CLUSTERPORTAL; - nonclosingportals++; - //recheck all the other portals again - i = 0; - } //end if - } //end for - botimport.Print(PRT_MESSAGE, "\r%6d non closing portals removed\n", nonclosingportals); -} //end of the function AAS_RemoveNotClusterClosingPortals - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== - -void AAS_AddTeleporterPortals(void) -{ - int j, area2num, facenum, otherareanum; - char *target, *targetname, *classname; - bsp_entity_t *entities, *ent, *dest; - vec3_t origin, destorigin, mins, maxs, end; - vec3_t bbmins, bbmaxs; - aas_area_t *area; - aas_face_t *face; - aas_trace_t trace; - aas_link_t *areas, *link; - - entities = AAS_ParseBSPEntities(); - - for (ent = entities; ent; ent = ent->next) - { - classname = AAS_ValueForBSPEpairKey(ent, "classname"); - if (classname && !strcmp(classname, "misc_teleporter")) - { - if (!AAS_VectorForBSPEpairKey(ent, "origin", origin)) - { - botimport.Print(PRT_ERROR, "teleporter (%s) without origin\n", target); - continue; - } //end if - // - target = AAS_ValueForBSPEpairKey(ent, "target"); - if (!target) - { - botimport.Print(PRT_ERROR, "teleporter (%s) without target\n", target); - continue; - } //end if - for (dest = entities; dest; dest = dest->next) - { - classname = AAS_ValueForBSPEpairKey(dest, "classname"); - if (classname && !strcmp(classname, "misc_teleporter_dest")) - { - targetname = AAS_ValueForBSPEpairKey(dest, "targetname"); - if (targetname && !strcmp(targetname, target)) - { - break; - } //end if - } //end if - } //end for - if (!dest) - { - botimport.Print(PRT_ERROR, "teleporter without destination (%s)\n", target); - continue; - } //end if - if (!AAS_VectorForBSPEpairKey(dest, "origin", destorigin)) - { - botimport.Print(PRT_ERROR, "teleporter destination (%s) without origin\n", target); - continue; - } //end if - destorigin[2] += 24; //just for q2e1m2, the dork has put the telepads in the ground - VectorCopy(destorigin, end); - end[2] -= 100; - trace = AAS_TraceClientBBox(destorigin, end, PRESENCE_CROUCH, -1); - if (trace.startsolid) - { - botimport.Print(PRT_ERROR, "teleporter destination (%s) in solid\n", target); - continue; - } //end if - VectorCopy(trace.endpos, destorigin); - area2num = AAS_PointAreaNum(destorigin); - //reset all cluster fields - for (j = 0; j < aasworld.numareas; j++) - { - aasworld.areasettings[j].cluster = 0; - } //end for - // - VectorSet(mins, -8, -8, 8); - VectorSet(maxs, 8, 8, 24); - // - AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, bbmins, bbmaxs); - // - VectorAdd(origin, mins, mins); - VectorAdd(origin, maxs, maxs); - //add bounding box size - VectorSubtract(mins, bbmaxs, mins); - VectorSubtract(maxs, bbmins, maxs); - //link an invalid (-1) entity - areas = AAS_AASLinkEntity(mins, maxs, -1); - // - for (link = areas; link; link = link->next_area) - { - if (!AAS_AreaGrounded(link->areanum)) continue; - //add the teleporter portal mark - aasworld.areasettings[link->areanum].contents |= AREACONTENTS_CLUSTERPORTAL | - AREACONTENTS_TELEPORTAL; - } //end for - // - for (link = areas; link; link = link->next_area) - { - if (!AAS_AreaGrounded(link->areanum)) continue; - //find a non-portal area adjacent to the portal area and flood - //the cluster from there - area = &aasworld.areas[link->areanum]; - for (j = 0; j < area->numfaces; j++) - { - facenum = abs(aasworld.faceindex[area->firstface + j]); - face = &aasworld.faces[facenum]; - // - if (face->frontarea != link->areanum) otherareanum = face->frontarea; - else otherareanum = face->backarea; - // - if (!otherareanum) continue; - // - if (aasworld.areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) - { - continue; - } //end if - // - AAS_FloodCluster_r(otherareanum, 1); - } //end for - } //end for - //if the teleport destination IS in the same cluster - if (aasworld.areasettings[area2num].cluster) - { - for (link = areas; link; link = link->next_area) - { - if (!AAS_AreaGrounded(link->areanum)) continue; - //add the teleporter portal mark - aasworld.areasettings[link->areanum].contents &= ~(AREACONTENTS_CLUSTERPORTAL | - AREACONTENTS_TELEPORTAL); - } //end for - } //end if - } //end if - } //end for - AAS_FreeBSPEntities(entities); -} //end of the function AAS_AddTeleporterPortals - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_AddTeleporterPortals(void) -{ - int i, j, areanum; - - for (i = 1; i < aasworld.numareas; i++) - { - for (j = 0; j < aasworld.areasettings[i].numreachableareas; j++) - { - if (aasworld.reachability[aasworld.areasettings[i].firstreachablearea + j].traveltype != TRAVEL_TELEPORT) continue; - areanum = aasworld.reachability[aasworld.areasettings[i].firstreachablearea + j].areanum; - aasworld.areasettings[areanum].contents |= AREACONTENTS_CLUSTERPORTAL; - } //end for - } //end for -} //end of the function AAS_AddTeleporterPortals - -#endif - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_TestPortals(void) -{ - int i; - aas_portal_t *portal; - - for (i = 1; i < aasworld.numportals; i++) - { - portal = &aasworld.portals[i]; - if (!portal->frontcluster) - { - aasworld.areasettings[portal->areanum].contents &= ~AREACONTENTS_CLUSTERPORTAL; - Log_Write("portal area %d has no front cluster\r\n", portal->areanum); - return qfalse; - } //end if - if (!portal->backcluster) - { - aasworld.areasettings[portal->areanum].contents &= ~AREACONTENTS_CLUSTERPORTAL; - Log_Write("portal area %d has no back cluster\r\n", portal->areanum); - return qfalse; - } //end if - } //end for - return qtrue; -} //end of the function -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_CountForcedClusterPortals(void) -{ - int num, i; - - num = 0; - for (i = 1; i < aasworld.numareas; i++) - { - if (aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL) - { - Log_Write("area %d is a forced portal area\r\n", i); - num++; - } //end if - } //end for - botimport.Print(PRT_MESSAGE, "%6d forced portal areas\n", num); -} //end of the function AAS_CountForcedClusterPortals -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_CreateViewPortals(void) -{ - int i; - - for (i = 1; i < aasworld.numareas; i++) - { - if (aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL) - { - aasworld.areasettings[i].contents |= AREACONTENTS_VIEWPORTAL; - } //end if - } //end for -} //end of the function AAS_CreateViewPortals -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_SetViewPortalsAsClusterPortals(void) -{ - int i; - - for (i = 1; i < aasworld.numareas; i++) - { - if (aasworld.areasettings[i].contents & AREACONTENTS_VIEWPORTAL) - { - aasworld.areasettings[i].contents |= AREACONTENTS_CLUSTERPORTAL; - } //end if - } //end for -} //end of the function AAS_SetViewPortalsAsClusterPortals -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_InitClustering(void) -{ - int i, removedPortalAreas; - int n, total, numreachabilityareas; - - if (!aasworld.loaded) return; - //if there are clusters - if (aasworld.numclusters >= 1) - { -#ifndef BSPC - //if clustering isn't forced - if (!((int)LibVarGetValue("forceclustering")) && - !((int)LibVarGetValue("forcereachability"))) return; -#endif - } //end if - //set all view portals as cluster portals in case we re-calculate the reachabilities and clusters (with -reach) - AAS_SetViewPortalsAsClusterPortals(); - //count the number of forced cluster portals - AAS_CountForcedClusterPortals(); - //remove all area cluster marks - AAS_RemoveClusterAreas(); - //find possible cluster portals - AAS_FindPossiblePortals(); - //craete portals to for the bot view - AAS_CreateViewPortals(); - //remove all portals that are not closing a cluster - //AAS_RemoveNotClusterClosingPortals(); - //initialize portal memory - if (aasworld.portals) FreeMemory(aasworld.portals); - aasworld.portals = (aas_portal_t *) GetClearedMemory(AAS_MAX_PORTALS * sizeof(aas_portal_t)); - //initialize portal index memory - if (aasworld.portalindex) FreeMemory(aasworld.portalindex); - aasworld.portalindex = (aas_portalindex_t *) GetClearedMemory(AAS_MAX_PORTALINDEXSIZE * sizeof(aas_portalindex_t)); - //initialize cluster memory - if (aasworld.clusters) FreeMemory(aasworld.clusters); - aasworld.clusters = (aas_cluster_t *) GetClearedMemory(AAS_MAX_CLUSTERS * sizeof(aas_cluster_t)); - // - removedPortalAreas = 0; - botimport.Print(PRT_MESSAGE, "\r%6d removed portal areas", removedPortalAreas); - while(1) - { - botimport.Print(PRT_MESSAGE, "\r%6d", removedPortalAreas); - //initialize the number of portals and clusters - aasworld.numportals = 1; //portal 0 is a dummy - aasworld.portalindexsize = 0; - aasworld.numclusters = 1; //cluster 0 is a dummy - //create the portals from the portal areas - AAS_CreatePortals(); - // - removedPortalAreas++; - //find the clusters - if (!AAS_FindClusters()) - continue; - //test the portals - if (!AAS_TestPortals()) - continue; - // - break; - } //end while - botimport.Print(PRT_MESSAGE, "\n"); - //the AAS file should be saved - aasworld.savefile = qtrue; - //write the portal areas to the log file - for (i = 1; i < aasworld.numportals; i++) - { - Log_Write("portal %d: area %d\r\n", i, aasworld.portals[i].areanum); - } //end for - // report cluster info - botimport.Print(PRT_MESSAGE, "%6d portals created\n", aasworld.numportals); - botimport.Print(PRT_MESSAGE, "%6d clusters created\n", aasworld.numclusters); - for (i = 1; i < aasworld.numclusters; i++) - { - botimport.Print(PRT_MESSAGE, "cluster %d has %d reachability areas\n", i, - aasworld.clusters[i].numreachabilityareas); - } //end for - // report AAS file efficiency - numreachabilityareas = 0; - total = 0; - for (i = 0; i < aasworld.numclusters; i++) { - n = aasworld.clusters[i].numreachabilityareas; - numreachabilityareas += n; - total += n * n; - } - total += numreachabilityareas * aasworld.numportals; - // - botimport.Print(PRT_MESSAGE, "%6i total reachability areas\n", numreachabilityareas); - botimport.Print(PRT_MESSAGE, "%6i AAS memory/CPU usage (the lower the better)\n", total * 3); -} //end of the function AAS_InitClustering +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_cluster.c + * + * desc: area clustering + * + * $Archive: /MissionPack/code/botlib/be_aas_cluster.c $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_log.h" +#include "l_memory.h" +#include "l_libvar.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_aas_def.h" + +extern botlib_import_t botimport; + +#define AAS_MAX_PORTALS 65536 +#define AAS_MAX_PORTALINDEXSIZE 65536 +#define AAS_MAX_CLUSTERS 65536 +// +#define MAX_PORTALAREAS 1024 + +// do not flood through area faces, only use reachabilities +int nofaceflood = qtrue; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RemoveClusterAreas(void) +{ + int i; + + for (i = 1; i < aasworld.numareas; i++) + { + aasworld.areasettings[i].cluster = 0; + } //end for +} //end of the function AAS_RemoveClusterAreas +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ClearCluster(int clusternum) +{ + int i; + + for (i = 1; i < aasworld.numareas; i++) + { + if (aasworld.areasettings[i].cluster == clusternum) + { + aasworld.areasettings[i].cluster = 0; + } //end if + } //end for +} //end of the function AAS_ClearCluster +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RemovePortalsClusterReference(int clusternum) +{ + int portalnum; + + for (portalnum = 1; portalnum < aasworld.numportals; portalnum++) + { + if (aasworld.portals[portalnum].frontcluster == clusternum) + { + aasworld.portals[portalnum].frontcluster = 0; + } //end if + if (aasworld.portals[portalnum].backcluster == clusternum) + { + aasworld.portals[portalnum].backcluster = 0; + } //end if + } //end for +} //end of the function AAS_RemovePortalsClusterReference +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_UpdatePortal(int areanum, int clusternum) +{ + int portalnum; + aas_portal_t *portal; + aas_cluster_t *cluster; + + //find the portal of the area + for (portalnum = 1; portalnum < aasworld.numportals; portalnum++) + { + if (aasworld.portals[portalnum].areanum == areanum) break; + } //end for + // + if (portalnum == aasworld.numportals) + { + AAS_Error("no portal of area %d", areanum); + return qtrue; + } //end if + // + portal = &aasworld.portals[portalnum]; + //if the portal is already fully updated + if (portal->frontcluster == clusternum) return qtrue; + if (portal->backcluster == clusternum) return qtrue; + //if the portal has no front cluster yet + if (!portal->frontcluster) + { + portal->frontcluster = clusternum; + } //end if + //if the portal has no back cluster yet + else if (!portal->backcluster) + { + portal->backcluster = clusternum; + } //end else if + else + { + //remove the cluster portal flag contents + aasworld.areasettings[areanum].contents &= ~AREACONTENTS_CLUSTERPORTAL; + Log_Write("portal area %d is seperating more than two clusters\r\n", areanum); + return qfalse; + } //end else + if (aasworld.portalindexsize >= AAS_MAX_PORTALINDEXSIZE) + { + AAS_Error("AAS_MAX_PORTALINDEXSIZE"); + return qtrue; + } //end if + //set the area cluster number to the negative portal number + aasworld.areasettings[areanum].cluster = -portalnum; + //add the portal to the cluster using the portal index + cluster = &aasworld.clusters[clusternum]; + aasworld.portalindex[cluster->firstportal + cluster->numportals] = portalnum; + aasworld.portalindexsize++; + cluster->numportals++; + return qtrue; +} //end of the function AAS_UpdatePortal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_FloodClusterAreas_r(int areanum, int clusternum) +{ + aas_area_t *area; + aas_face_t *face; + int facenum, i; + + // + if (areanum <= 0 || areanum >= aasworld.numareas) + { + AAS_Error("AAS_FloodClusterAreas_r: areanum out of range"); + return qfalse; + } //end if + //if the area is already part of a cluster + if (aasworld.areasettings[areanum].cluster > 0) + { + if (aasworld.areasettings[areanum].cluster == clusternum) return qtrue; + // + //there's a reachability going from one cluster to another only in one direction + // + AAS_Error("cluster %d touched cluster %d at area %d\r\n", + clusternum, aasworld.areasettings[areanum].cluster, areanum); + return qfalse; + } //end if + //don't add the cluster portal areas to the clusters + if (aasworld.areasettings[areanum].contents & AREACONTENTS_CLUSTERPORTAL) + { + return AAS_UpdatePortal(areanum, clusternum); + } //end if + //set the area cluster number + aasworld.areasettings[areanum].cluster = clusternum; + aasworld.areasettings[areanum].clusterareanum = + aasworld.clusters[clusternum].numareas; + //the cluster has an extra area + aasworld.clusters[clusternum].numareas++; + + area = &aasworld.areas[areanum]; + //use area faces to flood into adjacent areas + if (!nofaceflood) + { + for (i = 0; i < area->numfaces; i++) + { + facenum = abs(aasworld.faceindex[area->firstface + i]); + face = &aasworld.faces[facenum]; + if (face->frontarea == areanum) + { + if (face->backarea) if (!AAS_FloodClusterAreas_r(face->backarea, clusternum)) return qfalse; + } //end if + else + { + if (face->frontarea) if (!AAS_FloodClusterAreas_r(face->frontarea, clusternum)) return qfalse; + } //end else + } //end for + } //end if + //use the reachabilities to flood into other areas + for (i = 0; i < aasworld.areasettings[areanum].numreachableareas; i++) + { + if (!aasworld.reachability[ + aasworld.areasettings[areanum].firstreachablearea + i].areanum) + { + continue; + } //end if + if (!AAS_FloodClusterAreas_r(aasworld.reachability[ + aasworld.areasettings[areanum].firstreachablearea + i].areanum, clusternum)) return qfalse; + } //end for + return qtrue; +} //end of the function AAS_FloodClusterAreas_r +//=========================================================================== +// try to flood from all areas without cluster into areas with a cluster set +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_FloodClusterAreasUsingReachabilities(int clusternum) +{ + int i, j, areanum; + + for (i = 1; i < aasworld.numareas; i++) + { + //if this area already has a cluster set + if (aasworld.areasettings[i].cluster) + continue; + //if this area is a cluster portal + if (aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL) + continue; + //loop over the reachable areas from this area + for (j = 0; j < aasworld.areasettings[i].numreachableareas; j++) + { + //the reachable area + areanum = aasworld.reachability[aasworld.areasettings[i].firstreachablearea + j].areanum; + //if this area is a cluster portal + if (aasworld.areasettings[areanum].contents & AREACONTENTS_CLUSTERPORTAL) + continue; + //if this area has a cluster set + if (aasworld.areasettings[areanum].cluster) + { + if (!AAS_FloodClusterAreas_r(i, clusternum)) + return qfalse; + i = 0; + break; + } //end if + } //end for + } //end for + return qtrue; +} //end of the function AAS_FloodClusterAreasUsingReachabilities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_NumberClusterPortals(int clusternum) +{ + int i, portalnum; + aas_cluster_t *cluster; + aas_portal_t *portal; + + cluster = &aasworld.clusters[clusternum]; + for (i = 0; i < cluster->numportals; i++) + { + portalnum = aasworld.portalindex[cluster->firstportal + i]; + portal = &aasworld.portals[portalnum]; + if (portal->frontcluster == clusternum) + { + portal->clusterareanum[0] = cluster->numareas++; + } //end if + else + { + portal->clusterareanum[1] = cluster->numareas++; + } //end else + } //end for +} //end of the function AAS_NumberClusterPortals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_NumberClusterAreas(int clusternum) +{ + int i, portalnum; + aas_cluster_t *cluster; + aas_portal_t *portal; + + aasworld.clusters[clusternum].numareas = 0; + aasworld.clusters[clusternum].numreachabilityareas = 0; + //number all areas in this cluster WITH reachabilities + for (i = 1; i < aasworld.numareas; i++) + { + // + if (aasworld.areasettings[i].cluster != clusternum) continue; + // + if (!AAS_AreaReachability(i)) continue; + // + aasworld.areasettings[i].clusterareanum = aasworld.clusters[clusternum].numareas; + //the cluster has an extra area + aasworld.clusters[clusternum].numareas++; + aasworld.clusters[clusternum].numreachabilityareas++; + } //end for + //number all portals in this cluster WITH reachabilities + cluster = &aasworld.clusters[clusternum]; + for (i = 0; i < cluster->numportals; i++) + { + portalnum = aasworld.portalindex[cluster->firstportal + i]; + portal = &aasworld.portals[portalnum]; + if (!AAS_AreaReachability(portal->areanum)) continue; + if (portal->frontcluster == clusternum) + { + portal->clusterareanum[0] = cluster->numareas++; + aasworld.clusters[clusternum].numreachabilityareas++; + } //end if + else + { + portal->clusterareanum[1] = cluster->numareas++; + aasworld.clusters[clusternum].numreachabilityareas++; + } //end else + } //end for + //number all areas in this cluster WITHOUT reachabilities + for (i = 1; i < aasworld.numareas; i++) + { + // + if (aasworld.areasettings[i].cluster != clusternum) continue; + // + if (AAS_AreaReachability(i)) continue; + // + aasworld.areasettings[i].clusterareanum = aasworld.clusters[clusternum].numareas; + //the cluster has an extra area + aasworld.clusters[clusternum].numareas++; + } //end for + //number all portals in this cluster WITHOUT reachabilities + cluster = &aasworld.clusters[clusternum]; + for (i = 0; i < cluster->numportals; i++) + { + portalnum = aasworld.portalindex[cluster->firstportal + i]; + portal = &aasworld.portals[portalnum]; + if (AAS_AreaReachability(portal->areanum)) continue; + if (portal->frontcluster == clusternum) + { + portal->clusterareanum[0] = cluster->numareas++; + } //end if + else + { + portal->clusterareanum[1] = cluster->numareas++; + } //end else + } //end for +} //end of the function AAS_NumberClusterAreas +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_FindClusters(void) +{ + int i; + aas_cluster_t *cluster; + + AAS_RemoveClusterAreas(); + // + for (i = 1; i < aasworld.numareas; i++) + { + //if the area is already part of a cluster + if (aasworld.areasettings[i].cluster) + continue; + // if not flooding through faces only use areas that have reachabilities + if (nofaceflood) + { + if (!aasworld.areasettings[i].numreachableareas) + continue; + } //end if + //if the area is a cluster portal + if (aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL) + continue; + if (aasworld.numclusters >= AAS_MAX_CLUSTERS) + { + AAS_Error("AAS_MAX_CLUSTERS"); + return qfalse; + } //end if + cluster = &aasworld.clusters[aasworld.numclusters]; + cluster->numareas = 0; + cluster->numreachabilityareas = 0; + cluster->firstportal = aasworld.portalindexsize; + cluster->numportals = 0; + //flood the areas in this cluster + if (!AAS_FloodClusterAreas_r(i, aasworld.numclusters)) + return qfalse; + if (!AAS_FloodClusterAreasUsingReachabilities(aasworld.numclusters)) + return qfalse; + //number the cluster areas + //AAS_NumberClusterPortals(aasworld.numclusters); + AAS_NumberClusterAreas(aasworld.numclusters); + //Log_Write("cluster %d has %d areas\r\n", aasworld.numclusters, cluster->numareas); + aasworld.numclusters++; + } //end for + return qtrue; +} //end of the function AAS_FindClusters +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CreatePortals(void) +{ + int i; + aas_portal_t *portal; + + for (i = 1; i < aasworld.numareas; i++) + { + //if the area is a cluster portal + if (aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL) + { + if (aasworld.numportals >= AAS_MAX_PORTALS) + { + AAS_Error("AAS_MAX_PORTALS"); + return; + } //end if + portal = &aasworld.portals[aasworld.numportals]; + portal->areanum = i; + portal->frontcluster = 0; + portal->backcluster = 0; + aasworld.numportals++; + } //end if + } //end for +} //end of the function AAS_CreatePortals +/* +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_MapContainsTeleporters(void) +{ + bsp_entity_t *entities, *ent; + char *classname; + + entities = AAS_ParseBSPEntities(); + + for (ent = entities; ent; ent = ent->next) + { + classname = AAS_ValueForBSPEpairKey(ent, "classname"); + if (classname && !strcmp(classname, "misc_teleporter")) + { + AAS_FreeBSPEntities(entities); + return qtrue; + } //end if + } //end for + return qfalse; +} //end of the function AAS_MapContainsTeleporters +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_NonConvexFaces(aas_face_t *face1, aas_face_t *face2, int side1, int side2) +{ + int i, j, edgenum; + aas_plane_t *plane1, *plane2; + aas_edge_t *edge; + + + plane1 = &aasworld.planes[face1->planenum ^ side1]; + plane2 = &aasworld.planes[face2->planenum ^ side2]; + + //check if one of the points of face1 is at the back of the plane of face2 + for (i = 0; i < face1->numedges; i++) + { + edgenum = abs(aasworld.edgeindex[face1->firstedge + i]); + edge = &aasworld.edges[edgenum]; + for (j = 0; j < 2; j++) + { + if (DotProduct(plane2->normal, aasworld.vertexes[edge->v[j]]) - + plane2->dist < -0.01) return qtrue; + } //end for + } //end for + for (i = 0; i < face2->numedges; i++) + { + edgenum = abs(aasworld.edgeindex[face2->firstedge + i]); + edge = &aasworld.edges[edgenum]; + for (j = 0; j < 2; j++) + { + if (DotProduct(plane1->normal, aasworld.vertexes[edge->v[j]]) - + plane1->dist < -0.01) return qtrue; + } //end for + } //end for + + return qfalse; +} //end of the function AAS_NonConvexFaces +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_CanMergeAreas(int *areanums, int numareas) +{ + int i, j, s, face1num, face2num, side1, side2, fn1, fn2; + aas_face_t *face1, *face2; + aas_area_t *area1, *area2; + + for (i = 0; i < numareas; i++) + { + area1 = &aasworld.areas[areanums[i]]; + for (fn1 = 0; fn1 < area1->numfaces; fn1++) + { + face1num = abs(aasworld.faceindex[area1->firstface + fn1]); + face1 = &aasworld.faces[face1num]; + side1 = face1->frontarea != areanums[i]; + //check if the face isn't a shared one with one of the other areas + for (s = 0; s < numareas; s++) + { + if (s == i) continue; + if (face1->frontarea == s || face1->backarea == s) break; + } //end for + //if the face was a shared one + if (s != numareas) continue; + // + for (j = 0; j < numareas; j++) + { + if (j == i) continue; + area2 = &aasworld.areas[areanums[j]]; + for (fn2 = 0; fn2 < area2->numfaces; fn2++) + { + face2num = abs(aasworld.faceindex[area2->firstface + fn2]); + face2 = &aasworld.faces[face2num]; + side2 = face2->frontarea != areanums[j]; + //check if the face isn't a shared one with one of the other areas + for (s = 0; s < numareas; s++) + { + if (s == j) continue; + if (face2->frontarea == s || face2->backarea == s) break; + } //end for + //if the face was a shared one + if (s != numareas) continue; + // + if (AAS_NonConvexFaces(face1, face2, side1, side2)) return qfalse; + } //end for + } //end for + } //end for + } //end for + return qtrue; +} //end of the function AAS_CanMergeAreas +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_NonConvexEdges(aas_edge_t *edge1, aas_edge_t *edge2, int side1, int side2, int planenum) +{ + int i; + vec3_t edgevec1, edgevec2, normal1, normal2; + float dist1, dist2; + aas_plane_t *plane; + + plane = &aasworld.planes[planenum]; + VectorSubtract(aasworld.vertexes[edge1->v[1]], aasworld.vertexes[edge1->v[0]], edgevec1); + VectorSubtract(aasworld.vertexes[edge2->v[1]], aasworld.vertexes[edge2->v[0]], edgevec2); + if (side1) VectorInverse(edgevec1); + if (side2) VectorInverse(edgevec2); + // + CrossProduct(edgevec1, plane->normal, normal1); + dist1 = DotProduct(normal1, aasworld.vertexes[edge1->v[0]]); + CrossProduct(edgevec2, plane->normal, normal2); + dist2 = DotProduct(normal2, aasworld.vertexes[edge2->v[0]]); + + for (i = 0; i < 2; i++) + { + if (DotProduct(aasworld.vertexes[edge1->v[i]], normal2) - dist2 < -0.01) return qfalse; + } //end for + for (i = 0; i < 2; i++) + { + if (DotProduct(aasworld.vertexes[edge2->v[i]], normal1) - dist1 < -0.01) return qfalse; + } //end for + return qtrue; +} //end of the function AAS_NonConvexEdges +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_CanMergeFaces(int *facenums, int numfaces, int planenum) +{ + int i, j, s, edgenum1, edgenum2, side1, side2, en1, en2, ens; + aas_face_t *face1, *face2, *otherface; + aas_edge_t *edge1, *edge2; + + for (i = 0; i < numfaces; i++) + { + face1 = &aasworld.faces[facenums[i]]; + for (en1 = 0; en1 < face1->numedges; en1++) + { + edgenum1 = aasworld.edgeindex[face1->firstedge + en1]; + side1 = (edgenum1 < 0) ^ (face1->planenum != planenum); + edgenum1 = abs(edgenum1); + edge1 = &aasworld.edges[edgenum1]; + //check if the edge is shared with another face + for (s = 0; s < numfaces; s++) + { + if (s == i) continue; + otherface = &aasworld.faces[facenums[s]]; + for (ens = 0; ens < otherface->numedges; ens++) + { + if (edgenum1 == abs(aasworld.edgeindex[otherface->firstedge + ens])) break; + } //end for + if (ens != otherface->numedges) break; + } //end for + //if the edge was shared + if (s != numfaces) continue; + // + for (j = 0; j < numfaces; j++) + { + if (j == i) continue; + face2 = &aasworld.faces[facenums[j]]; + for (en2 = 0; en2 < face2->numedges; en2++) + { + edgenum2 = aasworld.edgeindex[face2->firstedge + en2]; + side2 = (edgenum2 < 0) ^ (face2->planenum != planenum); + edgenum2 = abs(edgenum2); + edge2 = &aasworld.edges[edgenum2]; + //check if the edge is shared with another face + for (s = 0; s < numfaces; s++) + { + if (s == i) continue; + otherface = &aasworld.faces[facenums[s]]; + for (ens = 0; ens < otherface->numedges; ens++) + { + if (edgenum2 == abs(aasworld.edgeindex[otherface->firstedge + ens])) break; + } //end for + if (ens != otherface->numedges) break; + } //end for + //if the edge was shared + if (s != numfaces) continue; + // + if (AAS_NonConvexEdges(edge1, edge2, side1, side2, planenum)) return qfalse; + } //end for + } //end for + } //end for + } //end for + return qtrue; +} //end of the function AAS_CanMergeFaces*/ +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ConnectedAreas_r(int *areanums, int numareas, int *connectedareas, int curarea) +{ + int i, j, otherareanum, facenum; + aas_area_t *area; + aas_face_t *face; + + connectedareas[curarea] = qtrue; + area = &aasworld.areas[areanums[curarea]]; + for (i = 0; i < area->numfaces; i++) + { + facenum = abs(aasworld.faceindex[area->firstface + i]); + face = &aasworld.faces[facenum]; + //if the face is solid + if (face->faceflags & FACE_SOLID) continue; + //get the area at the other side of the face + if (face->frontarea != areanums[curarea]) otherareanum = face->frontarea; + else otherareanum = face->backarea; + //check if the face is leading to one of the other areas + for (j = 0; j < numareas; j++) + { + if (areanums[j] == otherareanum) break; + } //end for + //if the face isn't leading to one of the other areas + if (j == numareas) continue; + //if the other area is already connected + if (connectedareas[j]) continue; + //recursively proceed with the other area + AAS_ConnectedAreas_r(areanums, numareas, connectedareas, j); + } //end for +} //end of the function AAS_ConnectedAreas_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_ConnectedAreas(int *areanums, int numareas) +{ + int connectedareas[MAX_PORTALAREAS], i; + + Com_Memset(connectedareas, 0, sizeof(connectedareas)); + if (numareas < 1) return qfalse; + if (numareas == 1) return qtrue; + AAS_ConnectedAreas_r(areanums, numareas, connectedareas, 0); + for (i = 0; i < numareas; i++) + { + if (!connectedareas[i]) return qfalse; + } //end for + return qtrue; +} //end of the function AAS_ConnectedAreas +//=========================================================================== +// gets adjacent areas with less presence types recursively +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_GetAdjacentAreasWithLessPresenceTypes_r(int *areanums, int numareas, int curareanum) +{ + int i, j, presencetype, otherpresencetype, otherareanum, facenum; + aas_area_t *area; + aas_face_t *face; + + areanums[numareas++] = curareanum; + area = &aasworld.areas[curareanum]; + presencetype = aasworld.areasettings[curareanum].presencetype; + for (i = 0; i < area->numfaces; i++) + { + facenum = abs(aasworld.faceindex[area->firstface + i]); + face = &aasworld.faces[facenum]; + //if the face is solid + if (face->faceflags & FACE_SOLID) continue; + //the area at the other side of the face + if (face->frontarea != curareanum) otherareanum = face->frontarea; + else otherareanum = face->backarea; + // + otherpresencetype = aasworld.areasettings[otherareanum].presencetype; + //if the other area has less presence types + if ((presencetype & ~otherpresencetype) && + !(otherpresencetype & ~presencetype)) + { + //check if the other area isn't already in the list + for (j = 0; j < numareas; j++) + { + if (otherareanum == areanums[j]) break; + } //end for + //if the other area isn't already in the list + if (j == numareas) + { + if (numareas >= MAX_PORTALAREAS) + { + AAS_Error("MAX_PORTALAREAS"); + return numareas; + } //end if + numareas = AAS_GetAdjacentAreasWithLessPresenceTypes_r(areanums, numareas, otherareanum); + } //end if + } //end if + } //end for + return numareas; +} //end of the function AAS_GetAdjacentAreasWithLessPresenceTypes_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_CheckAreaForPossiblePortals(int areanum) +{ + int i, j, k, fen, ben, frontedgenum, backedgenum, facenum; + int areanums[MAX_PORTALAREAS], numareas, otherareanum; + int numareafrontfaces[MAX_PORTALAREAS], numareabackfaces[MAX_PORTALAREAS]; + int frontfacenums[MAX_PORTALAREAS], backfacenums[MAX_PORTALAREAS]; + int numfrontfaces, numbackfaces; + int frontareanums[MAX_PORTALAREAS], backareanums[MAX_PORTALAREAS]; + int numfrontareas, numbackareas; + int frontplanenum, backplanenum, faceplanenum; + aas_area_t *area; + aas_face_t *frontface, *backface, *face; + + //if it isn't already a portal + if (aasworld.areasettings[areanum].contents & AREACONTENTS_CLUSTERPORTAL) return 0; + //it must be a grounded area + if (!(aasworld.areasettings[areanum].areaflags & AREA_GROUNDED)) return 0; + // + Com_Memset(numareafrontfaces, 0, sizeof(numareafrontfaces)); + Com_Memset(numareabackfaces, 0, sizeof(numareabackfaces)); + numareas = numfrontfaces = numbackfaces = 0; + numfrontareas = numbackareas = 0; + frontplanenum = backplanenum = -1; + //add any adjacent areas with less presence types + numareas = AAS_GetAdjacentAreasWithLessPresenceTypes_r(areanums, 0, areanum); + // + for (i = 0; i < numareas; i++) + { + area = &aasworld.areas[areanums[i]]; + for (j = 0; j < area->numfaces; j++) + { + facenum = abs(aasworld.faceindex[area->firstface + j]); + face = &aasworld.faces[facenum]; + //if the face is solid + if (face->faceflags & FACE_SOLID) continue; + //check if the face is shared with one of the other areas + for (k = 0; k < numareas; k++) + { + if (k == i) continue; + if (face->frontarea == areanums[k] || face->backarea == areanums[k]) break; + } //end for + //if the face is shared + if (k != numareas) continue; + //the number of the area at the other side of the face + if (face->frontarea == areanums[i]) otherareanum = face->backarea; + else otherareanum = face->frontarea; + //if the other area already is a cluter portal + if (aasworld.areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) return 0; + //number of the plane of the area + faceplanenum = face->planenum & ~1; + // + if (frontplanenum < 0 || faceplanenum == frontplanenum) + { + frontplanenum = faceplanenum; + frontfacenums[numfrontfaces++] = facenum; + for (k = 0; k < numfrontareas; k++) + { + if (frontareanums[k] == otherareanum) break; + } //end for + if (k == numfrontareas) frontareanums[numfrontareas++] = otherareanum; + numareafrontfaces[i]++; + } //end if + else if (backplanenum < 0 || faceplanenum == backplanenum) + { + backplanenum = faceplanenum; + backfacenums[numbackfaces++] = facenum; + for (k = 0; k < numbackareas; k++) + { + if (backareanums[k] == otherareanum) break; + } //end for + if (k == numbackareas) backareanums[numbackareas++] = otherareanum; + numareabackfaces[i]++; + } //end else + else + { + return 0; + } //end else + } //end for + } //end for + //every area should have at least one front face and one back face + for (i = 0; i < numareas; i++) + { + if (!numareafrontfaces[i] || !numareabackfaces[i]) return 0; + } //end for + //the front areas should all be connected + if (!AAS_ConnectedAreas(frontareanums, numfrontareas)) return 0; + //the back areas should all be connected + if (!AAS_ConnectedAreas(backareanums, numbackareas)) return 0; + //none of the front faces should have a shared edge with a back face + for (i = 0; i < numfrontfaces; i++) + { + frontface = &aasworld.faces[frontfacenums[i]]; + for (fen = 0; fen < frontface->numedges; fen++) + { + frontedgenum = abs(aasworld.edgeindex[frontface->firstedge + fen]); + for (j = 0; j < numbackfaces; j++) + { + backface = &aasworld.faces[backfacenums[j]]; + for (ben = 0; ben < backface->numedges; ben++) + { + backedgenum = abs(aasworld.edgeindex[backface->firstedge + ben]); + if (frontedgenum == backedgenum) break; + } //end for + if (ben != backface->numedges) break; + } //end for + if (j != numbackfaces) break; + } //end for + if (fen != frontface->numedges) break; + } //end for + if (i != numfrontfaces) return 0; + //set the cluster portal contents + for (i = 0; i < numareas; i++) + { + aasworld.areasettings[areanums[i]].contents |= AREACONTENTS_CLUSTERPORTAL; + //this area can be used as a route portal + aasworld.areasettings[areanums[i]].contents |= AREACONTENTS_ROUTEPORTAL; + Log_Write("possible portal: %d\r\n", areanums[i]); + } //end for + // + return numareas; +} //end of the function AAS_CheckAreaForPossiblePortals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FindPossiblePortals(void) +{ + int i, numpossibleportals; + + numpossibleportals = 0; + for (i = 1; i < aasworld.numareas; i++) + { + numpossibleportals += AAS_CheckAreaForPossiblePortals(i); + } //end for + botimport.Print(PRT_MESSAGE, "\r%6d possible portal areas\n", numpossibleportals); +} //end of the function AAS_FindPossiblePortals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RemoveAllPortals(void) +{ + int i; + + for (i = 1; i < aasworld.numareas; i++) + { + aasworld.areasettings[i].contents &= ~AREACONTENTS_CLUSTERPORTAL; + } //end for +} //end of the function AAS_RemoveAllPortals + +#if 0 +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FloodCluster_r(int areanum, int clusternum) +{ + int i, otherareanum; + aas_face_t *face; + aas_area_t *area; + + //set cluster mark + aasworld.areasettings[areanum].cluster = clusternum; + //if the area is a portal + //if (aasworld.areasettings[areanum].contents & AREACONTENTS_CLUSTERPORTAL) return; + // + area = &aasworld.areas[areanum]; + //use area faces to flood into adjacent areas + for (i = 0; i < area->numfaces; i++) + { + face = &aasworld.faces[abs(aasworld.faceindex[area->firstface + i])]; + // + if (face->frontarea != areanum) otherareanum = face->frontarea; + else otherareanum = face->backarea; + //if there's no area at the other side + if (!otherareanum) continue; + //if the area is a portal + if (aasworld.areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) continue; + //if the area is already marked + if (aasworld.areasettings[otherareanum].cluster) continue; + // + AAS_FloodCluster_r(otherareanum, clusternum); + } //end for + //use the reachabilities to flood into other areas + for (i = 0; i < aasworld.areasettings[areanum].numreachableareas; i++) + { + otherareanum = aasworld.reachability[ + aasworld.areasettings[areanum].firstreachablearea + i].areanum; + if (!otherareanum) + { + continue; + AAS_Error("reachability %d has zero area\n", aasworld.areasettings[areanum].firstreachablearea + i); + } //end if + //if the area is a portal + if (aasworld.areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) continue; + //if the area is already marked + if (aasworld.areasettings[otherareanum].cluster) continue; + // + AAS_FloodCluster_r(otherareanum, clusternum); + } //end for +} //end of the function AAS_FloodCluster_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RemoveTeleporterPortals(void) +{ + int i, j, areanum; + + for (i = 1; i < aasworld.numareas; i++) + { + for (j = 0; j < aasworld.areasettings[i].numreachableareas; j++) + { + areanum = aasworld.reachability[aasworld.areasettings[i].firstreachablearea + j].areanum; + if (aasworld.reachability[aasworld.areasettings[i].firstreachablearea + j].traveltype == TRAVEL_TELEPORT) + { + aasworld.areasettings[i].contents &= ~AREACONTENTS_CLUSTERPORTAL; + aasworld.areasettings[areanum].contents &= ~AREACONTENTS_CLUSTERPORTAL; + break; + } //end if + } //end for + } //end for +} //end of the function AAS_RemoveTeleporterPortals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FloodClusterReachabilities(int clusternum) +{ + int i, j, areanum; + + for (i = 1; i < aasworld.numareas; i++) + { + //if this area already has a cluster set + if (aasworld.areasettings[i].cluster) continue; + //if this area is a cluster portal + if (aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL) continue; + //loop over the reachable areas from this area + for (j = 0; j < aasworld.areasettings[i].numreachableareas; j++) + { + //the reachable area + areanum = aasworld.reachability[aasworld.areasettings[i].firstreachablearea + j].areanum; + //if this area is a cluster portal + if (aasworld.areasettings[areanum].contents & AREACONTENTS_CLUSTERPORTAL) continue; + //if this area has a cluster set + if (aasworld.areasettings[areanum].cluster == clusternum) + { + AAS_FloodCluster_r(i, clusternum); + i = 0; + break; + } //end if + } //end for + } //end for +} //end of the function AAS_FloodClusterReachabilities + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RemoveNotClusterClosingPortals(void) +{ + int i, j, k, facenum, otherareanum, nonclosingportals; + aas_area_t *area; + aas_face_t *face; + + AAS_RemoveTeleporterPortals(); + // + nonclosingportals = 0; + for (i = 1; i < aasworld.numareas; i++) + { + if (!(aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL)) continue; + //find a non-portal area adjacent to the portal area and flood + //the cluster from there + area = &aasworld.areas[i]; + for (j = 0; j < area->numfaces; j++) + { + facenum = abs(aasworld.faceindex[area->firstface + j]); + face = &aasworld.faces[facenum]; + // + if (face->frontarea != i) otherareanum = face->frontarea; + else otherareanum = face->backarea; + // + if (!otherareanum) continue; + // + if (aasworld.areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) + { + continue; + } //end if + //reset all cluster fields + AAS_RemoveClusterAreas(); + // + AAS_FloodCluster_r(otherareanum, 1); + AAS_FloodClusterReachabilities(1); + //check if all adjacent non-portal areas have a cluster set + for (k = 0; k < area->numfaces; k++) + { + facenum = abs(aasworld.faceindex[area->firstface + k]); + face = &aasworld.faces[facenum]; + // + if (face->frontarea != i) otherareanum = face->frontarea; + else otherareanum = face->backarea; + // + if (!otherareanum) continue; + // + if (aasworld.areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) + { + continue; + } //end if + // + if (!aasworld.areasettings[otherareanum].cluster) break; + } //end for + //if all adjacent non-portal areas have a cluster set then the portal + //didn't seal a cluster + if (k >= area->numfaces) + { + aasworld.areasettings[i].contents &= ~AREACONTENTS_CLUSTERPORTAL; + nonclosingportals++; + //recheck all the other portals again + i = 0; + break; + } //end if + } //end for + } //end for + botimport.Print(PRT_MESSAGE, "\r%6d non closing portals removed\n", nonclosingportals); +} //end of the function AAS_RemoveNotClusterClosingPortals + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== + +void AAS_RemoveNotClusterClosingPortals(void) +{ + int i, j, facenum, otherareanum, nonclosingportals, numseperatedclusters; + aas_area_t *area; + aas_face_t *face; + + AAS_RemoveTeleporterPortals(); + // + nonclosingportals = 0; + for (i = 1; i < aasworld.numareas; i++) + { + if (!(aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL)) continue; + // + numseperatedclusters = 0; + //reset all cluster fields + AAS_RemoveClusterAreas(); + //find a non-portal area adjacent to the portal area and flood + //the cluster from there + area = &aasworld.areas[i]; + for (j = 0; j < area->numfaces; j++) + { + facenum = abs(aasworld.faceindex[area->firstface + j]); + face = &aasworld.faces[facenum]; + // + if (face->frontarea != i) otherareanum = face->frontarea; + else otherareanum = face->backarea; + //if not solid at the other side of the face + if (!otherareanum) continue; + //don't flood into other portals + if (aasworld.areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) continue; + //if the area already has a cluster set + if (aasworld.areasettings[otherareanum].cluster) continue; + //another cluster is seperated by this portal + numseperatedclusters++; + //flood the cluster + AAS_FloodCluster_r(otherareanum, numseperatedclusters); + AAS_FloodClusterReachabilities(numseperatedclusters); + } //end for + //use the reachabilities to flood into other areas + for (j = 0; j < aasworld.areasettings[i].numreachableareas; j++) + { + otherareanum = aasworld.reachability[ + aasworld.areasettings[i].firstreachablearea + j].areanum; + //this should never be qtrue but we check anyway + if (!otherareanum) continue; + //don't flood into other portals + if (aasworld.areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) continue; + //if the area already has a cluster set + if (aasworld.areasettings[otherareanum].cluster) continue; + //another cluster is seperated by this portal + numseperatedclusters++; + //flood the cluster + AAS_FloodCluster_r(otherareanum, numseperatedclusters); + AAS_FloodClusterReachabilities(numseperatedclusters); + } //end for + //a portal must seperate no more and no less than 2 clusters + if (numseperatedclusters != 2) + { + aasworld.areasettings[i].contents &= ~AREACONTENTS_CLUSTERPORTAL; + nonclosingportals++; + //recheck all the other portals again + i = 0; + } //end if + } //end for + botimport.Print(PRT_MESSAGE, "\r%6d non closing portals removed\n", nonclosingportals); +} //end of the function AAS_RemoveNotClusterClosingPortals + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== + +void AAS_AddTeleporterPortals(void) +{ + int j, area2num, facenum, otherareanum; + char *target, *targetname, *classname; + bsp_entity_t *entities, *ent, *dest; + vec3_t origin, destorigin, mins, maxs, end; + vec3_t bbmins, bbmaxs; + aas_area_t *area; + aas_face_t *face; + aas_trace_t trace; + aas_link_t *areas, *link; + + entities = AAS_ParseBSPEntities(); + + for (ent = entities; ent; ent = ent->next) + { + classname = AAS_ValueForBSPEpairKey(ent, "classname"); + if (classname && !strcmp(classname, "misc_teleporter")) + { + if (!AAS_VectorForBSPEpairKey(ent, "origin", origin)) + { + botimport.Print(PRT_ERROR, "teleporter (%s) without origin\n", target); + continue; + } //end if + // + target = AAS_ValueForBSPEpairKey(ent, "target"); + if (!target) + { + botimport.Print(PRT_ERROR, "teleporter (%s) without target\n", target); + continue; + } //end if + for (dest = entities; dest; dest = dest->next) + { + classname = AAS_ValueForBSPEpairKey(dest, "classname"); + if (classname && !strcmp(classname, "misc_teleporter_dest")) + { + targetname = AAS_ValueForBSPEpairKey(dest, "targetname"); + if (targetname && !strcmp(targetname, target)) + { + break; + } //end if + } //end if + } //end for + if (!dest) + { + botimport.Print(PRT_ERROR, "teleporter without destination (%s)\n", target); + continue; + } //end if + if (!AAS_VectorForBSPEpairKey(dest, "origin", destorigin)) + { + botimport.Print(PRT_ERROR, "teleporter destination (%s) without origin\n", target); + continue; + } //end if + destorigin[2] += 24; //just for q2e1m2, the dork has put the telepads in the ground + VectorCopy(destorigin, end); + end[2] -= 100; + trace = AAS_TraceClientBBox(destorigin, end, PRESENCE_CROUCH, -1); + if (trace.startsolid) + { + botimport.Print(PRT_ERROR, "teleporter destination (%s) in solid\n", target); + continue; + } //end if + VectorCopy(trace.endpos, destorigin); + area2num = AAS_PointAreaNum(destorigin); + //reset all cluster fields + for (j = 0; j < aasworld.numareas; j++) + { + aasworld.areasettings[j].cluster = 0; + } //end for + // + VectorSet(mins, -8, -8, 8); + VectorSet(maxs, 8, 8, 24); + // + AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, bbmins, bbmaxs); + // + VectorAdd(origin, mins, mins); + VectorAdd(origin, maxs, maxs); + //add bounding box size + VectorSubtract(mins, bbmaxs, mins); + VectorSubtract(maxs, bbmins, maxs); + //link an invalid (-1) entity + areas = AAS_AASLinkEntity(mins, maxs, -1); + // + for (link = areas; link; link = link->next_area) + { + if (!AAS_AreaGrounded(link->areanum)) continue; + //add the teleporter portal mark + aasworld.areasettings[link->areanum].contents |= AREACONTENTS_CLUSTERPORTAL | + AREACONTENTS_TELEPORTAL; + } //end for + // + for (link = areas; link; link = link->next_area) + { + if (!AAS_AreaGrounded(link->areanum)) continue; + //find a non-portal area adjacent to the portal area and flood + //the cluster from there + area = &aasworld.areas[link->areanum]; + for (j = 0; j < area->numfaces; j++) + { + facenum = abs(aasworld.faceindex[area->firstface + j]); + face = &aasworld.faces[facenum]; + // + if (face->frontarea != link->areanum) otherareanum = face->frontarea; + else otherareanum = face->backarea; + // + if (!otherareanum) continue; + // + if (aasworld.areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) + { + continue; + } //end if + // + AAS_FloodCluster_r(otherareanum, 1); + } //end for + } //end for + //if the teleport destination IS in the same cluster + if (aasworld.areasettings[area2num].cluster) + { + for (link = areas; link; link = link->next_area) + { + if (!AAS_AreaGrounded(link->areanum)) continue; + //add the teleporter portal mark + aasworld.areasettings[link->areanum].contents &= ~(AREACONTENTS_CLUSTERPORTAL | + AREACONTENTS_TELEPORTAL); + } //end for + } //end if + } //end if + } //end for + AAS_FreeBSPEntities(entities); +} //end of the function AAS_AddTeleporterPortals + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_AddTeleporterPortals(void) +{ + int i, j, areanum; + + for (i = 1; i < aasworld.numareas; i++) + { + for (j = 0; j < aasworld.areasettings[i].numreachableareas; j++) + { + if (aasworld.reachability[aasworld.areasettings[i].firstreachablearea + j].traveltype != TRAVEL_TELEPORT) continue; + areanum = aasworld.reachability[aasworld.areasettings[i].firstreachablearea + j].areanum; + aasworld.areasettings[areanum].contents |= AREACONTENTS_CLUSTERPORTAL; + } //end for + } //end for +} //end of the function AAS_AddTeleporterPortals + +#endif + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_TestPortals(void) +{ + int i; + aas_portal_t *portal; + + for (i = 1; i < aasworld.numportals; i++) + { + portal = &aasworld.portals[i]; + if (!portal->frontcluster) + { + aasworld.areasettings[portal->areanum].contents &= ~AREACONTENTS_CLUSTERPORTAL; + Log_Write("portal area %d has no front cluster\r\n", portal->areanum); + return qfalse; + } //end if + if (!portal->backcluster) + { + aasworld.areasettings[portal->areanum].contents &= ~AREACONTENTS_CLUSTERPORTAL; + Log_Write("portal area %d has no back cluster\r\n", portal->areanum); + return qfalse; + } //end if + } //end for + return qtrue; +} //end of the function +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CountForcedClusterPortals(void) +{ + int num, i; + + num = 0; + for (i = 1; i < aasworld.numareas; i++) + { + if (aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL) + { + Log_Write("area %d is a forced portal area\r\n", i); + num++; + } //end if + } //end for + botimport.Print(PRT_MESSAGE, "%6d forced portal areas\n", num); +} //end of the function AAS_CountForcedClusterPortals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CreateViewPortals(void) +{ + int i; + + for (i = 1; i < aasworld.numareas; i++) + { + if (aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL) + { + aasworld.areasettings[i].contents |= AREACONTENTS_VIEWPORTAL; + } //end if + } //end for +} //end of the function AAS_CreateViewPortals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_SetViewPortalsAsClusterPortals(void) +{ + int i; + + for (i = 1; i < aasworld.numareas; i++) + { + if (aasworld.areasettings[i].contents & AREACONTENTS_VIEWPORTAL) + { + aasworld.areasettings[i].contents |= AREACONTENTS_CLUSTERPORTAL; + } //end if + } //end for +} //end of the function AAS_SetViewPortalsAsClusterPortals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitClustering(void) +{ + int i, removedPortalAreas; + int n, total, numreachabilityareas; + + if (!aasworld.loaded) return; + //if there are clusters + if (aasworld.numclusters >= 1) + { +#ifndef BSPC + //if clustering isn't forced + if (!((int)LibVarGetValue("forceclustering")) && + !((int)LibVarGetValue("forcereachability"))) return; +#endif + } //end if + //set all view portals as cluster portals in case we re-calculate the reachabilities and clusters (with -reach) + AAS_SetViewPortalsAsClusterPortals(); + //count the number of forced cluster portals + AAS_CountForcedClusterPortals(); + //remove all area cluster marks + AAS_RemoveClusterAreas(); + //find possible cluster portals + AAS_FindPossiblePortals(); + //craete portals to for the bot view + AAS_CreateViewPortals(); + //remove all portals that are not closing a cluster + //AAS_RemoveNotClusterClosingPortals(); + //initialize portal memory + if (aasworld.portals) FreeMemory(aasworld.portals); + aasworld.portals = (aas_portal_t *) GetClearedMemory(AAS_MAX_PORTALS * sizeof(aas_portal_t)); + //initialize portal index memory + if (aasworld.portalindex) FreeMemory(aasworld.portalindex); + aasworld.portalindex = (aas_portalindex_t *) GetClearedMemory(AAS_MAX_PORTALINDEXSIZE * sizeof(aas_portalindex_t)); + //initialize cluster memory + if (aasworld.clusters) FreeMemory(aasworld.clusters); + aasworld.clusters = (aas_cluster_t *) GetClearedMemory(AAS_MAX_CLUSTERS * sizeof(aas_cluster_t)); + // + removedPortalAreas = 0; + botimport.Print(PRT_MESSAGE, "\r%6d removed portal areas", removedPortalAreas); + while(1) + { + botimport.Print(PRT_MESSAGE, "\r%6d", removedPortalAreas); + //initialize the number of portals and clusters + aasworld.numportals = 1; //portal 0 is a dummy + aasworld.portalindexsize = 0; + aasworld.numclusters = 1; //cluster 0 is a dummy + //create the portals from the portal areas + AAS_CreatePortals(); + // + removedPortalAreas++; + //find the clusters + if (!AAS_FindClusters()) + continue; + //test the portals + if (!AAS_TestPortals()) + continue; + // + break; + } //end while + botimport.Print(PRT_MESSAGE, "\n"); + //the AAS file should be saved + aasworld.savefile = qtrue; + //write the portal areas to the log file + for (i = 1; i < aasworld.numportals; i++) + { + Log_Write("portal %d: area %d\r\n", i, aasworld.portals[i].areanum); + } //end for + // report cluster info + botimport.Print(PRT_MESSAGE, "%6d portals created\n", aasworld.numportals); + botimport.Print(PRT_MESSAGE, "%6d clusters created\n", aasworld.numclusters); + for (i = 1; i < aasworld.numclusters; i++) + { + botimport.Print(PRT_MESSAGE, "cluster %d has %d reachability areas\n", i, + aasworld.clusters[i].numreachabilityareas); + } //end for + // report AAS file efficiency + numreachabilityareas = 0; + total = 0; + for (i = 0; i < aasworld.numclusters; i++) { + n = aasworld.clusters[i].numreachabilityareas; + numreachabilityareas += n; + total += n * n; + } + total += numreachabilityareas * aasworld.numportals; + // + botimport.Print(PRT_MESSAGE, "%6i total reachability areas\n", numreachabilityareas); + botimport.Print(PRT_MESSAGE, "%6i AAS memory/CPU usage (the lower the better)\n", total * 3); +} //end of the function AAS_InitClustering diff --git a/code/botlib/be_aas_cluster.h b/code/botlib/be_aas_cluster.h index 3f52a46..86a4bfd 100755 --- a/code/botlib/be_aas_cluster.h +++ b/code/botlib/be_aas_cluster.h @@ -1,38 +1,38 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: be_aas_cluster.h - * - * desc: AAS - * - * $Archive: /source/code/botlib/be_aas_cluster.h $ - * - *****************************************************************************/ - -#ifdef AASINTERN -//initialize the AAS clustering -void AAS_InitClustering(void); -// -void AAS_SetViewPortalsAsClusterPortals(void); -#endif //AASINTERN - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_cluster.h + * + * desc: AAS + * + * $Archive: /source/code/botlib/be_aas_cluster.h $ + * + *****************************************************************************/ + +#ifdef AASINTERN +//initialize the AAS clustering +void AAS_InitClustering(void); +// +void AAS_SetViewPortalsAsClusterPortals(void); +#endif //AASINTERN + diff --git a/code/botlib/be_aas_debug.c b/code/botlib/be_aas_debug.c index 6271677..a665c0c 100755 --- a/code/botlib/be_aas_debug.c +++ b/code/botlib/be_aas_debug.c @@ -1,777 +1,777 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: be_aas_debug.c - * - * desc: AAS debug code - * - * $Archive: /MissionPack/code/botlib/be_aas_debug.c $ - * - *****************************************************************************/ - -#include "../game/q_shared.h" -#include "l_memory.h" -#include "l_script.h" -#include "l_precomp.h" -#include "l_struct.h" -#include "l_libvar.h" -#include "aasfile.h" -#include "../game/botlib.h" -#include "../game/be_aas.h" -#include "be_interface.h" -#include "be_aas_funcs.h" -#include "be_aas_def.h" - -#define MAX_DEBUGLINES 1024 -#define MAX_DEBUGPOLYGONS 8192 - -int debuglines[MAX_DEBUGLINES]; -int debuglinevisible[MAX_DEBUGLINES]; -int numdebuglines; - -static int debugpolygons[MAX_DEBUGPOLYGONS]; - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_ClearShownPolygons(void) -{ - int i; -//* - for (i = 0; i < MAX_DEBUGPOLYGONS; i++) - { - if (debugpolygons[i]) botimport.DebugPolygonDelete(debugpolygons[i]); - debugpolygons[i] = 0; - } //end for -//*/ -/* - for (i = 0; i < MAX_DEBUGPOLYGONS; i++) - { - botimport.DebugPolygonDelete(i); - debugpolygons[i] = 0; - } //end for -*/ -} //end of the function AAS_ClearShownPolygons -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_ShowPolygon(int color, int numpoints, vec3_t *points) -{ - int i; - - for (i = 0; i < MAX_DEBUGPOLYGONS; i++) - { - if (!debugpolygons[i]) - { - debugpolygons[i] = botimport.DebugPolygonCreate(color, numpoints, points); - break; - } //end if - } //end for -} //end of the function AAS_ShowPolygon -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_ClearShownDebugLines(void) -{ - int i; - - //make all lines invisible - for (i = 0; i < MAX_DEBUGLINES; i++) - { - if (debuglines[i]) - { - //botimport.DebugLineShow(debuglines[i], NULL, NULL, LINECOLOR_NONE); - botimport.DebugLineDelete(debuglines[i]); - debuglines[i] = 0; - debuglinevisible[i] = qfalse; - } //end if - } //end for -} //end of the function AAS_ClearShownDebugLines -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_DebugLine(vec3_t start, vec3_t end, int color) -{ - int line; - - for (line = 0; line < MAX_DEBUGLINES; line++) - { - if (!debuglines[line]) - { - debuglines[line] = botimport.DebugLineCreate(); - debuglinevisible[line] = qfalse; - numdebuglines++; - } //end if - if (!debuglinevisible[line]) - { - botimport.DebugLineShow(debuglines[line], start, end, color); - debuglinevisible[line] = qtrue; - return; - } //end else - } //end for -} //end of the function AAS_DebugLine -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_PermanentLine(vec3_t start, vec3_t end, int color) -{ - int line; - - line = botimport.DebugLineCreate(); - botimport.DebugLineShow(line, start, end, color); -} //end of the function AAS_PermenentLine -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_DrawPermanentCross(vec3_t origin, float size, int color) -{ - int i, debugline; - vec3_t start, end; - - for (i = 0; i < 3; i++) - { - VectorCopy(origin, start); - start[i] += size; - VectorCopy(origin, end); - end[i] -= size; - AAS_DebugLine(start, end, color); - debugline = botimport.DebugLineCreate(); - botimport.DebugLineShow(debugline, start, end, color); - } //end for -} //end of the function AAS_DrawPermanentCross -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_DrawPlaneCross(vec3_t point, vec3_t normal, float dist, int type, int color) -{ - int n0, n1, n2, j, line, lines[2]; - vec3_t start1, end1, start2, end2; - - //make a cross in the hit plane at the hit point - VectorCopy(point, start1); - VectorCopy(point, end1); - VectorCopy(point, start2); - VectorCopy(point, end2); - - n0 = type % 3; - n1 = (type + 1) % 3; - n2 = (type + 2) % 3; - start1[n1] -= 6; - start1[n2] -= 6; - end1[n1] += 6; - end1[n2] += 6; - start2[n1] += 6; - start2[n2] -= 6; - end2[n1] -= 6; - end2[n2] += 6; - - start1[n0] = (dist - (start1[n1] * normal[n1] + - start1[n2] * normal[n2])) / normal[n0]; - end1[n0] = (dist - (end1[n1] * normal[n1] + - end1[n2] * normal[n2])) / normal[n0]; - start2[n0] = (dist - (start2[n1] * normal[n1] + - start2[n2] * normal[n2])) / normal[n0]; - end2[n0] = (dist - (end2[n1] * normal[n1] + - end2[n2] * normal[n2])) / normal[n0]; - - for (j = 0, line = 0; j < 2 && line < MAX_DEBUGLINES; line++) - { - if (!debuglines[line]) - { - debuglines[line] = botimport.DebugLineCreate(); - lines[j++] = debuglines[line]; - debuglinevisible[line] = qtrue; - numdebuglines++; - } //end if - else if (!debuglinevisible[line]) - { - lines[j++] = debuglines[line]; - debuglinevisible[line] = qtrue; - } //end else - } //end for - botimport.DebugLineShow(lines[0], start1, end1, color); - botimport.DebugLineShow(lines[1], start2, end2, color); -} //end of the function AAS_DrawPlaneCross -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_ShowBoundingBox(vec3_t origin, vec3_t mins, vec3_t maxs) -{ - vec3_t bboxcorners[8]; - int lines[3]; - int i, j, line; - - //upper corners - bboxcorners[0][0] = origin[0] + maxs[0]; - bboxcorners[0][1] = origin[1] + maxs[1]; - bboxcorners[0][2] = origin[2] + maxs[2]; - // - bboxcorners[1][0] = origin[0] + mins[0]; - bboxcorners[1][1] = origin[1] + maxs[1]; - bboxcorners[1][2] = origin[2] + maxs[2]; - // - bboxcorners[2][0] = origin[0] + mins[0]; - bboxcorners[2][1] = origin[1] + mins[1]; - bboxcorners[2][2] = origin[2] + maxs[2]; - // - bboxcorners[3][0] = origin[0] + maxs[0]; - bboxcorners[3][1] = origin[1] + mins[1]; - bboxcorners[3][2] = origin[2] + maxs[2]; - //lower corners - Com_Memcpy(bboxcorners[4], bboxcorners[0], sizeof(vec3_t) * 4); - for (i = 0; i < 4; i++) bboxcorners[4 + i][2] = origin[2] + mins[2]; - //draw bounding box - for (i = 0; i < 4; i++) - { - for (j = 0, line = 0; j < 3 && line < MAX_DEBUGLINES; line++) - { - if (!debuglines[line]) - { - debuglines[line] = botimport.DebugLineCreate(); - lines[j++] = debuglines[line]; - debuglinevisible[line] = qtrue; - numdebuglines++; - } //end if - else if (!debuglinevisible[line]) - { - lines[j++] = debuglines[line]; - debuglinevisible[line] = qtrue; - } //end else - } //end for - //top plane - botimport.DebugLineShow(lines[0], bboxcorners[i], - bboxcorners[(i+1)&3], LINECOLOR_RED); - //bottom plane - botimport.DebugLineShow(lines[1], bboxcorners[4+i], - bboxcorners[4+((i+1)&3)], LINECOLOR_RED); - //vertical lines - botimport.DebugLineShow(lines[2], bboxcorners[i], - bboxcorners[4+i], LINECOLOR_RED); - } //end for -} //end of the function AAS_ShowBoundingBox -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_ShowFace(int facenum) -{ - int i, color, edgenum; - aas_edge_t *edge; - aas_face_t *face; - aas_plane_t *plane; - vec3_t start, end; - - color = LINECOLOR_YELLOW; - //check if face number is in range - if (facenum >= aasworld.numfaces) - { - botimport.Print(PRT_ERROR, "facenum %d out of range\n", facenum); - } //end if - face = &aasworld.faces[facenum]; - //walk through the edges of the face - for (i = 0; i < face->numedges; i++) - { - //edge number - edgenum = abs(aasworld.edgeindex[face->firstedge + i]); - //check if edge number is in range - if (edgenum >= aasworld.numedges) - { - botimport.Print(PRT_ERROR, "edgenum %d out of range\n", edgenum); - } //end if - edge = &aasworld.edges[edgenum]; - if (color == LINECOLOR_RED) color = LINECOLOR_GREEN; - else if (color == LINECOLOR_GREEN) color = LINECOLOR_BLUE; - else if (color == LINECOLOR_BLUE) color = LINECOLOR_YELLOW; - else color = LINECOLOR_RED; - AAS_DebugLine(aasworld.vertexes[edge->v[0]], - aasworld.vertexes[edge->v[1]], - color); - } //end for - plane = &aasworld.planes[face->planenum]; - edgenum = abs(aasworld.edgeindex[face->firstedge]); - edge = &aasworld.edges[edgenum]; - VectorCopy(aasworld.vertexes[edge->v[0]], start); - VectorMA(start, 20, plane->normal, end); - AAS_DebugLine(start, end, LINECOLOR_RED); -} //end of the function AAS_ShowFace -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_ShowFacePolygon(int facenum, int color, int flip) -{ - int i, edgenum, numpoints; - vec3_t points[128]; - aas_edge_t *edge; - aas_face_t *face; - - //check if face number is in range - if (facenum >= aasworld.numfaces) - { - botimport.Print(PRT_ERROR, "facenum %d out of range\n", facenum); - } //end if - face = &aasworld.faces[facenum]; - //walk through the edges of the face - numpoints = 0; - if (flip) - { - for (i = face->numedges-1; i >= 0; i--) - { - //edge number - edgenum = aasworld.edgeindex[face->firstedge + i]; - edge = &aasworld.edges[abs(edgenum)]; - VectorCopy(aasworld.vertexes[edge->v[edgenum < 0]], points[numpoints]); - numpoints++; - } //end for - } //end if - else - { - for (i = 0; i < face->numedges; i++) - { - //edge number - edgenum = aasworld.edgeindex[face->firstedge + i]; - edge = &aasworld.edges[abs(edgenum)]; - VectorCopy(aasworld.vertexes[edge->v[edgenum < 0]], points[numpoints]); - numpoints++; - } //end for - } //end else - AAS_ShowPolygon(color, numpoints, points); -} //end of the function AAS_ShowFacePolygon -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_ShowArea(int areanum, int groundfacesonly) -{ - int areaedges[MAX_DEBUGLINES]; - int numareaedges, i, j, n, color = 0, line; - int facenum, edgenum; - aas_area_t *area; - aas_face_t *face; - aas_edge_t *edge; - - // - numareaedges = 0; - // - if (areanum < 0 || areanum >= aasworld.numareas) - { - botimport.Print(PRT_ERROR, "area %d out of range [0, %d]\n", - areanum, aasworld.numareas); - return; - } //end if - //pointer to the convex area - area = &aasworld.areas[areanum]; - //walk through the faces of the area - for (i = 0; i < area->numfaces; i++) - { - facenum = abs(aasworld.faceindex[area->firstface + i]); - //check if face number is in range - if (facenum >= aasworld.numfaces) - { - botimport.Print(PRT_ERROR, "facenum %d out of range\n", facenum); - } //end if - face = &aasworld.faces[facenum]; - //ground faces only - if (groundfacesonly) - { - if (!(face->faceflags & (FACE_GROUND | FACE_LADDER))) continue; - } //end if - //walk through the edges of the face - for (j = 0; j < face->numedges; j++) - { - //edge number - edgenum = abs(aasworld.edgeindex[face->firstedge + j]); - //check if edge number is in range - if (edgenum >= aasworld.numedges) - { - botimport.Print(PRT_ERROR, "edgenum %d out of range\n", edgenum); - } //end if - //check if the edge is stored already - for (n = 0; n < numareaedges; n++) - { - if (areaedges[n] == edgenum) break; - } //end for - if (n == numareaedges && numareaedges < MAX_DEBUGLINES) - { - areaedges[numareaedges++] = edgenum; - } //end if - } //end for - //AAS_ShowFace(facenum); - } //end for - //draw all the edges - for (n = 0; n < numareaedges; n++) - { - for (line = 0; line < MAX_DEBUGLINES; line++) - { - if (!debuglines[line]) - { - debuglines[line] = botimport.DebugLineCreate(); - debuglinevisible[line] = qfalse; - numdebuglines++; - } //end if - if (!debuglinevisible[line]) - { - break; - } //end else - } //end for - if (line >= MAX_DEBUGLINES) return; - edge = &aasworld.edges[areaedges[n]]; - if (color == LINECOLOR_RED) color = LINECOLOR_BLUE; - else if (color == LINECOLOR_BLUE) color = LINECOLOR_GREEN; - else if (color == LINECOLOR_GREEN) color = LINECOLOR_YELLOW; - else color = LINECOLOR_RED; - botimport.DebugLineShow(debuglines[line], - aasworld.vertexes[edge->v[0]], - aasworld.vertexes[edge->v[1]], - color); - debuglinevisible[line] = qtrue; - } //end for*/ -} //end of the function AAS_ShowArea -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_ShowAreaPolygons(int areanum, int color, int groundfacesonly) -{ - int i, facenum; - aas_area_t *area; - aas_face_t *face; - - // - if (areanum < 0 || areanum >= aasworld.numareas) - { - botimport.Print(PRT_ERROR, "area %d out of range [0, %d]\n", - areanum, aasworld.numareas); - return; - } //end if - //pointer to the convex area - area = &aasworld.areas[areanum]; - //walk through the faces of the area - for (i = 0; i < area->numfaces; i++) - { - facenum = abs(aasworld.faceindex[area->firstface + i]); - //check if face number is in range - if (facenum >= aasworld.numfaces) - { - botimport.Print(PRT_ERROR, "facenum %d out of range\n", facenum); - } //end if - face = &aasworld.faces[facenum]; - //ground faces only - if (groundfacesonly) - { - if (!(face->faceflags & (FACE_GROUND | FACE_LADDER))) continue; - } //end if - AAS_ShowFacePolygon(facenum, color, face->frontarea != areanum); - } //end for -} //end of the function AAS_ShowAreaPolygons -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_DrawCross(vec3_t origin, float size, int color) -{ - int i; - vec3_t start, end; - - for (i = 0; i < 3; i++) - { - VectorCopy(origin, start); - start[i] += size; - VectorCopy(origin, end); - end[i] -= size; - AAS_DebugLine(start, end, color); - } //end for -} //end of the function AAS_DrawCross -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_PrintTravelType(int traveltype) -{ -#ifdef DEBUG - char *str; - // - switch(traveltype & TRAVELTYPE_MASK) - { - case TRAVEL_INVALID: str = "TRAVEL_INVALID"; break; - case TRAVEL_WALK: str = "TRAVEL_WALK"; break; - case TRAVEL_CROUCH: str = "TRAVEL_CROUCH"; break; - case TRAVEL_BARRIERJUMP: str = "TRAVEL_BARRIERJUMP"; break; - case TRAVEL_JUMP: str = "TRAVEL_JUMP"; break; - case TRAVEL_LADDER: str = "TRAVEL_LADDER"; break; - case TRAVEL_WALKOFFLEDGE: str = "TRAVEL_WALKOFFLEDGE"; break; - case TRAVEL_SWIM: str = "TRAVEL_SWIM"; break; - case TRAVEL_WATERJUMP: str = "TRAVEL_WATERJUMP"; break; - case TRAVEL_TELEPORT: str = "TRAVEL_TELEPORT"; break; - case TRAVEL_ELEVATOR: str = "TRAVEL_ELEVATOR"; break; - case TRAVEL_ROCKETJUMP: str = "TRAVEL_ROCKETJUMP"; break; - case TRAVEL_BFGJUMP: str = "TRAVEL_BFGJUMP"; break; - case TRAVEL_GRAPPLEHOOK: str = "TRAVEL_GRAPPLEHOOK"; break; - case TRAVEL_JUMPPAD: str = "TRAVEL_JUMPPAD"; break; - case TRAVEL_FUNCBOB: str = "TRAVEL_FUNCBOB"; break; - default: str = "UNKNOWN TRAVEL TYPE"; break; - } //end switch - botimport.Print(PRT_MESSAGE, "%s", str); -#endif -} //end of the function AAS_PrintTravelType -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_DrawArrow(vec3_t start, vec3_t end, int linecolor, int arrowcolor) -{ - vec3_t dir, cross, p1, p2, up = {0, 0, 1}; - float dot; - - VectorSubtract(end, start, dir); - VectorNormalize(dir); - dot = DotProduct(dir, up); - if (dot > 0.99 || dot < -0.99) VectorSet(cross, 1, 0, 0); - else CrossProduct(dir, up, cross); - - VectorMA(end, -6, dir, p1); - VectorCopy(p1, p2); - VectorMA(p1, 6, cross, p1); - VectorMA(p2, -6, cross, p2); - - AAS_DebugLine(start, end, linecolor); - AAS_DebugLine(p1, end, arrowcolor); - AAS_DebugLine(p2, end, arrowcolor); -} //end of the function AAS_DrawArrow -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_ShowReachability(aas_reachability_t *reach) -{ - vec3_t dir, cmdmove, velocity; - float speed, zvel; - aas_clientmove_t move; - - AAS_ShowAreaPolygons(reach->areanum, 5, qtrue); - //AAS_ShowArea(reach->areanum, qtrue); - AAS_DrawArrow(reach->start, reach->end, LINECOLOR_BLUE, LINECOLOR_YELLOW); - // - if ((reach->traveltype & TRAVELTYPE_MASK) == TRAVEL_JUMP || - (reach->traveltype & TRAVELTYPE_MASK) == TRAVEL_WALKOFFLEDGE) - { - AAS_HorizontalVelocityForJump(aassettings.phys_jumpvel, reach->start, reach->end, &speed); - // - VectorSubtract(reach->end, reach->start, dir); - dir[2] = 0; - VectorNormalize(dir); - //set the velocity - VectorScale(dir, speed, velocity); - //set the command movement - VectorClear(cmdmove); - cmdmove[2] = aassettings.phys_jumpvel; - // - AAS_PredictClientMovement(&move, -1, reach->start, PRESENCE_NORMAL, qtrue, - velocity, cmdmove, 3, 30, 0.1f, - SE_HITGROUND|SE_ENTERWATER|SE_ENTERSLIME| - SE_ENTERLAVA|SE_HITGROUNDDAMAGE, 0, qtrue); - // - if ((reach->traveltype & TRAVELTYPE_MASK) == TRAVEL_JUMP) - { - AAS_JumpReachRunStart(reach, dir); - AAS_DrawCross(dir, 4, LINECOLOR_BLUE); - } //end if - } //end if - else if ((reach->traveltype & TRAVELTYPE_MASK) == TRAVEL_ROCKETJUMP) - { - zvel = AAS_RocketJumpZVelocity(reach->start); - AAS_HorizontalVelocityForJump(zvel, reach->start, reach->end, &speed); - // - VectorSubtract(reach->end, reach->start, dir); - dir[2] = 0; - VectorNormalize(dir); - //get command movement - VectorScale(dir, speed, cmdmove); - VectorSet(velocity, 0, 0, zvel); - // - AAS_PredictClientMovement(&move, -1, reach->start, PRESENCE_NORMAL, qtrue, - velocity, cmdmove, 30, 30, 0.1f, - SE_ENTERWATER|SE_ENTERSLIME| - SE_ENTERLAVA|SE_HITGROUNDDAMAGE| - SE_TOUCHJUMPPAD|SE_HITGROUNDAREA, reach->areanum, qtrue); - } //end else if - else if ((reach->traveltype & TRAVELTYPE_MASK) == TRAVEL_JUMPPAD) - { - VectorSet(cmdmove, 0, 0, 0); - // - VectorSubtract(reach->end, reach->start, dir); - dir[2] = 0; - VectorNormalize(dir); - //set the velocity - //NOTE: the edgenum is the horizontal velocity - VectorScale(dir, reach->edgenum, velocity); - //NOTE: the facenum is the Z velocity - velocity[2] = reach->facenum; - // - AAS_PredictClientMovement(&move, -1, reach->start, PRESENCE_NORMAL, qtrue, - velocity, cmdmove, 30, 30, 0.1f, - SE_ENTERWATER|SE_ENTERSLIME| - SE_ENTERLAVA|SE_HITGROUNDDAMAGE| - SE_TOUCHJUMPPAD|SE_HITGROUNDAREA, reach->areanum, qtrue); - } //end else if -} //end of the function AAS_ShowReachability -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_ShowReachableAreas(int areanum) -{ - aas_areasettings_t *settings; - static aas_reachability_t reach; - static int index, lastareanum; - static float lasttime; - - if (areanum != lastareanum) - { - index = 0; - lastareanum = areanum; - } //end if - settings = &aasworld.areasettings[areanum]; - // - if (!settings->numreachableareas) return; - // - if (index >= settings->numreachableareas) index = 0; - // - if (AAS_Time() - lasttime > 1.5) - { - Com_Memcpy(&reach, &aasworld.reachability[settings->firstreachablearea + index], sizeof(aas_reachability_t)); - index++; - lasttime = AAS_Time(); - AAS_PrintTravelType(reach.traveltype & TRAVELTYPE_MASK); - botimport.Print(PRT_MESSAGE, "\n"); - } //end if - AAS_ShowReachability(&reach); -} //end of the function ShowReachableAreas - -void AAS_FloodAreas_r(int areanum, int cluster, int *done) -{ - int nextareanum, i, facenum; - aas_area_t *area; - aas_face_t *face; - aas_areasettings_t *settings; - aas_reachability_t *reach; - - AAS_ShowAreaPolygons(areanum, 1, qtrue); - //pointer to the convex area - area = &aasworld.areas[areanum]; - settings = &aasworld.areasettings[areanum]; - //walk through the faces of the area - for (i = 0; i < area->numfaces; i++) - { - facenum = abs(aasworld.faceindex[area->firstface + i]); - face = &aasworld.faces[facenum]; - if (face->frontarea == areanum) - nextareanum = face->backarea; - else - nextareanum = face->frontarea; - if (!nextareanum) - continue; - if (done[nextareanum]) - continue; - done[nextareanum] = qtrue; - if (aasworld.areasettings[nextareanum].contents & AREACONTENTS_VIEWPORTAL) - continue; - if (AAS_AreaCluster(nextareanum) != cluster) - continue; - AAS_FloodAreas_r(nextareanum, cluster, done); - } //end for - // - for (i = 0; i < settings->numreachableareas; i++) - { - reach = &aasworld.reachability[settings->firstreachablearea + i]; - nextareanum = reach->areanum; - if (!nextareanum) - continue; - if (done[nextareanum]) - continue; - done[nextareanum] = qtrue; - if (aasworld.areasettings[nextareanum].contents & AREACONTENTS_VIEWPORTAL) - continue; - if (AAS_AreaCluster(nextareanum) != cluster) - continue; - /* - if ((reach->traveltype & TRAVELTYPE_MASK) == TRAVEL_WALKOFFLEDGE) - { - AAS_DebugLine(reach->start, reach->end, 1); - } - */ - AAS_FloodAreas_r(nextareanum, cluster, done); - } -} - -void AAS_FloodAreas(vec3_t origin) -{ - int areanum, cluster, *done; - - done = (int *) GetClearedMemory(aasworld.numareas * sizeof(int)); - areanum = AAS_PointAreaNum(origin); - cluster = AAS_AreaCluster(areanum); - AAS_FloodAreas_r(areanum, cluster, done); -} +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_debug.c + * + * desc: AAS debug code + * + * $Archive: /MissionPack/code/botlib/be_aas_debug.c $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_libvar.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_interface.h" +#include "be_aas_funcs.h" +#include "be_aas_def.h" + +#define MAX_DEBUGLINES 1024 +#define MAX_DEBUGPOLYGONS 8192 + +int debuglines[MAX_DEBUGLINES]; +int debuglinevisible[MAX_DEBUGLINES]; +int numdebuglines; + +static int debugpolygons[MAX_DEBUGPOLYGONS]; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ClearShownPolygons(void) +{ + int i; +//* + for (i = 0; i < MAX_DEBUGPOLYGONS; i++) + { + if (debugpolygons[i]) botimport.DebugPolygonDelete(debugpolygons[i]); + debugpolygons[i] = 0; + } //end for +//*/ +/* + for (i = 0; i < MAX_DEBUGPOLYGONS; i++) + { + botimport.DebugPolygonDelete(i); + debugpolygons[i] = 0; + } //end for +*/ +} //end of the function AAS_ClearShownPolygons +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowPolygon(int color, int numpoints, vec3_t *points) +{ + int i; + + for (i = 0; i < MAX_DEBUGPOLYGONS; i++) + { + if (!debugpolygons[i]) + { + debugpolygons[i] = botimport.DebugPolygonCreate(color, numpoints, points); + break; + } //end if + } //end for +} //end of the function AAS_ShowPolygon +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ClearShownDebugLines(void) +{ + int i; + + //make all lines invisible + for (i = 0; i < MAX_DEBUGLINES; i++) + { + if (debuglines[i]) + { + //botimport.DebugLineShow(debuglines[i], NULL, NULL, LINECOLOR_NONE); + botimport.DebugLineDelete(debuglines[i]); + debuglines[i] = 0; + debuglinevisible[i] = qfalse; + } //end if + } //end for +} //end of the function AAS_ClearShownDebugLines +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DebugLine(vec3_t start, vec3_t end, int color) +{ + int line; + + for (line = 0; line < MAX_DEBUGLINES; line++) + { + if (!debuglines[line]) + { + debuglines[line] = botimport.DebugLineCreate(); + debuglinevisible[line] = qfalse; + numdebuglines++; + } //end if + if (!debuglinevisible[line]) + { + botimport.DebugLineShow(debuglines[line], start, end, color); + debuglinevisible[line] = qtrue; + return; + } //end else + } //end for +} //end of the function AAS_DebugLine +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_PermanentLine(vec3_t start, vec3_t end, int color) +{ + int line; + + line = botimport.DebugLineCreate(); + botimport.DebugLineShow(line, start, end, color); +} //end of the function AAS_PermenentLine +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DrawPermanentCross(vec3_t origin, float size, int color) +{ + int i, debugline; + vec3_t start, end; + + for (i = 0; i < 3; i++) + { + VectorCopy(origin, start); + start[i] += size; + VectorCopy(origin, end); + end[i] -= size; + AAS_DebugLine(start, end, color); + debugline = botimport.DebugLineCreate(); + botimport.DebugLineShow(debugline, start, end, color); + } //end for +} //end of the function AAS_DrawPermanentCross +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DrawPlaneCross(vec3_t point, vec3_t normal, float dist, int type, int color) +{ + int n0, n1, n2, j, line, lines[2]; + vec3_t start1, end1, start2, end2; + + //make a cross in the hit plane at the hit point + VectorCopy(point, start1); + VectorCopy(point, end1); + VectorCopy(point, start2); + VectorCopy(point, end2); + + n0 = type % 3; + n1 = (type + 1) % 3; + n2 = (type + 2) % 3; + start1[n1] -= 6; + start1[n2] -= 6; + end1[n1] += 6; + end1[n2] += 6; + start2[n1] += 6; + start2[n2] -= 6; + end2[n1] -= 6; + end2[n2] += 6; + + start1[n0] = (dist - (start1[n1] * normal[n1] + + start1[n2] * normal[n2])) / normal[n0]; + end1[n0] = (dist - (end1[n1] * normal[n1] + + end1[n2] * normal[n2])) / normal[n0]; + start2[n0] = (dist - (start2[n1] * normal[n1] + + start2[n2] * normal[n2])) / normal[n0]; + end2[n0] = (dist - (end2[n1] * normal[n1] + + end2[n2] * normal[n2])) / normal[n0]; + + for (j = 0, line = 0; j < 2 && line < MAX_DEBUGLINES; line++) + { + if (!debuglines[line]) + { + debuglines[line] = botimport.DebugLineCreate(); + lines[j++] = debuglines[line]; + debuglinevisible[line] = qtrue; + numdebuglines++; + } //end if + else if (!debuglinevisible[line]) + { + lines[j++] = debuglines[line]; + debuglinevisible[line] = qtrue; + } //end else + } //end for + botimport.DebugLineShow(lines[0], start1, end1, color); + botimport.DebugLineShow(lines[1], start2, end2, color); +} //end of the function AAS_DrawPlaneCross +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowBoundingBox(vec3_t origin, vec3_t mins, vec3_t maxs) +{ + vec3_t bboxcorners[8]; + int lines[3]; + int i, j, line; + + //upper corners + bboxcorners[0][0] = origin[0] + maxs[0]; + bboxcorners[0][1] = origin[1] + maxs[1]; + bboxcorners[0][2] = origin[2] + maxs[2]; + // + bboxcorners[1][0] = origin[0] + mins[0]; + bboxcorners[1][1] = origin[1] + maxs[1]; + bboxcorners[1][2] = origin[2] + maxs[2]; + // + bboxcorners[2][0] = origin[0] + mins[0]; + bboxcorners[2][1] = origin[1] + mins[1]; + bboxcorners[2][2] = origin[2] + maxs[2]; + // + bboxcorners[3][0] = origin[0] + maxs[0]; + bboxcorners[3][1] = origin[1] + mins[1]; + bboxcorners[3][2] = origin[2] + maxs[2]; + //lower corners + Com_Memcpy(bboxcorners[4], bboxcorners[0], sizeof(vec3_t) * 4); + for (i = 0; i < 4; i++) bboxcorners[4 + i][2] = origin[2] + mins[2]; + //draw bounding box + for (i = 0; i < 4; i++) + { + for (j = 0, line = 0; j < 3 && line < MAX_DEBUGLINES; line++) + { + if (!debuglines[line]) + { + debuglines[line] = botimport.DebugLineCreate(); + lines[j++] = debuglines[line]; + debuglinevisible[line] = qtrue; + numdebuglines++; + } //end if + else if (!debuglinevisible[line]) + { + lines[j++] = debuglines[line]; + debuglinevisible[line] = qtrue; + } //end else + } //end for + //top plane + botimport.DebugLineShow(lines[0], bboxcorners[i], + bboxcorners[(i+1)&3], LINECOLOR_RED); + //bottom plane + botimport.DebugLineShow(lines[1], bboxcorners[4+i], + bboxcorners[4+((i+1)&3)], LINECOLOR_RED); + //vertical lines + botimport.DebugLineShow(lines[2], bboxcorners[i], + bboxcorners[4+i], LINECOLOR_RED); + } //end for +} //end of the function AAS_ShowBoundingBox +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowFace(int facenum) +{ + int i, color, edgenum; + aas_edge_t *edge; + aas_face_t *face; + aas_plane_t *plane; + vec3_t start, end; + + color = LINECOLOR_YELLOW; + //check if face number is in range + if (facenum >= aasworld.numfaces) + { + botimport.Print(PRT_ERROR, "facenum %d out of range\n", facenum); + } //end if + face = &aasworld.faces[facenum]; + //walk through the edges of the face + for (i = 0; i < face->numedges; i++) + { + //edge number + edgenum = abs(aasworld.edgeindex[face->firstedge + i]); + //check if edge number is in range + if (edgenum >= aasworld.numedges) + { + botimport.Print(PRT_ERROR, "edgenum %d out of range\n", edgenum); + } //end if + edge = &aasworld.edges[edgenum]; + if (color == LINECOLOR_RED) color = LINECOLOR_GREEN; + else if (color == LINECOLOR_GREEN) color = LINECOLOR_BLUE; + else if (color == LINECOLOR_BLUE) color = LINECOLOR_YELLOW; + else color = LINECOLOR_RED; + AAS_DebugLine(aasworld.vertexes[edge->v[0]], + aasworld.vertexes[edge->v[1]], + color); + } //end for + plane = &aasworld.planes[face->planenum]; + edgenum = abs(aasworld.edgeindex[face->firstedge]); + edge = &aasworld.edges[edgenum]; + VectorCopy(aasworld.vertexes[edge->v[0]], start); + VectorMA(start, 20, plane->normal, end); + AAS_DebugLine(start, end, LINECOLOR_RED); +} //end of the function AAS_ShowFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowFacePolygon(int facenum, int color, int flip) +{ + int i, edgenum, numpoints; + vec3_t points[128]; + aas_edge_t *edge; + aas_face_t *face; + + //check if face number is in range + if (facenum >= aasworld.numfaces) + { + botimport.Print(PRT_ERROR, "facenum %d out of range\n", facenum); + } //end if + face = &aasworld.faces[facenum]; + //walk through the edges of the face + numpoints = 0; + if (flip) + { + for (i = face->numedges-1; i >= 0; i--) + { + //edge number + edgenum = aasworld.edgeindex[face->firstedge + i]; + edge = &aasworld.edges[abs(edgenum)]; + VectorCopy(aasworld.vertexes[edge->v[edgenum < 0]], points[numpoints]); + numpoints++; + } //end for + } //end if + else + { + for (i = 0; i < face->numedges; i++) + { + //edge number + edgenum = aasworld.edgeindex[face->firstedge + i]; + edge = &aasworld.edges[abs(edgenum)]; + VectorCopy(aasworld.vertexes[edge->v[edgenum < 0]], points[numpoints]); + numpoints++; + } //end for + } //end else + AAS_ShowPolygon(color, numpoints, points); +} //end of the function AAS_ShowFacePolygon +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowArea(int areanum, int groundfacesonly) +{ + int areaedges[MAX_DEBUGLINES]; + int numareaedges, i, j, n, color = 0, line; + int facenum, edgenum; + aas_area_t *area; + aas_face_t *face; + aas_edge_t *edge; + + // + numareaedges = 0; + // + if (areanum < 0 || areanum >= aasworld.numareas) + { + botimport.Print(PRT_ERROR, "area %d out of range [0, %d]\n", + areanum, aasworld.numareas); + return; + } //end if + //pointer to the convex area + area = &aasworld.areas[areanum]; + //walk through the faces of the area + for (i = 0; i < area->numfaces; i++) + { + facenum = abs(aasworld.faceindex[area->firstface + i]); + //check if face number is in range + if (facenum >= aasworld.numfaces) + { + botimport.Print(PRT_ERROR, "facenum %d out of range\n", facenum); + } //end if + face = &aasworld.faces[facenum]; + //ground faces only + if (groundfacesonly) + { + if (!(face->faceflags & (FACE_GROUND | FACE_LADDER))) continue; + } //end if + //walk through the edges of the face + for (j = 0; j < face->numedges; j++) + { + //edge number + edgenum = abs(aasworld.edgeindex[face->firstedge + j]); + //check if edge number is in range + if (edgenum >= aasworld.numedges) + { + botimport.Print(PRT_ERROR, "edgenum %d out of range\n", edgenum); + } //end if + //check if the edge is stored already + for (n = 0; n < numareaedges; n++) + { + if (areaedges[n] == edgenum) break; + } //end for + if (n == numareaedges && numareaedges < MAX_DEBUGLINES) + { + areaedges[numareaedges++] = edgenum; + } //end if + } //end for + //AAS_ShowFace(facenum); + } //end for + //draw all the edges + for (n = 0; n < numareaedges; n++) + { + for (line = 0; line < MAX_DEBUGLINES; line++) + { + if (!debuglines[line]) + { + debuglines[line] = botimport.DebugLineCreate(); + debuglinevisible[line] = qfalse; + numdebuglines++; + } //end if + if (!debuglinevisible[line]) + { + break; + } //end else + } //end for + if (line >= MAX_DEBUGLINES) return; + edge = &aasworld.edges[areaedges[n]]; + if (color == LINECOLOR_RED) color = LINECOLOR_BLUE; + else if (color == LINECOLOR_BLUE) color = LINECOLOR_GREEN; + else if (color == LINECOLOR_GREEN) color = LINECOLOR_YELLOW; + else color = LINECOLOR_RED; + botimport.DebugLineShow(debuglines[line], + aasworld.vertexes[edge->v[0]], + aasworld.vertexes[edge->v[1]], + color); + debuglinevisible[line] = qtrue; + } //end for*/ +} //end of the function AAS_ShowArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowAreaPolygons(int areanum, int color, int groundfacesonly) +{ + int i, facenum; + aas_area_t *area; + aas_face_t *face; + + // + if (areanum < 0 || areanum >= aasworld.numareas) + { + botimport.Print(PRT_ERROR, "area %d out of range [0, %d]\n", + areanum, aasworld.numareas); + return; + } //end if + //pointer to the convex area + area = &aasworld.areas[areanum]; + //walk through the faces of the area + for (i = 0; i < area->numfaces; i++) + { + facenum = abs(aasworld.faceindex[area->firstface + i]); + //check if face number is in range + if (facenum >= aasworld.numfaces) + { + botimport.Print(PRT_ERROR, "facenum %d out of range\n", facenum); + } //end if + face = &aasworld.faces[facenum]; + //ground faces only + if (groundfacesonly) + { + if (!(face->faceflags & (FACE_GROUND | FACE_LADDER))) continue; + } //end if + AAS_ShowFacePolygon(facenum, color, face->frontarea != areanum); + } //end for +} //end of the function AAS_ShowAreaPolygons +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DrawCross(vec3_t origin, float size, int color) +{ + int i; + vec3_t start, end; + + for (i = 0; i < 3; i++) + { + VectorCopy(origin, start); + start[i] += size; + VectorCopy(origin, end); + end[i] -= size; + AAS_DebugLine(start, end, color); + } //end for +} //end of the function AAS_DrawCross +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_PrintTravelType(int traveltype) +{ +#ifdef DEBUG + char *str; + // + switch(traveltype & TRAVELTYPE_MASK) + { + case TRAVEL_INVALID: str = "TRAVEL_INVALID"; break; + case TRAVEL_WALK: str = "TRAVEL_WALK"; break; + case TRAVEL_CROUCH: str = "TRAVEL_CROUCH"; break; + case TRAVEL_BARRIERJUMP: str = "TRAVEL_BARRIERJUMP"; break; + case TRAVEL_JUMP: str = "TRAVEL_JUMP"; break; + case TRAVEL_LADDER: str = "TRAVEL_LADDER"; break; + case TRAVEL_WALKOFFLEDGE: str = "TRAVEL_WALKOFFLEDGE"; break; + case TRAVEL_SWIM: str = "TRAVEL_SWIM"; break; + case TRAVEL_WATERJUMP: str = "TRAVEL_WATERJUMP"; break; + case TRAVEL_TELEPORT: str = "TRAVEL_TELEPORT"; break; + case TRAVEL_ELEVATOR: str = "TRAVEL_ELEVATOR"; break; + case TRAVEL_ROCKETJUMP: str = "TRAVEL_ROCKETJUMP"; break; + case TRAVEL_BFGJUMP: str = "TRAVEL_BFGJUMP"; break; + case TRAVEL_GRAPPLEHOOK: str = "TRAVEL_GRAPPLEHOOK"; break; + case TRAVEL_JUMPPAD: str = "TRAVEL_JUMPPAD"; break; + case TRAVEL_FUNCBOB: str = "TRAVEL_FUNCBOB"; break; + default: str = "UNKNOWN TRAVEL TYPE"; break; + } //end switch + botimport.Print(PRT_MESSAGE, "%s", str); +#endif +} //end of the function AAS_PrintTravelType +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DrawArrow(vec3_t start, vec3_t end, int linecolor, int arrowcolor) +{ + vec3_t dir, cross, p1, p2, up = {0, 0, 1}; + float dot; + + VectorSubtract(end, start, dir); + VectorNormalize(dir); + dot = DotProduct(dir, up); + if (dot > 0.99 || dot < -0.99) VectorSet(cross, 1, 0, 0); + else CrossProduct(dir, up, cross); + + VectorMA(end, -6, dir, p1); + VectorCopy(p1, p2); + VectorMA(p1, 6, cross, p1); + VectorMA(p2, -6, cross, p2); + + AAS_DebugLine(start, end, linecolor); + AAS_DebugLine(p1, end, arrowcolor); + AAS_DebugLine(p2, end, arrowcolor); +} //end of the function AAS_DrawArrow +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowReachability(aas_reachability_t *reach) +{ + vec3_t dir, cmdmove, velocity; + float speed, zvel; + aas_clientmove_t move; + + AAS_ShowAreaPolygons(reach->areanum, 5, qtrue); + //AAS_ShowArea(reach->areanum, qtrue); + AAS_DrawArrow(reach->start, reach->end, LINECOLOR_BLUE, LINECOLOR_YELLOW); + // + if ((reach->traveltype & TRAVELTYPE_MASK) == TRAVEL_JUMP || + (reach->traveltype & TRAVELTYPE_MASK) == TRAVEL_WALKOFFLEDGE) + { + AAS_HorizontalVelocityForJump(aassettings.phys_jumpvel, reach->start, reach->end, &speed); + // + VectorSubtract(reach->end, reach->start, dir); + dir[2] = 0; + VectorNormalize(dir); + //set the velocity + VectorScale(dir, speed, velocity); + //set the command movement + VectorClear(cmdmove); + cmdmove[2] = aassettings.phys_jumpvel; + // + AAS_PredictClientMovement(&move, -1, reach->start, PRESENCE_NORMAL, qtrue, + velocity, cmdmove, 3, 30, 0.1f, + SE_HITGROUND|SE_ENTERWATER|SE_ENTERSLIME| + SE_ENTERLAVA|SE_HITGROUNDDAMAGE, 0, qtrue); + // + if ((reach->traveltype & TRAVELTYPE_MASK) == TRAVEL_JUMP) + { + AAS_JumpReachRunStart(reach, dir); + AAS_DrawCross(dir, 4, LINECOLOR_BLUE); + } //end if + } //end if + else if ((reach->traveltype & TRAVELTYPE_MASK) == TRAVEL_ROCKETJUMP) + { + zvel = AAS_RocketJumpZVelocity(reach->start); + AAS_HorizontalVelocityForJump(zvel, reach->start, reach->end, &speed); + // + VectorSubtract(reach->end, reach->start, dir); + dir[2] = 0; + VectorNormalize(dir); + //get command movement + VectorScale(dir, speed, cmdmove); + VectorSet(velocity, 0, 0, zvel); + // + AAS_PredictClientMovement(&move, -1, reach->start, PRESENCE_NORMAL, qtrue, + velocity, cmdmove, 30, 30, 0.1f, + SE_ENTERWATER|SE_ENTERSLIME| + SE_ENTERLAVA|SE_HITGROUNDDAMAGE| + SE_TOUCHJUMPPAD|SE_HITGROUNDAREA, reach->areanum, qtrue); + } //end else if + else if ((reach->traveltype & TRAVELTYPE_MASK) == TRAVEL_JUMPPAD) + { + VectorSet(cmdmove, 0, 0, 0); + // + VectorSubtract(reach->end, reach->start, dir); + dir[2] = 0; + VectorNormalize(dir); + //set the velocity + //NOTE: the edgenum is the horizontal velocity + VectorScale(dir, reach->edgenum, velocity); + //NOTE: the facenum is the Z velocity + velocity[2] = reach->facenum; + // + AAS_PredictClientMovement(&move, -1, reach->start, PRESENCE_NORMAL, qtrue, + velocity, cmdmove, 30, 30, 0.1f, + SE_ENTERWATER|SE_ENTERSLIME| + SE_ENTERLAVA|SE_HITGROUNDDAMAGE| + SE_TOUCHJUMPPAD|SE_HITGROUNDAREA, reach->areanum, qtrue); + } //end else if +} //end of the function AAS_ShowReachability +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowReachableAreas(int areanum) +{ + aas_areasettings_t *settings; + static aas_reachability_t reach; + static int index, lastareanum; + static float lasttime; + + if (areanum != lastareanum) + { + index = 0; + lastareanum = areanum; + } //end if + settings = &aasworld.areasettings[areanum]; + // + if (!settings->numreachableareas) return; + // + if (index >= settings->numreachableareas) index = 0; + // + if (AAS_Time() - lasttime > 1.5) + { + Com_Memcpy(&reach, &aasworld.reachability[settings->firstreachablearea + index], sizeof(aas_reachability_t)); + index++; + lasttime = AAS_Time(); + AAS_PrintTravelType(reach.traveltype & TRAVELTYPE_MASK); + botimport.Print(PRT_MESSAGE, "\n"); + } //end if + AAS_ShowReachability(&reach); +} //end of the function ShowReachableAreas + +void AAS_FloodAreas_r(int areanum, int cluster, int *done) +{ + int nextareanum, i, facenum; + aas_area_t *area; + aas_face_t *face; + aas_areasettings_t *settings; + aas_reachability_t *reach; + + AAS_ShowAreaPolygons(areanum, 1, qtrue); + //pointer to the convex area + area = &aasworld.areas[areanum]; + settings = &aasworld.areasettings[areanum]; + //walk through the faces of the area + for (i = 0; i < area->numfaces; i++) + { + facenum = abs(aasworld.faceindex[area->firstface + i]); + face = &aasworld.faces[facenum]; + if (face->frontarea == areanum) + nextareanum = face->backarea; + else + nextareanum = face->frontarea; + if (!nextareanum) + continue; + if (done[nextareanum]) + continue; + done[nextareanum] = qtrue; + if (aasworld.areasettings[nextareanum].contents & AREACONTENTS_VIEWPORTAL) + continue; + if (AAS_AreaCluster(nextareanum) != cluster) + continue; + AAS_FloodAreas_r(nextareanum, cluster, done); + } //end for + // + for (i = 0; i < settings->numreachableareas; i++) + { + reach = &aasworld.reachability[settings->firstreachablearea + i]; + nextareanum = reach->areanum; + if (!nextareanum) + continue; + if (done[nextareanum]) + continue; + done[nextareanum] = qtrue; + if (aasworld.areasettings[nextareanum].contents & AREACONTENTS_VIEWPORTAL) + continue; + if (AAS_AreaCluster(nextareanum) != cluster) + continue; + /* + if ((reach->traveltype & TRAVELTYPE_MASK) == TRAVEL_WALKOFFLEDGE) + { + AAS_DebugLine(reach->start, reach->end, 1); + } + */ + AAS_FloodAreas_r(nextareanum, cluster, done); + } +} + +void AAS_FloodAreas(vec3_t origin) +{ + int areanum, cluster, *done; + + done = (int *) GetClearedMemory(aasworld.numareas * sizeof(int)); + areanum = AAS_PointAreaNum(origin); + cluster = AAS_AreaCluster(areanum); + AAS_FloodAreas_r(areanum, cluster, done); +} diff --git a/code/botlib/be_aas_debug.h b/code/botlib/be_aas_debug.h index f0147c1..ad0f2a2 100755 --- a/code/botlib/be_aas_debug.h +++ b/code/botlib/be_aas_debug.h @@ -1,62 +1,62 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: be_aas_debug.h - * - * desc: AAS - * - * $Archive: /source/code/botlib/be_aas_debug.h $ - * - *****************************************************************************/ - -//clear the shown debug lines -void AAS_ClearShownDebugLines(void); -// -void AAS_ClearShownPolygons(void); -//show a debug line -void AAS_DebugLine(vec3_t start, vec3_t end, int color); -//show a permenent line -void AAS_PermanentLine(vec3_t start, vec3_t end, int color); -//show a permanent cross -void AAS_DrawPermanentCross(vec3_t origin, float size, int color); -//draw a cross in the plane -void AAS_DrawPlaneCross(vec3_t point, vec3_t normal, float dist, int type, int color); -//show a bounding box -void AAS_ShowBoundingBox(vec3_t origin, vec3_t mins, vec3_t maxs); -//show a face -void AAS_ShowFace(int facenum); -//show an area -void AAS_ShowArea(int areanum, int groundfacesonly); -// -void AAS_ShowAreaPolygons(int areanum, int color, int groundfacesonly); -//draw a cros -void AAS_DrawCross(vec3_t origin, float size, int color); -//print the travel type -void AAS_PrintTravelType(int traveltype); -//draw an arrow -void AAS_DrawArrow(vec3_t start, vec3_t end, int linecolor, int arrowcolor); -//visualize the given reachability -void AAS_ShowReachability(struct aas_reachability_s *reach); -//show the reachable areas from the given area -void AAS_ShowReachableAreas(int areanum); - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_debug.h + * + * desc: AAS + * + * $Archive: /source/code/botlib/be_aas_debug.h $ + * + *****************************************************************************/ + +//clear the shown debug lines +void AAS_ClearShownDebugLines(void); +// +void AAS_ClearShownPolygons(void); +//show a debug line +void AAS_DebugLine(vec3_t start, vec3_t end, int color); +//show a permenent line +void AAS_PermanentLine(vec3_t start, vec3_t end, int color); +//show a permanent cross +void AAS_DrawPermanentCross(vec3_t origin, float size, int color); +//draw a cross in the plane +void AAS_DrawPlaneCross(vec3_t point, vec3_t normal, float dist, int type, int color); +//show a bounding box +void AAS_ShowBoundingBox(vec3_t origin, vec3_t mins, vec3_t maxs); +//show a face +void AAS_ShowFace(int facenum); +//show an area +void AAS_ShowArea(int areanum, int groundfacesonly); +// +void AAS_ShowAreaPolygons(int areanum, int color, int groundfacesonly); +//draw a cros +void AAS_DrawCross(vec3_t origin, float size, int color); +//print the travel type +void AAS_PrintTravelType(int traveltype); +//draw an arrow +void AAS_DrawArrow(vec3_t start, vec3_t end, int linecolor, int arrowcolor); +//visualize the given reachability +void AAS_ShowReachability(struct aas_reachability_s *reach); +//show the reachable areas from the given area +void AAS_ShowReachableAreas(int areanum); + diff --git a/code/botlib/be_aas_def.h b/code/botlib/be_aas_def.h index 22ebbc5..72bb3e6 100755 --- a/code/botlib/be_aas_def.h +++ b/code/botlib/be_aas_def.h @@ -1,306 +1,306 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: be_aas_def.h - * - * desc: AAS - * - * $Archive: /source/code/botlib/be_aas_def.h $ - * - *****************************************************************************/ - -//debugging on -#define AAS_DEBUG - -#define MAX_CLIENTS 64 -#define MAX_MODELS 256 // these are sent over the net as 8 bits -#define MAX_SOUNDS 256 // so they cannot be blindly increased -#define MAX_CONFIGSTRINGS 1024 - -#define CS_SCORES 32 -#define CS_MODELS (CS_SCORES+MAX_CLIENTS) -#define CS_SOUNDS (CS_MODELS+MAX_MODELS) - -#define DF_AASENTNUMBER(x) (x - aasworld.entities) -#define DF_NUMBERAASENT(x) (&aasworld.entities[x]) -#define DF_AASENTCLIENT(x) (x - aasworld.entities - 1) -#define DF_CLIENTAASENT(x) (&aasworld.entities[x + 1]) - -#ifndef MAX_PATH - #define MAX_PATH MAX_QPATH -#endif - -//string index (for model, sound and image index) -typedef struct aas_stringindex_s -{ - int numindexes; - char **index; -} aas_stringindex_t; - -//structure to link entities to areas and areas to entities -typedef struct aas_link_s -{ - int entnum; - int areanum; - struct aas_link_s *next_ent, *prev_ent; - struct aas_link_s *next_area, *prev_area; -} aas_link_t; - -//structure to link entities to leaves and leaves to entities -typedef struct bsp_link_s -{ - int entnum; - int leafnum; - struct bsp_link_s *next_ent, *prev_ent; - struct bsp_link_s *next_leaf, *prev_leaf; -} bsp_link_t; - -typedef struct bsp_entdata_s -{ - vec3_t origin; - vec3_t angles; - vec3_t absmins; - vec3_t absmaxs; - int solid; - int modelnum; -} bsp_entdata_t; - -//entity -typedef struct aas_entity_s -{ - //entity info - aas_entityinfo_t i; - //links into the AAS areas - aas_link_t *areas; - //links into the BSP leaves - bsp_link_t *leaves; -} aas_entity_t; - -typedef struct aas_settings_s -{ - vec3_t phys_gravitydirection; - float phys_friction; - float phys_stopspeed; - float phys_gravity; - float phys_waterfriction; - float phys_watergravity; - float phys_maxvelocity; - float phys_maxwalkvelocity; - float phys_maxcrouchvelocity; - float phys_maxswimvelocity; - float phys_walkaccelerate; - float phys_airaccelerate; - float phys_swimaccelerate; - float phys_maxstep; - float phys_maxsteepness; - float phys_maxwaterjump; - float phys_maxbarrier; - float phys_jumpvel; - float phys_falldelta5; - float phys_falldelta10; - float rs_waterjump; - float rs_teleport; - float rs_barrierjump; - float rs_startcrouch; - float rs_startgrapple; - float rs_startwalkoffledge; - float rs_startjump; - float rs_rocketjump; - float rs_bfgjump; - float rs_jumppad; - float rs_aircontrolledjumppad; - float rs_funcbob; - float rs_startelevator; - float rs_falldamage5; - float rs_falldamage10; - float rs_maxfallheight; - float rs_maxjumpfallheight; -} aas_settings_t; - -#define CACHETYPE_PORTAL 0 -#define CACHETYPE_AREA 1 - -//routing cache -typedef struct aas_routingcache_s -{ - byte type; //portal or area cache - float time; //last time accessed or updated - int size; //size of the routing cache - int cluster; //cluster the cache is for - int areanum; //area the cache is created for - vec3_t origin; //origin within the area - float starttraveltime; //travel time to start with - int travelflags; //combinations of the travel flags - struct aas_routingcache_s *prev, *next; - struct aas_routingcache_s *time_prev, *time_next; - unsigned char *reachabilities; //reachabilities used for routing - unsigned short int traveltimes[1]; //travel time for every area (variable sized) -} aas_routingcache_t; - -//fields for the routing algorithm -typedef struct aas_routingupdate_s -{ - int cluster; - int areanum; //area number of the update - vec3_t start; //start point the area was entered - unsigned short int tmptraveltime; //temporary travel time - unsigned short int *areatraveltimes; //travel times within the area - qboolean inlist; //true if the update is in the list - struct aas_routingupdate_s *next; - struct aas_routingupdate_s *prev; -} aas_routingupdate_t; - -//reversed reachability link -typedef struct aas_reversedlink_s -{ - int linknum; //the aas_areareachability_t - int areanum; //reachable from this area - struct aas_reversedlink_s *next; //next link -} aas_reversedlink_t; - -//reversed area reachability -typedef struct aas_reversedreachability_s -{ - int numlinks; - aas_reversedlink_t *first; -} aas_reversedreachability_t; - -//areas a reachability goes through -typedef struct aas_reachabilityareas_s -{ - int firstarea, numareas; -} aas_reachabilityareas_t; - -typedef struct aas_s -{ - int loaded; //true when an AAS file is loaded - int initialized; //true when AAS has been initialized - int savefile; //set true when file should be saved - int bspchecksum; - //current time - float time; - int numframes; - //name of the aas file - char filename[MAX_PATH]; - char mapname[MAX_PATH]; - //bounding boxes - int numbboxes; - aas_bbox_t *bboxes; - //vertexes - int numvertexes; - aas_vertex_t *vertexes; - //planes - int numplanes; - aas_plane_t *planes; - //edges - int numedges; - aas_edge_t *edges; - //edge index - int edgeindexsize; - aas_edgeindex_t *edgeindex; - //faces - int numfaces; - aas_face_t *faces; - //face index - int faceindexsize; - aas_faceindex_t *faceindex; - //convex areas - int numareas; - aas_area_t *areas; - //convex area settings - int numareasettings; - aas_areasettings_t *areasettings; - //reachablity list - int reachabilitysize; - aas_reachability_t *reachability; - //nodes of the bsp tree - int numnodes; - aas_node_t *nodes; - //cluster portals - int numportals; - aas_portal_t *portals; - //cluster portal index - int portalindexsize; - aas_portalindex_t *portalindex; - //clusters - int numclusters; - aas_cluster_t *clusters; - // - int numreachabilityareas; - float reachabilitytime; - //enities linked in the areas - aas_link_t *linkheap; //heap with link structures - int linkheapsize; //size of the link heap - aas_link_t *freelinks; //first free link - aas_link_t **arealinkedentities; //entities linked into areas - //entities - int maxentities; - int maxclients; - aas_entity_t *entities; - //string indexes - char *configstrings[MAX_CONFIGSTRINGS]; - int indexessetup; - //index to retrieve travel flag for a travel type - int travelflagfortype[MAX_TRAVELTYPES]; - //travel flags for each area based on contents - int *areacontentstravelflags; - //routing update - aas_routingupdate_t *areaupdate; - aas_routingupdate_t *portalupdate; - //number of routing updates during a frame (reset every frame) - int frameroutingupdates; - //reversed reachability links - aas_reversedreachability_t *reversedreachability; - //travel times within the areas - unsigned short ***areatraveltimes; - //array of size numclusters with cluster cache - aas_routingcache_t ***clusterareacache; - aas_routingcache_t **portalcache; - //cache list sorted on time - aas_routingcache_t *oldestcache; // start of cache list sorted on time - aas_routingcache_t *newestcache; // end of cache list sorted on time - //maximum travel time through portal areas - int *portalmaxtraveltimes; - //areas the reachabilities go through - int *reachabilityareaindex; - aas_reachabilityareas_t *reachabilityareas; -} aas_t; - -#define AASINTERN - -#ifndef BSPCINCLUDE - -#include "be_aas_main.h" -#include "be_aas_entity.h" -#include "be_aas_sample.h" -#include "be_aas_cluster.h" -#include "be_aas_reach.h" -#include "be_aas_route.h" -#include "be_aas_routealt.h" -#include "be_aas_debug.h" -#include "be_aas_file.h" -#include "be_aas_optimize.h" -#include "be_aas_bsp.h" -#include "be_aas_move.h" - -#endif //BSPCINCLUDE +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_def.h + * + * desc: AAS + * + * $Archive: /source/code/botlib/be_aas_def.h $ + * + *****************************************************************************/ + +//debugging on +#define AAS_DEBUG + +#define MAX_CLIENTS 64 +#define MAX_MODELS 256 // these are sent over the net as 8 bits +#define MAX_SOUNDS 256 // so they cannot be blindly increased +#define MAX_CONFIGSTRINGS 1024 + +#define CS_SCORES 32 +#define CS_MODELS (CS_SCORES+MAX_CLIENTS) +#define CS_SOUNDS (CS_MODELS+MAX_MODELS) + +#define DF_AASENTNUMBER(x) (x - aasworld.entities) +#define DF_NUMBERAASENT(x) (&aasworld.entities[x]) +#define DF_AASENTCLIENT(x) (x - aasworld.entities - 1) +#define DF_CLIENTAASENT(x) (&aasworld.entities[x + 1]) + +#ifndef MAX_PATH + #define MAX_PATH MAX_QPATH +#endif + +//string index (for model, sound and image index) +typedef struct aas_stringindex_s +{ + int numindexes; + char **index; +} aas_stringindex_t; + +//structure to link entities to areas and areas to entities +typedef struct aas_link_s +{ + int entnum; + int areanum; + struct aas_link_s *next_ent, *prev_ent; + struct aas_link_s *next_area, *prev_area; +} aas_link_t; + +//structure to link entities to leaves and leaves to entities +typedef struct bsp_link_s +{ + int entnum; + int leafnum; + struct bsp_link_s *next_ent, *prev_ent; + struct bsp_link_s *next_leaf, *prev_leaf; +} bsp_link_t; + +typedef struct bsp_entdata_s +{ + vec3_t origin; + vec3_t angles; + vec3_t absmins; + vec3_t absmaxs; + int solid; + int modelnum; +} bsp_entdata_t; + +//entity +typedef struct aas_entity_s +{ + //entity info + aas_entityinfo_t i; + //links into the AAS areas + aas_link_t *areas; + //links into the BSP leaves + bsp_link_t *leaves; +} aas_entity_t; + +typedef struct aas_settings_s +{ + vec3_t phys_gravitydirection; + float phys_friction; + float phys_stopspeed; + float phys_gravity; + float phys_waterfriction; + float phys_watergravity; + float phys_maxvelocity; + float phys_maxwalkvelocity; + float phys_maxcrouchvelocity; + float phys_maxswimvelocity; + float phys_walkaccelerate; + float phys_airaccelerate; + float phys_swimaccelerate; + float phys_maxstep; + float phys_maxsteepness; + float phys_maxwaterjump; + float phys_maxbarrier; + float phys_jumpvel; + float phys_falldelta5; + float phys_falldelta10; + float rs_waterjump; + float rs_teleport; + float rs_barrierjump; + float rs_startcrouch; + float rs_startgrapple; + float rs_startwalkoffledge; + float rs_startjump; + float rs_rocketjump; + float rs_bfgjump; + float rs_jumppad; + float rs_aircontrolledjumppad; + float rs_funcbob; + float rs_startelevator; + float rs_falldamage5; + float rs_falldamage10; + float rs_maxfallheight; + float rs_maxjumpfallheight; +} aas_settings_t; + +#define CACHETYPE_PORTAL 0 +#define CACHETYPE_AREA 1 + +//routing cache +typedef struct aas_routingcache_s +{ + byte type; //portal or area cache + float time; //last time accessed or updated + int size; //size of the routing cache + int cluster; //cluster the cache is for + int areanum; //area the cache is created for + vec3_t origin; //origin within the area + float starttraveltime; //travel time to start with + int travelflags; //combinations of the travel flags + struct aas_routingcache_s *prev, *next; + struct aas_routingcache_s *time_prev, *time_next; + unsigned char *reachabilities; //reachabilities used for routing + unsigned short int traveltimes[1]; //travel time for every area (variable sized) +} aas_routingcache_t; + +//fields for the routing algorithm +typedef struct aas_routingupdate_s +{ + int cluster; + int areanum; //area number of the update + vec3_t start; //start point the area was entered + unsigned short int tmptraveltime; //temporary travel time + unsigned short int *areatraveltimes; //travel times within the area + qboolean inlist; //true if the update is in the list + struct aas_routingupdate_s *next; + struct aas_routingupdate_s *prev; +} aas_routingupdate_t; + +//reversed reachability link +typedef struct aas_reversedlink_s +{ + int linknum; //the aas_areareachability_t + int areanum; //reachable from this area + struct aas_reversedlink_s *next; //next link +} aas_reversedlink_t; + +//reversed area reachability +typedef struct aas_reversedreachability_s +{ + int numlinks; + aas_reversedlink_t *first; +} aas_reversedreachability_t; + +//areas a reachability goes through +typedef struct aas_reachabilityareas_s +{ + int firstarea, numareas; +} aas_reachabilityareas_t; + +typedef struct aas_s +{ + int loaded; //true when an AAS file is loaded + int initialized; //true when AAS has been initialized + int savefile; //set true when file should be saved + int bspchecksum; + //current time + float time; + int numframes; + //name of the aas file + char filename[MAX_PATH]; + char mapname[MAX_PATH]; + //bounding boxes + int numbboxes; + aas_bbox_t *bboxes; + //vertexes + int numvertexes; + aas_vertex_t *vertexes; + //planes + int numplanes; + aas_plane_t *planes; + //edges + int numedges; + aas_edge_t *edges; + //edge index + int edgeindexsize; + aas_edgeindex_t *edgeindex; + //faces + int numfaces; + aas_face_t *faces; + //face index + int faceindexsize; + aas_faceindex_t *faceindex; + //convex areas + int numareas; + aas_area_t *areas; + //convex area settings + int numareasettings; + aas_areasettings_t *areasettings; + //reachablity list + int reachabilitysize; + aas_reachability_t *reachability; + //nodes of the bsp tree + int numnodes; + aas_node_t *nodes; + //cluster portals + int numportals; + aas_portal_t *portals; + //cluster portal index + int portalindexsize; + aas_portalindex_t *portalindex; + //clusters + int numclusters; + aas_cluster_t *clusters; + // + int numreachabilityareas; + float reachabilitytime; + //enities linked in the areas + aas_link_t *linkheap; //heap with link structures + int linkheapsize; //size of the link heap + aas_link_t *freelinks; //first free link + aas_link_t **arealinkedentities; //entities linked into areas + //entities + int maxentities; + int maxclients; + aas_entity_t *entities; + //string indexes + char *configstrings[MAX_CONFIGSTRINGS]; + int indexessetup; + //index to retrieve travel flag for a travel type + int travelflagfortype[MAX_TRAVELTYPES]; + //travel flags for each area based on contents + int *areacontentstravelflags; + //routing update + aas_routingupdate_t *areaupdate; + aas_routingupdate_t *portalupdate; + //number of routing updates during a frame (reset every frame) + int frameroutingupdates; + //reversed reachability links + aas_reversedreachability_t *reversedreachability; + //travel times within the areas + unsigned short ***areatraveltimes; + //array of size numclusters with cluster cache + aas_routingcache_t ***clusterareacache; + aas_routingcache_t **portalcache; + //cache list sorted on time + aas_routingcache_t *oldestcache; // start of cache list sorted on time + aas_routingcache_t *newestcache; // end of cache list sorted on time + //maximum travel time through portal areas + int *portalmaxtraveltimes; + //areas the reachabilities go through + int *reachabilityareaindex; + aas_reachabilityareas_t *reachabilityareas; +} aas_t; + +#define AASINTERN + +#ifndef BSPCINCLUDE + +#include "be_aas_main.h" +#include "be_aas_entity.h" +#include "be_aas_sample.h" +#include "be_aas_cluster.h" +#include "be_aas_reach.h" +#include "be_aas_route.h" +#include "be_aas_routealt.h" +#include "be_aas_debug.h" +#include "be_aas_file.h" +#include "be_aas_optimize.h" +#include "be_aas_bsp.h" +#include "be_aas_move.h" + +#endif //BSPCINCLUDE diff --git a/code/botlib/be_aas_entity.c b/code/botlib/be_aas_entity.c index e3ed85e..e11356b 100755 --- a/code/botlib/be_aas_entity.c +++ b/code/botlib/be_aas_entity.c @@ -1,437 +1,437 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: be_aas_entity.c - * - * desc: AAS entities - * - * $Archive: /MissionPack/code/botlib/be_aas_entity.c $ - * - *****************************************************************************/ - -#include "../game/q_shared.h" -#include "l_memory.h" -#include "l_script.h" -#include "l_precomp.h" -#include "l_struct.h" -#include "l_utils.h" -#include "l_log.h" -#include "aasfile.h" -#include "../game/botlib.h" -#include "../game/be_aas.h" -#include "be_aas_funcs.h" -#include "be_interface.h" -#include "be_aas_def.h" - -#define MASK_SOLID CONTENTS_PLAYERCLIP - -//FIXME: these might change -enum { - ET_GENERAL, - ET_PLAYER, - ET_ITEM, - ET_MISSILE, - ET_MOVER -}; - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_UpdateEntity(int entnum, bot_entitystate_t *state) -{ - int relink; - aas_entity_t *ent; - vec3_t absmins, absmaxs; - - if (!aasworld.loaded) - { - botimport.Print(PRT_MESSAGE, "AAS_UpdateEntity: not loaded\n"); - return BLERR_NOAASFILE; - } //end if - - ent = &aasworld.entities[entnum]; - - if (!state) { - //unlink the entity - AAS_UnlinkFromAreas(ent->areas); - //unlink the entity from the BSP leaves - AAS_UnlinkFromBSPLeaves(ent->leaves); - // - ent->areas = NULL; - // - ent->leaves = NULL; - return BLERR_NOERROR; - } - - ent->i.update_time = AAS_Time() - ent->i.ltime; - ent->i.type = state->type; - ent->i.flags = state->flags; - ent->i.ltime = AAS_Time(); - VectorCopy(ent->i.origin, ent->i.lastvisorigin); - VectorCopy(state->old_origin, ent->i.old_origin); - ent->i.solid = state->solid; - ent->i.groundent = state->groundent; - ent->i.modelindex = state->modelindex; - ent->i.modelindex2 = state->modelindex2; - ent->i.frame = state->frame; - ent->i.event = state->event; - ent->i.eventParm = state->eventParm; - ent->i.powerups = state->powerups; - ent->i.weapon = state->weapon; - ent->i.legsAnim = state->legsAnim; - ent->i.torsoAnim = state->torsoAnim; - //number of the entity - ent->i.number = entnum; - //updated so set valid flag - ent->i.valid = qtrue; - //link everything the first frame - if (aasworld.numframes == 1) relink = qtrue; - else relink = qfalse; - // - if (ent->i.solid == SOLID_BSP) - { - //if the angles of the model changed - if (!VectorCompare(state->angles, ent->i.angles)) - { - VectorCopy(state->angles, ent->i.angles); - relink = qtrue; - } //end if - //get the mins and maxs of the model - //FIXME: rotate mins and maxs - AAS_BSPModelMinsMaxsOrigin(ent->i.modelindex, ent->i.angles, ent->i.mins, ent->i.maxs, NULL); - } //end if - else if (ent->i.solid == SOLID_BBOX) - { - //if the bounding box size changed - if (!VectorCompare(state->mins, ent->i.mins) || - !VectorCompare(state->maxs, ent->i.maxs)) - { - VectorCopy(state->mins, ent->i.mins); - VectorCopy(state->maxs, ent->i.maxs); - relink = qtrue; - } //end if - VectorCopy(state->angles, ent->i.angles); - } //end if - //if the origin changed - if (!VectorCompare(state->origin, ent->i.origin)) - { - VectorCopy(state->origin, ent->i.origin); - relink = qtrue; - } //end if - //if the entity should be relinked - if (relink) - { - //don't link the world model - if (entnum != ENTITYNUM_WORLD) - { - //absolute mins and maxs - VectorAdd(ent->i.mins, ent->i.origin, absmins); - VectorAdd(ent->i.maxs, ent->i.origin, absmaxs); - //unlink the entity - AAS_UnlinkFromAreas(ent->areas); - //relink the entity to the AAS areas (use the larges bbox) - ent->areas = AAS_LinkEntityClientBBox(absmins, absmaxs, entnum, PRESENCE_NORMAL); - //unlink the entity from the BSP leaves - AAS_UnlinkFromBSPLeaves(ent->leaves); - //link the entity to the world BSP tree - ent->leaves = AAS_BSPLinkEntity(absmins, absmaxs, entnum, 0); - } //end if - } //end if - return BLERR_NOERROR; -} //end of the function AAS_UpdateEntity -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_EntityInfo(int entnum, aas_entityinfo_t *info) -{ - if (!aasworld.initialized) - { - botimport.Print(PRT_FATAL, "AAS_EntityInfo: aasworld not initialized\n"); - Com_Memset(info, 0, sizeof(aas_entityinfo_t)); - return; - } //end if - - if (entnum < 0 || entnum >= aasworld.maxentities) - { - botimport.Print(PRT_FATAL, "AAS_EntityInfo: entnum %d out of range\n", entnum); - Com_Memset(info, 0, sizeof(aas_entityinfo_t)); - return; - } //end if - - Com_Memcpy(info, &aasworld.entities[entnum].i, sizeof(aas_entityinfo_t)); -} //end of the function AAS_EntityInfo -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_EntityOrigin(int entnum, vec3_t origin) -{ - if (entnum < 0 || entnum >= aasworld.maxentities) - { - botimport.Print(PRT_FATAL, "AAS_EntityOrigin: entnum %d out of range\n", entnum); - VectorClear(origin); - return; - } //end if - - VectorCopy(aasworld.entities[entnum].i.origin, origin); -} //end of the function AAS_EntityOrigin -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_EntityModelindex(int entnum) -{ - if (entnum < 0 || entnum >= aasworld.maxentities) - { - botimport.Print(PRT_FATAL, "AAS_EntityModelindex: entnum %d out of range\n", entnum); - return 0; - } //end if - return aasworld.entities[entnum].i.modelindex; -} //end of the function AAS_EntityModelindex -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_EntityType(int entnum) -{ - if (!aasworld.initialized) return 0; - - if (entnum < 0 || entnum >= aasworld.maxentities) - { - botimport.Print(PRT_FATAL, "AAS_EntityType: entnum %d out of range\n", entnum); - return 0; - } //end if - return aasworld.entities[entnum].i.type; -} //end of the AAS_EntityType -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_EntityModelNum(int entnum) -{ - if (!aasworld.initialized) return 0; - - if (entnum < 0 || entnum >= aasworld.maxentities) - { - botimport.Print(PRT_FATAL, "AAS_EntityModelNum: entnum %d out of range\n", entnum); - return 0; - } //end if - return aasworld.entities[entnum].i.modelindex; -} //end of the function AAS_EntityModelNum -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_OriginOfMoverWithModelNum(int modelnum, vec3_t origin) -{ - int i; - aas_entity_t *ent; - - for (i = 0; i < aasworld.maxentities; i++) - { - ent = &aasworld.entities[i]; - if (ent->i.type == ET_MOVER) - { - if (ent->i.modelindex == modelnum) - { - VectorCopy(ent->i.origin, origin); - return qtrue; - } //end if - } //end if - } //end for - return qfalse; -} //end of the function AAS_OriginOfMoverWithModelNum -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_EntitySize(int entnum, vec3_t mins, vec3_t maxs) -{ - aas_entity_t *ent; - - if (!aasworld.initialized) return; - - if (entnum < 0 || entnum >= aasworld.maxentities) - { - botimport.Print(PRT_FATAL, "AAS_EntitySize: entnum %d out of range\n", entnum); - return; - } //end if - - ent = &aasworld.entities[entnum]; - VectorCopy(ent->i.mins, mins); - VectorCopy(ent->i.maxs, maxs); -} //end of the function AAS_EntitySize -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_EntityBSPData(int entnum, bsp_entdata_t *entdata) -{ - aas_entity_t *ent; - - ent = &aasworld.entities[entnum]; - VectorCopy(ent->i.origin, entdata->origin); - VectorCopy(ent->i.angles, entdata->angles); - VectorAdd(ent->i.origin, ent->i.mins, entdata->absmins); - VectorAdd(ent->i.origin, ent->i.maxs, entdata->absmaxs); - entdata->solid = ent->i.solid; - entdata->modelnum = ent->i.modelindex - 1; -} //end of the function AAS_EntityBSPData -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_ResetEntityLinks(void) -{ - int i; - for (i = 0; i < aasworld.maxentities; i++) - { - aasworld.entities[i].areas = NULL; - aasworld.entities[i].leaves = NULL; - } //end for -} //end of the function AAS_ResetEntityLinks -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_InvalidateEntities(void) -{ - int i; - for (i = 0; i < aasworld.maxentities; i++) - { - aasworld.entities[i].i.valid = qfalse; - aasworld.entities[i].i.number = i; - } //end for -} //end of the function AAS_InvalidateEntities -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_UnlinkInvalidEntities(void) -{ - int i; - aas_entity_t *ent; - - for (i = 0; i < aasworld.maxentities; i++) - { - ent = &aasworld.entities[i]; - if (!ent->i.valid) - { - AAS_UnlinkFromAreas( ent->areas ); - ent->areas = NULL; - AAS_UnlinkFromBSPLeaves( ent->leaves ); - ent->leaves = NULL; - } //end for - } //end for -} //end of the function AAS_UnlinkInvalidEntities -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_NearestEntity(vec3_t origin, int modelindex) -{ - int i, bestentnum; - float dist, bestdist; - aas_entity_t *ent; - vec3_t dir; - - bestentnum = 0; - bestdist = 99999; - for (i = 0; i < aasworld.maxentities; i++) - { - ent = &aasworld.entities[i]; - if (ent->i.modelindex != modelindex) continue; - VectorSubtract(ent->i.origin, origin, dir); - if (abs(dir[0]) < 40) - { - if (abs(dir[1]) < 40) - { - dist = VectorLength(dir); - if (dist < bestdist) - { - bestdist = dist; - bestentnum = i; - } //end if - } //end if - } //end if - } //end for - return bestentnum; -} //end of the function AAS_NearestEntity -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_BestReachableEntityArea(int entnum) -{ - aas_entity_t *ent; - - ent = &aasworld.entities[entnum]; - return AAS_BestReachableLinkArea(ent->areas); -} //end of the function AAS_BestReachableEntityArea -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_NextEntity(int entnum) -{ - if (!aasworld.loaded) return 0; - - if (entnum < 0) entnum = -1; - while(++entnum < aasworld.maxentities) - { - if (aasworld.entities[entnum].i.valid) return entnum; - } //end while - return 0; -} //end of the function AAS_NextEntity +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_entity.c + * + * desc: AAS entities + * + * $Archive: /MissionPack/code/botlib/be_aas_entity.c $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_utils.h" +#include "l_log.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_aas_def.h" + +#define MASK_SOLID CONTENTS_PLAYERCLIP + +//FIXME: these might change +enum { + ET_GENERAL, + ET_PLAYER, + ET_ITEM, + ET_MISSILE, + ET_MOVER +}; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_UpdateEntity(int entnum, bot_entitystate_t *state) +{ + int relink; + aas_entity_t *ent; + vec3_t absmins, absmaxs; + + if (!aasworld.loaded) + { + botimport.Print(PRT_MESSAGE, "AAS_UpdateEntity: not loaded\n"); + return BLERR_NOAASFILE; + } //end if + + ent = &aasworld.entities[entnum]; + + if (!state) { + //unlink the entity + AAS_UnlinkFromAreas(ent->areas); + //unlink the entity from the BSP leaves + AAS_UnlinkFromBSPLeaves(ent->leaves); + // + ent->areas = NULL; + // + ent->leaves = NULL; + return BLERR_NOERROR; + } + + ent->i.update_time = AAS_Time() - ent->i.ltime; + ent->i.type = state->type; + ent->i.flags = state->flags; + ent->i.ltime = AAS_Time(); + VectorCopy(ent->i.origin, ent->i.lastvisorigin); + VectorCopy(state->old_origin, ent->i.old_origin); + ent->i.solid = state->solid; + ent->i.groundent = state->groundent; + ent->i.modelindex = state->modelindex; + ent->i.modelindex2 = state->modelindex2; + ent->i.frame = state->frame; + ent->i.event = state->event; + ent->i.eventParm = state->eventParm; + ent->i.powerups = state->powerups; + ent->i.weapon = state->weapon; + ent->i.legsAnim = state->legsAnim; + ent->i.torsoAnim = state->torsoAnim; + //number of the entity + ent->i.number = entnum; + //updated so set valid flag + ent->i.valid = qtrue; + //link everything the first frame + if (aasworld.numframes == 1) relink = qtrue; + else relink = qfalse; + // + if (ent->i.solid == SOLID_BSP) + { + //if the angles of the model changed + if (!VectorCompare(state->angles, ent->i.angles)) + { + VectorCopy(state->angles, ent->i.angles); + relink = qtrue; + } //end if + //get the mins and maxs of the model + //FIXME: rotate mins and maxs + AAS_BSPModelMinsMaxsOrigin(ent->i.modelindex, ent->i.angles, ent->i.mins, ent->i.maxs, NULL); + } //end if + else if (ent->i.solid == SOLID_BBOX) + { + //if the bounding box size changed + if (!VectorCompare(state->mins, ent->i.mins) || + !VectorCompare(state->maxs, ent->i.maxs)) + { + VectorCopy(state->mins, ent->i.mins); + VectorCopy(state->maxs, ent->i.maxs); + relink = qtrue; + } //end if + VectorCopy(state->angles, ent->i.angles); + } //end if + //if the origin changed + if (!VectorCompare(state->origin, ent->i.origin)) + { + VectorCopy(state->origin, ent->i.origin); + relink = qtrue; + } //end if + //if the entity should be relinked + if (relink) + { + //don't link the world model + if (entnum != ENTITYNUM_WORLD) + { + //absolute mins and maxs + VectorAdd(ent->i.mins, ent->i.origin, absmins); + VectorAdd(ent->i.maxs, ent->i.origin, absmaxs); + //unlink the entity + AAS_UnlinkFromAreas(ent->areas); + //relink the entity to the AAS areas (use the larges bbox) + ent->areas = AAS_LinkEntityClientBBox(absmins, absmaxs, entnum, PRESENCE_NORMAL); + //unlink the entity from the BSP leaves + AAS_UnlinkFromBSPLeaves(ent->leaves); + //link the entity to the world BSP tree + ent->leaves = AAS_BSPLinkEntity(absmins, absmaxs, entnum, 0); + } //end if + } //end if + return BLERR_NOERROR; +} //end of the function AAS_UpdateEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_EntityInfo(int entnum, aas_entityinfo_t *info) +{ + if (!aasworld.initialized) + { + botimport.Print(PRT_FATAL, "AAS_EntityInfo: aasworld not initialized\n"); + Com_Memset(info, 0, sizeof(aas_entityinfo_t)); + return; + } //end if + + if (entnum < 0 || entnum >= aasworld.maxentities) + { + botimport.Print(PRT_FATAL, "AAS_EntityInfo: entnum %d out of range\n", entnum); + Com_Memset(info, 0, sizeof(aas_entityinfo_t)); + return; + } //end if + + Com_Memcpy(info, &aasworld.entities[entnum].i, sizeof(aas_entityinfo_t)); +} //end of the function AAS_EntityInfo +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_EntityOrigin(int entnum, vec3_t origin) +{ + if (entnum < 0 || entnum >= aasworld.maxentities) + { + botimport.Print(PRT_FATAL, "AAS_EntityOrigin: entnum %d out of range\n", entnum); + VectorClear(origin); + return; + } //end if + + VectorCopy(aasworld.entities[entnum].i.origin, origin); +} //end of the function AAS_EntityOrigin +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_EntityModelindex(int entnum) +{ + if (entnum < 0 || entnum >= aasworld.maxentities) + { + botimport.Print(PRT_FATAL, "AAS_EntityModelindex: entnum %d out of range\n", entnum); + return 0; + } //end if + return aasworld.entities[entnum].i.modelindex; +} //end of the function AAS_EntityModelindex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_EntityType(int entnum) +{ + if (!aasworld.initialized) return 0; + + if (entnum < 0 || entnum >= aasworld.maxentities) + { + botimport.Print(PRT_FATAL, "AAS_EntityType: entnum %d out of range\n", entnum); + return 0; + } //end if + return aasworld.entities[entnum].i.type; +} //end of the AAS_EntityType +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_EntityModelNum(int entnum) +{ + if (!aasworld.initialized) return 0; + + if (entnum < 0 || entnum >= aasworld.maxentities) + { + botimport.Print(PRT_FATAL, "AAS_EntityModelNum: entnum %d out of range\n", entnum); + return 0; + } //end if + return aasworld.entities[entnum].i.modelindex; +} //end of the function AAS_EntityModelNum +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_OriginOfMoverWithModelNum(int modelnum, vec3_t origin) +{ + int i; + aas_entity_t *ent; + + for (i = 0; i < aasworld.maxentities; i++) + { + ent = &aasworld.entities[i]; + if (ent->i.type == ET_MOVER) + { + if (ent->i.modelindex == modelnum) + { + VectorCopy(ent->i.origin, origin); + return qtrue; + } //end if + } //end if + } //end for + return qfalse; +} //end of the function AAS_OriginOfMoverWithModelNum +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_EntitySize(int entnum, vec3_t mins, vec3_t maxs) +{ + aas_entity_t *ent; + + if (!aasworld.initialized) return; + + if (entnum < 0 || entnum >= aasworld.maxentities) + { + botimport.Print(PRT_FATAL, "AAS_EntitySize: entnum %d out of range\n", entnum); + return; + } //end if + + ent = &aasworld.entities[entnum]; + VectorCopy(ent->i.mins, mins); + VectorCopy(ent->i.maxs, maxs); +} //end of the function AAS_EntitySize +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_EntityBSPData(int entnum, bsp_entdata_t *entdata) +{ + aas_entity_t *ent; + + ent = &aasworld.entities[entnum]; + VectorCopy(ent->i.origin, entdata->origin); + VectorCopy(ent->i.angles, entdata->angles); + VectorAdd(ent->i.origin, ent->i.mins, entdata->absmins); + VectorAdd(ent->i.origin, ent->i.maxs, entdata->absmaxs); + entdata->solid = ent->i.solid; + entdata->modelnum = ent->i.modelindex - 1; +} //end of the function AAS_EntityBSPData +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ResetEntityLinks(void) +{ + int i; + for (i = 0; i < aasworld.maxentities; i++) + { + aasworld.entities[i].areas = NULL; + aasworld.entities[i].leaves = NULL; + } //end for +} //end of the function AAS_ResetEntityLinks +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InvalidateEntities(void) +{ + int i; + for (i = 0; i < aasworld.maxentities; i++) + { + aasworld.entities[i].i.valid = qfalse; + aasworld.entities[i].i.number = i; + } //end for +} //end of the function AAS_InvalidateEntities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_UnlinkInvalidEntities(void) +{ + int i; + aas_entity_t *ent; + + for (i = 0; i < aasworld.maxentities; i++) + { + ent = &aasworld.entities[i]; + if (!ent->i.valid) + { + AAS_UnlinkFromAreas( ent->areas ); + ent->areas = NULL; + AAS_UnlinkFromBSPLeaves( ent->leaves ); + ent->leaves = NULL; + } //end for + } //end for +} //end of the function AAS_UnlinkInvalidEntities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_NearestEntity(vec3_t origin, int modelindex) +{ + int i, bestentnum; + float dist, bestdist; + aas_entity_t *ent; + vec3_t dir; + + bestentnum = 0; + bestdist = 99999; + for (i = 0; i < aasworld.maxentities; i++) + { + ent = &aasworld.entities[i]; + if (ent->i.modelindex != modelindex) continue; + VectorSubtract(ent->i.origin, origin, dir); + if (abs(dir[0]) < 40) + { + if (abs(dir[1]) < 40) + { + dist = VectorLength(dir); + if (dist < bestdist) + { + bestdist = dist; + bestentnum = i; + } //end if + } //end if + } //end if + } //end for + return bestentnum; +} //end of the function AAS_NearestEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BestReachableEntityArea(int entnum) +{ + aas_entity_t *ent; + + ent = &aasworld.entities[entnum]; + return AAS_BestReachableLinkArea(ent->areas); +} //end of the function AAS_BestReachableEntityArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_NextEntity(int entnum) +{ + if (!aasworld.loaded) return 0; + + if (entnum < 0) entnum = -1; + while(++entnum < aasworld.maxentities) + { + if (aasworld.entities[entnum].i.valid) return entnum; + } //end while + return 0; +} //end of the function AAS_NextEntity diff --git a/code/botlib/be_aas_entity.h b/code/botlib/be_aas_entity.h index f5286dd..fc3675b 100755 --- a/code/botlib/be_aas_entity.h +++ b/code/botlib/be_aas_entity.h @@ -1,63 +1,63 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: be_aas_entity.h - * - * desc: AAS - * - * $Archive: /source/code/botlib/be_aas_entity.h $ - * - *****************************************************************************/ - -#ifdef AASINTERN -//invalidates all entity infos -void AAS_InvalidateEntities(void); -//unlink not updated entities -void AAS_UnlinkInvalidEntities(void); -//resets the entity AAS and BSP links (sets areas and leaves pointers to NULL) -void AAS_ResetEntityLinks(void); -//updates an entity -int AAS_UpdateEntity(int ent, bot_entitystate_t *state); -//gives the entity data used for collision detection -void AAS_EntityBSPData(int entnum, bsp_entdata_t *entdata); -#endif //AASINTERN - -//returns the size of the entity bounding box in mins and maxs -void AAS_EntitySize(int entnum, vec3_t mins, vec3_t maxs); -//returns the BSP model number of the entity -int AAS_EntityModelNum(int entnum); -//returns the origin of an entity with the given model number -int AAS_OriginOfMoverWithModelNum(int modelnum, vec3_t origin); -//returns the best reachable area the entity is situated in -int AAS_BestReachableEntityArea(int entnum); -//returns the info of the given entity -void AAS_EntityInfo(int entnum, aas_entityinfo_t *info); -//returns the next entity -int AAS_NextEntity(int entnum); -//returns the origin of the entity -void AAS_EntityOrigin(int entnum, vec3_t origin); -//returns the entity type -int AAS_EntityType(int entnum); -//returns the model index of the entity -int AAS_EntityModelindex(int entnum); - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_entity.h + * + * desc: AAS + * + * $Archive: /source/code/botlib/be_aas_entity.h $ + * + *****************************************************************************/ + +#ifdef AASINTERN +//invalidates all entity infos +void AAS_InvalidateEntities(void); +//unlink not updated entities +void AAS_UnlinkInvalidEntities(void); +//resets the entity AAS and BSP links (sets areas and leaves pointers to NULL) +void AAS_ResetEntityLinks(void); +//updates an entity +int AAS_UpdateEntity(int ent, bot_entitystate_t *state); +//gives the entity data used for collision detection +void AAS_EntityBSPData(int entnum, bsp_entdata_t *entdata); +#endif //AASINTERN + +//returns the size of the entity bounding box in mins and maxs +void AAS_EntitySize(int entnum, vec3_t mins, vec3_t maxs); +//returns the BSP model number of the entity +int AAS_EntityModelNum(int entnum); +//returns the origin of an entity with the given model number +int AAS_OriginOfMoverWithModelNum(int modelnum, vec3_t origin); +//returns the best reachable area the entity is situated in +int AAS_BestReachableEntityArea(int entnum); +//returns the info of the given entity +void AAS_EntityInfo(int entnum, aas_entityinfo_t *info); +//returns the next entity +int AAS_NextEntity(int entnum); +//returns the origin of the entity +void AAS_EntityOrigin(int entnum, vec3_t origin); +//returns the entity type +int AAS_EntityType(int entnum); +//returns the model index of the entity +int AAS_EntityModelindex(int entnum); + diff --git a/code/botlib/be_aas_file.c b/code/botlib/be_aas_file.c index 35147fc..50d644e 100755 --- a/code/botlib/be_aas_file.c +++ b/code/botlib/be_aas_file.c @@ -1,582 +1,582 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: be_aas_file.c - * - * desc: AAS file loading/writing - * - * $Archive: /MissionPack/code/botlib/be_aas_file.c $ - * - *****************************************************************************/ - -#include "../game/q_shared.h" -#include "l_memory.h" -#include "l_script.h" -#include "l_precomp.h" -#include "l_struct.h" -#include "l_libvar.h" -#include "l_utils.h" -#include "aasfile.h" -#include "../game/botlib.h" -#include "../game/be_aas.h" -#include "be_aas_funcs.h" -#include "be_interface.h" -#include "be_aas_def.h" - -//#define AASFILEDEBUG - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_SwapAASData(void) -{ - int i, j; - //bounding boxes - for (i = 0; i < aasworld.numbboxes; i++) - { - aasworld.bboxes[i].presencetype = LittleLong(aasworld.bboxes[i].presencetype); - aasworld.bboxes[i].flags = LittleLong(aasworld.bboxes[i].flags); - for (j = 0; j < 3; j++) - { - aasworld.bboxes[i].mins[j] = LittleLong(aasworld.bboxes[i].mins[j]); - aasworld.bboxes[i].maxs[j] = LittleLong(aasworld.bboxes[i].maxs[j]); - } //end for - } //end for - //vertexes - for (i = 0; i < aasworld.numvertexes; i++) - { - for (j = 0; j < 3; j++) - aasworld.vertexes[i][j] = LittleFloat(aasworld.vertexes[i][j]); - } //end for - //planes - for (i = 0; i < aasworld.numplanes; i++) - { - for (j = 0; j < 3; j++) - aasworld.planes[i].normal[j] = LittleFloat(aasworld.planes[i].normal[j]); - aasworld.planes[i].dist = LittleFloat(aasworld.planes[i].dist); - aasworld.planes[i].type = LittleLong(aasworld.planes[i].type); - } //end for - //edges - for (i = 0; i < aasworld.numedges; i++) - { - aasworld.edges[i].v[0] = LittleLong(aasworld.edges[i].v[0]); - aasworld.edges[i].v[1] = LittleLong(aasworld.edges[i].v[1]); - } //end for - //edgeindex - for (i = 0; i < aasworld.edgeindexsize; i++) - { - aasworld.edgeindex[i] = LittleLong(aasworld.edgeindex[i]); - } //end for - //faces - for (i = 0; i < aasworld.numfaces; i++) - { - aasworld.faces[i].planenum = LittleLong(aasworld.faces[i].planenum); - aasworld.faces[i].faceflags = LittleLong(aasworld.faces[i].faceflags); - aasworld.faces[i].numedges = LittleLong(aasworld.faces[i].numedges); - aasworld.faces[i].firstedge = LittleLong(aasworld.faces[i].firstedge); - aasworld.faces[i].frontarea = LittleLong(aasworld.faces[i].frontarea); - aasworld.faces[i].backarea = LittleLong(aasworld.faces[i].backarea); - } //end for - //face index - for (i = 0; i < aasworld.faceindexsize; i++) - { - aasworld.faceindex[i] = LittleLong(aasworld.faceindex[i]); - } //end for - //convex areas - for (i = 0; i < aasworld.numareas; i++) - { - aasworld.areas[i].areanum = LittleLong(aasworld.areas[i].areanum); - aasworld.areas[i].numfaces = LittleLong(aasworld.areas[i].numfaces); - aasworld.areas[i].firstface = LittleLong(aasworld.areas[i].firstface); - for (j = 0; j < 3; j++) - { - aasworld.areas[i].mins[j] = LittleFloat(aasworld.areas[i].mins[j]); - aasworld.areas[i].maxs[j] = LittleFloat(aasworld.areas[i].maxs[j]); - aasworld.areas[i].center[j] = LittleFloat(aasworld.areas[i].center[j]); - } //end for - } //end for - //area settings - for (i = 0; i < aasworld.numareasettings; i++) - { - aasworld.areasettings[i].contents = LittleLong(aasworld.areasettings[i].contents); - aasworld.areasettings[i].areaflags = LittleLong(aasworld.areasettings[i].areaflags); - aasworld.areasettings[i].presencetype = LittleLong(aasworld.areasettings[i].presencetype); - aasworld.areasettings[i].cluster = LittleLong(aasworld.areasettings[i].cluster); - aasworld.areasettings[i].clusterareanum = LittleLong(aasworld.areasettings[i].clusterareanum); - aasworld.areasettings[i].numreachableareas = LittleLong(aasworld.areasettings[i].numreachableareas); - aasworld.areasettings[i].firstreachablearea = LittleLong(aasworld.areasettings[i].firstreachablearea); - } //end for - //area reachability - for (i = 0; i < aasworld.reachabilitysize; i++) - { - aasworld.reachability[i].areanum = LittleLong(aasworld.reachability[i].areanum); - aasworld.reachability[i].facenum = LittleLong(aasworld.reachability[i].facenum); - aasworld.reachability[i].edgenum = LittleLong(aasworld.reachability[i].edgenum); - for (j = 0; j < 3; j++) - { - aasworld.reachability[i].start[j] = LittleFloat(aasworld.reachability[i].start[j]); - aasworld.reachability[i].end[j] = LittleFloat(aasworld.reachability[i].end[j]); - } //end for - aasworld.reachability[i].traveltype = LittleLong(aasworld.reachability[i].traveltype); - aasworld.reachability[i].traveltime = LittleShort(aasworld.reachability[i].traveltime); - } //end for - //nodes - for (i = 0; i < aasworld.numnodes; i++) - { - aasworld.nodes[i].planenum = LittleLong(aasworld.nodes[i].planenum); - aasworld.nodes[i].children[0] = LittleLong(aasworld.nodes[i].children[0]); - aasworld.nodes[i].children[1] = LittleLong(aasworld.nodes[i].children[1]); - } //end for - //cluster portals - for (i = 0; i < aasworld.numportals; i++) - { - aasworld.portals[i].areanum = LittleLong(aasworld.portals[i].areanum); - aasworld.portals[i].frontcluster = LittleLong(aasworld.portals[i].frontcluster); - aasworld.portals[i].backcluster = LittleLong(aasworld.portals[i].backcluster); - aasworld.portals[i].clusterareanum[0] = LittleLong(aasworld.portals[i].clusterareanum[0]); - aasworld.portals[i].clusterareanum[1] = LittleLong(aasworld.portals[i].clusterareanum[1]); - } //end for - //cluster portal index - for (i = 0; i < aasworld.portalindexsize; i++) - { - aasworld.portalindex[i] = LittleLong(aasworld.portalindex[i]); - } //end for - //cluster - for (i = 0; i < aasworld.numclusters; i++) - { - aasworld.clusters[i].numareas = LittleLong(aasworld.clusters[i].numareas); - aasworld.clusters[i].numreachabilityareas = LittleLong(aasworld.clusters[i].numreachabilityareas); - aasworld.clusters[i].numportals = LittleLong(aasworld.clusters[i].numportals); - aasworld.clusters[i].firstportal = LittleLong(aasworld.clusters[i].firstportal); - } //end for -} //end of the function AAS_SwapAASData -//=========================================================================== -// dump the current loaded aas file -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_DumpAASData(void) -{ - aasworld.numbboxes = 0; - if (aasworld.bboxes) FreeMemory(aasworld.bboxes); - aasworld.bboxes = NULL; - aasworld.numvertexes = 0; - if (aasworld.vertexes) FreeMemory(aasworld.vertexes); - aasworld.vertexes = NULL; - aasworld.numplanes = 0; - if (aasworld.planes) FreeMemory(aasworld.planes); - aasworld.planes = NULL; - aasworld.numedges = 0; - if (aasworld.edges) FreeMemory(aasworld.edges); - aasworld.edges = NULL; - aasworld.edgeindexsize = 0; - if (aasworld.edgeindex) FreeMemory(aasworld.edgeindex); - aasworld.edgeindex = NULL; - aasworld.numfaces = 0; - if (aasworld.faces) FreeMemory(aasworld.faces); - aasworld.faces = NULL; - aasworld.faceindexsize = 0; - if (aasworld.faceindex) FreeMemory(aasworld.faceindex); - aasworld.faceindex = NULL; - aasworld.numareas = 0; - if (aasworld.areas) FreeMemory(aasworld.areas); - aasworld.areas = NULL; - aasworld.numareasettings = 0; - if (aasworld.areasettings) FreeMemory(aasworld.areasettings); - aasworld.areasettings = NULL; - aasworld.reachabilitysize = 0; - if (aasworld.reachability) FreeMemory(aasworld.reachability); - aasworld.reachability = NULL; - aasworld.numnodes = 0; - if (aasworld.nodes) FreeMemory(aasworld.nodes); - aasworld.nodes = NULL; - aasworld.numportals = 0; - if (aasworld.portals) FreeMemory(aasworld.portals); - aasworld.portals = NULL; - aasworld.numportals = 0; - if (aasworld.portalindex) FreeMemory(aasworld.portalindex); - aasworld.portalindex = NULL; - aasworld.portalindexsize = 0; - if (aasworld.clusters) FreeMemory(aasworld.clusters); - aasworld.clusters = NULL; - aasworld.numclusters = 0; - // - aasworld.loaded = qfalse; - aasworld.initialized = qfalse; - aasworld.savefile = qfalse; -} //end of the function AAS_DumpAASData -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -#ifdef AASFILEDEBUG -void AAS_FileInfo(void) -{ - int i, n, optimized; - - botimport.Print(PRT_MESSAGE, "version = %d\n", AASVERSION); - botimport.Print(PRT_MESSAGE, "numvertexes = %d\n", aasworld.numvertexes); - botimport.Print(PRT_MESSAGE, "numplanes = %d\n", aasworld.numplanes); - botimport.Print(PRT_MESSAGE, "numedges = %d\n", aasworld.numedges); - botimport.Print(PRT_MESSAGE, "edgeindexsize = %d\n", aasworld.edgeindexsize); - botimport.Print(PRT_MESSAGE, "numfaces = %d\n", aasworld.numfaces); - botimport.Print(PRT_MESSAGE, "faceindexsize = %d\n", aasworld.faceindexsize); - botimport.Print(PRT_MESSAGE, "numareas = %d\n", aasworld.numareas); - botimport.Print(PRT_MESSAGE, "numareasettings = %d\n", aasworld.numareasettings); - botimport.Print(PRT_MESSAGE, "reachabilitysize = %d\n", aasworld.reachabilitysize); - botimport.Print(PRT_MESSAGE, "numnodes = %d\n", aasworld.numnodes); - botimport.Print(PRT_MESSAGE, "numportals = %d\n", aasworld.numportals); - botimport.Print(PRT_MESSAGE, "portalindexsize = %d\n", aasworld.portalindexsize); - botimport.Print(PRT_MESSAGE, "numclusters = %d\n", aasworld.numclusters); - // - for (n = 0, i = 0; i < aasworld.numareasettings; i++) - { - if (aasworld.areasettings[i].areaflags & AREA_GROUNDED) n++; - } //end for - botimport.Print(PRT_MESSAGE, "num grounded areas = %d\n", n); - // - botimport.Print(PRT_MESSAGE, "planes size %d bytes\n", aasworld.numplanes * sizeof(aas_plane_t)); - botimport.Print(PRT_MESSAGE, "areas size %d bytes\n", aasworld.numareas * sizeof(aas_area_t)); - botimport.Print(PRT_MESSAGE, "areasettings size %d bytes\n", aasworld.numareasettings * sizeof(aas_areasettings_t)); - botimport.Print(PRT_MESSAGE, "nodes size %d bytes\n", aasworld.numnodes * sizeof(aas_node_t)); - botimport.Print(PRT_MESSAGE, "reachability size %d bytes\n", aasworld.reachabilitysize * sizeof(aas_reachability_t)); - botimport.Print(PRT_MESSAGE, "portals size %d bytes\n", aasworld.numportals * sizeof(aas_portal_t)); - botimport.Print(PRT_MESSAGE, "clusters size %d bytes\n", aasworld.numclusters * sizeof(aas_cluster_t)); - - optimized = aasworld.numplanes * sizeof(aas_plane_t) + - aasworld.numareas * sizeof(aas_area_t) + - aasworld.numareasettings * sizeof(aas_areasettings_t) + - aasworld.numnodes * sizeof(aas_node_t) + - aasworld.reachabilitysize * sizeof(aas_reachability_t) + - aasworld.numportals * sizeof(aas_portal_t) + - aasworld.numclusters * sizeof(aas_cluster_t); - botimport.Print(PRT_MESSAGE, "optimzed size %d KB\n", optimized >> 10); -} //end of the function AAS_FileInfo -#endif //AASFILEDEBUG -//=========================================================================== -// allocate memory and read a lump of a AAS file -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -char *AAS_LoadAASLump(fileHandle_t fp, int offset, int length, int *lastoffset, int size) -{ - char *buf; - // - if (!length) - { - //just alloc a dummy - return (char *) GetClearedHunkMemory(size+1); - } //end if - //seek to the data - if (offset != *lastoffset) - { - botimport.Print(PRT_WARNING, "AAS file not sequentially read\n"); - if (botimport.FS_Seek(fp, offset, FS_SEEK_SET)) - { - AAS_Error("can't seek to aas lump\n"); - AAS_DumpAASData(); - botimport.FS_FCloseFile(fp); - return 0; - } //end if - } //end if - //allocate memory - buf = (char *) GetClearedHunkMemory(length+1); - //read the data - if (length) - { - botimport.FS_Read(buf, length, fp ); - *lastoffset += length; - } //end if - return buf; -} //end of the function AAS_LoadAASLump -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_DData(unsigned char *data, int size) -{ - int i; - - for (i = 0; i < size; i++) - { - data[i] ^= (unsigned char) i * 119; - } //end for -} //end of the function AAS_DData -//=========================================================================== -// load an aas file -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_LoadAASFile(char *filename) -{ - fileHandle_t fp; - aas_header_t header; - int offset, length, lastoffset; - - botimport.Print(PRT_MESSAGE, "trying to load %s\n", filename); - //dump current loaded aas file - AAS_DumpAASData(); - //open the file - botimport.FS_FOpenFile( filename, &fp, FS_READ ); - if (!fp) - { - AAS_Error("can't open %s\n", filename); - return BLERR_CANNOTOPENAASFILE; - } //end if - //read the header - botimport.FS_Read(&header, sizeof(aas_header_t), fp ); - lastoffset = sizeof(aas_header_t); - //check header identification - header.ident = LittleLong(header.ident); - if (header.ident != AASID) - { - AAS_Error("%s is not an AAS file\n", filename); - botimport.FS_FCloseFile(fp); - return BLERR_WRONGAASFILEID; - } //end if - //check the version - header.version = LittleLong(header.version); - // - if (header.version != AASVERSION_OLD && header.version != AASVERSION) - { - AAS_Error("aas file %s is version %i, not %i\n", filename, header.version, AASVERSION); - botimport.FS_FCloseFile(fp); - return BLERR_WRONGAASFILEVERSION; - } //end if - // - if (header.version == AASVERSION) - { - AAS_DData((unsigned char *) &header + 8, sizeof(aas_header_t) - 8); - } //end if - // - aasworld.bspchecksum = atoi(LibVarGetString( "sv_mapChecksum")); - if (LittleLong(header.bspchecksum) != aasworld.bspchecksum) - { - AAS_Error("aas file %s is out of date\n", filename); - botimport.FS_FCloseFile(fp); - return BLERR_WRONGAASFILEVERSION; - } //end if - //load the lumps: - //bounding boxes - offset = LittleLong(header.lumps[AASLUMP_BBOXES].fileofs); - length = LittleLong(header.lumps[AASLUMP_BBOXES].filelen); - aasworld.bboxes = (aas_bbox_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_bbox_t)); - aasworld.numbboxes = length / sizeof(aas_bbox_t); - if (aasworld.numbboxes && !aasworld.bboxes) return BLERR_CANNOTREADAASLUMP; - //vertexes - offset = LittleLong(header.lumps[AASLUMP_VERTEXES].fileofs); - length = LittleLong(header.lumps[AASLUMP_VERTEXES].filelen); - aasworld.vertexes = (aas_vertex_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_vertex_t)); - aasworld.numvertexes = length / sizeof(aas_vertex_t); - if (aasworld.numvertexes && !aasworld.vertexes) return BLERR_CANNOTREADAASLUMP; - //planes - offset = LittleLong(header.lumps[AASLUMP_PLANES].fileofs); - length = LittleLong(header.lumps[AASLUMP_PLANES].filelen); - aasworld.planes = (aas_plane_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_plane_t)); - aasworld.numplanes = length / sizeof(aas_plane_t); - if (aasworld.numplanes && !aasworld.planes) return BLERR_CANNOTREADAASLUMP; - //edges - offset = LittleLong(header.lumps[AASLUMP_EDGES].fileofs); - length = LittleLong(header.lumps[AASLUMP_EDGES].filelen); - aasworld.edges = (aas_edge_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_edge_t)); - aasworld.numedges = length / sizeof(aas_edge_t); - if (aasworld.numedges && !aasworld.edges) return BLERR_CANNOTREADAASLUMP; - //edgeindex - offset = LittleLong(header.lumps[AASLUMP_EDGEINDEX].fileofs); - length = LittleLong(header.lumps[AASLUMP_EDGEINDEX].filelen); - aasworld.edgeindex = (aas_edgeindex_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_edgeindex_t)); - aasworld.edgeindexsize = length / sizeof(aas_edgeindex_t); - if (aasworld.edgeindexsize && !aasworld.edgeindex) return BLERR_CANNOTREADAASLUMP; - //faces - offset = LittleLong(header.lumps[AASLUMP_FACES].fileofs); - length = LittleLong(header.lumps[AASLUMP_FACES].filelen); - aasworld.faces = (aas_face_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_face_t)); - aasworld.numfaces = length / sizeof(aas_face_t); - if (aasworld.numfaces && !aasworld.faces) return BLERR_CANNOTREADAASLUMP; - //faceindex - offset = LittleLong(header.lumps[AASLUMP_FACEINDEX].fileofs); - length = LittleLong(header.lumps[AASLUMP_FACEINDEX].filelen); - aasworld.faceindex = (aas_faceindex_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_faceindex_t)); - aasworld.faceindexsize = length / sizeof(aas_faceindex_t); - if (aasworld.faceindexsize && !aasworld.faceindex) return BLERR_CANNOTREADAASLUMP; - //convex areas - offset = LittleLong(header.lumps[AASLUMP_AREAS].fileofs); - length = LittleLong(header.lumps[AASLUMP_AREAS].filelen); - aasworld.areas = (aas_area_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_area_t)); - aasworld.numareas = length / sizeof(aas_area_t); - if (aasworld.numareas && !aasworld.areas) return BLERR_CANNOTREADAASLUMP; - //area settings - offset = LittleLong(header.lumps[AASLUMP_AREASETTINGS].fileofs); - length = LittleLong(header.lumps[AASLUMP_AREASETTINGS].filelen); - aasworld.areasettings = (aas_areasettings_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_areasettings_t)); - aasworld.numareasettings = length / sizeof(aas_areasettings_t); - if (aasworld.numareasettings && !aasworld.areasettings) return BLERR_CANNOTREADAASLUMP; - //reachability list - offset = LittleLong(header.lumps[AASLUMP_REACHABILITY].fileofs); - length = LittleLong(header.lumps[AASLUMP_REACHABILITY].filelen); - aasworld.reachability = (aas_reachability_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_reachability_t)); - aasworld.reachabilitysize = length / sizeof(aas_reachability_t); - if (aasworld.reachabilitysize && !aasworld.reachability) return BLERR_CANNOTREADAASLUMP; - //nodes - offset = LittleLong(header.lumps[AASLUMP_NODES].fileofs); - length = LittleLong(header.lumps[AASLUMP_NODES].filelen); - aasworld.nodes = (aas_node_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_node_t)); - aasworld.numnodes = length / sizeof(aas_node_t); - if (aasworld.numnodes && !aasworld.nodes) return BLERR_CANNOTREADAASLUMP; - //cluster portals - offset = LittleLong(header.lumps[AASLUMP_PORTALS].fileofs); - length = LittleLong(header.lumps[AASLUMP_PORTALS].filelen); - aasworld.portals = (aas_portal_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_portal_t)); - aasworld.numportals = length / sizeof(aas_portal_t); - if (aasworld.numportals && !aasworld.portals) return BLERR_CANNOTREADAASLUMP; - //cluster portal index - offset = LittleLong(header.lumps[AASLUMP_PORTALINDEX].fileofs); - length = LittleLong(header.lumps[AASLUMP_PORTALINDEX].filelen); - aasworld.portalindex = (aas_portalindex_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_portalindex_t)); - aasworld.portalindexsize = length / sizeof(aas_portalindex_t); - if (aasworld.portalindexsize && !aasworld.portalindex) return BLERR_CANNOTREADAASLUMP; - //clusters - offset = LittleLong(header.lumps[AASLUMP_CLUSTERS].fileofs); - length = LittleLong(header.lumps[AASLUMP_CLUSTERS].filelen); - aasworld.clusters = (aas_cluster_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_cluster_t)); - aasworld.numclusters = length / sizeof(aas_cluster_t); - if (aasworld.numclusters && !aasworld.clusters) return BLERR_CANNOTREADAASLUMP; - //swap everything - AAS_SwapAASData(); - //aas file is loaded - aasworld.loaded = qtrue; - //close the file - botimport.FS_FCloseFile(fp); - // -#ifdef AASFILEDEBUG - AAS_FileInfo(); -#endif //AASFILEDEBUG - // - return BLERR_NOERROR; -} //end of the function AAS_LoadAASFile -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -static int AAS_WriteAASLump_offset; - -int AAS_WriteAASLump(fileHandle_t fp, aas_header_t *h, int lumpnum, void *data, int length) -{ - aas_lump_t *lump; - - lump = &h->lumps[lumpnum]; - - lump->fileofs = LittleLong(AAS_WriteAASLump_offset); //LittleLong(ftell(fp)); - lump->filelen = LittleLong(length); - - if (length > 0) - { - botimport.FS_Write(data, length, fp ); - } //end if - - AAS_WriteAASLump_offset += length; - - return qtrue; -} //end of the function AAS_WriteAASLump -//=========================================================================== -// aas data is useless after writing to file because it is byte swapped -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean AAS_WriteAASFile(char *filename) -{ - aas_header_t header; - fileHandle_t fp; - - botimport.Print(PRT_MESSAGE, "writing %s\n", filename); - //swap the aas data - AAS_SwapAASData(); - //initialize the file header - Com_Memset(&header, 0, sizeof(aas_header_t)); - header.ident = LittleLong(AASID); - header.version = LittleLong(AASVERSION); - header.bspchecksum = LittleLong(aasworld.bspchecksum); - //open a new file - botimport.FS_FOpenFile( filename, &fp, FS_WRITE ); - if (!fp) - { - botimport.Print(PRT_ERROR, "error opening %s\n", filename); - return qfalse; - } //end if - //write the header - botimport.FS_Write(&header, sizeof(aas_header_t), fp); - AAS_WriteAASLump_offset = sizeof(aas_header_t); - //add the data lumps to the file - if (!AAS_WriteAASLump(fp, &header, AASLUMP_BBOXES, aasworld.bboxes, - aasworld.numbboxes * sizeof(aas_bbox_t))) return qfalse; - if (!AAS_WriteAASLump(fp, &header, AASLUMP_VERTEXES, aasworld.vertexes, - aasworld.numvertexes * sizeof(aas_vertex_t))) return qfalse; - if (!AAS_WriteAASLump(fp, &header, AASLUMP_PLANES, aasworld.planes, - aasworld.numplanes * sizeof(aas_plane_t))) return qfalse; - if (!AAS_WriteAASLump(fp, &header, AASLUMP_EDGES, aasworld.edges, - aasworld.numedges * sizeof(aas_edge_t))) return qfalse; - if (!AAS_WriteAASLump(fp, &header, AASLUMP_EDGEINDEX, aasworld.edgeindex, - aasworld.edgeindexsize * sizeof(aas_edgeindex_t))) return qfalse; - if (!AAS_WriteAASLump(fp, &header, AASLUMP_FACES, aasworld.faces, - aasworld.numfaces * sizeof(aas_face_t))) return qfalse; - if (!AAS_WriteAASLump(fp, &header, AASLUMP_FACEINDEX, aasworld.faceindex, - aasworld.faceindexsize * sizeof(aas_faceindex_t))) return qfalse; - if (!AAS_WriteAASLump(fp, &header, AASLUMP_AREAS, aasworld.areas, - aasworld.numareas * sizeof(aas_area_t))) return qfalse; - if (!AAS_WriteAASLump(fp, &header, AASLUMP_AREASETTINGS, aasworld.areasettings, - aasworld.numareasettings * sizeof(aas_areasettings_t))) return qfalse; - if (!AAS_WriteAASLump(fp, &header, AASLUMP_REACHABILITY, aasworld.reachability, - aasworld.reachabilitysize * sizeof(aas_reachability_t))) return qfalse; - if (!AAS_WriteAASLump(fp, &header, AASLUMP_NODES, aasworld.nodes, - aasworld.numnodes * sizeof(aas_node_t))) return qfalse; - if (!AAS_WriteAASLump(fp, &header, AASLUMP_PORTALS, aasworld.portals, - aasworld.numportals * sizeof(aas_portal_t))) return qfalse; - if (!AAS_WriteAASLump(fp, &header, AASLUMP_PORTALINDEX, aasworld.portalindex, - aasworld.portalindexsize * sizeof(aas_portalindex_t))) return qfalse; - if (!AAS_WriteAASLump(fp, &header, AASLUMP_CLUSTERS, aasworld.clusters, - aasworld.numclusters * sizeof(aas_cluster_t))) return qfalse; - //rewrite the header with the added lumps - botimport.FS_Seek(fp, 0, FS_SEEK_SET); - AAS_DData((unsigned char *) &header + 8, sizeof(aas_header_t) - 8); - botimport.FS_Write(&header, sizeof(aas_header_t), fp); - //close the file - botimport.FS_FCloseFile(fp); - return qtrue; -} //end of the function AAS_WriteAASFile +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_file.c + * + * desc: AAS file loading/writing + * + * $Archive: /MissionPack/code/botlib/be_aas_file.c $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_libvar.h" +#include "l_utils.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_aas_def.h" + +//#define AASFILEDEBUG + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_SwapAASData(void) +{ + int i, j; + //bounding boxes + for (i = 0; i < aasworld.numbboxes; i++) + { + aasworld.bboxes[i].presencetype = LittleLong(aasworld.bboxes[i].presencetype); + aasworld.bboxes[i].flags = LittleLong(aasworld.bboxes[i].flags); + for (j = 0; j < 3; j++) + { + aasworld.bboxes[i].mins[j] = LittleLong(aasworld.bboxes[i].mins[j]); + aasworld.bboxes[i].maxs[j] = LittleLong(aasworld.bboxes[i].maxs[j]); + } //end for + } //end for + //vertexes + for (i = 0; i < aasworld.numvertexes; i++) + { + for (j = 0; j < 3; j++) + aasworld.vertexes[i][j] = LittleFloat(aasworld.vertexes[i][j]); + } //end for + //planes + for (i = 0; i < aasworld.numplanes; i++) + { + for (j = 0; j < 3; j++) + aasworld.planes[i].normal[j] = LittleFloat(aasworld.planes[i].normal[j]); + aasworld.planes[i].dist = LittleFloat(aasworld.planes[i].dist); + aasworld.planes[i].type = LittleLong(aasworld.planes[i].type); + } //end for + //edges + for (i = 0; i < aasworld.numedges; i++) + { + aasworld.edges[i].v[0] = LittleLong(aasworld.edges[i].v[0]); + aasworld.edges[i].v[1] = LittleLong(aasworld.edges[i].v[1]); + } //end for + //edgeindex + for (i = 0; i < aasworld.edgeindexsize; i++) + { + aasworld.edgeindex[i] = LittleLong(aasworld.edgeindex[i]); + } //end for + //faces + for (i = 0; i < aasworld.numfaces; i++) + { + aasworld.faces[i].planenum = LittleLong(aasworld.faces[i].planenum); + aasworld.faces[i].faceflags = LittleLong(aasworld.faces[i].faceflags); + aasworld.faces[i].numedges = LittleLong(aasworld.faces[i].numedges); + aasworld.faces[i].firstedge = LittleLong(aasworld.faces[i].firstedge); + aasworld.faces[i].frontarea = LittleLong(aasworld.faces[i].frontarea); + aasworld.faces[i].backarea = LittleLong(aasworld.faces[i].backarea); + } //end for + //face index + for (i = 0; i < aasworld.faceindexsize; i++) + { + aasworld.faceindex[i] = LittleLong(aasworld.faceindex[i]); + } //end for + //convex areas + for (i = 0; i < aasworld.numareas; i++) + { + aasworld.areas[i].areanum = LittleLong(aasworld.areas[i].areanum); + aasworld.areas[i].numfaces = LittleLong(aasworld.areas[i].numfaces); + aasworld.areas[i].firstface = LittleLong(aasworld.areas[i].firstface); + for (j = 0; j < 3; j++) + { + aasworld.areas[i].mins[j] = LittleFloat(aasworld.areas[i].mins[j]); + aasworld.areas[i].maxs[j] = LittleFloat(aasworld.areas[i].maxs[j]); + aasworld.areas[i].center[j] = LittleFloat(aasworld.areas[i].center[j]); + } //end for + } //end for + //area settings + for (i = 0; i < aasworld.numareasettings; i++) + { + aasworld.areasettings[i].contents = LittleLong(aasworld.areasettings[i].contents); + aasworld.areasettings[i].areaflags = LittleLong(aasworld.areasettings[i].areaflags); + aasworld.areasettings[i].presencetype = LittleLong(aasworld.areasettings[i].presencetype); + aasworld.areasettings[i].cluster = LittleLong(aasworld.areasettings[i].cluster); + aasworld.areasettings[i].clusterareanum = LittleLong(aasworld.areasettings[i].clusterareanum); + aasworld.areasettings[i].numreachableareas = LittleLong(aasworld.areasettings[i].numreachableareas); + aasworld.areasettings[i].firstreachablearea = LittleLong(aasworld.areasettings[i].firstreachablearea); + } //end for + //area reachability + for (i = 0; i < aasworld.reachabilitysize; i++) + { + aasworld.reachability[i].areanum = LittleLong(aasworld.reachability[i].areanum); + aasworld.reachability[i].facenum = LittleLong(aasworld.reachability[i].facenum); + aasworld.reachability[i].edgenum = LittleLong(aasworld.reachability[i].edgenum); + for (j = 0; j < 3; j++) + { + aasworld.reachability[i].start[j] = LittleFloat(aasworld.reachability[i].start[j]); + aasworld.reachability[i].end[j] = LittleFloat(aasworld.reachability[i].end[j]); + } //end for + aasworld.reachability[i].traveltype = LittleLong(aasworld.reachability[i].traveltype); + aasworld.reachability[i].traveltime = LittleShort(aasworld.reachability[i].traveltime); + } //end for + //nodes + for (i = 0; i < aasworld.numnodes; i++) + { + aasworld.nodes[i].planenum = LittleLong(aasworld.nodes[i].planenum); + aasworld.nodes[i].children[0] = LittleLong(aasworld.nodes[i].children[0]); + aasworld.nodes[i].children[1] = LittleLong(aasworld.nodes[i].children[1]); + } //end for + //cluster portals + for (i = 0; i < aasworld.numportals; i++) + { + aasworld.portals[i].areanum = LittleLong(aasworld.portals[i].areanum); + aasworld.portals[i].frontcluster = LittleLong(aasworld.portals[i].frontcluster); + aasworld.portals[i].backcluster = LittleLong(aasworld.portals[i].backcluster); + aasworld.portals[i].clusterareanum[0] = LittleLong(aasworld.portals[i].clusterareanum[0]); + aasworld.portals[i].clusterareanum[1] = LittleLong(aasworld.portals[i].clusterareanum[1]); + } //end for + //cluster portal index + for (i = 0; i < aasworld.portalindexsize; i++) + { + aasworld.portalindex[i] = LittleLong(aasworld.portalindex[i]); + } //end for + //cluster + for (i = 0; i < aasworld.numclusters; i++) + { + aasworld.clusters[i].numareas = LittleLong(aasworld.clusters[i].numareas); + aasworld.clusters[i].numreachabilityareas = LittleLong(aasworld.clusters[i].numreachabilityareas); + aasworld.clusters[i].numportals = LittleLong(aasworld.clusters[i].numportals); + aasworld.clusters[i].firstportal = LittleLong(aasworld.clusters[i].firstportal); + } //end for +} //end of the function AAS_SwapAASData +//=========================================================================== +// dump the current loaded aas file +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DumpAASData(void) +{ + aasworld.numbboxes = 0; + if (aasworld.bboxes) FreeMemory(aasworld.bboxes); + aasworld.bboxes = NULL; + aasworld.numvertexes = 0; + if (aasworld.vertexes) FreeMemory(aasworld.vertexes); + aasworld.vertexes = NULL; + aasworld.numplanes = 0; + if (aasworld.planes) FreeMemory(aasworld.planes); + aasworld.planes = NULL; + aasworld.numedges = 0; + if (aasworld.edges) FreeMemory(aasworld.edges); + aasworld.edges = NULL; + aasworld.edgeindexsize = 0; + if (aasworld.edgeindex) FreeMemory(aasworld.edgeindex); + aasworld.edgeindex = NULL; + aasworld.numfaces = 0; + if (aasworld.faces) FreeMemory(aasworld.faces); + aasworld.faces = NULL; + aasworld.faceindexsize = 0; + if (aasworld.faceindex) FreeMemory(aasworld.faceindex); + aasworld.faceindex = NULL; + aasworld.numareas = 0; + if (aasworld.areas) FreeMemory(aasworld.areas); + aasworld.areas = NULL; + aasworld.numareasettings = 0; + if (aasworld.areasettings) FreeMemory(aasworld.areasettings); + aasworld.areasettings = NULL; + aasworld.reachabilitysize = 0; + if (aasworld.reachability) FreeMemory(aasworld.reachability); + aasworld.reachability = NULL; + aasworld.numnodes = 0; + if (aasworld.nodes) FreeMemory(aasworld.nodes); + aasworld.nodes = NULL; + aasworld.numportals = 0; + if (aasworld.portals) FreeMemory(aasworld.portals); + aasworld.portals = NULL; + aasworld.numportals = 0; + if (aasworld.portalindex) FreeMemory(aasworld.portalindex); + aasworld.portalindex = NULL; + aasworld.portalindexsize = 0; + if (aasworld.clusters) FreeMemory(aasworld.clusters); + aasworld.clusters = NULL; + aasworld.numclusters = 0; + // + aasworld.loaded = qfalse; + aasworld.initialized = qfalse; + aasworld.savefile = qfalse; +} //end of the function AAS_DumpAASData +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef AASFILEDEBUG +void AAS_FileInfo(void) +{ + int i, n, optimized; + + botimport.Print(PRT_MESSAGE, "version = %d\n", AASVERSION); + botimport.Print(PRT_MESSAGE, "numvertexes = %d\n", aasworld.numvertexes); + botimport.Print(PRT_MESSAGE, "numplanes = %d\n", aasworld.numplanes); + botimport.Print(PRT_MESSAGE, "numedges = %d\n", aasworld.numedges); + botimport.Print(PRT_MESSAGE, "edgeindexsize = %d\n", aasworld.edgeindexsize); + botimport.Print(PRT_MESSAGE, "numfaces = %d\n", aasworld.numfaces); + botimport.Print(PRT_MESSAGE, "faceindexsize = %d\n", aasworld.faceindexsize); + botimport.Print(PRT_MESSAGE, "numareas = %d\n", aasworld.numareas); + botimport.Print(PRT_MESSAGE, "numareasettings = %d\n", aasworld.numareasettings); + botimport.Print(PRT_MESSAGE, "reachabilitysize = %d\n", aasworld.reachabilitysize); + botimport.Print(PRT_MESSAGE, "numnodes = %d\n", aasworld.numnodes); + botimport.Print(PRT_MESSAGE, "numportals = %d\n", aasworld.numportals); + botimport.Print(PRT_MESSAGE, "portalindexsize = %d\n", aasworld.portalindexsize); + botimport.Print(PRT_MESSAGE, "numclusters = %d\n", aasworld.numclusters); + // + for (n = 0, i = 0; i < aasworld.numareasettings; i++) + { + if (aasworld.areasettings[i].areaflags & AREA_GROUNDED) n++; + } //end for + botimport.Print(PRT_MESSAGE, "num grounded areas = %d\n", n); + // + botimport.Print(PRT_MESSAGE, "planes size %d bytes\n", aasworld.numplanes * sizeof(aas_plane_t)); + botimport.Print(PRT_MESSAGE, "areas size %d bytes\n", aasworld.numareas * sizeof(aas_area_t)); + botimport.Print(PRT_MESSAGE, "areasettings size %d bytes\n", aasworld.numareasettings * sizeof(aas_areasettings_t)); + botimport.Print(PRT_MESSAGE, "nodes size %d bytes\n", aasworld.numnodes * sizeof(aas_node_t)); + botimport.Print(PRT_MESSAGE, "reachability size %d bytes\n", aasworld.reachabilitysize * sizeof(aas_reachability_t)); + botimport.Print(PRT_MESSAGE, "portals size %d bytes\n", aasworld.numportals * sizeof(aas_portal_t)); + botimport.Print(PRT_MESSAGE, "clusters size %d bytes\n", aasworld.numclusters * sizeof(aas_cluster_t)); + + optimized = aasworld.numplanes * sizeof(aas_plane_t) + + aasworld.numareas * sizeof(aas_area_t) + + aasworld.numareasettings * sizeof(aas_areasettings_t) + + aasworld.numnodes * sizeof(aas_node_t) + + aasworld.reachabilitysize * sizeof(aas_reachability_t) + + aasworld.numportals * sizeof(aas_portal_t) + + aasworld.numclusters * sizeof(aas_cluster_t); + botimport.Print(PRT_MESSAGE, "optimzed size %d KB\n", optimized >> 10); +} //end of the function AAS_FileInfo +#endif //AASFILEDEBUG +//=========================================================================== +// allocate memory and read a lump of a AAS file +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *AAS_LoadAASLump(fileHandle_t fp, int offset, int length, int *lastoffset, int size) +{ + char *buf; + // + if (!length) + { + //just alloc a dummy + return (char *) GetClearedHunkMemory(size+1); + } //end if + //seek to the data + if (offset != *lastoffset) + { + botimport.Print(PRT_WARNING, "AAS file not sequentially read\n"); + if (botimport.FS_Seek(fp, offset, FS_SEEK_SET)) + { + AAS_Error("can't seek to aas lump\n"); + AAS_DumpAASData(); + botimport.FS_FCloseFile(fp); + return 0; + } //end if + } //end if + //allocate memory + buf = (char *) GetClearedHunkMemory(length+1); + //read the data + if (length) + { + botimport.FS_Read(buf, length, fp ); + *lastoffset += length; + } //end if + return buf; +} //end of the function AAS_LoadAASLump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DData(unsigned char *data, int size) +{ + int i; + + for (i = 0; i < size; i++) + { + data[i] ^= (unsigned char) i * 119; + } //end for +} //end of the function AAS_DData +//=========================================================================== +// load an aas file +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_LoadAASFile(char *filename) +{ + fileHandle_t fp; + aas_header_t header; + int offset, length, lastoffset; + + botimport.Print(PRT_MESSAGE, "trying to load %s\n", filename); + //dump current loaded aas file + AAS_DumpAASData(); + //open the file + botimport.FS_FOpenFile( filename, &fp, FS_READ ); + if (!fp) + { + AAS_Error("can't open %s\n", filename); + return BLERR_CANNOTOPENAASFILE; + } //end if + //read the header + botimport.FS_Read(&header, sizeof(aas_header_t), fp ); + lastoffset = sizeof(aas_header_t); + //check header identification + header.ident = LittleLong(header.ident); + if (header.ident != AASID) + { + AAS_Error("%s is not an AAS file\n", filename); + botimport.FS_FCloseFile(fp); + return BLERR_WRONGAASFILEID; + } //end if + //check the version + header.version = LittleLong(header.version); + // + if (header.version != AASVERSION_OLD && header.version != AASVERSION) + { + AAS_Error("aas file %s is version %i, not %i\n", filename, header.version, AASVERSION); + botimport.FS_FCloseFile(fp); + return BLERR_WRONGAASFILEVERSION; + } //end if + // + if (header.version == AASVERSION) + { + AAS_DData((unsigned char *) &header + 8, sizeof(aas_header_t) - 8); + } //end if + // + aasworld.bspchecksum = atoi(LibVarGetString( "sv_mapChecksum")); + if (LittleLong(header.bspchecksum) != aasworld.bspchecksum) + { + AAS_Error("aas file %s is out of date\n", filename); + botimport.FS_FCloseFile(fp); + return BLERR_WRONGAASFILEVERSION; + } //end if + //load the lumps: + //bounding boxes + offset = LittleLong(header.lumps[AASLUMP_BBOXES].fileofs); + length = LittleLong(header.lumps[AASLUMP_BBOXES].filelen); + aasworld.bboxes = (aas_bbox_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_bbox_t)); + aasworld.numbboxes = length / sizeof(aas_bbox_t); + if (aasworld.numbboxes && !aasworld.bboxes) return BLERR_CANNOTREADAASLUMP; + //vertexes + offset = LittleLong(header.lumps[AASLUMP_VERTEXES].fileofs); + length = LittleLong(header.lumps[AASLUMP_VERTEXES].filelen); + aasworld.vertexes = (aas_vertex_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_vertex_t)); + aasworld.numvertexes = length / sizeof(aas_vertex_t); + if (aasworld.numvertexes && !aasworld.vertexes) return BLERR_CANNOTREADAASLUMP; + //planes + offset = LittleLong(header.lumps[AASLUMP_PLANES].fileofs); + length = LittleLong(header.lumps[AASLUMP_PLANES].filelen); + aasworld.planes = (aas_plane_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_plane_t)); + aasworld.numplanes = length / sizeof(aas_plane_t); + if (aasworld.numplanes && !aasworld.planes) return BLERR_CANNOTREADAASLUMP; + //edges + offset = LittleLong(header.lumps[AASLUMP_EDGES].fileofs); + length = LittleLong(header.lumps[AASLUMP_EDGES].filelen); + aasworld.edges = (aas_edge_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_edge_t)); + aasworld.numedges = length / sizeof(aas_edge_t); + if (aasworld.numedges && !aasworld.edges) return BLERR_CANNOTREADAASLUMP; + //edgeindex + offset = LittleLong(header.lumps[AASLUMP_EDGEINDEX].fileofs); + length = LittleLong(header.lumps[AASLUMP_EDGEINDEX].filelen); + aasworld.edgeindex = (aas_edgeindex_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_edgeindex_t)); + aasworld.edgeindexsize = length / sizeof(aas_edgeindex_t); + if (aasworld.edgeindexsize && !aasworld.edgeindex) return BLERR_CANNOTREADAASLUMP; + //faces + offset = LittleLong(header.lumps[AASLUMP_FACES].fileofs); + length = LittleLong(header.lumps[AASLUMP_FACES].filelen); + aasworld.faces = (aas_face_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_face_t)); + aasworld.numfaces = length / sizeof(aas_face_t); + if (aasworld.numfaces && !aasworld.faces) return BLERR_CANNOTREADAASLUMP; + //faceindex + offset = LittleLong(header.lumps[AASLUMP_FACEINDEX].fileofs); + length = LittleLong(header.lumps[AASLUMP_FACEINDEX].filelen); + aasworld.faceindex = (aas_faceindex_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_faceindex_t)); + aasworld.faceindexsize = length / sizeof(aas_faceindex_t); + if (aasworld.faceindexsize && !aasworld.faceindex) return BLERR_CANNOTREADAASLUMP; + //convex areas + offset = LittleLong(header.lumps[AASLUMP_AREAS].fileofs); + length = LittleLong(header.lumps[AASLUMP_AREAS].filelen); + aasworld.areas = (aas_area_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_area_t)); + aasworld.numareas = length / sizeof(aas_area_t); + if (aasworld.numareas && !aasworld.areas) return BLERR_CANNOTREADAASLUMP; + //area settings + offset = LittleLong(header.lumps[AASLUMP_AREASETTINGS].fileofs); + length = LittleLong(header.lumps[AASLUMP_AREASETTINGS].filelen); + aasworld.areasettings = (aas_areasettings_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_areasettings_t)); + aasworld.numareasettings = length / sizeof(aas_areasettings_t); + if (aasworld.numareasettings && !aasworld.areasettings) return BLERR_CANNOTREADAASLUMP; + //reachability list + offset = LittleLong(header.lumps[AASLUMP_REACHABILITY].fileofs); + length = LittleLong(header.lumps[AASLUMP_REACHABILITY].filelen); + aasworld.reachability = (aas_reachability_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_reachability_t)); + aasworld.reachabilitysize = length / sizeof(aas_reachability_t); + if (aasworld.reachabilitysize && !aasworld.reachability) return BLERR_CANNOTREADAASLUMP; + //nodes + offset = LittleLong(header.lumps[AASLUMP_NODES].fileofs); + length = LittleLong(header.lumps[AASLUMP_NODES].filelen); + aasworld.nodes = (aas_node_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_node_t)); + aasworld.numnodes = length / sizeof(aas_node_t); + if (aasworld.numnodes && !aasworld.nodes) return BLERR_CANNOTREADAASLUMP; + //cluster portals + offset = LittleLong(header.lumps[AASLUMP_PORTALS].fileofs); + length = LittleLong(header.lumps[AASLUMP_PORTALS].filelen); + aasworld.portals = (aas_portal_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_portal_t)); + aasworld.numportals = length / sizeof(aas_portal_t); + if (aasworld.numportals && !aasworld.portals) return BLERR_CANNOTREADAASLUMP; + //cluster portal index + offset = LittleLong(header.lumps[AASLUMP_PORTALINDEX].fileofs); + length = LittleLong(header.lumps[AASLUMP_PORTALINDEX].filelen); + aasworld.portalindex = (aas_portalindex_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_portalindex_t)); + aasworld.portalindexsize = length / sizeof(aas_portalindex_t); + if (aasworld.portalindexsize && !aasworld.portalindex) return BLERR_CANNOTREADAASLUMP; + //clusters + offset = LittleLong(header.lumps[AASLUMP_CLUSTERS].fileofs); + length = LittleLong(header.lumps[AASLUMP_CLUSTERS].filelen); + aasworld.clusters = (aas_cluster_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_cluster_t)); + aasworld.numclusters = length / sizeof(aas_cluster_t); + if (aasworld.numclusters && !aasworld.clusters) return BLERR_CANNOTREADAASLUMP; + //swap everything + AAS_SwapAASData(); + //aas file is loaded + aasworld.loaded = qtrue; + //close the file + botimport.FS_FCloseFile(fp); + // +#ifdef AASFILEDEBUG + AAS_FileInfo(); +#endif //AASFILEDEBUG + // + return BLERR_NOERROR; +} //end of the function AAS_LoadAASFile +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +static int AAS_WriteAASLump_offset; + +int AAS_WriteAASLump(fileHandle_t fp, aas_header_t *h, int lumpnum, void *data, int length) +{ + aas_lump_t *lump; + + lump = &h->lumps[lumpnum]; + + lump->fileofs = LittleLong(AAS_WriteAASLump_offset); //LittleLong(ftell(fp)); + lump->filelen = LittleLong(length); + + if (length > 0) + { + botimport.FS_Write(data, length, fp ); + } //end if + + AAS_WriteAASLump_offset += length; + + return qtrue; +} //end of the function AAS_WriteAASLump +//=========================================================================== +// aas data is useless after writing to file because it is byte swapped +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_WriteAASFile(char *filename) +{ + aas_header_t header; + fileHandle_t fp; + + botimport.Print(PRT_MESSAGE, "writing %s\n", filename); + //swap the aas data + AAS_SwapAASData(); + //initialize the file header + Com_Memset(&header, 0, sizeof(aas_header_t)); + header.ident = LittleLong(AASID); + header.version = LittleLong(AASVERSION); + header.bspchecksum = LittleLong(aasworld.bspchecksum); + //open a new file + botimport.FS_FOpenFile( filename, &fp, FS_WRITE ); + if (!fp) + { + botimport.Print(PRT_ERROR, "error opening %s\n", filename); + return qfalse; + } //end if + //write the header + botimport.FS_Write(&header, sizeof(aas_header_t), fp); + AAS_WriteAASLump_offset = sizeof(aas_header_t); + //add the data lumps to the file + if (!AAS_WriteAASLump(fp, &header, AASLUMP_BBOXES, aasworld.bboxes, + aasworld.numbboxes * sizeof(aas_bbox_t))) return qfalse; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_VERTEXES, aasworld.vertexes, + aasworld.numvertexes * sizeof(aas_vertex_t))) return qfalse; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_PLANES, aasworld.planes, + aasworld.numplanes * sizeof(aas_plane_t))) return qfalse; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_EDGES, aasworld.edges, + aasworld.numedges * sizeof(aas_edge_t))) return qfalse; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_EDGEINDEX, aasworld.edgeindex, + aasworld.edgeindexsize * sizeof(aas_edgeindex_t))) return qfalse; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_FACES, aasworld.faces, + aasworld.numfaces * sizeof(aas_face_t))) return qfalse; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_FACEINDEX, aasworld.faceindex, + aasworld.faceindexsize * sizeof(aas_faceindex_t))) return qfalse; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_AREAS, aasworld.areas, + aasworld.numareas * sizeof(aas_area_t))) return qfalse; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_AREASETTINGS, aasworld.areasettings, + aasworld.numareasettings * sizeof(aas_areasettings_t))) return qfalse; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_REACHABILITY, aasworld.reachability, + aasworld.reachabilitysize * sizeof(aas_reachability_t))) return qfalse; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_NODES, aasworld.nodes, + aasworld.numnodes * sizeof(aas_node_t))) return qfalse; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_PORTALS, aasworld.portals, + aasworld.numportals * sizeof(aas_portal_t))) return qfalse; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_PORTALINDEX, aasworld.portalindex, + aasworld.portalindexsize * sizeof(aas_portalindex_t))) return qfalse; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_CLUSTERS, aasworld.clusters, + aasworld.numclusters * sizeof(aas_cluster_t))) return qfalse; + //rewrite the header with the added lumps + botimport.FS_Seek(fp, 0, FS_SEEK_SET); + AAS_DData((unsigned char *) &header + 8, sizeof(aas_header_t) - 8); + botimport.FS_Write(&header, sizeof(aas_header_t), fp); + //close the file + botimport.FS_FCloseFile(fp); + return qtrue; +} //end of the function AAS_WriteAASFile diff --git a/code/botlib/be_aas_file.h b/code/botlib/be_aas_file.h index 87a38d0..0d9383e 100755 --- a/code/botlib/be_aas_file.h +++ b/code/botlib/be_aas_file.h @@ -1,42 +1,42 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: be_aas_file.h - * - * desc: AAS - * - * $Archive: /source/code/botlib/be_aas_file.h $ - * - *****************************************************************************/ - -#ifdef AASINTERN -//loads the AAS file with the given name -int AAS_LoadAASFile(char *filename); -//writes an AAS file with the given name -qboolean AAS_WriteAASFile(char *filename); -//dumps the loaded AAS data -void AAS_DumpAASData(void); -//print AAS file information -void AAS_FileInfo(void); -#endif //AASINTERN - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_file.h + * + * desc: AAS + * + * $Archive: /source/code/botlib/be_aas_file.h $ + * + *****************************************************************************/ + +#ifdef AASINTERN +//loads the AAS file with the given name +int AAS_LoadAASFile(char *filename); +//writes an AAS file with the given name +qboolean AAS_WriteAASFile(char *filename); +//dumps the loaded AAS data +void AAS_DumpAASData(void); +//print AAS file information +void AAS_FileInfo(void); +#endif //AASINTERN + diff --git a/code/botlib/be_aas_funcs.h b/code/botlib/be_aas_funcs.h index 4dd37e9..e70f348 100755 --- a/code/botlib/be_aas_funcs.h +++ b/code/botlib/be_aas_funcs.h @@ -1,47 +1,47 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: be_aas_funcs.h - * - * desc: AAS - * - * $Archive: /source/code/botlib/be_aas_funcs.h $ - * - *****************************************************************************/ - -#ifndef BSPCINCLUDE - -#include "be_aas_main.h" -#include "be_aas_entity.h" -#include "be_aas_sample.h" -#include "be_aas_cluster.h" -#include "be_aas_reach.h" -#include "be_aas_route.h" -#include "be_aas_routealt.h" -#include "be_aas_debug.h" -#include "be_aas_file.h" -#include "be_aas_optimize.h" -#include "be_aas_bsp.h" -#include "be_aas_move.h" - -#endif //BSPCINCLUDE +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_funcs.h + * + * desc: AAS + * + * $Archive: /source/code/botlib/be_aas_funcs.h $ + * + *****************************************************************************/ + +#ifndef BSPCINCLUDE + +#include "be_aas_main.h" +#include "be_aas_entity.h" +#include "be_aas_sample.h" +#include "be_aas_cluster.h" +#include "be_aas_reach.h" +#include "be_aas_route.h" +#include "be_aas_routealt.h" +#include "be_aas_debug.h" +#include "be_aas_file.h" +#include "be_aas_optimize.h" +#include "be_aas_bsp.h" +#include "be_aas_move.h" + +#endif //BSPCINCLUDE diff --git a/code/botlib/be_aas_main.c b/code/botlib/be_aas_main.c index 45a9dc3..dac1ce3 100755 --- a/code/botlib/be_aas_main.c +++ b/code/botlib/be_aas_main.c @@ -1,429 +1,429 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: be_aas_main.c - * - * desc: AAS - * - * $Archive: /MissionPack/code/botlib/be_aas_main.c $ - * - *****************************************************************************/ - -#include "../game/q_shared.h" -#include "l_memory.h" -#include "l_libvar.h" -#include "l_utils.h" -#include "l_script.h" -#include "l_precomp.h" -#include "l_struct.h" -#include "l_log.h" -#include "aasfile.h" -#include "../game/botlib.h" -#include "../game/be_aas.h" -#include "be_aas_funcs.h" -#include "be_interface.h" -#include "be_aas_def.h" - -aas_t aasworld; - -libvar_t *saveroutingcache; - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void QDECL AAS_Error(char *fmt, ...) -{ - char str[1024]; - va_list arglist; - - va_start(arglist, fmt); - vsprintf(str, fmt, arglist); - va_end(arglist); - botimport.Print(PRT_FATAL, str); -} //end of the function AAS_Error -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -char *AAS_StringFromIndex(char *indexname, char *stringindex[], int numindexes, int index) -{ - if (!aasworld.indexessetup) - { - botimport.Print(PRT_ERROR, "%s: index %d not setup\n", indexname, index); - return ""; - } //end if - if (index < 0 || index >= numindexes) - { - botimport.Print(PRT_ERROR, "%s: index %d out of range\n", indexname, index); - return ""; - } //end if - if (!stringindex[index]) - { - if (index) - { - botimport.Print(PRT_ERROR, "%s: reference to unused index %d\n", indexname, index); - } //end if - return ""; - } //end if - return stringindex[index]; -} //end of the function AAS_StringFromIndex -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_IndexFromString(char *indexname, char *stringindex[], int numindexes, char *string) -{ - int i; - if (!aasworld.indexessetup) - { - botimport.Print(PRT_ERROR, "%s: index not setup \"%s\"\n", indexname, string); - return 0; - } //end if - for (i = 0; i < numindexes; i++) - { - if (!stringindex[i]) continue; - if (!Q_stricmp(stringindex[i], string)) return i; - } //end for - return 0; -} //end of the function AAS_IndexFromString -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -char *AAS_ModelFromIndex(int index) -{ - return AAS_StringFromIndex("ModelFromIndex", &aasworld.configstrings[CS_MODELS], MAX_MODELS, index); -} //end of the function AAS_ModelFromIndex -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_IndexFromModel(char *modelname) -{ - return AAS_IndexFromString("IndexFromModel", &aasworld.configstrings[CS_MODELS], MAX_MODELS, modelname); -} //end of the function AAS_IndexFromModel -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_UpdateStringIndexes(int numconfigstrings, char *configstrings[]) -{ - int i; - //set string pointers and copy the strings - for (i = 0; i < numconfigstrings; i++) - { - if (configstrings[i]) - { - //if (aasworld.configstrings[i]) FreeMemory(aasworld.configstrings[i]); - aasworld.configstrings[i] = (char *) GetMemory(strlen(configstrings[i]) + 1); - strcpy(aasworld.configstrings[i], configstrings[i]); - } //end if - } //end for - aasworld.indexessetup = qtrue; -} //end of the function AAS_UpdateStringIndexes -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_Loaded(void) -{ - return aasworld.loaded; -} //end of the function AAS_Loaded -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_Initialized(void) -{ - return aasworld.initialized; -} //end of the function AAS_Initialized -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_SetInitialized(void) -{ - aasworld.initialized = qtrue; - botimport.Print(PRT_MESSAGE, "AAS initialized.\n"); -#ifdef DEBUG - //create all the routing cache - //AAS_CreateAllRoutingCache(); - // - //AAS_RoutingInfo(); -#endif -} //end of the function AAS_SetInitialized -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_ContinueInit(float time) -{ - //if no AAS file loaded - if (!aasworld.loaded) return; - //if AAS is already initialized - if (aasworld.initialized) return; - //calculate reachability, if not finished return - if (AAS_ContinueInitReachability(time)) return; - //initialize clustering for the new map - AAS_InitClustering(); - //if reachability has been calculated and an AAS file should be written - //or there is a forced data optimization - if (aasworld.savefile || ((int)LibVarGetValue("forcewrite"))) - { - //optimize the AAS data - if ((int)LibVarValue("aasoptimize", "0")) AAS_Optimize(); - //save the AAS file - if (AAS_WriteAASFile(aasworld.filename)) - { - botimport.Print(PRT_MESSAGE, "%s written succesfully\n", aasworld.filename); - } //end if - else - { - botimport.Print(PRT_ERROR, "couldn't write %s\n", aasworld.filename); - } //end else - } //end if - //initialize the routing - AAS_InitRouting(); - //at this point AAS is initialized - AAS_SetInitialized(); -} //end of the function AAS_ContinueInit -//=========================================================================== -// called at the start of every frame -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_StartFrame(float time) -{ - aasworld.time = time; - //unlink all entities that were not updated last frame - AAS_UnlinkInvalidEntities(); - //invalidate the entities - AAS_InvalidateEntities(); - //initialize AAS - AAS_ContinueInit(time); - // - aasworld.frameroutingupdates = 0; - // - if (bot_developer) - { - if (LibVarGetValue("showcacheupdates")) - { - AAS_RoutingInfo(); - LibVarSet("showcacheupdates", "0"); - } //end if - if (LibVarGetValue("showmemoryusage")) - { - PrintUsedMemorySize(); - LibVarSet("showmemoryusage", "0"); - } //end if - if (LibVarGetValue("memorydump")) - { - PrintMemoryLabels(); - LibVarSet("memorydump", "0"); - } //end if - } //end if - // - if (saveroutingcache->value) - { - AAS_WriteRouteCache(); - LibVarSet("saveroutingcache", "0"); - } //end if - // - aasworld.numframes++; - return BLERR_NOERROR; -} //end of the function AAS_StartFrame -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -float AAS_Time(void) -{ - return aasworld.time; -} //end of the function AAS_Time -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_ProjectPointOntoVector( vec3_t point, vec3_t vStart, vec3_t vEnd, vec3_t vProj ) -{ - vec3_t pVec, vec; - - VectorSubtract( point, vStart, pVec ); - VectorSubtract( vEnd, vStart, vec ); - VectorNormalize( vec ); - // project onto the directional vector for this segment - VectorMA( vStart, DotProduct( pVec, vec ), vec, vProj ); -} //end of the function AAS_ProjectPointOntoVector -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_LoadFiles(const char *mapname) -{ - int errnum; - char aasfile[MAX_PATH]; -// char bspfile[MAX_PATH]; - - strcpy(aasworld.mapname, mapname); - //NOTE: first reset the entity links into the AAS areas and BSP leaves - // the AAS link heap and BSP link heap are reset after respectively the - // AAS file and BSP file are loaded - AAS_ResetEntityLinks(); - // load bsp info - AAS_LoadBSPFile(); - - //load the aas file - Com_sprintf(aasfile, MAX_PATH, "maps/%s.aas", mapname); - errnum = AAS_LoadAASFile(aasfile); - if (errnum != BLERR_NOERROR) - return errnum; - - botimport.Print(PRT_MESSAGE, "loaded %s\n", aasfile); - strncpy(aasworld.filename, aasfile, MAX_PATH); - return BLERR_NOERROR; -} //end of the function AAS_LoadFiles -//=========================================================================== -// called everytime a map changes -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_LoadMap(const char *mapname) -{ - int errnum; - - //if no mapname is provided then the string indexes are updated - if (!mapname) - { - return 0; - } //end if - // - aasworld.initialized = qfalse; - //NOTE: free the routing caches before loading a new map because - // to free the caches the old number of areas, number of clusters - // and number of areas in a clusters must be available - AAS_FreeRoutingCaches(); - //load the map - errnum = AAS_LoadFiles(mapname); - if (errnum != BLERR_NOERROR) - { - aasworld.loaded = qfalse; - return errnum; - } //end if - // - AAS_InitSettings(); - //initialize the AAS link heap for the new map - AAS_InitAASLinkHeap(); - //initialize the AAS linked entities for the new map - AAS_InitAASLinkedEntities(); - //initialize reachability for the new map - AAS_InitReachability(); - //initialize the alternative routing - AAS_InitAlternativeRouting(); - //everything went ok - return 0; -} //end of the function AAS_LoadMap -//=========================================================================== -// called when the library is first loaded -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_Setup(void) -{ - aasworld.maxclients = (int) LibVarValue("maxclients", "128"); - aasworld.maxentities = (int) LibVarValue("maxentities", "1024"); - // as soon as it's set to 1 the routing cache will be saved - saveroutingcache = LibVar("saveroutingcache", "0"); - //allocate memory for the entities - if (aasworld.entities) FreeMemory(aasworld.entities); - aasworld.entities = (aas_entity_t *) GetClearedHunkMemory(aasworld.maxentities * sizeof(aas_entity_t)); - //invalidate all the entities - AAS_InvalidateEntities(); - //force some recalculations - //LibVarSet("forceclustering", "1"); //force clustering calculation - //LibVarSet("forcereachability", "1"); //force reachability calculation - aasworld.numframes = 0; - return BLERR_NOERROR; -} //end of the function AAS_Setup -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_Shutdown(void) -{ - AAS_ShutdownAlternativeRouting(); - // - AAS_DumpBSPData(); - //free routing caches - AAS_FreeRoutingCaches(); - //free aas link heap - AAS_FreeAASLinkHeap(); - //free aas linked entities - AAS_FreeAASLinkedEntities(); - //free the aas data - AAS_DumpAASData(); - //free the entities - if (aasworld.entities) FreeMemory(aasworld.entities); - //clear the aasworld structure - Com_Memset(&aasworld, 0, sizeof(aas_t)); - //aas has not been initialized - aasworld.initialized = qfalse; - //NOTE: as soon as a new .bsp file is loaded the .bsp file memory is - // freed an reallocated, so there's no need to free that memory here - //print shutdown - botimport.Print(PRT_MESSAGE, "AAS shutdown.\n"); -} //end of the function AAS_Shutdown +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_main.c + * + * desc: AAS + * + * $Archive: /MissionPack/code/botlib/be_aas_main.c $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_libvar.h" +#include "l_utils.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_log.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_aas_def.h" + +aas_t aasworld; + +libvar_t *saveroutingcache; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void QDECL AAS_Error(char *fmt, ...) +{ + char str[1024]; + va_list arglist; + + va_start(arglist, fmt); + vsprintf(str, fmt, arglist); + va_end(arglist); + botimport.Print(PRT_FATAL, str); +} //end of the function AAS_Error +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *AAS_StringFromIndex(char *indexname, char *stringindex[], int numindexes, int index) +{ + if (!aasworld.indexessetup) + { + botimport.Print(PRT_ERROR, "%s: index %d not setup\n", indexname, index); + return ""; + } //end if + if (index < 0 || index >= numindexes) + { + botimport.Print(PRT_ERROR, "%s: index %d out of range\n", indexname, index); + return ""; + } //end if + if (!stringindex[index]) + { + if (index) + { + botimport.Print(PRT_ERROR, "%s: reference to unused index %d\n", indexname, index); + } //end if + return ""; + } //end if + return stringindex[index]; +} //end of the function AAS_StringFromIndex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_IndexFromString(char *indexname, char *stringindex[], int numindexes, char *string) +{ + int i; + if (!aasworld.indexessetup) + { + botimport.Print(PRT_ERROR, "%s: index not setup \"%s\"\n", indexname, string); + return 0; + } //end if + for (i = 0; i < numindexes; i++) + { + if (!stringindex[i]) continue; + if (!Q_stricmp(stringindex[i], string)) return i; + } //end for + return 0; +} //end of the function AAS_IndexFromString +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *AAS_ModelFromIndex(int index) +{ + return AAS_StringFromIndex("ModelFromIndex", &aasworld.configstrings[CS_MODELS], MAX_MODELS, index); +} //end of the function AAS_ModelFromIndex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_IndexFromModel(char *modelname) +{ + return AAS_IndexFromString("IndexFromModel", &aasworld.configstrings[CS_MODELS], MAX_MODELS, modelname); +} //end of the function AAS_IndexFromModel +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_UpdateStringIndexes(int numconfigstrings, char *configstrings[]) +{ + int i; + //set string pointers and copy the strings + for (i = 0; i < numconfigstrings; i++) + { + if (configstrings[i]) + { + //if (aasworld.configstrings[i]) FreeMemory(aasworld.configstrings[i]); + aasworld.configstrings[i] = (char *) GetMemory(strlen(configstrings[i]) + 1); + strcpy(aasworld.configstrings[i], configstrings[i]); + } //end if + } //end for + aasworld.indexessetup = qtrue; +} //end of the function AAS_UpdateStringIndexes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Loaded(void) +{ + return aasworld.loaded; +} //end of the function AAS_Loaded +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Initialized(void) +{ + return aasworld.initialized; +} //end of the function AAS_Initialized +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_SetInitialized(void) +{ + aasworld.initialized = qtrue; + botimport.Print(PRT_MESSAGE, "AAS initialized.\n"); +#ifdef DEBUG + //create all the routing cache + //AAS_CreateAllRoutingCache(); + // + //AAS_RoutingInfo(); +#endif +} //end of the function AAS_SetInitialized +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ContinueInit(float time) +{ + //if no AAS file loaded + if (!aasworld.loaded) return; + //if AAS is already initialized + if (aasworld.initialized) return; + //calculate reachability, if not finished return + if (AAS_ContinueInitReachability(time)) return; + //initialize clustering for the new map + AAS_InitClustering(); + //if reachability has been calculated and an AAS file should be written + //or there is a forced data optimization + if (aasworld.savefile || ((int)LibVarGetValue("forcewrite"))) + { + //optimize the AAS data + if ((int)LibVarValue("aasoptimize", "0")) AAS_Optimize(); + //save the AAS file + if (AAS_WriteAASFile(aasworld.filename)) + { + botimport.Print(PRT_MESSAGE, "%s written succesfully\n", aasworld.filename); + } //end if + else + { + botimport.Print(PRT_ERROR, "couldn't write %s\n", aasworld.filename); + } //end else + } //end if + //initialize the routing + AAS_InitRouting(); + //at this point AAS is initialized + AAS_SetInitialized(); +} //end of the function AAS_ContinueInit +//=========================================================================== +// called at the start of every frame +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_StartFrame(float time) +{ + aasworld.time = time; + //unlink all entities that were not updated last frame + AAS_UnlinkInvalidEntities(); + //invalidate the entities + AAS_InvalidateEntities(); + //initialize AAS + AAS_ContinueInit(time); + // + aasworld.frameroutingupdates = 0; + // + if (bot_developer) + { + if (LibVarGetValue("showcacheupdates")) + { + AAS_RoutingInfo(); + LibVarSet("showcacheupdates", "0"); + } //end if + if (LibVarGetValue("showmemoryusage")) + { + PrintUsedMemorySize(); + LibVarSet("showmemoryusage", "0"); + } //end if + if (LibVarGetValue("memorydump")) + { + PrintMemoryLabels(); + LibVarSet("memorydump", "0"); + } //end if + } //end if + // + if (saveroutingcache->value) + { + AAS_WriteRouteCache(); + LibVarSet("saveroutingcache", "0"); + } //end if + // + aasworld.numframes++; + return BLERR_NOERROR; +} //end of the function AAS_StartFrame +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_Time(void) +{ + return aasworld.time; +} //end of the function AAS_Time +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ProjectPointOntoVector( vec3_t point, vec3_t vStart, vec3_t vEnd, vec3_t vProj ) +{ + vec3_t pVec, vec; + + VectorSubtract( point, vStart, pVec ); + VectorSubtract( vEnd, vStart, vec ); + VectorNormalize( vec ); + // project onto the directional vector for this segment + VectorMA( vStart, DotProduct( pVec, vec ), vec, vProj ); +} //end of the function AAS_ProjectPointOntoVector +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_LoadFiles(const char *mapname) +{ + int errnum; + char aasfile[MAX_PATH]; +// char bspfile[MAX_PATH]; + + strcpy(aasworld.mapname, mapname); + //NOTE: first reset the entity links into the AAS areas and BSP leaves + // the AAS link heap and BSP link heap are reset after respectively the + // AAS file and BSP file are loaded + AAS_ResetEntityLinks(); + // load bsp info + AAS_LoadBSPFile(); + + //load the aas file + Com_sprintf(aasfile, MAX_PATH, "maps/%s.aas", mapname); + errnum = AAS_LoadAASFile(aasfile); + if (errnum != BLERR_NOERROR) + return errnum; + + botimport.Print(PRT_MESSAGE, "loaded %s\n", aasfile); + strncpy(aasworld.filename, aasfile, MAX_PATH); + return BLERR_NOERROR; +} //end of the function AAS_LoadFiles +//=========================================================================== +// called everytime a map changes +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_LoadMap(const char *mapname) +{ + int errnum; + + //if no mapname is provided then the string indexes are updated + if (!mapname) + { + return 0; + } //end if + // + aasworld.initialized = qfalse; + //NOTE: free the routing caches before loading a new map because + // to free the caches the old number of areas, number of clusters + // and number of areas in a clusters must be available + AAS_FreeRoutingCaches(); + //load the map + errnum = AAS_LoadFiles(mapname); + if (errnum != BLERR_NOERROR) + { + aasworld.loaded = qfalse; + return errnum; + } //end if + // + AAS_InitSettings(); + //initialize the AAS link heap for the new map + AAS_InitAASLinkHeap(); + //initialize the AAS linked entities for the new map + AAS_InitAASLinkedEntities(); + //initialize reachability for the new map + AAS_InitReachability(); + //initialize the alternative routing + AAS_InitAlternativeRouting(); + //everything went ok + return 0; +} //end of the function AAS_LoadMap +//=========================================================================== +// called when the library is first loaded +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Setup(void) +{ + aasworld.maxclients = (int) LibVarValue("maxclients", "128"); + aasworld.maxentities = (int) LibVarValue("maxentities", "1024"); + // as soon as it's set to 1 the routing cache will be saved + saveroutingcache = LibVar("saveroutingcache", "0"); + //allocate memory for the entities + if (aasworld.entities) FreeMemory(aasworld.entities); + aasworld.entities = (aas_entity_t *) GetClearedHunkMemory(aasworld.maxentities * sizeof(aas_entity_t)); + //invalidate all the entities + AAS_InvalidateEntities(); + //force some recalculations + //LibVarSet("forceclustering", "1"); //force clustering calculation + //LibVarSet("forcereachability", "1"); //force reachability calculation + aasworld.numframes = 0; + return BLERR_NOERROR; +} //end of the function AAS_Setup +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Shutdown(void) +{ + AAS_ShutdownAlternativeRouting(); + // + AAS_DumpBSPData(); + //free routing caches + AAS_FreeRoutingCaches(); + //free aas link heap + AAS_FreeAASLinkHeap(); + //free aas linked entities + AAS_FreeAASLinkedEntities(); + //free the aas data + AAS_DumpAASData(); + //free the entities + if (aasworld.entities) FreeMemory(aasworld.entities); + //clear the aasworld structure + Com_Memset(&aasworld, 0, sizeof(aas_t)); + //aas has not been initialized + aasworld.initialized = qfalse; + //NOTE: as soon as a new .bsp file is loaded the .bsp file memory is + // freed an reallocated, so there's no need to free that memory here + //print shutdown + botimport.Print(PRT_MESSAGE, "AAS shutdown.\n"); +} //end of the function AAS_Shutdown diff --git a/code/botlib/be_aas_main.h b/code/botlib/be_aas_main.h index 13564a9..d8dc41c 100755 --- a/code/botlib/be_aas_main.h +++ b/code/botlib/be_aas_main.h @@ -1,61 +1,61 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: be_aas_main.h - * - * desc: AAS - * - * $Archive: /source/code/botlib/be_aas_main.h $ - * - *****************************************************************************/ - -#ifdef AASINTERN - -extern aas_t aasworld; - -//AAS error message -void QDECL AAS_Error(char *fmt, ...); -//set AAS initialized -void AAS_SetInitialized(void); -//setup AAS with the given number of entities and clients -int AAS_Setup(void); -//shutdown AAS -void AAS_Shutdown(void); -//start a new map -int AAS_LoadMap(const char *mapname); -//start a new time frame -int AAS_StartFrame(float time); -#endif //AASINTERN - -//returns true if AAS is initialized -int AAS_Initialized(void); -//returns true if the AAS file is loaded -int AAS_Loaded(void); -//returns the model name from the given index -char *AAS_ModelFromIndex(int index); -//returns the index from the given model name -int AAS_IndexFromModel(char *modelname); -//returns the current time -float AAS_Time(void); -// -void AAS_ProjectPointOntoVector( vec3_t point, vec3_t vStart, vec3_t vEnd, vec3_t vProj ); +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_main.h + * + * desc: AAS + * + * $Archive: /source/code/botlib/be_aas_main.h $ + * + *****************************************************************************/ + +#ifdef AASINTERN + +extern aas_t aasworld; + +//AAS error message +void QDECL AAS_Error(char *fmt, ...); +//set AAS initialized +void AAS_SetInitialized(void); +//setup AAS with the given number of entities and clients +int AAS_Setup(void); +//shutdown AAS +void AAS_Shutdown(void); +//start a new map +int AAS_LoadMap(const char *mapname); +//start a new time frame +int AAS_StartFrame(float time); +#endif //AASINTERN + +//returns true if AAS is initialized +int AAS_Initialized(void); +//returns true if the AAS file is loaded +int AAS_Loaded(void); +//returns the model name from the given index +char *AAS_ModelFromIndex(int index); +//returns the index from the given model name +int AAS_IndexFromModel(char *modelname); +//returns the current time +float AAS_Time(void); +// +void AAS_ProjectPointOntoVector( vec3_t point, vec3_t vStart, vec3_t vEnd, vec3_t vProj ); diff --git a/code/botlib/be_aas_move.c b/code/botlib/be_aas_move.c index d8b6cfd..3433153 100755 --- a/code/botlib/be_aas_move.c +++ b/code/botlib/be_aas_move.c @@ -1,1101 +1,1101 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: be_aas_move.c - * - * desc: AAS - * - * $Archive: /MissionPack/code/botlib/be_aas_move.c $ - * - *****************************************************************************/ - -#include "../game/q_shared.h" -#include "l_memory.h" -#include "l_script.h" -#include "l_precomp.h" -#include "l_struct.h" -#include "l_libvar.h" -#include "aasfile.h" -#include "../game/botlib.h" -#include "../game/be_aas.h" -#include "be_aas_funcs.h" -#include "be_aas_def.h" - -extern botlib_import_t botimport; - -aas_settings_t aassettings; - -//#define AAS_MOVE_DEBUG - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_DropToFloor(vec3_t origin, vec3_t mins, vec3_t maxs) -{ - vec3_t end; - bsp_trace_t trace; - - VectorCopy(origin, end); - end[2] -= 100; - trace = AAS_Trace(origin, mins, maxs, end, 0, CONTENTS_SOLID); - if (trace.startsolid) return qfalse; - VectorCopy(trace.endpos, origin); - return qtrue; -} //end of the function AAS_DropToFloor -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_InitSettings(void) -{ - aassettings.phys_gravitydirection[0] = 0; - aassettings.phys_gravitydirection[1] = 0; - aassettings.phys_gravitydirection[2] = -1; - aassettings.phys_friction = LibVarValue("phys_friction", "6"); - aassettings.phys_stopspeed = LibVarValue("phys_stopspeed", "100"); - aassettings.phys_gravity = LibVarValue("phys_gravity", "800"); - aassettings.phys_waterfriction = LibVarValue("phys_waterfriction", "1"); - aassettings.phys_watergravity = LibVarValue("phys_watergravity", "400"); - aassettings.phys_maxvelocity = LibVarValue("phys_maxvelocity", "320"); - aassettings.phys_maxwalkvelocity = LibVarValue("phys_maxwalkvelocity", "320"); - aassettings.phys_maxcrouchvelocity = LibVarValue("phys_maxcrouchvelocity", "100"); - aassettings.phys_maxswimvelocity = LibVarValue("phys_maxswimvelocity", "150"); - aassettings.phys_walkaccelerate = LibVarValue("phys_walkaccelerate", "10"); - aassettings.phys_airaccelerate = LibVarValue("phys_airaccelerate", "1"); - aassettings.phys_swimaccelerate = LibVarValue("phys_swimaccelerate", "4"); - aassettings.phys_maxstep = LibVarValue("phys_maxstep", "19"); - aassettings.phys_maxsteepness = LibVarValue("phys_maxsteepness", "0.7"); - aassettings.phys_maxwaterjump = LibVarValue("phys_maxwaterjump", "18"); - aassettings.phys_maxbarrier = LibVarValue("phys_maxbarrier", "33"); - aassettings.phys_jumpvel = LibVarValue("phys_jumpvel", "270"); - aassettings.phys_falldelta5 = LibVarValue("phys_falldelta5", "40"); - aassettings.phys_falldelta10 = LibVarValue("phys_falldelta10", "60"); - aassettings.rs_waterjump = LibVarValue("rs_waterjump", "400"); - aassettings.rs_teleport = LibVarValue("rs_teleport", "50"); - aassettings.rs_barrierjump = LibVarValue("rs_barrierjump", "100"); - aassettings.rs_startcrouch = LibVarValue("rs_startcrouch", "300"); - aassettings.rs_startgrapple = LibVarValue("rs_startgrapple", "500"); - aassettings.rs_startwalkoffledge = LibVarValue("rs_startwalkoffledge", "70"); - aassettings.rs_startjump = LibVarValue("rs_startjump", "300"); - aassettings.rs_rocketjump = LibVarValue("rs_rocketjump", "500"); - aassettings.rs_bfgjump = LibVarValue("rs_bfgjump", "500"); - aassettings.rs_jumppad = LibVarValue("rs_jumppad", "250"); - aassettings.rs_aircontrolledjumppad = LibVarValue("rs_aircontrolledjumppad", "300"); - aassettings.rs_funcbob = LibVarValue("rs_funcbob", "300"); - aassettings.rs_startelevator = LibVarValue("rs_startelevator", "50"); - aassettings.rs_falldamage5 = LibVarValue("rs_falldamage5", "300"); - aassettings.rs_falldamage10 = LibVarValue("rs_falldamage10", "500"); - aassettings.rs_maxfallheight = LibVarValue("rs_maxfallheight", "0"); - aassettings.rs_maxjumpfallheight = LibVarValue("rs_maxjumpfallheight", "450"); -} //end of the function AAS_InitSettings -//=========================================================================== -// returns qtrue if the bot is against a ladder -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_AgainstLadder(vec3_t origin) -{ - int areanum, i, facenum, side; - vec3_t org; - aas_plane_t *plane; - aas_face_t *face; - aas_area_t *area; - - VectorCopy(origin, org); - areanum = AAS_PointAreaNum(org); - if (!areanum) - { - org[0] += 1; - areanum = AAS_PointAreaNum(org); - if (!areanum) - { - org[1] += 1; - areanum = AAS_PointAreaNum(org); - if (!areanum) - { - org[0] -= 2; - areanum = AAS_PointAreaNum(org); - if (!areanum) - { - org[1] -= 2; - areanum = AAS_PointAreaNum(org); - } //end if - } //end if - } //end if - } //end if - //if in solid... wrrr shouldn't happen - if (!areanum) return qfalse; - //if not in a ladder area - if (!(aasworld.areasettings[areanum].areaflags & AREA_LADDER)) return qfalse; - //if a crouch only area - if (!(aasworld.areasettings[areanum].presencetype & PRESENCE_NORMAL)) return qfalse; - // - area = &aasworld.areas[areanum]; - for (i = 0; i < area->numfaces; i++) - { - facenum = aasworld.faceindex[area->firstface + i]; - side = facenum < 0; - face = &aasworld.faces[abs(facenum)]; - //if the face isn't a ladder face - if (!(face->faceflags & FACE_LADDER)) continue; - //get the plane the face is in - plane = &aasworld.planes[face->planenum ^ side]; - //if the origin is pretty close to the plane - if (abs(DotProduct(plane->normal, origin) - plane->dist) < 3) - { - if (AAS_PointInsideFace(abs(facenum), origin, 0.1f)) return qtrue; - } //end if - } //end for - return qfalse; -} //end of the function AAS_AgainstLadder -//=========================================================================== -// returns qtrue if the bot is on the ground -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_OnGround(vec3_t origin, int presencetype, int passent) -{ - aas_trace_t trace; - vec3_t end, up = {0, 0, 1}; - aas_plane_t *plane; - - VectorCopy(origin, end); - end[2] -= 10; - - trace = AAS_TraceClientBBox(origin, end, presencetype, passent); - - //if in solid - if (trace.startsolid) return qfalse; - //if nothing hit at all - if (trace.fraction >= 1.0) return qfalse; - //if too far from the hit plane - if (origin[2] - trace.endpos[2] > 10) return qfalse; - //check if the plane isn't too steep - plane = AAS_PlaneFromNum(trace.planenum); - if (DotProduct(plane->normal, up) < aassettings.phys_maxsteepness) return qfalse; - //the bot is on the ground - return qtrue; -} //end of the function AAS_OnGround -//=========================================================================== -// returns qtrue if a bot at the given position is swimming -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_Swimming(vec3_t origin) -{ - vec3_t testorg; - - VectorCopy(origin, testorg); - testorg[2] -= 2; - if (AAS_PointContents(testorg) & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) return qtrue; - return qfalse; -} //end of the function AAS_Swimming -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -static vec3_t VEC_UP = {0, -1, 0}; -static vec3_t MOVEDIR_UP = {0, 0, 1}; -static vec3_t VEC_DOWN = {0, -2, 0}; -static vec3_t MOVEDIR_DOWN = {0, 0, -1}; - -void AAS_SetMovedir(vec3_t angles, vec3_t movedir) -{ - if (VectorCompare(angles, VEC_UP)) - { - VectorCopy(MOVEDIR_UP, movedir); - } //end if - else if (VectorCompare(angles, VEC_DOWN)) - { - VectorCopy(MOVEDIR_DOWN, movedir); - } //end else if - else - { - AngleVectors(angles, movedir, NULL, NULL); - } //end else -} //end of the function AAS_SetMovedir -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_JumpReachRunStart(aas_reachability_t *reach, vec3_t runstart) -{ - vec3_t hordir, start, cmdmove; - aas_clientmove_t move; - - // - hordir[0] = reach->start[0] - reach->end[0]; - hordir[1] = reach->start[1] - reach->end[1]; - hordir[2] = 0; - VectorNormalize(hordir); - //start point - VectorCopy(reach->start, start); - start[2] += 1; - //get command movement - VectorScale(hordir, 400, cmdmove); - // - AAS_PredictClientMovement(&move, -1, start, PRESENCE_NORMAL, qtrue, - vec3_origin, cmdmove, 1, 2, 0.1f, - SE_ENTERWATER|SE_ENTERSLIME|SE_ENTERLAVA| - SE_HITGROUNDDAMAGE|SE_GAP, 0, qfalse); - VectorCopy(move.endpos, runstart); - //don't enter slime or lava and don't fall from too high - if (move.stopevent & (SE_ENTERSLIME|SE_ENTERLAVA|SE_HITGROUNDDAMAGE)) - { - VectorCopy(start, runstart); - } //end if -} //end of the function AAS_JumpReachRunStart -//=========================================================================== -// returns the Z velocity when rocket jumping at the origin -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -float AAS_WeaponJumpZVelocity(vec3_t origin, float radiusdamage) -{ - vec3_t kvel, v, start, end, forward, right, viewangles, dir; - float mass, knockback, points; - vec3_t rocketoffset = {8, 8, -8}; - vec3_t botmins = {-16, -16, -24}; - vec3_t botmaxs = {16, 16, 32}; - bsp_trace_t bsptrace; - - //look down (90 degrees) - viewangles[PITCH] = 90; - viewangles[YAW] = 0; - viewangles[ROLL] = 0; - //get the start point shooting from - VectorCopy(origin, start); - start[2] += 8; //view offset Z - AngleVectors(viewangles, forward, right, NULL); - start[0] += forward[0] * rocketoffset[0] + right[0] * rocketoffset[1]; - start[1] += forward[1] * rocketoffset[0] + right[1] * rocketoffset[1]; - start[2] += forward[2] * rocketoffset[0] + right[2] * rocketoffset[1] + rocketoffset[2]; - //end point of the trace - VectorMA(start, 500, forward, end); - //trace a line to get the impact point - bsptrace = AAS_Trace(start, NULL, NULL, end, 1, CONTENTS_SOLID); - //calculate the damage the bot will get from the rocket impact - VectorAdd(botmins, botmaxs, v); - VectorMA(origin, 0.5, v, v); - VectorSubtract(bsptrace.endpos, v, v); - // - points = radiusdamage - 0.5 * VectorLength(v); - if (points < 0) points = 0; - //the owner of the rocket gets half the damage - points *= 0.5; - //mass of the bot (p_client.c: PutClientInServer) - mass = 200; - //knockback is the same as the damage points - knockback = points; - //direction of the damage (from trace.endpos to bot origin) - VectorSubtract(origin, bsptrace.endpos, dir); - VectorNormalize(dir); - //damage velocity - VectorScale(dir, 1600.0 * (float)knockback / mass, kvel); //the rocket jump hack... - //rocket impact velocity + jump velocity - return kvel[2] + aassettings.phys_jumpvel; -} //end of the function AAS_WeaponJumpZVelocity -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -float AAS_RocketJumpZVelocity(vec3_t origin) -{ - //rocket radius damage is 120 (p_weapon.c: Weapon_RocketLauncher_Fire) - return AAS_WeaponJumpZVelocity(origin, 120); -} //end of the function AAS_RocketJumpZVelocity -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -float AAS_BFGJumpZVelocity(vec3_t origin) -{ - //bfg radius damage is 1000 (p_weapon.c: weapon_bfg_fire) - return AAS_WeaponJumpZVelocity(origin, 120); -} //end of the function AAS_BFGJumpZVelocity -//=========================================================================== -// applies ground friction to the given velocity -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_Accelerate(vec3_t velocity, float frametime, vec3_t wishdir, float wishspeed, float accel) -{ - // q2 style - int i; - float addspeed, accelspeed, currentspeed; - - currentspeed = DotProduct(velocity, wishdir); - addspeed = wishspeed - currentspeed; - if (addspeed <= 0) { - return; - } - accelspeed = accel*frametime*wishspeed; - if (accelspeed > addspeed) { - accelspeed = addspeed; - } - - for (i=0 ; i<3 ; i++) { - velocity[i] += accelspeed*wishdir[i]; - } -} //end of the function AAS_Accelerate -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_AirControl(vec3_t start, vec3_t end, vec3_t velocity, vec3_t cmdmove) -{ - vec3_t dir; - - VectorSubtract(end, start, dir); -} //end of the function AAS_AirControl -//=========================================================================== -// applies ground friction to the given velocity -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_ApplyFriction(vec3_t vel, float friction, float stopspeed, - float frametime) -{ - float speed, control, newspeed; - - //horizontal speed - speed = sqrt(vel[0] * vel[0] + vel[1] * vel[1]); - if (speed) - { - control = speed < stopspeed ? stopspeed : speed; - newspeed = speed - frametime * control * friction; - if (newspeed < 0) newspeed = 0; - newspeed /= speed; - vel[0] *= newspeed; - vel[1] *= newspeed; - } //end if -} //end of the function AAS_ApplyFriction -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_ClipToBBox(aas_trace_t *trace, vec3_t start, vec3_t end, int presencetype, vec3_t mins, vec3_t maxs) -{ - int i, j, side; - float front, back, frac, planedist; - vec3_t bboxmins, bboxmaxs, absmins, absmaxs, dir, mid; - - AAS_PresenceTypeBoundingBox(presencetype, bboxmins, bboxmaxs); - VectorSubtract(mins, bboxmaxs, absmins); - VectorSubtract(maxs, bboxmins, absmaxs); - // - VectorCopy(end, trace->endpos); - trace->fraction = 1; - for (i = 0; i < 3; i++) - { - if (start[i] < absmins[i] && end[i] < absmins[i]) return qfalse; - if (start[i] > absmaxs[i] && end[i] > absmaxs[i]) return qfalse; - } //end for - //check bounding box collision - VectorSubtract(end, start, dir); - frac = 1; - for (i = 0; i < 3; i++) - { - //get plane to test collision with for the current axis direction - if (dir[i] > 0) planedist = absmins[i]; - else planedist = absmaxs[i]; - //calculate collision fraction - front = start[i] - planedist; - back = end[i] - planedist; - frac = front / (front-back); - //check if between bounding planes of next axis - side = i + 1; - if (side > 2) side = 0; - mid[side] = start[side] + dir[side] * frac; - if (mid[side] > absmins[side] && mid[side] < absmaxs[side]) - { - //check if between bounding planes of next axis - side++; - if (side > 2) side = 0; - mid[side] = start[side] + dir[side] * frac; - if (mid[side] > absmins[side] && mid[side] < absmaxs[side]) - { - mid[i] = planedist; - break; - } //end if - } //end if - } //end for - //if there was a collision - if (i != 3) - { - trace->startsolid = qfalse; - trace->fraction = frac; - trace->ent = 0; - trace->planenum = 0; - trace->area = 0; - trace->lastarea = 0; - //trace endpos - for (j = 0; j < 3; j++) trace->endpos[j] = start[j] + dir[j] * frac; - return qtrue; - } //end if - return qfalse; -} //end of the function AAS_ClipToBBox -//=========================================================================== -// predicts the movement -// assumes regular bounding box sizes -// NOTE: out of water jumping is not included -// NOTE: grappling hook is not included -// -// Parameter: origin : origin to start with -// presencetype : presence type to start with -// velocity : velocity to start with -// cmdmove : client command movement -// cmdframes : number of frame cmdmove is valid -// maxframes : maximum number of predicted frames -// frametime : duration of one predicted frame -// stopevent : events that stop the prediction -// stopareanum : stop as soon as entered this area -// Returns: aas_clientmove_t -// Changes Globals: - -//=========================================================================== -int AAS_ClientMovementPrediction(struct aas_clientmove_s *move, - int entnum, vec3_t origin, - int presencetype, int onground, - vec3_t velocity, vec3_t cmdmove, - int cmdframes, - int maxframes, float frametime, - int stopevent, int stopareanum, - vec3_t mins, vec3_t maxs, int visualize) -{ - float phys_friction, phys_stopspeed, phys_gravity, phys_waterfriction; - float phys_watergravity; - float phys_walkaccelerate, phys_airaccelerate, phys_swimaccelerate; - float phys_maxwalkvelocity, phys_maxcrouchvelocity, phys_maxswimvelocity; - float phys_maxstep, phys_maxsteepness, phys_jumpvel, friction; - float gravity, delta, maxvel, wishspeed, accelerate; - //float velchange, newvel; - int n, i, j, pc, step, swimming, ax, crouch, event, jump_frame, areanum; - int areas[20], numareas; - vec3_t points[20]; - vec3_t org, end, feet, start, stepend, lastorg, wishdir; - vec3_t frame_test_vel, old_frame_test_vel, left_test_vel; - vec3_t up = {0, 0, 1}; - aas_plane_t *plane, *plane2; - aas_trace_t trace, steptrace; - - if (frametime <= 0) frametime = 0.1f; - // - phys_friction = aassettings.phys_friction; - phys_stopspeed = aassettings.phys_stopspeed; - phys_gravity = aassettings.phys_gravity; - phys_waterfriction = aassettings.phys_waterfriction; - phys_watergravity = aassettings.phys_watergravity; - phys_maxwalkvelocity = aassettings.phys_maxwalkvelocity;// * frametime; - phys_maxcrouchvelocity = aassettings.phys_maxcrouchvelocity;// * frametime; - phys_maxswimvelocity = aassettings.phys_maxswimvelocity;// * frametime; - phys_walkaccelerate = aassettings.phys_walkaccelerate; - phys_airaccelerate = aassettings.phys_airaccelerate; - phys_swimaccelerate = aassettings.phys_swimaccelerate; - phys_maxstep = aassettings.phys_maxstep; - phys_maxsteepness = aassettings.phys_maxsteepness; - phys_jumpvel = aassettings.phys_jumpvel * frametime; - // - Com_Memset(move, 0, sizeof(aas_clientmove_t)); - Com_Memset(&trace, 0, sizeof(aas_trace_t)); - //start at the current origin - VectorCopy(origin, org); - org[2] += 0.25; - //velocity to test for the first frame - VectorScale(velocity, frametime, frame_test_vel); - // - jump_frame = -1; - //predict a maximum of 'maxframes' ahead - for (n = 0; n < maxframes; n++) - { - swimming = AAS_Swimming(org); - //get gravity depending on swimming or not - gravity = swimming ? phys_watergravity : phys_gravity; - //apply gravity at the START of the frame - frame_test_vel[2] = frame_test_vel[2] - (gravity * 0.1 * frametime); - //if on the ground or swimming - if (onground || swimming) - { - friction = swimming ? phys_friction : phys_waterfriction; - //apply friction - VectorScale(frame_test_vel, 1/frametime, frame_test_vel); - AAS_ApplyFriction(frame_test_vel, friction, phys_stopspeed, frametime); - VectorScale(frame_test_vel, frametime, frame_test_vel); - } //end if - crouch = qfalse; - //apply command movement - if (n < cmdframes) - { - ax = 0; - maxvel = phys_maxwalkvelocity; - accelerate = phys_airaccelerate; - VectorCopy(cmdmove, wishdir); - if (onground) - { - if (cmdmove[2] < -300) - { - crouch = qtrue; - maxvel = phys_maxcrouchvelocity; - } //end if - //if not swimming and upmove is positive then jump - if (!swimming && cmdmove[2] > 1) - { - //jump velocity minus the gravity for one frame + 5 for safety - frame_test_vel[2] = phys_jumpvel - (gravity * 0.1 * frametime) + 5; - jump_frame = n; - //jumping so air accelerate - accelerate = phys_airaccelerate; - } //end if - else - { - accelerate = phys_walkaccelerate; - } //end else - ax = 2; - } //end if - if (swimming) - { - maxvel = phys_maxswimvelocity; - accelerate = phys_swimaccelerate; - ax = 3; - } //end if - else - { - wishdir[2] = 0; - } //end else - // - wishspeed = VectorNormalize(wishdir); - if (wishspeed > maxvel) wishspeed = maxvel; - VectorScale(frame_test_vel, 1/frametime, frame_test_vel); - AAS_Accelerate(frame_test_vel, frametime, wishdir, wishspeed, accelerate); - VectorScale(frame_test_vel, frametime, frame_test_vel); - /* - for (i = 0; i < ax; i++) - { - velchange = (cmdmove[i] * frametime) - frame_test_vel[i]; - if (velchange > phys_maxacceleration) velchange = phys_maxacceleration; - else if (velchange < -phys_maxacceleration) velchange = -phys_maxacceleration; - newvel = frame_test_vel[i] + velchange; - // - if (frame_test_vel[i] <= maxvel && newvel > maxvel) frame_test_vel[i] = maxvel; - else if (frame_test_vel[i] >= -maxvel && newvel < -maxvel) frame_test_vel[i] = -maxvel; - else frame_test_vel[i] = newvel; - } //end for - */ - } //end if - if (crouch) - { - presencetype = PRESENCE_CROUCH; - } //end if - else if (presencetype == PRESENCE_CROUCH) - { - if (AAS_PointPresenceType(org) & PRESENCE_NORMAL) - { - presencetype = PRESENCE_NORMAL; - } //end if - } //end else - //save the current origin - VectorCopy(org, lastorg); - //move linear during one frame - VectorCopy(frame_test_vel, left_test_vel); - j = 0; - do - { - VectorAdd(org, left_test_vel, end); - //trace a bounding box - trace = AAS_TraceClientBBox(org, end, presencetype, entnum); - // -//#ifdef AAS_MOVE_DEBUG - if (visualize) - { - if (trace.startsolid) botimport.Print(PRT_MESSAGE, "PredictMovement: start solid\n"); - AAS_DebugLine(org, trace.endpos, LINECOLOR_RED); - } //end if -//#endif //AAS_MOVE_DEBUG - // - if (stopevent & (SE_ENTERAREA|SE_TOUCHJUMPPAD|SE_TOUCHTELEPORTER|SE_TOUCHCLUSTERPORTAL)) - { - numareas = AAS_TraceAreas(org, trace.endpos, areas, points, 20); - for (i = 0; i < numareas; i++) - { - if (stopevent & SE_ENTERAREA) - { - if (areas[i] == stopareanum) - { - VectorCopy(points[i], move->endpos); - VectorScale(frame_test_vel, 1/frametime, move->velocity); - move->endarea = areas[i]; - move->trace = trace; - move->stopevent = SE_ENTERAREA; - move->presencetype = presencetype; - move->endcontents = 0; - move->time = n * frametime; - move->frames = n; - return qtrue; - } //end if - } //end if - //NOTE: if not the first frame - if ((stopevent & SE_TOUCHJUMPPAD) && n) - { - if (aasworld.areasettings[areas[i]].contents & AREACONTENTS_JUMPPAD) - { - VectorCopy(points[i], move->endpos); - VectorScale(frame_test_vel, 1/frametime, move->velocity); - move->endarea = areas[i]; - move->trace = trace; - move->stopevent = SE_TOUCHJUMPPAD; - move->presencetype = presencetype; - move->endcontents = 0; - move->time = n * frametime; - move->frames = n; - return qtrue; - } //end if - } //end if - if (stopevent & SE_TOUCHTELEPORTER) - { - if (aasworld.areasettings[areas[i]].contents & AREACONTENTS_TELEPORTER) - { - VectorCopy(points[i], move->endpos); - move->endarea = areas[i]; - VectorScale(frame_test_vel, 1/frametime, move->velocity); - move->trace = trace; - move->stopevent = SE_TOUCHTELEPORTER; - move->presencetype = presencetype; - move->endcontents = 0; - move->time = n * frametime; - move->frames = n; - return qtrue; - } //end if - } //end if - if (stopevent & SE_TOUCHCLUSTERPORTAL) - { - if (aasworld.areasettings[areas[i]].contents & AREACONTENTS_CLUSTERPORTAL) - { - VectorCopy(points[i], move->endpos); - move->endarea = areas[i]; - VectorScale(frame_test_vel, 1/frametime, move->velocity); - move->trace = trace; - move->stopevent = SE_TOUCHCLUSTERPORTAL; - move->presencetype = presencetype; - move->endcontents = 0; - move->time = n * frametime; - move->frames = n; - return qtrue; - } //end if - } //end if - } //end for - } //end if - // - if (stopevent & SE_HITBOUNDINGBOX) - { - if (AAS_ClipToBBox(&trace, org, trace.endpos, presencetype, mins, maxs)) - { - VectorCopy(trace.endpos, move->endpos); - move->endarea = AAS_PointAreaNum(move->endpos); - VectorScale(frame_test_vel, 1/frametime, move->velocity); - move->trace = trace; - move->stopevent = SE_HITBOUNDINGBOX; - move->presencetype = presencetype; - move->endcontents = 0; - move->time = n * frametime; - move->frames = n; - return qtrue; - } //end if - } //end if - //move the entity to the trace end point - VectorCopy(trace.endpos, org); - //if there was a collision - if (trace.fraction < 1.0) - { - //get the plane the bounding box collided with - plane = AAS_PlaneFromNum(trace.planenum); - // - if (stopevent & SE_HITGROUNDAREA) - { - if (DotProduct(plane->normal, up) > phys_maxsteepness) - { - VectorCopy(org, start); - start[2] += 0.5; - if (AAS_PointAreaNum(start) == stopareanum) - { - VectorCopy(start, move->endpos); - move->endarea = stopareanum; - VectorScale(frame_test_vel, 1/frametime, move->velocity); - move->trace = trace; - move->stopevent = SE_HITGROUNDAREA; - move->presencetype = presencetype; - move->endcontents = 0; - move->time = n * frametime; - move->frames = n; - return qtrue; - } //end if - } //end if - } //end if - //assume there's no step - step = qfalse; - //if it is a vertical plane and the bot didn't jump recently - if (plane->normal[2] == 0 && (jump_frame < 0 || n - jump_frame > 2)) - { - //check for a step - VectorMA(org, -0.25, plane->normal, start); - VectorCopy(start, stepend); - start[2] += phys_maxstep; - steptrace = AAS_TraceClientBBox(start, stepend, presencetype, entnum); - // - if (!steptrace.startsolid) - { - plane2 = AAS_PlaneFromNum(steptrace.planenum); - if (DotProduct(plane2->normal, up) > phys_maxsteepness) - { - VectorSubtract(end, steptrace.endpos, left_test_vel); - left_test_vel[2] = 0; - frame_test_vel[2] = 0; -//#ifdef AAS_MOVE_DEBUG - if (visualize) - { - if (steptrace.endpos[2] - org[2] > 0.125) - { - VectorCopy(org, start); - start[2] = steptrace.endpos[2]; - AAS_DebugLine(org, start, LINECOLOR_BLUE); - } //end if - } //end if -//#endif //AAS_MOVE_DEBUG - org[2] = steptrace.endpos[2]; - step = qtrue; - } //end if - } //end if - } //end if - // - if (!step) - { - //velocity left to test for this frame is the projection - //of the current test velocity into the hit plane - VectorMA(left_test_vel, -DotProduct(left_test_vel, plane->normal), - plane->normal, left_test_vel); - //store the old velocity for landing check - VectorCopy(frame_test_vel, old_frame_test_vel); - //test velocity for the next frame is the projection - //of the velocity of the current frame into the hit plane - VectorMA(frame_test_vel, -DotProduct(frame_test_vel, plane->normal), - plane->normal, frame_test_vel); - //check for a landing on an almost horizontal floor - if (DotProduct(plane->normal, up) > phys_maxsteepness) - { - onground = qtrue; - } //end if - if (stopevent & SE_HITGROUNDDAMAGE) - { - delta = 0; - if (old_frame_test_vel[2] < 0 && - frame_test_vel[2] > old_frame_test_vel[2] && - !onground) - { - delta = old_frame_test_vel[2]; - } //end if - else if (onground) - { - delta = frame_test_vel[2] - old_frame_test_vel[2]; - } //end else - if (delta) - { - delta = delta * 10; - delta = delta * delta * 0.0001; - if (swimming) delta = 0; - // never take falling damage if completely underwater - /* - if (ent->waterlevel == 3) return; - if (ent->waterlevel == 2) delta *= 0.25; - if (ent->waterlevel == 1) delta *= 0.5; - */ - if (delta > 40) - { - VectorCopy(org, move->endpos); - move->endarea = AAS_PointAreaNum(org); - VectorCopy(frame_test_vel, move->velocity); - move->trace = trace; - move->stopevent = SE_HITGROUNDDAMAGE; - move->presencetype = presencetype; - move->endcontents = 0; - move->time = n * frametime; - move->frames = n; - return qtrue; - } //end if - } //end if - } //end if - } //end if - } //end if - //extra check to prevent endless loop - if (++j > 20) return qfalse; - //while there is a plane hit - } while(trace.fraction < 1.0); - //if going down - if (frame_test_vel[2] <= 10) - { - //check for a liquid at the feet of the bot - VectorCopy(org, feet); - feet[2] -= 22; - pc = AAS_PointContents(feet); - //get event from pc - event = SE_NONE; - if (pc & CONTENTS_LAVA) event |= SE_ENTERLAVA; - if (pc & CONTENTS_SLIME) event |= SE_ENTERSLIME; - if (pc & CONTENTS_WATER) event |= SE_ENTERWATER; - // - areanum = AAS_PointAreaNum(org); - if (aasworld.areasettings[areanum].contents & AREACONTENTS_LAVA) - event |= SE_ENTERLAVA; - if (aasworld.areasettings[areanum].contents & AREACONTENTS_SLIME) - event |= SE_ENTERSLIME; - if (aasworld.areasettings[areanum].contents & AREACONTENTS_WATER) - event |= SE_ENTERWATER; - //if in lava or slime - if (event & stopevent) - { - VectorCopy(org, move->endpos); - move->endarea = areanum; - VectorScale(frame_test_vel, 1/frametime, move->velocity); - move->stopevent = event & stopevent; - move->presencetype = presencetype; - move->endcontents = pc; - move->time = n * frametime; - move->frames = n; - return qtrue; - } //end if - } //end if - // - onground = AAS_OnGround(org, presencetype, entnum); - //if onground and on the ground for at least one whole frame - if (onground) - { - if (stopevent & SE_HITGROUND) - { - VectorCopy(org, move->endpos); - move->endarea = AAS_PointAreaNum(org); - VectorScale(frame_test_vel, 1/frametime, move->velocity); - move->trace = trace; - move->stopevent = SE_HITGROUND; - move->presencetype = presencetype; - move->endcontents = 0; - move->time = n * frametime; - move->frames = n; - return qtrue; - } //end if - } //end if - else if (stopevent & SE_LEAVEGROUND) - { - VectorCopy(org, move->endpos); - move->endarea = AAS_PointAreaNum(org); - VectorScale(frame_test_vel, 1/frametime, move->velocity); - move->trace = trace; - move->stopevent = SE_LEAVEGROUND; - move->presencetype = presencetype; - move->endcontents = 0; - move->time = n * frametime; - move->frames = n; - return qtrue; - } //end else if - else if (stopevent & SE_GAP) - { - aas_trace_t gaptrace; - - VectorCopy(org, start); - VectorCopy(start, end); - end[2] -= 48 + aassettings.phys_maxbarrier; - gaptrace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, -1); - //if solid is found the bot cannot walk any further and will not fall into a gap - if (!gaptrace.startsolid) - { - //if it is a gap (lower than one step height) - if (gaptrace.endpos[2] < org[2] - aassettings.phys_maxstep - 1) - { - if (!(AAS_PointContents(end) & CONTENTS_WATER)) - { - VectorCopy(lastorg, move->endpos); - move->endarea = AAS_PointAreaNum(lastorg); - VectorScale(frame_test_vel, 1/frametime, move->velocity); - move->trace = trace; - move->stopevent = SE_GAP; - move->presencetype = presencetype; - move->endcontents = 0; - move->time = n * frametime; - move->frames = n; - return qtrue; - } //end if - } //end if - } //end if - } //end else if - } //end for - // - VectorCopy(org, move->endpos); - move->endarea = AAS_PointAreaNum(org); - VectorScale(frame_test_vel, 1/frametime, move->velocity); - move->stopevent = SE_NONE; - move->presencetype = presencetype; - move->endcontents = 0; - move->time = n * frametime; - move->frames = n; - // - return qtrue; -} //end of the function AAS_ClientMovementPrediction -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_PredictClientMovement(struct aas_clientmove_s *move, - int entnum, vec3_t origin, - int presencetype, int onground, - vec3_t velocity, vec3_t cmdmove, - int cmdframes, - int maxframes, float frametime, - int stopevent, int stopareanum, int visualize) -{ - vec3_t mins, maxs; - return AAS_ClientMovementPrediction(move, entnum, origin, presencetype, onground, - velocity, cmdmove, cmdframes, maxframes, - frametime, stopevent, stopareanum, - mins, maxs, visualize); -} //end of the function AAS_PredictClientMovement -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_ClientMovementHitBBox(struct aas_clientmove_s *move, - int entnum, vec3_t origin, - int presencetype, int onground, - vec3_t velocity, vec3_t cmdmove, - int cmdframes, - int maxframes, float frametime, - vec3_t mins, vec3_t maxs, int visualize) -{ - return AAS_ClientMovementPrediction(move, entnum, origin, presencetype, onground, - velocity, cmdmove, cmdframes, maxframes, - frametime, SE_HITBOUNDINGBOX, 0, - mins, maxs, visualize); -} //end of the function AAS_ClientMovementHitBBox -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_TestMovementPrediction(int entnum, vec3_t origin, vec3_t dir) -{ - vec3_t velocity, cmdmove; - aas_clientmove_t move; - - VectorClear(velocity); - if (!AAS_Swimming(origin)) dir[2] = 0; - VectorNormalize(dir); - VectorScale(dir, 400, cmdmove); - cmdmove[2] = 224; - AAS_ClearShownDebugLines(); - AAS_PredictClientMovement(&move, entnum, origin, PRESENCE_NORMAL, qtrue, - velocity, cmdmove, 13, 13, 0.1f, SE_HITGROUND, 0, qtrue);//SE_LEAVEGROUND); - if (move.stopevent & SE_LEAVEGROUND) - { - botimport.Print(PRT_MESSAGE, "leave ground\n"); - } //end if -} //end of the function TestMovementPrediction -//=========================================================================== -// calculates the horizontal velocity needed to perform a jump from start -// to end -// -// Parameter: zvel : z velocity for jump -// start : start position of jump -// end : end position of jump -// *speed : returned speed for jump -// Returns: qfalse if too high or too far from start to end -// Changes Globals: - -//=========================================================================== -int AAS_HorizontalVelocityForJump(float zvel, vec3_t start, vec3_t end, float *velocity) -{ - float phys_gravity, phys_maxvelocity; - float maxjump, height2fall, t, top; - vec3_t dir; - - phys_gravity = aassettings.phys_gravity; - phys_maxvelocity = aassettings.phys_maxvelocity; - - //maximum height a player can jump with the given initial z velocity - maxjump = 0.5 * phys_gravity * (zvel / phys_gravity) * (zvel / phys_gravity); - //top of the parabolic jump - top = start[2] + maxjump; - //height the bot will fall from the top - height2fall = top - end[2]; - //if the goal is to high to jump to - if (height2fall < 0) - { - *velocity = phys_maxvelocity; - return 0; - } //end if - //time a player takes to fall the height - t = sqrt(height2fall / (0.5 * phys_gravity)); - //direction from start to end - VectorSubtract(end, start, dir); - // - if ( (t + zvel / phys_gravity) == 0.0f ) { - *velocity = phys_maxvelocity; - return 0; - } - //calculate horizontal speed - *velocity = sqrt(dir[0]*dir[0] + dir[1]*dir[1]) / (t + zvel / phys_gravity); - //the horizontal speed must be lower than the max speed - if (*velocity > phys_maxvelocity) - { - *velocity = phys_maxvelocity; - return 0; - } //end if - return 1; -} //end of the function AAS_HorizontalVelocityForJump +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_move.c + * + * desc: AAS + * + * $Archive: /MissionPack/code/botlib/be_aas_move.c $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_libvar.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_aas_def.h" + +extern botlib_import_t botimport; + +aas_settings_t aassettings; + +//#define AAS_MOVE_DEBUG + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_DropToFloor(vec3_t origin, vec3_t mins, vec3_t maxs) +{ + vec3_t end; + bsp_trace_t trace; + + VectorCopy(origin, end); + end[2] -= 100; + trace = AAS_Trace(origin, mins, maxs, end, 0, CONTENTS_SOLID); + if (trace.startsolid) return qfalse; + VectorCopy(trace.endpos, origin); + return qtrue; +} //end of the function AAS_DropToFloor +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitSettings(void) +{ + aassettings.phys_gravitydirection[0] = 0; + aassettings.phys_gravitydirection[1] = 0; + aassettings.phys_gravitydirection[2] = -1; + aassettings.phys_friction = LibVarValue("phys_friction", "6"); + aassettings.phys_stopspeed = LibVarValue("phys_stopspeed", "100"); + aassettings.phys_gravity = LibVarValue("phys_gravity", "800"); + aassettings.phys_waterfriction = LibVarValue("phys_waterfriction", "1"); + aassettings.phys_watergravity = LibVarValue("phys_watergravity", "400"); + aassettings.phys_maxvelocity = LibVarValue("phys_maxvelocity", "320"); + aassettings.phys_maxwalkvelocity = LibVarValue("phys_maxwalkvelocity", "320"); + aassettings.phys_maxcrouchvelocity = LibVarValue("phys_maxcrouchvelocity", "100"); + aassettings.phys_maxswimvelocity = LibVarValue("phys_maxswimvelocity", "150"); + aassettings.phys_walkaccelerate = LibVarValue("phys_walkaccelerate", "10"); + aassettings.phys_airaccelerate = LibVarValue("phys_airaccelerate", "1"); + aassettings.phys_swimaccelerate = LibVarValue("phys_swimaccelerate", "4"); + aassettings.phys_maxstep = LibVarValue("phys_maxstep", "19"); + aassettings.phys_maxsteepness = LibVarValue("phys_maxsteepness", "0.7"); + aassettings.phys_maxwaterjump = LibVarValue("phys_maxwaterjump", "18"); + aassettings.phys_maxbarrier = LibVarValue("phys_maxbarrier", "33"); + aassettings.phys_jumpvel = LibVarValue("phys_jumpvel", "270"); + aassettings.phys_falldelta5 = LibVarValue("phys_falldelta5", "40"); + aassettings.phys_falldelta10 = LibVarValue("phys_falldelta10", "60"); + aassettings.rs_waterjump = LibVarValue("rs_waterjump", "400"); + aassettings.rs_teleport = LibVarValue("rs_teleport", "50"); + aassettings.rs_barrierjump = LibVarValue("rs_barrierjump", "100"); + aassettings.rs_startcrouch = LibVarValue("rs_startcrouch", "300"); + aassettings.rs_startgrapple = LibVarValue("rs_startgrapple", "500"); + aassettings.rs_startwalkoffledge = LibVarValue("rs_startwalkoffledge", "70"); + aassettings.rs_startjump = LibVarValue("rs_startjump", "300"); + aassettings.rs_rocketjump = LibVarValue("rs_rocketjump", "500"); + aassettings.rs_bfgjump = LibVarValue("rs_bfgjump", "500"); + aassettings.rs_jumppad = LibVarValue("rs_jumppad", "250"); + aassettings.rs_aircontrolledjumppad = LibVarValue("rs_aircontrolledjumppad", "300"); + aassettings.rs_funcbob = LibVarValue("rs_funcbob", "300"); + aassettings.rs_startelevator = LibVarValue("rs_startelevator", "50"); + aassettings.rs_falldamage5 = LibVarValue("rs_falldamage5", "300"); + aassettings.rs_falldamage10 = LibVarValue("rs_falldamage10", "500"); + aassettings.rs_maxfallheight = LibVarValue("rs_maxfallheight", "0"); + aassettings.rs_maxjumpfallheight = LibVarValue("rs_maxjumpfallheight", "450"); +} //end of the function AAS_InitSettings +//=========================================================================== +// returns qtrue if the bot is against a ladder +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AgainstLadder(vec3_t origin) +{ + int areanum, i, facenum, side; + vec3_t org; + aas_plane_t *plane; + aas_face_t *face; + aas_area_t *area; + + VectorCopy(origin, org); + areanum = AAS_PointAreaNum(org); + if (!areanum) + { + org[0] += 1; + areanum = AAS_PointAreaNum(org); + if (!areanum) + { + org[1] += 1; + areanum = AAS_PointAreaNum(org); + if (!areanum) + { + org[0] -= 2; + areanum = AAS_PointAreaNum(org); + if (!areanum) + { + org[1] -= 2; + areanum = AAS_PointAreaNum(org); + } //end if + } //end if + } //end if + } //end if + //if in solid... wrrr shouldn't happen + if (!areanum) return qfalse; + //if not in a ladder area + if (!(aasworld.areasettings[areanum].areaflags & AREA_LADDER)) return qfalse; + //if a crouch only area + if (!(aasworld.areasettings[areanum].presencetype & PRESENCE_NORMAL)) return qfalse; + // + area = &aasworld.areas[areanum]; + for (i = 0; i < area->numfaces; i++) + { + facenum = aasworld.faceindex[area->firstface + i]; + side = facenum < 0; + face = &aasworld.faces[abs(facenum)]; + //if the face isn't a ladder face + if (!(face->faceflags & FACE_LADDER)) continue; + //get the plane the face is in + plane = &aasworld.planes[face->planenum ^ side]; + //if the origin is pretty close to the plane + if (abs(DotProduct(plane->normal, origin) - plane->dist) < 3) + { + if (AAS_PointInsideFace(abs(facenum), origin, 0.1f)) return qtrue; + } //end if + } //end for + return qfalse; +} //end of the function AAS_AgainstLadder +//=========================================================================== +// returns qtrue if the bot is on the ground +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_OnGround(vec3_t origin, int presencetype, int passent) +{ + aas_trace_t trace; + vec3_t end, up = {0, 0, 1}; + aas_plane_t *plane; + + VectorCopy(origin, end); + end[2] -= 10; + + trace = AAS_TraceClientBBox(origin, end, presencetype, passent); + + //if in solid + if (trace.startsolid) return qfalse; + //if nothing hit at all + if (trace.fraction >= 1.0) return qfalse; + //if too far from the hit plane + if (origin[2] - trace.endpos[2] > 10) return qfalse; + //check if the plane isn't too steep + plane = AAS_PlaneFromNum(trace.planenum); + if (DotProduct(plane->normal, up) < aassettings.phys_maxsteepness) return qfalse; + //the bot is on the ground + return qtrue; +} //end of the function AAS_OnGround +//=========================================================================== +// returns qtrue if a bot at the given position is swimming +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Swimming(vec3_t origin) +{ + vec3_t testorg; + + VectorCopy(origin, testorg); + testorg[2] -= 2; + if (AAS_PointContents(testorg) & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) return qtrue; + return qfalse; +} //end of the function AAS_Swimming +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +static vec3_t VEC_UP = {0, -1, 0}; +static vec3_t MOVEDIR_UP = {0, 0, 1}; +static vec3_t VEC_DOWN = {0, -2, 0}; +static vec3_t MOVEDIR_DOWN = {0, 0, -1}; + +void AAS_SetMovedir(vec3_t angles, vec3_t movedir) +{ + if (VectorCompare(angles, VEC_UP)) + { + VectorCopy(MOVEDIR_UP, movedir); + } //end if + else if (VectorCompare(angles, VEC_DOWN)) + { + VectorCopy(MOVEDIR_DOWN, movedir); + } //end else if + else + { + AngleVectors(angles, movedir, NULL, NULL); + } //end else +} //end of the function AAS_SetMovedir +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_JumpReachRunStart(aas_reachability_t *reach, vec3_t runstart) +{ + vec3_t hordir, start, cmdmove; + aas_clientmove_t move; + + // + hordir[0] = reach->start[0] - reach->end[0]; + hordir[1] = reach->start[1] - reach->end[1]; + hordir[2] = 0; + VectorNormalize(hordir); + //start point + VectorCopy(reach->start, start); + start[2] += 1; + //get command movement + VectorScale(hordir, 400, cmdmove); + // + AAS_PredictClientMovement(&move, -1, start, PRESENCE_NORMAL, qtrue, + vec3_origin, cmdmove, 1, 2, 0.1f, + SE_ENTERWATER|SE_ENTERSLIME|SE_ENTERLAVA| + SE_HITGROUNDDAMAGE|SE_GAP, 0, qfalse); + VectorCopy(move.endpos, runstart); + //don't enter slime or lava and don't fall from too high + if (move.stopevent & (SE_ENTERSLIME|SE_ENTERLAVA|SE_HITGROUNDDAMAGE)) + { + VectorCopy(start, runstart); + } //end if +} //end of the function AAS_JumpReachRunStart +//=========================================================================== +// returns the Z velocity when rocket jumping at the origin +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_WeaponJumpZVelocity(vec3_t origin, float radiusdamage) +{ + vec3_t kvel, v, start, end, forward, right, viewangles, dir; + float mass, knockback, points; + vec3_t rocketoffset = {8, 8, -8}; + vec3_t botmins = {-16, -16, -24}; + vec3_t botmaxs = {16, 16, 32}; + bsp_trace_t bsptrace; + + //look down (90 degrees) + viewangles[PITCH] = 90; + viewangles[YAW] = 0; + viewangles[ROLL] = 0; + //get the start point shooting from + VectorCopy(origin, start); + start[2] += 8; //view offset Z + AngleVectors(viewangles, forward, right, NULL); + start[0] += forward[0] * rocketoffset[0] + right[0] * rocketoffset[1]; + start[1] += forward[1] * rocketoffset[0] + right[1] * rocketoffset[1]; + start[2] += forward[2] * rocketoffset[0] + right[2] * rocketoffset[1] + rocketoffset[2]; + //end point of the trace + VectorMA(start, 500, forward, end); + //trace a line to get the impact point + bsptrace = AAS_Trace(start, NULL, NULL, end, 1, CONTENTS_SOLID); + //calculate the damage the bot will get from the rocket impact + VectorAdd(botmins, botmaxs, v); + VectorMA(origin, 0.5, v, v); + VectorSubtract(bsptrace.endpos, v, v); + // + points = radiusdamage - 0.5 * VectorLength(v); + if (points < 0) points = 0; + //the owner of the rocket gets half the damage + points *= 0.5; + //mass of the bot (p_client.c: PutClientInServer) + mass = 200; + //knockback is the same as the damage points + knockback = points; + //direction of the damage (from trace.endpos to bot origin) + VectorSubtract(origin, bsptrace.endpos, dir); + VectorNormalize(dir); + //damage velocity + VectorScale(dir, 1600.0 * (float)knockback / mass, kvel); //the rocket jump hack... + //rocket impact velocity + jump velocity + return kvel[2] + aassettings.phys_jumpvel; +} //end of the function AAS_WeaponJumpZVelocity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_RocketJumpZVelocity(vec3_t origin) +{ + //rocket radius damage is 120 (p_weapon.c: Weapon_RocketLauncher_Fire) + return AAS_WeaponJumpZVelocity(origin, 120); +} //end of the function AAS_RocketJumpZVelocity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_BFGJumpZVelocity(vec3_t origin) +{ + //bfg radius damage is 1000 (p_weapon.c: weapon_bfg_fire) + return AAS_WeaponJumpZVelocity(origin, 120); +} //end of the function AAS_BFGJumpZVelocity +//=========================================================================== +// applies ground friction to the given velocity +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Accelerate(vec3_t velocity, float frametime, vec3_t wishdir, float wishspeed, float accel) +{ + // q2 style + int i; + float addspeed, accelspeed, currentspeed; + + currentspeed = DotProduct(velocity, wishdir); + addspeed = wishspeed - currentspeed; + if (addspeed <= 0) { + return; + } + accelspeed = accel*frametime*wishspeed; + if (accelspeed > addspeed) { + accelspeed = addspeed; + } + + for (i=0 ; i<3 ; i++) { + velocity[i] += accelspeed*wishdir[i]; + } +} //end of the function AAS_Accelerate +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_AirControl(vec3_t start, vec3_t end, vec3_t velocity, vec3_t cmdmove) +{ + vec3_t dir; + + VectorSubtract(end, start, dir); +} //end of the function AAS_AirControl +//=========================================================================== +// applies ground friction to the given velocity +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ApplyFriction(vec3_t vel, float friction, float stopspeed, + float frametime) +{ + float speed, control, newspeed; + + //horizontal speed + speed = sqrt(vel[0] * vel[0] + vel[1] * vel[1]); + if (speed) + { + control = speed < stopspeed ? stopspeed : speed; + newspeed = speed - frametime * control * friction; + if (newspeed < 0) newspeed = 0; + newspeed /= speed; + vel[0] *= newspeed; + vel[1] *= newspeed; + } //end if +} //end of the function AAS_ApplyFriction +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_ClipToBBox(aas_trace_t *trace, vec3_t start, vec3_t end, int presencetype, vec3_t mins, vec3_t maxs) +{ + int i, j, side; + float front, back, frac, planedist; + vec3_t bboxmins, bboxmaxs, absmins, absmaxs, dir, mid; + + AAS_PresenceTypeBoundingBox(presencetype, bboxmins, bboxmaxs); + VectorSubtract(mins, bboxmaxs, absmins); + VectorSubtract(maxs, bboxmins, absmaxs); + // + VectorCopy(end, trace->endpos); + trace->fraction = 1; + for (i = 0; i < 3; i++) + { + if (start[i] < absmins[i] && end[i] < absmins[i]) return qfalse; + if (start[i] > absmaxs[i] && end[i] > absmaxs[i]) return qfalse; + } //end for + //check bounding box collision + VectorSubtract(end, start, dir); + frac = 1; + for (i = 0; i < 3; i++) + { + //get plane to test collision with for the current axis direction + if (dir[i] > 0) planedist = absmins[i]; + else planedist = absmaxs[i]; + //calculate collision fraction + front = start[i] - planedist; + back = end[i] - planedist; + frac = front / (front-back); + //check if between bounding planes of next axis + side = i + 1; + if (side > 2) side = 0; + mid[side] = start[side] + dir[side] * frac; + if (mid[side] > absmins[side] && mid[side] < absmaxs[side]) + { + //check if between bounding planes of next axis + side++; + if (side > 2) side = 0; + mid[side] = start[side] + dir[side] * frac; + if (mid[side] > absmins[side] && mid[side] < absmaxs[side]) + { + mid[i] = planedist; + break; + } //end if + } //end if + } //end for + //if there was a collision + if (i != 3) + { + trace->startsolid = qfalse; + trace->fraction = frac; + trace->ent = 0; + trace->planenum = 0; + trace->area = 0; + trace->lastarea = 0; + //trace endpos + for (j = 0; j < 3; j++) trace->endpos[j] = start[j] + dir[j] * frac; + return qtrue; + } //end if + return qfalse; +} //end of the function AAS_ClipToBBox +//=========================================================================== +// predicts the movement +// assumes regular bounding box sizes +// NOTE: out of water jumping is not included +// NOTE: grappling hook is not included +// +// Parameter: origin : origin to start with +// presencetype : presence type to start with +// velocity : velocity to start with +// cmdmove : client command movement +// cmdframes : number of frame cmdmove is valid +// maxframes : maximum number of predicted frames +// frametime : duration of one predicted frame +// stopevent : events that stop the prediction +// stopareanum : stop as soon as entered this area +// Returns: aas_clientmove_t +// Changes Globals: - +//=========================================================================== +int AAS_ClientMovementPrediction(struct aas_clientmove_s *move, + int entnum, vec3_t origin, + int presencetype, int onground, + vec3_t velocity, vec3_t cmdmove, + int cmdframes, + int maxframes, float frametime, + int stopevent, int stopareanum, + vec3_t mins, vec3_t maxs, int visualize) +{ + float phys_friction, phys_stopspeed, phys_gravity, phys_waterfriction; + float phys_watergravity; + float phys_walkaccelerate, phys_airaccelerate, phys_swimaccelerate; + float phys_maxwalkvelocity, phys_maxcrouchvelocity, phys_maxswimvelocity; + float phys_maxstep, phys_maxsteepness, phys_jumpvel, friction; + float gravity, delta, maxvel, wishspeed, accelerate; + //float velchange, newvel; + int n, i, j, pc, step, swimming, ax, crouch, event, jump_frame, areanum; + int areas[20], numareas; + vec3_t points[20]; + vec3_t org, end, feet, start, stepend, lastorg, wishdir; + vec3_t frame_test_vel, old_frame_test_vel, left_test_vel; + vec3_t up = {0, 0, 1}; + aas_plane_t *plane, *plane2; + aas_trace_t trace, steptrace; + + if (frametime <= 0) frametime = 0.1f; + // + phys_friction = aassettings.phys_friction; + phys_stopspeed = aassettings.phys_stopspeed; + phys_gravity = aassettings.phys_gravity; + phys_waterfriction = aassettings.phys_waterfriction; + phys_watergravity = aassettings.phys_watergravity; + phys_maxwalkvelocity = aassettings.phys_maxwalkvelocity;// * frametime; + phys_maxcrouchvelocity = aassettings.phys_maxcrouchvelocity;// * frametime; + phys_maxswimvelocity = aassettings.phys_maxswimvelocity;// * frametime; + phys_walkaccelerate = aassettings.phys_walkaccelerate; + phys_airaccelerate = aassettings.phys_airaccelerate; + phys_swimaccelerate = aassettings.phys_swimaccelerate; + phys_maxstep = aassettings.phys_maxstep; + phys_maxsteepness = aassettings.phys_maxsteepness; + phys_jumpvel = aassettings.phys_jumpvel * frametime; + // + Com_Memset(move, 0, sizeof(aas_clientmove_t)); + Com_Memset(&trace, 0, sizeof(aas_trace_t)); + //start at the current origin + VectorCopy(origin, org); + org[2] += 0.25; + //velocity to test for the first frame + VectorScale(velocity, frametime, frame_test_vel); + // + jump_frame = -1; + //predict a maximum of 'maxframes' ahead + for (n = 0; n < maxframes; n++) + { + swimming = AAS_Swimming(org); + //get gravity depending on swimming or not + gravity = swimming ? phys_watergravity : phys_gravity; + //apply gravity at the START of the frame + frame_test_vel[2] = frame_test_vel[2] - (gravity * 0.1 * frametime); + //if on the ground or swimming + if (onground || swimming) + { + friction = swimming ? phys_friction : phys_waterfriction; + //apply friction + VectorScale(frame_test_vel, 1/frametime, frame_test_vel); + AAS_ApplyFriction(frame_test_vel, friction, phys_stopspeed, frametime); + VectorScale(frame_test_vel, frametime, frame_test_vel); + } //end if + crouch = qfalse; + //apply command movement + if (n < cmdframes) + { + ax = 0; + maxvel = phys_maxwalkvelocity; + accelerate = phys_airaccelerate; + VectorCopy(cmdmove, wishdir); + if (onground) + { + if (cmdmove[2] < -300) + { + crouch = qtrue; + maxvel = phys_maxcrouchvelocity; + } //end if + //if not swimming and upmove is positive then jump + if (!swimming && cmdmove[2] > 1) + { + //jump velocity minus the gravity for one frame + 5 for safety + frame_test_vel[2] = phys_jumpvel - (gravity * 0.1 * frametime) + 5; + jump_frame = n; + //jumping so air accelerate + accelerate = phys_airaccelerate; + } //end if + else + { + accelerate = phys_walkaccelerate; + } //end else + ax = 2; + } //end if + if (swimming) + { + maxvel = phys_maxswimvelocity; + accelerate = phys_swimaccelerate; + ax = 3; + } //end if + else + { + wishdir[2] = 0; + } //end else + // + wishspeed = VectorNormalize(wishdir); + if (wishspeed > maxvel) wishspeed = maxvel; + VectorScale(frame_test_vel, 1/frametime, frame_test_vel); + AAS_Accelerate(frame_test_vel, frametime, wishdir, wishspeed, accelerate); + VectorScale(frame_test_vel, frametime, frame_test_vel); + /* + for (i = 0; i < ax; i++) + { + velchange = (cmdmove[i] * frametime) - frame_test_vel[i]; + if (velchange > phys_maxacceleration) velchange = phys_maxacceleration; + else if (velchange < -phys_maxacceleration) velchange = -phys_maxacceleration; + newvel = frame_test_vel[i] + velchange; + // + if (frame_test_vel[i] <= maxvel && newvel > maxvel) frame_test_vel[i] = maxvel; + else if (frame_test_vel[i] >= -maxvel && newvel < -maxvel) frame_test_vel[i] = -maxvel; + else frame_test_vel[i] = newvel; + } //end for + */ + } //end if + if (crouch) + { + presencetype = PRESENCE_CROUCH; + } //end if + else if (presencetype == PRESENCE_CROUCH) + { + if (AAS_PointPresenceType(org) & PRESENCE_NORMAL) + { + presencetype = PRESENCE_NORMAL; + } //end if + } //end else + //save the current origin + VectorCopy(org, lastorg); + //move linear during one frame + VectorCopy(frame_test_vel, left_test_vel); + j = 0; + do + { + VectorAdd(org, left_test_vel, end); + //trace a bounding box + trace = AAS_TraceClientBBox(org, end, presencetype, entnum); + // +//#ifdef AAS_MOVE_DEBUG + if (visualize) + { + if (trace.startsolid) botimport.Print(PRT_MESSAGE, "PredictMovement: start solid\n"); + AAS_DebugLine(org, trace.endpos, LINECOLOR_RED); + } //end if +//#endif //AAS_MOVE_DEBUG + // + if (stopevent & (SE_ENTERAREA|SE_TOUCHJUMPPAD|SE_TOUCHTELEPORTER|SE_TOUCHCLUSTERPORTAL)) + { + numareas = AAS_TraceAreas(org, trace.endpos, areas, points, 20); + for (i = 0; i < numareas; i++) + { + if (stopevent & SE_ENTERAREA) + { + if (areas[i] == stopareanum) + { + VectorCopy(points[i], move->endpos); + VectorScale(frame_test_vel, 1/frametime, move->velocity); + move->endarea = areas[i]; + move->trace = trace; + move->stopevent = SE_ENTERAREA; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + //NOTE: if not the first frame + if ((stopevent & SE_TOUCHJUMPPAD) && n) + { + if (aasworld.areasettings[areas[i]].contents & AREACONTENTS_JUMPPAD) + { + VectorCopy(points[i], move->endpos); + VectorScale(frame_test_vel, 1/frametime, move->velocity); + move->endarea = areas[i]; + move->trace = trace; + move->stopevent = SE_TOUCHJUMPPAD; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + if (stopevent & SE_TOUCHTELEPORTER) + { + if (aasworld.areasettings[areas[i]].contents & AREACONTENTS_TELEPORTER) + { + VectorCopy(points[i], move->endpos); + move->endarea = areas[i]; + VectorScale(frame_test_vel, 1/frametime, move->velocity); + move->trace = trace; + move->stopevent = SE_TOUCHTELEPORTER; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + if (stopevent & SE_TOUCHCLUSTERPORTAL) + { + if (aasworld.areasettings[areas[i]].contents & AREACONTENTS_CLUSTERPORTAL) + { + VectorCopy(points[i], move->endpos); + move->endarea = areas[i]; + VectorScale(frame_test_vel, 1/frametime, move->velocity); + move->trace = trace; + move->stopevent = SE_TOUCHCLUSTERPORTAL; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + } //end for + } //end if + // + if (stopevent & SE_HITBOUNDINGBOX) + { + if (AAS_ClipToBBox(&trace, org, trace.endpos, presencetype, mins, maxs)) + { + VectorCopy(trace.endpos, move->endpos); + move->endarea = AAS_PointAreaNum(move->endpos); + VectorScale(frame_test_vel, 1/frametime, move->velocity); + move->trace = trace; + move->stopevent = SE_HITBOUNDINGBOX; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + //move the entity to the trace end point + VectorCopy(trace.endpos, org); + //if there was a collision + if (trace.fraction < 1.0) + { + //get the plane the bounding box collided with + plane = AAS_PlaneFromNum(trace.planenum); + // + if (stopevent & SE_HITGROUNDAREA) + { + if (DotProduct(plane->normal, up) > phys_maxsteepness) + { + VectorCopy(org, start); + start[2] += 0.5; + if (AAS_PointAreaNum(start) == stopareanum) + { + VectorCopy(start, move->endpos); + move->endarea = stopareanum; + VectorScale(frame_test_vel, 1/frametime, move->velocity); + move->trace = trace; + move->stopevent = SE_HITGROUNDAREA; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + } //end if + //assume there's no step + step = qfalse; + //if it is a vertical plane and the bot didn't jump recently + if (plane->normal[2] == 0 && (jump_frame < 0 || n - jump_frame > 2)) + { + //check for a step + VectorMA(org, -0.25, plane->normal, start); + VectorCopy(start, stepend); + start[2] += phys_maxstep; + steptrace = AAS_TraceClientBBox(start, stepend, presencetype, entnum); + // + if (!steptrace.startsolid) + { + plane2 = AAS_PlaneFromNum(steptrace.planenum); + if (DotProduct(plane2->normal, up) > phys_maxsteepness) + { + VectorSubtract(end, steptrace.endpos, left_test_vel); + left_test_vel[2] = 0; + frame_test_vel[2] = 0; +//#ifdef AAS_MOVE_DEBUG + if (visualize) + { + if (steptrace.endpos[2] - org[2] > 0.125) + { + VectorCopy(org, start); + start[2] = steptrace.endpos[2]; + AAS_DebugLine(org, start, LINECOLOR_BLUE); + } //end if + } //end if +//#endif //AAS_MOVE_DEBUG + org[2] = steptrace.endpos[2]; + step = qtrue; + } //end if + } //end if + } //end if + // + if (!step) + { + //velocity left to test for this frame is the projection + //of the current test velocity into the hit plane + VectorMA(left_test_vel, -DotProduct(left_test_vel, plane->normal), + plane->normal, left_test_vel); + //store the old velocity for landing check + VectorCopy(frame_test_vel, old_frame_test_vel); + //test velocity for the next frame is the projection + //of the velocity of the current frame into the hit plane + VectorMA(frame_test_vel, -DotProduct(frame_test_vel, plane->normal), + plane->normal, frame_test_vel); + //check for a landing on an almost horizontal floor + if (DotProduct(plane->normal, up) > phys_maxsteepness) + { + onground = qtrue; + } //end if + if (stopevent & SE_HITGROUNDDAMAGE) + { + delta = 0; + if (old_frame_test_vel[2] < 0 && + frame_test_vel[2] > old_frame_test_vel[2] && + !onground) + { + delta = old_frame_test_vel[2]; + } //end if + else if (onground) + { + delta = frame_test_vel[2] - old_frame_test_vel[2]; + } //end else + if (delta) + { + delta = delta * 10; + delta = delta * delta * 0.0001; + if (swimming) delta = 0; + // never take falling damage if completely underwater + /* + if (ent->waterlevel == 3) return; + if (ent->waterlevel == 2) delta *= 0.25; + if (ent->waterlevel == 1) delta *= 0.5; + */ + if (delta > 40) + { + VectorCopy(org, move->endpos); + move->endarea = AAS_PointAreaNum(org); + VectorCopy(frame_test_vel, move->velocity); + move->trace = trace; + move->stopevent = SE_HITGROUNDDAMAGE; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + } //end if + } //end if + } //end if + //extra check to prevent endless loop + if (++j > 20) return qfalse; + //while there is a plane hit + } while(trace.fraction < 1.0); + //if going down + if (frame_test_vel[2] <= 10) + { + //check for a liquid at the feet of the bot + VectorCopy(org, feet); + feet[2] -= 22; + pc = AAS_PointContents(feet); + //get event from pc + event = SE_NONE; + if (pc & CONTENTS_LAVA) event |= SE_ENTERLAVA; + if (pc & CONTENTS_SLIME) event |= SE_ENTERSLIME; + if (pc & CONTENTS_WATER) event |= SE_ENTERWATER; + // + areanum = AAS_PointAreaNum(org); + if (aasworld.areasettings[areanum].contents & AREACONTENTS_LAVA) + event |= SE_ENTERLAVA; + if (aasworld.areasettings[areanum].contents & AREACONTENTS_SLIME) + event |= SE_ENTERSLIME; + if (aasworld.areasettings[areanum].contents & AREACONTENTS_WATER) + event |= SE_ENTERWATER; + //if in lava or slime + if (event & stopevent) + { + VectorCopy(org, move->endpos); + move->endarea = areanum; + VectorScale(frame_test_vel, 1/frametime, move->velocity); + move->stopevent = event & stopevent; + move->presencetype = presencetype; + move->endcontents = pc; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + // + onground = AAS_OnGround(org, presencetype, entnum); + //if onground and on the ground for at least one whole frame + if (onground) + { + if (stopevent & SE_HITGROUND) + { + VectorCopy(org, move->endpos); + move->endarea = AAS_PointAreaNum(org); + VectorScale(frame_test_vel, 1/frametime, move->velocity); + move->trace = trace; + move->stopevent = SE_HITGROUND; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + else if (stopevent & SE_LEAVEGROUND) + { + VectorCopy(org, move->endpos); + move->endarea = AAS_PointAreaNum(org); + VectorScale(frame_test_vel, 1/frametime, move->velocity); + move->trace = trace; + move->stopevent = SE_LEAVEGROUND; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end else if + else if (stopevent & SE_GAP) + { + aas_trace_t gaptrace; + + VectorCopy(org, start); + VectorCopy(start, end); + end[2] -= 48 + aassettings.phys_maxbarrier; + gaptrace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, -1); + //if solid is found the bot cannot walk any further and will not fall into a gap + if (!gaptrace.startsolid) + { + //if it is a gap (lower than one step height) + if (gaptrace.endpos[2] < org[2] - aassettings.phys_maxstep - 1) + { + if (!(AAS_PointContents(end) & CONTENTS_WATER)) + { + VectorCopy(lastorg, move->endpos); + move->endarea = AAS_PointAreaNum(lastorg); + VectorScale(frame_test_vel, 1/frametime, move->velocity); + move->trace = trace; + move->stopevent = SE_GAP; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + } //end if + } //end else if + } //end for + // + VectorCopy(org, move->endpos); + move->endarea = AAS_PointAreaNum(org); + VectorScale(frame_test_vel, 1/frametime, move->velocity); + move->stopevent = SE_NONE; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + // + return qtrue; +} //end of the function AAS_ClientMovementPrediction +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_PredictClientMovement(struct aas_clientmove_s *move, + int entnum, vec3_t origin, + int presencetype, int onground, + vec3_t velocity, vec3_t cmdmove, + int cmdframes, + int maxframes, float frametime, + int stopevent, int stopareanum, int visualize) +{ + vec3_t mins, maxs; + return AAS_ClientMovementPrediction(move, entnum, origin, presencetype, onground, + velocity, cmdmove, cmdframes, maxframes, + frametime, stopevent, stopareanum, + mins, maxs, visualize); +} //end of the function AAS_PredictClientMovement +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_ClientMovementHitBBox(struct aas_clientmove_s *move, + int entnum, vec3_t origin, + int presencetype, int onground, + vec3_t velocity, vec3_t cmdmove, + int cmdframes, + int maxframes, float frametime, + vec3_t mins, vec3_t maxs, int visualize) +{ + return AAS_ClientMovementPrediction(move, entnum, origin, presencetype, onground, + velocity, cmdmove, cmdframes, maxframes, + frametime, SE_HITBOUNDINGBOX, 0, + mins, maxs, visualize); +} //end of the function AAS_ClientMovementHitBBox +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_TestMovementPrediction(int entnum, vec3_t origin, vec3_t dir) +{ + vec3_t velocity, cmdmove; + aas_clientmove_t move; + + VectorClear(velocity); + if (!AAS_Swimming(origin)) dir[2] = 0; + VectorNormalize(dir); + VectorScale(dir, 400, cmdmove); + cmdmove[2] = 224; + AAS_ClearShownDebugLines(); + AAS_PredictClientMovement(&move, entnum, origin, PRESENCE_NORMAL, qtrue, + velocity, cmdmove, 13, 13, 0.1f, SE_HITGROUND, 0, qtrue);//SE_LEAVEGROUND); + if (move.stopevent & SE_LEAVEGROUND) + { + botimport.Print(PRT_MESSAGE, "leave ground\n"); + } //end if +} //end of the function TestMovementPrediction +//=========================================================================== +// calculates the horizontal velocity needed to perform a jump from start +// to end +// +// Parameter: zvel : z velocity for jump +// start : start position of jump +// end : end position of jump +// *speed : returned speed for jump +// Returns: qfalse if too high or too far from start to end +// Changes Globals: - +//=========================================================================== +int AAS_HorizontalVelocityForJump(float zvel, vec3_t start, vec3_t end, float *velocity) +{ + float phys_gravity, phys_maxvelocity; + float maxjump, height2fall, t, top; + vec3_t dir; + + phys_gravity = aassettings.phys_gravity; + phys_maxvelocity = aassettings.phys_maxvelocity; + + //maximum height a player can jump with the given initial z velocity + maxjump = 0.5 * phys_gravity * (zvel / phys_gravity) * (zvel / phys_gravity); + //top of the parabolic jump + top = start[2] + maxjump; + //height the bot will fall from the top + height2fall = top - end[2]; + //if the goal is to high to jump to + if (height2fall < 0) + { + *velocity = phys_maxvelocity; + return 0; + } //end if + //time a player takes to fall the height + t = sqrt(height2fall / (0.5 * phys_gravity)); + //direction from start to end + VectorSubtract(end, start, dir); + // + if ( (t + zvel / phys_gravity) == 0.0f ) { + *velocity = phys_maxvelocity; + return 0; + } + //calculate horizontal speed + *velocity = sqrt(dir[0]*dir[0] + dir[1]*dir[1]) / (t + zvel / phys_gravity); + //the horizontal speed must be lower than the max speed + if (*velocity > phys_maxvelocity) + { + *velocity = phys_maxvelocity; + return 0; + } //end if + return 1; +} //end of the function AAS_HorizontalVelocityForJump diff --git a/code/botlib/be_aas_move.h b/code/botlib/be_aas_move.h index 5b491a4..5705e46 100755 --- a/code/botlib/be_aas_move.h +++ b/code/botlib/be_aas_move.h @@ -1,71 +1,71 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: be_aas_move.h - * - * desc: AAS - * - * $Archive: /source/code/botlib/be_aas_move.h $ - * - *****************************************************************************/ - -#ifdef AASINTERN -extern aas_settings_t aassettings; -#endif //AASINTERN - -//movement prediction -int AAS_PredictClientMovement(struct aas_clientmove_s *move, - int entnum, vec3_t origin, - int presencetype, int onground, - vec3_t velocity, vec3_t cmdmove, - int cmdframes, - int maxframes, float frametime, - int stopevent, int stopareanum, int visualize); -//predict movement until bounding box is hit -int AAS_ClientMovementHitBBox(struct aas_clientmove_s *move, - int entnum, vec3_t origin, - int presencetype, int onground, - vec3_t velocity, vec3_t cmdmove, - int cmdframes, - int maxframes, float frametime, - vec3_t mins, vec3_t maxs, int visualize); -//returns true if on the ground at the given origin -int AAS_OnGround(vec3_t origin, int presencetype, int passent); -//returns true if swimming at the given origin -int AAS_Swimming(vec3_t origin); -//returns the jump reachability run start point -void AAS_JumpReachRunStart(struct aas_reachability_s *reach, vec3_t runstart); -//returns true if against a ladder at the given origin -int AAS_AgainstLadder(vec3_t origin); -//rocket jump Z velocity when rocket-jumping at origin -float AAS_RocketJumpZVelocity(vec3_t origin); -//bfg jump Z velocity when bfg-jumping at origin -float AAS_BFGJumpZVelocity(vec3_t origin); -//calculates the horizontal velocity needed for a jump and returns true this velocity could be calculated -int AAS_HorizontalVelocityForJump(float zvel, vec3_t start, vec3_t end, float *velocity); -// -void AAS_SetMovedir(vec3_t angles, vec3_t movedir); -// -int AAS_DropToFloor(vec3_t origin, vec3_t mins, vec3_t maxs); -// -void AAS_InitSettings(void); +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_move.h + * + * desc: AAS + * + * $Archive: /source/code/botlib/be_aas_move.h $ + * + *****************************************************************************/ + +#ifdef AASINTERN +extern aas_settings_t aassettings; +#endif //AASINTERN + +//movement prediction +int AAS_PredictClientMovement(struct aas_clientmove_s *move, + int entnum, vec3_t origin, + int presencetype, int onground, + vec3_t velocity, vec3_t cmdmove, + int cmdframes, + int maxframes, float frametime, + int stopevent, int stopareanum, int visualize); +//predict movement until bounding box is hit +int AAS_ClientMovementHitBBox(struct aas_clientmove_s *move, + int entnum, vec3_t origin, + int presencetype, int onground, + vec3_t velocity, vec3_t cmdmove, + int cmdframes, + int maxframes, float frametime, + vec3_t mins, vec3_t maxs, int visualize); +//returns true if on the ground at the given origin +int AAS_OnGround(vec3_t origin, int presencetype, int passent); +//returns true if swimming at the given origin +int AAS_Swimming(vec3_t origin); +//returns the jump reachability run start point +void AAS_JumpReachRunStart(struct aas_reachability_s *reach, vec3_t runstart); +//returns true if against a ladder at the given origin +int AAS_AgainstLadder(vec3_t origin); +//rocket jump Z velocity when rocket-jumping at origin +float AAS_RocketJumpZVelocity(vec3_t origin); +//bfg jump Z velocity when bfg-jumping at origin +float AAS_BFGJumpZVelocity(vec3_t origin); +//calculates the horizontal velocity needed for a jump and returns true this velocity could be calculated +int AAS_HorizontalVelocityForJump(float zvel, vec3_t start, vec3_t end, float *velocity); +// +void AAS_SetMovedir(vec3_t angles, vec3_t movedir); +// +int AAS_DropToFloor(vec3_t origin, vec3_t mins, vec3_t maxs); +// +void AAS_InitSettings(void); diff --git a/code/botlib/be_aas_optimize.c b/code/botlib/be_aas_optimize.c index f420fd1..605dc4d 100755 --- a/code/botlib/be_aas_optimize.c +++ b/code/botlib/be_aas_optimize.c @@ -1,312 +1,312 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: be_aas_optimize.c - * - * desc: decreases the .aas file size after the reachabilities have - * been calculated, just dumps all the faces, edges and vertexes - * - * $Archive: /MissionPack/code/botlib/be_aas_optimize.c $ - * - *****************************************************************************/ - -#include "../game/q_shared.h" -#include "l_libvar.h" -#include "l_memory.h" -#include "l_script.h" -#include "l_precomp.h" -#include "l_struct.h" -#include "aasfile.h" -#include "../game/botlib.h" -#include "../game/be_aas.h" -#include "be_aas_funcs.h" -#include "be_interface.h" -#include "be_aas_def.h" - -typedef struct optimized_s -{ - //vertexes - int numvertexes; - aas_vertex_t *vertexes; - //edges - int numedges; - aas_edge_t *edges; - //edge index - int edgeindexsize; - aas_edgeindex_t *edgeindex; - //faces - int numfaces; - aas_face_t *faces; - //face index - int faceindexsize; - aas_faceindex_t *faceindex; - //convex areas - int numareas; - aas_area_t *areas; - // - int *vertexoptimizeindex; - int *edgeoptimizeindex; - int *faceoptimizeindex; -} optimized_t; - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_KeepEdge(aas_edge_t *edge) -{ - return 1; -} //end of the function AAS_KeepFace -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_OptimizeEdge(optimized_t *optimized, int edgenum) -{ - int i, optedgenum; - aas_edge_t *edge, *optedge; - - edge = &aasworld.edges[abs(edgenum)]; - if (!AAS_KeepEdge(edge)) return 0; - - optedgenum = optimized->edgeoptimizeindex[abs(edgenum)]; - if (optedgenum) - { - //keep the edge reversed sign - if (edgenum > 0) return optedgenum; - else return -optedgenum; - } //end if - - optedge = &optimized->edges[optimized->numedges]; - - for (i = 0; i < 2; i++) - { - if (optimized->vertexoptimizeindex[edge->v[i]]) - { - optedge->v[i] = optimized->vertexoptimizeindex[edge->v[i]]; - } //end if - else - { - VectorCopy(aasworld.vertexes[edge->v[i]], optimized->vertexes[optimized->numvertexes]); - optedge->v[i] = optimized->numvertexes; - optimized->vertexoptimizeindex[edge->v[i]] = optimized->numvertexes; - optimized->numvertexes++; - } //end else - } //end for - optimized->edgeoptimizeindex[abs(edgenum)] = optimized->numedges; - optedgenum = optimized->numedges; - optimized->numedges++; - //keep the edge reversed sign - if (edgenum > 0) return optedgenum; - else return -optedgenum; -} //end of the function AAS_OptimizeEdge -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_KeepFace(aas_face_t *face) -{ - if (!(face->faceflags & FACE_LADDER)) return 0; - else return 1; -} //end of the function AAS_KeepFace -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_OptimizeFace(optimized_t *optimized, int facenum) -{ - int i, edgenum, optedgenum, optfacenum; - aas_face_t *face, *optface; - - face = &aasworld.faces[abs(facenum)]; - if (!AAS_KeepFace(face)) return 0; - - optfacenum = optimized->faceoptimizeindex[abs(facenum)]; - if (optfacenum) - { - //keep the face side sign - if (facenum > 0) return optfacenum; - else return -optfacenum; - } //end if - - optface = &optimized->faces[optimized->numfaces]; - Com_Memcpy(optface, face, sizeof(aas_face_t)); - - optface->numedges = 0; - optface->firstedge = optimized->edgeindexsize; - for (i = 0; i < face->numedges; i++) - { - edgenum = aasworld.edgeindex[face->firstedge + i]; - optedgenum = AAS_OptimizeEdge(optimized, edgenum); - if (optedgenum) - { - optimized->edgeindex[optface->firstedge + optface->numedges] = optedgenum; - optface->numedges++; - optimized->edgeindexsize++; - } //end if - } //end for - optimized->faceoptimizeindex[abs(facenum)] = optimized->numfaces; - optfacenum = optimized->numfaces; - optimized->numfaces++; - //keep the face side sign - if (facenum > 0) return optfacenum; - else return -optfacenum; -} //end of the function AAS_OptimizeFace -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_OptimizeArea(optimized_t *optimized, int areanum) -{ - int i, facenum, optfacenum; - aas_area_t *area, *optarea; - - area = &aasworld.areas[areanum]; - optarea = &optimized->areas[areanum]; - Com_Memcpy(optarea, area, sizeof(aas_area_t)); - - optarea->numfaces = 0; - optarea->firstface = optimized->faceindexsize; - for (i = 0; i < area->numfaces; i++) - { - facenum = aasworld.faceindex[area->firstface + i]; - optfacenum = AAS_OptimizeFace(optimized, facenum); - if (optfacenum) - { - optimized->faceindex[optarea->firstface + optarea->numfaces] = optfacenum; - optarea->numfaces++; - optimized->faceindexsize++; - } //end if - } //end for -} //end of the function AAS_OptimizeArea -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_OptimizeAlloc(optimized_t *optimized) -{ - optimized->vertexes = (aas_vertex_t *) GetClearedMemory(aasworld.numvertexes * sizeof(aas_vertex_t)); - optimized->numvertexes = 0; - optimized->edges = (aas_edge_t *) GetClearedMemory(aasworld.numedges * sizeof(aas_edge_t)); - optimized->numedges = 1; //edge zero is a dummy - optimized->edgeindex = (aas_edgeindex_t *) GetClearedMemory(aasworld.edgeindexsize * sizeof(aas_edgeindex_t)); - optimized->edgeindexsize = 0; - optimized->faces = (aas_face_t *) GetClearedMemory(aasworld.numfaces * sizeof(aas_face_t)); - optimized->numfaces = 1; //face zero is a dummy - optimized->faceindex = (aas_faceindex_t *) GetClearedMemory(aasworld.faceindexsize * sizeof(aas_faceindex_t)); - optimized->faceindexsize = 0; - optimized->areas = (aas_area_t *) GetClearedMemory(aasworld.numareas * sizeof(aas_area_t)); - optimized->numareas = aasworld.numareas; - // - optimized->vertexoptimizeindex = (int *) GetClearedMemory(aasworld.numvertexes * sizeof(int)); - optimized->edgeoptimizeindex = (int *) GetClearedMemory(aasworld.numedges * sizeof(int)); - optimized->faceoptimizeindex = (int *) GetClearedMemory(aasworld.numfaces * sizeof(int)); -} //end of the function AAS_OptimizeAlloc -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_OptimizeStore(optimized_t *optimized) -{ - //store the optimized vertexes - if (aasworld.vertexes) FreeMemory(aasworld.vertexes); - aasworld.vertexes = optimized->vertexes; - aasworld.numvertexes = optimized->numvertexes; - //store the optimized edges - if (aasworld.edges) FreeMemory(aasworld.edges); - aasworld.edges = optimized->edges; - aasworld.numedges = optimized->numedges; - //store the optimized edge index - if (aasworld.edgeindex) FreeMemory(aasworld.edgeindex); - aasworld.edgeindex = optimized->edgeindex; - aasworld.edgeindexsize = optimized->edgeindexsize; - //store the optimized faces - if (aasworld.faces) FreeMemory(aasworld.faces); - aasworld.faces = optimized->faces; - aasworld.numfaces = optimized->numfaces; - //store the optimized face index - if (aasworld.faceindex) FreeMemory(aasworld.faceindex); - aasworld.faceindex = optimized->faceindex; - aasworld.faceindexsize = optimized->faceindexsize; - //store the optimized areas - if (aasworld.areas) FreeMemory(aasworld.areas); - aasworld.areas = optimized->areas; - aasworld.numareas = optimized->numareas; - //free optimize indexes - FreeMemory(optimized->vertexoptimizeindex); - FreeMemory(optimized->edgeoptimizeindex); - FreeMemory(optimized->faceoptimizeindex); -} //end of the function AAS_OptimizeStore -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_Optimize(void) -{ - int i, sign; - optimized_t optimized; - - AAS_OptimizeAlloc(&optimized); - for (i = 1; i < aasworld.numareas; i++) - { - AAS_OptimizeArea(&optimized, i); - } //end for - //reset the reachability face pointers - for (i = 0; i < aasworld.reachabilitysize; i++) - { - //NOTE: for TRAVEL_ELEVATOR the facenum is the model number of - // the elevator - if ((aasworld.reachability[i].traveltype & TRAVELTYPE_MASK) == TRAVEL_ELEVATOR) continue; - //NOTE: for TRAVEL_JUMPPAD the facenum is the Z velocity and the edgenum is the hor velocity - if ((aasworld.reachability[i].traveltype & TRAVELTYPE_MASK) == TRAVEL_JUMPPAD) continue; - //NOTE: for TRAVEL_FUNCBOB the facenum and edgenum contain other coded information - if ((aasworld.reachability[i].traveltype & TRAVELTYPE_MASK) == TRAVEL_FUNCBOB) continue; - // - sign = aasworld.reachability[i].facenum; - aasworld.reachability[i].facenum = optimized.faceoptimizeindex[abs(aasworld.reachability[i].facenum)]; - if (sign < 0) aasworld.reachability[i].facenum = -aasworld.reachability[i].facenum; - sign = aasworld.reachability[i].edgenum; - aasworld.reachability[i].edgenum = optimized.edgeoptimizeindex[abs(aasworld.reachability[i].edgenum)]; - if (sign < 0) aasworld.reachability[i].edgenum = -aasworld.reachability[i].edgenum; - } //end for - //store the optimized AAS data into aasworld - AAS_OptimizeStore(&optimized); - //print some nice stuff :) - botimport.Print(PRT_MESSAGE, "AAS data optimized.\n"); -} //end of the function AAS_Optimize +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_optimize.c + * + * desc: decreases the .aas file size after the reachabilities have + * been calculated, just dumps all the faces, edges and vertexes + * + * $Archive: /MissionPack/code/botlib/be_aas_optimize.c $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_libvar.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_aas_def.h" + +typedef struct optimized_s +{ + //vertexes + int numvertexes; + aas_vertex_t *vertexes; + //edges + int numedges; + aas_edge_t *edges; + //edge index + int edgeindexsize; + aas_edgeindex_t *edgeindex; + //faces + int numfaces; + aas_face_t *faces; + //face index + int faceindexsize; + aas_faceindex_t *faceindex; + //convex areas + int numareas; + aas_area_t *areas; + // + int *vertexoptimizeindex; + int *edgeoptimizeindex; + int *faceoptimizeindex; +} optimized_t; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_KeepEdge(aas_edge_t *edge) +{ + return 1; +} //end of the function AAS_KeepFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_OptimizeEdge(optimized_t *optimized, int edgenum) +{ + int i, optedgenum; + aas_edge_t *edge, *optedge; + + edge = &aasworld.edges[abs(edgenum)]; + if (!AAS_KeepEdge(edge)) return 0; + + optedgenum = optimized->edgeoptimizeindex[abs(edgenum)]; + if (optedgenum) + { + //keep the edge reversed sign + if (edgenum > 0) return optedgenum; + else return -optedgenum; + } //end if + + optedge = &optimized->edges[optimized->numedges]; + + for (i = 0; i < 2; i++) + { + if (optimized->vertexoptimizeindex[edge->v[i]]) + { + optedge->v[i] = optimized->vertexoptimizeindex[edge->v[i]]; + } //end if + else + { + VectorCopy(aasworld.vertexes[edge->v[i]], optimized->vertexes[optimized->numvertexes]); + optedge->v[i] = optimized->numvertexes; + optimized->vertexoptimizeindex[edge->v[i]] = optimized->numvertexes; + optimized->numvertexes++; + } //end else + } //end for + optimized->edgeoptimizeindex[abs(edgenum)] = optimized->numedges; + optedgenum = optimized->numedges; + optimized->numedges++; + //keep the edge reversed sign + if (edgenum > 0) return optedgenum; + else return -optedgenum; +} //end of the function AAS_OptimizeEdge +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_KeepFace(aas_face_t *face) +{ + if (!(face->faceflags & FACE_LADDER)) return 0; + else return 1; +} //end of the function AAS_KeepFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_OptimizeFace(optimized_t *optimized, int facenum) +{ + int i, edgenum, optedgenum, optfacenum; + aas_face_t *face, *optface; + + face = &aasworld.faces[abs(facenum)]; + if (!AAS_KeepFace(face)) return 0; + + optfacenum = optimized->faceoptimizeindex[abs(facenum)]; + if (optfacenum) + { + //keep the face side sign + if (facenum > 0) return optfacenum; + else return -optfacenum; + } //end if + + optface = &optimized->faces[optimized->numfaces]; + Com_Memcpy(optface, face, sizeof(aas_face_t)); + + optface->numedges = 0; + optface->firstedge = optimized->edgeindexsize; + for (i = 0; i < face->numedges; i++) + { + edgenum = aasworld.edgeindex[face->firstedge + i]; + optedgenum = AAS_OptimizeEdge(optimized, edgenum); + if (optedgenum) + { + optimized->edgeindex[optface->firstedge + optface->numedges] = optedgenum; + optface->numedges++; + optimized->edgeindexsize++; + } //end if + } //end for + optimized->faceoptimizeindex[abs(facenum)] = optimized->numfaces; + optfacenum = optimized->numfaces; + optimized->numfaces++; + //keep the face side sign + if (facenum > 0) return optfacenum; + else return -optfacenum; +} //end of the function AAS_OptimizeFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_OptimizeArea(optimized_t *optimized, int areanum) +{ + int i, facenum, optfacenum; + aas_area_t *area, *optarea; + + area = &aasworld.areas[areanum]; + optarea = &optimized->areas[areanum]; + Com_Memcpy(optarea, area, sizeof(aas_area_t)); + + optarea->numfaces = 0; + optarea->firstface = optimized->faceindexsize; + for (i = 0; i < area->numfaces; i++) + { + facenum = aasworld.faceindex[area->firstface + i]; + optfacenum = AAS_OptimizeFace(optimized, facenum); + if (optfacenum) + { + optimized->faceindex[optarea->firstface + optarea->numfaces] = optfacenum; + optarea->numfaces++; + optimized->faceindexsize++; + } //end if + } //end for +} //end of the function AAS_OptimizeArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_OptimizeAlloc(optimized_t *optimized) +{ + optimized->vertexes = (aas_vertex_t *) GetClearedMemory(aasworld.numvertexes * sizeof(aas_vertex_t)); + optimized->numvertexes = 0; + optimized->edges = (aas_edge_t *) GetClearedMemory(aasworld.numedges * sizeof(aas_edge_t)); + optimized->numedges = 1; //edge zero is a dummy + optimized->edgeindex = (aas_edgeindex_t *) GetClearedMemory(aasworld.edgeindexsize * sizeof(aas_edgeindex_t)); + optimized->edgeindexsize = 0; + optimized->faces = (aas_face_t *) GetClearedMemory(aasworld.numfaces * sizeof(aas_face_t)); + optimized->numfaces = 1; //face zero is a dummy + optimized->faceindex = (aas_faceindex_t *) GetClearedMemory(aasworld.faceindexsize * sizeof(aas_faceindex_t)); + optimized->faceindexsize = 0; + optimized->areas = (aas_area_t *) GetClearedMemory(aasworld.numareas * sizeof(aas_area_t)); + optimized->numareas = aasworld.numareas; + // + optimized->vertexoptimizeindex = (int *) GetClearedMemory(aasworld.numvertexes * sizeof(int)); + optimized->edgeoptimizeindex = (int *) GetClearedMemory(aasworld.numedges * sizeof(int)); + optimized->faceoptimizeindex = (int *) GetClearedMemory(aasworld.numfaces * sizeof(int)); +} //end of the function AAS_OptimizeAlloc +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_OptimizeStore(optimized_t *optimized) +{ + //store the optimized vertexes + if (aasworld.vertexes) FreeMemory(aasworld.vertexes); + aasworld.vertexes = optimized->vertexes; + aasworld.numvertexes = optimized->numvertexes; + //store the optimized edges + if (aasworld.edges) FreeMemory(aasworld.edges); + aasworld.edges = optimized->edges; + aasworld.numedges = optimized->numedges; + //store the optimized edge index + if (aasworld.edgeindex) FreeMemory(aasworld.edgeindex); + aasworld.edgeindex = optimized->edgeindex; + aasworld.edgeindexsize = optimized->edgeindexsize; + //store the optimized faces + if (aasworld.faces) FreeMemory(aasworld.faces); + aasworld.faces = optimized->faces; + aasworld.numfaces = optimized->numfaces; + //store the optimized face index + if (aasworld.faceindex) FreeMemory(aasworld.faceindex); + aasworld.faceindex = optimized->faceindex; + aasworld.faceindexsize = optimized->faceindexsize; + //store the optimized areas + if (aasworld.areas) FreeMemory(aasworld.areas); + aasworld.areas = optimized->areas; + aasworld.numareas = optimized->numareas; + //free optimize indexes + FreeMemory(optimized->vertexoptimizeindex); + FreeMemory(optimized->edgeoptimizeindex); + FreeMemory(optimized->faceoptimizeindex); +} //end of the function AAS_OptimizeStore +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Optimize(void) +{ + int i, sign; + optimized_t optimized; + + AAS_OptimizeAlloc(&optimized); + for (i = 1; i < aasworld.numareas; i++) + { + AAS_OptimizeArea(&optimized, i); + } //end for + //reset the reachability face pointers + for (i = 0; i < aasworld.reachabilitysize; i++) + { + //NOTE: for TRAVEL_ELEVATOR the facenum is the model number of + // the elevator + if ((aasworld.reachability[i].traveltype & TRAVELTYPE_MASK) == TRAVEL_ELEVATOR) continue; + //NOTE: for TRAVEL_JUMPPAD the facenum is the Z velocity and the edgenum is the hor velocity + if ((aasworld.reachability[i].traveltype & TRAVELTYPE_MASK) == TRAVEL_JUMPPAD) continue; + //NOTE: for TRAVEL_FUNCBOB the facenum and edgenum contain other coded information + if ((aasworld.reachability[i].traveltype & TRAVELTYPE_MASK) == TRAVEL_FUNCBOB) continue; + // + sign = aasworld.reachability[i].facenum; + aasworld.reachability[i].facenum = optimized.faceoptimizeindex[abs(aasworld.reachability[i].facenum)]; + if (sign < 0) aasworld.reachability[i].facenum = -aasworld.reachability[i].facenum; + sign = aasworld.reachability[i].edgenum; + aasworld.reachability[i].edgenum = optimized.edgeoptimizeindex[abs(aasworld.reachability[i].edgenum)]; + if (sign < 0) aasworld.reachability[i].edgenum = -aasworld.reachability[i].edgenum; + } //end for + //store the optimized AAS data into aasworld + AAS_OptimizeStore(&optimized); + //print some nice stuff :) + botimport.Print(PRT_MESSAGE, "AAS data optimized.\n"); +} //end of the function AAS_Optimize diff --git a/code/botlib/be_aas_optimize.h b/code/botlib/be_aas_optimize.h index 15e44e6..d59bbc9 100755 --- a/code/botlib/be_aas_optimize.h +++ b/code/botlib/be_aas_optimize.h @@ -1,33 +1,33 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: be_aas_optimize.h - * - * desc: AAS - * - * $Archive: /source/code/botlib/be_aas_optimize.h $ - * - *****************************************************************************/ - -void AAS_Optimize(void); - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_optimize.h + * + * desc: AAS + * + * $Archive: /source/code/botlib/be_aas_optimize.h $ + * + *****************************************************************************/ + +void AAS_Optimize(void); + diff --git a/code/botlib/be_aas_reach.c b/code/botlib/be_aas_reach.c index 465da80..4b69d28 100755 --- a/code/botlib/be_aas_reach.c +++ b/code/botlib/be_aas_reach.c @@ -1,4547 +1,4547 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: be_aas_reach.c - * - * desc: reachability calculations - * - * $Archive: /MissionPack/code/botlib/be_aas_reach.c $ - * - *****************************************************************************/ - -#include "../game/q_shared.h" -#include "l_log.h" -#include "l_memory.h" -#include "l_script.h" -#include "l_libvar.h" -#include "l_precomp.h" -#include "l_struct.h" -#include "aasfile.h" -#include "../game/botlib.h" -#include "../game/be_aas.h" -#include "be_aas_funcs.h" -#include "be_aas_def.h" - -extern int Sys_MilliSeconds(void); - - -extern botlib_import_t botimport; - -//#define REACH_DEBUG - -//NOTE: all travel times are in hundreth of a second -//maximum number of reachability links -#define AAS_MAX_REACHABILITYSIZE 65536 -//number of areas reachability is calculated for each frame -#define REACHABILITYAREASPERCYCLE 15 -//number of units reachability points are placed inside the areas -#define INSIDEUNITS 2 -#define INSIDEUNITS_WALKEND 5 -#define INSIDEUNITS_WALKSTART 0.1 -#define INSIDEUNITS_WATERJUMP 15 -//area flag used for weapon jumping -#define AREA_WEAPONJUMP 8192 //valid area to weapon jump to -//number of reachabilities of each type -int reach_swim; //swim -int reach_equalfloor; //walk on floors with equal height -int reach_step; //step up -int reach_walk; //walk of step -int reach_barrier; //jump up to a barrier -int reach_waterjump; //jump out of water -int reach_walkoffledge; //walk of a ledge -int reach_jump; //jump -int reach_ladder; //climb or descent a ladder -int reach_teleport; //teleport -int reach_elevator; //use an elevator -int reach_funcbob; //use a func bob -int reach_grapple; //grapple hook -int reach_doublejump; //double jump -int reach_rampjump; //ramp jump -int reach_strafejump; //strafe jump (just normal jump but further) -int reach_rocketjump; //rocket jump -int reach_bfgjump; //bfg jump -int reach_jumppad; //jump pads -//if true grapple reachabilities are skipped -int calcgrapplereach; -//linked reachability -typedef struct aas_lreachability_s -{ - int areanum; //number of the reachable area - int facenum; //number of the face towards the other area - int edgenum; //number of the edge towards the other area - vec3_t start; //start point of inter area movement - vec3_t end; //end point of inter area movement - int traveltype; //type of travel required to get to the area - unsigned short int traveltime; //travel time of the inter area movement - // - struct aas_lreachability_s *next; -} aas_lreachability_t; -//temporary reachabilities -aas_lreachability_t *reachabilityheap; //heap with reachabilities -aas_lreachability_t *nextreachability; //next free reachability from the heap -aas_lreachability_t **areareachability; //reachability links for every area -int numlreachabilities; - -//=========================================================================== -// returns the surface area of the given face -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -float AAS_FaceArea(aas_face_t *face) -{ - int i, edgenum, side; - float total; - vec_t *v; - vec3_t d1, d2, cross; - aas_edge_t *edge; - - edgenum = aasworld.edgeindex[face->firstedge]; - side = edgenum < 0; - edge = &aasworld.edges[abs(edgenum)]; - v = aasworld.vertexes[edge->v[side]]; - - total = 0; - for (i = 1; i < face->numedges - 1; i++) - { - edgenum = aasworld.edgeindex[face->firstedge + i]; - side = edgenum < 0; - edge = &aasworld.edges[abs(edgenum)]; - VectorSubtract(aasworld.vertexes[edge->v[side]], v, d1); - VectorSubtract(aasworld.vertexes[edge->v[!side]], v, d2); - CrossProduct(d1, d2, cross); - total += 0.5 * VectorLength(cross); - } //end for - return total; -} //end of the function AAS_FaceArea -//=========================================================================== -// returns the volume of an area -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -float AAS_AreaVolume(int areanum) -{ - int i, edgenum, facenum, side; - vec_t d, a, volume; - vec3_t corner; - aas_plane_t *plane; - aas_edge_t *edge; - aas_face_t *face; - aas_area_t *area; - - area = &aasworld.areas[areanum]; - facenum = aasworld.faceindex[area->firstface]; - face = &aasworld.faces[abs(facenum)]; - edgenum = aasworld.edgeindex[face->firstedge]; - edge = &aasworld.edges[abs(edgenum)]; - // - VectorCopy(aasworld.vertexes[edge->v[0]], corner); - - //make tetrahedrons to all other faces - volume = 0; - for (i = 0; i < area->numfaces; i++) - { - facenum = abs(aasworld.faceindex[area->firstface + i]); - face = &aasworld.faces[facenum]; - side = face->backarea != areanum; - plane = &aasworld.planes[face->planenum ^ side]; - d = -(DotProduct (corner, plane->normal) - plane->dist); - a = AAS_FaceArea(face); - volume += d * a; - } //end for - - volume /= 3; - return volume; -} //end of the function AAS_AreaVolume -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_BestReachableLinkArea(aas_link_t *areas) -{ - aas_link_t *link; - - for (link = areas; link; link = link->next_area) - { - if (AAS_AreaGrounded(link->areanum) || AAS_AreaSwim(link->areanum)) - { - return link->areanum; - } //end if - } //end for - // - for (link = areas; link; link = link->next_area) - { - if (link->areanum) return link->areanum; - //FIXME: this is a bad idea when the reachability is not yet - // calculated when the level items are loaded - if (AAS_AreaReachability(link->areanum)) - return link->areanum; - } //end for - return 0; -} //end of the function AAS_BestReachableLinkArea -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_GetJumpPadInfo(int ent, vec3_t areastart, vec3_t absmins, vec3_t absmaxs, vec3_t velocity) -{ - int modelnum, ent2; - float speed, height, gravity, time, dist, forward; - vec3_t origin, angles, teststart, ent2origin; - aas_trace_t trace; - char model[MAX_EPAIRKEY]; - char target[MAX_EPAIRKEY], targetname[MAX_EPAIRKEY]; - - // - AAS_FloatForBSPEpairKey(ent, "speed", &speed); - if (!speed) speed = 1000; - VectorClear(angles); - //get the mins, maxs and origin of the model - AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY); - if (model[0]) modelnum = atoi(model+1); - else modelnum = 0; - AAS_BSPModelMinsMaxsOrigin(modelnum, angles, absmins, absmaxs, origin); - VectorAdd(origin, absmins, absmins); - VectorAdd(origin, absmaxs, absmaxs); - VectorAdd(absmins, absmaxs, origin); - VectorScale (origin, 0.5, origin); - - //get the start areas - VectorCopy(origin, teststart); - teststart[2] += 64; - trace = AAS_TraceClientBBox(teststart, origin, PRESENCE_CROUCH, -1); - if (trace.startsolid) - { - botimport.Print(PRT_MESSAGE, "trigger_push start solid\n"); - VectorCopy(origin, areastart); - } //end if - else - { - VectorCopy(trace.endpos, areastart); - } //end else - areastart[2] += 0.125; - // - //AAS_DrawPermanentCross(origin, 4, 4); - //get the target entity - AAS_ValueForBSPEpairKey(ent, "target", target, MAX_EPAIRKEY); - for (ent2 = AAS_NextBSPEntity(0); ent2; ent2 = AAS_NextBSPEntity(ent2)) - { - if (!AAS_ValueForBSPEpairKey(ent2, "targetname", targetname, MAX_EPAIRKEY)) continue; - if (!strcmp(targetname, target)) break; - } //end for - if (!ent2) - { - botimport.Print(PRT_MESSAGE, "trigger_push without target entity %s\n", target); - return qfalse; - } //end if - AAS_VectorForBSPEpairKey(ent2, "origin", ent2origin); - // - height = ent2origin[2] - origin[2]; - gravity = aassettings.phys_gravity; - time = sqrt( height / ( 0.5 * gravity ) ); - if (!time) - { - botimport.Print(PRT_MESSAGE, "trigger_push without time\n"); - return qfalse; - } //end if - // set s.origin2 to the push velocity - VectorSubtract ( ent2origin, origin, velocity); - dist = VectorNormalize( velocity); - forward = dist / time; - //FIXME: why multiply by 1.1 - forward *= 1.1f; - VectorScale(velocity, forward, velocity); - velocity[2] = time * gravity; - return qtrue; -} //end of the function AAS_GetJumpPadInfo -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_BestReachableFromJumpPadArea(vec3_t origin, vec3_t mins, vec3_t maxs) -{ - int area2num, ent, bot_visualizejumppads, bestareanum; - float volume, bestareavolume; - vec3_t areastart, cmdmove, bboxmins, bboxmaxs; - vec3_t absmins, absmaxs, velocity; - aas_clientmove_t move; - aas_link_t *areas, *link; - char classname[MAX_EPAIRKEY]; - -#ifdef BSPC - bot_visualizejumppads = 0; -#else - bot_visualizejumppads = LibVarValue("bot_visualizejumppads", "0"); -#endif - VectorAdd(origin, mins, bboxmins); - VectorAdd(origin, maxs, bboxmaxs); - for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) - { - if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; - if (strcmp(classname, "trigger_push")) continue; - // - if (!AAS_GetJumpPadInfo(ent, areastart, absmins, absmaxs, velocity)) continue; - //get the areas the jump pad brush is in - areas = AAS_LinkEntityClientBBox(absmins, absmaxs, -1, PRESENCE_CROUCH); - for (link = areas; link; link = link->next_area) - { - if (AAS_AreaJumpPad(link->areanum)) break; - } //end for - if (!link) - { - botimport.Print(PRT_MESSAGE, "trigger_push not in any jump pad area\n"); - AAS_UnlinkFromAreas(areas); - continue; - } //end if - // - //botimport.Print(PRT_MESSAGE, "found a trigger_push with velocity %f %f %f\n", velocity[0], velocity[1], velocity[2]); - // - VectorSet(cmdmove, 0, 0, 0); - Com_Memset(&move, 0, sizeof(aas_clientmove_t)); - area2num = 0; - AAS_ClientMovementHitBBox(&move, -1, areastart, PRESENCE_NORMAL, qfalse, - velocity, cmdmove, 0, 30, 0.1f, bboxmins, bboxmaxs, bot_visualizejumppads); - if (move.frames < 30) - { - bestareanum = 0; - bestareavolume = 0; - for (link = areas; link; link = link->next_area) - { - if (!AAS_AreaJumpPad(link->areanum)) continue; - volume = AAS_AreaVolume(link->areanum); - if (volume >= bestareavolume) - { - bestareanum = link->areanum; - bestareavolume = volume; - } //end if - } //end if - AAS_UnlinkFromAreas(areas); - return bestareanum; - } //end if - AAS_UnlinkFromAreas(areas); - } //end for - return 0; -} //end of the function AAS_BestReachableFromJumpPadArea -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_BestReachableArea(vec3_t origin, vec3_t mins, vec3_t maxs, vec3_t goalorigin) -{ - int areanum, i, j, k, l; - aas_link_t *areas; - vec3_t absmins, absmaxs; - //vec3_t bbmins, bbmaxs; - vec3_t start, end; - aas_trace_t trace; - - if (!aasworld.loaded) - { - botimport.Print(PRT_ERROR, "AAS_BestReachableArea: aas not loaded\n"); - return 0; - } //end if - //find a point in an area - VectorCopy(origin, start); - areanum = AAS_PointAreaNum(start); - //while no area found fudge around a little - for (i = 0; i < 5 && !areanum; i++) - { - for (j = 0; j < 5 && !areanum; j++) - { - for (k = -1; k <= 1 && !areanum; k++) - { - for (l = -1; l <= 1 && !areanum; l++) - { - VectorCopy(origin, start); - start[0] += (float) j * 4 * k; - start[1] += (float) j * 4 * l; - start[2] += (float) i * 4; - areanum = AAS_PointAreaNum(start); - } //end for - } //end for - } //end for - } //end for - //if an area was found - if (areanum) - { - //drop client bbox down and try again - VectorCopy(start, end); - start[2] += 0.25; - end[2] -= 50; - trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, -1); - if (!trace.startsolid) - { - areanum = AAS_PointAreaNum(trace.endpos); - VectorCopy(trace.endpos, goalorigin); - //FIXME: cannot enable next line right now because the reachability - // does not have to be calculated when the level items are loaded - //if the origin is in an area with reachability - //if (AAS_AreaReachability(areanum)) return areanum; - if (areanum) return areanum; - } //end if - else - { - //it can very well happen that the AAS_PointAreaNum function tells that - //a point is in an area and that starting a AAS_TraceClientBBox from that - //point will return trace.startsolid qtrue -#if 0 - if (AAS_PointAreaNum(start)) - { - Log_Write("point %f %f %f in area %d but trace startsolid", start[0], start[1], start[2], areanum); - AAS_DrawPermanentCross(start, 4, LINECOLOR_RED); - } //end if - botimport.Print(PRT_MESSAGE, "AAS_BestReachableArea: start solid\n"); -#endif - VectorCopy(start, goalorigin); - return areanum; - } //end else - } //end if - // - //AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, bbmins, bbmaxs); - //NOTE: the goal origin does not have to be in the goal area - // because the bot will have to move towards the item origin anyway - VectorCopy(origin, goalorigin); - // - VectorAdd(origin, mins, absmins); - VectorAdd(origin, maxs, absmaxs); - //add bounding box size - //VectorSubtract(absmins, bbmaxs, absmins); - //VectorSubtract(absmaxs, bbmins, absmaxs); - //link an invalid (-1) entity - areas = AAS_LinkEntityClientBBox(absmins, absmaxs, -1, PRESENCE_CROUCH); - //get the reachable link arae - areanum = AAS_BestReachableLinkArea(areas); - //unlink the invalid entity - AAS_UnlinkFromAreas(areas); - // - return areanum; -} //end of the function AAS_BestReachableArea -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_SetupReachabilityHeap(void) -{ - int i; - - reachabilityheap = (aas_lreachability_t *) GetClearedMemory( - AAS_MAX_REACHABILITYSIZE * sizeof(aas_lreachability_t)); - for (i = 0; i < AAS_MAX_REACHABILITYSIZE-1; i++) - { - reachabilityheap[i].next = &reachabilityheap[i+1]; - } //end for - reachabilityheap[AAS_MAX_REACHABILITYSIZE-1].next = NULL; - nextreachability = reachabilityheap; - numlreachabilities = 0; -} //end of the function AAS_InitReachabilityHeap -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_ShutDownReachabilityHeap(void) -{ - FreeMemory(reachabilityheap); - numlreachabilities = 0; -} //end of the function AAS_ShutDownReachabilityHeap -//=========================================================================== -// returns a reachability link -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -aas_lreachability_t *AAS_AllocReachability(void) -{ - aas_lreachability_t *r; - - if (!nextreachability) return NULL; - //make sure the error message only shows up once - if (!nextreachability->next) AAS_Error("AAS_MAX_REACHABILITYSIZE"); - // - r = nextreachability; - nextreachability = nextreachability->next; - numlreachabilities++; - return r; -} //end of the function AAS_AllocReachability -//=========================================================================== -// frees a reachability link -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_FreeReachability(aas_lreachability_t *lreach) -{ - Com_Memset(lreach, 0, sizeof(aas_lreachability_t)); - - lreach->next = nextreachability; - nextreachability = lreach; - numlreachabilities--; -} //end of the function AAS_FreeReachability -//=========================================================================== -// returns qtrue if the area has reachability links -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_AreaReachability(int areanum) -{ - if (areanum < 0 || areanum >= aasworld.numareas) - { - AAS_Error("AAS_AreaReachability: areanum %d out of range", areanum); - return 0; - } //end if - return aasworld.areasettings[areanum].numreachableareas; -} //end of the function AAS_AreaReachability -//=========================================================================== -// returns the surface area of all ground faces together of the area -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -float AAS_AreaGroundFaceArea(int areanum) -{ - int i; - float total; - aas_area_t *area; - aas_face_t *face; - - total = 0; - area = &aasworld.areas[areanum]; - for (i = 0; i < area->numfaces; i++) - { - face = &aasworld.faces[abs(aasworld.faceindex[area->firstface + i])]; - if (!(face->faceflags & FACE_GROUND)) continue; - // - total += AAS_FaceArea(face); - } //end for - return total; -} //end of the function AAS_AreaGroundFaceArea -//=========================================================================== -// returns the center of a face -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_FaceCenter(int facenum, vec3_t center) -{ - int i; - float scale; - aas_face_t *face; - aas_edge_t *edge; - - face = &aasworld.faces[facenum]; - - VectorClear(center); - for (i = 0; i < face->numedges; i++) - { - edge = &aasworld.edges[abs(aasworld.edgeindex[face->firstedge + i])]; - VectorAdd(center, aasworld.vertexes[edge->v[0]], center); - VectorAdd(center, aasworld.vertexes[edge->v[1]], center); - } //end for - scale = 0.5 / face->numedges; - VectorScale(center, scale, center); -} //end of the function AAS_FaceCenter -//=========================================================================== -// returns the maximum distance a player can fall before being damaged -// damage = deltavelocity*deltavelocity * 0.0001 -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_FallDamageDistance(void) -{ - float maxzvelocity, gravity, t; - - maxzvelocity = sqrt(30 * 10000); - gravity = aassettings.phys_gravity; - t = maxzvelocity / gravity; - return 0.5 * gravity * t * t; -} //end of the function AAS_FallDamageDistance -//=========================================================================== -// distance = 0.5 * gravity * t * t -// vel = t * gravity -// damage = vel * vel * 0.0001 -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -float AAS_FallDelta(float distance) -{ - float t, delta, gravity; - - gravity = aassettings.phys_gravity; - t = sqrt(fabs(distance) * 2 / gravity); - delta = t * gravity; - return delta * delta * 0.0001; -} //end of the function AAS_FallDelta -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -float AAS_MaxJumpHeight(float phys_jumpvel) -{ - float phys_gravity; - - phys_gravity = aassettings.phys_gravity; - //maximum height a player can jump with the given initial z velocity - return 0.5 * phys_gravity * (phys_jumpvel / phys_gravity) * (phys_jumpvel / phys_gravity); -} //end of the function MaxJumpHeight -//=========================================================================== -// returns true if a player can only crouch in the area -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -float AAS_MaxJumpDistance(float phys_jumpvel) -{ - float phys_gravity, phys_maxvelocity, t; - - phys_gravity = aassettings.phys_gravity; - phys_maxvelocity = aassettings.phys_maxvelocity; - //time a player takes to fall the height - t = sqrt(aassettings.rs_maxjumpfallheight / (0.5 * phys_gravity)); - //maximum distance - return phys_maxvelocity * (t + phys_jumpvel / phys_gravity); -} //end of the function AAS_MaxJumpDistance -//=========================================================================== -// returns true if a player can only crouch in the area -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_AreaCrouch(int areanum) -{ - if (!(aasworld.areasettings[areanum].presencetype & PRESENCE_NORMAL)) return qtrue; - else return qfalse; -} //end of the function AAS_AreaCrouch -//=========================================================================== -// returns qtrue if it is possible to swim in the area -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_AreaSwim(int areanum) -{ - if (aasworld.areasettings[areanum].areaflags & AREA_LIQUID) return qtrue; - else return qfalse; -} //end of the function AAS_AreaSwim -//=========================================================================== -// returns qtrue if the area contains a liquid -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_AreaLiquid(int areanum) -{ - if (aasworld.areasettings[areanum].areaflags & AREA_LIQUID) return qtrue; - else return qfalse; -} //end of the function AAS_AreaLiquid -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_AreaLava(int areanum) -{ - return (aasworld.areasettings[areanum].contents & AREACONTENTS_LAVA); -} //end of the function AAS_AreaLava -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_AreaSlime(int areanum) -{ - return (aasworld.areasettings[areanum].contents & AREACONTENTS_SLIME); -} //end of the function AAS_AreaSlime -//=========================================================================== -// returns qtrue if the area contains ground faces -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_AreaGrounded(int areanum) -{ - return (aasworld.areasettings[areanum].areaflags & AREA_GROUNDED); -} //end of the function AAS_AreaGround -//=========================================================================== -// returns true if the area contains ladder faces -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_AreaLadder(int areanum) -{ - return (aasworld.areasettings[areanum].areaflags & AREA_LADDER); -} //end of the function AAS_AreaLadder -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_AreaJumpPad(int areanum) -{ - return (aasworld.areasettings[areanum].contents & AREACONTENTS_JUMPPAD); -} //end of the function AAS_AreaJumpPad -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_AreaTeleporter(int areanum) -{ - return (aasworld.areasettings[areanum].contents & AREACONTENTS_TELEPORTER); -} //end of the function AAS_AreaTeleporter -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_AreaClusterPortal(int areanum) -{ - return (aasworld.areasettings[areanum].contents & AREACONTENTS_CLUSTERPORTAL); -} //end of the function AAS_AreaClusterPortal -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_AreaDoNotEnter(int areanum) -{ - return (aasworld.areasettings[areanum].contents & AREACONTENTS_DONOTENTER); -} //end of the function AAS_AreaDoNotEnter -//=========================================================================== -// returns the time it takes perform a barrier jump -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -unsigned short int AAS_BarrierJumpTravelTime(void) -{ - return aassettings.phys_jumpvel / (aassettings.phys_gravity * 0.1); -} //end op the function AAS_BarrierJumpTravelTime -//=========================================================================== -// returns true if there already exists a reachability from area1 to area2 -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean AAS_ReachabilityExists(int area1num, int area2num) -{ - aas_lreachability_t *r; - - for (r = areareachability[area1num]; r; r = r->next) - { - if (r->areanum == area2num) return qtrue; - } //end for - return qfalse; -} //end of the function AAS_ReachabilityExists -//=========================================================================== -// returns true if there is a solid just after the end point when going -// from start to end -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_NearbySolidOrGap(vec3_t start, vec3_t end) -{ - vec3_t dir, testpoint; - int areanum; - - VectorSubtract(end, start, dir); - dir[2] = 0; - VectorNormalize(dir); - VectorMA(end, 48, dir, testpoint); - - areanum = AAS_PointAreaNum(testpoint); - if (!areanum) - { - testpoint[2] += 16; - areanum = AAS_PointAreaNum(testpoint); - if (!areanum) return qtrue; - } //end if - VectorMA(end, 64, dir, testpoint); - areanum = AAS_PointAreaNum(testpoint); - if (areanum) - { - if (!AAS_AreaSwim(areanum) && !AAS_AreaGrounded(areanum)) return qtrue; - } //end if - return qfalse; -} //end of the function AAS_SolidGapTime -//=========================================================================== -// searches for swim reachabilities between adjacent areas -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_Reachability_Swim(int area1num, int area2num) -{ - int i, j, face1num, face2num, side1; - aas_area_t *area1, *area2; - aas_areasettings_t *areasettings; - aas_lreachability_t *lreach; - aas_face_t *face1; - aas_plane_t *plane; - vec3_t start; - - if (!AAS_AreaSwim(area1num) || !AAS_AreaSwim(area2num)) return qfalse; - //if the second area is crouch only - if (!(aasworld.areasettings[area2num].presencetype & PRESENCE_NORMAL)) return qfalse; - - area1 = &aasworld.areas[area1num]; - area2 = &aasworld.areas[area2num]; - - //if the areas are not near anough - for (i = 0; i < 3; i++) - { - if (area1->mins[i] > area2->maxs[i] + 10) return qfalse; - if (area1->maxs[i] < area2->mins[i] - 10) return qfalse; - } //end for - //find a shared face and create a reachability link - for (i = 0; i < area1->numfaces; i++) - { - face1num = aasworld.faceindex[area1->firstface + i]; - side1 = face1num < 0; - face1num = abs(face1num); - // - for (j = 0; j < area2->numfaces; j++) - { - face2num = abs(aasworld.faceindex[area2->firstface + j]); - // - if (face1num == face2num) - { - AAS_FaceCenter(face1num, start); - // - if (AAS_PointContents(start) & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) - { - // - face1 = &aasworld.faces[face1num]; - areasettings = &aasworld.areasettings[area1num]; - //create a new reachability link - lreach = AAS_AllocReachability(); - if (!lreach) return qfalse; - lreach->areanum = area2num; - lreach->facenum = face1num; - lreach->edgenum = 0; - VectorCopy(start, lreach->start); - plane = &aasworld.planes[face1->planenum ^ side1]; - VectorMA(lreach->start, -INSIDEUNITS, plane->normal, lreach->end); - lreach->traveltype = TRAVEL_SWIM; - lreach->traveltime = 1; - //if the volume of the area is rather small - if (AAS_AreaVolume(area2num) < 800) - lreach->traveltime += 200; - //if (!(AAS_PointContents(start) & MASK_WATER)) lreach->traveltime += 500; - //link the reachability - lreach->next = areareachability[area1num]; - areareachability[area1num] = lreach; - reach_swim++; - return qtrue; - } //end if - } //end if - } //end for - } //end for - return qfalse; -} //end of the function AAS_Reachability_Swim -//=========================================================================== -// searches for reachabilities between adjacent areas with equal floor -// heights -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_Reachability_EqualFloorHeight(int area1num, int area2num) -{ - int i, j, edgenum, edgenum1, edgenum2, foundreach, side; - float height, bestheight, length, bestlength; - vec3_t dir, start, end, normal, invgravity, gravitydirection = {0, 0, -1}; - vec3_t edgevec; - aas_area_t *area1, *area2; - aas_face_t *face1, *face2; - aas_edge_t *edge; - aas_plane_t *plane2; - aas_lreachability_t lr, *lreach; - - if (!AAS_AreaGrounded(area1num) || !AAS_AreaGrounded(area2num)) return qfalse; - - area1 = &aasworld.areas[area1num]; - area2 = &aasworld.areas[area2num]; - //if the areas are not near anough in the x-y direction - for (i = 0; i < 2; i++) - { - if (area1->mins[i] > area2->maxs[i] + 10) return qfalse; - if (area1->maxs[i] < area2->mins[i] - 10) return qfalse; - } //end for - //if area 2 is too high above area 1 - if (area2->mins[2] > area1->maxs[2]) return qfalse; - // - VectorCopy(gravitydirection, invgravity); - VectorInverse(invgravity); - // - bestheight = 99999; - bestlength = 0; - foundreach = qfalse; - Com_Memset(&lr, 0, sizeof(aas_lreachability_t)); //make the compiler happy - // - //check if the areas have ground faces with a common edge - //if existing use the lowest common edge for a reachability link - for (i = 0; i < area1->numfaces; i++) - { - face1 = &aasworld.faces[abs(aasworld.faceindex[area1->firstface + i])]; - if (!(face1->faceflags & FACE_GROUND)) continue; - // - for (j = 0; j < area2->numfaces; j++) - { - face2 = &aasworld.faces[abs(aasworld.faceindex[area2->firstface + j])]; - if (!(face2->faceflags & FACE_GROUND)) continue; - //if there is a common edge - for (edgenum1 = 0; edgenum1 < face1->numedges; edgenum1++) - { - for (edgenum2 = 0; edgenum2 < face2->numedges; edgenum2++) - { - if (abs(aasworld.edgeindex[face1->firstedge + edgenum1]) != - abs(aasworld.edgeindex[face2->firstedge + edgenum2])) - continue; - edgenum = aasworld.edgeindex[face1->firstedge + edgenum1]; - side = edgenum < 0; - edge = &aasworld.edges[abs(edgenum)]; - //get the length of the edge - VectorSubtract(aasworld.vertexes[edge->v[1]], - aasworld.vertexes[edge->v[0]], dir); - length = VectorLength(dir); - //get the start point - VectorAdd(aasworld.vertexes[edge->v[0]], - aasworld.vertexes[edge->v[1]], start); - VectorScale(start, 0.5, start); - VectorCopy(start, end); - //get the end point several units inside area2 - //and the start point several units inside area1 - //NOTE: normal is pointing into area2 because the - //face edges are stored counter clockwise - VectorSubtract(aasworld.vertexes[edge->v[side]], - aasworld.vertexes[edge->v[!side]], edgevec); - plane2 = &aasworld.planes[face2->planenum]; - CrossProduct(edgevec, plane2->normal, normal); - VectorNormalize(normal); - // - //VectorMA(start, -1, normal, start); - VectorMA(end, INSIDEUNITS_WALKEND, normal, end); - VectorMA(start, INSIDEUNITS_WALKSTART, normal, start); - end[2] += 0.125; - // - height = DotProduct(invgravity, start); - //NOTE: if there's nearby solid or a gap area after this area - //disabled this crap - //if (AAS_NearbySolidOrGap(start, end)) height += 200; - //NOTE: disabled because it disables reachabilities to very small areas - //if (AAS_PointAreaNum(end) != area2num) continue; - //get the longest lowest edge - if (height < bestheight || - (height < bestheight + 1 && length > bestlength)) - { - bestheight = height; - bestlength = length; - //create a new reachability link - lr.areanum = area2num; - lr.facenum = 0; - lr.edgenum = edgenum; - VectorCopy(start, lr.start); - VectorCopy(end, lr.end); - lr.traveltype = TRAVEL_WALK; - lr.traveltime = 1; - foundreach = qtrue; - } //end if - } //end for - } //end for - } //end for - } //end for - if (foundreach) - { - //create a new reachability link - lreach = AAS_AllocReachability(); - if (!lreach) return qfalse; - lreach->areanum = lr.areanum; - lreach->facenum = lr.facenum; - lreach->edgenum = lr.edgenum; - VectorCopy(lr.start, lreach->start); - VectorCopy(lr.end, lreach->end); - lreach->traveltype = lr.traveltype; - lreach->traveltime = lr.traveltime; - lreach->next = areareachability[area1num]; - areareachability[area1num] = lreach; - //if going into a crouch area - if (!AAS_AreaCrouch(area1num) && AAS_AreaCrouch(area2num)) - { - lreach->traveltime += aassettings.rs_startcrouch; - } //end if - /* - //NOTE: if there's nearby solid or a gap area after this area - if (!AAS_NearbySolidOrGap(lreach->start, lreach->end)) - { - lreach->traveltime += 100; - } //end if - */ - //avoid rather small areas - //if (AAS_AreaGroundFaceArea(lreach->areanum) < 500) lreach->traveltime += 100; - // - reach_equalfloor++; - return qtrue; - } //end if - return qfalse; -} //end of the function AAS_Reachability_EqualFloorHeight -//=========================================================================== -// searches step, barrier, waterjump and walk off ledge reachabilities -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_Reachability_Step_Barrier_WaterJump_WalkOffLedge(int area1num, int area2num) -{ - int i, j, k, l, edge1num, edge2num, areas[10], numareas; - int ground_bestarea2groundedgenum, ground_foundreach; - int water_bestarea2groundedgenum, water_foundreach; - int side1, area1swim, faceside1, groundface1num; - float dist, dist1, dist2, diff, invgravitydot, ortdot; - float x1, x2, x3, x4, y1, y2, y3, y4, tmp, y; - float length, ground_bestlength, water_bestlength, ground_bestdist, water_bestdist; - vec3_t v1, v2, v3, v4, tmpv, p1area1, p1area2, p2area1, p2area2; - vec3_t normal, ort, edgevec, start, end, dir; - vec3_t ground_beststart, ground_bestend, ground_bestnormal; - vec3_t water_beststart, water_bestend, water_bestnormal; - vec3_t invgravity = {0, 0, 1}; - vec3_t testpoint; - aas_plane_t *plane; - aas_area_t *area1, *area2; - aas_face_t *groundface1, *groundface2, *ground_bestface1, *water_bestface1; - aas_edge_t *edge1, *edge2; - aas_lreachability_t *lreach; - aas_trace_t trace; - - //must be able to walk or swim in the first area - if (!AAS_AreaGrounded(area1num) && !AAS_AreaSwim(area1num)) return qfalse; - // - if (!AAS_AreaGrounded(area2num) && !AAS_AreaSwim(area2num)) return qfalse; - // - area1 = &aasworld.areas[area1num]; - area2 = &aasworld.areas[area2num]; - //if the first area contains a liquid - area1swim = AAS_AreaSwim(area1num); - //if the areas are not near anough in the x-y direction - for (i = 0; i < 2; i++) - { - if (area1->mins[i] > area2->maxs[i] + 10) return qfalse; - if (area1->maxs[i] < area2->mins[i] - 10) return qfalse; - } //end for - // - ground_foundreach = qfalse; - ground_bestdist = 99999; - ground_bestlength = 0; - ground_bestarea2groundedgenum = 0; - // - water_foundreach = qfalse; - water_bestdist = 99999; - water_bestlength = 0; - water_bestarea2groundedgenum = 0; - // - for (i = 0; i < area1->numfaces; i++) - { - groundface1num = aasworld.faceindex[area1->firstface + i]; - faceside1 = groundface1num < 0; - groundface1 = &aasworld.faces[abs(groundface1num)]; - //if this isn't a ground face - if (!(groundface1->faceflags & FACE_GROUND)) - { - //if we can swim in the first area - if (area1swim) - { - //face plane must be more or less horizontal - plane = &aasworld.planes[groundface1->planenum ^ (!faceside1)]; - if (DotProduct(plane->normal, invgravity) < 0.7) continue; - } //end if - else - { - //if we can't swim in the area it must be a ground face - continue; - } //end else - } //end if - // - for (k = 0; k < groundface1->numedges; k++) - { - edge1num = aasworld.edgeindex[groundface1->firstedge + k]; - side1 = (edge1num < 0); - //NOTE: for water faces we must take the side area 1 is - // on into account because the face is shared and doesn't - // have to be oriented correctly - if (!(groundface1->faceflags & FACE_GROUND)) side1 = (side1 == faceside1); - edge1num = abs(edge1num); - edge1 = &aasworld.edges[edge1num]; - //vertexes of the edge - VectorCopy(aasworld.vertexes[edge1->v[!side1]], v1); - VectorCopy(aasworld.vertexes[edge1->v[side1]], v2); - //get a vertical plane through the edge - //NOTE: normal is pointing into area 2 because the - //face edges are stored counter clockwise - VectorSubtract(v2, v1, edgevec); - CrossProduct(edgevec, invgravity, normal); - VectorNormalize(normal); - dist = DotProduct(normal, v1); - //check the faces from the second area - for (j = 0; j < area2->numfaces; j++) - { - groundface2 = &aasworld.faces[abs(aasworld.faceindex[area2->firstface + j])]; - //must be a ground face - if (!(groundface2->faceflags & FACE_GROUND)) continue; - //check the edges of this ground face - for (l = 0; l < groundface2->numedges; l++) - { - edge2num = abs(aasworld.edgeindex[groundface2->firstedge + l]); - edge2 = &aasworld.edges[edge2num]; - //vertexes of the edge - VectorCopy(aasworld.vertexes[edge2->v[0]], v3); - VectorCopy(aasworld.vertexes[edge2->v[1]], v4); - //check the distance between the two points and the vertical plane - //through the edge of area1 - diff = DotProduct(normal, v3) - dist; - if (diff < -0.1 || diff > 0.1) continue; - diff = DotProduct(normal, v4) - dist; - if (diff < -0.1 || diff > 0.1) continue; - // - //project the two ground edges into the step side plane - //and calculate the shortest distance between the two - //edges if they overlap in the direction orthogonal to - //the gravity direction - CrossProduct(invgravity, normal, ort); - invgravitydot = DotProduct(invgravity, invgravity); - ortdot = DotProduct(ort, ort); - //projection into the step plane - //NOTE: since gravity is vertical this is just the z coordinate - y1 = v1[2];//DotProduct(v1, invgravity) / invgravitydot; - y2 = v2[2];//DotProduct(v2, invgravity) / invgravitydot; - y3 = v3[2];//DotProduct(v3, invgravity) / invgravitydot; - y4 = v4[2];//DotProduct(v4, invgravity) / invgravitydot; - // - x1 = DotProduct(v1, ort) / ortdot; - x2 = DotProduct(v2, ort) / ortdot; - x3 = DotProduct(v3, ort) / ortdot; - x4 = DotProduct(v4, ort) / ortdot; - // - if (x1 > x2) - { - tmp = x1; x1 = x2; x2 = tmp; - tmp = y1; y1 = y2; y2 = tmp; - VectorCopy(v1, tmpv); VectorCopy(v2, v1); VectorCopy(tmpv, v2); - } //end if - if (x3 > x4) - { - tmp = x3; x3 = x4; x4 = tmp; - tmp = y3; y3 = y4; y4 = tmp; - VectorCopy(v3, tmpv); VectorCopy(v4, v3); VectorCopy(tmpv, v4); - } //end if - //if the two projected edge lines have no overlap - if (x2 <= x3 || x4 <= x1) - { -// Log_Write("lines no overlap: from area %d to %d\r\n", area1num, area2num); - continue; - } //end if - //if the two lines fully overlap - if ((x1 - 0.5 < x3 && x4 < x2 + 0.5) && - (x3 - 0.5 < x1 && x2 < x4 + 0.5)) - { - dist1 = y3 - y1; - dist2 = y4 - y2; - VectorCopy(v1, p1area1); - VectorCopy(v2, p2area1); - VectorCopy(v3, p1area2); - VectorCopy(v4, p2area2); - } //end if - else - { - //if the points are equal - if (x1 > x3 - 0.1 && x1 < x3 + 0.1) - { - dist1 = y3 - y1; - VectorCopy(v1, p1area1); - VectorCopy(v3, p1area2); - } //end if - else if (x1 < x3) - { - y = y1 + (x3 - x1) * (y2 - y1) / (x2 - x1); - dist1 = y3 - y; - VectorCopy(v3, p1area1); - p1area1[2] = y; - VectorCopy(v3, p1area2); - } //end if - else - { - y = y3 + (x1 - x3) * (y4 - y3) / (x4 - x3); - dist1 = y - y1; - VectorCopy(v1, p1area1); - VectorCopy(v1, p1area2); - p1area2[2] = y; - } //end if - //if the points are equal - if (x2 > x4 - 0.1 && x2 < x4 + 0.1) - { - dist2 = y4 - y2; - VectorCopy(v2, p2area1); - VectorCopy(v4, p2area2); - } //end if - else if (x2 < x4) - { - y = y3 + (x2 - x3) * (y4 - y3) / (x4 - x3); - dist2 = y - y2; - VectorCopy(v2, p2area1); - VectorCopy(v2, p2area2); - p2area2[2] = y; - } //end if - else - { - y = y1 + (x4 - x1) * (y2 - y1) / (x2 - x1); - dist2 = y4 - y; - VectorCopy(v4, p2area1); - p2area1[2] = y; - VectorCopy(v4, p2area2); - } //end else - } //end else - //if both distances are pretty much equal - //then we take the middle of the points - if (dist1 > dist2 - 1 && dist1 < dist2 + 1) - { - dist = dist1; - VectorAdd(p1area1, p2area1, start); - VectorScale(start, 0.5, start); - VectorAdd(p1area2, p2area2, end); - VectorScale(end, 0.5, end); - } //end if - else if (dist1 < dist2) - { - dist = dist1; - VectorCopy(p1area1, start); - VectorCopy(p1area2, end); - } //end else if - else - { - dist = dist2; - VectorCopy(p2area1, start); - VectorCopy(p2area2, end); - } //end else - //get the length of the overlapping part of the edges of the two areas - VectorSubtract(p2area2, p1area2, dir); - length = VectorLength(dir); - // - if (groundface1->faceflags & FACE_GROUND) - { - //if the vertical distance is smaller - if (dist < ground_bestdist || - //or the vertical distance is pretty much the same - //but the overlapping part of the edges is longer - (dist < ground_bestdist + 1 && length > ground_bestlength)) - { - ground_bestdist = dist; - ground_bestlength = length; - ground_foundreach = qtrue; - ground_bestarea2groundedgenum = edge1num; - ground_bestface1 = groundface1; - //best point towards area1 - VectorCopy(start, ground_beststart); - //normal is pointing into area2 - VectorCopy(normal, ground_bestnormal); - //best point towards area2 - VectorCopy(end, ground_bestend); - } //end if - } //end if - else - { - //if the vertical distance is smaller - if (dist < water_bestdist || - //or the vertical distance is pretty much the same - //but the overlapping part of the edges is longer - (dist < water_bestdist + 1 && length > water_bestlength)) - { - water_bestdist = dist; - water_bestlength = length; - water_foundreach = qtrue; - water_bestarea2groundedgenum = edge1num; - water_bestface1 = groundface1; - //best point towards area1 - VectorCopy(start, water_beststart); - //normal is pointing into area2 - VectorCopy(normal, water_bestnormal); - //best point towards area2 - VectorCopy(end, water_bestend); - } //end if - } //end else - } //end for - } //end for - } //end for - } //end for - // - // NOTE: swim reachabilities are already filtered out - // - // Steps - // - // --------- - // | step height -> TRAVEL_WALK - //--------| - // - // --------- - //~~~~~~~~| step height and low water -> TRAVEL_WALK - //--------| - // - //~~~~~~~~~~~~~~~~~~ - // --------- - // | step height and low water up to the step -> TRAVEL_WALK - //--------| - // - //check for a step reachability - if (ground_foundreach) - { - //if area2 is higher but lower than the maximum step height - //NOTE: ground_bestdist >= 0 also catches equal floor reachabilities - if (ground_bestdist >= 0 && ground_bestdist < aassettings.phys_maxstep) - { - //create walk reachability from area1 to area2 - lreach = AAS_AllocReachability(); - if (!lreach) return qfalse; - lreach->areanum = area2num; - lreach->facenum = 0; - lreach->edgenum = ground_bestarea2groundedgenum; - VectorMA(ground_beststart, INSIDEUNITS_WALKSTART, ground_bestnormal, lreach->start); - VectorMA(ground_bestend, INSIDEUNITS_WALKEND, ground_bestnormal, lreach->end); - lreach->traveltype = TRAVEL_WALK; - lreach->traveltime = 0;//1; - //if going into a crouch area - if (!AAS_AreaCrouch(area1num) && AAS_AreaCrouch(area2num)) - { - lreach->traveltime += aassettings.rs_startcrouch; - } //end if - lreach->next = areareachability[area1num]; - areareachability[area1num] = lreach; - //NOTE: if there's nearby solid or a gap area after this area - /* - if (!AAS_NearbySolidOrGap(lreach->start, lreach->end)) - { - lreach->traveltime += 100; - } //end if - */ - //avoid rather small areas - //if (AAS_AreaGroundFaceArea(lreach->areanum) < 500) lreach->traveltime += 100; - // - reach_step++; - return qtrue; - } //end if - } //end if - // - // Water Jumps - // - // --------- - // | - //~~~~~~~~| - // | - // | higher than step height and water up to waterjump height -> TRAVEL_WATERJUMP - //--------| - // - //~~~~~~~~~~~~~~~~~~ - // --------- - // | - // | - // | - // | higher than step height and low water up to the step -> TRAVEL_WATERJUMP - //--------| - // - //check for a waterjump reachability - if (water_foundreach) - { - //get a test point a little bit towards area1 - VectorMA(water_bestend, -INSIDEUNITS, water_bestnormal, testpoint); - //go down the maximum waterjump height - testpoint[2] -= aassettings.phys_maxwaterjump; - //if there IS water the sv_maxwaterjump height below the bestend point - if (aasworld.areasettings[AAS_PointAreaNum(testpoint)].areaflags & AREA_LIQUID) - { - //don't create rediculous water jump reachabilities from areas very far below - //the water surface - if (water_bestdist < aassettings.phys_maxwaterjump + 24) - { - //waterjumping from or towards a crouch only area is not possible in Quake2 - if ((aasworld.areasettings[area1num].presencetype & PRESENCE_NORMAL) && - (aasworld.areasettings[area2num].presencetype & PRESENCE_NORMAL)) - { - //create water jump reachability from area1 to area2 - lreach = AAS_AllocReachability(); - if (!lreach) return qfalse; - lreach->areanum = area2num; - lreach->facenum = 0; - lreach->edgenum = water_bestarea2groundedgenum; - VectorCopy(water_beststart, lreach->start); - VectorMA(water_bestend, INSIDEUNITS_WATERJUMP, water_bestnormal, lreach->end); - lreach->traveltype = TRAVEL_WATERJUMP; - lreach->traveltime = aassettings.rs_waterjump; - lreach->next = areareachability[area1num]; - areareachability[area1num] = lreach; - //we've got another waterjump reachability - reach_waterjump++; - return qtrue; - } //end if - } //end if - } //end if - } //end if - // - // Barrier Jumps - // - // --------- - // | - // | - // | - // | higher than step height lower than barrier height -> TRAVEL_BARRIERJUMP - //--------| - // - // --------- - // | - // | - // | - //~~~~~~~~| higher than step height lower than barrier height - //--------| and a thin layer of water in the area to jump from -> TRAVEL_BARRIERJUMP - // - //check for a barrier jump reachability - if (ground_foundreach) - { - //if area2 is higher but lower than the maximum barrier jump height - if (ground_bestdist > 0 && ground_bestdist < aassettings.phys_maxbarrier) - { - //if no water in area1 or a very thin layer of water on the ground - if (!water_foundreach || (ground_bestdist - water_bestdist < 16)) - { - //cannot perform a barrier jump towards or from a crouch area in Quake2 - if (!AAS_AreaCrouch(area1num) && !AAS_AreaCrouch(area2num)) - { - //create barrier jump reachability from area1 to area2 - lreach = AAS_AllocReachability(); - if (!lreach) return qfalse; - lreach->areanum = area2num; - lreach->facenum = 0; - lreach->edgenum = ground_bestarea2groundedgenum; - VectorMA(ground_beststart, INSIDEUNITS_WALKSTART, ground_bestnormal, lreach->start); - VectorMA(ground_bestend, INSIDEUNITS_WALKEND, ground_bestnormal, lreach->end); - lreach->traveltype = TRAVEL_BARRIERJUMP; - lreach->traveltime = aassettings.rs_barrierjump;//AAS_BarrierJumpTravelTime(); - lreach->next = areareachability[area1num]; - areareachability[area1num] = lreach; - //we've got another barrierjump reachability - reach_barrier++; - return qtrue; - } //end if - } //end if - } //end if - } //end if - // - // Walk and Walk Off Ledge - // - //--------| - // | can walk or step back -> TRAVEL_WALK - // --------- - // - //--------| - // | - // | - // | - // | cannot walk/step back -> TRAVEL_WALKOFFLEDGE - // --------- - // - //--------| - // | - // |~~~~~~~~ - // | - // | cannot step back but can waterjump back -> TRAVEL_WALKOFFLEDGE - // --------- FIXME: create TRAVEL_WALK reach?? - // - //check for a walk or walk off ledge reachability - if (ground_foundreach) - { - if (ground_bestdist < 0) - { - if (ground_bestdist > -aassettings.phys_maxstep) - { - //create walk reachability from area1 to area2 - lreach = AAS_AllocReachability(); - if (!lreach) return qfalse; - lreach->areanum = area2num; - lreach->facenum = 0; - lreach->edgenum = ground_bestarea2groundedgenum; - VectorMA(ground_beststart, INSIDEUNITS_WALKSTART, ground_bestnormal, lreach->start); - VectorMA(ground_bestend, INSIDEUNITS_WALKEND, ground_bestnormal, lreach->end); - lreach->traveltype = TRAVEL_WALK; - lreach->traveltime = 1; - lreach->next = areareachability[area1num]; - areareachability[area1num] = lreach; - //we've got another walk reachability - reach_walk++; - return qtrue; - } //end if - // if no maximum fall height set or less than the max - if (!aassettings.rs_maxfallheight || fabs(ground_bestdist) < aassettings.rs_maxfallheight) { - //trace a bounding box vertically to check for solids - VectorMA(ground_bestend, INSIDEUNITS, ground_bestnormal, ground_bestend); - VectorCopy(ground_bestend, start); - start[2] = ground_beststart[2]; - VectorCopy(ground_bestend, end); - end[2] += 4; - trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, -1); - //if no solids were found - if (!trace.startsolid && trace.fraction >= 1.0) - { - //the trace end point must be in the goal area - trace.endpos[2] += 1; - if (AAS_PointAreaNum(trace.endpos) == area2num) - { - //if not going through a cluster portal - numareas = AAS_TraceAreas(start, end, areas, NULL, sizeof(areas) / sizeof(int)); - for (i = 0; i < numareas; i++) - if (AAS_AreaClusterPortal(areas[i])) - break; - if (i >= numareas) - { - //create a walk off ledge reachability from area1 to area2 - lreach = AAS_AllocReachability(); - if (!lreach) return qfalse; - lreach->areanum = area2num; - lreach->facenum = 0; - lreach->edgenum = ground_bestarea2groundedgenum; - VectorCopy(ground_beststart, lreach->start); - VectorCopy(ground_bestend, lreach->end); - lreach->traveltype = TRAVEL_WALKOFFLEDGE; - lreach->traveltime = aassettings.rs_startwalkoffledge + fabs(ground_bestdist) * 50 / aassettings.phys_gravity; - //if falling from too high and not falling into water - if (!AAS_AreaSwim(area2num) && !AAS_AreaJumpPad(area2num)) - { - if (AAS_FallDelta(ground_bestdist) > aassettings.phys_falldelta5) - { - lreach->traveltime += aassettings.rs_falldamage5; - } //end if - if (AAS_FallDelta(ground_bestdist) > aassettings.phys_falldelta10) - { - lreach->traveltime += aassettings.rs_falldamage10; - } //end if - } //end if - lreach->next = areareachability[area1num]; - areareachability[area1num] = lreach; - // - reach_walkoffledge++; - //NOTE: don't create a weapon (rl, bfg) jump reachability here - //because it interferes with other reachabilities - //like the ladder reachability - return qtrue; - } //end if - } //end if - } //end if - } //end if - } //end else - } //end if - return qfalse; -} //end of the function AAS_Reachability_Step_Barrier_WaterJump_WalkOffLedge -//=========================================================================== -// returns the distance between the two vectors -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -float VectorDistance(vec3_t v1, vec3_t v2) -{ - vec3_t dir; - - VectorSubtract(v2, v1, dir); - return VectorLength(dir); -} //end of the function VectorDistance -//=========================================================================== -// returns true if the first vector is between the last two vectors -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int VectorBetweenVectors(vec3_t v, vec3_t v1, vec3_t v2) -{ - vec3_t dir1, dir2; - - VectorSubtract(v, v1, dir1); - VectorSubtract(v, v2, dir2); - return (DotProduct(dir1, dir2) <= 0); -} //end of the function VectorBetweenVectors -//=========================================================================== -// returns the mid point between the two vectors -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void VectorMiddle(vec3_t v1, vec3_t v2, vec3_t middle) -{ - VectorAdd(v1, v2, middle); - VectorScale(middle, 0.5, middle); -} //end of the function VectorMiddle -//=========================================================================== -// calculate a range of points closest to each other on both edges -// -// Parameter: beststart1 start of the range of points on edge v1-v2 -// beststart2 end of the range of points on edge v1-v2 -// bestend1 start of the range of points on edge v3-v4 -// bestend2 end of the range of points on edge v3-v4 -// bestdist best distance so far -// Returns: - -// Changes Globals: - -//=========================================================================== -/* -float AAS_ClosestEdgePoints(vec3_t v1, vec3_t v2, vec3_t v3, vec3_t v4, - aas_plane_t *plane1, aas_plane_t *plane2, - vec3_t beststart, vec3_t bestend, float bestdist) -{ - vec3_t dir1, dir2, p1, p2, p3, p4; - float a1, a2, b1, b2, dist; - int founddist; - - //edge vectors - VectorSubtract(v2, v1, dir1); - VectorSubtract(v4, v3, dir2); - //get the horizontal directions - dir1[2] = 0; - dir2[2] = 0; - // - // p1 = point on an edge vector of area2 closest to v1 - // p2 = point on an edge vector of area2 closest to v2 - // p3 = point on an edge vector of area1 closest to v3 - // p4 = point on an edge vector of area1 closest to v4 - // - if (dir2[0]) - { - a2 = dir2[1] / dir2[0]; - b2 = v3[1] - a2 * v3[0]; - //point on the edge vector of area2 closest to v1 - p1[0] = (DotProduct(v1, dir2) - (a2 * dir2[0] + b2 * dir2[1])) / dir2[0]; - p1[1] = a2 * p1[0] + b2; - //point on the edge vector of area2 closest to v2 - p2[0] = (DotProduct(v2, dir2) - (a2 * dir2[0] + b2 * dir2[1])) / dir2[0]; - p2[1] = a2 * p2[0] + b2; - } //end if - else - { - //point on the edge vector of area2 closest to v1 - p1[0] = v3[0]; - p1[1] = v1[1]; - //point on the edge vector of area2 closest to v2 - p2[0] = v3[0]; - p2[1] = v2[1]; - } //end else - // - if (dir1[0]) - { - // - a1 = dir1[1] / dir1[0]; - b1 = v1[1] - a1 * v1[0]; - //point on the edge vector of area1 closest to v3 - p3[0] = (DotProduct(v3, dir1) - (a1 * dir1[0] + b1 * dir1[1])) / dir1[0]; - p3[1] = a1 * p3[0] + b1; - //point on the edge vector of area1 closest to v4 - p4[0] = (DotProduct(v4, dir1) - (a1 * dir1[0] + b1 * dir1[1])) / dir1[0]; - p4[1] = a1 * p4[0] + b1; - } //end if - else - { - //point on the edge vector of area1 closest to v3 - p3[0] = v1[0]; - p3[1] = v3[1]; - //point on the edge vector of area1 closest to v4 - p4[0] = v1[0]; - p4[1] = v4[1]; - } //end else - //start with zero z-coordinates - p1[2] = 0; - p2[2] = 0; - p3[2] = 0; - p4[2] = 0; - //calculate the z-coordinates from the ground planes - p1[2] = (plane2->dist - DotProduct(plane2->normal, p1)) / plane2->normal[2]; - p2[2] = (plane2->dist - DotProduct(plane2->normal, p2)) / plane2->normal[2]; - p3[2] = (plane1->dist - DotProduct(plane1->normal, p3)) / plane1->normal[2]; - p4[2] = (plane1->dist - DotProduct(plane1->normal, p4)) / plane1->normal[2]; - // - founddist = qfalse; - // - if (VectorBetweenVectors(p1, v3, v4)) - { - dist = VectorDistance(v1, p1); - if (dist > bestdist - 0.5 && dist < bestdist + 0.5) - { - VectorMiddle(beststart, v1, beststart); - VectorMiddle(bestend, p1, bestend); - } //end if - else if (dist < bestdist) - { - bestdist = dist; - VectorCopy(v1, beststart); - VectorCopy(p1, bestend); - } //end if - founddist = qtrue; - } //end if - if (VectorBetweenVectors(p2, v3, v4)) - { - dist = VectorDistance(v2, p2); - if (dist > bestdist - 0.5 && dist < bestdist + 0.5) - { - VectorMiddle(beststart, v2, beststart); - VectorMiddle(bestend, p2, bestend); - } //end if - else if (dist < bestdist) - { - bestdist = dist; - VectorCopy(v2, beststart); - VectorCopy(p2, bestend); - } //end if - founddist = qtrue; - } //end else if - if (VectorBetweenVectors(p3, v1, v2)) - { - dist = VectorDistance(v3, p3); - if (dist > bestdist - 0.5 && dist < bestdist + 0.5) - { - VectorMiddle(beststart, p3, beststart); - VectorMiddle(bestend, v3, bestend); - } //end if - else if (dist < bestdist) - { - bestdist = dist; - VectorCopy(p3, beststart); - VectorCopy(v3, bestend); - } //end if - founddist = qtrue; - } //end else if - if (VectorBetweenVectors(p4, v1, v2)) - { - dist = VectorDistance(v4, p4); - if (dist > bestdist - 0.5 && dist < bestdist + 0.5) - { - VectorMiddle(beststart, p4, beststart); - VectorMiddle(bestend, v4, bestend); - } //end if - else if (dist < bestdist) - { - bestdist = dist; - VectorCopy(p4, beststart); - VectorCopy(v4, bestend); - } //end if - founddist = qtrue; - } //end else if - //if no shortest distance was found the shortest distance - //is between one of the vertexes of edge1 and one of edge2 - if (!founddist) - { - dist = VectorDistance(v1, v3); - if (dist < bestdist) - { - bestdist = dist; - VectorCopy(v1, beststart); - VectorCopy(v3, bestend); - } //end if - dist = VectorDistance(v1, v4); - if (dist < bestdist) - { - bestdist = dist; - VectorCopy(v1, beststart); - VectorCopy(v4, bestend); - } //end if - dist = VectorDistance(v2, v3); - if (dist < bestdist) - { - bestdist = dist; - VectorCopy(v2, beststart); - VectorCopy(v3, bestend); - } //end if - dist = VectorDistance(v2, v4); - if (dist < bestdist) - { - bestdist = dist; - VectorCopy(v2, beststart); - VectorCopy(v4, bestend); - } //end if - } //end if - return bestdist; -} //end of the function AAS_ClosestEdgePoints*/ - -float AAS_ClosestEdgePoints(vec3_t v1, vec3_t v2, vec3_t v3, vec3_t v4, - aas_plane_t *plane1, aas_plane_t *plane2, - vec3_t beststart1, vec3_t bestend1, - vec3_t beststart2, vec3_t bestend2, float bestdist) -{ - vec3_t dir1, dir2, p1, p2, p3, p4; - float a1, a2, b1, b2, dist, dist1, dist2; - int founddist; - - //edge vectors - VectorSubtract(v2, v1, dir1); - VectorSubtract(v4, v3, dir2); - //get the horizontal directions - dir1[2] = 0; - dir2[2] = 0; - // - // p1 = point on an edge vector of area2 closest to v1 - // p2 = point on an edge vector of area2 closest to v2 - // p3 = point on an edge vector of area1 closest to v3 - // p4 = point on an edge vector of area1 closest to v4 - // - if (dir2[0]) - { - a2 = dir2[1] / dir2[0]; - b2 = v3[1] - a2 * v3[0]; - //point on the edge vector of area2 closest to v1 - p1[0] = (DotProduct(v1, dir2) - (a2 * dir2[0] + b2 * dir2[1])) / dir2[0]; - p1[1] = a2 * p1[0] + b2; - //point on the edge vector of area2 closest to v2 - p2[0] = (DotProduct(v2, dir2) - (a2 * dir2[0] + b2 * dir2[1])) / dir2[0]; - p2[1] = a2 * p2[0] + b2; - } //end if - else - { - //point on the edge vector of area2 closest to v1 - p1[0] = v3[0]; - p1[1] = v1[1]; - //point on the edge vector of area2 closest to v2 - p2[0] = v3[0]; - p2[1] = v2[1]; - } //end else - // - if (dir1[0]) - { - // - a1 = dir1[1] / dir1[0]; - b1 = v1[1] - a1 * v1[0]; - //point on the edge vector of area1 closest to v3 - p3[0] = (DotProduct(v3, dir1) - (a1 * dir1[0] + b1 * dir1[1])) / dir1[0]; - p3[1] = a1 * p3[0] + b1; - //point on the edge vector of area1 closest to v4 - p4[0] = (DotProduct(v4, dir1) - (a1 * dir1[0] + b1 * dir1[1])) / dir1[0]; - p4[1] = a1 * p4[0] + b1; - } //end if - else - { - //point on the edge vector of area1 closest to v3 - p3[0] = v1[0]; - p3[1] = v3[1]; - //point on the edge vector of area1 closest to v4 - p4[0] = v1[0]; - p4[1] = v4[1]; - } //end else - //start with zero z-coordinates - p1[2] = 0; - p2[2] = 0; - p3[2] = 0; - p4[2] = 0; - //calculate the z-coordinates from the ground planes - p1[2] = (plane2->dist - DotProduct(plane2->normal, p1)) / plane2->normal[2]; - p2[2] = (plane2->dist - DotProduct(plane2->normal, p2)) / plane2->normal[2]; - p3[2] = (plane1->dist - DotProduct(plane1->normal, p3)) / plane1->normal[2]; - p4[2] = (plane1->dist - DotProduct(plane1->normal, p4)) / plane1->normal[2]; - // - founddist = qfalse; - // - if (VectorBetweenVectors(p1, v3, v4)) - { - dist = VectorDistance(v1, p1); - if (dist > bestdist - 0.5 && dist < bestdist + 0.5) - { - dist1 = VectorDistance(beststart1, v1); - dist2 = VectorDistance(beststart2, v1); - if (dist1 > dist2) - { - if (dist1 > VectorDistance(beststart1, beststart2)) VectorCopy(v1, beststart2); - } //end if - else - { - if (dist2 > VectorDistance(beststart1, beststart2)) VectorCopy(v1, beststart1); - } //end else - dist1 = VectorDistance(bestend1, p1); - dist2 = VectorDistance(bestend2, p1); - if (dist1 > dist2) - { - if (dist1 > VectorDistance(bestend1, bestend2)) VectorCopy(p1, bestend2); - } //end if - else - { - if (dist2 > VectorDistance(bestend1, bestend2)) VectorCopy(p1, bestend1); - } //end else - } //end if - else if (dist < bestdist) - { - bestdist = dist; - VectorCopy(v1, beststart1); - VectorCopy(v1, beststart2); - VectorCopy(p1, bestend1); - VectorCopy(p1, bestend2); - } //end if - founddist = qtrue; - } //end if - if (VectorBetweenVectors(p2, v3, v4)) - { - dist = VectorDistance(v2, p2); - if (dist > bestdist - 0.5 && dist < bestdist + 0.5) - { - dist1 = VectorDistance(beststart1, v2); - dist2 = VectorDistance(beststart2, v2); - if (dist1 > dist2) - { - if (dist1 > VectorDistance(beststart1, beststart2)) VectorCopy(v2, beststart2); - } //end if - else - { - if (dist2 > VectorDistance(beststart1, beststart2)) VectorCopy(v2, beststart1); - } //end else - dist1 = VectorDistance(bestend1, p2); - dist2 = VectorDistance(bestend2, p2); - if (dist1 > dist2) - { - if (dist1 > VectorDistance(bestend1, bestend2)) VectorCopy(p2, bestend2); - } //end if - else - { - if (dist2 > VectorDistance(bestend1, bestend2)) VectorCopy(p2, bestend1); - } //end else - } //end if - else if (dist < bestdist) - { - bestdist = dist; - VectorCopy(v2, beststart1); - VectorCopy(v2, beststart2); - VectorCopy(p2, bestend1); - VectorCopy(p2, bestend2); - } //end if - founddist = qtrue; - } //end else if - if (VectorBetweenVectors(p3, v1, v2)) - { - dist = VectorDistance(v3, p3); - if (dist > bestdist - 0.5 && dist < bestdist + 0.5) - { - dist1 = VectorDistance(beststart1, p3); - dist2 = VectorDistance(beststart2, p3); - if (dist1 > dist2) - { - if (dist1 > VectorDistance(beststart1, beststart2)) VectorCopy(p3, beststart2); - } //end if - else - { - if (dist2 > VectorDistance(beststart1, beststart2)) VectorCopy(p3, beststart1); - } //end else - dist1 = VectorDistance(bestend1, v3); - dist2 = VectorDistance(bestend2, v3); - if (dist1 > dist2) - { - if (dist1 > VectorDistance(bestend1, bestend2)) VectorCopy(v3, bestend2); - } //end if - else - { - if (dist2 > VectorDistance(bestend1, bestend2)) VectorCopy(v3, bestend1); - } //end else - } //end if - else if (dist < bestdist) - { - bestdist = dist; - VectorCopy(p3, beststart1); - VectorCopy(p3, beststart2); - VectorCopy(v3, bestend1); - VectorCopy(v3, bestend2); - } //end if - founddist = qtrue; - } //end else if - if (VectorBetweenVectors(p4, v1, v2)) - { - dist = VectorDistance(v4, p4); - if (dist > bestdist - 0.5 && dist < bestdist + 0.5) - { - dist1 = VectorDistance(beststart1, p4); - dist2 = VectorDistance(beststart2, p4); - if (dist1 > dist2) - { - if (dist1 > VectorDistance(beststart1, beststart2)) VectorCopy(p4, beststart2); - } //end if - else - { - if (dist2 > VectorDistance(beststart1, beststart2)) VectorCopy(p4, beststart1); - } //end else - dist1 = VectorDistance(bestend1, v4); - dist2 = VectorDistance(bestend2, v4); - if (dist1 > dist2) - { - if (dist1 > VectorDistance(bestend1, bestend2)) VectorCopy(v4, bestend2); - } //end if - else - { - if (dist2 > VectorDistance(bestend1, bestend2)) VectorCopy(v4, bestend1); - } //end else - } //end if - else if (dist < bestdist) - { - bestdist = dist; - VectorCopy(p4, beststart1); - VectorCopy(p4, beststart2); - VectorCopy(v4, bestend1); - VectorCopy(v4, bestend2); - } //end if - founddist = qtrue; - } //end else if - //if no shortest distance was found the shortest distance - //is between one of the vertexes of edge1 and one of edge2 - if (!founddist) - { - dist = VectorDistance(v1, v3); - if (dist < bestdist) - { - bestdist = dist; - VectorCopy(v1, beststart1); - VectorCopy(v1, beststart2); - VectorCopy(v3, bestend1); - VectorCopy(v3, bestend2); - } //end if - dist = VectorDistance(v1, v4); - if (dist < bestdist) - { - bestdist = dist; - VectorCopy(v1, beststart1); - VectorCopy(v1, beststart2); - VectorCopy(v4, bestend1); - VectorCopy(v4, bestend2); - } //end if - dist = VectorDistance(v2, v3); - if (dist < bestdist) - { - bestdist = dist; - VectorCopy(v2, beststart1); - VectorCopy(v2, beststart2); - VectorCopy(v3, bestend1); - VectorCopy(v3, bestend2); - } //end if - dist = VectorDistance(v2, v4); - if (dist < bestdist) - { - bestdist = dist; - VectorCopy(v2, beststart1); - VectorCopy(v2, beststart2); - VectorCopy(v4, bestend1); - VectorCopy(v4, bestend2); - } //end if - } //end if - return bestdist; -} //end of the function AAS_ClosestEdgePoints -//=========================================================================== -// creates possible jump reachabilities between the areas -// -// The two closest points on the ground of the areas are calculated -// One of the points will be on an edge of a ground face of area1 and -// one on an edge of a ground face of area2. -// If there is a range of closest points the point in the middle of this range -// is selected. -// Between these two points there must be one or more gaps. -// If the gaps exist a potential jump is predicted. -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_Reachability_Jump(int area1num, int area2num) -{ - int i, j, k, l, face1num, face2num, edge1num, edge2num, traveltype; - int stopevent, areas[10], numareas; - float phys_jumpvel, maxjumpdistance, maxjumpheight, height, bestdist, speed; - vec_t *v1, *v2, *v3, *v4; - vec3_t beststart, beststart2, bestend, bestend2; - vec3_t teststart, testend, dir, velocity, cmdmove, up = {0, 0, 1}, sidewards; - aas_area_t *area1, *area2; - aas_face_t *face1, *face2; - aas_edge_t *edge1, *edge2; - aas_plane_t *plane1, *plane2, *plane; - aas_trace_t trace; - aas_clientmove_t move; - aas_lreachability_t *lreach; - - if (!AAS_AreaGrounded(area1num) || !AAS_AreaGrounded(area2num)) return qfalse; - //cannot jump from or to a crouch area - if (AAS_AreaCrouch(area1num) || AAS_AreaCrouch(area2num)) return qfalse; - // - area1 = &aasworld.areas[area1num]; - area2 = &aasworld.areas[area2num]; - // - phys_jumpvel = aassettings.phys_jumpvel; - //maximum distance a player can jump - maxjumpdistance = 2 * AAS_MaxJumpDistance(phys_jumpvel); - //maximum height a player can jump with the given initial z velocity - maxjumpheight = AAS_MaxJumpHeight(phys_jumpvel); - - //if the areas are not near anough in the x-y direction - for (i = 0; i < 2; i++) - { - if (area1->mins[i] > area2->maxs[i] + maxjumpdistance) return qfalse; - if (area1->maxs[i] < area2->mins[i] - maxjumpdistance) return qfalse; - } //end for - //if area2 is way to high to jump up to - if (area2->mins[2] > area1->maxs[2] + maxjumpheight) return qfalse; - // - bestdist = 999999; - // - for (i = 0; i < area1->numfaces; i++) - { - face1num = aasworld.faceindex[area1->firstface + i]; - face1 = &aasworld.faces[abs(face1num)]; - //if not a ground face - if (!(face1->faceflags & FACE_GROUND)) continue; - // - for (j = 0; j < area2->numfaces; j++) - { - face2num = aasworld.faceindex[area2->firstface + j]; - face2 = &aasworld.faces[abs(face2num)]; - //if not a ground face - if (!(face2->faceflags & FACE_GROUND)) continue; - // - for (k = 0; k < face1->numedges; k++) - { - edge1num = abs(aasworld.edgeindex[face1->firstedge + k]); - edge1 = &aasworld.edges[edge1num]; - for (l = 0; l < face2->numedges; l++) - { - edge2num = abs(aasworld.edgeindex[face2->firstedge + l]); - edge2 = &aasworld.edges[edge2num]; - //calculate the minimum distance between the two edges - v1 = aasworld.vertexes[edge1->v[0]]; - v2 = aasworld.vertexes[edge1->v[1]]; - v3 = aasworld.vertexes[edge2->v[0]]; - v4 = aasworld.vertexes[edge2->v[1]]; - //get the ground planes - plane1 = &aasworld.planes[face1->planenum]; - plane2 = &aasworld.planes[face2->planenum]; - // - bestdist = AAS_ClosestEdgePoints(v1, v2, v3, v4, plane1, plane2, - beststart, bestend, - beststart2, bestend2, bestdist); - } //end for - } //end for - } //end for - } //end for - VectorMiddle(beststart, beststart2, beststart); - VectorMiddle(bestend, bestend2, bestend); - if (bestdist > 4 && bestdist < maxjumpdistance) - { -// Log_Write("shortest distance between %d and %d is %f\r\n", area1num, area2num, bestdist); - // if very close and almost no height difference then the bot can walk - if (bestdist <= 48 && fabs(beststart[2] - bestend[2]) < 8) - { - speed = 400; - traveltype = TRAVEL_WALKOFFLEDGE; - } //end if - else if (AAS_HorizontalVelocityForJump(0, beststart, bestend, &speed)) - { - //FIXME: why multiply with 1.2??? - speed *= 1.2f; - traveltype = TRAVEL_WALKOFFLEDGE; - } //end else if - else - { - //get the horizontal speed for the jump, if it isn't possible to calculate this - //speed (the jump is not possible) then there's no jump reachability created - if (!AAS_HorizontalVelocityForJump(phys_jumpvel, beststart, bestend, &speed)) - return qfalse; - speed *= 1.05f; - traveltype = TRAVEL_JUMP; - // - //NOTE: test if the horizontal distance isn't too small - VectorSubtract(bestend, beststart, dir); - dir[2] = 0; - if (VectorLength(dir) < 10) - return qfalse; - } //end if - // - VectorSubtract(bestend, beststart, dir); - VectorNormalize(dir); - VectorMA(beststart, 1, dir, teststart); - // - VectorCopy(teststart, testend); - testend[2] -= 100; - trace = AAS_TraceClientBBox(teststart, testend, PRESENCE_NORMAL, -1); - // - if (trace.startsolid) - return qfalse; - if (trace.fraction < 1) - { - plane = &aasworld.planes[trace.planenum]; - // if the bot can stand on the surface - if (DotProduct(plane->normal, up) >= 0.7) - { - // if no lava or slime below - if (!(AAS_PointContents(trace.endpos) & (CONTENTS_LAVA|CONTENTS_SLIME))) - { - if (teststart[2] - trace.endpos[2] <= aassettings.phys_maxbarrier) - return qfalse; - } //end if - } //end if - } //end if - // - VectorMA(bestend, -1, dir, teststart); - // - VectorCopy(teststart, testend); - testend[2] -= 100; - trace = AAS_TraceClientBBox(teststart, testend, PRESENCE_NORMAL, -1); - // - if (trace.startsolid) - return qfalse; - if (trace.fraction < 1) - { - plane = &aasworld.planes[trace.planenum]; - // if the bot can stand on the surface - if (DotProduct(plane->normal, up) >= 0.7) - { - // if no lava or slime below - if (!(AAS_PointContents(trace.endpos) & (CONTENTS_LAVA|CONTENTS_SLIME))) - { - if (teststart[2] - trace.endpos[2] <= aassettings.phys_maxbarrier) - return qfalse; - } //end if - } //end if - } //end if - // - // get command movement - VectorClear(cmdmove); - if ((traveltype & TRAVELTYPE_MASK) == TRAVEL_JUMP) - cmdmove[2] = aassettings.phys_jumpvel; - else - cmdmove[2] = 0; - // - VectorSubtract(bestend, beststart, dir); - dir[2] = 0; - VectorNormalize(dir); - CrossProduct(dir, up, sidewards); - // - stopevent = SE_HITGROUND|SE_ENTERWATER|SE_ENTERSLIME|SE_ENTERLAVA|SE_HITGROUNDDAMAGE; - if (!AAS_AreaClusterPortal(area1num) && !AAS_AreaClusterPortal(area2num)) - stopevent |= SE_TOUCHCLUSTERPORTAL; - // - for (i = 0; i < 3; i++) - { - // - if (i == 1) - VectorAdd(testend, sidewards, testend); - else if (i == 2) - VectorSubtract(bestend, sidewards, testend); - else - VectorCopy(bestend, testend); - VectorSubtract(testend, beststart, dir); - dir[2] = 0; - VectorNormalize(dir); - VectorScale(dir, speed, velocity); - // - AAS_PredictClientMovement(&move, -1, beststart, PRESENCE_NORMAL, qtrue, - velocity, cmdmove, 3, 30, 0.1f, - stopevent, 0, qfalse); - // if prediction time wasn't enough to fully predict the movement - if (move.frames >= 30) - return qfalse; - // don't enter slime or lava and don't fall from too high - if (move.stopevent & (SE_ENTERSLIME|SE_ENTERLAVA)) - return qfalse; - // never jump or fall through a cluster portal - if (move.stopevent & SE_TOUCHCLUSTERPORTAL) - return qfalse; - //the end position should be in area2, also test a little bit back - //because the predicted jump could have rushed through the area - VectorMA(move.endpos, -64, dir, teststart); - teststart[2] += 1; - numareas = AAS_TraceAreas(move.endpos, teststart, areas, NULL, sizeof(areas) / sizeof(int)); - for (j = 0; j < numareas; j++) - { - if (areas[j] == area2num) - break; - } //end for - if (j < numareas) - break; - } - if (i >= 3) - return qfalse; - // -#ifdef REACH_DEBUG - //create the reachability - Log_Write("jump reachability between %d and %d\r\n", area1num, area2num); -#endif //REACH_DEBUG - //create a new reachability link - lreach = AAS_AllocReachability(); - if (!lreach) return qfalse; - lreach->areanum = area2num; - lreach->facenum = 0; - lreach->edgenum = 0; - VectorCopy(beststart, lreach->start); - VectorCopy(bestend, lreach->end); - lreach->traveltype = traveltype; - - VectorSubtract(bestend, beststart, dir); - height = dir[2]; - dir[2] = 0; - if ((traveltype & TRAVELTYPE_MASK) == TRAVEL_WALKOFFLEDGE && height > VectorLength(dir)) - { - lreach->traveltime = aassettings.rs_startwalkoffledge + height * 50 / aassettings.phys_gravity; - } - else - { - lreach->traveltime = aassettings.rs_startjump + VectorDistance(bestend, beststart) * 240 / aassettings.phys_maxwalkvelocity; - } //end if - // - if (!AAS_AreaJumpPad(area2num)) - { - if (AAS_FallDelta(beststart[2] - bestend[2]) > aassettings.phys_falldelta5) - { - lreach->traveltime += aassettings.rs_falldamage5; - } //end if - else if (AAS_FallDelta(beststart[2] - bestend[2]) > aassettings.phys_falldelta10) - { - lreach->traveltime += aassettings.rs_falldamage10; - } //end if - } //end if - lreach->next = areareachability[area1num]; - areareachability[area1num] = lreach; - // - if ((traveltype & TRAVELTYPE_MASK) == TRAVEL_JUMP) - reach_jump++; - else - reach_walkoffledge++; - } //end if - return qfalse; -} //end of the function AAS_Reachability_Jump -//=========================================================================== -// create a possible ladder reachability from area1 to area2 -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_Reachability_Ladder(int area1num, int area2num) -{ - int i, j, k, l, edge1num, edge2num, sharededgenum, lowestedgenum; - int face1num, face2num, ladderface1num, ladderface2num; - int ladderface1vertical, ladderface2vertical, firstv; - float face1area, face2area, bestface1area, bestface2area; - float phys_jumpvel, maxjumpheight; - vec3_t area1point, area2point, v1, v2, up = {0, 0, 1}; - vec3_t mid, lowestpoint, start, end, sharededgevec, dir; - aas_area_t *area1, *area2; - aas_face_t *face1, *face2, *ladderface1, *ladderface2; - aas_plane_t *plane1, *plane2; - aas_edge_t *sharededge, *edge1; - aas_lreachability_t *lreach; - aas_trace_t trace; - - if (!AAS_AreaLadder(area1num) || !AAS_AreaLadder(area2num)) return qfalse; - // - phys_jumpvel = aassettings.phys_jumpvel; - //maximum height a player can jump with the given initial z velocity - maxjumpheight = AAS_MaxJumpHeight(phys_jumpvel); - - area1 = &aasworld.areas[area1num]; - area2 = &aasworld.areas[area2num]; - // - ladderface1 = NULL; - ladderface2 = NULL; - ladderface1num = 0; //make compiler happy - ladderface2num = 0; //make compiler happy - bestface1area = -9999; - bestface2area = -9999; - sharededgenum = 0; //make compiler happy - lowestedgenum = 0; //make compiler happy - // - for (i = 0; i < area1->numfaces; i++) - { - face1num = aasworld.faceindex[area1->firstface + i]; - face1 = &aasworld.faces[abs(face1num)]; - //if not a ladder face - if (!(face1->faceflags & FACE_LADDER)) continue; - // - for (j = 0; j < area2->numfaces; j++) - { - face2num = aasworld.faceindex[area2->firstface + j]; - face2 = &aasworld.faces[abs(face2num)]; - //if not a ladder face - if (!(face2->faceflags & FACE_LADDER)) continue; - //check if the faces share an edge - for (k = 0; k < face1->numedges; k++) - { - edge1num = aasworld.edgeindex[face1->firstedge + k]; - for (l = 0; l < face2->numedges; l++) - { - edge2num = aasworld.edgeindex[face2->firstedge + l]; - if (abs(edge1num) == abs(edge2num)) - { - //get the face with the largest area - face1area = AAS_FaceArea(face1); - face2area = AAS_FaceArea(face2); - if (face1area > bestface1area && face2area > bestface2area) - { - bestface1area = face1area; - bestface2area = face2area; - ladderface1 = face1; - ladderface2 = face2; - ladderface1num = face1num; - ladderface2num = face2num; - sharededgenum = edge1num; - } //end if - break; - } //end if - } //end for - if (l != face2->numedges) break; - } //end for - } //end for - } //end for - // - if (ladderface1 && ladderface2) - { - //get the middle of the shared edge - sharededge = &aasworld.edges[abs(sharededgenum)]; - firstv = sharededgenum < 0; - // - VectorCopy(aasworld.vertexes[sharededge->v[firstv]], v1); - VectorCopy(aasworld.vertexes[sharededge->v[!firstv]], v2); - VectorAdd(v1, v2, area1point); - VectorScale(area1point, 0.5, area1point); - VectorCopy(area1point, area2point); - // - //if the face plane in area 1 is pretty much vertical - plane1 = &aasworld.planes[ladderface1->planenum ^ (ladderface1num < 0)]; - plane2 = &aasworld.planes[ladderface2->planenum ^ (ladderface2num < 0)]; - // - //get the points really into the areas - VectorSubtract(v2, v1, sharededgevec); - CrossProduct(plane1->normal, sharededgevec, dir); - VectorNormalize(dir); - //NOTE: 32 because that's larger than 16 (bot bbox x,y) - VectorMA(area1point, -32, dir, area1point); - VectorMA(area2point, 32, dir, area2point); - // - ladderface1vertical = abs(DotProduct(plane1->normal, up)) < 0.1; - ladderface2vertical = abs(DotProduct(plane2->normal, up)) < 0.1; - //there's only reachability between vertical ladder faces - if (!ladderface1vertical && !ladderface2vertical) return qfalse; - //if both vertical ladder faces - if (ladderface1vertical && ladderface2vertical - //and the ladder faces do not make a sharp corner - && DotProduct(plane1->normal, plane2->normal) > 0.7 - //and the shared edge is not too vertical - && abs(DotProduct(sharededgevec, up)) < 0.7) - { - //create a new reachability link - lreach = AAS_AllocReachability(); - if (!lreach) return qfalse; - lreach->areanum = area2num; - lreach->facenum = ladderface1num; - lreach->edgenum = abs(sharededgenum); - VectorCopy(area1point, lreach->start); - //VectorCopy(area2point, lreach->end); - VectorMA(area2point, -3, plane1->normal, lreach->end); - lreach->traveltype = TRAVEL_LADDER; - lreach->traveltime = 10; - lreach->next = areareachability[area1num]; - areareachability[area1num] = lreach; - // - reach_ladder++; - //create a new reachability link - lreach = AAS_AllocReachability(); - if (!lreach) return qfalse; - lreach->areanum = area1num; - lreach->facenum = ladderface2num; - lreach->edgenum = abs(sharededgenum); - VectorCopy(area2point, lreach->start); - //VectorCopy(area1point, lreach->end); - VectorMA(area1point, -3, plane1->normal, lreach->end); - lreach->traveltype = TRAVEL_LADDER; - lreach->traveltime = 10; - lreach->next = areareachability[area2num]; - areareachability[area2num] = lreach; - // - reach_ladder++; - // - return qtrue; - } //end if - //if the second ladder face is also a ground face - //create ladder end (just ladder) reachability and - //walk off a ladder (ledge) reachability - if (ladderface1vertical && (ladderface2->faceflags & FACE_GROUND)) - { - //create a new reachability link - lreach = AAS_AllocReachability(); - if (!lreach) return qfalse; - lreach->areanum = area2num; - lreach->facenum = ladderface1num; - lreach->edgenum = abs(sharededgenum); - VectorCopy(area1point, lreach->start); - VectorCopy(area2point, lreach->end); - lreach->end[2] += 16; - VectorMA(lreach->end, -15, plane1->normal, lreach->end); - lreach->traveltype = TRAVEL_LADDER; - lreach->traveltime = 10; - lreach->next = areareachability[area1num]; - areareachability[area1num] = lreach; - // - reach_ladder++; - //create a new reachability link - lreach = AAS_AllocReachability(); - if (!lreach) return qfalse; - lreach->areanum = area1num; - lreach->facenum = ladderface2num; - lreach->edgenum = abs(sharededgenum); - VectorCopy(area2point, lreach->start); - VectorCopy(area1point, lreach->end); - lreach->traveltype = TRAVEL_WALKOFFLEDGE; - lreach->traveltime = 10; - lreach->next = areareachability[area2num]; - areareachability[area2num] = lreach; - // - reach_walkoffledge++; - // - return qtrue; - } //end if - // - if (ladderface1vertical) - { - //find lowest edge of the ladder face - lowestpoint[2] = 99999; - for (i = 0; i < ladderface1->numedges; i++) - { - edge1num = abs(aasworld.edgeindex[ladderface1->firstedge + i]); - edge1 = &aasworld.edges[edge1num]; - // - VectorCopy(aasworld.vertexes[edge1->v[0]], v1); - VectorCopy(aasworld.vertexes[edge1->v[1]], v2); - // - VectorAdd(v1, v2, mid); - VectorScale(mid, 0.5, mid); - // - if (mid[2] < lowestpoint[2]) - { - VectorCopy(mid, lowestpoint); - lowestedgenum = edge1num; - } //end if - } //end for - // - plane1 = &aasworld.planes[ladderface1->planenum]; - //trace down in the middle of this edge - VectorMA(lowestpoint, 5, plane1->normal, start); - VectorCopy(start, end); - start[2] += 5; - end[2] -= 100; - //trace without entity collision - trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, -1); - // - // -#ifdef REACH_DEBUG - if (trace.startsolid) - { - Log_Write("trace from area %d started in solid\r\n", area1num); - } //end if -#endif //REACH_DEBUG - // - trace.endpos[2] += 1; - area2num = AAS_PointAreaNum(trace.endpos); - // - area2 = &aasworld.areas[area2num]; - for (i = 0; i < area2->numfaces; i++) - { - face2num = aasworld.faceindex[area2->firstface + i]; - face2 = &aasworld.faces[abs(face2num)]; - // - if (face2->faceflags & FACE_LADDER) - { - plane2 = &aasworld.planes[face2->planenum]; - if (abs(DotProduct(plane2->normal, up)) < 0.1) break; - } //end if - } //end for - //if from another area without vertical ladder faces - if (i >= area2->numfaces && area2num != area1num && - //the reachabilities shouldn't exist already - !AAS_ReachabilityExists(area1num, area2num) && - !AAS_ReachabilityExists(area2num, area1num)) - { - //if the height is jumpable - if (start[2] - trace.endpos[2] < maxjumpheight) - { - //create a new reachability link - lreach = AAS_AllocReachability(); - if (!lreach) return qfalse; - lreach->areanum = area2num; - lreach->facenum = ladderface1num; - lreach->edgenum = lowestedgenum; - VectorCopy(lowestpoint, lreach->start); - VectorCopy(trace.endpos, lreach->end); - lreach->traveltype = TRAVEL_LADDER; - lreach->traveltime = 10; - lreach->next = areareachability[area1num]; - areareachability[area1num] = lreach; - // - reach_ladder++; - //create a new reachability link - lreach = AAS_AllocReachability(); - if (!lreach) return qfalse; - lreach->areanum = area1num; - lreach->facenum = ladderface1num; - lreach->edgenum = lowestedgenum; - VectorCopy(trace.endpos, lreach->start); - //get the end point a little bit into the ladder - VectorMA(lowestpoint, -5, plane1->normal, lreach->end); - //get the end point a little higher - lreach->end[2] += 10; - lreach->traveltype = TRAVEL_JUMP; - lreach->traveltime = 10; - lreach->next = areareachability[area2num]; - areareachability[area2num] = lreach; - // - reach_jump++; - // - return qtrue; -#ifdef REACH_DEBUG - Log_Write("jump up to ladder reach between %d and %d\r\n", area2num, area1num); -#endif //REACH_DEBUG - } //end if -#ifdef REACH_DEBUG - else Log_Write("jump too high between area %d and %d\r\n", area2num, area1num); -#endif //REACH_DEBUG - } //end if - /*//if slime or lava below the ladder - //try jump reachability from far towards the ladder - if (aasworld.areasettings[area2num].contents & (AREACONTENTS_SLIME - | AREACONTENTS_LAVA)) - { - for (i = 20; i <= 120; i += 20) - { - //trace down in the middle of this edge - VectorMA(lowestpoint, i, plane1->normal, start); - VectorCopy(start, end); - start[2] += 5; - end[2] -= 100; - //trace without entity collision - trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, -1); - // - if (trace.startsolid) break; - trace.endpos[2] += 1; - area2num = AAS_PointAreaNum(trace.endpos); - if (area2num == area1num) continue; - // - if (start[2] - trace.endpos[2] > maxjumpheight) continue; - if (aasworld.areasettings[area2num].contents & (AREACONTENTS_SLIME - | AREACONTENTS_LAVA)) continue; - // - //create a new reachability link - lreach = AAS_AllocReachability(); - if (!lreach) return qfalse; - lreach->areanum = area1num; - lreach->facenum = ladderface1num; - lreach->edgenum = lowestedgenum; - VectorCopy(trace.endpos, lreach->start); - VectorCopy(lowestpoint, lreach->end); - lreach->end[2] += 5; - lreach->traveltype = TRAVEL_JUMP; - lreach->traveltime = 10; - lreach->next = areareachability[area2num]; - areareachability[area2num] = lreach; - // - reach_jump++; - // - Log_Write("jump far to ladder reach between %d and %d\r\n", area2num, area1num); - // - break; - } //end for - } //end if*/ - } //end if - } //end if - return qfalse; -} //end of the function AAS_Reachability_Ladder -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_TravelFlagsForTeam(int ent) -{ - int notteam; - - if (!AAS_IntForBSPEpairKey(ent, "bot_notteam", ¬team)) - return 0; - if (notteam == 1) - return TRAVELFLAG_NOTTEAM1; - if (notteam == 2) - return TRAVELFLAG_NOTTEAM2; - return 0; -} //end of the function AAS_TravelFlagsForTeam -//=========================================================================== -// create possible teleporter reachabilities -// this is very game dependent.... :( -// -// classname = trigger_multiple or trigger_teleport -// target = "t1" -// -// classname = target_teleporter -// targetname = "t1" -// target = "t2" -// -// classname = misc_teleporter_dest -// targetname = "t2" -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_Reachability_Teleport(void) -{ - int area1num, area2num; - char target[MAX_EPAIRKEY], targetname[MAX_EPAIRKEY]; - char classname[MAX_EPAIRKEY], model[MAX_EPAIRKEY]; - int ent, dest; - float angle; - vec3_t origin, destorigin, mins, maxs, end, angles; - vec3_t mid, velocity, cmdmove; - aas_lreachability_t *lreach; - aas_clientmove_t move; - aas_trace_t trace; - aas_link_t *areas, *link; - - for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) - { - if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; - if (!strcmp(classname, "trigger_multiple")) - { - AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY); -//#ifdef REACH_DEBUG - botimport.Print(PRT_MESSAGE, "trigger_multiple model = \"%s\"\n", model); -//#endif REACH_DEBUG - VectorClear(angles); - AAS_BSPModelMinsMaxsOrigin(atoi(model+1), angles, mins, maxs, origin); - // - if (!AAS_ValueForBSPEpairKey(ent, "target", target, MAX_EPAIRKEY)) - { - botimport.Print(PRT_ERROR, "trigger_multiple at %1.0f %1.0f %1.0f without target\n", - origin[0], origin[1], origin[2]); - continue; - } //end if - for (dest = AAS_NextBSPEntity(0); dest; dest = AAS_NextBSPEntity(dest)) - { - if (!AAS_ValueForBSPEpairKey(dest, "classname", classname, MAX_EPAIRKEY)) continue; - if (!strcmp(classname, "target_teleporter")) - { - if (!AAS_ValueForBSPEpairKey(dest, "targetname", targetname, MAX_EPAIRKEY)) continue; - if (!strcmp(targetname, target)) - { - break; - } //end if - } //end if - } //end for - if (!dest) - { - continue; - } //end if - if (!AAS_ValueForBSPEpairKey(dest, "target", target, MAX_EPAIRKEY)) - { - botimport.Print(PRT_ERROR, "target_teleporter without target\n"); - continue; - } //end if - } //end else - else if (!strcmp(classname, "trigger_teleport")) - { - AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY); -//#ifdef REACH_DEBUG - botimport.Print(PRT_MESSAGE, "trigger_teleport model = \"%s\"\n", model); -//#endif REACH_DEBUG - VectorClear(angles); - AAS_BSPModelMinsMaxsOrigin(atoi(model+1), angles, mins, maxs, origin); - // - if (!AAS_ValueForBSPEpairKey(ent, "target", target, MAX_EPAIRKEY)) - { - botimport.Print(PRT_ERROR, "trigger_teleport at %1.0f %1.0f %1.0f without target\n", - origin[0], origin[1], origin[2]); - continue; - } //end if - } //end if - else - { - continue; - } //end else - // - for (dest = AAS_NextBSPEntity(0); dest; dest = AAS_NextBSPEntity(dest)) - { - //classname should be misc_teleporter_dest - //but I've also seen target_position and actually any - //entity could be used... burp - if (AAS_ValueForBSPEpairKey(dest, "targetname", targetname, MAX_EPAIRKEY)) - { - if (!strcmp(targetname, target)) - { - break; - } //end if - } //end if - } //end for - if (!dest) - { - botimport.Print(PRT_ERROR, "teleporter without misc_teleporter_dest (%s)\n", target); - continue; - } //end if - if (!AAS_VectorForBSPEpairKey(dest, "origin", destorigin)) - { - botimport.Print(PRT_ERROR, "teleporter destination (%s) without origin\n", target); - continue; - } //end if - // - area2num = AAS_PointAreaNum(destorigin); - //if not teleported into a teleporter or into a jumppad - if (!AAS_AreaTeleporter(area2num) && !AAS_AreaJumpPad(area2num)) - { - VectorCopy(destorigin, end); - end[2] -= 64; - trace = AAS_TraceClientBBox(destorigin, end, PRESENCE_CROUCH, -1); - if (trace.startsolid) - { - botimport.Print(PRT_ERROR, "teleporter destination (%s) in solid\n", target); - continue; - } //end if - area2num = AAS_PointAreaNum(trace.endpos); - // - /* - if (!AAS_AreaTeleporter(area2num) && - !AAS_AreaJumpPad(area2num) && - !AAS_AreaGrounded(area2num)) - { - VectorCopy(trace.endpos, destorigin); - } - else*/ - { - //predict where you'll end up - AAS_FloatForBSPEpairKey(dest, "angle", &angle); - if (angle) - { - VectorSet(angles, 0, angle, 0); - AngleVectors(angles, velocity, NULL, NULL); - VectorScale(velocity, 400, velocity); - } //end if - else - { - VectorClear(velocity); - } //end else - VectorClear(cmdmove); - AAS_PredictClientMovement(&move, -1, destorigin, PRESENCE_NORMAL, qfalse, - velocity, cmdmove, 0, 30, 0.1f, - SE_HITGROUND|SE_ENTERWATER|SE_ENTERSLIME| - SE_ENTERLAVA|SE_HITGROUNDDAMAGE|SE_TOUCHJUMPPAD|SE_TOUCHTELEPORTER, 0, qfalse); //qtrue); - area2num = AAS_PointAreaNum(move.endpos); - if (move.stopevent & (SE_ENTERSLIME|SE_ENTERLAVA)) - { - botimport.Print(PRT_WARNING, "teleported into slime or lava at dest %s\n", target); - } //end if - VectorCopy(move.endpos, destorigin); - } //end else - } //end if - // - //botimport.Print(PRT_MESSAGE, "teleporter brush origin at %f %f %f\n", origin[0], origin[1], origin[2]); - //botimport.Print(PRT_MESSAGE, "teleporter brush mins = %f %f %f\n", mins[0], mins[1], mins[2]); - //botimport.Print(PRT_MESSAGE, "teleporter brush maxs = %f %f %f\n", maxs[0], maxs[1], maxs[2]); - VectorAdd(origin, mins, mins); - VectorAdd(origin, maxs, maxs); - // - VectorAdd(mins, maxs, mid); - VectorScale(mid, 0.5, mid); - //link an invalid (-1) entity - areas = AAS_LinkEntityClientBBox(mins, maxs, -1, PRESENCE_CROUCH); - if (!areas) botimport.Print(PRT_MESSAGE, "trigger_multiple not in any area\n"); - // - for (link = areas; link; link = link->next_area) - { - //if (!AAS_AreaGrounded(link->areanum)) continue; - if (!AAS_AreaTeleporter(link->areanum)) continue; - // - area1num = link->areanum; - //create a new reachability link - lreach = AAS_AllocReachability(); - if (!lreach) break; - lreach->areanum = area2num; - lreach->facenum = 0; - lreach->edgenum = 0; - VectorCopy(mid, lreach->start); - VectorCopy(destorigin, lreach->end); - lreach->traveltype = TRAVEL_TELEPORT; - lreach->traveltype |= AAS_TravelFlagsForTeam(ent); - lreach->traveltime = aassettings.rs_teleport; - lreach->next = areareachability[area1num]; - areareachability[area1num] = lreach; - // - reach_teleport++; - } //end for - //unlink the invalid entity - AAS_UnlinkFromAreas(areas); - } //end for -} //end of the function AAS_Reachability_Teleport -//=========================================================================== -// create possible elevator (func_plat) reachabilities -// this is very game dependent.... :( -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_Reachability_Elevator(void) -{ - int area1num, area2num, modelnum, i, j, k, l, n, p; - float lip, height, speed; - char model[MAX_EPAIRKEY], classname[MAX_EPAIRKEY]; - int ent; - vec3_t mins, maxs, origin, angles = {0, 0, 0}; - vec3_t pos1, pos2, mids, platbottom, plattop; - vec3_t bottomorg, toporg, start, end, dir; - vec_t xvals[8], yvals[8], xvals_top[8], yvals_top[8]; - aas_lreachability_t *lreach; - aas_trace_t trace; - -#ifdef REACH_DEBUG - Log_Write("AAS_Reachability_Elevator\r\n"); -#endif //REACH_DEBUG - for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) - { - if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; - if (!strcmp(classname, "func_plat")) - { -#ifdef REACH_DEBUG - Log_Write("found func plat\r\n"); -#endif //REACH_DEBUG - if (!AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY)) - { - botimport.Print(PRT_ERROR, "func_plat without model\n"); - continue; - } //end if - //get the model number, and skip the leading * - modelnum = atoi(model+1); - if (modelnum <= 0) - { - botimport.Print(PRT_ERROR, "func_plat with invalid model number\n"); - continue; - } //end if - //get the mins, maxs and origin of the model - //NOTE: the origin is usually (0,0,0) and the mins and maxs - // are the absolute mins and maxs - AAS_BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, origin); - // - AAS_VectorForBSPEpairKey(ent, "origin", origin); - //pos1 is the top position, pos2 is the bottom - VectorCopy(origin, pos1); - VectorCopy(origin, pos2); - //get the lip of the plat - AAS_FloatForBSPEpairKey(ent, "lip", &lip); - if (!lip) lip = 8; - //get the movement height of the plat - AAS_FloatForBSPEpairKey(ent, "height", &height); - if (!height) height = (maxs[2] - mins[2]) - lip; - //get the speed of the plat - AAS_FloatForBSPEpairKey(ent, "speed", &speed); - if (!speed) speed = 200; - //get bottom position below pos1 - pos2[2] -= height; - // - //get a point just above the plat in the bottom position - VectorAdd(mins, maxs, mids); - VectorMA(pos2, 0.5, mids, platbottom); - platbottom[2] = maxs[2] - (pos1[2] - pos2[2]) + 2; - //get a point just above the plat in the top position - VectorAdd(mins, maxs, mids); - VectorMA(pos2, 0.5, mids, plattop); - plattop[2] = maxs[2] + 2; - // - /*if (!area1num) - { - Log_Write("no grounded area near plat bottom\r\n"); - continue; - } //end if*/ - //get the mins and maxs a little larger - for (i = 0; i < 3; i++) - { - mins[i] -= 1; - maxs[i] += 1; - } //end for - // - //botimport.Print(PRT_MESSAGE, "platbottom[2] = %1.1f plattop[2] = %1.1f\n", platbottom[2], plattop[2]); - // - VectorAdd(mins, maxs, mids); - VectorScale(mids, 0.5, mids); - // - xvals[0] = mins[0]; xvals[1] = mids[0]; xvals[2] = maxs[0]; xvals[3] = mids[0]; - yvals[0] = mids[1]; yvals[1] = maxs[1]; yvals[2] = mids[1]; yvals[3] = mins[1]; - // - xvals[4] = mins[0]; xvals[5] = maxs[0]; xvals[6] = maxs[0]; xvals[7] = mins[0]; - yvals[4] = maxs[1]; yvals[5] = maxs[1]; yvals[6] = mins[1]; yvals[7] = mins[1]; - //find adjacent areas around the bottom of the plat - for (i = 0; i < 9; i++) - { - if (i < 8) //check at the sides of the plat - { - bottomorg[0] = origin[0] + xvals[i]; - bottomorg[1] = origin[1] + yvals[i]; - bottomorg[2] = platbottom[2] + 16; - //get a grounded or swim area near the plat in the bottom position - area1num = AAS_PointAreaNum(bottomorg); - for (k = 0; k < 16; k++) - { - if (area1num) - { - if (AAS_AreaGrounded(area1num) || AAS_AreaSwim(area1num)) break; - } //end if - bottomorg[2] += 4; - area1num = AAS_PointAreaNum(bottomorg); - } //end if - //if in solid - if (k >= 16) - { - continue; - } //end if - } //end if - else //at the middle of the plat - { - VectorCopy(plattop, bottomorg); - bottomorg[2] += 24; - area1num = AAS_PointAreaNum(bottomorg); - if (!area1num) continue; - VectorCopy(platbottom, bottomorg); - bottomorg[2] += 24; - } //end else - //look at adjacent areas around the top of the plat - //make larger steps to outside the plat everytime - for (n = 0; n < 3; n++) - { - for (k = 0; k < 3; k++) - { - mins[k] -= 4; - maxs[k] += 4; - } //end for - xvals_top[0] = mins[0]; xvals_top[1] = mids[0]; xvals_top[2] = maxs[0]; xvals_top[3] = mids[0]; - yvals_top[0] = mids[1]; yvals_top[1] = maxs[1]; yvals_top[2] = mids[1]; yvals_top[3] = mins[1]; - // - xvals_top[4] = mins[0]; xvals_top[5] = maxs[0]; xvals_top[6] = maxs[0]; xvals_top[7] = mins[0]; - yvals_top[4] = maxs[1]; yvals_top[5] = maxs[1]; yvals_top[6] = mins[1]; yvals_top[7] = mins[1]; - // - for (j = 0; j < 8; j++) - { - toporg[0] = origin[0] + xvals_top[j]; - toporg[1] = origin[1] + yvals_top[j]; - toporg[2] = plattop[2] + 16; - //get a grounded or swim area near the plat in the top position - area2num = AAS_PointAreaNum(toporg); - for (l = 0; l < 16; l++) - { - if (area2num) - { - if (AAS_AreaGrounded(area2num) || AAS_AreaSwim(area2num)) - { - VectorCopy(plattop, start); - start[2] += 32; - VectorCopy(toporg, end); - end[2] += 1; - trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, -1); - if (trace.fraction >= 1) break; - } //end if - } //end if - toporg[2] += 4; - area2num = AAS_PointAreaNum(toporg); - } //end if - //if in solid - if (l >= 16) continue; - //never create a reachability in the same area - if (area2num == area1num) continue; - //if the area isn't grounded - if (!AAS_AreaGrounded(area2num)) continue; - //if there already exists reachability between the areas - if (AAS_ReachabilityExists(area1num, area2num)) continue; - //if the reachability start is within the elevator bounding box - VectorSubtract(bottomorg, platbottom, dir); - VectorNormalize(dir); - dir[0] = bottomorg[0] + 24 * dir[0]; - dir[1] = bottomorg[1] + 24 * dir[1]; - dir[2] = bottomorg[2]; - // - for (p = 0; p < 3; p++) - if (dir[p] < origin[p] + mins[p] || dir[p] > origin[p] + maxs[p]) break; - if (p >= 3) continue; - //create a new reachability link - lreach = AAS_AllocReachability(); - if (!lreach) continue; - lreach->areanum = area2num; - //the facenum is the model number - lreach->facenum = modelnum; - //the edgenum is the height - lreach->edgenum = (int) height; - // - VectorCopy(dir, lreach->start); - VectorCopy(toporg, lreach->end); - lreach->traveltype = TRAVEL_ELEVATOR; - lreach->traveltype |= AAS_TravelFlagsForTeam(ent); - lreach->traveltime = aassettings.rs_startelevator + height * 100 / speed; - lreach->next = areareachability[area1num]; - areareachability[area1num] = lreach; - //don't go any further to the outside - n = 9999; - // -#ifdef REACH_DEBUG - Log_Write("elevator reach from %d to %d\r\n", area1num, area2num); -#endif //REACH_DEBUG - // - reach_elevator++; - } //end for - } //end for - } //end for - } //end if - } //end for -} //end of the function AAS_Reachability_Elevator -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -aas_lreachability_t *AAS_FindFaceReachabilities(vec3_t *facepoints, int numpoints, aas_plane_t *plane, int towardsface) -{ - int i, j, k, l; - int facenum, edgenum, bestfacenum; - float *v1, *v2, *v3, *v4; - float bestdist, speed, hordist, dist; - vec3_t beststart, beststart2, bestend, bestend2, tmp, hordir, testpoint; - aas_lreachability_t *lreach, *lreachabilities; - aas_area_t *area; - aas_face_t *face; - aas_edge_t *edge; - aas_plane_t *faceplane, *bestfaceplane; - - // - lreachabilities = NULL; - bestfacenum = 0; - bestfaceplane = NULL; - // - for (i = 1; i < aasworld.numareas; i++) - { - area = &aasworld.areas[i]; - // get the shortest distance between one of the func_bob start edges and - // one of the face edges of area1 - bestdist = 999999; - for (j = 0; j < area->numfaces; j++) - { - facenum = aasworld.faceindex[area->firstface + j]; - face = &aasworld.faces[abs(facenum)]; - //if not a ground face - if (!(face->faceflags & FACE_GROUND)) continue; - //get the ground planes - faceplane = &aasworld.planes[face->planenum]; - // - for (k = 0; k < face->numedges; k++) - { - edgenum = abs(aasworld.edgeindex[face->firstedge + k]); - edge = &aasworld.edges[edgenum]; - //calculate the minimum distance between the two edges - v1 = aasworld.vertexes[edge->v[0]]; - v2 = aasworld.vertexes[edge->v[1]]; - // - for (l = 0; l < numpoints; l++) - { - v3 = facepoints[l]; - v4 = facepoints[(l+1) % numpoints]; - dist = AAS_ClosestEdgePoints(v1, v2, v3, v4, faceplane, plane, - beststart, bestend, - beststart2, bestend2, bestdist); - if (dist < bestdist) - { - bestfacenum = facenum; - bestfaceplane = faceplane; - bestdist = dist; - } //end if - } //end for - } //end for - } //end for - // - if (bestdist > 192) continue; - // - VectorMiddle(beststart, beststart2, beststart); - VectorMiddle(bestend, bestend2, bestend); - // - if (!towardsface) - { - VectorCopy(beststart, tmp); - VectorCopy(bestend, beststart); - VectorCopy(tmp, bestend); - } //end if - // - VectorSubtract(bestend, beststart, hordir); - hordir[2] = 0; - hordist = VectorLength(hordir); - // - if (hordist > 2 * AAS_MaxJumpDistance(aassettings.phys_jumpvel)) continue; - //the end point should not be significantly higher than the start point - if (bestend[2] - 32 > beststart[2]) continue; - //don't fall down too far - if (bestend[2] < beststart[2] - 128) continue; - //the distance should not be too far - if (hordist > 32) - { - //check for walk off ledge - if (!AAS_HorizontalVelocityForJump(0, beststart, bestend, &speed)) continue; - } //end if - // - beststart[2] += 1; - bestend[2] += 1; - // - if (towardsface) VectorCopy(bestend, testpoint); - else VectorCopy(beststart, testpoint); - testpoint[2] = 0; - testpoint[2] = (bestfaceplane->dist - DotProduct(bestfaceplane->normal, testpoint)) / bestfaceplane->normal[2]; - // - if (!AAS_PointInsideFace(bestfacenum, testpoint, 0.1f)) - { - //if the faces are not overlapping then only go down - if (bestend[2] - 16 > beststart[2]) continue; - } //end if - lreach = AAS_AllocReachability(); - if (!lreach) return lreachabilities; - lreach->areanum = i; - lreach->facenum = 0; - lreach->edgenum = 0; - VectorCopy(beststart, lreach->start); - VectorCopy(bestend, lreach->end); - lreach->traveltype = 0; - lreach->traveltime = 0; - lreach->next = lreachabilities; - lreachabilities = lreach; -#ifndef BSPC - if (towardsface) AAS_PermanentLine(lreach->start, lreach->end, 1); - else AAS_PermanentLine(lreach->start, lreach->end, 2); -#endif - } //end for - return lreachabilities; -} //end of the function AAS_FindFaceReachabilities -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_Reachability_FuncBobbing(void) -{ - int ent, spawnflags, modelnum, axis; - int i, numareas, areas[10]; - char classname[MAX_EPAIRKEY], model[MAX_EPAIRKEY]; - vec3_t origin, move_end, move_start, move_start_top, move_end_top; - vec3_t mins, maxs, angles = {0, 0, 0}; - vec3_t start_edgeverts[4], end_edgeverts[4], mid; - vec3_t org, start, end, dir, points[10]; - float height; - aas_plane_t start_plane, end_plane; - aas_lreachability_t *startreach, *endreach, *nextstartreach, *nextendreach, *lreach; - aas_lreachability_t *firststartreach, *firstendreach; - - for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) - { - if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; - if (strcmp(classname, "func_bobbing")) continue; - AAS_FloatForBSPEpairKey(ent, "height", &height); - if (!height) height = 32; - // - if (!AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY)) - { - botimport.Print(PRT_ERROR, "func_bobbing without model\n"); - continue; - } //end if - //get the model number, and skip the leading * - modelnum = atoi(model+1); - if (modelnum <= 0) - { - botimport.Print(PRT_ERROR, "func_bobbing with invalid model number\n"); - continue; - } //end if - //if the entity has an origin set then use it - if (!AAS_VectorForBSPEpairKey(ent, "origin", origin)) - VectorSet(origin, 0, 0, 0); - // - AAS_BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, NULL); - // - VectorAdd(mins, origin, mins); - VectorAdd(maxs, origin, maxs); - // - VectorAdd(mins, maxs, mid); - VectorScale(mid, 0.5, mid); - VectorCopy(mid, origin); - // - VectorCopy(origin, move_end); - VectorCopy(origin, move_start); - // - AAS_IntForBSPEpairKey(ent, "spawnflags", &spawnflags); - // set the axis of bobbing - if (spawnflags & 1) axis = 0; - else if (spawnflags & 2) axis = 1; - else axis = 2; - // - move_start[axis] -= height; - move_end[axis] += height; - // - Log_Write("funcbob model %d, start = {%1.1f, %1.1f, %1.1f} end = {%1.1f, %1.1f, %1.1f}\n", - modelnum, move_start[0], move_start[1], move_start[2], move_end[0], move_end[1], move_end[2]); - // -#ifndef BSPC - /* - AAS_DrawPermanentCross(move_start, 4, 1); - AAS_DrawPermanentCross(move_end, 4, 2); - */ -#endif - // - for (i = 0; i < 4; i++) - { - VectorCopy(move_start, start_edgeverts[i]); - start_edgeverts[i][2] += maxs[2] - mid[2]; //+ bbox maxs z - start_edgeverts[i][2] += 24; //+ player origin to ground dist - } //end for - start_edgeverts[0][0] += maxs[0] - mid[0]; - start_edgeverts[0][1] += maxs[1] - mid[1]; - start_edgeverts[1][0] += maxs[0] - mid[0]; - start_edgeverts[1][1] += mins[1] - mid[1]; - start_edgeverts[2][0] += mins[0] - mid[0]; - start_edgeverts[2][1] += mins[1] - mid[1]; - start_edgeverts[3][0] += mins[0] - mid[0]; - start_edgeverts[3][1] += maxs[1] - mid[1]; - // - start_plane.dist = start_edgeverts[0][2]; - VectorSet(start_plane.normal, 0, 0, 1); - // - for (i = 0; i < 4; i++) - { - VectorCopy(move_end, end_edgeverts[i]); - end_edgeverts[i][2] += maxs[2] - mid[2]; //+ bbox maxs z - end_edgeverts[i][2] += 24; //+ player origin to ground dist - } //end for - end_edgeverts[0][0] += maxs[0] - mid[0]; - end_edgeverts[0][1] += maxs[1] - mid[1]; - end_edgeverts[1][0] += maxs[0] - mid[0]; - end_edgeverts[1][1] += mins[1] - mid[1]; - end_edgeverts[2][0] += mins[0] - mid[0]; - end_edgeverts[2][1] += mins[1] - mid[1]; - end_edgeverts[3][0] += mins[0] - mid[0]; - end_edgeverts[3][1] += maxs[1] - mid[1]; - // - end_plane.dist = end_edgeverts[0][2]; - VectorSet(end_plane.normal, 0, 0, 1); - // -#ifndef BSPC -#if 0 - for (i = 0; i < 4; i++) - { - AAS_PermanentLine(start_edgeverts[i], start_edgeverts[(i+1)%4], 1); - AAS_PermanentLine(end_edgeverts[i], end_edgeverts[(i+1)%4], 1); - } //end for -#endif -#endif - VectorCopy(move_start, move_start_top); - move_start_top[2] += maxs[2] - mid[2] + 24; //+ bbox maxs z - VectorCopy(move_end, move_end_top); - move_end_top[2] += maxs[2] - mid[2] + 24; //+ bbox maxs z - // - if (!AAS_PointAreaNum(move_start_top)) continue; - if (!AAS_PointAreaNum(move_end_top)) continue; - // - for (i = 0; i < 2; i++) - { - firststartreach = firstendreach = NULL; - // - if (i == 0) - { - firststartreach = AAS_FindFaceReachabilities(start_edgeverts, 4, &start_plane, qtrue); - firstendreach = AAS_FindFaceReachabilities(end_edgeverts, 4, &end_plane, qfalse); - } //end if - else - { - firststartreach = AAS_FindFaceReachabilities(end_edgeverts, 4, &end_plane, qtrue); - firstendreach = AAS_FindFaceReachabilities(start_edgeverts, 4, &start_plane, qfalse); - } //end else - // - //create reachabilities from start to end - for (startreach = firststartreach; startreach; startreach = nextstartreach) - { - nextstartreach = startreach->next; - // - //trace = AAS_TraceClientBBox(startreach->start, move_start_top, PRESENCE_NORMAL, -1); - //if (trace.fraction < 1) continue; - // - for (endreach = firstendreach; endreach; endreach = nextendreach) - { - nextendreach = endreach->next; - // - //trace = AAS_TraceClientBBox(endreach->end, move_end_top, PRESENCE_NORMAL, -1); - //if (trace.fraction < 1) continue; - // - Log_Write("funcbob reach from area %d to %d\n", startreach->areanum, endreach->areanum); - // - // - if (i == 0) VectorCopy(move_start_top, org); - else VectorCopy(move_end_top, org); - VectorSubtract(startreach->start, org, dir); - dir[2] = 0; - VectorNormalize(dir); - VectorCopy(startreach->start, start); - VectorMA(startreach->start, 1, dir, start); - start[2] += 1; - VectorMA(startreach->start, 16, dir, end); - end[2] += 1; - // - numareas = AAS_TraceAreas(start, end, areas, points, 10); - if (numareas <= 0) continue; - if (numareas > 1) VectorCopy(points[1], startreach->start); - else VectorCopy(end, startreach->start); - // - if (!AAS_PointAreaNum(startreach->start)) continue; - if (!AAS_PointAreaNum(endreach->end)) continue; - // - lreach = AAS_AllocReachability(); - lreach->areanum = endreach->areanum; - if (i == 0) lreach->edgenum = ((int)move_start[axis] << 16) | ((int) move_end[axis] & 0x0000ffff); - else lreach->edgenum = ((int)move_end[axis] << 16) | ((int) move_start[axis] & 0x0000ffff); - lreach->facenum = (spawnflags << 16) | modelnum; - VectorCopy(startreach->start, lreach->start); - VectorCopy(endreach->end, lreach->end); -#ifndef BSPC -// AAS_DrawArrow(lreach->start, lreach->end, LINECOLOR_BLUE, LINECOLOR_YELLOW); -// AAS_PermanentLine(lreach->start, lreach->end, 1); -#endif - lreach->traveltype = TRAVEL_FUNCBOB; - lreach->traveltype |= AAS_TravelFlagsForTeam(ent); - lreach->traveltime = aassettings.rs_funcbob; - reach_funcbob++; - lreach->next = areareachability[startreach->areanum]; - areareachability[startreach->areanum] = lreach; - // - } //end for - } //end for - for (startreach = firststartreach; startreach; startreach = nextstartreach) - { - nextstartreach = startreach->next; - AAS_FreeReachability(startreach); - } //end for - for (endreach = firstendreach; endreach; endreach = nextendreach) - { - nextendreach = endreach->next; - AAS_FreeReachability(endreach); - } //end for - //only go up with func_bobbing entities that go up and down - if (!(spawnflags & 1) && !(spawnflags & 2)) break; - } //end for - } //end for -} //end of the function AAS_Reachability_FuncBobbing -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_Reachability_JumpPad(void) -{ - int face2num, i, ret, area2num, visualize, ent, bot_visualizejumppads; - //int modelnum, ent2; - //float dist, time, height, gravity, forward; - float speed, zvel, hordist; - aas_face_t *face2; - aas_area_t *area2; - aas_lreachability_t *lreach; - vec3_t areastart, facecenter, dir, cmdmove; - vec3_t velocity, absmins, absmaxs; - //vec3_t origin, ent2origin, angles, teststart; - aas_clientmove_t move; - //aas_trace_t trace; - aas_link_t *areas, *link; - //char target[MAX_EPAIRKEY], targetname[MAX_EPAIRKEY], model[MAX_EPAIRKEY]; - char classname[MAX_EPAIRKEY]; - -#ifdef BSPC - bot_visualizejumppads = 0; -#else - bot_visualizejumppads = LibVarValue("bot_visualizejumppads", "0"); -#endif - for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) - { - if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; - if (strcmp(classname, "trigger_push")) continue; - // - if (!AAS_GetJumpPadInfo(ent, areastart, absmins, absmaxs, velocity)) continue; - /* - // - AAS_FloatForBSPEpairKey(ent, "speed", &speed); - if (!speed) speed = 1000; -// AAS_VectorForBSPEpairKey(ent, "angles", angles); -// AAS_SetMovedir(angles, velocity); -// VectorScale(velocity, speed, velocity); - VectorClear(angles); - //get the mins, maxs and origin of the model - AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY); - if (model[0]) modelnum = atoi(model+1); - else modelnum = 0; - AAS_BSPModelMinsMaxsOrigin(modelnum, angles, absmins, absmaxs, origin); - VectorAdd(origin, absmins, absmins); - VectorAdd(origin, absmaxs, absmaxs); - // -#ifdef REACH_DEBUG - botimport.Print(PRT_MESSAGE, "absmins = %f %f %f\n", absmins[0], absmins[1], absmins[2]); - botimport.Print(PRT_MESSAGE, "absmaxs = %f %f %f\n", absmaxs[0], absmaxs[1], absmaxs[2]); -#endif REACH_DEBUG - VectorAdd(absmins, absmaxs, origin); - VectorScale (origin, 0.5, origin); - - //get the start areas - VectorCopy(origin, teststart); - teststart[2] += 64; - trace = AAS_TraceClientBBox(teststart, origin, PRESENCE_CROUCH, -1); - if (trace.startsolid) - { - botimport.Print(PRT_MESSAGE, "trigger_push start solid\n"); - VectorCopy(origin, areastart); - } //end if - else - { - VectorCopy(trace.endpos, areastart); - } //end else - areastart[2] += 0.125; - // - //AAS_DrawPermanentCross(origin, 4, 4); - //get the target entity - AAS_ValueForBSPEpairKey(ent, "target", target, MAX_EPAIRKEY); - for (ent2 = AAS_NextBSPEntity(0); ent2; ent2 = AAS_NextBSPEntity(ent2)) - { - if (!AAS_ValueForBSPEpairKey(ent2, "targetname", targetname, MAX_EPAIRKEY)) continue; - if (!strcmp(targetname, target)) break; - } //end for - if (!ent2) - { - botimport.Print(PRT_MESSAGE, "trigger_push without target entity %s\n", target); - continue; - } //end if - AAS_VectorForBSPEpairKey(ent2, "origin", ent2origin); - // - height = ent2origin[2] - origin[2]; - gravity = aassettings.sv_gravity; - time = sqrt( height / ( 0.5 * gravity ) ); - if (!time) - { - botimport.Print(PRT_MESSAGE, "trigger_push without time\n"); - continue; - } //end if - // set s.origin2 to the push velocity - VectorSubtract ( ent2origin, origin, velocity); - dist = VectorNormalize( velocity); - forward = dist / time; - //FIXME: why multiply by 1.1 - forward *= 1.1; - VectorScale(velocity, forward, velocity); - velocity[2] = time * gravity; - */ - //get the areas the jump pad brush is in - areas = AAS_LinkEntityClientBBox(absmins, absmaxs, -1, PRESENCE_CROUCH); - /* - for (link = areas; link; link = link->next_area) - { - if (link->areanum == 563) - { - ret = qfalse; - } - } - */ - for (link = areas; link; link = link->next_area) - { - if (AAS_AreaJumpPad(link->areanum)) break; - } //end for - if (!link) - { - botimport.Print(PRT_MESSAGE, "trigger_push not in any jump pad area\n"); - AAS_UnlinkFromAreas(areas); - continue; - } //end if - // - botimport.Print(PRT_MESSAGE, "found a trigger_push with velocity %f %f %f\n", velocity[0], velocity[1], velocity[2]); - //if there is a horizontal velocity check for a reachability without air control - if (velocity[0] || velocity[1]) - { - VectorSet(cmdmove, 0, 0, 0); - //VectorCopy(velocity, cmdmove); - //cmdmove[2] = 0; - Com_Memset(&move, 0, sizeof(aas_clientmove_t)); - area2num = 0; - for (i = 0; i < 20; i++) - { - AAS_PredictClientMovement(&move, -1, areastart, PRESENCE_NORMAL, qfalse, - velocity, cmdmove, 0, 30, 0.1f, - SE_HITGROUND|SE_ENTERWATER|SE_ENTERSLIME| - SE_ENTERLAVA|SE_HITGROUNDDAMAGE|SE_TOUCHJUMPPAD|SE_TOUCHTELEPORTER, 0, bot_visualizejumppads); - area2num = move.endarea; - for (link = areas; link; link = link->next_area) - { - if (!AAS_AreaJumpPad(link->areanum)) continue; - if (link->areanum == area2num) break; - } //end if - if (!link) break; - VectorCopy(move.endpos, areastart); - VectorCopy(move.velocity, velocity); - } //end for - if (area2num && i < 20) - { - for (link = areas; link; link = link->next_area) - { - if (!AAS_AreaJumpPad(link->areanum)) continue; - if (AAS_ReachabilityExists(link->areanum, area2num)) continue; - //create a rocket or bfg jump reachability from area1 to area2 - lreach = AAS_AllocReachability(); - if (!lreach) - { - AAS_UnlinkFromAreas(areas); - return; - } //end if - lreach->areanum = area2num; - //NOTE: the facenum is the Z velocity - lreach->facenum = velocity[2]; - //NOTE: the edgenum is the horizontal velocity - lreach->edgenum = sqrt(velocity[0] * velocity[0] + velocity[1] * velocity[1]); - VectorCopy(areastart, lreach->start); - VectorCopy(move.endpos, lreach->end); - lreach->traveltype = TRAVEL_JUMPPAD; - lreach->traveltype |= AAS_TravelFlagsForTeam(ent); - lreach->traveltime = aassettings.rs_jumppad; - lreach->next = areareachability[link->areanum]; - areareachability[link->areanum] = lreach; - // - reach_jumppad++; - } //end for - } //end if - } //end if - // - if (fabs(velocity[0]) > 100 || fabs(velocity[1]) > 100) continue; - //check for areas we can reach with air control - for (area2num = 1; area2num < aasworld.numareas; area2num++) - { - visualize = qfalse; - /* - if (area2num == 3568) - { - for (link = areas; link; link = link->next_area) - { - if (link->areanum == 3380) - { - visualize = qtrue; - botimport.Print(PRT_MESSAGE, "bah\n"); - } //end if - } //end for - } //end if*/ - //never try to go back to one of the original jumppad areas - //and don't create reachabilities if they already exist - for (link = areas; link; link = link->next_area) - { - if (AAS_ReachabilityExists(link->areanum, area2num)) break; - if (AAS_AreaJumpPad(link->areanum)) - { - if (link->areanum == area2num) break; - } //end if - } //end if - if (link) continue; - // - area2 = &aasworld.areas[area2num]; - for (i = 0; i < area2->numfaces; i++) - { - face2num = aasworld.faceindex[area2->firstface + i]; - face2 = &aasworld.faces[abs(face2num)]; - //if it is not a ground face - if (!(face2->faceflags & FACE_GROUND)) continue; - //get the center of the face - AAS_FaceCenter(face2num, facecenter); - //only go higher up - if (facecenter[2] < areastart[2]) continue; - //get the jumppad jump z velocity - zvel = velocity[2]; - //get the horizontal speed for the jump, if it isn't possible to calculate this - //speed - ret = AAS_HorizontalVelocityForJump(zvel, areastart, facecenter, &speed); - if (ret && speed < 150) - { - //direction towards the face center - VectorSubtract(facecenter, areastart, dir); - dir[2] = 0; - hordist = VectorNormalize(dir); - //if (hordist < 1.6 * facecenter[2] - areastart[2]) - { - //get command movement - VectorScale(dir, speed, cmdmove); - // - AAS_PredictClientMovement(&move, -1, areastart, PRESENCE_NORMAL, qfalse, - velocity, cmdmove, 30, 30, 0.1f, - SE_ENTERWATER|SE_ENTERSLIME| - SE_ENTERLAVA|SE_HITGROUNDDAMAGE| - SE_TOUCHJUMPPAD|SE_TOUCHTELEPORTER|SE_HITGROUNDAREA, area2num, visualize); - //if prediction time wasn't enough to fully predict the movement - //don't enter slime or lava and don't fall from too high - if (move.frames < 30 && - !(move.stopevent & (SE_ENTERSLIME|SE_ENTERLAVA|SE_HITGROUNDDAMAGE)) - && (move.stopevent & (SE_HITGROUNDAREA|SE_TOUCHJUMPPAD|SE_TOUCHTELEPORTER))) - { - //never go back to the same jumppad - for (link = areas; link; link = link->next_area) - { - if (link->areanum == move.endarea) break; - } - if (!link) - { - for (link = areas; link; link = link->next_area) - { - if (!AAS_AreaJumpPad(link->areanum)) continue; - if (AAS_ReachabilityExists(link->areanum, area2num)) continue; - //create a jumppad reachability from area1 to area2 - lreach = AAS_AllocReachability(); - if (!lreach) - { - AAS_UnlinkFromAreas(areas); - return; - } //end if - lreach->areanum = move.endarea; - //NOTE: the facenum is the Z velocity - lreach->facenum = velocity[2]; - //NOTE: the edgenum is the horizontal velocity - lreach->edgenum = sqrt(cmdmove[0] * cmdmove[0] + cmdmove[1] * cmdmove[1]); - VectorCopy(areastart, lreach->start); - VectorCopy(facecenter, lreach->end); - lreach->traveltype = TRAVEL_JUMPPAD; - lreach->traveltype |= AAS_TravelFlagsForTeam(ent); - lreach->traveltime = aassettings.rs_aircontrolledjumppad; - lreach->next = areareachability[link->areanum]; - areareachability[link->areanum] = lreach; - // - reach_jumppad++; - } //end for - } - } //end if - } //end if - } //end for - } //end for - } //end for - AAS_UnlinkFromAreas(areas); - } //end for -} //end of the function AAS_Reachability_JumpPad -//=========================================================================== -// never point at ground faces -// always a higher and pretty far area -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_Reachability_Grapple(int area1num, int area2num) -{ - int face2num, i, j, areanum, numareas, areas[20]; - float mingrappleangle, z, hordist; - bsp_trace_t bsptrace; - aas_trace_t trace; - aas_face_t *face2; - aas_area_t *area1, *area2; - aas_lreachability_t *lreach; - vec3_t areastart, facecenter, start, end, dir, down = {0, 0, -1}; - vec_t *v; - - //only grapple when on the ground or swimming - if (!AAS_AreaGrounded(area1num) && !AAS_AreaSwim(area1num)) return qfalse; - //don't grapple from a crouch area - if (!(AAS_AreaPresenceType(area1num) & PRESENCE_NORMAL)) return qfalse; - //NOTE: disabled area swim it doesn't work right - if (AAS_AreaSwim(area1num)) return qfalse; - // - area1 = &aasworld.areas[area1num]; - area2 = &aasworld.areas[area2num]; - //don't grapple towards way lower areas - if (area2->maxs[2] < area1->mins[2]) return qfalse; - // - VectorCopy(aasworld.areas[area1num].center, start); - //if not a swim area - if (!AAS_AreaSwim(area1num)) - { - if (!AAS_PointAreaNum(start)) Log_Write("area %d center %f %f %f in solid?\r\n", area1num, - start[0], start[1], start[2]); - VectorCopy(start, end); - end[2] -= 1000; - trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, -1); - if (trace.startsolid) return qfalse; - VectorCopy(trace.endpos, areastart); - } //end if - else - { - if (!(AAS_PointContents(start) & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER))) return qfalse; - } //end else - // - //start is now the start point - // - for (i = 0; i < area2->numfaces; i++) - { - face2num = aasworld.faceindex[area2->firstface + i]; - face2 = &aasworld.faces[abs(face2num)]; - //if it is not a solid face - if (!(face2->faceflags & FACE_SOLID)) continue; - //direction towards the first vertex of the face - v = aasworld.vertexes[aasworld.edges[abs(aasworld.edgeindex[face2->firstedge])].v[0]]; - VectorSubtract(v, areastart, dir); - //if the face plane is facing away - if (DotProduct(aasworld.planes[face2->planenum].normal, dir) > 0) continue; - //get the center of the face - AAS_FaceCenter(face2num, facecenter); - //only go higher up with the grapple - if (facecenter[2] < areastart[2] + 64) continue; - //only use vertical faces or downward facing faces - if (DotProduct(aasworld.planes[face2->planenum].normal, down) < 0) continue; - //direction towards the face center - VectorSubtract(facecenter, areastart, dir); - // - z = dir[2]; - dir[2] = 0; - hordist = VectorLength(dir); - if (!hordist) continue; - //if too far - if (hordist > 2000) continue; - //check the minimal angle of the movement - mingrappleangle = 15; //15 degrees - if (z / hordist < tan(2 * M_PI * mingrappleangle / 360)) continue; - // - VectorCopy(facecenter, start); - VectorMA(facecenter, -500, aasworld.planes[face2->planenum].normal, end); - // - bsptrace = AAS_Trace(start, NULL, NULL, end, 0, CONTENTS_SOLID); - //the grapple won't stick to the sky and the grapple point should be near the AAS wall - if ((bsptrace.surface.flags & SURF_SKY) || (bsptrace.fraction * 500 > 32)) continue; - //trace a full bounding box from the area center on the ground to - //the center of the face - VectorSubtract(facecenter, areastart, dir); - VectorNormalize(dir); - VectorMA(areastart, 4, dir, start); - VectorCopy(bsptrace.endpos, end); - trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, -1); - VectorSubtract(trace.endpos, facecenter, dir); - if (VectorLength(dir) > 24) continue; - // - VectorCopy(trace.endpos, start); - VectorCopy(trace.endpos, end); - end[2] -= AAS_FallDamageDistance(); - trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, -1); - if (trace.fraction >= 1) continue; - //area to end in - areanum = AAS_PointAreaNum(trace.endpos); - //if not in lava or slime - if (aasworld.areasettings[areanum].contents & (AREACONTENTS_SLIME|AREACONTENTS_LAVA)) - { - continue; - } //end if - //do not go the the source area - if (areanum == area1num) continue; - //don't create reachabilities if they already exist - if (AAS_ReachabilityExists(area1num, areanum)) continue; - //only end in areas we can stand - if (!AAS_AreaGrounded(areanum)) continue; - //never go through cluster portals!! - numareas = AAS_TraceAreas(areastart, bsptrace.endpos, areas, NULL, 20); - if (numareas >= 20) continue; - for (j = 0; j < numareas; j++) - { - if (aasworld.areasettings[areas[j]].contents & AREACONTENTS_CLUSTERPORTAL) break; - } //end for - if (j < numareas) continue; - //create a new reachability link - lreach = AAS_AllocReachability(); - if (!lreach) return qfalse; - lreach->areanum = areanum; - lreach->facenum = face2num; - lreach->edgenum = 0; - VectorCopy(areastart, lreach->start); - //VectorCopy(facecenter, lreach->end); - VectorCopy(bsptrace.endpos, lreach->end); - lreach->traveltype = TRAVEL_GRAPPLEHOOK; - VectorSubtract(lreach->end, lreach->start, dir); - lreach->traveltime = aassettings.rs_startgrapple + VectorLength(dir) * 0.25; - lreach->next = areareachability[area1num]; - areareachability[area1num] = lreach; - // - reach_grapple++; - } //end for - // - return qfalse; -} //end of the function AAS_Reachability_Grapple -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_SetWeaponJumpAreaFlags(void) -{ - int ent, i; - vec3_t mins = {-15, -15, -15}, maxs = {15, 15, 15}; - vec3_t origin; - int areanum, weaponjumpareas, spawnflags; - char classname[MAX_EPAIRKEY]; - - weaponjumpareas = 0; - for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) - { - if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; - if ( - !strcmp(classname, "item_armor_body") || - !strcmp(classname, "item_armor_combat") || - !strcmp(classname, "item_health_mega") || - !strcmp(classname, "weapon_grenadelauncher") || - !strcmp(classname, "weapon_rocketlauncher") || - !strcmp(classname, "weapon_lightning") || - !strcmp(classname, "weapon_plasmagun") || - !strcmp(classname, "weapon_railgun") || - !strcmp(classname, "weapon_bfg") || - !strcmp(classname, "item_quad") || - !strcmp(classname, "item_regen") || - !strcmp(classname, "item_invulnerability")) - { - if (AAS_VectorForBSPEpairKey(ent, "origin", origin)) - { - spawnflags = 0; - AAS_IntForBSPEpairKey(ent, "spawnflags", &spawnflags); - //if not a stationary item - if (!(spawnflags & 1)) - { - if (!AAS_DropToFloor(origin, mins, maxs)) - { - botimport.Print(PRT_MESSAGE, "%s in solid at (%1.1f %1.1f %1.1f)\n", - classname, origin[0], origin[1], origin[2]); - } //end if - } //end if - //areanum = AAS_PointAreaNum(origin); - areanum = AAS_BestReachableArea(origin, mins, maxs, origin); - //the bot may rocket jump towards this area - aasworld.areasettings[areanum].areaflags |= AREA_WEAPONJUMP; - // - //if (!AAS_AreaGrounded(areanum)) - // botimport.Print(PRT_MESSAGE, "area not grounded\n"); - // - weaponjumpareas++; - } //end if - } //end if - } //end for - for (i = 1; i < aasworld.numareas; i++) - { - if (aasworld.areasettings[i].contents & AREACONTENTS_JUMPPAD) - { - aasworld.areasettings[i].areaflags |= AREA_WEAPONJUMP; - weaponjumpareas++; - } //end if - } //end for - botimport.Print(PRT_MESSAGE, "%d weapon jump areas\n", weaponjumpareas); -} //end of the function AAS_SetWeaponJumpAreaFlags -//=========================================================================== -// create a possible weapon jump reachability from area1 to area2 -// -// check if there's a cool item in the second area -// check if area1 is lower than area2 -// check if the bot can rocketjump from area1 to area2 -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_Reachability_WeaponJump(int area1num, int area2num) -{ - int face2num, i, n, ret, visualize; - float speed, zvel, hordist; - aas_face_t *face2; - aas_area_t *area1, *area2; - aas_lreachability_t *lreach; - vec3_t areastart, facecenter, start, end, dir, cmdmove;// teststart; - vec3_t velocity; - aas_clientmove_t move; - aas_trace_t trace; - - visualize = qfalse; -// if (area1num == 4436 && area2num == 4318) -// { -// visualize = qtrue; -// } - if (!AAS_AreaGrounded(area1num) || AAS_AreaSwim(area1num)) return qfalse; - if (!AAS_AreaGrounded(area2num)) return qfalse; - //NOTE: only weapon jump towards areas with an interesting item in it?? - if (!(aasworld.areasettings[area2num].areaflags & AREA_WEAPONJUMP)) return qfalse; - // - area1 = &aasworld.areas[area1num]; - area2 = &aasworld.areas[area2num]; - //don't weapon jump towards way lower areas - if (area2->maxs[2] < area1->mins[2]) return qfalse; - // - VectorCopy(aasworld.areas[area1num].center, start); - //if not a swim area - if (!AAS_PointAreaNum(start)) Log_Write("area %d center %f %f %f in solid?\r\n", area1num, - start[0], start[1], start[2]); - VectorCopy(start, end); - end[2] -= 1000; - trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, -1); - if (trace.startsolid) return qfalse; - VectorCopy(trace.endpos, areastart); - // - //areastart is now the start point - // - for (i = 0; i < area2->numfaces; i++) - { - face2num = aasworld.faceindex[area2->firstface + i]; - face2 = &aasworld.faces[abs(face2num)]; - //if it is not a solid face - if (!(face2->faceflags & FACE_GROUND)) continue; - //get the center of the face - AAS_FaceCenter(face2num, facecenter); - //only go higher up with weapon jumps - if (facecenter[2] < areastart[2] + 64) continue; - //NOTE: set to 2 to allow bfg jump reachabilities - for (n = 0; n < 1/*2*/; n++) - { - //get the rocket jump z velocity - if (n) zvel = AAS_BFGJumpZVelocity(areastart); - else zvel = AAS_RocketJumpZVelocity(areastart); - //get the horizontal speed for the jump, if it isn't possible to calculate this - //speed (the jump is not possible) then there's no jump reachability created - ret = AAS_HorizontalVelocityForJump(zvel, areastart, facecenter, &speed); - if (ret && speed < 300) - { - //direction towards the face center - VectorSubtract(facecenter, areastart, dir); - dir[2] = 0; - hordist = VectorNormalize(dir); - //if (hordist < 1.6 * (facecenter[2] - areastart[2])) - { - //get command movement - VectorScale(dir, speed, cmdmove); - VectorSet(velocity, 0, 0, zvel); - /* - //get command movement - VectorScale(dir, speed, velocity); - velocity[2] = zvel; - VectorSet(cmdmove, 0, 0, 0); - */ - // - AAS_PredictClientMovement(&move, -1, areastart, PRESENCE_NORMAL, qtrue, - velocity, cmdmove, 30, 30, 0.1f, - SE_ENTERWATER|SE_ENTERSLIME| - SE_ENTERLAVA|SE_HITGROUNDDAMAGE| - SE_TOUCHJUMPPAD|SE_HITGROUND|SE_HITGROUNDAREA, area2num, visualize); - //if prediction time wasn't enough to fully predict the movement - //don't enter slime or lava and don't fall from too high - if (move.frames < 30 && - !(move.stopevent & (SE_ENTERSLIME|SE_ENTERLAVA|SE_HITGROUNDDAMAGE)) - && (move.stopevent & (SE_HITGROUNDAREA|SE_TOUCHJUMPPAD))) - { - //create a rocket or bfg jump reachability from area1 to area2 - lreach = AAS_AllocReachability(); - if (!lreach) return qfalse; - lreach->areanum = area2num; - lreach->facenum = 0; - lreach->edgenum = 0; - VectorCopy(areastart, lreach->start); - VectorCopy(facecenter, lreach->end); - if (n) - { - lreach->traveltype = TRAVEL_BFGJUMP; - lreach->traveltime = aassettings.rs_bfgjump; - } //end if - else - { - lreach->traveltype = TRAVEL_ROCKETJUMP; - lreach->traveltime = aassettings.rs_rocketjump; - } //end else - lreach->next = areareachability[area1num]; - areareachability[area1num] = lreach; - // - reach_rocketjump++; - return qtrue; - } //end if - } //end if - } //end if - } //end for - } //end for - // - return qfalse; -} //end of the function AAS_Reachability_WeaponJump -//=========================================================================== -// calculates additional walk off ledge reachabilities for the given area -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_Reachability_WalkOffLedge(int areanum) -{ - int i, j, k, l, m, n, p, areas[10], numareas; - int face1num, face2num, face3num, edge1num, edge2num, edge3num; - int otherareanum, gap, reachareanum, side; - aas_area_t *area, *area2; - aas_face_t *face1, *face2, *face3; - aas_edge_t *edge; - aas_plane_t *plane; - vec_t *v1, *v2; - vec3_t sharededgevec, mid, dir, testend; - aas_lreachability_t *lreach; - aas_trace_t trace; - - if (!AAS_AreaGrounded(areanum) || AAS_AreaSwim(areanum)) return; - // - area = &aasworld.areas[areanum]; - // - for (i = 0; i < area->numfaces; i++) - { - face1num = aasworld.faceindex[area->firstface + i]; - face1 = &aasworld.faces[abs(face1num)]; - //face 1 must be a ground face - if (!(face1->faceflags & FACE_GROUND)) continue; - //go through all the edges of this ground face - for (k = 0; k < face1->numedges; k++) - { - edge1num = aasworld.edgeindex[face1->firstedge + k]; - //find another not ground face using this same edge - for (j = 0; j < area->numfaces; j++) - { - face2num = aasworld.faceindex[area->firstface + j]; - face2 = &aasworld.faces[abs(face2num)]; - //face 2 may not be a ground face - if (face2->faceflags & FACE_GROUND) continue; - //compare all the edges - for (l = 0; l < face2->numedges; l++) - { - edge2num = aasworld.edgeindex[face2->firstedge + l]; - if (abs(edge1num) == abs(edge2num)) - { - //get the area at the other side of the face - if (face2->frontarea == areanum) otherareanum = face2->backarea; - else otherareanum = face2->frontarea; - // - area2 = &aasworld.areas[otherareanum]; - //if the other area is grounded! - if (aasworld.areasettings[otherareanum].areaflags & AREA_GROUNDED) - { - //check for a possible gap - gap = qfalse; - for (n = 0; n < area2->numfaces; n++) - { - face3num = aasworld.faceindex[area2->firstface + n]; - //may not be the shared face of the two areas - if (abs(face3num) == abs(face2num)) continue; - // - face3 = &aasworld.faces[abs(face3num)]; - //find an edge shared by all three faces - for (m = 0; m < face3->numedges; m++) - { - edge3num = aasworld.edgeindex[face3->firstedge + m]; - //but the edge should be shared by all three faces - if (abs(edge3num) == abs(edge1num)) - { - if (!(face3->faceflags & FACE_SOLID)) - { - gap = qtrue; - break; - } //end if - // - if (face3->faceflags & FACE_GROUND) - { - gap = qfalse; - break; - } //end if - //FIXME: there are more situations to be handled - gap = qtrue; - break; - } //end if - } //end for - if (m < face3->numedges) break; - } //end for - if (!gap) break; - } //end if - //check for a walk off ledge reachability - edge = &aasworld.edges[abs(edge1num)]; - side = edge1num < 0; - // - v1 = aasworld.vertexes[edge->v[side]]; - v2 = aasworld.vertexes[edge->v[!side]]; - // - plane = &aasworld.planes[face1->planenum]; - //get the points really into the areas - VectorSubtract(v2, v1, sharededgevec); - CrossProduct(plane->normal, sharededgevec, dir); - VectorNormalize(dir); - // - VectorAdd(v1, v2, mid); - VectorScale(mid, 0.5, mid); - VectorMA(mid, 8, dir, mid); - // - VectorCopy(mid, testend); - testend[2] -= 1000; - trace = AAS_TraceClientBBox(mid, testend, PRESENCE_CROUCH, -1); - // - if (trace.startsolid) - { - //Log_Write("area %d: trace.startsolid\r\n", areanum); - break; - } //end if - reachareanum = AAS_PointAreaNum(trace.endpos); - if (reachareanum == areanum) - { - //Log_Write("area %d: same area\r\n", areanum); - break; - } //end if - if (AAS_ReachabilityExists(areanum, reachareanum)) - { - //Log_Write("area %d: reachability already exists\r\n", areanum); - break; - } //end if - if (!AAS_AreaGrounded(reachareanum) && !AAS_AreaSwim(reachareanum)) - { - //Log_Write("area %d, reach area %d: not grounded and not swim\r\n", areanum, reachareanum); - break; - } //end if - // - if (aasworld.areasettings[reachareanum].contents & (AREACONTENTS_SLIME - | AREACONTENTS_LAVA)) - { - //Log_Write("area %d, reach area %d: lava or slime\r\n", areanum, reachareanum); - break; - } //end if - //if not going through a cluster portal - numareas = AAS_TraceAreas(mid, testend, areas, NULL, sizeof(areas) / sizeof(int)); - for (p = 0; p < numareas; p++) - if (AAS_AreaClusterPortal(areas[p])) - break; - if (p < numareas) - break; - // if a maximum fall height is set and the bot would fall down further - if (aassettings.rs_maxfallheight && fabs(mid[2] - trace.endpos[2]) > aassettings.rs_maxfallheight) - break; - // - lreach = AAS_AllocReachability(); - if (!lreach) break; - lreach->areanum = reachareanum; - lreach->facenum = 0; - lreach->edgenum = edge1num; - VectorCopy(mid, lreach->start); - VectorCopy(trace.endpos, lreach->end); - lreach->traveltype = TRAVEL_WALKOFFLEDGE; - lreach->traveltime = aassettings.rs_startwalkoffledge + fabs(mid[2] - trace.endpos[2]) * 50 / aassettings.phys_gravity; - if (!AAS_AreaSwim(reachareanum) && !AAS_AreaJumpPad(reachareanum)) - { - if (AAS_FallDelta(mid[2] - trace.endpos[2]) > aassettings.phys_falldelta5) - { - lreach->traveltime += aassettings.rs_falldamage5; - } //end if - else if (AAS_FallDelta(mid[2] - trace.endpos[2]) > aassettings.phys_falldelta10) - { - lreach->traveltime += aassettings.rs_falldamage10; - } //end if - } //end if - lreach->next = areareachability[areanum]; - areareachability[areanum] = lreach; - //we've got another walk off ledge reachability - reach_walkoffledge++; - } //end if - } //end for - } //end for - } //end for - } //end for -} //end of the function AAS_Reachability_WalkOffLedge -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_StoreReachability(void) -{ - int i; - aas_areasettings_t *areasettings; - aas_lreachability_t *lreach; - aas_reachability_t *reach; - - if (aasworld.reachability) FreeMemory(aasworld.reachability); - aasworld.reachability = (aas_reachability_t *) GetClearedMemory((numlreachabilities + 10) * sizeof(aas_reachability_t)); - aasworld.reachabilitysize = 1; - for (i = 0; i < aasworld.numareas; i++) - { - areasettings = &aasworld.areasettings[i]; - areasettings->firstreachablearea = aasworld.reachabilitysize; - areasettings->numreachableareas = 0; - for (lreach = areareachability[i]; lreach; lreach = lreach->next) - { - reach = &aasworld.reachability[areasettings->firstreachablearea + - areasettings->numreachableareas]; - reach->areanum = lreach->areanum; - reach->facenum = lreach->facenum; - reach->edgenum = lreach->edgenum; - VectorCopy(lreach->start, reach->start); - VectorCopy(lreach->end, reach->end); - reach->traveltype = lreach->traveltype; - reach->traveltime = lreach->traveltime; - // - areasettings->numreachableareas++; - } //end for - aasworld.reachabilitysize += areasettings->numreachableareas; - } //end for -} //end of the function AAS_StoreReachability -//=========================================================================== -// -// TRAVEL_WALK 100% equal floor height + steps -// TRAVEL_CROUCH 100% -// TRAVEL_BARRIERJUMP 100% -// TRAVEL_JUMP 80% -// TRAVEL_LADDER 100% + fall down from ladder + jump up to ladder -// TRAVEL_WALKOFFLEDGE 90% walk off very steep walls? -// TRAVEL_SWIM 100% -// TRAVEL_WATERJUMP 100% -// TRAVEL_TELEPORT 100% -// TRAVEL_ELEVATOR 100% -// TRAVEL_GRAPPLEHOOK 100% -// TRAVEL_DOUBLEJUMP 0% -// TRAVEL_RAMPJUMP 0% -// TRAVEL_STRAFEJUMP 0% -// TRAVEL_ROCKETJUMP 100% (currently limited towards areas with items) -// TRAVEL_BFGJUMP 0% (currently disabled) -// TRAVEL_JUMPPAD 100% -// TRAVEL_FUNCBOB 100% -// -// Parameter: - -// Returns: true if NOT finished -// Changes Globals: - -//=========================================================================== -int AAS_ContinueInitReachability(float time) -{ - int i, j, todo, start_time; - static float framereachability, reachability_delay; - static int lastpercentage; - - if (!aasworld.loaded) return qfalse; - //if reachability is calculated for all areas - if (aasworld.numreachabilityareas >= aasworld.numareas + 2) return qfalse; - //if starting with area 1 (area 0 is a dummy) - if (aasworld.numreachabilityareas == 1) - { - botimport.Print(PRT_MESSAGE, "calculating reachability...\n"); - lastpercentage = 0; - framereachability = 2000; - reachability_delay = 1000; - } //end if - //number of areas to calculate reachability for this cycle - todo = aasworld.numreachabilityareas + (int) framereachability; - start_time = Sys_MilliSeconds(); - //loop over the areas - for (i = aasworld.numreachabilityareas; i < aasworld.numareas && i < todo; i++) - { - aasworld.numreachabilityareas++; - //only create jumppad reachabilities from jumppad areas - if (aasworld.areasettings[i].contents & AREACONTENTS_JUMPPAD) - { - continue; - } //end if - //loop over the areas - for (j = 1; j < aasworld.numareas; j++) - { - if (i == j) continue; - //never create reachabilities from teleporter or jumppad areas to regular areas - if (aasworld.areasettings[i].contents & (AREACONTENTS_TELEPORTER|AREACONTENTS_JUMPPAD)) - { - if (!(aasworld.areasettings[j].contents & (AREACONTENTS_TELEPORTER|AREACONTENTS_JUMPPAD))) - { - continue; - } //end if - } //end if - //if there already is a reachability link from area i to j - if (AAS_ReachabilityExists(i, j)) continue; - //check for a swim reachability - if (AAS_Reachability_Swim(i, j)) continue; - //check for a simple walk on equal floor height reachability - if (AAS_Reachability_EqualFloorHeight(i, j)) continue; - //check for step, barrier, waterjump and walk off ledge reachabilities - if (AAS_Reachability_Step_Barrier_WaterJump_WalkOffLedge(i, j)) continue; - //check for ladder reachabilities - if (AAS_Reachability_Ladder(i, j)) continue; - //check for a jump reachability - if (AAS_Reachability_Jump(i, j)) continue; - } //end for - //never create these reachabilities from teleporter or jumppad areas - if (aasworld.areasettings[i].contents & (AREACONTENTS_TELEPORTER|AREACONTENTS_JUMPPAD)) - { - continue; - } //end if - //loop over the areas - for (j = 1; j < aasworld.numareas; j++) - { - if (i == j) continue; - // - if (AAS_ReachabilityExists(i, j)) continue; - //check for a grapple hook reachability - if (calcgrapplereach) AAS_Reachability_Grapple(i, j); - //check for a weapon jump reachability - AAS_Reachability_WeaponJump(i, j); - } //end for - //if the calculation took more time than the max reachability delay - if (Sys_MilliSeconds() - start_time > (int) reachability_delay) break; - // - if (aasworld.numreachabilityareas * 1000 / aasworld.numareas > lastpercentage) break; - } //end for - // - if (aasworld.numreachabilityareas == aasworld.numareas) - { - botimport.Print(PRT_MESSAGE, "\r%6.1f%%", (float) 100.0); - botimport.Print(PRT_MESSAGE, "\nplease wait while storing reachability...\n"); - aasworld.numreachabilityareas++; - } //end if - //if this is the last step in the reachability calculations - else if (aasworld.numreachabilityareas == aasworld.numareas + 1) - { - //create additional walk off ledge reachabilities for every area - for (i = 1; i < aasworld.numareas; i++) - { - //only create jumppad reachabilities from jumppad areas - if (aasworld.areasettings[i].contents & AREACONTENTS_JUMPPAD) - { - continue; - } //end if - AAS_Reachability_WalkOffLedge(i); - } //end for - //create jump pad reachabilities - AAS_Reachability_JumpPad(); - //create teleporter reachabilities - AAS_Reachability_Teleport(); - //create elevator (func_plat) reachabilities - AAS_Reachability_Elevator(); - //create func_bobbing reachabilities - AAS_Reachability_FuncBobbing(); - // -#ifdef DEBUG - botimport.Print(PRT_MESSAGE, "%6d reach swim\n", reach_swim); - botimport.Print(PRT_MESSAGE, "%6d reach equal floor\n", reach_equalfloor); - botimport.Print(PRT_MESSAGE, "%6d reach step\n", reach_step); - botimport.Print(PRT_MESSAGE, "%6d reach barrier\n", reach_barrier); - botimport.Print(PRT_MESSAGE, "%6d reach waterjump\n", reach_waterjump); - botimport.Print(PRT_MESSAGE, "%6d reach walkoffledge\n", reach_walkoffledge); - botimport.Print(PRT_MESSAGE, "%6d reach jump\n", reach_jump); - botimport.Print(PRT_MESSAGE, "%6d reach ladder\n", reach_ladder); - botimport.Print(PRT_MESSAGE, "%6d reach walk\n", reach_walk); - botimport.Print(PRT_MESSAGE, "%6d reach teleport\n", reach_teleport); - botimport.Print(PRT_MESSAGE, "%6d reach funcbob\n", reach_funcbob); - botimport.Print(PRT_MESSAGE, "%6d reach elevator\n", reach_elevator); - botimport.Print(PRT_MESSAGE, "%6d reach grapple\n", reach_grapple); - botimport.Print(PRT_MESSAGE, "%6d reach rocketjump\n", reach_rocketjump); - botimport.Print(PRT_MESSAGE, "%6d reach jumppad\n", reach_jumppad); -#endif - //*/ - //store all the reachabilities - AAS_StoreReachability(); - //free the reachability link heap - AAS_ShutDownReachabilityHeap(); - // - FreeMemory(areareachability); - // - aasworld.numreachabilityareas++; - // - botimport.Print(PRT_MESSAGE, "calculating clusters...\n"); - } //end if - else - { - lastpercentage = aasworld.numreachabilityareas * 1000 / aasworld.numareas; - botimport.Print(PRT_MESSAGE, "\r%6.1f%%", (float) lastpercentage / 10); - } //end else - //not yet finished - return qtrue; -} //end of the function AAS_ContinueInitReachability -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_InitReachability(void) -{ - if (!aasworld.loaded) return; - - if (aasworld.reachabilitysize) - { -#ifndef BSPC - if (!((int)LibVarGetValue("forcereachability"))) - { - aasworld.numreachabilityareas = aasworld.numareas + 2; - return; - } //end if -#else - aasworld.numreachabilityareas = aasworld.numareas + 2; - return; -#endif //BSPC - } //end if -#ifndef BSPC - calcgrapplereach = LibVarGetValue("grapplereach"); -#endif - aasworld.savefile = qtrue; - //start with area 1 because area zero is a dummy - aasworld.numreachabilityareas = 1; - ////aasworld.numreachabilityareas = aasworld.numareas + 1; //only calculate entity reachabilities - //setup the heap with reachability links - AAS_SetupReachabilityHeap(); - //allocate area reachability link array - areareachability = (aas_lreachability_t **) GetClearedMemory( - aasworld.numareas * sizeof(aas_lreachability_t *)); - // - AAS_SetWeaponJumpAreaFlags(); -} //end of the function AAS_InitReachable +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_reach.c + * + * desc: reachability calculations + * + * $Archive: /MissionPack/code/botlib/be_aas_reach.c $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_log.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_libvar.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_aas_def.h" + +extern int Sys_MilliSeconds(void); + + +extern botlib_import_t botimport; + +//#define REACH_DEBUG + +//NOTE: all travel times are in hundreth of a second +//maximum number of reachability links +#define AAS_MAX_REACHABILITYSIZE 65536 +//number of areas reachability is calculated for each frame +#define REACHABILITYAREASPERCYCLE 15 +//number of units reachability points are placed inside the areas +#define INSIDEUNITS 2 +#define INSIDEUNITS_WALKEND 5 +#define INSIDEUNITS_WALKSTART 0.1 +#define INSIDEUNITS_WATERJUMP 15 +//area flag used for weapon jumping +#define AREA_WEAPONJUMP 8192 //valid area to weapon jump to +//number of reachabilities of each type +int reach_swim; //swim +int reach_equalfloor; //walk on floors with equal height +int reach_step; //step up +int reach_walk; //walk of step +int reach_barrier; //jump up to a barrier +int reach_waterjump; //jump out of water +int reach_walkoffledge; //walk of a ledge +int reach_jump; //jump +int reach_ladder; //climb or descent a ladder +int reach_teleport; //teleport +int reach_elevator; //use an elevator +int reach_funcbob; //use a func bob +int reach_grapple; //grapple hook +int reach_doublejump; //double jump +int reach_rampjump; //ramp jump +int reach_strafejump; //strafe jump (just normal jump but further) +int reach_rocketjump; //rocket jump +int reach_bfgjump; //bfg jump +int reach_jumppad; //jump pads +//if true grapple reachabilities are skipped +int calcgrapplereach; +//linked reachability +typedef struct aas_lreachability_s +{ + int areanum; //number of the reachable area + int facenum; //number of the face towards the other area + int edgenum; //number of the edge towards the other area + vec3_t start; //start point of inter area movement + vec3_t end; //end point of inter area movement + int traveltype; //type of travel required to get to the area + unsigned short int traveltime; //travel time of the inter area movement + // + struct aas_lreachability_s *next; +} aas_lreachability_t; +//temporary reachabilities +aas_lreachability_t *reachabilityheap; //heap with reachabilities +aas_lreachability_t *nextreachability; //next free reachability from the heap +aas_lreachability_t **areareachability; //reachability links for every area +int numlreachabilities; + +//=========================================================================== +// returns the surface area of the given face +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_FaceArea(aas_face_t *face) +{ + int i, edgenum, side; + float total; + vec_t *v; + vec3_t d1, d2, cross; + aas_edge_t *edge; + + edgenum = aasworld.edgeindex[face->firstedge]; + side = edgenum < 0; + edge = &aasworld.edges[abs(edgenum)]; + v = aasworld.vertexes[edge->v[side]]; + + total = 0; + for (i = 1; i < face->numedges - 1; i++) + { + edgenum = aasworld.edgeindex[face->firstedge + i]; + side = edgenum < 0; + edge = &aasworld.edges[abs(edgenum)]; + VectorSubtract(aasworld.vertexes[edge->v[side]], v, d1); + VectorSubtract(aasworld.vertexes[edge->v[!side]], v, d2); + CrossProduct(d1, d2, cross); + total += 0.5 * VectorLength(cross); + } //end for + return total; +} //end of the function AAS_FaceArea +//=========================================================================== +// returns the volume of an area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_AreaVolume(int areanum) +{ + int i, edgenum, facenum, side; + vec_t d, a, volume; + vec3_t corner; + aas_plane_t *plane; + aas_edge_t *edge; + aas_face_t *face; + aas_area_t *area; + + area = &aasworld.areas[areanum]; + facenum = aasworld.faceindex[area->firstface]; + face = &aasworld.faces[abs(facenum)]; + edgenum = aasworld.edgeindex[face->firstedge]; + edge = &aasworld.edges[abs(edgenum)]; + // + VectorCopy(aasworld.vertexes[edge->v[0]], corner); + + //make tetrahedrons to all other faces + volume = 0; + for (i = 0; i < area->numfaces; i++) + { + facenum = abs(aasworld.faceindex[area->firstface + i]); + face = &aasworld.faces[facenum]; + side = face->backarea != areanum; + plane = &aasworld.planes[face->planenum ^ side]; + d = -(DotProduct (corner, plane->normal) - plane->dist); + a = AAS_FaceArea(face); + volume += d * a; + } //end for + + volume /= 3; + return volume; +} //end of the function AAS_AreaVolume +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BestReachableLinkArea(aas_link_t *areas) +{ + aas_link_t *link; + + for (link = areas; link; link = link->next_area) + { + if (AAS_AreaGrounded(link->areanum) || AAS_AreaSwim(link->areanum)) + { + return link->areanum; + } //end if + } //end for + // + for (link = areas; link; link = link->next_area) + { + if (link->areanum) return link->areanum; + //FIXME: this is a bad idea when the reachability is not yet + // calculated when the level items are loaded + if (AAS_AreaReachability(link->areanum)) + return link->areanum; + } //end for + return 0; +} //end of the function AAS_BestReachableLinkArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_GetJumpPadInfo(int ent, vec3_t areastart, vec3_t absmins, vec3_t absmaxs, vec3_t velocity) +{ + int modelnum, ent2; + float speed, height, gravity, time, dist, forward; + vec3_t origin, angles, teststart, ent2origin; + aas_trace_t trace; + char model[MAX_EPAIRKEY]; + char target[MAX_EPAIRKEY], targetname[MAX_EPAIRKEY]; + + // + AAS_FloatForBSPEpairKey(ent, "speed", &speed); + if (!speed) speed = 1000; + VectorClear(angles); + //get the mins, maxs and origin of the model + AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY); + if (model[0]) modelnum = atoi(model+1); + else modelnum = 0; + AAS_BSPModelMinsMaxsOrigin(modelnum, angles, absmins, absmaxs, origin); + VectorAdd(origin, absmins, absmins); + VectorAdd(origin, absmaxs, absmaxs); + VectorAdd(absmins, absmaxs, origin); + VectorScale (origin, 0.5, origin); + + //get the start areas + VectorCopy(origin, teststart); + teststart[2] += 64; + trace = AAS_TraceClientBBox(teststart, origin, PRESENCE_CROUCH, -1); + if (trace.startsolid) + { + botimport.Print(PRT_MESSAGE, "trigger_push start solid\n"); + VectorCopy(origin, areastart); + } //end if + else + { + VectorCopy(trace.endpos, areastart); + } //end else + areastart[2] += 0.125; + // + //AAS_DrawPermanentCross(origin, 4, 4); + //get the target entity + AAS_ValueForBSPEpairKey(ent, "target", target, MAX_EPAIRKEY); + for (ent2 = AAS_NextBSPEntity(0); ent2; ent2 = AAS_NextBSPEntity(ent2)) + { + if (!AAS_ValueForBSPEpairKey(ent2, "targetname", targetname, MAX_EPAIRKEY)) continue; + if (!strcmp(targetname, target)) break; + } //end for + if (!ent2) + { + botimport.Print(PRT_MESSAGE, "trigger_push without target entity %s\n", target); + return qfalse; + } //end if + AAS_VectorForBSPEpairKey(ent2, "origin", ent2origin); + // + height = ent2origin[2] - origin[2]; + gravity = aassettings.phys_gravity; + time = sqrt( height / ( 0.5 * gravity ) ); + if (!time) + { + botimport.Print(PRT_MESSAGE, "trigger_push without time\n"); + return qfalse; + } //end if + // set s.origin2 to the push velocity + VectorSubtract ( ent2origin, origin, velocity); + dist = VectorNormalize( velocity); + forward = dist / time; + //FIXME: why multiply by 1.1 + forward *= 1.1f; + VectorScale(velocity, forward, velocity); + velocity[2] = time * gravity; + return qtrue; +} //end of the function AAS_GetJumpPadInfo +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BestReachableFromJumpPadArea(vec3_t origin, vec3_t mins, vec3_t maxs) +{ + int area2num, ent, bot_visualizejumppads, bestareanum; + float volume, bestareavolume; + vec3_t areastart, cmdmove, bboxmins, bboxmaxs; + vec3_t absmins, absmaxs, velocity; + aas_clientmove_t move; + aas_link_t *areas, *link; + char classname[MAX_EPAIRKEY]; + +#ifdef BSPC + bot_visualizejumppads = 0; +#else + bot_visualizejumppads = LibVarValue("bot_visualizejumppads", "0"); +#endif + VectorAdd(origin, mins, bboxmins); + VectorAdd(origin, maxs, bboxmaxs); + for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) + { + if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; + if (strcmp(classname, "trigger_push")) continue; + // + if (!AAS_GetJumpPadInfo(ent, areastart, absmins, absmaxs, velocity)) continue; + //get the areas the jump pad brush is in + areas = AAS_LinkEntityClientBBox(absmins, absmaxs, -1, PRESENCE_CROUCH); + for (link = areas; link; link = link->next_area) + { + if (AAS_AreaJumpPad(link->areanum)) break; + } //end for + if (!link) + { + botimport.Print(PRT_MESSAGE, "trigger_push not in any jump pad area\n"); + AAS_UnlinkFromAreas(areas); + continue; + } //end if + // + //botimport.Print(PRT_MESSAGE, "found a trigger_push with velocity %f %f %f\n", velocity[0], velocity[1], velocity[2]); + // + VectorSet(cmdmove, 0, 0, 0); + Com_Memset(&move, 0, sizeof(aas_clientmove_t)); + area2num = 0; + AAS_ClientMovementHitBBox(&move, -1, areastart, PRESENCE_NORMAL, qfalse, + velocity, cmdmove, 0, 30, 0.1f, bboxmins, bboxmaxs, bot_visualizejumppads); + if (move.frames < 30) + { + bestareanum = 0; + bestareavolume = 0; + for (link = areas; link; link = link->next_area) + { + if (!AAS_AreaJumpPad(link->areanum)) continue; + volume = AAS_AreaVolume(link->areanum); + if (volume >= bestareavolume) + { + bestareanum = link->areanum; + bestareavolume = volume; + } //end if + } //end if + AAS_UnlinkFromAreas(areas); + return bestareanum; + } //end if + AAS_UnlinkFromAreas(areas); + } //end for + return 0; +} //end of the function AAS_BestReachableFromJumpPadArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BestReachableArea(vec3_t origin, vec3_t mins, vec3_t maxs, vec3_t goalorigin) +{ + int areanum, i, j, k, l; + aas_link_t *areas; + vec3_t absmins, absmaxs; + //vec3_t bbmins, bbmaxs; + vec3_t start, end; + aas_trace_t trace; + + if (!aasworld.loaded) + { + botimport.Print(PRT_ERROR, "AAS_BestReachableArea: aas not loaded\n"); + return 0; + } //end if + //find a point in an area + VectorCopy(origin, start); + areanum = AAS_PointAreaNum(start); + //while no area found fudge around a little + for (i = 0; i < 5 && !areanum; i++) + { + for (j = 0; j < 5 && !areanum; j++) + { + for (k = -1; k <= 1 && !areanum; k++) + { + for (l = -1; l <= 1 && !areanum; l++) + { + VectorCopy(origin, start); + start[0] += (float) j * 4 * k; + start[1] += (float) j * 4 * l; + start[2] += (float) i * 4; + areanum = AAS_PointAreaNum(start); + } //end for + } //end for + } //end for + } //end for + //if an area was found + if (areanum) + { + //drop client bbox down and try again + VectorCopy(start, end); + start[2] += 0.25; + end[2] -= 50; + trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, -1); + if (!trace.startsolid) + { + areanum = AAS_PointAreaNum(trace.endpos); + VectorCopy(trace.endpos, goalorigin); + //FIXME: cannot enable next line right now because the reachability + // does not have to be calculated when the level items are loaded + //if the origin is in an area with reachability + //if (AAS_AreaReachability(areanum)) return areanum; + if (areanum) return areanum; + } //end if + else + { + //it can very well happen that the AAS_PointAreaNum function tells that + //a point is in an area and that starting a AAS_TraceClientBBox from that + //point will return trace.startsolid qtrue +#if 0 + if (AAS_PointAreaNum(start)) + { + Log_Write("point %f %f %f in area %d but trace startsolid", start[0], start[1], start[2], areanum); + AAS_DrawPermanentCross(start, 4, LINECOLOR_RED); + } //end if + botimport.Print(PRT_MESSAGE, "AAS_BestReachableArea: start solid\n"); +#endif + VectorCopy(start, goalorigin); + return areanum; + } //end else + } //end if + // + //AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, bbmins, bbmaxs); + //NOTE: the goal origin does not have to be in the goal area + // because the bot will have to move towards the item origin anyway + VectorCopy(origin, goalorigin); + // + VectorAdd(origin, mins, absmins); + VectorAdd(origin, maxs, absmaxs); + //add bounding box size + //VectorSubtract(absmins, bbmaxs, absmins); + //VectorSubtract(absmaxs, bbmins, absmaxs); + //link an invalid (-1) entity + areas = AAS_LinkEntityClientBBox(absmins, absmaxs, -1, PRESENCE_CROUCH); + //get the reachable link arae + areanum = AAS_BestReachableLinkArea(areas); + //unlink the invalid entity + AAS_UnlinkFromAreas(areas); + // + return areanum; +} //end of the function AAS_BestReachableArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_SetupReachabilityHeap(void) +{ + int i; + + reachabilityheap = (aas_lreachability_t *) GetClearedMemory( + AAS_MAX_REACHABILITYSIZE * sizeof(aas_lreachability_t)); + for (i = 0; i < AAS_MAX_REACHABILITYSIZE-1; i++) + { + reachabilityheap[i].next = &reachabilityheap[i+1]; + } //end for + reachabilityheap[AAS_MAX_REACHABILITYSIZE-1].next = NULL; + nextreachability = reachabilityheap; + numlreachabilities = 0; +} //end of the function AAS_InitReachabilityHeap +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShutDownReachabilityHeap(void) +{ + FreeMemory(reachabilityheap); + numlreachabilities = 0; +} //end of the function AAS_ShutDownReachabilityHeap +//=========================================================================== +// returns a reachability link +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_lreachability_t *AAS_AllocReachability(void) +{ + aas_lreachability_t *r; + + if (!nextreachability) return NULL; + //make sure the error message only shows up once + if (!nextreachability->next) AAS_Error("AAS_MAX_REACHABILITYSIZE"); + // + r = nextreachability; + nextreachability = nextreachability->next; + numlreachabilities++; + return r; +} //end of the function AAS_AllocReachability +//=========================================================================== +// frees a reachability link +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeReachability(aas_lreachability_t *lreach) +{ + Com_Memset(lreach, 0, sizeof(aas_lreachability_t)); + + lreach->next = nextreachability; + nextreachability = lreach; + numlreachabilities--; +} //end of the function AAS_FreeReachability +//=========================================================================== +// returns qtrue if the area has reachability links +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaReachability(int areanum) +{ + if (areanum < 0 || areanum >= aasworld.numareas) + { + AAS_Error("AAS_AreaReachability: areanum %d out of range", areanum); + return 0; + } //end if + return aasworld.areasettings[areanum].numreachableareas; +} //end of the function AAS_AreaReachability +//=========================================================================== +// returns the surface area of all ground faces together of the area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_AreaGroundFaceArea(int areanum) +{ + int i; + float total; + aas_area_t *area; + aas_face_t *face; + + total = 0; + area = &aasworld.areas[areanum]; + for (i = 0; i < area->numfaces; i++) + { + face = &aasworld.faces[abs(aasworld.faceindex[area->firstface + i])]; + if (!(face->faceflags & FACE_GROUND)) continue; + // + total += AAS_FaceArea(face); + } //end for + return total; +} //end of the function AAS_AreaGroundFaceArea +//=========================================================================== +// returns the center of a face +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FaceCenter(int facenum, vec3_t center) +{ + int i; + float scale; + aas_face_t *face; + aas_edge_t *edge; + + face = &aasworld.faces[facenum]; + + VectorClear(center); + for (i = 0; i < face->numedges; i++) + { + edge = &aasworld.edges[abs(aasworld.edgeindex[face->firstedge + i])]; + VectorAdd(center, aasworld.vertexes[edge->v[0]], center); + VectorAdd(center, aasworld.vertexes[edge->v[1]], center); + } //end for + scale = 0.5 / face->numedges; + VectorScale(center, scale, center); +} //end of the function AAS_FaceCenter +//=========================================================================== +// returns the maximum distance a player can fall before being damaged +// damage = deltavelocity*deltavelocity * 0.0001 +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_FallDamageDistance(void) +{ + float maxzvelocity, gravity, t; + + maxzvelocity = sqrt(30 * 10000); + gravity = aassettings.phys_gravity; + t = maxzvelocity / gravity; + return 0.5 * gravity * t * t; +} //end of the function AAS_FallDamageDistance +//=========================================================================== +// distance = 0.5 * gravity * t * t +// vel = t * gravity +// damage = vel * vel * 0.0001 +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_FallDelta(float distance) +{ + float t, delta, gravity; + + gravity = aassettings.phys_gravity; + t = sqrt(fabs(distance) * 2 / gravity); + delta = t * gravity; + return delta * delta * 0.0001; +} //end of the function AAS_FallDelta +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_MaxJumpHeight(float phys_jumpvel) +{ + float phys_gravity; + + phys_gravity = aassettings.phys_gravity; + //maximum height a player can jump with the given initial z velocity + return 0.5 * phys_gravity * (phys_jumpvel / phys_gravity) * (phys_jumpvel / phys_gravity); +} //end of the function MaxJumpHeight +//=========================================================================== +// returns true if a player can only crouch in the area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_MaxJumpDistance(float phys_jumpvel) +{ + float phys_gravity, phys_maxvelocity, t; + + phys_gravity = aassettings.phys_gravity; + phys_maxvelocity = aassettings.phys_maxvelocity; + //time a player takes to fall the height + t = sqrt(aassettings.rs_maxjumpfallheight / (0.5 * phys_gravity)); + //maximum distance + return phys_maxvelocity * (t + phys_jumpvel / phys_gravity); +} //end of the function AAS_MaxJumpDistance +//=========================================================================== +// returns true if a player can only crouch in the area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaCrouch(int areanum) +{ + if (!(aasworld.areasettings[areanum].presencetype & PRESENCE_NORMAL)) return qtrue; + else return qfalse; +} //end of the function AAS_AreaCrouch +//=========================================================================== +// returns qtrue if it is possible to swim in the area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaSwim(int areanum) +{ + if (aasworld.areasettings[areanum].areaflags & AREA_LIQUID) return qtrue; + else return qfalse; +} //end of the function AAS_AreaSwim +//=========================================================================== +// returns qtrue if the area contains a liquid +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaLiquid(int areanum) +{ + if (aasworld.areasettings[areanum].areaflags & AREA_LIQUID) return qtrue; + else return qfalse; +} //end of the function AAS_AreaLiquid +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaLava(int areanum) +{ + return (aasworld.areasettings[areanum].contents & AREACONTENTS_LAVA); +} //end of the function AAS_AreaLava +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaSlime(int areanum) +{ + return (aasworld.areasettings[areanum].contents & AREACONTENTS_SLIME); +} //end of the function AAS_AreaSlime +//=========================================================================== +// returns qtrue if the area contains ground faces +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaGrounded(int areanum) +{ + return (aasworld.areasettings[areanum].areaflags & AREA_GROUNDED); +} //end of the function AAS_AreaGround +//=========================================================================== +// returns true if the area contains ladder faces +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaLadder(int areanum) +{ + return (aasworld.areasettings[areanum].areaflags & AREA_LADDER); +} //end of the function AAS_AreaLadder +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaJumpPad(int areanum) +{ + return (aasworld.areasettings[areanum].contents & AREACONTENTS_JUMPPAD); +} //end of the function AAS_AreaJumpPad +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaTeleporter(int areanum) +{ + return (aasworld.areasettings[areanum].contents & AREACONTENTS_TELEPORTER); +} //end of the function AAS_AreaTeleporter +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaClusterPortal(int areanum) +{ + return (aasworld.areasettings[areanum].contents & AREACONTENTS_CLUSTERPORTAL); +} //end of the function AAS_AreaClusterPortal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaDoNotEnter(int areanum) +{ + return (aasworld.areasettings[areanum].contents & AREACONTENTS_DONOTENTER); +} //end of the function AAS_AreaDoNotEnter +//=========================================================================== +// returns the time it takes perform a barrier jump +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +unsigned short int AAS_BarrierJumpTravelTime(void) +{ + return aassettings.phys_jumpvel / (aassettings.phys_gravity * 0.1); +} //end op the function AAS_BarrierJumpTravelTime +//=========================================================================== +// returns true if there already exists a reachability from area1 to area2 +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_ReachabilityExists(int area1num, int area2num) +{ + aas_lreachability_t *r; + + for (r = areareachability[area1num]; r; r = r->next) + { + if (r->areanum == area2num) return qtrue; + } //end for + return qfalse; +} //end of the function AAS_ReachabilityExists +//=========================================================================== +// returns true if there is a solid just after the end point when going +// from start to end +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_NearbySolidOrGap(vec3_t start, vec3_t end) +{ + vec3_t dir, testpoint; + int areanum; + + VectorSubtract(end, start, dir); + dir[2] = 0; + VectorNormalize(dir); + VectorMA(end, 48, dir, testpoint); + + areanum = AAS_PointAreaNum(testpoint); + if (!areanum) + { + testpoint[2] += 16; + areanum = AAS_PointAreaNum(testpoint); + if (!areanum) return qtrue; + } //end if + VectorMA(end, 64, dir, testpoint); + areanum = AAS_PointAreaNum(testpoint); + if (areanum) + { + if (!AAS_AreaSwim(areanum) && !AAS_AreaGrounded(areanum)) return qtrue; + } //end if + return qfalse; +} //end of the function AAS_SolidGapTime +//=========================================================================== +// searches for swim reachabilities between adjacent areas +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Reachability_Swim(int area1num, int area2num) +{ + int i, j, face1num, face2num, side1; + aas_area_t *area1, *area2; + aas_areasettings_t *areasettings; + aas_lreachability_t *lreach; + aas_face_t *face1; + aas_plane_t *plane; + vec3_t start; + + if (!AAS_AreaSwim(area1num) || !AAS_AreaSwim(area2num)) return qfalse; + //if the second area is crouch only + if (!(aasworld.areasettings[area2num].presencetype & PRESENCE_NORMAL)) return qfalse; + + area1 = &aasworld.areas[area1num]; + area2 = &aasworld.areas[area2num]; + + //if the areas are not near anough + for (i = 0; i < 3; i++) + { + if (area1->mins[i] > area2->maxs[i] + 10) return qfalse; + if (area1->maxs[i] < area2->mins[i] - 10) return qfalse; + } //end for + //find a shared face and create a reachability link + for (i = 0; i < area1->numfaces; i++) + { + face1num = aasworld.faceindex[area1->firstface + i]; + side1 = face1num < 0; + face1num = abs(face1num); + // + for (j = 0; j < area2->numfaces; j++) + { + face2num = abs(aasworld.faceindex[area2->firstface + j]); + // + if (face1num == face2num) + { + AAS_FaceCenter(face1num, start); + // + if (AAS_PointContents(start) & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) + { + // + face1 = &aasworld.faces[face1num]; + areasettings = &aasworld.areasettings[area1num]; + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area2num; + lreach->facenum = face1num; + lreach->edgenum = 0; + VectorCopy(start, lreach->start); + plane = &aasworld.planes[face1->planenum ^ side1]; + VectorMA(lreach->start, -INSIDEUNITS, plane->normal, lreach->end); + lreach->traveltype = TRAVEL_SWIM; + lreach->traveltime = 1; + //if the volume of the area is rather small + if (AAS_AreaVolume(area2num) < 800) + lreach->traveltime += 200; + //if (!(AAS_PointContents(start) & MASK_WATER)) lreach->traveltime += 500; + //link the reachability + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + reach_swim++; + return qtrue; + } //end if + } //end if + } //end for + } //end for + return qfalse; +} //end of the function AAS_Reachability_Swim +//=========================================================================== +// searches for reachabilities between adjacent areas with equal floor +// heights +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Reachability_EqualFloorHeight(int area1num, int area2num) +{ + int i, j, edgenum, edgenum1, edgenum2, foundreach, side; + float height, bestheight, length, bestlength; + vec3_t dir, start, end, normal, invgravity, gravitydirection = {0, 0, -1}; + vec3_t edgevec; + aas_area_t *area1, *area2; + aas_face_t *face1, *face2; + aas_edge_t *edge; + aas_plane_t *plane2; + aas_lreachability_t lr, *lreach; + + if (!AAS_AreaGrounded(area1num) || !AAS_AreaGrounded(area2num)) return qfalse; + + area1 = &aasworld.areas[area1num]; + area2 = &aasworld.areas[area2num]; + //if the areas are not near anough in the x-y direction + for (i = 0; i < 2; i++) + { + if (area1->mins[i] > area2->maxs[i] + 10) return qfalse; + if (area1->maxs[i] < area2->mins[i] - 10) return qfalse; + } //end for + //if area 2 is too high above area 1 + if (area2->mins[2] > area1->maxs[2]) return qfalse; + // + VectorCopy(gravitydirection, invgravity); + VectorInverse(invgravity); + // + bestheight = 99999; + bestlength = 0; + foundreach = qfalse; + Com_Memset(&lr, 0, sizeof(aas_lreachability_t)); //make the compiler happy + // + //check if the areas have ground faces with a common edge + //if existing use the lowest common edge for a reachability link + for (i = 0; i < area1->numfaces; i++) + { + face1 = &aasworld.faces[abs(aasworld.faceindex[area1->firstface + i])]; + if (!(face1->faceflags & FACE_GROUND)) continue; + // + for (j = 0; j < area2->numfaces; j++) + { + face2 = &aasworld.faces[abs(aasworld.faceindex[area2->firstface + j])]; + if (!(face2->faceflags & FACE_GROUND)) continue; + //if there is a common edge + for (edgenum1 = 0; edgenum1 < face1->numedges; edgenum1++) + { + for (edgenum2 = 0; edgenum2 < face2->numedges; edgenum2++) + { + if (abs(aasworld.edgeindex[face1->firstedge + edgenum1]) != + abs(aasworld.edgeindex[face2->firstedge + edgenum2])) + continue; + edgenum = aasworld.edgeindex[face1->firstedge + edgenum1]; + side = edgenum < 0; + edge = &aasworld.edges[abs(edgenum)]; + //get the length of the edge + VectorSubtract(aasworld.vertexes[edge->v[1]], + aasworld.vertexes[edge->v[0]], dir); + length = VectorLength(dir); + //get the start point + VectorAdd(aasworld.vertexes[edge->v[0]], + aasworld.vertexes[edge->v[1]], start); + VectorScale(start, 0.5, start); + VectorCopy(start, end); + //get the end point several units inside area2 + //and the start point several units inside area1 + //NOTE: normal is pointing into area2 because the + //face edges are stored counter clockwise + VectorSubtract(aasworld.vertexes[edge->v[side]], + aasworld.vertexes[edge->v[!side]], edgevec); + plane2 = &aasworld.planes[face2->planenum]; + CrossProduct(edgevec, plane2->normal, normal); + VectorNormalize(normal); + // + //VectorMA(start, -1, normal, start); + VectorMA(end, INSIDEUNITS_WALKEND, normal, end); + VectorMA(start, INSIDEUNITS_WALKSTART, normal, start); + end[2] += 0.125; + // + height = DotProduct(invgravity, start); + //NOTE: if there's nearby solid or a gap area after this area + //disabled this crap + //if (AAS_NearbySolidOrGap(start, end)) height += 200; + //NOTE: disabled because it disables reachabilities to very small areas + //if (AAS_PointAreaNum(end) != area2num) continue; + //get the longest lowest edge + if (height < bestheight || + (height < bestheight + 1 && length > bestlength)) + { + bestheight = height; + bestlength = length; + //create a new reachability link + lr.areanum = area2num; + lr.facenum = 0; + lr.edgenum = edgenum; + VectorCopy(start, lr.start); + VectorCopy(end, lr.end); + lr.traveltype = TRAVEL_WALK; + lr.traveltime = 1; + foundreach = qtrue; + } //end if + } //end for + } //end for + } //end for + } //end for + if (foundreach) + { + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = lr.areanum; + lreach->facenum = lr.facenum; + lreach->edgenum = lr.edgenum; + VectorCopy(lr.start, lreach->start); + VectorCopy(lr.end, lreach->end); + lreach->traveltype = lr.traveltype; + lreach->traveltime = lr.traveltime; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + //if going into a crouch area + if (!AAS_AreaCrouch(area1num) && AAS_AreaCrouch(area2num)) + { + lreach->traveltime += aassettings.rs_startcrouch; + } //end if + /* + //NOTE: if there's nearby solid or a gap area after this area + if (!AAS_NearbySolidOrGap(lreach->start, lreach->end)) + { + lreach->traveltime += 100; + } //end if + */ + //avoid rather small areas + //if (AAS_AreaGroundFaceArea(lreach->areanum) < 500) lreach->traveltime += 100; + // + reach_equalfloor++; + return qtrue; + } //end if + return qfalse; +} //end of the function AAS_Reachability_EqualFloorHeight +//=========================================================================== +// searches step, barrier, waterjump and walk off ledge reachabilities +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Reachability_Step_Barrier_WaterJump_WalkOffLedge(int area1num, int area2num) +{ + int i, j, k, l, edge1num, edge2num, areas[10], numareas; + int ground_bestarea2groundedgenum, ground_foundreach; + int water_bestarea2groundedgenum, water_foundreach; + int side1, area1swim, faceside1, groundface1num; + float dist, dist1, dist2, diff, invgravitydot, ortdot; + float x1, x2, x3, x4, y1, y2, y3, y4, tmp, y; + float length, ground_bestlength, water_bestlength, ground_bestdist, water_bestdist; + vec3_t v1, v2, v3, v4, tmpv, p1area1, p1area2, p2area1, p2area2; + vec3_t normal, ort, edgevec, start, end, dir; + vec3_t ground_beststart, ground_bestend, ground_bestnormal; + vec3_t water_beststart, water_bestend, water_bestnormal; + vec3_t invgravity = {0, 0, 1}; + vec3_t testpoint; + aas_plane_t *plane; + aas_area_t *area1, *area2; + aas_face_t *groundface1, *groundface2, *ground_bestface1, *water_bestface1; + aas_edge_t *edge1, *edge2; + aas_lreachability_t *lreach; + aas_trace_t trace; + + //must be able to walk or swim in the first area + if (!AAS_AreaGrounded(area1num) && !AAS_AreaSwim(area1num)) return qfalse; + // + if (!AAS_AreaGrounded(area2num) && !AAS_AreaSwim(area2num)) return qfalse; + // + area1 = &aasworld.areas[area1num]; + area2 = &aasworld.areas[area2num]; + //if the first area contains a liquid + area1swim = AAS_AreaSwim(area1num); + //if the areas are not near anough in the x-y direction + for (i = 0; i < 2; i++) + { + if (area1->mins[i] > area2->maxs[i] + 10) return qfalse; + if (area1->maxs[i] < area2->mins[i] - 10) return qfalse; + } //end for + // + ground_foundreach = qfalse; + ground_bestdist = 99999; + ground_bestlength = 0; + ground_bestarea2groundedgenum = 0; + // + water_foundreach = qfalse; + water_bestdist = 99999; + water_bestlength = 0; + water_bestarea2groundedgenum = 0; + // + for (i = 0; i < area1->numfaces; i++) + { + groundface1num = aasworld.faceindex[area1->firstface + i]; + faceside1 = groundface1num < 0; + groundface1 = &aasworld.faces[abs(groundface1num)]; + //if this isn't a ground face + if (!(groundface1->faceflags & FACE_GROUND)) + { + //if we can swim in the first area + if (area1swim) + { + //face plane must be more or less horizontal + plane = &aasworld.planes[groundface1->planenum ^ (!faceside1)]; + if (DotProduct(plane->normal, invgravity) < 0.7) continue; + } //end if + else + { + //if we can't swim in the area it must be a ground face + continue; + } //end else + } //end if + // + for (k = 0; k < groundface1->numedges; k++) + { + edge1num = aasworld.edgeindex[groundface1->firstedge + k]; + side1 = (edge1num < 0); + //NOTE: for water faces we must take the side area 1 is + // on into account because the face is shared and doesn't + // have to be oriented correctly + if (!(groundface1->faceflags & FACE_GROUND)) side1 = (side1 == faceside1); + edge1num = abs(edge1num); + edge1 = &aasworld.edges[edge1num]; + //vertexes of the edge + VectorCopy(aasworld.vertexes[edge1->v[!side1]], v1); + VectorCopy(aasworld.vertexes[edge1->v[side1]], v2); + //get a vertical plane through the edge + //NOTE: normal is pointing into area 2 because the + //face edges are stored counter clockwise + VectorSubtract(v2, v1, edgevec); + CrossProduct(edgevec, invgravity, normal); + VectorNormalize(normal); + dist = DotProduct(normal, v1); + //check the faces from the second area + for (j = 0; j < area2->numfaces; j++) + { + groundface2 = &aasworld.faces[abs(aasworld.faceindex[area2->firstface + j])]; + //must be a ground face + if (!(groundface2->faceflags & FACE_GROUND)) continue; + //check the edges of this ground face + for (l = 0; l < groundface2->numedges; l++) + { + edge2num = abs(aasworld.edgeindex[groundface2->firstedge + l]); + edge2 = &aasworld.edges[edge2num]; + //vertexes of the edge + VectorCopy(aasworld.vertexes[edge2->v[0]], v3); + VectorCopy(aasworld.vertexes[edge2->v[1]], v4); + //check the distance between the two points and the vertical plane + //through the edge of area1 + diff = DotProduct(normal, v3) - dist; + if (diff < -0.1 || diff > 0.1) continue; + diff = DotProduct(normal, v4) - dist; + if (diff < -0.1 || diff > 0.1) continue; + // + //project the two ground edges into the step side plane + //and calculate the shortest distance between the two + //edges if they overlap in the direction orthogonal to + //the gravity direction + CrossProduct(invgravity, normal, ort); + invgravitydot = DotProduct(invgravity, invgravity); + ortdot = DotProduct(ort, ort); + //projection into the step plane + //NOTE: since gravity is vertical this is just the z coordinate + y1 = v1[2];//DotProduct(v1, invgravity) / invgravitydot; + y2 = v2[2];//DotProduct(v2, invgravity) / invgravitydot; + y3 = v3[2];//DotProduct(v3, invgravity) / invgravitydot; + y4 = v4[2];//DotProduct(v4, invgravity) / invgravitydot; + // + x1 = DotProduct(v1, ort) / ortdot; + x2 = DotProduct(v2, ort) / ortdot; + x3 = DotProduct(v3, ort) / ortdot; + x4 = DotProduct(v4, ort) / ortdot; + // + if (x1 > x2) + { + tmp = x1; x1 = x2; x2 = tmp; + tmp = y1; y1 = y2; y2 = tmp; + VectorCopy(v1, tmpv); VectorCopy(v2, v1); VectorCopy(tmpv, v2); + } //end if + if (x3 > x4) + { + tmp = x3; x3 = x4; x4 = tmp; + tmp = y3; y3 = y4; y4 = tmp; + VectorCopy(v3, tmpv); VectorCopy(v4, v3); VectorCopy(tmpv, v4); + } //end if + //if the two projected edge lines have no overlap + if (x2 <= x3 || x4 <= x1) + { +// Log_Write("lines no overlap: from area %d to %d\r\n", area1num, area2num); + continue; + } //end if + //if the two lines fully overlap + if ((x1 - 0.5 < x3 && x4 < x2 + 0.5) && + (x3 - 0.5 < x1 && x2 < x4 + 0.5)) + { + dist1 = y3 - y1; + dist2 = y4 - y2; + VectorCopy(v1, p1area1); + VectorCopy(v2, p2area1); + VectorCopy(v3, p1area2); + VectorCopy(v4, p2area2); + } //end if + else + { + //if the points are equal + if (x1 > x3 - 0.1 && x1 < x3 + 0.1) + { + dist1 = y3 - y1; + VectorCopy(v1, p1area1); + VectorCopy(v3, p1area2); + } //end if + else if (x1 < x3) + { + y = y1 + (x3 - x1) * (y2 - y1) / (x2 - x1); + dist1 = y3 - y; + VectorCopy(v3, p1area1); + p1area1[2] = y; + VectorCopy(v3, p1area2); + } //end if + else + { + y = y3 + (x1 - x3) * (y4 - y3) / (x4 - x3); + dist1 = y - y1; + VectorCopy(v1, p1area1); + VectorCopy(v1, p1area2); + p1area2[2] = y; + } //end if + //if the points are equal + if (x2 > x4 - 0.1 && x2 < x4 + 0.1) + { + dist2 = y4 - y2; + VectorCopy(v2, p2area1); + VectorCopy(v4, p2area2); + } //end if + else if (x2 < x4) + { + y = y3 + (x2 - x3) * (y4 - y3) / (x4 - x3); + dist2 = y - y2; + VectorCopy(v2, p2area1); + VectorCopy(v2, p2area2); + p2area2[2] = y; + } //end if + else + { + y = y1 + (x4 - x1) * (y2 - y1) / (x2 - x1); + dist2 = y4 - y; + VectorCopy(v4, p2area1); + p2area1[2] = y; + VectorCopy(v4, p2area2); + } //end else + } //end else + //if both distances are pretty much equal + //then we take the middle of the points + if (dist1 > dist2 - 1 && dist1 < dist2 + 1) + { + dist = dist1; + VectorAdd(p1area1, p2area1, start); + VectorScale(start, 0.5, start); + VectorAdd(p1area2, p2area2, end); + VectorScale(end, 0.5, end); + } //end if + else if (dist1 < dist2) + { + dist = dist1; + VectorCopy(p1area1, start); + VectorCopy(p1area2, end); + } //end else if + else + { + dist = dist2; + VectorCopy(p2area1, start); + VectorCopy(p2area2, end); + } //end else + //get the length of the overlapping part of the edges of the two areas + VectorSubtract(p2area2, p1area2, dir); + length = VectorLength(dir); + // + if (groundface1->faceflags & FACE_GROUND) + { + //if the vertical distance is smaller + if (dist < ground_bestdist || + //or the vertical distance is pretty much the same + //but the overlapping part of the edges is longer + (dist < ground_bestdist + 1 && length > ground_bestlength)) + { + ground_bestdist = dist; + ground_bestlength = length; + ground_foundreach = qtrue; + ground_bestarea2groundedgenum = edge1num; + ground_bestface1 = groundface1; + //best point towards area1 + VectorCopy(start, ground_beststart); + //normal is pointing into area2 + VectorCopy(normal, ground_bestnormal); + //best point towards area2 + VectorCopy(end, ground_bestend); + } //end if + } //end if + else + { + //if the vertical distance is smaller + if (dist < water_bestdist || + //or the vertical distance is pretty much the same + //but the overlapping part of the edges is longer + (dist < water_bestdist + 1 && length > water_bestlength)) + { + water_bestdist = dist; + water_bestlength = length; + water_foundreach = qtrue; + water_bestarea2groundedgenum = edge1num; + water_bestface1 = groundface1; + //best point towards area1 + VectorCopy(start, water_beststart); + //normal is pointing into area2 + VectorCopy(normal, water_bestnormal); + //best point towards area2 + VectorCopy(end, water_bestend); + } //end if + } //end else + } //end for + } //end for + } //end for + } //end for + // + // NOTE: swim reachabilities are already filtered out + // + // Steps + // + // --------- + // | step height -> TRAVEL_WALK + //--------| + // + // --------- + //~~~~~~~~| step height and low water -> TRAVEL_WALK + //--------| + // + //~~~~~~~~~~~~~~~~~~ + // --------- + // | step height and low water up to the step -> TRAVEL_WALK + //--------| + // + //check for a step reachability + if (ground_foundreach) + { + //if area2 is higher but lower than the maximum step height + //NOTE: ground_bestdist >= 0 also catches equal floor reachabilities + if (ground_bestdist >= 0 && ground_bestdist < aassettings.phys_maxstep) + { + //create walk reachability from area1 to area2 + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area2num; + lreach->facenum = 0; + lreach->edgenum = ground_bestarea2groundedgenum; + VectorMA(ground_beststart, INSIDEUNITS_WALKSTART, ground_bestnormal, lreach->start); + VectorMA(ground_bestend, INSIDEUNITS_WALKEND, ground_bestnormal, lreach->end); + lreach->traveltype = TRAVEL_WALK; + lreach->traveltime = 0;//1; + //if going into a crouch area + if (!AAS_AreaCrouch(area1num) && AAS_AreaCrouch(area2num)) + { + lreach->traveltime += aassettings.rs_startcrouch; + } //end if + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + //NOTE: if there's nearby solid or a gap area after this area + /* + if (!AAS_NearbySolidOrGap(lreach->start, lreach->end)) + { + lreach->traveltime += 100; + } //end if + */ + //avoid rather small areas + //if (AAS_AreaGroundFaceArea(lreach->areanum) < 500) lreach->traveltime += 100; + // + reach_step++; + return qtrue; + } //end if + } //end if + // + // Water Jumps + // + // --------- + // | + //~~~~~~~~| + // | + // | higher than step height and water up to waterjump height -> TRAVEL_WATERJUMP + //--------| + // + //~~~~~~~~~~~~~~~~~~ + // --------- + // | + // | + // | + // | higher than step height and low water up to the step -> TRAVEL_WATERJUMP + //--------| + // + //check for a waterjump reachability + if (water_foundreach) + { + //get a test point a little bit towards area1 + VectorMA(water_bestend, -INSIDEUNITS, water_bestnormal, testpoint); + //go down the maximum waterjump height + testpoint[2] -= aassettings.phys_maxwaterjump; + //if there IS water the sv_maxwaterjump height below the bestend point + if (aasworld.areasettings[AAS_PointAreaNum(testpoint)].areaflags & AREA_LIQUID) + { + //don't create rediculous water jump reachabilities from areas very far below + //the water surface + if (water_bestdist < aassettings.phys_maxwaterjump + 24) + { + //waterjumping from or towards a crouch only area is not possible in Quake2 + if ((aasworld.areasettings[area1num].presencetype & PRESENCE_NORMAL) && + (aasworld.areasettings[area2num].presencetype & PRESENCE_NORMAL)) + { + //create water jump reachability from area1 to area2 + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area2num; + lreach->facenum = 0; + lreach->edgenum = water_bestarea2groundedgenum; + VectorCopy(water_beststart, lreach->start); + VectorMA(water_bestend, INSIDEUNITS_WATERJUMP, water_bestnormal, lreach->end); + lreach->traveltype = TRAVEL_WATERJUMP; + lreach->traveltime = aassettings.rs_waterjump; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + //we've got another waterjump reachability + reach_waterjump++; + return qtrue; + } //end if + } //end if + } //end if + } //end if + // + // Barrier Jumps + // + // --------- + // | + // | + // | + // | higher than step height lower than barrier height -> TRAVEL_BARRIERJUMP + //--------| + // + // --------- + // | + // | + // | + //~~~~~~~~| higher than step height lower than barrier height + //--------| and a thin layer of water in the area to jump from -> TRAVEL_BARRIERJUMP + // + //check for a barrier jump reachability + if (ground_foundreach) + { + //if area2 is higher but lower than the maximum barrier jump height + if (ground_bestdist > 0 && ground_bestdist < aassettings.phys_maxbarrier) + { + //if no water in area1 or a very thin layer of water on the ground + if (!water_foundreach || (ground_bestdist - water_bestdist < 16)) + { + //cannot perform a barrier jump towards or from a crouch area in Quake2 + if (!AAS_AreaCrouch(area1num) && !AAS_AreaCrouch(area2num)) + { + //create barrier jump reachability from area1 to area2 + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area2num; + lreach->facenum = 0; + lreach->edgenum = ground_bestarea2groundedgenum; + VectorMA(ground_beststart, INSIDEUNITS_WALKSTART, ground_bestnormal, lreach->start); + VectorMA(ground_bestend, INSIDEUNITS_WALKEND, ground_bestnormal, lreach->end); + lreach->traveltype = TRAVEL_BARRIERJUMP; + lreach->traveltime = aassettings.rs_barrierjump;//AAS_BarrierJumpTravelTime(); + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + //we've got another barrierjump reachability + reach_barrier++; + return qtrue; + } //end if + } //end if + } //end if + } //end if + // + // Walk and Walk Off Ledge + // + //--------| + // | can walk or step back -> TRAVEL_WALK + // --------- + // + //--------| + // | + // | + // | + // | cannot walk/step back -> TRAVEL_WALKOFFLEDGE + // --------- + // + //--------| + // | + // |~~~~~~~~ + // | + // | cannot step back but can waterjump back -> TRAVEL_WALKOFFLEDGE + // --------- FIXME: create TRAVEL_WALK reach?? + // + //check for a walk or walk off ledge reachability + if (ground_foundreach) + { + if (ground_bestdist < 0) + { + if (ground_bestdist > -aassettings.phys_maxstep) + { + //create walk reachability from area1 to area2 + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area2num; + lreach->facenum = 0; + lreach->edgenum = ground_bestarea2groundedgenum; + VectorMA(ground_beststart, INSIDEUNITS_WALKSTART, ground_bestnormal, lreach->start); + VectorMA(ground_bestend, INSIDEUNITS_WALKEND, ground_bestnormal, lreach->end); + lreach->traveltype = TRAVEL_WALK; + lreach->traveltime = 1; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + //we've got another walk reachability + reach_walk++; + return qtrue; + } //end if + // if no maximum fall height set or less than the max + if (!aassettings.rs_maxfallheight || fabs(ground_bestdist) < aassettings.rs_maxfallheight) { + //trace a bounding box vertically to check for solids + VectorMA(ground_bestend, INSIDEUNITS, ground_bestnormal, ground_bestend); + VectorCopy(ground_bestend, start); + start[2] = ground_beststart[2]; + VectorCopy(ground_bestend, end); + end[2] += 4; + trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, -1); + //if no solids were found + if (!trace.startsolid && trace.fraction >= 1.0) + { + //the trace end point must be in the goal area + trace.endpos[2] += 1; + if (AAS_PointAreaNum(trace.endpos) == area2num) + { + //if not going through a cluster portal + numareas = AAS_TraceAreas(start, end, areas, NULL, sizeof(areas) / sizeof(int)); + for (i = 0; i < numareas; i++) + if (AAS_AreaClusterPortal(areas[i])) + break; + if (i >= numareas) + { + //create a walk off ledge reachability from area1 to area2 + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area2num; + lreach->facenum = 0; + lreach->edgenum = ground_bestarea2groundedgenum; + VectorCopy(ground_beststart, lreach->start); + VectorCopy(ground_bestend, lreach->end); + lreach->traveltype = TRAVEL_WALKOFFLEDGE; + lreach->traveltime = aassettings.rs_startwalkoffledge + fabs(ground_bestdist) * 50 / aassettings.phys_gravity; + //if falling from too high and not falling into water + if (!AAS_AreaSwim(area2num) && !AAS_AreaJumpPad(area2num)) + { + if (AAS_FallDelta(ground_bestdist) > aassettings.phys_falldelta5) + { + lreach->traveltime += aassettings.rs_falldamage5; + } //end if + if (AAS_FallDelta(ground_bestdist) > aassettings.phys_falldelta10) + { + lreach->traveltime += aassettings.rs_falldamage10; + } //end if + } //end if + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + // + reach_walkoffledge++; + //NOTE: don't create a weapon (rl, bfg) jump reachability here + //because it interferes with other reachabilities + //like the ladder reachability + return qtrue; + } //end if + } //end if + } //end if + } //end if + } //end else + } //end if + return qfalse; +} //end of the function AAS_Reachability_Step_Barrier_WaterJump_WalkOffLedge +//=========================================================================== +// returns the distance between the two vectors +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float VectorDistance(vec3_t v1, vec3_t v2) +{ + vec3_t dir; + + VectorSubtract(v2, v1, dir); + return VectorLength(dir); +} //end of the function VectorDistance +//=========================================================================== +// returns true if the first vector is between the last two vectors +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int VectorBetweenVectors(vec3_t v, vec3_t v1, vec3_t v2) +{ + vec3_t dir1, dir2; + + VectorSubtract(v, v1, dir1); + VectorSubtract(v, v2, dir2); + return (DotProduct(dir1, dir2) <= 0); +} //end of the function VectorBetweenVectors +//=========================================================================== +// returns the mid point between the two vectors +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void VectorMiddle(vec3_t v1, vec3_t v2, vec3_t middle) +{ + VectorAdd(v1, v2, middle); + VectorScale(middle, 0.5, middle); +} //end of the function VectorMiddle +//=========================================================================== +// calculate a range of points closest to each other on both edges +// +// Parameter: beststart1 start of the range of points on edge v1-v2 +// beststart2 end of the range of points on edge v1-v2 +// bestend1 start of the range of points on edge v3-v4 +// bestend2 end of the range of points on edge v3-v4 +// bestdist best distance so far +// Returns: - +// Changes Globals: - +//=========================================================================== +/* +float AAS_ClosestEdgePoints(vec3_t v1, vec3_t v2, vec3_t v3, vec3_t v4, + aas_plane_t *plane1, aas_plane_t *plane2, + vec3_t beststart, vec3_t bestend, float bestdist) +{ + vec3_t dir1, dir2, p1, p2, p3, p4; + float a1, a2, b1, b2, dist; + int founddist; + + //edge vectors + VectorSubtract(v2, v1, dir1); + VectorSubtract(v4, v3, dir2); + //get the horizontal directions + dir1[2] = 0; + dir2[2] = 0; + // + // p1 = point on an edge vector of area2 closest to v1 + // p2 = point on an edge vector of area2 closest to v2 + // p3 = point on an edge vector of area1 closest to v3 + // p4 = point on an edge vector of area1 closest to v4 + // + if (dir2[0]) + { + a2 = dir2[1] / dir2[0]; + b2 = v3[1] - a2 * v3[0]; + //point on the edge vector of area2 closest to v1 + p1[0] = (DotProduct(v1, dir2) - (a2 * dir2[0] + b2 * dir2[1])) / dir2[0]; + p1[1] = a2 * p1[0] + b2; + //point on the edge vector of area2 closest to v2 + p2[0] = (DotProduct(v2, dir2) - (a2 * dir2[0] + b2 * dir2[1])) / dir2[0]; + p2[1] = a2 * p2[0] + b2; + } //end if + else + { + //point on the edge vector of area2 closest to v1 + p1[0] = v3[0]; + p1[1] = v1[1]; + //point on the edge vector of area2 closest to v2 + p2[0] = v3[0]; + p2[1] = v2[1]; + } //end else + // + if (dir1[0]) + { + // + a1 = dir1[1] / dir1[0]; + b1 = v1[1] - a1 * v1[0]; + //point on the edge vector of area1 closest to v3 + p3[0] = (DotProduct(v3, dir1) - (a1 * dir1[0] + b1 * dir1[1])) / dir1[0]; + p3[1] = a1 * p3[0] + b1; + //point on the edge vector of area1 closest to v4 + p4[0] = (DotProduct(v4, dir1) - (a1 * dir1[0] + b1 * dir1[1])) / dir1[0]; + p4[1] = a1 * p4[0] + b1; + } //end if + else + { + //point on the edge vector of area1 closest to v3 + p3[0] = v1[0]; + p3[1] = v3[1]; + //point on the edge vector of area1 closest to v4 + p4[0] = v1[0]; + p4[1] = v4[1]; + } //end else + //start with zero z-coordinates + p1[2] = 0; + p2[2] = 0; + p3[2] = 0; + p4[2] = 0; + //calculate the z-coordinates from the ground planes + p1[2] = (plane2->dist - DotProduct(plane2->normal, p1)) / plane2->normal[2]; + p2[2] = (plane2->dist - DotProduct(plane2->normal, p2)) / plane2->normal[2]; + p3[2] = (plane1->dist - DotProduct(plane1->normal, p3)) / plane1->normal[2]; + p4[2] = (plane1->dist - DotProduct(plane1->normal, p4)) / plane1->normal[2]; + // + founddist = qfalse; + // + if (VectorBetweenVectors(p1, v3, v4)) + { + dist = VectorDistance(v1, p1); + if (dist > bestdist - 0.5 && dist < bestdist + 0.5) + { + VectorMiddle(beststart, v1, beststart); + VectorMiddle(bestend, p1, bestend); + } //end if + else if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v1, beststart); + VectorCopy(p1, bestend); + } //end if + founddist = qtrue; + } //end if + if (VectorBetweenVectors(p2, v3, v4)) + { + dist = VectorDistance(v2, p2); + if (dist > bestdist - 0.5 && dist < bestdist + 0.5) + { + VectorMiddle(beststart, v2, beststart); + VectorMiddle(bestend, p2, bestend); + } //end if + else if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v2, beststart); + VectorCopy(p2, bestend); + } //end if + founddist = qtrue; + } //end else if + if (VectorBetweenVectors(p3, v1, v2)) + { + dist = VectorDistance(v3, p3); + if (dist > bestdist - 0.5 && dist < bestdist + 0.5) + { + VectorMiddle(beststart, p3, beststart); + VectorMiddle(bestend, v3, bestend); + } //end if + else if (dist < bestdist) + { + bestdist = dist; + VectorCopy(p3, beststart); + VectorCopy(v3, bestend); + } //end if + founddist = qtrue; + } //end else if + if (VectorBetweenVectors(p4, v1, v2)) + { + dist = VectorDistance(v4, p4); + if (dist > bestdist - 0.5 && dist < bestdist + 0.5) + { + VectorMiddle(beststart, p4, beststart); + VectorMiddle(bestend, v4, bestend); + } //end if + else if (dist < bestdist) + { + bestdist = dist; + VectorCopy(p4, beststart); + VectorCopy(v4, bestend); + } //end if + founddist = qtrue; + } //end else if + //if no shortest distance was found the shortest distance + //is between one of the vertexes of edge1 and one of edge2 + if (!founddist) + { + dist = VectorDistance(v1, v3); + if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v1, beststart); + VectorCopy(v3, bestend); + } //end if + dist = VectorDistance(v1, v4); + if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v1, beststart); + VectorCopy(v4, bestend); + } //end if + dist = VectorDistance(v2, v3); + if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v2, beststart); + VectorCopy(v3, bestend); + } //end if + dist = VectorDistance(v2, v4); + if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v2, beststart); + VectorCopy(v4, bestend); + } //end if + } //end if + return bestdist; +} //end of the function AAS_ClosestEdgePoints*/ + +float AAS_ClosestEdgePoints(vec3_t v1, vec3_t v2, vec3_t v3, vec3_t v4, + aas_plane_t *plane1, aas_plane_t *plane2, + vec3_t beststart1, vec3_t bestend1, + vec3_t beststart2, vec3_t bestend2, float bestdist) +{ + vec3_t dir1, dir2, p1, p2, p3, p4; + float a1, a2, b1, b2, dist, dist1, dist2; + int founddist; + + //edge vectors + VectorSubtract(v2, v1, dir1); + VectorSubtract(v4, v3, dir2); + //get the horizontal directions + dir1[2] = 0; + dir2[2] = 0; + // + // p1 = point on an edge vector of area2 closest to v1 + // p2 = point on an edge vector of area2 closest to v2 + // p3 = point on an edge vector of area1 closest to v3 + // p4 = point on an edge vector of area1 closest to v4 + // + if (dir2[0]) + { + a2 = dir2[1] / dir2[0]; + b2 = v3[1] - a2 * v3[0]; + //point on the edge vector of area2 closest to v1 + p1[0] = (DotProduct(v1, dir2) - (a2 * dir2[0] + b2 * dir2[1])) / dir2[0]; + p1[1] = a2 * p1[0] + b2; + //point on the edge vector of area2 closest to v2 + p2[0] = (DotProduct(v2, dir2) - (a2 * dir2[0] + b2 * dir2[1])) / dir2[0]; + p2[1] = a2 * p2[0] + b2; + } //end if + else + { + //point on the edge vector of area2 closest to v1 + p1[0] = v3[0]; + p1[1] = v1[1]; + //point on the edge vector of area2 closest to v2 + p2[0] = v3[0]; + p2[1] = v2[1]; + } //end else + // + if (dir1[0]) + { + // + a1 = dir1[1] / dir1[0]; + b1 = v1[1] - a1 * v1[0]; + //point on the edge vector of area1 closest to v3 + p3[0] = (DotProduct(v3, dir1) - (a1 * dir1[0] + b1 * dir1[1])) / dir1[0]; + p3[1] = a1 * p3[0] + b1; + //point on the edge vector of area1 closest to v4 + p4[0] = (DotProduct(v4, dir1) - (a1 * dir1[0] + b1 * dir1[1])) / dir1[0]; + p4[1] = a1 * p4[0] + b1; + } //end if + else + { + //point on the edge vector of area1 closest to v3 + p3[0] = v1[0]; + p3[1] = v3[1]; + //point on the edge vector of area1 closest to v4 + p4[0] = v1[0]; + p4[1] = v4[1]; + } //end else + //start with zero z-coordinates + p1[2] = 0; + p2[2] = 0; + p3[2] = 0; + p4[2] = 0; + //calculate the z-coordinates from the ground planes + p1[2] = (plane2->dist - DotProduct(plane2->normal, p1)) / plane2->normal[2]; + p2[2] = (plane2->dist - DotProduct(plane2->normal, p2)) / plane2->normal[2]; + p3[2] = (plane1->dist - DotProduct(plane1->normal, p3)) / plane1->normal[2]; + p4[2] = (plane1->dist - DotProduct(plane1->normal, p4)) / plane1->normal[2]; + // + founddist = qfalse; + // + if (VectorBetweenVectors(p1, v3, v4)) + { + dist = VectorDistance(v1, p1); + if (dist > bestdist - 0.5 && dist < bestdist + 0.5) + { + dist1 = VectorDistance(beststart1, v1); + dist2 = VectorDistance(beststart2, v1); + if (dist1 > dist2) + { + if (dist1 > VectorDistance(beststart1, beststart2)) VectorCopy(v1, beststart2); + } //end if + else + { + if (dist2 > VectorDistance(beststart1, beststart2)) VectorCopy(v1, beststart1); + } //end else + dist1 = VectorDistance(bestend1, p1); + dist2 = VectorDistance(bestend2, p1); + if (dist1 > dist2) + { + if (dist1 > VectorDistance(bestend1, bestend2)) VectorCopy(p1, bestend2); + } //end if + else + { + if (dist2 > VectorDistance(bestend1, bestend2)) VectorCopy(p1, bestend1); + } //end else + } //end if + else if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v1, beststart1); + VectorCopy(v1, beststart2); + VectorCopy(p1, bestend1); + VectorCopy(p1, bestend2); + } //end if + founddist = qtrue; + } //end if + if (VectorBetweenVectors(p2, v3, v4)) + { + dist = VectorDistance(v2, p2); + if (dist > bestdist - 0.5 && dist < bestdist + 0.5) + { + dist1 = VectorDistance(beststart1, v2); + dist2 = VectorDistance(beststart2, v2); + if (dist1 > dist2) + { + if (dist1 > VectorDistance(beststart1, beststart2)) VectorCopy(v2, beststart2); + } //end if + else + { + if (dist2 > VectorDistance(beststart1, beststart2)) VectorCopy(v2, beststart1); + } //end else + dist1 = VectorDistance(bestend1, p2); + dist2 = VectorDistance(bestend2, p2); + if (dist1 > dist2) + { + if (dist1 > VectorDistance(bestend1, bestend2)) VectorCopy(p2, bestend2); + } //end if + else + { + if (dist2 > VectorDistance(bestend1, bestend2)) VectorCopy(p2, bestend1); + } //end else + } //end if + else if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v2, beststart1); + VectorCopy(v2, beststart2); + VectorCopy(p2, bestend1); + VectorCopy(p2, bestend2); + } //end if + founddist = qtrue; + } //end else if + if (VectorBetweenVectors(p3, v1, v2)) + { + dist = VectorDistance(v3, p3); + if (dist > bestdist - 0.5 && dist < bestdist + 0.5) + { + dist1 = VectorDistance(beststart1, p3); + dist2 = VectorDistance(beststart2, p3); + if (dist1 > dist2) + { + if (dist1 > VectorDistance(beststart1, beststart2)) VectorCopy(p3, beststart2); + } //end if + else + { + if (dist2 > VectorDistance(beststart1, beststart2)) VectorCopy(p3, beststart1); + } //end else + dist1 = VectorDistance(bestend1, v3); + dist2 = VectorDistance(bestend2, v3); + if (dist1 > dist2) + { + if (dist1 > VectorDistance(bestend1, bestend2)) VectorCopy(v3, bestend2); + } //end if + else + { + if (dist2 > VectorDistance(bestend1, bestend2)) VectorCopy(v3, bestend1); + } //end else + } //end if + else if (dist < bestdist) + { + bestdist = dist; + VectorCopy(p3, beststart1); + VectorCopy(p3, beststart2); + VectorCopy(v3, bestend1); + VectorCopy(v3, bestend2); + } //end if + founddist = qtrue; + } //end else if + if (VectorBetweenVectors(p4, v1, v2)) + { + dist = VectorDistance(v4, p4); + if (dist > bestdist - 0.5 && dist < bestdist + 0.5) + { + dist1 = VectorDistance(beststart1, p4); + dist2 = VectorDistance(beststart2, p4); + if (dist1 > dist2) + { + if (dist1 > VectorDistance(beststart1, beststart2)) VectorCopy(p4, beststart2); + } //end if + else + { + if (dist2 > VectorDistance(beststart1, beststart2)) VectorCopy(p4, beststart1); + } //end else + dist1 = VectorDistance(bestend1, v4); + dist2 = VectorDistance(bestend2, v4); + if (dist1 > dist2) + { + if (dist1 > VectorDistance(bestend1, bestend2)) VectorCopy(v4, bestend2); + } //end if + else + { + if (dist2 > VectorDistance(bestend1, bestend2)) VectorCopy(v4, bestend1); + } //end else + } //end if + else if (dist < bestdist) + { + bestdist = dist; + VectorCopy(p4, beststart1); + VectorCopy(p4, beststart2); + VectorCopy(v4, bestend1); + VectorCopy(v4, bestend2); + } //end if + founddist = qtrue; + } //end else if + //if no shortest distance was found the shortest distance + //is between one of the vertexes of edge1 and one of edge2 + if (!founddist) + { + dist = VectorDistance(v1, v3); + if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v1, beststart1); + VectorCopy(v1, beststart2); + VectorCopy(v3, bestend1); + VectorCopy(v3, bestend2); + } //end if + dist = VectorDistance(v1, v4); + if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v1, beststart1); + VectorCopy(v1, beststart2); + VectorCopy(v4, bestend1); + VectorCopy(v4, bestend2); + } //end if + dist = VectorDistance(v2, v3); + if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v2, beststart1); + VectorCopy(v2, beststart2); + VectorCopy(v3, bestend1); + VectorCopy(v3, bestend2); + } //end if + dist = VectorDistance(v2, v4); + if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v2, beststart1); + VectorCopy(v2, beststart2); + VectorCopy(v4, bestend1); + VectorCopy(v4, bestend2); + } //end if + } //end if + return bestdist; +} //end of the function AAS_ClosestEdgePoints +//=========================================================================== +// creates possible jump reachabilities between the areas +// +// The two closest points on the ground of the areas are calculated +// One of the points will be on an edge of a ground face of area1 and +// one on an edge of a ground face of area2. +// If there is a range of closest points the point in the middle of this range +// is selected. +// Between these two points there must be one or more gaps. +// If the gaps exist a potential jump is predicted. +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Reachability_Jump(int area1num, int area2num) +{ + int i, j, k, l, face1num, face2num, edge1num, edge2num, traveltype; + int stopevent, areas[10], numareas; + float phys_jumpvel, maxjumpdistance, maxjumpheight, height, bestdist, speed; + vec_t *v1, *v2, *v3, *v4; + vec3_t beststart, beststart2, bestend, bestend2; + vec3_t teststart, testend, dir, velocity, cmdmove, up = {0, 0, 1}, sidewards; + aas_area_t *area1, *area2; + aas_face_t *face1, *face2; + aas_edge_t *edge1, *edge2; + aas_plane_t *plane1, *plane2, *plane; + aas_trace_t trace; + aas_clientmove_t move; + aas_lreachability_t *lreach; + + if (!AAS_AreaGrounded(area1num) || !AAS_AreaGrounded(area2num)) return qfalse; + //cannot jump from or to a crouch area + if (AAS_AreaCrouch(area1num) || AAS_AreaCrouch(area2num)) return qfalse; + // + area1 = &aasworld.areas[area1num]; + area2 = &aasworld.areas[area2num]; + // + phys_jumpvel = aassettings.phys_jumpvel; + //maximum distance a player can jump + maxjumpdistance = 2 * AAS_MaxJumpDistance(phys_jumpvel); + //maximum height a player can jump with the given initial z velocity + maxjumpheight = AAS_MaxJumpHeight(phys_jumpvel); + + //if the areas are not near anough in the x-y direction + for (i = 0; i < 2; i++) + { + if (area1->mins[i] > area2->maxs[i] + maxjumpdistance) return qfalse; + if (area1->maxs[i] < area2->mins[i] - maxjumpdistance) return qfalse; + } //end for + //if area2 is way to high to jump up to + if (area2->mins[2] > area1->maxs[2] + maxjumpheight) return qfalse; + // + bestdist = 999999; + // + for (i = 0; i < area1->numfaces; i++) + { + face1num = aasworld.faceindex[area1->firstface + i]; + face1 = &aasworld.faces[abs(face1num)]; + //if not a ground face + if (!(face1->faceflags & FACE_GROUND)) continue; + // + for (j = 0; j < area2->numfaces; j++) + { + face2num = aasworld.faceindex[area2->firstface + j]; + face2 = &aasworld.faces[abs(face2num)]; + //if not a ground face + if (!(face2->faceflags & FACE_GROUND)) continue; + // + for (k = 0; k < face1->numedges; k++) + { + edge1num = abs(aasworld.edgeindex[face1->firstedge + k]); + edge1 = &aasworld.edges[edge1num]; + for (l = 0; l < face2->numedges; l++) + { + edge2num = abs(aasworld.edgeindex[face2->firstedge + l]); + edge2 = &aasworld.edges[edge2num]; + //calculate the minimum distance between the two edges + v1 = aasworld.vertexes[edge1->v[0]]; + v2 = aasworld.vertexes[edge1->v[1]]; + v3 = aasworld.vertexes[edge2->v[0]]; + v4 = aasworld.vertexes[edge2->v[1]]; + //get the ground planes + plane1 = &aasworld.planes[face1->planenum]; + plane2 = &aasworld.planes[face2->planenum]; + // + bestdist = AAS_ClosestEdgePoints(v1, v2, v3, v4, plane1, plane2, + beststart, bestend, + beststart2, bestend2, bestdist); + } //end for + } //end for + } //end for + } //end for + VectorMiddle(beststart, beststart2, beststart); + VectorMiddle(bestend, bestend2, bestend); + if (bestdist > 4 && bestdist < maxjumpdistance) + { +// Log_Write("shortest distance between %d and %d is %f\r\n", area1num, area2num, bestdist); + // if very close and almost no height difference then the bot can walk + if (bestdist <= 48 && fabs(beststart[2] - bestend[2]) < 8) + { + speed = 400; + traveltype = TRAVEL_WALKOFFLEDGE; + } //end if + else if (AAS_HorizontalVelocityForJump(0, beststart, bestend, &speed)) + { + //FIXME: why multiply with 1.2??? + speed *= 1.2f; + traveltype = TRAVEL_WALKOFFLEDGE; + } //end else if + else + { + //get the horizontal speed for the jump, if it isn't possible to calculate this + //speed (the jump is not possible) then there's no jump reachability created + if (!AAS_HorizontalVelocityForJump(phys_jumpvel, beststart, bestend, &speed)) + return qfalse; + speed *= 1.05f; + traveltype = TRAVEL_JUMP; + // + //NOTE: test if the horizontal distance isn't too small + VectorSubtract(bestend, beststart, dir); + dir[2] = 0; + if (VectorLength(dir) < 10) + return qfalse; + } //end if + // + VectorSubtract(bestend, beststart, dir); + VectorNormalize(dir); + VectorMA(beststart, 1, dir, teststart); + // + VectorCopy(teststart, testend); + testend[2] -= 100; + trace = AAS_TraceClientBBox(teststart, testend, PRESENCE_NORMAL, -1); + // + if (trace.startsolid) + return qfalse; + if (trace.fraction < 1) + { + plane = &aasworld.planes[trace.planenum]; + // if the bot can stand on the surface + if (DotProduct(plane->normal, up) >= 0.7) + { + // if no lava or slime below + if (!(AAS_PointContents(trace.endpos) & (CONTENTS_LAVA|CONTENTS_SLIME))) + { + if (teststart[2] - trace.endpos[2] <= aassettings.phys_maxbarrier) + return qfalse; + } //end if + } //end if + } //end if + // + VectorMA(bestend, -1, dir, teststart); + // + VectorCopy(teststart, testend); + testend[2] -= 100; + trace = AAS_TraceClientBBox(teststart, testend, PRESENCE_NORMAL, -1); + // + if (trace.startsolid) + return qfalse; + if (trace.fraction < 1) + { + plane = &aasworld.planes[trace.planenum]; + // if the bot can stand on the surface + if (DotProduct(plane->normal, up) >= 0.7) + { + // if no lava or slime below + if (!(AAS_PointContents(trace.endpos) & (CONTENTS_LAVA|CONTENTS_SLIME))) + { + if (teststart[2] - trace.endpos[2] <= aassettings.phys_maxbarrier) + return qfalse; + } //end if + } //end if + } //end if + // + // get command movement + VectorClear(cmdmove); + if ((traveltype & TRAVELTYPE_MASK) == TRAVEL_JUMP) + cmdmove[2] = aassettings.phys_jumpvel; + else + cmdmove[2] = 0; + // + VectorSubtract(bestend, beststart, dir); + dir[2] = 0; + VectorNormalize(dir); + CrossProduct(dir, up, sidewards); + // + stopevent = SE_HITGROUND|SE_ENTERWATER|SE_ENTERSLIME|SE_ENTERLAVA|SE_HITGROUNDDAMAGE; + if (!AAS_AreaClusterPortal(area1num) && !AAS_AreaClusterPortal(area2num)) + stopevent |= SE_TOUCHCLUSTERPORTAL; + // + for (i = 0; i < 3; i++) + { + // + if (i == 1) + VectorAdd(testend, sidewards, testend); + else if (i == 2) + VectorSubtract(bestend, sidewards, testend); + else + VectorCopy(bestend, testend); + VectorSubtract(testend, beststart, dir); + dir[2] = 0; + VectorNormalize(dir); + VectorScale(dir, speed, velocity); + // + AAS_PredictClientMovement(&move, -1, beststart, PRESENCE_NORMAL, qtrue, + velocity, cmdmove, 3, 30, 0.1f, + stopevent, 0, qfalse); + // if prediction time wasn't enough to fully predict the movement + if (move.frames >= 30) + return qfalse; + // don't enter slime or lava and don't fall from too high + if (move.stopevent & (SE_ENTERSLIME|SE_ENTERLAVA)) + return qfalse; + // never jump or fall through a cluster portal + if (move.stopevent & SE_TOUCHCLUSTERPORTAL) + return qfalse; + //the end position should be in area2, also test a little bit back + //because the predicted jump could have rushed through the area + VectorMA(move.endpos, -64, dir, teststart); + teststart[2] += 1; + numareas = AAS_TraceAreas(move.endpos, teststart, areas, NULL, sizeof(areas) / sizeof(int)); + for (j = 0; j < numareas; j++) + { + if (areas[j] == area2num) + break; + } //end for + if (j < numareas) + break; + } + if (i >= 3) + return qfalse; + // +#ifdef REACH_DEBUG + //create the reachability + Log_Write("jump reachability between %d and %d\r\n", area1num, area2num); +#endif //REACH_DEBUG + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area2num; + lreach->facenum = 0; + lreach->edgenum = 0; + VectorCopy(beststart, lreach->start); + VectorCopy(bestend, lreach->end); + lreach->traveltype = traveltype; + + VectorSubtract(bestend, beststart, dir); + height = dir[2]; + dir[2] = 0; + if ((traveltype & TRAVELTYPE_MASK) == TRAVEL_WALKOFFLEDGE && height > VectorLength(dir)) + { + lreach->traveltime = aassettings.rs_startwalkoffledge + height * 50 / aassettings.phys_gravity; + } + else + { + lreach->traveltime = aassettings.rs_startjump + VectorDistance(bestend, beststart) * 240 / aassettings.phys_maxwalkvelocity; + } //end if + // + if (!AAS_AreaJumpPad(area2num)) + { + if (AAS_FallDelta(beststart[2] - bestend[2]) > aassettings.phys_falldelta5) + { + lreach->traveltime += aassettings.rs_falldamage5; + } //end if + else if (AAS_FallDelta(beststart[2] - bestend[2]) > aassettings.phys_falldelta10) + { + lreach->traveltime += aassettings.rs_falldamage10; + } //end if + } //end if + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + // + if ((traveltype & TRAVELTYPE_MASK) == TRAVEL_JUMP) + reach_jump++; + else + reach_walkoffledge++; + } //end if + return qfalse; +} //end of the function AAS_Reachability_Jump +//=========================================================================== +// create a possible ladder reachability from area1 to area2 +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Reachability_Ladder(int area1num, int area2num) +{ + int i, j, k, l, edge1num, edge2num, sharededgenum, lowestedgenum; + int face1num, face2num, ladderface1num, ladderface2num; + int ladderface1vertical, ladderface2vertical, firstv; + float face1area, face2area, bestface1area, bestface2area; + float phys_jumpvel, maxjumpheight; + vec3_t area1point, area2point, v1, v2, up = {0, 0, 1}; + vec3_t mid, lowestpoint, start, end, sharededgevec, dir; + aas_area_t *area1, *area2; + aas_face_t *face1, *face2, *ladderface1, *ladderface2; + aas_plane_t *plane1, *plane2; + aas_edge_t *sharededge, *edge1; + aas_lreachability_t *lreach; + aas_trace_t trace; + + if (!AAS_AreaLadder(area1num) || !AAS_AreaLadder(area2num)) return qfalse; + // + phys_jumpvel = aassettings.phys_jumpvel; + //maximum height a player can jump with the given initial z velocity + maxjumpheight = AAS_MaxJumpHeight(phys_jumpvel); + + area1 = &aasworld.areas[area1num]; + area2 = &aasworld.areas[area2num]; + // + ladderface1 = NULL; + ladderface2 = NULL; + ladderface1num = 0; //make compiler happy + ladderface2num = 0; //make compiler happy + bestface1area = -9999; + bestface2area = -9999; + sharededgenum = 0; //make compiler happy + lowestedgenum = 0; //make compiler happy + // + for (i = 0; i < area1->numfaces; i++) + { + face1num = aasworld.faceindex[area1->firstface + i]; + face1 = &aasworld.faces[abs(face1num)]; + //if not a ladder face + if (!(face1->faceflags & FACE_LADDER)) continue; + // + for (j = 0; j < area2->numfaces; j++) + { + face2num = aasworld.faceindex[area2->firstface + j]; + face2 = &aasworld.faces[abs(face2num)]; + //if not a ladder face + if (!(face2->faceflags & FACE_LADDER)) continue; + //check if the faces share an edge + for (k = 0; k < face1->numedges; k++) + { + edge1num = aasworld.edgeindex[face1->firstedge + k]; + for (l = 0; l < face2->numedges; l++) + { + edge2num = aasworld.edgeindex[face2->firstedge + l]; + if (abs(edge1num) == abs(edge2num)) + { + //get the face with the largest area + face1area = AAS_FaceArea(face1); + face2area = AAS_FaceArea(face2); + if (face1area > bestface1area && face2area > bestface2area) + { + bestface1area = face1area; + bestface2area = face2area; + ladderface1 = face1; + ladderface2 = face2; + ladderface1num = face1num; + ladderface2num = face2num; + sharededgenum = edge1num; + } //end if + break; + } //end if + } //end for + if (l != face2->numedges) break; + } //end for + } //end for + } //end for + // + if (ladderface1 && ladderface2) + { + //get the middle of the shared edge + sharededge = &aasworld.edges[abs(sharededgenum)]; + firstv = sharededgenum < 0; + // + VectorCopy(aasworld.vertexes[sharededge->v[firstv]], v1); + VectorCopy(aasworld.vertexes[sharededge->v[!firstv]], v2); + VectorAdd(v1, v2, area1point); + VectorScale(area1point, 0.5, area1point); + VectorCopy(area1point, area2point); + // + //if the face plane in area 1 is pretty much vertical + plane1 = &aasworld.planes[ladderface1->planenum ^ (ladderface1num < 0)]; + plane2 = &aasworld.planes[ladderface2->planenum ^ (ladderface2num < 0)]; + // + //get the points really into the areas + VectorSubtract(v2, v1, sharededgevec); + CrossProduct(plane1->normal, sharededgevec, dir); + VectorNormalize(dir); + //NOTE: 32 because that's larger than 16 (bot bbox x,y) + VectorMA(area1point, -32, dir, area1point); + VectorMA(area2point, 32, dir, area2point); + // + ladderface1vertical = abs(DotProduct(plane1->normal, up)) < 0.1; + ladderface2vertical = abs(DotProduct(plane2->normal, up)) < 0.1; + //there's only reachability between vertical ladder faces + if (!ladderface1vertical && !ladderface2vertical) return qfalse; + //if both vertical ladder faces + if (ladderface1vertical && ladderface2vertical + //and the ladder faces do not make a sharp corner + && DotProduct(plane1->normal, plane2->normal) > 0.7 + //and the shared edge is not too vertical + && abs(DotProduct(sharededgevec, up)) < 0.7) + { + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area2num; + lreach->facenum = ladderface1num; + lreach->edgenum = abs(sharededgenum); + VectorCopy(area1point, lreach->start); + //VectorCopy(area2point, lreach->end); + VectorMA(area2point, -3, plane1->normal, lreach->end); + lreach->traveltype = TRAVEL_LADDER; + lreach->traveltime = 10; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + // + reach_ladder++; + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area1num; + lreach->facenum = ladderface2num; + lreach->edgenum = abs(sharededgenum); + VectorCopy(area2point, lreach->start); + //VectorCopy(area1point, lreach->end); + VectorMA(area1point, -3, plane1->normal, lreach->end); + lreach->traveltype = TRAVEL_LADDER; + lreach->traveltime = 10; + lreach->next = areareachability[area2num]; + areareachability[area2num] = lreach; + // + reach_ladder++; + // + return qtrue; + } //end if + //if the second ladder face is also a ground face + //create ladder end (just ladder) reachability and + //walk off a ladder (ledge) reachability + if (ladderface1vertical && (ladderface2->faceflags & FACE_GROUND)) + { + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area2num; + lreach->facenum = ladderface1num; + lreach->edgenum = abs(sharededgenum); + VectorCopy(area1point, lreach->start); + VectorCopy(area2point, lreach->end); + lreach->end[2] += 16; + VectorMA(lreach->end, -15, plane1->normal, lreach->end); + lreach->traveltype = TRAVEL_LADDER; + lreach->traveltime = 10; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + // + reach_ladder++; + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area1num; + lreach->facenum = ladderface2num; + lreach->edgenum = abs(sharededgenum); + VectorCopy(area2point, lreach->start); + VectorCopy(area1point, lreach->end); + lreach->traveltype = TRAVEL_WALKOFFLEDGE; + lreach->traveltime = 10; + lreach->next = areareachability[area2num]; + areareachability[area2num] = lreach; + // + reach_walkoffledge++; + // + return qtrue; + } //end if + // + if (ladderface1vertical) + { + //find lowest edge of the ladder face + lowestpoint[2] = 99999; + for (i = 0; i < ladderface1->numedges; i++) + { + edge1num = abs(aasworld.edgeindex[ladderface1->firstedge + i]); + edge1 = &aasworld.edges[edge1num]; + // + VectorCopy(aasworld.vertexes[edge1->v[0]], v1); + VectorCopy(aasworld.vertexes[edge1->v[1]], v2); + // + VectorAdd(v1, v2, mid); + VectorScale(mid, 0.5, mid); + // + if (mid[2] < lowestpoint[2]) + { + VectorCopy(mid, lowestpoint); + lowestedgenum = edge1num; + } //end if + } //end for + // + plane1 = &aasworld.planes[ladderface1->planenum]; + //trace down in the middle of this edge + VectorMA(lowestpoint, 5, plane1->normal, start); + VectorCopy(start, end); + start[2] += 5; + end[2] -= 100; + //trace without entity collision + trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, -1); + // + // +#ifdef REACH_DEBUG + if (trace.startsolid) + { + Log_Write("trace from area %d started in solid\r\n", area1num); + } //end if +#endif //REACH_DEBUG + // + trace.endpos[2] += 1; + area2num = AAS_PointAreaNum(trace.endpos); + // + area2 = &aasworld.areas[area2num]; + for (i = 0; i < area2->numfaces; i++) + { + face2num = aasworld.faceindex[area2->firstface + i]; + face2 = &aasworld.faces[abs(face2num)]; + // + if (face2->faceflags & FACE_LADDER) + { + plane2 = &aasworld.planes[face2->planenum]; + if (abs(DotProduct(plane2->normal, up)) < 0.1) break; + } //end if + } //end for + //if from another area without vertical ladder faces + if (i >= area2->numfaces && area2num != area1num && + //the reachabilities shouldn't exist already + !AAS_ReachabilityExists(area1num, area2num) && + !AAS_ReachabilityExists(area2num, area1num)) + { + //if the height is jumpable + if (start[2] - trace.endpos[2] < maxjumpheight) + { + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area2num; + lreach->facenum = ladderface1num; + lreach->edgenum = lowestedgenum; + VectorCopy(lowestpoint, lreach->start); + VectorCopy(trace.endpos, lreach->end); + lreach->traveltype = TRAVEL_LADDER; + lreach->traveltime = 10; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + // + reach_ladder++; + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area1num; + lreach->facenum = ladderface1num; + lreach->edgenum = lowestedgenum; + VectorCopy(trace.endpos, lreach->start); + //get the end point a little bit into the ladder + VectorMA(lowestpoint, -5, plane1->normal, lreach->end); + //get the end point a little higher + lreach->end[2] += 10; + lreach->traveltype = TRAVEL_JUMP; + lreach->traveltime = 10; + lreach->next = areareachability[area2num]; + areareachability[area2num] = lreach; + // + reach_jump++; + // + return qtrue; +#ifdef REACH_DEBUG + Log_Write("jump up to ladder reach between %d and %d\r\n", area2num, area1num); +#endif //REACH_DEBUG + } //end if +#ifdef REACH_DEBUG + else Log_Write("jump too high between area %d and %d\r\n", area2num, area1num); +#endif //REACH_DEBUG + } //end if + /*//if slime or lava below the ladder + //try jump reachability from far towards the ladder + if (aasworld.areasettings[area2num].contents & (AREACONTENTS_SLIME + | AREACONTENTS_LAVA)) + { + for (i = 20; i <= 120; i += 20) + { + //trace down in the middle of this edge + VectorMA(lowestpoint, i, plane1->normal, start); + VectorCopy(start, end); + start[2] += 5; + end[2] -= 100; + //trace without entity collision + trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, -1); + // + if (trace.startsolid) break; + trace.endpos[2] += 1; + area2num = AAS_PointAreaNum(trace.endpos); + if (area2num == area1num) continue; + // + if (start[2] - trace.endpos[2] > maxjumpheight) continue; + if (aasworld.areasettings[area2num].contents & (AREACONTENTS_SLIME + | AREACONTENTS_LAVA)) continue; + // + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area1num; + lreach->facenum = ladderface1num; + lreach->edgenum = lowestedgenum; + VectorCopy(trace.endpos, lreach->start); + VectorCopy(lowestpoint, lreach->end); + lreach->end[2] += 5; + lreach->traveltype = TRAVEL_JUMP; + lreach->traveltime = 10; + lreach->next = areareachability[area2num]; + areareachability[area2num] = lreach; + // + reach_jump++; + // + Log_Write("jump far to ladder reach between %d and %d\r\n", area2num, area1num); + // + break; + } //end for + } //end if*/ + } //end if + } //end if + return qfalse; +} //end of the function AAS_Reachability_Ladder +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_TravelFlagsForTeam(int ent) +{ + int notteam; + + if (!AAS_IntForBSPEpairKey(ent, "bot_notteam", ¬team)) + return 0; + if (notteam == 1) + return TRAVELFLAG_NOTTEAM1; + if (notteam == 2) + return TRAVELFLAG_NOTTEAM2; + return 0; +} //end of the function AAS_TravelFlagsForTeam +//=========================================================================== +// create possible teleporter reachabilities +// this is very game dependent.... :( +// +// classname = trigger_multiple or trigger_teleport +// target = "t1" +// +// classname = target_teleporter +// targetname = "t1" +// target = "t2" +// +// classname = misc_teleporter_dest +// targetname = "t2" +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Reachability_Teleport(void) +{ + int area1num, area2num; + char target[MAX_EPAIRKEY], targetname[MAX_EPAIRKEY]; + char classname[MAX_EPAIRKEY], model[MAX_EPAIRKEY]; + int ent, dest; + float angle; + vec3_t origin, destorigin, mins, maxs, end, angles; + vec3_t mid, velocity, cmdmove; + aas_lreachability_t *lreach; + aas_clientmove_t move; + aas_trace_t trace; + aas_link_t *areas, *link; + + for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) + { + if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; + if (!strcmp(classname, "trigger_multiple")) + { + AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY); +//#ifdef REACH_DEBUG + botimport.Print(PRT_MESSAGE, "trigger_multiple model = \"%s\"\n", model); +//#endif REACH_DEBUG + VectorClear(angles); + AAS_BSPModelMinsMaxsOrigin(atoi(model+1), angles, mins, maxs, origin); + // + if (!AAS_ValueForBSPEpairKey(ent, "target", target, MAX_EPAIRKEY)) + { + botimport.Print(PRT_ERROR, "trigger_multiple at %1.0f %1.0f %1.0f without target\n", + origin[0], origin[1], origin[2]); + continue; + } //end if + for (dest = AAS_NextBSPEntity(0); dest; dest = AAS_NextBSPEntity(dest)) + { + if (!AAS_ValueForBSPEpairKey(dest, "classname", classname, MAX_EPAIRKEY)) continue; + if (!strcmp(classname, "target_teleporter")) + { + if (!AAS_ValueForBSPEpairKey(dest, "targetname", targetname, MAX_EPAIRKEY)) continue; + if (!strcmp(targetname, target)) + { + break; + } //end if + } //end if + } //end for + if (!dest) + { + continue; + } //end if + if (!AAS_ValueForBSPEpairKey(dest, "target", target, MAX_EPAIRKEY)) + { + botimport.Print(PRT_ERROR, "target_teleporter without target\n"); + continue; + } //end if + } //end else + else if (!strcmp(classname, "trigger_teleport")) + { + AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY); +//#ifdef REACH_DEBUG + botimport.Print(PRT_MESSAGE, "trigger_teleport model = \"%s\"\n", model); +//#endif REACH_DEBUG + VectorClear(angles); + AAS_BSPModelMinsMaxsOrigin(atoi(model+1), angles, mins, maxs, origin); + // + if (!AAS_ValueForBSPEpairKey(ent, "target", target, MAX_EPAIRKEY)) + { + botimport.Print(PRT_ERROR, "trigger_teleport at %1.0f %1.0f %1.0f without target\n", + origin[0], origin[1], origin[2]); + continue; + } //end if + } //end if + else + { + continue; + } //end else + // + for (dest = AAS_NextBSPEntity(0); dest; dest = AAS_NextBSPEntity(dest)) + { + //classname should be misc_teleporter_dest + //but I've also seen target_position and actually any + //entity could be used... burp + if (AAS_ValueForBSPEpairKey(dest, "targetname", targetname, MAX_EPAIRKEY)) + { + if (!strcmp(targetname, target)) + { + break; + } //end if + } //end if + } //end for + if (!dest) + { + botimport.Print(PRT_ERROR, "teleporter without misc_teleporter_dest (%s)\n", target); + continue; + } //end if + if (!AAS_VectorForBSPEpairKey(dest, "origin", destorigin)) + { + botimport.Print(PRT_ERROR, "teleporter destination (%s) without origin\n", target); + continue; + } //end if + // + area2num = AAS_PointAreaNum(destorigin); + //if not teleported into a teleporter or into a jumppad + if (!AAS_AreaTeleporter(area2num) && !AAS_AreaJumpPad(area2num)) + { + VectorCopy(destorigin, end); + end[2] -= 64; + trace = AAS_TraceClientBBox(destorigin, end, PRESENCE_CROUCH, -1); + if (trace.startsolid) + { + botimport.Print(PRT_ERROR, "teleporter destination (%s) in solid\n", target); + continue; + } //end if + area2num = AAS_PointAreaNum(trace.endpos); + // + /* + if (!AAS_AreaTeleporter(area2num) && + !AAS_AreaJumpPad(area2num) && + !AAS_AreaGrounded(area2num)) + { + VectorCopy(trace.endpos, destorigin); + } + else*/ + { + //predict where you'll end up + AAS_FloatForBSPEpairKey(dest, "angle", &angle); + if (angle) + { + VectorSet(angles, 0, angle, 0); + AngleVectors(angles, velocity, NULL, NULL); + VectorScale(velocity, 400, velocity); + } //end if + else + { + VectorClear(velocity); + } //end else + VectorClear(cmdmove); + AAS_PredictClientMovement(&move, -1, destorigin, PRESENCE_NORMAL, qfalse, + velocity, cmdmove, 0, 30, 0.1f, + SE_HITGROUND|SE_ENTERWATER|SE_ENTERSLIME| + SE_ENTERLAVA|SE_HITGROUNDDAMAGE|SE_TOUCHJUMPPAD|SE_TOUCHTELEPORTER, 0, qfalse); //qtrue); + area2num = AAS_PointAreaNum(move.endpos); + if (move.stopevent & (SE_ENTERSLIME|SE_ENTERLAVA)) + { + botimport.Print(PRT_WARNING, "teleported into slime or lava at dest %s\n", target); + } //end if + VectorCopy(move.endpos, destorigin); + } //end else + } //end if + // + //botimport.Print(PRT_MESSAGE, "teleporter brush origin at %f %f %f\n", origin[0], origin[1], origin[2]); + //botimport.Print(PRT_MESSAGE, "teleporter brush mins = %f %f %f\n", mins[0], mins[1], mins[2]); + //botimport.Print(PRT_MESSAGE, "teleporter brush maxs = %f %f %f\n", maxs[0], maxs[1], maxs[2]); + VectorAdd(origin, mins, mins); + VectorAdd(origin, maxs, maxs); + // + VectorAdd(mins, maxs, mid); + VectorScale(mid, 0.5, mid); + //link an invalid (-1) entity + areas = AAS_LinkEntityClientBBox(mins, maxs, -1, PRESENCE_CROUCH); + if (!areas) botimport.Print(PRT_MESSAGE, "trigger_multiple not in any area\n"); + // + for (link = areas; link; link = link->next_area) + { + //if (!AAS_AreaGrounded(link->areanum)) continue; + if (!AAS_AreaTeleporter(link->areanum)) continue; + // + area1num = link->areanum; + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) break; + lreach->areanum = area2num; + lreach->facenum = 0; + lreach->edgenum = 0; + VectorCopy(mid, lreach->start); + VectorCopy(destorigin, lreach->end); + lreach->traveltype = TRAVEL_TELEPORT; + lreach->traveltype |= AAS_TravelFlagsForTeam(ent); + lreach->traveltime = aassettings.rs_teleport; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + // + reach_teleport++; + } //end for + //unlink the invalid entity + AAS_UnlinkFromAreas(areas); + } //end for +} //end of the function AAS_Reachability_Teleport +//=========================================================================== +// create possible elevator (func_plat) reachabilities +// this is very game dependent.... :( +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Reachability_Elevator(void) +{ + int area1num, area2num, modelnum, i, j, k, l, n, p; + float lip, height, speed; + char model[MAX_EPAIRKEY], classname[MAX_EPAIRKEY]; + int ent; + vec3_t mins, maxs, origin, angles = {0, 0, 0}; + vec3_t pos1, pos2, mids, platbottom, plattop; + vec3_t bottomorg, toporg, start, end, dir; + vec_t xvals[8], yvals[8], xvals_top[8], yvals_top[8]; + aas_lreachability_t *lreach; + aas_trace_t trace; + +#ifdef REACH_DEBUG + Log_Write("AAS_Reachability_Elevator\r\n"); +#endif //REACH_DEBUG + for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) + { + if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; + if (!strcmp(classname, "func_plat")) + { +#ifdef REACH_DEBUG + Log_Write("found func plat\r\n"); +#endif //REACH_DEBUG + if (!AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY)) + { + botimport.Print(PRT_ERROR, "func_plat without model\n"); + continue; + } //end if + //get the model number, and skip the leading * + modelnum = atoi(model+1); + if (modelnum <= 0) + { + botimport.Print(PRT_ERROR, "func_plat with invalid model number\n"); + continue; + } //end if + //get the mins, maxs and origin of the model + //NOTE: the origin is usually (0,0,0) and the mins and maxs + // are the absolute mins and maxs + AAS_BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, origin); + // + AAS_VectorForBSPEpairKey(ent, "origin", origin); + //pos1 is the top position, pos2 is the bottom + VectorCopy(origin, pos1); + VectorCopy(origin, pos2); + //get the lip of the plat + AAS_FloatForBSPEpairKey(ent, "lip", &lip); + if (!lip) lip = 8; + //get the movement height of the plat + AAS_FloatForBSPEpairKey(ent, "height", &height); + if (!height) height = (maxs[2] - mins[2]) - lip; + //get the speed of the plat + AAS_FloatForBSPEpairKey(ent, "speed", &speed); + if (!speed) speed = 200; + //get bottom position below pos1 + pos2[2] -= height; + // + //get a point just above the plat in the bottom position + VectorAdd(mins, maxs, mids); + VectorMA(pos2, 0.5, mids, platbottom); + platbottom[2] = maxs[2] - (pos1[2] - pos2[2]) + 2; + //get a point just above the plat in the top position + VectorAdd(mins, maxs, mids); + VectorMA(pos2, 0.5, mids, plattop); + plattop[2] = maxs[2] + 2; + // + /*if (!area1num) + { + Log_Write("no grounded area near plat bottom\r\n"); + continue; + } //end if*/ + //get the mins and maxs a little larger + for (i = 0; i < 3; i++) + { + mins[i] -= 1; + maxs[i] += 1; + } //end for + // + //botimport.Print(PRT_MESSAGE, "platbottom[2] = %1.1f plattop[2] = %1.1f\n", platbottom[2], plattop[2]); + // + VectorAdd(mins, maxs, mids); + VectorScale(mids, 0.5, mids); + // + xvals[0] = mins[0]; xvals[1] = mids[0]; xvals[2] = maxs[0]; xvals[3] = mids[0]; + yvals[0] = mids[1]; yvals[1] = maxs[1]; yvals[2] = mids[1]; yvals[3] = mins[1]; + // + xvals[4] = mins[0]; xvals[5] = maxs[0]; xvals[6] = maxs[0]; xvals[7] = mins[0]; + yvals[4] = maxs[1]; yvals[5] = maxs[1]; yvals[6] = mins[1]; yvals[7] = mins[1]; + //find adjacent areas around the bottom of the plat + for (i = 0; i < 9; i++) + { + if (i < 8) //check at the sides of the plat + { + bottomorg[0] = origin[0] + xvals[i]; + bottomorg[1] = origin[1] + yvals[i]; + bottomorg[2] = platbottom[2] + 16; + //get a grounded or swim area near the plat in the bottom position + area1num = AAS_PointAreaNum(bottomorg); + for (k = 0; k < 16; k++) + { + if (area1num) + { + if (AAS_AreaGrounded(area1num) || AAS_AreaSwim(area1num)) break; + } //end if + bottomorg[2] += 4; + area1num = AAS_PointAreaNum(bottomorg); + } //end if + //if in solid + if (k >= 16) + { + continue; + } //end if + } //end if + else //at the middle of the plat + { + VectorCopy(plattop, bottomorg); + bottomorg[2] += 24; + area1num = AAS_PointAreaNum(bottomorg); + if (!area1num) continue; + VectorCopy(platbottom, bottomorg); + bottomorg[2] += 24; + } //end else + //look at adjacent areas around the top of the plat + //make larger steps to outside the plat everytime + for (n = 0; n < 3; n++) + { + for (k = 0; k < 3; k++) + { + mins[k] -= 4; + maxs[k] += 4; + } //end for + xvals_top[0] = mins[0]; xvals_top[1] = mids[0]; xvals_top[2] = maxs[0]; xvals_top[3] = mids[0]; + yvals_top[0] = mids[1]; yvals_top[1] = maxs[1]; yvals_top[2] = mids[1]; yvals_top[3] = mins[1]; + // + xvals_top[4] = mins[0]; xvals_top[5] = maxs[0]; xvals_top[6] = maxs[0]; xvals_top[7] = mins[0]; + yvals_top[4] = maxs[1]; yvals_top[5] = maxs[1]; yvals_top[6] = mins[1]; yvals_top[7] = mins[1]; + // + for (j = 0; j < 8; j++) + { + toporg[0] = origin[0] + xvals_top[j]; + toporg[1] = origin[1] + yvals_top[j]; + toporg[2] = plattop[2] + 16; + //get a grounded or swim area near the plat in the top position + area2num = AAS_PointAreaNum(toporg); + for (l = 0; l < 16; l++) + { + if (area2num) + { + if (AAS_AreaGrounded(area2num) || AAS_AreaSwim(area2num)) + { + VectorCopy(plattop, start); + start[2] += 32; + VectorCopy(toporg, end); + end[2] += 1; + trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, -1); + if (trace.fraction >= 1) break; + } //end if + } //end if + toporg[2] += 4; + area2num = AAS_PointAreaNum(toporg); + } //end if + //if in solid + if (l >= 16) continue; + //never create a reachability in the same area + if (area2num == area1num) continue; + //if the area isn't grounded + if (!AAS_AreaGrounded(area2num)) continue; + //if there already exists reachability between the areas + if (AAS_ReachabilityExists(area1num, area2num)) continue; + //if the reachability start is within the elevator bounding box + VectorSubtract(bottomorg, platbottom, dir); + VectorNormalize(dir); + dir[0] = bottomorg[0] + 24 * dir[0]; + dir[1] = bottomorg[1] + 24 * dir[1]; + dir[2] = bottomorg[2]; + // + for (p = 0; p < 3; p++) + if (dir[p] < origin[p] + mins[p] || dir[p] > origin[p] + maxs[p]) break; + if (p >= 3) continue; + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) continue; + lreach->areanum = area2num; + //the facenum is the model number + lreach->facenum = modelnum; + //the edgenum is the height + lreach->edgenum = (int) height; + // + VectorCopy(dir, lreach->start); + VectorCopy(toporg, lreach->end); + lreach->traveltype = TRAVEL_ELEVATOR; + lreach->traveltype |= AAS_TravelFlagsForTeam(ent); + lreach->traveltime = aassettings.rs_startelevator + height * 100 / speed; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + //don't go any further to the outside + n = 9999; + // +#ifdef REACH_DEBUG + Log_Write("elevator reach from %d to %d\r\n", area1num, area2num); +#endif //REACH_DEBUG + // + reach_elevator++; + } //end for + } //end for + } //end for + } //end if + } //end for +} //end of the function AAS_Reachability_Elevator +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_lreachability_t *AAS_FindFaceReachabilities(vec3_t *facepoints, int numpoints, aas_plane_t *plane, int towardsface) +{ + int i, j, k, l; + int facenum, edgenum, bestfacenum; + float *v1, *v2, *v3, *v4; + float bestdist, speed, hordist, dist; + vec3_t beststart, beststart2, bestend, bestend2, tmp, hordir, testpoint; + aas_lreachability_t *lreach, *lreachabilities; + aas_area_t *area; + aas_face_t *face; + aas_edge_t *edge; + aas_plane_t *faceplane, *bestfaceplane; + + // + lreachabilities = NULL; + bestfacenum = 0; + bestfaceplane = NULL; + // + for (i = 1; i < aasworld.numareas; i++) + { + area = &aasworld.areas[i]; + // get the shortest distance between one of the func_bob start edges and + // one of the face edges of area1 + bestdist = 999999; + for (j = 0; j < area->numfaces; j++) + { + facenum = aasworld.faceindex[area->firstface + j]; + face = &aasworld.faces[abs(facenum)]; + //if not a ground face + if (!(face->faceflags & FACE_GROUND)) continue; + //get the ground planes + faceplane = &aasworld.planes[face->planenum]; + // + for (k = 0; k < face->numedges; k++) + { + edgenum = abs(aasworld.edgeindex[face->firstedge + k]); + edge = &aasworld.edges[edgenum]; + //calculate the minimum distance between the two edges + v1 = aasworld.vertexes[edge->v[0]]; + v2 = aasworld.vertexes[edge->v[1]]; + // + for (l = 0; l < numpoints; l++) + { + v3 = facepoints[l]; + v4 = facepoints[(l+1) % numpoints]; + dist = AAS_ClosestEdgePoints(v1, v2, v3, v4, faceplane, plane, + beststart, bestend, + beststart2, bestend2, bestdist); + if (dist < bestdist) + { + bestfacenum = facenum; + bestfaceplane = faceplane; + bestdist = dist; + } //end if + } //end for + } //end for + } //end for + // + if (bestdist > 192) continue; + // + VectorMiddle(beststart, beststart2, beststart); + VectorMiddle(bestend, bestend2, bestend); + // + if (!towardsface) + { + VectorCopy(beststart, tmp); + VectorCopy(bestend, beststart); + VectorCopy(tmp, bestend); + } //end if + // + VectorSubtract(bestend, beststart, hordir); + hordir[2] = 0; + hordist = VectorLength(hordir); + // + if (hordist > 2 * AAS_MaxJumpDistance(aassettings.phys_jumpvel)) continue; + //the end point should not be significantly higher than the start point + if (bestend[2] - 32 > beststart[2]) continue; + //don't fall down too far + if (bestend[2] < beststart[2] - 128) continue; + //the distance should not be too far + if (hordist > 32) + { + //check for walk off ledge + if (!AAS_HorizontalVelocityForJump(0, beststart, bestend, &speed)) continue; + } //end if + // + beststart[2] += 1; + bestend[2] += 1; + // + if (towardsface) VectorCopy(bestend, testpoint); + else VectorCopy(beststart, testpoint); + testpoint[2] = 0; + testpoint[2] = (bestfaceplane->dist - DotProduct(bestfaceplane->normal, testpoint)) / bestfaceplane->normal[2]; + // + if (!AAS_PointInsideFace(bestfacenum, testpoint, 0.1f)) + { + //if the faces are not overlapping then only go down + if (bestend[2] - 16 > beststart[2]) continue; + } //end if + lreach = AAS_AllocReachability(); + if (!lreach) return lreachabilities; + lreach->areanum = i; + lreach->facenum = 0; + lreach->edgenum = 0; + VectorCopy(beststart, lreach->start); + VectorCopy(bestend, lreach->end); + lreach->traveltype = 0; + lreach->traveltime = 0; + lreach->next = lreachabilities; + lreachabilities = lreach; +#ifndef BSPC + if (towardsface) AAS_PermanentLine(lreach->start, lreach->end, 1); + else AAS_PermanentLine(lreach->start, lreach->end, 2); +#endif + } //end for + return lreachabilities; +} //end of the function AAS_FindFaceReachabilities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Reachability_FuncBobbing(void) +{ + int ent, spawnflags, modelnum, axis; + int i, numareas, areas[10]; + char classname[MAX_EPAIRKEY], model[MAX_EPAIRKEY]; + vec3_t origin, move_end, move_start, move_start_top, move_end_top; + vec3_t mins, maxs, angles = {0, 0, 0}; + vec3_t start_edgeverts[4], end_edgeverts[4], mid; + vec3_t org, start, end, dir, points[10]; + float height; + aas_plane_t start_plane, end_plane; + aas_lreachability_t *startreach, *endreach, *nextstartreach, *nextendreach, *lreach; + aas_lreachability_t *firststartreach, *firstendreach; + + for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) + { + if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; + if (strcmp(classname, "func_bobbing")) continue; + AAS_FloatForBSPEpairKey(ent, "height", &height); + if (!height) height = 32; + // + if (!AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY)) + { + botimport.Print(PRT_ERROR, "func_bobbing without model\n"); + continue; + } //end if + //get the model number, and skip the leading * + modelnum = atoi(model+1); + if (modelnum <= 0) + { + botimport.Print(PRT_ERROR, "func_bobbing with invalid model number\n"); + continue; + } //end if + //if the entity has an origin set then use it + if (!AAS_VectorForBSPEpairKey(ent, "origin", origin)) + VectorSet(origin, 0, 0, 0); + // + AAS_BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, NULL); + // + VectorAdd(mins, origin, mins); + VectorAdd(maxs, origin, maxs); + // + VectorAdd(mins, maxs, mid); + VectorScale(mid, 0.5, mid); + VectorCopy(mid, origin); + // + VectorCopy(origin, move_end); + VectorCopy(origin, move_start); + // + AAS_IntForBSPEpairKey(ent, "spawnflags", &spawnflags); + // set the axis of bobbing + if (spawnflags & 1) axis = 0; + else if (spawnflags & 2) axis = 1; + else axis = 2; + // + move_start[axis] -= height; + move_end[axis] += height; + // + Log_Write("funcbob model %d, start = {%1.1f, %1.1f, %1.1f} end = {%1.1f, %1.1f, %1.1f}\n", + modelnum, move_start[0], move_start[1], move_start[2], move_end[0], move_end[1], move_end[2]); + // +#ifndef BSPC + /* + AAS_DrawPermanentCross(move_start, 4, 1); + AAS_DrawPermanentCross(move_end, 4, 2); + */ +#endif + // + for (i = 0; i < 4; i++) + { + VectorCopy(move_start, start_edgeverts[i]); + start_edgeverts[i][2] += maxs[2] - mid[2]; //+ bbox maxs z + start_edgeverts[i][2] += 24; //+ player origin to ground dist + } //end for + start_edgeverts[0][0] += maxs[0] - mid[0]; + start_edgeverts[0][1] += maxs[1] - mid[1]; + start_edgeverts[1][0] += maxs[0] - mid[0]; + start_edgeverts[1][1] += mins[1] - mid[1]; + start_edgeverts[2][0] += mins[0] - mid[0]; + start_edgeverts[2][1] += mins[1] - mid[1]; + start_edgeverts[3][0] += mins[0] - mid[0]; + start_edgeverts[3][1] += maxs[1] - mid[1]; + // + start_plane.dist = start_edgeverts[0][2]; + VectorSet(start_plane.normal, 0, 0, 1); + // + for (i = 0; i < 4; i++) + { + VectorCopy(move_end, end_edgeverts[i]); + end_edgeverts[i][2] += maxs[2] - mid[2]; //+ bbox maxs z + end_edgeverts[i][2] += 24; //+ player origin to ground dist + } //end for + end_edgeverts[0][0] += maxs[0] - mid[0]; + end_edgeverts[0][1] += maxs[1] - mid[1]; + end_edgeverts[1][0] += maxs[0] - mid[0]; + end_edgeverts[1][1] += mins[1] - mid[1]; + end_edgeverts[2][0] += mins[0] - mid[0]; + end_edgeverts[2][1] += mins[1] - mid[1]; + end_edgeverts[3][0] += mins[0] - mid[0]; + end_edgeverts[3][1] += maxs[1] - mid[1]; + // + end_plane.dist = end_edgeverts[0][2]; + VectorSet(end_plane.normal, 0, 0, 1); + // +#ifndef BSPC +#if 0 + for (i = 0; i < 4; i++) + { + AAS_PermanentLine(start_edgeverts[i], start_edgeverts[(i+1)%4], 1); + AAS_PermanentLine(end_edgeverts[i], end_edgeverts[(i+1)%4], 1); + } //end for +#endif +#endif + VectorCopy(move_start, move_start_top); + move_start_top[2] += maxs[2] - mid[2] + 24; //+ bbox maxs z + VectorCopy(move_end, move_end_top); + move_end_top[2] += maxs[2] - mid[2] + 24; //+ bbox maxs z + // + if (!AAS_PointAreaNum(move_start_top)) continue; + if (!AAS_PointAreaNum(move_end_top)) continue; + // + for (i = 0; i < 2; i++) + { + firststartreach = firstendreach = NULL; + // + if (i == 0) + { + firststartreach = AAS_FindFaceReachabilities(start_edgeverts, 4, &start_plane, qtrue); + firstendreach = AAS_FindFaceReachabilities(end_edgeverts, 4, &end_plane, qfalse); + } //end if + else + { + firststartreach = AAS_FindFaceReachabilities(end_edgeverts, 4, &end_plane, qtrue); + firstendreach = AAS_FindFaceReachabilities(start_edgeverts, 4, &start_plane, qfalse); + } //end else + // + //create reachabilities from start to end + for (startreach = firststartreach; startreach; startreach = nextstartreach) + { + nextstartreach = startreach->next; + // + //trace = AAS_TraceClientBBox(startreach->start, move_start_top, PRESENCE_NORMAL, -1); + //if (trace.fraction < 1) continue; + // + for (endreach = firstendreach; endreach; endreach = nextendreach) + { + nextendreach = endreach->next; + // + //trace = AAS_TraceClientBBox(endreach->end, move_end_top, PRESENCE_NORMAL, -1); + //if (trace.fraction < 1) continue; + // + Log_Write("funcbob reach from area %d to %d\n", startreach->areanum, endreach->areanum); + // + // + if (i == 0) VectorCopy(move_start_top, org); + else VectorCopy(move_end_top, org); + VectorSubtract(startreach->start, org, dir); + dir[2] = 0; + VectorNormalize(dir); + VectorCopy(startreach->start, start); + VectorMA(startreach->start, 1, dir, start); + start[2] += 1; + VectorMA(startreach->start, 16, dir, end); + end[2] += 1; + // + numareas = AAS_TraceAreas(start, end, areas, points, 10); + if (numareas <= 0) continue; + if (numareas > 1) VectorCopy(points[1], startreach->start); + else VectorCopy(end, startreach->start); + // + if (!AAS_PointAreaNum(startreach->start)) continue; + if (!AAS_PointAreaNum(endreach->end)) continue; + // + lreach = AAS_AllocReachability(); + lreach->areanum = endreach->areanum; + if (i == 0) lreach->edgenum = ((int)move_start[axis] << 16) | ((int) move_end[axis] & 0x0000ffff); + else lreach->edgenum = ((int)move_end[axis] << 16) | ((int) move_start[axis] & 0x0000ffff); + lreach->facenum = (spawnflags << 16) | modelnum; + VectorCopy(startreach->start, lreach->start); + VectorCopy(endreach->end, lreach->end); +#ifndef BSPC +// AAS_DrawArrow(lreach->start, lreach->end, LINECOLOR_BLUE, LINECOLOR_YELLOW); +// AAS_PermanentLine(lreach->start, lreach->end, 1); +#endif + lreach->traveltype = TRAVEL_FUNCBOB; + lreach->traveltype |= AAS_TravelFlagsForTeam(ent); + lreach->traveltime = aassettings.rs_funcbob; + reach_funcbob++; + lreach->next = areareachability[startreach->areanum]; + areareachability[startreach->areanum] = lreach; + // + } //end for + } //end for + for (startreach = firststartreach; startreach; startreach = nextstartreach) + { + nextstartreach = startreach->next; + AAS_FreeReachability(startreach); + } //end for + for (endreach = firstendreach; endreach; endreach = nextendreach) + { + nextendreach = endreach->next; + AAS_FreeReachability(endreach); + } //end for + //only go up with func_bobbing entities that go up and down + if (!(spawnflags & 1) && !(spawnflags & 2)) break; + } //end for + } //end for +} //end of the function AAS_Reachability_FuncBobbing +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Reachability_JumpPad(void) +{ + int face2num, i, ret, area2num, visualize, ent, bot_visualizejumppads; + //int modelnum, ent2; + //float dist, time, height, gravity, forward; + float speed, zvel, hordist; + aas_face_t *face2; + aas_area_t *area2; + aas_lreachability_t *lreach; + vec3_t areastart, facecenter, dir, cmdmove; + vec3_t velocity, absmins, absmaxs; + //vec3_t origin, ent2origin, angles, teststart; + aas_clientmove_t move; + //aas_trace_t trace; + aas_link_t *areas, *link; + //char target[MAX_EPAIRKEY], targetname[MAX_EPAIRKEY], model[MAX_EPAIRKEY]; + char classname[MAX_EPAIRKEY]; + +#ifdef BSPC + bot_visualizejumppads = 0; +#else + bot_visualizejumppads = LibVarValue("bot_visualizejumppads", "0"); +#endif + for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) + { + if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; + if (strcmp(classname, "trigger_push")) continue; + // + if (!AAS_GetJumpPadInfo(ent, areastart, absmins, absmaxs, velocity)) continue; + /* + // + AAS_FloatForBSPEpairKey(ent, "speed", &speed); + if (!speed) speed = 1000; +// AAS_VectorForBSPEpairKey(ent, "angles", angles); +// AAS_SetMovedir(angles, velocity); +// VectorScale(velocity, speed, velocity); + VectorClear(angles); + //get the mins, maxs and origin of the model + AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY); + if (model[0]) modelnum = atoi(model+1); + else modelnum = 0; + AAS_BSPModelMinsMaxsOrigin(modelnum, angles, absmins, absmaxs, origin); + VectorAdd(origin, absmins, absmins); + VectorAdd(origin, absmaxs, absmaxs); + // +#ifdef REACH_DEBUG + botimport.Print(PRT_MESSAGE, "absmins = %f %f %f\n", absmins[0], absmins[1], absmins[2]); + botimport.Print(PRT_MESSAGE, "absmaxs = %f %f %f\n", absmaxs[0], absmaxs[1], absmaxs[2]); +#endif REACH_DEBUG + VectorAdd(absmins, absmaxs, origin); + VectorScale (origin, 0.5, origin); + + //get the start areas + VectorCopy(origin, teststart); + teststart[2] += 64; + trace = AAS_TraceClientBBox(teststart, origin, PRESENCE_CROUCH, -1); + if (trace.startsolid) + { + botimport.Print(PRT_MESSAGE, "trigger_push start solid\n"); + VectorCopy(origin, areastart); + } //end if + else + { + VectorCopy(trace.endpos, areastart); + } //end else + areastart[2] += 0.125; + // + //AAS_DrawPermanentCross(origin, 4, 4); + //get the target entity + AAS_ValueForBSPEpairKey(ent, "target", target, MAX_EPAIRKEY); + for (ent2 = AAS_NextBSPEntity(0); ent2; ent2 = AAS_NextBSPEntity(ent2)) + { + if (!AAS_ValueForBSPEpairKey(ent2, "targetname", targetname, MAX_EPAIRKEY)) continue; + if (!strcmp(targetname, target)) break; + } //end for + if (!ent2) + { + botimport.Print(PRT_MESSAGE, "trigger_push without target entity %s\n", target); + continue; + } //end if + AAS_VectorForBSPEpairKey(ent2, "origin", ent2origin); + // + height = ent2origin[2] - origin[2]; + gravity = aassettings.sv_gravity; + time = sqrt( height / ( 0.5 * gravity ) ); + if (!time) + { + botimport.Print(PRT_MESSAGE, "trigger_push without time\n"); + continue; + } //end if + // set s.origin2 to the push velocity + VectorSubtract ( ent2origin, origin, velocity); + dist = VectorNormalize( velocity); + forward = dist / time; + //FIXME: why multiply by 1.1 + forward *= 1.1; + VectorScale(velocity, forward, velocity); + velocity[2] = time * gravity; + */ + //get the areas the jump pad brush is in + areas = AAS_LinkEntityClientBBox(absmins, absmaxs, -1, PRESENCE_CROUCH); + /* + for (link = areas; link; link = link->next_area) + { + if (link->areanum == 563) + { + ret = qfalse; + } + } + */ + for (link = areas; link; link = link->next_area) + { + if (AAS_AreaJumpPad(link->areanum)) break; + } //end for + if (!link) + { + botimport.Print(PRT_MESSAGE, "trigger_push not in any jump pad area\n"); + AAS_UnlinkFromAreas(areas); + continue; + } //end if + // + botimport.Print(PRT_MESSAGE, "found a trigger_push with velocity %f %f %f\n", velocity[0], velocity[1], velocity[2]); + //if there is a horizontal velocity check for a reachability without air control + if (velocity[0] || velocity[1]) + { + VectorSet(cmdmove, 0, 0, 0); + //VectorCopy(velocity, cmdmove); + //cmdmove[2] = 0; + Com_Memset(&move, 0, sizeof(aas_clientmove_t)); + area2num = 0; + for (i = 0; i < 20; i++) + { + AAS_PredictClientMovement(&move, -1, areastart, PRESENCE_NORMAL, qfalse, + velocity, cmdmove, 0, 30, 0.1f, + SE_HITGROUND|SE_ENTERWATER|SE_ENTERSLIME| + SE_ENTERLAVA|SE_HITGROUNDDAMAGE|SE_TOUCHJUMPPAD|SE_TOUCHTELEPORTER, 0, bot_visualizejumppads); + area2num = move.endarea; + for (link = areas; link; link = link->next_area) + { + if (!AAS_AreaJumpPad(link->areanum)) continue; + if (link->areanum == area2num) break; + } //end if + if (!link) break; + VectorCopy(move.endpos, areastart); + VectorCopy(move.velocity, velocity); + } //end for + if (area2num && i < 20) + { + for (link = areas; link; link = link->next_area) + { + if (!AAS_AreaJumpPad(link->areanum)) continue; + if (AAS_ReachabilityExists(link->areanum, area2num)) continue; + //create a rocket or bfg jump reachability from area1 to area2 + lreach = AAS_AllocReachability(); + if (!lreach) + { + AAS_UnlinkFromAreas(areas); + return; + } //end if + lreach->areanum = area2num; + //NOTE: the facenum is the Z velocity + lreach->facenum = velocity[2]; + //NOTE: the edgenum is the horizontal velocity + lreach->edgenum = sqrt(velocity[0] * velocity[0] + velocity[1] * velocity[1]); + VectorCopy(areastart, lreach->start); + VectorCopy(move.endpos, lreach->end); + lreach->traveltype = TRAVEL_JUMPPAD; + lreach->traveltype |= AAS_TravelFlagsForTeam(ent); + lreach->traveltime = aassettings.rs_jumppad; + lreach->next = areareachability[link->areanum]; + areareachability[link->areanum] = lreach; + // + reach_jumppad++; + } //end for + } //end if + } //end if + // + if (fabs(velocity[0]) > 100 || fabs(velocity[1]) > 100) continue; + //check for areas we can reach with air control + for (area2num = 1; area2num < aasworld.numareas; area2num++) + { + visualize = qfalse; + /* + if (area2num == 3568) + { + for (link = areas; link; link = link->next_area) + { + if (link->areanum == 3380) + { + visualize = qtrue; + botimport.Print(PRT_MESSAGE, "bah\n"); + } //end if + } //end for + } //end if*/ + //never try to go back to one of the original jumppad areas + //and don't create reachabilities if they already exist + for (link = areas; link; link = link->next_area) + { + if (AAS_ReachabilityExists(link->areanum, area2num)) break; + if (AAS_AreaJumpPad(link->areanum)) + { + if (link->areanum == area2num) break; + } //end if + } //end if + if (link) continue; + // + area2 = &aasworld.areas[area2num]; + for (i = 0; i < area2->numfaces; i++) + { + face2num = aasworld.faceindex[area2->firstface + i]; + face2 = &aasworld.faces[abs(face2num)]; + //if it is not a ground face + if (!(face2->faceflags & FACE_GROUND)) continue; + //get the center of the face + AAS_FaceCenter(face2num, facecenter); + //only go higher up + if (facecenter[2] < areastart[2]) continue; + //get the jumppad jump z velocity + zvel = velocity[2]; + //get the horizontal speed for the jump, if it isn't possible to calculate this + //speed + ret = AAS_HorizontalVelocityForJump(zvel, areastart, facecenter, &speed); + if (ret && speed < 150) + { + //direction towards the face center + VectorSubtract(facecenter, areastart, dir); + dir[2] = 0; + hordist = VectorNormalize(dir); + //if (hordist < 1.6 * facecenter[2] - areastart[2]) + { + //get command movement + VectorScale(dir, speed, cmdmove); + // + AAS_PredictClientMovement(&move, -1, areastart, PRESENCE_NORMAL, qfalse, + velocity, cmdmove, 30, 30, 0.1f, + SE_ENTERWATER|SE_ENTERSLIME| + SE_ENTERLAVA|SE_HITGROUNDDAMAGE| + SE_TOUCHJUMPPAD|SE_TOUCHTELEPORTER|SE_HITGROUNDAREA, area2num, visualize); + //if prediction time wasn't enough to fully predict the movement + //don't enter slime or lava and don't fall from too high + if (move.frames < 30 && + !(move.stopevent & (SE_ENTERSLIME|SE_ENTERLAVA|SE_HITGROUNDDAMAGE)) + && (move.stopevent & (SE_HITGROUNDAREA|SE_TOUCHJUMPPAD|SE_TOUCHTELEPORTER))) + { + //never go back to the same jumppad + for (link = areas; link; link = link->next_area) + { + if (link->areanum == move.endarea) break; + } + if (!link) + { + for (link = areas; link; link = link->next_area) + { + if (!AAS_AreaJumpPad(link->areanum)) continue; + if (AAS_ReachabilityExists(link->areanum, area2num)) continue; + //create a jumppad reachability from area1 to area2 + lreach = AAS_AllocReachability(); + if (!lreach) + { + AAS_UnlinkFromAreas(areas); + return; + } //end if + lreach->areanum = move.endarea; + //NOTE: the facenum is the Z velocity + lreach->facenum = velocity[2]; + //NOTE: the edgenum is the horizontal velocity + lreach->edgenum = sqrt(cmdmove[0] * cmdmove[0] + cmdmove[1] * cmdmove[1]); + VectorCopy(areastart, lreach->start); + VectorCopy(facecenter, lreach->end); + lreach->traveltype = TRAVEL_JUMPPAD; + lreach->traveltype |= AAS_TravelFlagsForTeam(ent); + lreach->traveltime = aassettings.rs_aircontrolledjumppad; + lreach->next = areareachability[link->areanum]; + areareachability[link->areanum] = lreach; + // + reach_jumppad++; + } //end for + } + } //end if + } //end if + } //end for + } //end for + } //end for + AAS_UnlinkFromAreas(areas); + } //end for +} //end of the function AAS_Reachability_JumpPad +//=========================================================================== +// never point at ground faces +// always a higher and pretty far area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Reachability_Grapple(int area1num, int area2num) +{ + int face2num, i, j, areanum, numareas, areas[20]; + float mingrappleangle, z, hordist; + bsp_trace_t bsptrace; + aas_trace_t trace; + aas_face_t *face2; + aas_area_t *area1, *area2; + aas_lreachability_t *lreach; + vec3_t areastart, facecenter, start, end, dir, down = {0, 0, -1}; + vec_t *v; + + //only grapple when on the ground or swimming + if (!AAS_AreaGrounded(area1num) && !AAS_AreaSwim(area1num)) return qfalse; + //don't grapple from a crouch area + if (!(AAS_AreaPresenceType(area1num) & PRESENCE_NORMAL)) return qfalse; + //NOTE: disabled area swim it doesn't work right + if (AAS_AreaSwim(area1num)) return qfalse; + // + area1 = &aasworld.areas[area1num]; + area2 = &aasworld.areas[area2num]; + //don't grapple towards way lower areas + if (area2->maxs[2] < area1->mins[2]) return qfalse; + // + VectorCopy(aasworld.areas[area1num].center, start); + //if not a swim area + if (!AAS_AreaSwim(area1num)) + { + if (!AAS_PointAreaNum(start)) Log_Write("area %d center %f %f %f in solid?\r\n", area1num, + start[0], start[1], start[2]); + VectorCopy(start, end); + end[2] -= 1000; + trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, -1); + if (trace.startsolid) return qfalse; + VectorCopy(trace.endpos, areastart); + } //end if + else + { + if (!(AAS_PointContents(start) & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER))) return qfalse; + } //end else + // + //start is now the start point + // + for (i = 0; i < area2->numfaces; i++) + { + face2num = aasworld.faceindex[area2->firstface + i]; + face2 = &aasworld.faces[abs(face2num)]; + //if it is not a solid face + if (!(face2->faceflags & FACE_SOLID)) continue; + //direction towards the first vertex of the face + v = aasworld.vertexes[aasworld.edges[abs(aasworld.edgeindex[face2->firstedge])].v[0]]; + VectorSubtract(v, areastart, dir); + //if the face plane is facing away + if (DotProduct(aasworld.planes[face2->planenum].normal, dir) > 0) continue; + //get the center of the face + AAS_FaceCenter(face2num, facecenter); + //only go higher up with the grapple + if (facecenter[2] < areastart[2] + 64) continue; + //only use vertical faces or downward facing faces + if (DotProduct(aasworld.planes[face2->planenum].normal, down) < 0) continue; + //direction towards the face center + VectorSubtract(facecenter, areastart, dir); + // + z = dir[2]; + dir[2] = 0; + hordist = VectorLength(dir); + if (!hordist) continue; + //if too far + if (hordist > 2000) continue; + //check the minimal angle of the movement + mingrappleangle = 15; //15 degrees + if (z / hordist < tan(2 * M_PI * mingrappleangle / 360)) continue; + // + VectorCopy(facecenter, start); + VectorMA(facecenter, -500, aasworld.planes[face2->planenum].normal, end); + // + bsptrace = AAS_Trace(start, NULL, NULL, end, 0, CONTENTS_SOLID); + //the grapple won't stick to the sky and the grapple point should be near the AAS wall + if ((bsptrace.surface.flags & SURF_SKY) || (bsptrace.fraction * 500 > 32)) continue; + //trace a full bounding box from the area center on the ground to + //the center of the face + VectorSubtract(facecenter, areastart, dir); + VectorNormalize(dir); + VectorMA(areastart, 4, dir, start); + VectorCopy(bsptrace.endpos, end); + trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, -1); + VectorSubtract(trace.endpos, facecenter, dir); + if (VectorLength(dir) > 24) continue; + // + VectorCopy(trace.endpos, start); + VectorCopy(trace.endpos, end); + end[2] -= AAS_FallDamageDistance(); + trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, -1); + if (trace.fraction >= 1) continue; + //area to end in + areanum = AAS_PointAreaNum(trace.endpos); + //if not in lava or slime + if (aasworld.areasettings[areanum].contents & (AREACONTENTS_SLIME|AREACONTENTS_LAVA)) + { + continue; + } //end if + //do not go the the source area + if (areanum == area1num) continue; + //don't create reachabilities if they already exist + if (AAS_ReachabilityExists(area1num, areanum)) continue; + //only end in areas we can stand + if (!AAS_AreaGrounded(areanum)) continue; + //never go through cluster portals!! + numareas = AAS_TraceAreas(areastart, bsptrace.endpos, areas, NULL, 20); + if (numareas >= 20) continue; + for (j = 0; j < numareas; j++) + { + if (aasworld.areasettings[areas[j]].contents & AREACONTENTS_CLUSTERPORTAL) break; + } //end for + if (j < numareas) continue; + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = areanum; + lreach->facenum = face2num; + lreach->edgenum = 0; + VectorCopy(areastart, lreach->start); + //VectorCopy(facecenter, lreach->end); + VectorCopy(bsptrace.endpos, lreach->end); + lreach->traveltype = TRAVEL_GRAPPLEHOOK; + VectorSubtract(lreach->end, lreach->start, dir); + lreach->traveltime = aassettings.rs_startgrapple + VectorLength(dir) * 0.25; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + // + reach_grapple++; + } //end for + // + return qfalse; +} //end of the function AAS_Reachability_Grapple +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_SetWeaponJumpAreaFlags(void) +{ + int ent, i; + vec3_t mins = {-15, -15, -15}, maxs = {15, 15, 15}; + vec3_t origin; + int areanum, weaponjumpareas, spawnflags; + char classname[MAX_EPAIRKEY]; + + weaponjumpareas = 0; + for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) + { + if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; + if ( + !strcmp(classname, "item_armor_body") || + !strcmp(classname, "item_armor_combat") || + !strcmp(classname, "item_health_mega") || + !strcmp(classname, "weapon_grenadelauncher") || + !strcmp(classname, "weapon_rocketlauncher") || + !strcmp(classname, "weapon_lightning") || + !strcmp(classname, "weapon_plasmagun") || + !strcmp(classname, "weapon_railgun") || + !strcmp(classname, "weapon_bfg") || + !strcmp(classname, "item_quad") || + !strcmp(classname, "item_regen") || + !strcmp(classname, "item_invulnerability")) + { + if (AAS_VectorForBSPEpairKey(ent, "origin", origin)) + { + spawnflags = 0; + AAS_IntForBSPEpairKey(ent, "spawnflags", &spawnflags); + //if not a stationary item + if (!(spawnflags & 1)) + { + if (!AAS_DropToFloor(origin, mins, maxs)) + { + botimport.Print(PRT_MESSAGE, "%s in solid at (%1.1f %1.1f %1.1f)\n", + classname, origin[0], origin[1], origin[2]); + } //end if + } //end if + //areanum = AAS_PointAreaNum(origin); + areanum = AAS_BestReachableArea(origin, mins, maxs, origin); + //the bot may rocket jump towards this area + aasworld.areasettings[areanum].areaflags |= AREA_WEAPONJUMP; + // + //if (!AAS_AreaGrounded(areanum)) + // botimport.Print(PRT_MESSAGE, "area not grounded\n"); + // + weaponjumpareas++; + } //end if + } //end if + } //end for + for (i = 1; i < aasworld.numareas; i++) + { + if (aasworld.areasettings[i].contents & AREACONTENTS_JUMPPAD) + { + aasworld.areasettings[i].areaflags |= AREA_WEAPONJUMP; + weaponjumpareas++; + } //end if + } //end for + botimport.Print(PRT_MESSAGE, "%d weapon jump areas\n", weaponjumpareas); +} //end of the function AAS_SetWeaponJumpAreaFlags +//=========================================================================== +// create a possible weapon jump reachability from area1 to area2 +// +// check if there's a cool item in the second area +// check if area1 is lower than area2 +// check if the bot can rocketjump from area1 to area2 +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Reachability_WeaponJump(int area1num, int area2num) +{ + int face2num, i, n, ret, visualize; + float speed, zvel, hordist; + aas_face_t *face2; + aas_area_t *area1, *area2; + aas_lreachability_t *lreach; + vec3_t areastart, facecenter, start, end, dir, cmdmove;// teststart; + vec3_t velocity; + aas_clientmove_t move; + aas_trace_t trace; + + visualize = qfalse; +// if (area1num == 4436 && area2num == 4318) +// { +// visualize = qtrue; +// } + if (!AAS_AreaGrounded(area1num) || AAS_AreaSwim(area1num)) return qfalse; + if (!AAS_AreaGrounded(area2num)) return qfalse; + //NOTE: only weapon jump towards areas with an interesting item in it?? + if (!(aasworld.areasettings[area2num].areaflags & AREA_WEAPONJUMP)) return qfalse; + // + area1 = &aasworld.areas[area1num]; + area2 = &aasworld.areas[area2num]; + //don't weapon jump towards way lower areas + if (area2->maxs[2] < area1->mins[2]) return qfalse; + // + VectorCopy(aasworld.areas[area1num].center, start); + //if not a swim area + if (!AAS_PointAreaNum(start)) Log_Write("area %d center %f %f %f in solid?\r\n", area1num, + start[0], start[1], start[2]); + VectorCopy(start, end); + end[2] -= 1000; + trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, -1); + if (trace.startsolid) return qfalse; + VectorCopy(trace.endpos, areastart); + // + //areastart is now the start point + // + for (i = 0; i < area2->numfaces; i++) + { + face2num = aasworld.faceindex[area2->firstface + i]; + face2 = &aasworld.faces[abs(face2num)]; + //if it is not a solid face + if (!(face2->faceflags & FACE_GROUND)) continue; + //get the center of the face + AAS_FaceCenter(face2num, facecenter); + //only go higher up with weapon jumps + if (facecenter[2] < areastart[2] + 64) continue; + //NOTE: set to 2 to allow bfg jump reachabilities + for (n = 0; n < 1/*2*/; n++) + { + //get the rocket jump z velocity + if (n) zvel = AAS_BFGJumpZVelocity(areastart); + else zvel = AAS_RocketJumpZVelocity(areastart); + //get the horizontal speed for the jump, if it isn't possible to calculate this + //speed (the jump is not possible) then there's no jump reachability created + ret = AAS_HorizontalVelocityForJump(zvel, areastart, facecenter, &speed); + if (ret && speed < 300) + { + //direction towards the face center + VectorSubtract(facecenter, areastart, dir); + dir[2] = 0; + hordist = VectorNormalize(dir); + //if (hordist < 1.6 * (facecenter[2] - areastart[2])) + { + //get command movement + VectorScale(dir, speed, cmdmove); + VectorSet(velocity, 0, 0, zvel); + /* + //get command movement + VectorScale(dir, speed, velocity); + velocity[2] = zvel; + VectorSet(cmdmove, 0, 0, 0); + */ + // + AAS_PredictClientMovement(&move, -1, areastart, PRESENCE_NORMAL, qtrue, + velocity, cmdmove, 30, 30, 0.1f, + SE_ENTERWATER|SE_ENTERSLIME| + SE_ENTERLAVA|SE_HITGROUNDDAMAGE| + SE_TOUCHJUMPPAD|SE_HITGROUND|SE_HITGROUNDAREA, area2num, visualize); + //if prediction time wasn't enough to fully predict the movement + //don't enter slime or lava and don't fall from too high + if (move.frames < 30 && + !(move.stopevent & (SE_ENTERSLIME|SE_ENTERLAVA|SE_HITGROUNDDAMAGE)) + && (move.stopevent & (SE_HITGROUNDAREA|SE_TOUCHJUMPPAD))) + { + //create a rocket or bfg jump reachability from area1 to area2 + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area2num; + lreach->facenum = 0; + lreach->edgenum = 0; + VectorCopy(areastart, lreach->start); + VectorCopy(facecenter, lreach->end); + if (n) + { + lreach->traveltype = TRAVEL_BFGJUMP; + lreach->traveltime = aassettings.rs_bfgjump; + } //end if + else + { + lreach->traveltype = TRAVEL_ROCKETJUMP; + lreach->traveltime = aassettings.rs_rocketjump; + } //end else + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + // + reach_rocketjump++; + return qtrue; + } //end if + } //end if + } //end if + } //end for + } //end for + // + return qfalse; +} //end of the function AAS_Reachability_WeaponJump +//=========================================================================== +// calculates additional walk off ledge reachabilities for the given area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Reachability_WalkOffLedge(int areanum) +{ + int i, j, k, l, m, n, p, areas[10], numareas; + int face1num, face2num, face3num, edge1num, edge2num, edge3num; + int otherareanum, gap, reachareanum, side; + aas_area_t *area, *area2; + aas_face_t *face1, *face2, *face3; + aas_edge_t *edge; + aas_plane_t *plane; + vec_t *v1, *v2; + vec3_t sharededgevec, mid, dir, testend; + aas_lreachability_t *lreach; + aas_trace_t trace; + + if (!AAS_AreaGrounded(areanum) || AAS_AreaSwim(areanum)) return; + // + area = &aasworld.areas[areanum]; + // + for (i = 0; i < area->numfaces; i++) + { + face1num = aasworld.faceindex[area->firstface + i]; + face1 = &aasworld.faces[abs(face1num)]; + //face 1 must be a ground face + if (!(face1->faceflags & FACE_GROUND)) continue; + //go through all the edges of this ground face + for (k = 0; k < face1->numedges; k++) + { + edge1num = aasworld.edgeindex[face1->firstedge + k]; + //find another not ground face using this same edge + for (j = 0; j < area->numfaces; j++) + { + face2num = aasworld.faceindex[area->firstface + j]; + face2 = &aasworld.faces[abs(face2num)]; + //face 2 may not be a ground face + if (face2->faceflags & FACE_GROUND) continue; + //compare all the edges + for (l = 0; l < face2->numedges; l++) + { + edge2num = aasworld.edgeindex[face2->firstedge + l]; + if (abs(edge1num) == abs(edge2num)) + { + //get the area at the other side of the face + if (face2->frontarea == areanum) otherareanum = face2->backarea; + else otherareanum = face2->frontarea; + // + area2 = &aasworld.areas[otherareanum]; + //if the other area is grounded! + if (aasworld.areasettings[otherareanum].areaflags & AREA_GROUNDED) + { + //check for a possible gap + gap = qfalse; + for (n = 0; n < area2->numfaces; n++) + { + face3num = aasworld.faceindex[area2->firstface + n]; + //may not be the shared face of the two areas + if (abs(face3num) == abs(face2num)) continue; + // + face3 = &aasworld.faces[abs(face3num)]; + //find an edge shared by all three faces + for (m = 0; m < face3->numedges; m++) + { + edge3num = aasworld.edgeindex[face3->firstedge + m]; + //but the edge should be shared by all three faces + if (abs(edge3num) == abs(edge1num)) + { + if (!(face3->faceflags & FACE_SOLID)) + { + gap = qtrue; + break; + } //end if + // + if (face3->faceflags & FACE_GROUND) + { + gap = qfalse; + break; + } //end if + //FIXME: there are more situations to be handled + gap = qtrue; + break; + } //end if + } //end for + if (m < face3->numedges) break; + } //end for + if (!gap) break; + } //end if + //check for a walk off ledge reachability + edge = &aasworld.edges[abs(edge1num)]; + side = edge1num < 0; + // + v1 = aasworld.vertexes[edge->v[side]]; + v2 = aasworld.vertexes[edge->v[!side]]; + // + plane = &aasworld.planes[face1->planenum]; + //get the points really into the areas + VectorSubtract(v2, v1, sharededgevec); + CrossProduct(plane->normal, sharededgevec, dir); + VectorNormalize(dir); + // + VectorAdd(v1, v2, mid); + VectorScale(mid, 0.5, mid); + VectorMA(mid, 8, dir, mid); + // + VectorCopy(mid, testend); + testend[2] -= 1000; + trace = AAS_TraceClientBBox(mid, testend, PRESENCE_CROUCH, -1); + // + if (trace.startsolid) + { + //Log_Write("area %d: trace.startsolid\r\n", areanum); + break; + } //end if + reachareanum = AAS_PointAreaNum(trace.endpos); + if (reachareanum == areanum) + { + //Log_Write("area %d: same area\r\n", areanum); + break; + } //end if + if (AAS_ReachabilityExists(areanum, reachareanum)) + { + //Log_Write("area %d: reachability already exists\r\n", areanum); + break; + } //end if + if (!AAS_AreaGrounded(reachareanum) && !AAS_AreaSwim(reachareanum)) + { + //Log_Write("area %d, reach area %d: not grounded and not swim\r\n", areanum, reachareanum); + break; + } //end if + // + if (aasworld.areasettings[reachareanum].contents & (AREACONTENTS_SLIME + | AREACONTENTS_LAVA)) + { + //Log_Write("area %d, reach area %d: lava or slime\r\n", areanum, reachareanum); + break; + } //end if + //if not going through a cluster portal + numareas = AAS_TraceAreas(mid, testend, areas, NULL, sizeof(areas) / sizeof(int)); + for (p = 0; p < numareas; p++) + if (AAS_AreaClusterPortal(areas[p])) + break; + if (p < numareas) + break; + // if a maximum fall height is set and the bot would fall down further + if (aassettings.rs_maxfallheight && fabs(mid[2] - trace.endpos[2]) > aassettings.rs_maxfallheight) + break; + // + lreach = AAS_AllocReachability(); + if (!lreach) break; + lreach->areanum = reachareanum; + lreach->facenum = 0; + lreach->edgenum = edge1num; + VectorCopy(mid, lreach->start); + VectorCopy(trace.endpos, lreach->end); + lreach->traveltype = TRAVEL_WALKOFFLEDGE; + lreach->traveltime = aassettings.rs_startwalkoffledge + fabs(mid[2] - trace.endpos[2]) * 50 / aassettings.phys_gravity; + if (!AAS_AreaSwim(reachareanum) && !AAS_AreaJumpPad(reachareanum)) + { + if (AAS_FallDelta(mid[2] - trace.endpos[2]) > aassettings.phys_falldelta5) + { + lreach->traveltime += aassettings.rs_falldamage5; + } //end if + else if (AAS_FallDelta(mid[2] - trace.endpos[2]) > aassettings.phys_falldelta10) + { + lreach->traveltime += aassettings.rs_falldamage10; + } //end if + } //end if + lreach->next = areareachability[areanum]; + areareachability[areanum] = lreach; + //we've got another walk off ledge reachability + reach_walkoffledge++; + } //end if + } //end for + } //end for + } //end for + } //end for +} //end of the function AAS_Reachability_WalkOffLedge +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_StoreReachability(void) +{ + int i; + aas_areasettings_t *areasettings; + aas_lreachability_t *lreach; + aas_reachability_t *reach; + + if (aasworld.reachability) FreeMemory(aasworld.reachability); + aasworld.reachability = (aas_reachability_t *) GetClearedMemory((numlreachabilities + 10) * sizeof(aas_reachability_t)); + aasworld.reachabilitysize = 1; + for (i = 0; i < aasworld.numareas; i++) + { + areasettings = &aasworld.areasettings[i]; + areasettings->firstreachablearea = aasworld.reachabilitysize; + areasettings->numreachableareas = 0; + for (lreach = areareachability[i]; lreach; lreach = lreach->next) + { + reach = &aasworld.reachability[areasettings->firstreachablearea + + areasettings->numreachableareas]; + reach->areanum = lreach->areanum; + reach->facenum = lreach->facenum; + reach->edgenum = lreach->edgenum; + VectorCopy(lreach->start, reach->start); + VectorCopy(lreach->end, reach->end); + reach->traveltype = lreach->traveltype; + reach->traveltime = lreach->traveltime; + // + areasettings->numreachableareas++; + } //end for + aasworld.reachabilitysize += areasettings->numreachableareas; + } //end for +} //end of the function AAS_StoreReachability +//=========================================================================== +// +// TRAVEL_WALK 100% equal floor height + steps +// TRAVEL_CROUCH 100% +// TRAVEL_BARRIERJUMP 100% +// TRAVEL_JUMP 80% +// TRAVEL_LADDER 100% + fall down from ladder + jump up to ladder +// TRAVEL_WALKOFFLEDGE 90% walk off very steep walls? +// TRAVEL_SWIM 100% +// TRAVEL_WATERJUMP 100% +// TRAVEL_TELEPORT 100% +// TRAVEL_ELEVATOR 100% +// TRAVEL_GRAPPLEHOOK 100% +// TRAVEL_DOUBLEJUMP 0% +// TRAVEL_RAMPJUMP 0% +// TRAVEL_STRAFEJUMP 0% +// TRAVEL_ROCKETJUMP 100% (currently limited towards areas with items) +// TRAVEL_BFGJUMP 0% (currently disabled) +// TRAVEL_JUMPPAD 100% +// TRAVEL_FUNCBOB 100% +// +// Parameter: - +// Returns: true if NOT finished +// Changes Globals: - +//=========================================================================== +int AAS_ContinueInitReachability(float time) +{ + int i, j, todo, start_time; + static float framereachability, reachability_delay; + static int lastpercentage; + + if (!aasworld.loaded) return qfalse; + //if reachability is calculated for all areas + if (aasworld.numreachabilityareas >= aasworld.numareas + 2) return qfalse; + //if starting with area 1 (area 0 is a dummy) + if (aasworld.numreachabilityareas == 1) + { + botimport.Print(PRT_MESSAGE, "calculating reachability...\n"); + lastpercentage = 0; + framereachability = 2000; + reachability_delay = 1000; + } //end if + //number of areas to calculate reachability for this cycle + todo = aasworld.numreachabilityareas + (int) framereachability; + start_time = Sys_MilliSeconds(); + //loop over the areas + for (i = aasworld.numreachabilityareas; i < aasworld.numareas && i < todo; i++) + { + aasworld.numreachabilityareas++; + //only create jumppad reachabilities from jumppad areas + if (aasworld.areasettings[i].contents & AREACONTENTS_JUMPPAD) + { + continue; + } //end if + //loop over the areas + for (j = 1; j < aasworld.numareas; j++) + { + if (i == j) continue; + //never create reachabilities from teleporter or jumppad areas to regular areas + if (aasworld.areasettings[i].contents & (AREACONTENTS_TELEPORTER|AREACONTENTS_JUMPPAD)) + { + if (!(aasworld.areasettings[j].contents & (AREACONTENTS_TELEPORTER|AREACONTENTS_JUMPPAD))) + { + continue; + } //end if + } //end if + //if there already is a reachability link from area i to j + if (AAS_ReachabilityExists(i, j)) continue; + //check for a swim reachability + if (AAS_Reachability_Swim(i, j)) continue; + //check for a simple walk on equal floor height reachability + if (AAS_Reachability_EqualFloorHeight(i, j)) continue; + //check for step, barrier, waterjump and walk off ledge reachabilities + if (AAS_Reachability_Step_Barrier_WaterJump_WalkOffLedge(i, j)) continue; + //check for ladder reachabilities + if (AAS_Reachability_Ladder(i, j)) continue; + //check for a jump reachability + if (AAS_Reachability_Jump(i, j)) continue; + } //end for + //never create these reachabilities from teleporter or jumppad areas + if (aasworld.areasettings[i].contents & (AREACONTENTS_TELEPORTER|AREACONTENTS_JUMPPAD)) + { + continue; + } //end if + //loop over the areas + for (j = 1; j < aasworld.numareas; j++) + { + if (i == j) continue; + // + if (AAS_ReachabilityExists(i, j)) continue; + //check for a grapple hook reachability + if (calcgrapplereach) AAS_Reachability_Grapple(i, j); + //check for a weapon jump reachability + AAS_Reachability_WeaponJump(i, j); + } //end for + //if the calculation took more time than the max reachability delay + if (Sys_MilliSeconds() - start_time > (int) reachability_delay) break; + // + if (aasworld.numreachabilityareas * 1000 / aasworld.numareas > lastpercentage) break; + } //end for + // + if (aasworld.numreachabilityareas == aasworld.numareas) + { + botimport.Print(PRT_MESSAGE, "\r%6.1f%%", (float) 100.0); + botimport.Print(PRT_MESSAGE, "\nplease wait while storing reachability...\n"); + aasworld.numreachabilityareas++; + } //end if + //if this is the last step in the reachability calculations + else if (aasworld.numreachabilityareas == aasworld.numareas + 1) + { + //create additional walk off ledge reachabilities for every area + for (i = 1; i < aasworld.numareas; i++) + { + //only create jumppad reachabilities from jumppad areas + if (aasworld.areasettings[i].contents & AREACONTENTS_JUMPPAD) + { + continue; + } //end if + AAS_Reachability_WalkOffLedge(i); + } //end for + //create jump pad reachabilities + AAS_Reachability_JumpPad(); + //create teleporter reachabilities + AAS_Reachability_Teleport(); + //create elevator (func_plat) reachabilities + AAS_Reachability_Elevator(); + //create func_bobbing reachabilities + AAS_Reachability_FuncBobbing(); + // +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "%6d reach swim\n", reach_swim); + botimport.Print(PRT_MESSAGE, "%6d reach equal floor\n", reach_equalfloor); + botimport.Print(PRT_MESSAGE, "%6d reach step\n", reach_step); + botimport.Print(PRT_MESSAGE, "%6d reach barrier\n", reach_barrier); + botimport.Print(PRT_MESSAGE, "%6d reach waterjump\n", reach_waterjump); + botimport.Print(PRT_MESSAGE, "%6d reach walkoffledge\n", reach_walkoffledge); + botimport.Print(PRT_MESSAGE, "%6d reach jump\n", reach_jump); + botimport.Print(PRT_MESSAGE, "%6d reach ladder\n", reach_ladder); + botimport.Print(PRT_MESSAGE, "%6d reach walk\n", reach_walk); + botimport.Print(PRT_MESSAGE, "%6d reach teleport\n", reach_teleport); + botimport.Print(PRT_MESSAGE, "%6d reach funcbob\n", reach_funcbob); + botimport.Print(PRT_MESSAGE, "%6d reach elevator\n", reach_elevator); + botimport.Print(PRT_MESSAGE, "%6d reach grapple\n", reach_grapple); + botimport.Print(PRT_MESSAGE, "%6d reach rocketjump\n", reach_rocketjump); + botimport.Print(PRT_MESSAGE, "%6d reach jumppad\n", reach_jumppad); +#endif + //*/ + //store all the reachabilities + AAS_StoreReachability(); + //free the reachability link heap + AAS_ShutDownReachabilityHeap(); + // + FreeMemory(areareachability); + // + aasworld.numreachabilityareas++; + // + botimport.Print(PRT_MESSAGE, "calculating clusters...\n"); + } //end if + else + { + lastpercentage = aasworld.numreachabilityareas * 1000 / aasworld.numareas; + botimport.Print(PRT_MESSAGE, "\r%6.1f%%", (float) lastpercentage / 10); + } //end else + //not yet finished + return qtrue; +} //end of the function AAS_ContinueInitReachability +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitReachability(void) +{ + if (!aasworld.loaded) return; + + if (aasworld.reachabilitysize) + { +#ifndef BSPC + if (!((int)LibVarGetValue("forcereachability"))) + { + aasworld.numreachabilityareas = aasworld.numareas + 2; + return; + } //end if +#else + aasworld.numreachabilityareas = aasworld.numareas + 2; + return; +#endif //BSPC + } //end if +#ifndef BSPC + calcgrapplereach = LibVarGetValue("grapplereach"); +#endif + aasworld.savefile = qtrue; + //start with area 1 because area zero is a dummy + aasworld.numreachabilityareas = 1; + ////aasworld.numreachabilityareas = aasworld.numareas + 1; //only calculate entity reachabilities + //setup the heap with reachability links + AAS_SetupReachabilityHeap(); + //allocate area reachability link array + areareachability = (aas_lreachability_t **) GetClearedMemory( + aasworld.numareas * sizeof(aas_lreachability_t *)); + // + AAS_SetWeaponJumpAreaFlags(); +} //end of the function AAS_InitReachable diff --git a/code/botlib/be_aas_reach.h b/code/botlib/be_aas_reach.h index e4b5ef2..fa5dcd4 100755 --- a/code/botlib/be_aas_reach.h +++ b/code/botlib/be_aas_reach.h @@ -1,68 +1,68 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: be_aas_reach.h - * - * desc: AAS - * - * $Archive: /source/code/botlib/be_aas_reach.h $ - * - *****************************************************************************/ - -#ifdef AASINTERN -//initialize calculating the reachabilities -void AAS_InitReachability(void); -//continue calculating the reachabilities -int AAS_ContinueInitReachability(float time); -// -int AAS_BestReachableLinkArea(aas_link_t *areas); -#endif //AASINTERN - -//returns true if the are has reachabilities to other areas -int AAS_AreaReachability(int areanum); -//returns the best reachable area and goal origin for a bounding box at the given origin -int AAS_BestReachableArea(vec3_t origin, vec3_t mins, vec3_t maxs, vec3_t goalorigin); -//returns the best jumppad area from which the bbox at origin is reachable -int AAS_BestReachableFromJumpPadArea(vec3_t origin, vec3_t mins, vec3_t maxs); -//returns the next reachability using the given model -int AAS_NextModelReachability(int num, int modelnum); -//returns the total area of the ground faces of the given area -float AAS_AreaGroundFaceArea(int areanum); -//returns true if the area is crouch only -int AAS_AreaCrouch(int areanum); -//returns true if a player can swim in this area -int AAS_AreaSwim(int areanum); -//returns true if the area is filled with a liquid -int AAS_AreaLiquid(int areanum); -//returns true if the area contains lava -int AAS_AreaLava(int areanum); -//returns true if the area contains slime -int AAS_AreaSlime(int areanum); -//returns true if the area has one or more ground faces -int AAS_AreaGrounded(int areanum); -//returns true if the area has one or more ladder faces -int AAS_AreaLadder(int areanum); -//returns true if the area is a jump pad -int AAS_AreaJumpPad(int areanum); -//returns true if the area is donotenter -int AAS_AreaDoNotEnter(int areanum); +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_reach.h + * + * desc: AAS + * + * $Archive: /source/code/botlib/be_aas_reach.h $ + * + *****************************************************************************/ + +#ifdef AASINTERN +//initialize calculating the reachabilities +void AAS_InitReachability(void); +//continue calculating the reachabilities +int AAS_ContinueInitReachability(float time); +// +int AAS_BestReachableLinkArea(aas_link_t *areas); +#endif //AASINTERN + +//returns true if the are has reachabilities to other areas +int AAS_AreaReachability(int areanum); +//returns the best reachable area and goal origin for a bounding box at the given origin +int AAS_BestReachableArea(vec3_t origin, vec3_t mins, vec3_t maxs, vec3_t goalorigin); +//returns the best jumppad area from which the bbox at origin is reachable +int AAS_BestReachableFromJumpPadArea(vec3_t origin, vec3_t mins, vec3_t maxs); +//returns the next reachability using the given model +int AAS_NextModelReachability(int num, int modelnum); +//returns the total area of the ground faces of the given area +float AAS_AreaGroundFaceArea(int areanum); +//returns true if the area is crouch only +int AAS_AreaCrouch(int areanum); +//returns true if a player can swim in this area +int AAS_AreaSwim(int areanum); +//returns true if the area is filled with a liquid +int AAS_AreaLiquid(int areanum); +//returns true if the area contains lava +int AAS_AreaLava(int areanum); +//returns true if the area contains slime +int AAS_AreaSlime(int areanum); +//returns true if the area has one or more ground faces +int AAS_AreaGrounded(int areanum); +//returns true if the area has one or more ladder faces +int AAS_AreaLadder(int areanum); +//returns true if the area is a jump pad +int AAS_AreaJumpPad(int areanum); +//returns true if the area is donotenter +int AAS_AreaDoNotEnter(int areanum); diff --git a/code/botlib/be_aas_route.c b/code/botlib/be_aas_route.c index 441984d..642be81 100755 --- a/code/botlib/be_aas_route.c +++ b/code/botlib/be_aas_route.c @@ -1,2209 +1,2209 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: be_aas_route.c - * - * desc: AAS - * - * $Archive: /MissionPack/code/botlib/be_aas_route.c $ - * - *****************************************************************************/ - -#include "../game/q_shared.h" -#include "l_utils.h" -#include "l_memory.h" -#include "l_log.h" -#include "l_crc.h" -#include "l_libvar.h" -#include "l_script.h" -#include "l_precomp.h" -#include "l_struct.h" -#include "aasfile.h" -#include "../game/botlib.h" -#include "../game/be_aas.h" -#include "be_aas_funcs.h" -#include "be_interface.h" -#include "be_aas_def.h" - -#define ROUTING_DEBUG - -//travel time in hundreths of a second = distance * 100 / speed -#define DISTANCEFACTOR_CROUCH 1.3f //crouch speed = 100 -#define DISTANCEFACTOR_SWIM 1 //should be 0.66, swim speed = 150 -#define DISTANCEFACTOR_WALK 0.33f //walk speed = 300 - -//cache refresh time -#define CACHE_REFRESHTIME 15.0f //15 seconds refresh time - -//maximum number of routing updates each frame -#define MAX_FRAMEROUTINGUPDATES 10 - - -/* - - area routing cache: - stores the distances within one cluster to a specific goal area - this goal area is in this same cluster and could be a cluster portal - for every cluster there's a list with routing cache for every area - in that cluster (including the portals of that cluster) - area cache stores aasworld.clusters[?].numreachabilityareas travel times - - portal routing cache: - stores the distances of all portals to a specific goal area - this goal area could be in any cluster and could also be a cluster portal - for every area (aasworld.numareas) the portal cache stores - aasworld.numportals travel times - -*/ - -#ifdef ROUTING_DEBUG -int numareacacheupdates; -int numportalcacheupdates; -#endif //ROUTING_DEBUG - -int routingcachesize; -int max_routingcachesize; - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -#ifdef ROUTING_DEBUG -void AAS_RoutingInfo(void) -{ - botimport.Print(PRT_MESSAGE, "%d area cache updates\n", numareacacheupdates); - botimport.Print(PRT_MESSAGE, "%d portal cache updates\n", numportalcacheupdates); - botimport.Print(PRT_MESSAGE, "%d bytes routing cache\n", routingcachesize); -} //end of the function AAS_RoutingInfo -#endif //ROUTING_DEBUG -//=========================================================================== -// returns the number of the area in the cluster -// assumes the given area is in the given cluster or a portal of the cluster -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -__inline int AAS_ClusterAreaNum(int cluster, int areanum) -{ - int side, areacluster; - - areacluster = aasworld.areasettings[areanum].cluster; - if (areacluster > 0) return aasworld.areasettings[areanum].clusterareanum; - else - { -/*#ifdef ROUTING_DEBUG - if (aasworld.portals[-areacluster].frontcluster != cluster && - aasworld.portals[-areacluster].backcluster != cluster) - { - botimport.Print(PRT_ERROR, "portal %d: does not belong to cluster %d\n" - , -areacluster, cluster); - } //end if -#endif //ROUTING_DEBUG*/ - side = aasworld.portals[-areacluster].frontcluster != cluster; - return aasworld.portals[-areacluster].clusterareanum[side]; - } //end else -} //end of the function AAS_ClusterAreaNum -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_InitTravelFlagFromType(void) -{ - int i; - - for (i = 0; i < MAX_TRAVELTYPES; i++) - { - aasworld.travelflagfortype[i] = TFL_INVALID; - } //end for - aasworld.travelflagfortype[TRAVEL_INVALID] = TFL_INVALID; - aasworld.travelflagfortype[TRAVEL_WALK] = TFL_WALK; - aasworld.travelflagfortype[TRAVEL_CROUCH] = TFL_CROUCH; - aasworld.travelflagfortype[TRAVEL_BARRIERJUMP] = TFL_BARRIERJUMP; - aasworld.travelflagfortype[TRAVEL_JUMP] = TFL_JUMP; - aasworld.travelflagfortype[TRAVEL_LADDER] = TFL_LADDER; - aasworld.travelflagfortype[TRAVEL_WALKOFFLEDGE] = TFL_WALKOFFLEDGE; - aasworld.travelflagfortype[TRAVEL_SWIM] = TFL_SWIM; - aasworld.travelflagfortype[TRAVEL_WATERJUMP] = TFL_WATERJUMP; - aasworld.travelflagfortype[TRAVEL_TELEPORT] = TFL_TELEPORT; - aasworld.travelflagfortype[TRAVEL_ELEVATOR] = TFL_ELEVATOR; - aasworld.travelflagfortype[TRAVEL_ROCKETJUMP] = TFL_ROCKETJUMP; - aasworld.travelflagfortype[TRAVEL_BFGJUMP] = TFL_BFGJUMP; - aasworld.travelflagfortype[TRAVEL_GRAPPLEHOOK] = TFL_GRAPPLEHOOK; - aasworld.travelflagfortype[TRAVEL_DOUBLEJUMP] = TFL_DOUBLEJUMP; - aasworld.travelflagfortype[TRAVEL_RAMPJUMP] = TFL_RAMPJUMP; - aasworld.travelflagfortype[TRAVEL_STRAFEJUMP] = TFL_STRAFEJUMP; - aasworld.travelflagfortype[TRAVEL_JUMPPAD] = TFL_JUMPPAD; - aasworld.travelflagfortype[TRAVEL_FUNCBOB] = TFL_FUNCBOB; -} //end of the function AAS_InitTravelFlagFromType -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -__inline int AAS_TravelFlagForType_inline(int traveltype) -{ - int tfl; - - tfl = 0; - if (tfl & TRAVELFLAG_NOTTEAM1) - tfl |= TFL_NOTTEAM1; - if (tfl & TRAVELFLAG_NOTTEAM2) - tfl |= TFL_NOTTEAM2; - traveltype &= TRAVELTYPE_MASK; - if (traveltype < 0 || traveltype >= MAX_TRAVELTYPES) - return TFL_INVALID; - tfl |= aasworld.travelflagfortype[traveltype]; - return tfl; -} //end of the function AAS_TravelFlagForType_inline -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_TravelFlagForType(int traveltype) -{ - return AAS_TravelFlagForType_inline(traveltype); -} //end of the function AAS_TravelFlagForType_inline -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_UnlinkCache(aas_routingcache_t *cache) -{ - if (cache->time_next) cache->time_next->time_prev = cache->time_prev; - else aasworld.newestcache = cache->time_prev; - if (cache->time_prev) cache->time_prev->time_next = cache->time_next; - else aasworld.oldestcache = cache->time_next; - cache->time_next = NULL; - cache->time_prev = NULL; -} //end of the function AAS_UnlinkCache -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_LinkCache(aas_routingcache_t *cache) -{ - if (aasworld.newestcache) - { - aasworld.newestcache->time_next = cache; - cache->time_prev = aasworld.newestcache; - } //end if - else - { - aasworld.oldestcache = cache; - cache->time_prev = NULL; - } //end else - cache->time_next = NULL; - aasworld.newestcache = cache; -} //end of the function AAS_LinkCache -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_FreeRoutingCache(aas_routingcache_t *cache) -{ - AAS_UnlinkCache(cache); - routingcachesize -= cache->size; - FreeMemory(cache); -} //end of the function AAS_FreeRoutingCache -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_RemoveRoutingCacheInCluster( int clusternum ) -{ - int i; - aas_routingcache_t *cache, *nextcache; - aas_cluster_t *cluster; - - if (!aasworld.clusterareacache) - return; - cluster = &aasworld.clusters[clusternum]; - for (i = 0; i < cluster->numareas; i++) - { - for (cache = aasworld.clusterareacache[clusternum][i]; cache; cache = nextcache) - { - nextcache = cache->next; - AAS_FreeRoutingCache(cache); - } //end for - aasworld.clusterareacache[clusternum][i] = NULL; - } //end for -} //end of the function AAS_RemoveRoutingCacheInCluster -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_RemoveRoutingCacheUsingArea( int areanum ) -{ - int i, clusternum; - aas_routingcache_t *cache, *nextcache; - - clusternum = aasworld.areasettings[areanum].cluster; - if (clusternum > 0) - { - //remove all the cache in the cluster the area is in - AAS_RemoveRoutingCacheInCluster( clusternum ); - } //end if - else - { - // if this is a portal remove all cache in both the front and back cluster - AAS_RemoveRoutingCacheInCluster( aasworld.portals[-clusternum].frontcluster ); - AAS_RemoveRoutingCacheInCluster( aasworld.portals[-clusternum].backcluster ); - } //end else - // remove all portal cache - for (i = 0; i < aasworld.numareas; i++) - { - //refresh portal cache - for (cache = aasworld.portalcache[i]; cache; cache = nextcache) - { - nextcache = cache->next; - AAS_FreeRoutingCache(cache); - } //end for - aasworld.portalcache[i] = NULL; - } //end for -} //end of the function AAS_RemoveRoutingCacheUsingArea -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_EnableRoutingArea(int areanum, int enable) -{ - int flags; - - if (areanum <= 0 || areanum >= aasworld.numareas) - { - if (bot_developer) - { - botimport.Print(PRT_ERROR, "AAS_EnableRoutingArea: areanum %d out of range\n", areanum); - } //end if - return 0; - } //end if - flags = aasworld.areasettings[areanum].areaflags & AREA_DISABLED; - if (enable < 0) - return !flags; - - if (enable) - aasworld.areasettings[areanum].areaflags &= ~AREA_DISABLED; - else - aasworld.areasettings[areanum].areaflags |= AREA_DISABLED; - // if the status of the area changed - if ( (flags & AREA_DISABLED) != (aasworld.areasettings[areanum].areaflags & AREA_DISABLED) ) - { - //remove all routing cache involving this area - AAS_RemoveRoutingCacheUsingArea( areanum ); - } //end if - return !flags; -} //end of the function AAS_EnableRoutingArea -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -__inline float AAS_RoutingTime(void) -{ - return AAS_Time(); -} //end of the function AAS_RoutingTime -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_GetAreaContentsTravelFlags(int areanum) -{ - int contents, tfl; - - contents = aasworld.areasettings[areanum].contents; - tfl = 0; - if (contents & AREACONTENTS_WATER) - tfl |= TFL_WATER; - else if (contents & AREACONTENTS_SLIME) - tfl |= TFL_SLIME; - else if (contents & AREACONTENTS_LAVA) - tfl |= TFL_LAVA; - else - tfl |= TFL_AIR; - if (contents & AREACONTENTS_DONOTENTER) - tfl |= TFL_DONOTENTER; - if (contents & AREACONTENTS_NOTTEAM1) - tfl |= TFL_NOTTEAM1; - if (contents & AREACONTENTS_NOTTEAM2) - tfl |= TFL_NOTTEAM2; - if (aasworld.areasettings[areanum].areaflags & AREA_BRIDGE) - tfl |= TFL_BRIDGE; - return tfl; -} //end of the function AAS_GetAreaContentsTravelFlags -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -__inline int AAS_AreaContentsTravelFlags_inline(int areanum) -{ - return aasworld.areacontentstravelflags[areanum]; -} //end of the function AAS_AreaContentsTravelFlags -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_AreaContentsTravelFlags(int areanum) -{ - return aasworld.areacontentstravelflags[areanum]; -} //end of the function AAS_AreaContentsTravelFlags -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_InitAreaContentsTravelFlags(void) -{ - int i; - - if (aasworld.areacontentstravelflags) FreeMemory(aasworld.areacontentstravelflags); - aasworld.areacontentstravelflags = (int *) GetClearedMemory(aasworld.numareas * sizeof(int)); - // - for (i = 0; i < aasworld.numareas; i++) { - aasworld.areacontentstravelflags[i] = AAS_GetAreaContentsTravelFlags(i); - } -} //end of the function AAS_InitAreaContentsTravelFlags -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_CreateReversedReachability(void) -{ - int i, n; - aas_reversedlink_t *revlink; - aas_reachability_t *reach; - aas_areasettings_t *settings; - char *ptr; -#ifdef DEBUG - int starttime; - - starttime = Sys_MilliSeconds(); -#endif - //free reversed links that have already been created - if (aasworld.reversedreachability) FreeMemory(aasworld.reversedreachability); - //allocate memory for the reversed reachability links - ptr = (char *) GetClearedMemory(aasworld.numareas * sizeof(aas_reversedreachability_t) + - aasworld.reachabilitysize * sizeof(aas_reversedlink_t)); - // - aasworld.reversedreachability = (aas_reversedreachability_t *) ptr; - //pointer to the memory for the reversed links - ptr += aasworld.numareas * sizeof(aas_reversedreachability_t); - //check all reachabilities of all areas - for (i = 1; i < aasworld.numareas; i++) - { - //settings of the area - settings = &aasworld.areasettings[i]; - // - if (settings->numreachableareas >= 128) - botimport.Print(PRT_WARNING, "area %d has more than 128 reachabilities\n", i); - //create reversed links for the reachabilities - for (n = 0; n < settings->numreachableareas && n < 128; n++) - { - //reachability link - reach = &aasworld.reachability[settings->firstreachablearea + n]; - // - revlink = (aas_reversedlink_t *) ptr; - ptr += sizeof(aas_reversedlink_t); - // - revlink->areanum = i; - revlink->linknum = settings->firstreachablearea + n; - revlink->next = aasworld.reversedreachability[reach->areanum].first; - aasworld.reversedreachability[reach->areanum].first = revlink; - aasworld.reversedreachability[reach->areanum].numlinks++; - } //end for - } //end for -#ifdef DEBUG - botimport.Print(PRT_MESSAGE, "reversed reachability %d msec\n", Sys_MilliSeconds() - starttime); -#endif -} //end of the function AAS_CreateReversedReachability -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -unsigned short int AAS_AreaTravelTime(int areanum, vec3_t start, vec3_t end) -{ - int intdist; - float dist; - vec3_t dir; - - VectorSubtract(start, end, dir); - dist = VectorLength(dir); - //if crouch only area - if (AAS_AreaCrouch(areanum)) dist *= DISTANCEFACTOR_CROUCH; - //if swim area - else if (AAS_AreaSwim(areanum)) dist *= DISTANCEFACTOR_SWIM; - //normal walk area - else dist *= DISTANCEFACTOR_WALK; - // - intdist = (int) dist; - //make sure the distance isn't zero - if (intdist <= 0) intdist = 1; - return intdist; -} //end of the function AAS_AreaTravelTime -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_CalculateAreaTravelTimes(void) -{ - int i, l, n, size; - char *ptr; - vec3_t end; - aas_reversedreachability_t *revreach; - aas_reversedlink_t *revlink; - aas_reachability_t *reach; - aas_areasettings_t *settings; - int starttime; - - starttime = Sys_MilliSeconds(); - //if there are still area travel times, free the memory - if (aasworld.areatraveltimes) FreeMemory(aasworld.areatraveltimes); - //get the total size of all the area travel times - size = aasworld.numareas * sizeof(unsigned short **); - for (i = 0; i < aasworld.numareas; i++) - { - revreach = &aasworld.reversedreachability[i]; - //settings of the area - settings = &aasworld.areasettings[i]; - // - size += settings->numreachableareas * sizeof(unsigned short *); - // - size += settings->numreachableareas * revreach->numlinks * sizeof(unsigned short); - } //end for - //allocate memory for the area travel times - ptr = (char *) GetClearedMemory(size); - aasworld.areatraveltimes = (unsigned short ***) ptr; - ptr += aasworld.numareas * sizeof(unsigned short **); - //calcluate the travel times for all the areas - for (i = 0; i < aasworld.numareas; i++) - { - //reversed reachabilities of this area - revreach = &aasworld.reversedreachability[i]; - //settings of the area - settings = &aasworld.areasettings[i]; - // - aasworld.areatraveltimes[i] = (unsigned short **) ptr; - ptr += settings->numreachableareas * sizeof(unsigned short *); - // - for (l = 0; l < settings->numreachableareas; l++) - { - aasworld.areatraveltimes[i][l] = (unsigned short *) ptr; - ptr += revreach->numlinks * sizeof(unsigned short); - //reachability link - reach = &aasworld.reachability[settings->firstreachablearea + l]; - // - for (n = 0, revlink = revreach->first; revlink; revlink = revlink->next, n++) - { - VectorCopy(aasworld.reachability[revlink->linknum].end, end); - // - aasworld.areatraveltimes[i][l][n] = AAS_AreaTravelTime(i, end, reach->start); - } //end for - } //end for - } //end for -#ifdef DEBUG - botimport.Print(PRT_MESSAGE, "area travel times %d msec\n", Sys_MilliSeconds() - starttime); -#endif -} //end of the function AAS_CalculateAreaTravelTimes -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_PortalMaxTravelTime(int portalnum) -{ - int l, n, t, maxt; - aas_portal_t *portal; - aas_reversedreachability_t *revreach; - aas_reversedlink_t *revlink; - aas_areasettings_t *settings; - - portal = &aasworld.portals[portalnum]; - //reversed reachabilities of this portal area - revreach = &aasworld.reversedreachability[portal->areanum]; - //settings of the portal area - settings = &aasworld.areasettings[portal->areanum]; - // - maxt = 0; - for (l = 0; l < settings->numreachableareas; l++) - { - for (n = 0, revlink = revreach->first; revlink; revlink = revlink->next, n++) - { - t = aasworld.areatraveltimes[portal->areanum][l][n]; - if (t > maxt) - { - maxt = t; - } //end if - } //end for - } //end for - return maxt; -} //end of the function AAS_PortalMaxTravelTime -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_InitPortalMaxTravelTimes(void) -{ - int i; - - if (aasworld.portalmaxtraveltimes) FreeMemory(aasworld.portalmaxtraveltimes); - - aasworld.portalmaxtraveltimes = (int *) GetClearedMemory(aasworld.numportals * sizeof(int)); - - for (i = 0; i < aasworld.numportals; i++) - { - aasworld.portalmaxtraveltimes[i] = AAS_PortalMaxTravelTime(i); - //botimport.Print(PRT_MESSAGE, "portal %d max tt = %d\n", i, aasworld.portalmaxtraveltimes[i]); - } //end for -} //end of the function AAS_InitPortalMaxTravelTimes -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -/* -int AAS_FreeOldestCache(void) -{ - int i, j, bestcluster, bestarea, freed; - float besttime; - aas_routingcache_t *cache, *bestcache; - - freed = qfalse; - besttime = 999999999; - bestcache = NULL; - bestcluster = 0; - bestarea = 0; - //refresh cluster cache - for (i = 0; i < aasworld.numclusters; i++) - { - for (j = 0; j < aasworld.clusters[i].numareas; j++) - { - for (cache = aasworld.clusterareacache[i][j]; cache; cache = cache->next) - { - //never remove cache leading towards a portal - if (aasworld.areasettings[cache->areanum].cluster < 0) continue; - //if this cache is older than the cache we found so far - if (cache->time < besttime) - { - bestcache = cache; - bestcluster = i; - bestarea = j; - besttime = cache->time; - } //end if - } //end for - } //end for - } //end for - if (bestcache) - { - cache = bestcache; - if (cache->prev) cache->prev->next = cache->next; - else aasworld.clusterareacache[bestcluster][bestarea] = cache->next; - if (cache->next) cache->next->prev = cache->prev; - AAS_FreeRoutingCache(cache); - freed = qtrue; - } //end if - besttime = 999999999; - bestcache = NULL; - bestarea = 0; - for (i = 0; i < aasworld.numareas; i++) - { - //refresh portal cache - for (cache = aasworld.portalcache[i]; cache; cache = cache->next) - { - if (cache->time < besttime) - { - bestcache = cache; - bestarea = i; - besttime = cache->time; - } //end if - } //end for - } //end for - if (bestcache) - { - cache = bestcache; - if (cache->prev) cache->prev->next = cache->next; - else aasworld.portalcache[bestarea] = cache->next; - if (cache->next) cache->next->prev = cache->prev; - AAS_FreeRoutingCache(cache); - freed = qtrue; - } //end if - return freed; -} //end of the function AAS_FreeOldestCache -*/ -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_FreeOldestCache(void) -{ - int clusterareanum; - aas_routingcache_t *cache; - - for (cache = aasworld.oldestcache; cache; cache = cache->time_next) { - // never free area cache leading towards a portal - if (cache->type == CACHETYPE_AREA && aasworld.areasettings[cache->areanum].cluster < 0) { - continue; - } - break; - } - if (cache) { - // unlink the cache - if (cache->type == CACHETYPE_AREA) { - //number of the area in the cluster - clusterareanum = AAS_ClusterAreaNum(cache->cluster, cache->areanum); - // unlink from cluster area cache - if (cache->prev) cache->prev->next = cache->next; - else aasworld.clusterareacache[cache->cluster][clusterareanum] = cache->next; - if (cache->next) cache->next->prev = cache->prev; - } - else { - // unlink from portal cache - if (cache->prev) cache->prev->next = cache->next; - else aasworld.portalcache[cache->areanum] = cache->next; - if (cache->next) cache->next->prev = cache->prev; - } - AAS_FreeRoutingCache(cache); - return qtrue; - } - return qfalse; -} //end of the function AAS_FreeOldestCache -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -aas_routingcache_t *AAS_AllocRoutingCache(int numtraveltimes) -{ - aas_routingcache_t *cache; - int size; - - // - size = sizeof(aas_routingcache_t) - + numtraveltimes * sizeof(unsigned short int) - + numtraveltimes * sizeof(unsigned char); - // - routingcachesize += size; - // - cache = (aas_routingcache_t *) GetClearedMemory(size); - cache->reachabilities = (unsigned char *) cache + sizeof(aas_routingcache_t) - + numtraveltimes * sizeof(unsigned short int); - cache->size = size; - return cache; -} //end of the function AAS_AllocRoutingCache -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_FreeAllClusterAreaCache(void) -{ - int i, j; - aas_routingcache_t *cache, *nextcache; - aas_cluster_t *cluster; - - //free all cluster cache if existing - if (!aasworld.clusterareacache) return; - //free caches - for (i = 0; i < aasworld.numclusters; i++) - { - cluster = &aasworld.clusters[i]; - for (j = 0; j < cluster->numareas; j++) - { - for (cache = aasworld.clusterareacache[i][j]; cache; cache = nextcache) - { - nextcache = cache->next; - AAS_FreeRoutingCache(cache); - } //end for - aasworld.clusterareacache[i][j] = NULL; - } //end for - } //end for - //free the cluster cache array - FreeMemory(aasworld.clusterareacache); - aasworld.clusterareacache = NULL; -} //end of the function AAS_FreeAllClusterAreaCache -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_InitClusterAreaCache(void) -{ - int i, size; - char *ptr; - - // - for (size = 0, i = 0; i < aasworld.numclusters; i++) - { - size += aasworld.clusters[i].numareas; - } //end for - //two dimensional array with pointers for every cluster to routing cache - //for every area in that cluster - ptr = (char *) GetClearedMemory( - aasworld.numclusters * sizeof(aas_routingcache_t **) + - size * sizeof(aas_routingcache_t *)); - aasworld.clusterareacache = (aas_routingcache_t ***) ptr; - ptr += aasworld.numclusters * sizeof(aas_routingcache_t **); - for (i = 0; i < aasworld.numclusters; i++) - { - aasworld.clusterareacache[i] = (aas_routingcache_t **) ptr; - ptr += aasworld.clusters[i].numareas * sizeof(aas_routingcache_t *); - } //end for -} //end of the function AAS_InitClusterAreaCache -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_FreeAllPortalCache(void) -{ - int i; - aas_routingcache_t *cache, *nextcache; - - //free all portal cache if existing - if (!aasworld.portalcache) return; - //free portal caches - for (i = 0; i < aasworld.numareas; i++) - { - for (cache = aasworld.portalcache[i]; cache; cache = nextcache) - { - nextcache = cache->next; - AAS_FreeRoutingCache(cache); - } //end for - aasworld.portalcache[i] = NULL; - } //end for - FreeMemory(aasworld.portalcache); - aasworld.portalcache = NULL; -} //end of the function AAS_FreeAllPortalCache -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_InitPortalCache(void) -{ - // - aasworld.portalcache = (aas_routingcache_t **) GetClearedMemory( - aasworld.numareas * sizeof(aas_routingcache_t *)); -} //end of the function AAS_InitPortalCache -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_InitRoutingUpdate(void) -{ - int i, maxreachabilityareas; - - //free routing update fields if already existing - if (aasworld.areaupdate) FreeMemory(aasworld.areaupdate); - // - maxreachabilityareas = 0; - for (i = 0; i < aasworld.numclusters; i++) - { - if (aasworld.clusters[i].numreachabilityareas > maxreachabilityareas) - { - maxreachabilityareas = aasworld.clusters[i].numreachabilityareas; - } //end if - } //end for - //allocate memory for the routing update fields - aasworld.areaupdate = (aas_routingupdate_t *) GetClearedMemory( - maxreachabilityareas * sizeof(aas_routingupdate_t)); - // - if (aasworld.portalupdate) FreeMemory(aasworld.portalupdate); - //allocate memory for the portal update fields - aasworld.portalupdate = (aas_routingupdate_t *) GetClearedMemory( - (aasworld.numportals+1) * sizeof(aas_routingupdate_t)); -} //end of the function AAS_InitRoutingUpdate -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_CreateAllRoutingCache(void) -{ - int i, j, t; - - aasworld.initialized = qtrue; - botimport.Print(PRT_MESSAGE, "AAS_CreateAllRoutingCache\n"); - for (i = 1; i < aasworld.numareas; i++) - { - if (!AAS_AreaReachability(i)) continue; - for (j = 1; j < aasworld.numareas; j++) - { - if (i == j) continue; - if (!AAS_AreaReachability(j)) continue; - t = AAS_AreaTravelTimeToGoalArea(i, aasworld.areas[i].center, j, TFL_DEFAULT); - //Log_Write("traveltime from %d to %d is %d", i, j, t); - } //end for - } //end for - aasworld.initialized = qfalse; -} //end of the function AAS_CreateAllRoutingCache -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== - -//the route cache header -//this header is followed by numportalcache + numareacache aas_routingcache_t -//structures that store routing cache -typedef struct routecacheheader_s -{ - int ident; - int version; - int numareas; - int numclusters; - int areacrc; - int clustercrc; - int numportalcache; - int numareacache; -} routecacheheader_t; - -#define RCID (('C'<<24)+('R'<<16)+('E'<<8)+'M') -#define RCVERSION 2 - -//void AAS_DecompressVis(byte *in, int numareas, byte *decompressed); -//int AAS_CompressVis(byte *vis, int numareas, byte *dest); - -void AAS_WriteRouteCache(void) -{ - int i, j, numportalcache, numareacache, totalsize; - aas_routingcache_t *cache; - aas_cluster_t *cluster; - fileHandle_t fp; - char filename[MAX_QPATH]; - routecacheheader_t routecacheheader; - - numportalcache = 0; - for (i = 0; i < aasworld.numareas; i++) - { - for (cache = aasworld.portalcache[i]; cache; cache = cache->next) - { - numportalcache++; - } //end for - } //end for - numareacache = 0; - for (i = 0; i < aasworld.numclusters; i++) - { - cluster = &aasworld.clusters[i]; - for (j = 0; j < cluster->numareas; j++) - { - for (cache = aasworld.clusterareacache[i][j]; cache; cache = cache->next) - { - numareacache++; - } //end for - } //end for - } //end for - // open the file for writing - Com_sprintf(filename, MAX_QPATH, "maps/%s.rcd", aasworld.mapname); - botimport.FS_FOpenFile( filename, &fp, FS_WRITE ); - if (!fp) - { - AAS_Error("Unable to open file: %s\n", filename); - return; - } //end if - //create the header - routecacheheader.ident = RCID; - routecacheheader.version = RCVERSION; - routecacheheader.numareas = aasworld.numareas; - routecacheheader.numclusters = aasworld.numclusters; - routecacheheader.areacrc = CRC_ProcessString( (unsigned char *)aasworld.areas, sizeof(aas_area_t) * aasworld.numareas ); - routecacheheader.clustercrc = CRC_ProcessString( (unsigned char *)aasworld.clusters, sizeof(aas_cluster_t) * aasworld.numclusters ); - routecacheheader.numportalcache = numportalcache; - routecacheheader.numareacache = numareacache; - //write the header - botimport.FS_Write(&routecacheheader, sizeof(routecacheheader_t), fp); - // - totalsize = 0; - //write all the cache - for (i = 0; i < aasworld.numareas; i++) - { - for (cache = aasworld.portalcache[i]; cache; cache = cache->next) - { - botimport.FS_Write(cache, cache->size, fp); - totalsize += cache->size; - } //end for - } //end for - for (i = 0; i < aasworld.numclusters; i++) - { - cluster = &aasworld.clusters[i]; - for (j = 0; j < cluster->numareas; j++) - { - for (cache = aasworld.clusterareacache[i][j]; cache; cache = cache->next) - { - botimport.FS_Write(cache, cache->size, fp); - totalsize += cache->size; - } //end for - } //end for - } //end for - // write the visareas - /* - for (i = 0; i < aasworld.numareas; i++) - { - if (!aasworld.areavisibility[i]) { - size = 0; - botimport.FS_Write(&size, sizeof(int), fp); - continue; - } - AAS_DecompressVis( aasworld.areavisibility[i], aasworld.numareas, aasworld.decompressedvis ); - size = AAS_CompressVis( aasworld.decompressedvis, aasworld.numareas, aasworld.decompressedvis ); - botimport.FS_Write(&size, sizeof(int), fp); - botimport.FS_Write(aasworld.decompressedvis, size, fp); - } - */ - // - botimport.FS_FCloseFile(fp); - botimport.Print(PRT_MESSAGE, "\nroute cache written to %s\n", filename); - botimport.Print(PRT_MESSAGE, "written %d bytes of routing cache\n", totalsize); -} //end of the function AAS_WriteRouteCache -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -aas_routingcache_t *AAS_ReadCache(fileHandle_t fp) -{ - int size; - aas_routingcache_t *cache; - - botimport.FS_Read(&size, sizeof(size), fp); - cache = (aas_routingcache_t *) GetMemory(size); - cache->size = size; - botimport.FS_Read((unsigned char *)cache + sizeof(size), size - sizeof(size), fp); - cache->reachabilities = (unsigned char *) cache + sizeof(aas_routingcache_t) - sizeof(unsigned short) + - (size - sizeof(aas_routingcache_t) + sizeof(unsigned short)) / 3 * 2; - return cache; -} //end of the function AAS_ReadCache -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_ReadRouteCache(void) -{ - int i, clusterareanum;//, size; - fileHandle_t fp; - char filename[MAX_QPATH]; - routecacheheader_t routecacheheader; - aas_routingcache_t *cache; - - Com_sprintf(filename, MAX_QPATH, "maps/%s.rcd", aasworld.mapname); - botimport.FS_FOpenFile( filename, &fp, FS_READ ); - if (!fp) - { - return qfalse; - } //end if - botimport.FS_Read(&routecacheheader, sizeof(routecacheheader_t), fp ); - if (routecacheheader.ident != RCID) - { - AAS_Error("%s is not a route cache dump\n"); - return qfalse; - } //end if - if (routecacheheader.version != RCVERSION) - { - AAS_Error("route cache dump has wrong version %d, should be %d", routecacheheader.version, RCVERSION); - return qfalse; - } //end if - if (routecacheheader.numareas != aasworld.numareas) - { - //AAS_Error("route cache dump has wrong number of areas\n"); - return qfalse; - } //end if - if (routecacheheader.numclusters != aasworld.numclusters) - { - //AAS_Error("route cache dump has wrong number of clusters\n"); - return qfalse; - } //end if - if (routecacheheader.areacrc != - CRC_ProcessString( (unsigned char *)aasworld.areas, sizeof(aas_area_t) * aasworld.numareas )) - { - //AAS_Error("route cache dump area CRC incorrect\n"); - return qfalse; - } //end if - if (routecacheheader.clustercrc != - CRC_ProcessString( (unsigned char *)aasworld.clusters, sizeof(aas_cluster_t) * aasworld.numclusters )) - { - //AAS_Error("route cache dump cluster CRC incorrect\n"); - return qfalse; - } //end if - //read all the portal cache - for (i = 0; i < routecacheheader.numportalcache; i++) - { - cache = AAS_ReadCache(fp); - cache->next = aasworld.portalcache[cache->areanum]; - cache->prev = NULL; - if (aasworld.portalcache[cache->areanum]) - aasworld.portalcache[cache->areanum]->prev = cache; - aasworld.portalcache[cache->areanum] = cache; - } //end for - //read all the cluster area cache - for (i = 0; i < routecacheheader.numareacache; i++) - { - cache = AAS_ReadCache(fp); - clusterareanum = AAS_ClusterAreaNum(cache->cluster, cache->areanum); - cache->next = aasworld.clusterareacache[cache->cluster][clusterareanum]; - cache->prev = NULL; - if (aasworld.clusterareacache[cache->cluster][clusterareanum]) - aasworld.clusterareacache[cache->cluster][clusterareanum]->prev = cache; - aasworld.clusterareacache[cache->cluster][clusterareanum] = cache; - } //end for - // read the visareas - /* - aasworld.areavisibility = (byte **) GetClearedMemory(aasworld.numareas * sizeof(byte *)); - aasworld.decompressedvis = (byte *) GetClearedMemory(aasworld.numareas * sizeof(byte)); - for (i = 0; i < aasworld.numareas; i++) - { - botimport.FS_Read(&size, sizeof(size), fp ); - if (size) { - aasworld.areavisibility[i] = (byte *) GetMemory(size); - botimport.FS_Read(aasworld.areavisibility[i], size, fp ); - } - } - */ - // - botimport.FS_FCloseFile(fp); - return qtrue; -} //end of the function AAS_ReadRouteCache -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -#define MAX_REACHABILITYPASSAREAS 32 - -void AAS_InitReachabilityAreas(void) -{ - int i, j, numareas, areas[MAX_REACHABILITYPASSAREAS]; - int numreachareas; - aas_reachability_t *reach; - vec3_t start, end; - - if (aasworld.reachabilityareas) - FreeMemory(aasworld.reachabilityareas); - if (aasworld.reachabilityareaindex) - FreeMemory(aasworld.reachabilityareaindex); - - aasworld.reachabilityareas = (aas_reachabilityareas_t *) - GetClearedMemory(aasworld.reachabilitysize * sizeof(aas_reachabilityareas_t)); - aasworld.reachabilityareaindex = (int *) - GetClearedMemory(aasworld.reachabilitysize * MAX_REACHABILITYPASSAREAS * sizeof(int)); - numreachareas = 0; - for (i = 0; i < aasworld.reachabilitysize; i++) - { - reach = &aasworld.reachability[i]; - numareas = 0; - switch(reach->traveltype & TRAVELTYPE_MASK) - { - //trace areas from start to end - case TRAVEL_BARRIERJUMP: - case TRAVEL_WATERJUMP: - VectorCopy(reach->start, end); - end[2] = reach->end[2]; - numareas = AAS_TraceAreas(reach->start, end, areas, NULL, MAX_REACHABILITYPASSAREAS); - break; - case TRAVEL_WALKOFFLEDGE: - VectorCopy(reach->end, start); - start[2] = reach->start[2]; - numareas = AAS_TraceAreas(start, reach->end, areas, NULL, MAX_REACHABILITYPASSAREAS); - break; - case TRAVEL_GRAPPLEHOOK: - numareas = AAS_TraceAreas(reach->start, reach->end, areas, NULL, MAX_REACHABILITYPASSAREAS); - break; - - //trace arch - case TRAVEL_JUMP: break; - case TRAVEL_ROCKETJUMP: break; - case TRAVEL_BFGJUMP: break; - case TRAVEL_JUMPPAD: break; - - //trace from reach->start to entity center, along entity movement - //and from entity center to reach->end - case TRAVEL_ELEVATOR: break; - case TRAVEL_FUNCBOB: break; - - //no areas in between - case TRAVEL_WALK: break; - case TRAVEL_CROUCH: break; - case TRAVEL_LADDER: break; - case TRAVEL_SWIM: break; - case TRAVEL_TELEPORT: break; - default: break; - } //end switch - aasworld.reachabilityareas[i].firstarea = numreachareas; - aasworld.reachabilityareas[i].numareas = numareas; - for (j = 0; j < numareas; j++) - { - aasworld.reachabilityareaindex[numreachareas++] = areas[j]; - } //end for - } //end for -} //end of the function AAS_InitReachabilityAreas -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_InitRouting(void) -{ - AAS_InitTravelFlagFromType(); - // - AAS_InitAreaContentsTravelFlags(); - //initialize the routing update fields - AAS_InitRoutingUpdate(); - //create reversed reachability links used by the routing update algorithm - AAS_CreateReversedReachability(); - //initialize the cluster cache - AAS_InitClusterAreaCache(); - //initialize portal cache - AAS_InitPortalCache(); - //initialize the area travel times - AAS_CalculateAreaTravelTimes(); - //calculate the maximum travel times through portals - AAS_InitPortalMaxTravelTimes(); - //get the areas reachabilities go through - AAS_InitReachabilityAreas(); - // -#ifdef ROUTING_DEBUG - numareacacheupdates = 0; - numportalcacheupdates = 0; -#endif //ROUTING_DEBUG - // - routingcachesize = 0; - max_routingcachesize = 1024 * (int) LibVarValue("max_routingcache", "4096"); - // read any routing cache if available - AAS_ReadRouteCache(); -} //end of the function AAS_InitRouting -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_FreeRoutingCaches(void) -{ - // free all the existing cluster area cache - AAS_FreeAllClusterAreaCache(); - // free all the existing portal cache - AAS_FreeAllPortalCache(); - // free cached travel times within areas - if (aasworld.areatraveltimes) FreeMemory(aasworld.areatraveltimes); - aasworld.areatraveltimes = NULL; - // free cached maximum travel time through cluster portals - if (aasworld.portalmaxtraveltimes) FreeMemory(aasworld.portalmaxtraveltimes); - aasworld.portalmaxtraveltimes = NULL; - // free reversed reachability links - if (aasworld.reversedreachability) FreeMemory(aasworld.reversedreachability); - aasworld.reversedreachability = NULL; - // free routing algorithm memory - if (aasworld.areaupdate) FreeMemory(aasworld.areaupdate); - aasworld.areaupdate = NULL; - if (aasworld.portalupdate) FreeMemory(aasworld.portalupdate); - aasworld.portalupdate = NULL; - // free lists with areas the reachabilities go through - if (aasworld.reachabilityareas) FreeMemory(aasworld.reachabilityareas); - aasworld.reachabilityareas = NULL; - // free the reachability area index - if (aasworld.reachabilityareaindex) FreeMemory(aasworld.reachabilityareaindex); - aasworld.reachabilityareaindex = NULL; - // free area contents travel flags look up table - if (aasworld.areacontentstravelflags) FreeMemory(aasworld.areacontentstravelflags); - aasworld.areacontentstravelflags = NULL; -} //end of the function AAS_FreeRoutingCaches -//=========================================================================== -// update the given routing cache -// -// Parameter: areacache : routing cache to update -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_UpdateAreaRoutingCache(aas_routingcache_t *areacache) -{ - int i, nextareanum, cluster, badtravelflags, clusterareanum, linknum; - int numreachabilityareas; - unsigned short int t, startareatraveltimes[128]; //NOTE: not more than 128 reachabilities per area allowed - aas_routingupdate_t *updateliststart, *updatelistend, *curupdate, *nextupdate; - aas_reachability_t *reach; - aas_reversedreachability_t *revreach; - aas_reversedlink_t *revlink; - -#ifdef ROUTING_DEBUG - numareacacheupdates++; -#endif //ROUTING_DEBUG - //number of reachability areas within this cluster - numreachabilityareas = aasworld.clusters[areacache->cluster].numreachabilityareas; - // - aasworld.frameroutingupdates++; - //clear the routing update fields -// Com_Memset(aasworld.areaupdate, 0, aasworld.numareas * sizeof(aas_routingupdate_t)); - // - badtravelflags = ~areacache->travelflags; - // - clusterareanum = AAS_ClusterAreaNum(areacache->cluster, areacache->areanum); - if (clusterareanum >= numreachabilityareas) return; - // - Com_Memset(startareatraveltimes, 0, sizeof(startareatraveltimes)); - // - curupdate = &aasworld.areaupdate[clusterareanum]; - curupdate->areanum = areacache->areanum; - //VectorCopy(areacache->origin, curupdate->start); - curupdate->areatraveltimes = startareatraveltimes; - curupdate->tmptraveltime = areacache->starttraveltime; - // - areacache->traveltimes[clusterareanum] = areacache->starttraveltime; - //put the area to start with in the current read list - curupdate->next = NULL; - curupdate->prev = NULL; - updateliststart = curupdate; - updatelistend = curupdate; - //while there are updates in the current list - while (updateliststart) - { - curupdate = updateliststart; - // - if (curupdate->next) curupdate->next->prev = NULL; - else updatelistend = NULL; - updateliststart = curupdate->next; - // - curupdate->inlist = qfalse; - //check all reversed reachability links - revreach = &aasworld.reversedreachability[curupdate->areanum]; - // - for (i = 0, revlink = revreach->first; revlink; revlink = revlink->next, i++) - { - linknum = revlink->linknum; - reach = &aasworld.reachability[linknum]; - //if there is used an undesired travel type - if (AAS_TravelFlagForType_inline(reach->traveltype) & badtravelflags) continue; - //if not allowed to enter the next area - if (aasworld.areasettings[reach->areanum].areaflags & AREA_DISABLED) continue; - //if the next area has a not allowed travel flag - if (AAS_AreaContentsTravelFlags_inline(reach->areanum) & badtravelflags) continue; - //number of the area the reversed reachability leads to - nextareanum = revlink->areanum; - //get the cluster number of the area - cluster = aasworld.areasettings[nextareanum].cluster; - //don't leave the cluster - if (cluster > 0 && cluster != areacache->cluster) continue; - //get the number of the area in the cluster - clusterareanum = AAS_ClusterAreaNum(areacache->cluster, nextareanum); - if (clusterareanum >= numreachabilityareas) continue; - //time already travelled plus the traveltime through - //the current area plus the travel time from the reachability - t = curupdate->tmptraveltime + - //AAS_AreaTravelTime(curupdate->areanum, curupdate->start, reach->end) + - curupdate->areatraveltimes[i] + - reach->traveltime; - // - if (!areacache->traveltimes[clusterareanum] || - areacache->traveltimes[clusterareanum] > t) - { - areacache->traveltimes[clusterareanum] = t; - areacache->reachabilities[clusterareanum] = linknum - aasworld.areasettings[nextareanum].firstreachablearea; - nextupdate = &aasworld.areaupdate[clusterareanum]; - nextupdate->areanum = nextareanum; - nextupdate->tmptraveltime = t; - //VectorCopy(reach->start, nextupdate->start); - nextupdate->areatraveltimes = aasworld.areatraveltimes[nextareanum][linknum - - aasworld.areasettings[nextareanum].firstreachablearea]; - if (!nextupdate->inlist) - { - // we add the update to the end of the list - // we could also use a B+ tree to have a real sorted list - // on travel time which makes for faster routing updates - nextupdate->next = NULL; - nextupdate->prev = updatelistend; - if (updatelistend) updatelistend->next = nextupdate; - else updateliststart = nextupdate; - updatelistend = nextupdate; - nextupdate->inlist = qtrue; - } //end if - } //end if - } //end for - } //end while -} //end of the function AAS_UpdateAreaRoutingCache -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -aas_routingcache_t *AAS_GetAreaRoutingCache(int clusternum, int areanum, int travelflags) -{ - int clusterareanum; - aas_routingcache_t *cache, *clustercache; - - //number of the area in the cluster - clusterareanum = AAS_ClusterAreaNum(clusternum, areanum); - //pointer to the cache for the area in the cluster - clustercache = aasworld.clusterareacache[clusternum][clusterareanum]; - //find the cache without undesired travel flags - for (cache = clustercache; cache; cache = cache->next) - { - //if there aren't used any undesired travel types for the cache - if (cache->travelflags == travelflags) break; - } //end for - //if there was no cache - if (!cache) - { - cache = AAS_AllocRoutingCache(aasworld.clusters[clusternum].numreachabilityareas); - cache->cluster = clusternum; - cache->areanum = areanum; - VectorCopy(aasworld.areas[areanum].center, cache->origin); - cache->starttraveltime = 1; - cache->travelflags = travelflags; - cache->prev = NULL; - cache->next = clustercache; - if (clustercache) clustercache->prev = cache; - aasworld.clusterareacache[clusternum][clusterareanum] = cache; - AAS_UpdateAreaRoutingCache(cache); - } //end if - else - { - AAS_UnlinkCache(cache); - } //end else - //the cache has been accessed - cache->time = AAS_RoutingTime(); - cache->type = CACHETYPE_AREA; - AAS_LinkCache(cache); - return cache; -} //end of the function AAS_GetAreaRoutingCache -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_UpdatePortalRoutingCache(aas_routingcache_t *portalcache) -{ - int i, portalnum, clusterareanum, clusternum; - unsigned short int t; - aas_portal_t *portal; - aas_cluster_t *cluster; - aas_routingcache_t *cache; - aas_routingupdate_t *updateliststart, *updatelistend, *curupdate, *nextupdate; - -#ifdef ROUTING_DEBUG - numportalcacheupdates++; -#endif //ROUTING_DEBUG - //clear the routing update fields -// Com_Memset(aasworld.portalupdate, 0, (aasworld.numportals+1) * sizeof(aas_routingupdate_t)); - // - curupdate = &aasworld.portalupdate[aasworld.numportals]; - curupdate->cluster = portalcache->cluster; - curupdate->areanum = portalcache->areanum; - curupdate->tmptraveltime = portalcache->starttraveltime; - //if the start area is a cluster portal, store the travel time for that portal - clusternum = aasworld.areasettings[portalcache->areanum].cluster; - if (clusternum < 0) - { - portalcache->traveltimes[-clusternum] = portalcache->starttraveltime; - } //end if - //put the area to start with in the current read list - curupdate->next = NULL; - curupdate->prev = NULL; - updateliststart = curupdate; - updatelistend = curupdate; - //while there are updates in the current list - while (updateliststart) - { - curupdate = updateliststart; - //remove the current update from the list - if (curupdate->next) curupdate->next->prev = NULL; - else updatelistend = NULL; - updateliststart = curupdate->next; - //current update is removed from the list - curupdate->inlist = qfalse; - // - cluster = &aasworld.clusters[curupdate->cluster]; - // - cache = AAS_GetAreaRoutingCache(curupdate->cluster, - curupdate->areanum, portalcache->travelflags); - //take all portals of the cluster - for (i = 0; i < cluster->numportals; i++) - { - portalnum = aasworld.portalindex[cluster->firstportal + i]; - portal = &aasworld.portals[portalnum]; - //if this is the portal of the current update continue - if (portal->areanum == curupdate->areanum) continue; - // - clusterareanum = AAS_ClusterAreaNum(curupdate->cluster, portal->areanum); - if (clusterareanum >= cluster->numreachabilityareas) continue; - // - t = cache->traveltimes[clusterareanum]; - if (!t) continue; - t += curupdate->tmptraveltime; - // - if (!portalcache->traveltimes[portalnum] || - portalcache->traveltimes[portalnum] > t) - { - portalcache->traveltimes[portalnum] = t; - nextupdate = &aasworld.portalupdate[portalnum]; - if (portal->frontcluster == curupdate->cluster) - { - nextupdate->cluster = portal->backcluster; - } //end if - else - { - nextupdate->cluster = portal->frontcluster; - } //end else - nextupdate->areanum = portal->areanum; - //add travel time through the actual portal area for the next update - nextupdate->tmptraveltime = t + aasworld.portalmaxtraveltimes[portalnum]; - if (!nextupdate->inlist) - { - // we add the update to the end of the list - // we could also use a B+ tree to have a real sorted list - // on travel time which makes for faster routing updates - nextupdate->next = NULL; - nextupdate->prev = updatelistend; - if (updatelistend) updatelistend->next = nextupdate; - else updateliststart = nextupdate; - updatelistend = nextupdate; - nextupdate->inlist = qtrue; - } //end if - } //end if - } //end for - } //end while -} //end of the function AAS_UpdatePortalRoutingCache -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -aas_routingcache_t *AAS_GetPortalRoutingCache(int clusternum, int areanum, int travelflags) -{ - aas_routingcache_t *cache; - - //find the cached portal routing if existing - for (cache = aasworld.portalcache[areanum]; cache; cache = cache->next) - { - if (cache->travelflags == travelflags) break; - } //end for - //if the portal routing isn't cached - if (!cache) - { - cache = AAS_AllocRoutingCache(aasworld.numportals); - cache->cluster = clusternum; - cache->areanum = areanum; - VectorCopy(aasworld.areas[areanum].center, cache->origin); - cache->starttraveltime = 1; - cache->travelflags = travelflags; - //add the cache to the cache list - cache->prev = NULL; - cache->next = aasworld.portalcache[areanum]; - if (aasworld.portalcache[areanum]) aasworld.portalcache[areanum]->prev = cache; - aasworld.portalcache[areanum] = cache; - //update the cache - AAS_UpdatePortalRoutingCache(cache); - } //end if - else - { - AAS_UnlinkCache(cache); - } //end else - //the cache has been accessed - cache->time = AAS_RoutingTime(); - cache->type = CACHETYPE_PORTAL; - AAS_LinkCache(cache); - return cache; -} //end of the function AAS_GetPortalRoutingCache -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_AreaRouteToGoalArea(int areanum, vec3_t origin, int goalareanum, int travelflags, int *traveltime, int *reachnum) -{ - int clusternum, goalclusternum, portalnum, i, clusterareanum, bestreachnum; - unsigned short int t, besttime; - aas_portal_t *portal; - aas_cluster_t *cluster; - aas_routingcache_t *areacache, *portalcache; - aas_reachability_t *reach; - - if (!aasworld.initialized) return qfalse; - - if (areanum == goalareanum) - { - *traveltime = 1; - *reachnum = 0; - return qtrue; - } - // - if (areanum <= 0 || areanum >= aasworld.numareas) - { - if (bot_developer) - { - botimport.Print(PRT_ERROR, "AAS_AreaTravelTimeToGoalArea: areanum %d out of range\n", areanum); - } //end if - return qfalse; - } //end if - if (goalareanum <= 0 || goalareanum >= aasworld.numareas) - { - if (bot_developer) - { - botimport.Print(PRT_ERROR, "AAS_AreaTravelTimeToGoalArea: goalareanum %d out of range\n", goalareanum); - } //end if - return qfalse; - } //end if - // make sure the routing cache doesn't grow to large - while(AvailableMemory() < 1 * 1024 * 1024) { - if (!AAS_FreeOldestCache()) break; - } - // - if (AAS_AreaDoNotEnter(areanum) || AAS_AreaDoNotEnter(goalareanum)) - { - travelflags |= TFL_DONOTENTER; - } //end if - //NOTE: the number of routing updates is limited per frame - /* - if (aasworld.frameroutingupdates > MAX_FRAMEROUTINGUPDATES) - { -#ifdef DEBUG - //Log_Write("WARNING: AAS_AreaTravelTimeToGoalArea: frame routing updates overflowed"); -#endif - return 0; - } //end if - */ - // - clusternum = aasworld.areasettings[areanum].cluster; - goalclusternum = aasworld.areasettings[goalareanum].cluster; - //check if the area is a portal of the goal area cluster - if (clusternum < 0 && goalclusternum > 0) - { - portal = &aasworld.portals[-clusternum]; - if (portal->frontcluster == goalclusternum || - portal->backcluster == goalclusternum) - { - clusternum = goalclusternum; - } //end if - } //end if - //check if the goalarea is a portal of the area cluster - else if (clusternum > 0 && goalclusternum < 0) - { - portal = &aasworld.portals[-goalclusternum]; - if (portal->frontcluster == clusternum || - portal->backcluster == clusternum) - { - goalclusternum = clusternum; - } //end if - } //end if - //if both areas are in the same cluster - //NOTE: there might be a shorter route via another cluster!!! but we don't care - if (clusternum > 0 && goalclusternum > 0 && clusternum == goalclusternum) - { - // - areacache = AAS_GetAreaRoutingCache(clusternum, goalareanum, travelflags); - //the number of the area in the cluster - clusterareanum = AAS_ClusterAreaNum(clusternum, areanum); - //the cluster the area is in - cluster = &aasworld.clusters[clusternum]; - //if the area is NOT a reachability area - if (clusterareanum >= cluster->numreachabilityareas) return 0; - //if it is possible to travel to the goal area through this cluster - if (areacache->traveltimes[clusterareanum] != 0) - { - *reachnum = aasworld.areasettings[areanum].firstreachablearea + - areacache->reachabilities[clusterareanum]; - if (!origin) { - *traveltime = areacache->traveltimes[clusterareanum]; - return qtrue; - } - reach = &aasworld.reachability[*reachnum]; - *traveltime = areacache->traveltimes[clusterareanum] + - AAS_AreaTravelTime(areanum, origin, reach->start); - // - return qtrue; - } //end if - } //end if - // - clusternum = aasworld.areasettings[areanum].cluster; - goalclusternum = aasworld.areasettings[goalareanum].cluster; - //if the goal area is a portal - if (goalclusternum < 0) - { - //just assume the goal area is part of the front cluster - portal = &aasworld.portals[-goalclusternum]; - goalclusternum = portal->frontcluster; - } //end if - //get the portal routing cache - portalcache = AAS_GetPortalRoutingCache(goalclusternum, goalareanum, travelflags); - //if the area is a cluster portal, read directly from the portal cache - if (clusternum < 0) - { - *traveltime = portalcache->traveltimes[-clusternum]; - *reachnum = aasworld.areasettings[areanum].firstreachablearea + - portalcache->reachabilities[-clusternum]; - return qtrue; - } //end if - // - besttime = 0; - bestreachnum = -1; - //the cluster the area is in - cluster = &aasworld.clusters[clusternum]; - //find the portal of the area cluster leading towards the goal area - for (i = 0; i < cluster->numportals; i++) - { - portalnum = aasworld.portalindex[cluster->firstportal + i]; - //if the goal area isn't reachable from the portal - if (!portalcache->traveltimes[portalnum]) continue; - // - portal = &aasworld.portals[portalnum]; - //get the cache of the portal area - areacache = AAS_GetAreaRoutingCache(clusternum, portal->areanum, travelflags); - //current area inside the current cluster - clusterareanum = AAS_ClusterAreaNum(clusternum, areanum); - //if the area is NOT a reachability area - if (clusterareanum >= cluster->numreachabilityareas) continue; - //if the portal is NOT reachable from this area - if (!areacache->traveltimes[clusterareanum]) continue; - //total travel time is the travel time the portal area is from - //the goal area plus the travel time towards the portal area - t = portalcache->traveltimes[portalnum] + areacache->traveltimes[clusterareanum]; - //FIXME: add the exact travel time through the actual portal area - //NOTE: for now we just add the largest travel time through the portal area - // because we can't directly calculate the exact travel time - // to be more specific we don't know which reachability was used to travel - // into the portal area - t += aasworld.portalmaxtraveltimes[portalnum]; - // - if (origin) - { - *reachnum = aasworld.areasettings[areanum].firstreachablearea + - areacache->reachabilities[clusterareanum]; - reach = aasworld.reachability + *reachnum; - t += AAS_AreaTravelTime(areanum, origin, reach->start); - } //end if - //if the time is better than the one already found - if (!besttime || t < besttime) - { - bestreachnum = *reachnum; - besttime = t; - } //end if - } //end for - if (bestreachnum < 0) { - return qfalse; - } - *reachnum = bestreachnum; - *traveltime = besttime; - return qtrue; -} //end of the function AAS_AreaRouteToGoalArea -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_AreaTravelTimeToGoalArea(int areanum, vec3_t origin, int goalareanum, int travelflags) -{ - int traveltime, reachnum; - - if (AAS_AreaRouteToGoalArea(areanum, origin, goalareanum, travelflags, &traveltime, &reachnum)) - { - return traveltime; - } - return 0; -} //end of the function AAS_AreaTravelTimeToGoalArea -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_AreaReachabilityToGoalArea(int areanum, vec3_t origin, int goalareanum, int travelflags) -{ - int traveltime, reachnum; - - if (AAS_AreaRouteToGoalArea(areanum, origin, goalareanum, travelflags, &traveltime, &reachnum)) - { - return reachnum; - } - return 0; -} //end of the function AAS_AreaReachabilityToGoalArea -//=========================================================================== -// predict the route and stop on one of the stop events -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_PredictRoute(struct aas_predictroute_s *route, int areanum, vec3_t origin, - int goalareanum, int travelflags, int maxareas, int maxtime, - int stopevent, int stopcontents, int stoptfl, int stopareanum) -{ - int curareanum, reachnum, i, j, testareanum; - vec3_t curorigin; - aas_reachability_t *reach; - aas_reachabilityareas_t *reachareas; - - //init output - route->stopevent = RSE_NONE; - route->endarea = goalareanum; - route->endcontents = 0; - route->endtravelflags = 0; - VectorCopy(origin, route->endpos); - route->time = 0; - - curareanum = areanum; - VectorCopy(origin, curorigin); - - for (i = 0; curareanum != goalareanum && (!maxareas || i < maxareas) && i < aasworld.numareas; i++) - { - reachnum = AAS_AreaReachabilityToGoalArea(curareanum, curorigin, goalareanum, travelflags); - if (!reachnum) - { - route->stopevent = RSE_NOROUTE; - return qfalse; - } //end if - reach = &aasworld.reachability[reachnum]; - // - if (stopevent & RSE_USETRAVELTYPE) - { - if (AAS_TravelFlagForType_inline(reach->traveltype) & stoptfl) - { - route->stopevent = RSE_USETRAVELTYPE; - route->endarea = curareanum; - route->endcontents = aasworld.areasettings[curareanum].contents; - route->endtravelflags = AAS_TravelFlagForType_inline(reach->traveltype); - VectorCopy(reach->start, route->endpos); - return qtrue; - } //end if - if (AAS_AreaContentsTravelFlags_inline(reach->areanum) & stoptfl) - { - route->stopevent = RSE_USETRAVELTYPE; - route->endarea = reach->areanum; - route->endcontents = aasworld.areasettings[reach->areanum].contents; - route->endtravelflags = AAS_AreaContentsTravelFlags_inline(reach->areanum); - VectorCopy(reach->end, route->endpos); - route->time += AAS_AreaTravelTime(areanum, origin, reach->start); - route->time += reach->traveltime; - return qtrue; - } //end if - } //end if - reachareas = &aasworld.reachabilityareas[reachnum]; - for (j = 0; j < reachareas->numareas + 1; j++) - { - if (j >= reachareas->numareas) - testareanum = reach->areanum; - else - testareanum = aasworld.reachabilityareaindex[reachareas->firstarea + j]; - if (stopevent & RSE_ENTERCONTENTS) - { - if (aasworld.areasettings[testareanum].contents & stopcontents) - { - route->stopevent = RSE_ENTERCONTENTS; - route->endarea = testareanum; - route->endcontents = aasworld.areasettings[testareanum].contents; - VectorCopy(reach->end, route->endpos); - route->time += AAS_AreaTravelTime(areanum, origin, reach->start); - route->time += reach->traveltime; - return qtrue; - } //end if - } //end if - if (stopevent & RSE_ENTERAREA) - { - if (testareanum == stopareanum) - { - route->stopevent = RSE_ENTERAREA; - route->endarea = testareanum; - route->endcontents = aasworld.areasettings[testareanum].contents; - VectorCopy(reach->start, route->endpos); - return qtrue; - } //end if - } //end if - } //end for - - route->time += AAS_AreaTravelTime(areanum, origin, reach->start); - route->time += reach->traveltime; - route->endarea = reach->areanum; - route->endcontents = aasworld.areasettings[reach->areanum].contents; - route->endtravelflags = AAS_TravelFlagForType_inline(reach->traveltype); - VectorCopy(reach->end, route->endpos); - // - curareanum = reach->areanum; - VectorCopy(reach->end, curorigin); - // - if (maxtime && route->time > maxtime) - break; - } //end while - if (curareanum != goalareanum) - return qfalse; - return qtrue; -} //end of the function AAS_PredictRoute -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_BridgeWalkable(int areanum) -{ - return qfalse; -} //end of the function AAS_BridgeWalkable -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_ReachabilityFromNum(int num, struct aas_reachability_s *reach) -{ - if (!aasworld.initialized) - { - Com_Memset(reach, 0, sizeof(aas_reachability_t)); - return; - } //end if - if (num < 0 || num >= aasworld.reachabilitysize) - { - Com_Memset(reach, 0, sizeof(aas_reachability_t)); - return; - } //end if - Com_Memcpy(reach, &aasworld.reachability[num], sizeof(aas_reachability_t));; -} //end of the function AAS_ReachabilityFromNum -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_NextAreaReachability(int areanum, int reachnum) -{ - aas_areasettings_t *settings; - - if (!aasworld.initialized) return 0; - - if (areanum <= 0 || areanum >= aasworld.numareas) - { - botimport.Print(PRT_ERROR, "AAS_NextAreaReachability: areanum %d out of range\n", areanum); - return 0; - } //end if - - settings = &aasworld.areasettings[areanum]; - if (!reachnum) - { - return settings->firstreachablearea; - } //end if - if (reachnum < settings->firstreachablearea) - { - botimport.Print(PRT_FATAL, "AAS_NextAreaReachability: reachnum < settings->firstreachableara"); - return 0; - } //end if - reachnum++; - if (reachnum >= settings->firstreachablearea + settings->numreachableareas) - { - return 0; - } //end if - return reachnum; -} //end of the function AAS_NextAreaReachability -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_NextModelReachability(int num, int modelnum) -{ - int i; - - if (num <= 0) num = 1; - else if (num >= aasworld.reachabilitysize) return 0; - else num++; - // - for (i = num; i < aasworld.reachabilitysize; i++) - { - if ((aasworld.reachability[i].traveltype & TRAVELTYPE_MASK) == TRAVEL_ELEVATOR) - { - if (aasworld.reachability[i].facenum == modelnum) return i; - } //end if - else if ((aasworld.reachability[i].traveltype & TRAVELTYPE_MASK) == TRAVEL_FUNCBOB) - { - if ((aasworld.reachability[i].facenum & 0x0000FFFF) == modelnum) return i; - } //end if - } //end for - return 0; -} //end of the function AAS_NextModelReachability -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_RandomGoalArea(int areanum, int travelflags, int *goalareanum, vec3_t goalorigin) -{ - int i, n, t; - vec3_t start, end; - aas_trace_t trace; - - //if the area has no reachabilities - if (!AAS_AreaReachability(areanum)) return qfalse; - // - n = aasworld.numareas * random(); - for (i = 0; i < aasworld.numareas; i++) - { - if (n <= 0) n = 1; - if (n >= aasworld.numareas) n = 1; - if (AAS_AreaReachability(n)) - { - t = AAS_AreaTravelTimeToGoalArea(areanum, aasworld.areas[areanum].center, n, travelflags); - //if the goal is reachable - if (t > 0) - { - if (AAS_AreaSwim(n)) - { - *goalareanum = n; - VectorCopy(aasworld.areas[n].center, goalorigin); - //botimport.Print(PRT_MESSAGE, "found random goal area %d\n", *goalareanum); - return qtrue; - } //end if - VectorCopy(aasworld.areas[n].center, start); - if (!AAS_PointAreaNum(start)) - Log_Write("area %d center %f %f %f in solid?", n, start[0], start[1], start[2]); - VectorCopy(start, end); - end[2] -= 300; - trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, -1); - if (!trace.startsolid && trace.fraction < 1 && AAS_PointAreaNum(trace.endpos) == n) - { - if (AAS_AreaGroundFaceArea(n) > 300) - { - *goalareanum = n; - VectorCopy(trace.endpos, goalorigin); - //botimport.Print(PRT_MESSAGE, "found random goal area %d\n", *goalareanum); - return qtrue; - } //end if - } //end if - } //end if - } //end if - n++; - } //end for - return qfalse; -} //end of the function AAS_RandomGoalArea -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_AreaVisible(int srcarea, int destarea) -{ - return qfalse; -} //end of the function AAS_AreaVisible -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -float DistancePointToLine(vec3_t v1, vec3_t v2, vec3_t point) -{ - vec3_t vec, p2; - - AAS_ProjectPointOntoVector(point, v1, v2, p2); - VectorSubtract(point, p2, vec); - return VectorLength(vec); -} //end of the function DistancePointToLine -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_NearestHideArea(int srcnum, vec3_t origin, int areanum, int enemynum, vec3_t enemyorigin, int enemyareanum, int travelflags) -{ - int i, j, nextareanum, badtravelflags, numreach, bestarea; - unsigned short int t, besttraveltime; - static unsigned short int *hidetraveltimes; - aas_routingupdate_t *updateliststart, *updatelistend, *curupdate, *nextupdate; - aas_reachability_t *reach; - float dist1, dist2; - vec3_t v1, v2, p; - qboolean startVisible; - - // - if (!hidetraveltimes) - { - hidetraveltimes = (unsigned short int *) GetClearedMemory(aasworld.numareas * sizeof(unsigned short int)); - } //end if - else - { - Com_Memset(hidetraveltimes, 0, aasworld.numareas * sizeof(unsigned short int)); - } //end else - besttraveltime = 0; - bestarea = 0; - //assume visible - startVisible = qtrue; - // - badtravelflags = ~travelflags; - // - curupdate = &aasworld.areaupdate[areanum]; - curupdate->areanum = areanum; - VectorCopy(origin, curupdate->start); - curupdate->areatraveltimes = aasworld.areatraveltimes[areanum][0]; - curupdate->tmptraveltime = 0; - //put the area to start with in the current read list - curupdate->next = NULL; - curupdate->prev = NULL; - updateliststart = curupdate; - updatelistend = curupdate; - //while there are updates in the list - while (updateliststart) - { - curupdate = updateliststart; - // - if (curupdate->next) curupdate->next->prev = NULL; - else updatelistend = NULL; - updateliststart = curupdate->next; - // - curupdate->inlist = qfalse; - //check all reversed reachability links - numreach = aasworld.areasettings[curupdate->areanum].numreachableareas; - reach = &aasworld.reachability[aasworld.areasettings[curupdate->areanum].firstreachablearea]; - // - for (i = 0; i < numreach; i++, reach++) - { - //if an undesired travel type is used - if (AAS_TravelFlagForType_inline(reach->traveltype) & badtravelflags) continue; - // - if (AAS_AreaContentsTravelFlags_inline(reach->areanum) & badtravelflags) continue; - //number of the area the reachability leads to - nextareanum = reach->areanum; - // if this moves us into the enemies area, skip it - if (nextareanum == enemyareanum) continue; - //time already travelled plus the traveltime through - //the current area plus the travel time from the reachability - t = curupdate->tmptraveltime + - AAS_AreaTravelTime(curupdate->areanum, curupdate->start, reach->start) + - reach->traveltime; - - //avoid going near the enemy - AAS_ProjectPointOntoVector(enemyorigin, curupdate->start, reach->end, p); - for (j = 0; j < 3; j++) - if ((p[j] > curupdate->start[j] && p[j] > reach->end[j]) || - (p[j] < curupdate->start[j] && p[j] < reach->end[j])) - break; - if (j < 3) - { - VectorSubtract(enemyorigin, reach->end, v2); - } //end if - else - { - VectorSubtract(enemyorigin, p, v2); - } //end else - dist2 = VectorLength(v2); - //never go through the enemy - if (dist2 < 40) continue; - // - VectorSubtract(enemyorigin, curupdate->start, v1); - dist1 = VectorLength(v1); - // - if (dist2 < dist1) - { - t += (dist1 - dist2) * 10; - } - // if we weren't visible when starting, make sure we don't move into their view - if (!startVisible && AAS_AreaVisible(enemyareanum, nextareanum)) { - continue; - } - // - if (besttraveltime && t >= besttraveltime) continue; - // - if (!hidetraveltimes[nextareanum] || - hidetraveltimes[nextareanum] > t) - { - //if the nextarea is not visible from the enemy area - if (!AAS_AreaVisible(enemyareanum, nextareanum)) - { - besttraveltime = t; - bestarea = nextareanum; - } //end if - hidetraveltimes[nextareanum] = t; - nextupdate = &aasworld.areaupdate[nextareanum]; - nextupdate->areanum = nextareanum; - nextupdate->tmptraveltime = t; - //remember where we entered this area - VectorCopy(reach->end, nextupdate->start); - //if this update is not in the list yet - if (!nextupdate->inlist) - { - //add the new update to the end of the list - nextupdate->next = NULL; - nextupdate->prev = updatelistend; - if (updatelistend) updatelistend->next = nextupdate; - else updateliststart = nextupdate; - updatelistend = nextupdate; - nextupdate->inlist = qtrue; - } //end if - } //end if - } //end for - } //end while - return bestarea; -} //end of the function AAS_NearestHideArea +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_route.c + * + * desc: AAS + * + * $Archive: /MissionPack/code/botlib/be_aas_route.c $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_utils.h" +#include "l_memory.h" +#include "l_log.h" +#include "l_crc.h" +#include "l_libvar.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_aas_def.h" + +#define ROUTING_DEBUG + +//travel time in hundreths of a second = distance * 100 / speed +#define DISTANCEFACTOR_CROUCH 1.3f //crouch speed = 100 +#define DISTANCEFACTOR_SWIM 1 //should be 0.66, swim speed = 150 +#define DISTANCEFACTOR_WALK 0.33f //walk speed = 300 + +//cache refresh time +#define CACHE_REFRESHTIME 15.0f //15 seconds refresh time + +//maximum number of routing updates each frame +#define MAX_FRAMEROUTINGUPDATES 10 + + +/* + + area routing cache: + stores the distances within one cluster to a specific goal area + this goal area is in this same cluster and could be a cluster portal + for every cluster there's a list with routing cache for every area + in that cluster (including the portals of that cluster) + area cache stores aasworld.clusters[?].numreachabilityareas travel times + + portal routing cache: + stores the distances of all portals to a specific goal area + this goal area could be in any cluster and could also be a cluster portal + for every area (aasworld.numareas) the portal cache stores + aasworld.numportals travel times + +*/ + +#ifdef ROUTING_DEBUG +int numareacacheupdates; +int numportalcacheupdates; +#endif //ROUTING_DEBUG + +int routingcachesize; +int max_routingcachesize; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef ROUTING_DEBUG +void AAS_RoutingInfo(void) +{ + botimport.Print(PRT_MESSAGE, "%d area cache updates\n", numareacacheupdates); + botimport.Print(PRT_MESSAGE, "%d portal cache updates\n", numportalcacheupdates); + botimport.Print(PRT_MESSAGE, "%d bytes routing cache\n", routingcachesize); +} //end of the function AAS_RoutingInfo +#endif //ROUTING_DEBUG +//=========================================================================== +// returns the number of the area in the cluster +// assumes the given area is in the given cluster or a portal of the cluster +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +__inline int AAS_ClusterAreaNum(int cluster, int areanum) +{ + int side, areacluster; + + areacluster = aasworld.areasettings[areanum].cluster; + if (areacluster > 0) return aasworld.areasettings[areanum].clusterareanum; + else + { +/*#ifdef ROUTING_DEBUG + if (aasworld.portals[-areacluster].frontcluster != cluster && + aasworld.portals[-areacluster].backcluster != cluster) + { + botimport.Print(PRT_ERROR, "portal %d: does not belong to cluster %d\n" + , -areacluster, cluster); + } //end if +#endif //ROUTING_DEBUG*/ + side = aasworld.portals[-areacluster].frontcluster != cluster; + return aasworld.portals[-areacluster].clusterareanum[side]; + } //end else +} //end of the function AAS_ClusterAreaNum +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitTravelFlagFromType(void) +{ + int i; + + for (i = 0; i < MAX_TRAVELTYPES; i++) + { + aasworld.travelflagfortype[i] = TFL_INVALID; + } //end for + aasworld.travelflagfortype[TRAVEL_INVALID] = TFL_INVALID; + aasworld.travelflagfortype[TRAVEL_WALK] = TFL_WALK; + aasworld.travelflagfortype[TRAVEL_CROUCH] = TFL_CROUCH; + aasworld.travelflagfortype[TRAVEL_BARRIERJUMP] = TFL_BARRIERJUMP; + aasworld.travelflagfortype[TRAVEL_JUMP] = TFL_JUMP; + aasworld.travelflagfortype[TRAVEL_LADDER] = TFL_LADDER; + aasworld.travelflagfortype[TRAVEL_WALKOFFLEDGE] = TFL_WALKOFFLEDGE; + aasworld.travelflagfortype[TRAVEL_SWIM] = TFL_SWIM; + aasworld.travelflagfortype[TRAVEL_WATERJUMP] = TFL_WATERJUMP; + aasworld.travelflagfortype[TRAVEL_TELEPORT] = TFL_TELEPORT; + aasworld.travelflagfortype[TRAVEL_ELEVATOR] = TFL_ELEVATOR; + aasworld.travelflagfortype[TRAVEL_ROCKETJUMP] = TFL_ROCKETJUMP; + aasworld.travelflagfortype[TRAVEL_BFGJUMP] = TFL_BFGJUMP; + aasworld.travelflagfortype[TRAVEL_GRAPPLEHOOK] = TFL_GRAPPLEHOOK; + aasworld.travelflagfortype[TRAVEL_DOUBLEJUMP] = TFL_DOUBLEJUMP; + aasworld.travelflagfortype[TRAVEL_RAMPJUMP] = TFL_RAMPJUMP; + aasworld.travelflagfortype[TRAVEL_STRAFEJUMP] = TFL_STRAFEJUMP; + aasworld.travelflagfortype[TRAVEL_JUMPPAD] = TFL_JUMPPAD; + aasworld.travelflagfortype[TRAVEL_FUNCBOB] = TFL_FUNCBOB; +} //end of the function AAS_InitTravelFlagFromType +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +__inline int AAS_TravelFlagForType_inline(int traveltype) +{ + int tfl; + + tfl = 0; + if (tfl & TRAVELFLAG_NOTTEAM1) + tfl |= TFL_NOTTEAM1; + if (tfl & TRAVELFLAG_NOTTEAM2) + tfl |= TFL_NOTTEAM2; + traveltype &= TRAVELTYPE_MASK; + if (traveltype < 0 || traveltype >= MAX_TRAVELTYPES) + return TFL_INVALID; + tfl |= aasworld.travelflagfortype[traveltype]; + return tfl; +} //end of the function AAS_TravelFlagForType_inline +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_TravelFlagForType(int traveltype) +{ + return AAS_TravelFlagForType_inline(traveltype); +} //end of the function AAS_TravelFlagForType_inline +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_UnlinkCache(aas_routingcache_t *cache) +{ + if (cache->time_next) cache->time_next->time_prev = cache->time_prev; + else aasworld.newestcache = cache->time_prev; + if (cache->time_prev) cache->time_prev->time_next = cache->time_next; + else aasworld.oldestcache = cache->time_next; + cache->time_next = NULL; + cache->time_prev = NULL; +} //end of the function AAS_UnlinkCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_LinkCache(aas_routingcache_t *cache) +{ + if (aasworld.newestcache) + { + aasworld.newestcache->time_next = cache; + cache->time_prev = aasworld.newestcache; + } //end if + else + { + aasworld.oldestcache = cache; + cache->time_prev = NULL; + } //end else + cache->time_next = NULL; + aasworld.newestcache = cache; +} //end of the function AAS_LinkCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeRoutingCache(aas_routingcache_t *cache) +{ + AAS_UnlinkCache(cache); + routingcachesize -= cache->size; + FreeMemory(cache); +} //end of the function AAS_FreeRoutingCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RemoveRoutingCacheInCluster( int clusternum ) +{ + int i; + aas_routingcache_t *cache, *nextcache; + aas_cluster_t *cluster; + + if (!aasworld.clusterareacache) + return; + cluster = &aasworld.clusters[clusternum]; + for (i = 0; i < cluster->numareas; i++) + { + for (cache = aasworld.clusterareacache[clusternum][i]; cache; cache = nextcache) + { + nextcache = cache->next; + AAS_FreeRoutingCache(cache); + } //end for + aasworld.clusterareacache[clusternum][i] = NULL; + } //end for +} //end of the function AAS_RemoveRoutingCacheInCluster +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RemoveRoutingCacheUsingArea( int areanum ) +{ + int i, clusternum; + aas_routingcache_t *cache, *nextcache; + + clusternum = aasworld.areasettings[areanum].cluster; + if (clusternum > 0) + { + //remove all the cache in the cluster the area is in + AAS_RemoveRoutingCacheInCluster( clusternum ); + } //end if + else + { + // if this is a portal remove all cache in both the front and back cluster + AAS_RemoveRoutingCacheInCluster( aasworld.portals[-clusternum].frontcluster ); + AAS_RemoveRoutingCacheInCluster( aasworld.portals[-clusternum].backcluster ); + } //end else + // remove all portal cache + for (i = 0; i < aasworld.numareas; i++) + { + //refresh portal cache + for (cache = aasworld.portalcache[i]; cache; cache = nextcache) + { + nextcache = cache->next; + AAS_FreeRoutingCache(cache); + } //end for + aasworld.portalcache[i] = NULL; + } //end for +} //end of the function AAS_RemoveRoutingCacheUsingArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_EnableRoutingArea(int areanum, int enable) +{ + int flags; + + if (areanum <= 0 || areanum >= aasworld.numareas) + { + if (bot_developer) + { + botimport.Print(PRT_ERROR, "AAS_EnableRoutingArea: areanum %d out of range\n", areanum); + } //end if + return 0; + } //end if + flags = aasworld.areasettings[areanum].areaflags & AREA_DISABLED; + if (enable < 0) + return !flags; + + if (enable) + aasworld.areasettings[areanum].areaflags &= ~AREA_DISABLED; + else + aasworld.areasettings[areanum].areaflags |= AREA_DISABLED; + // if the status of the area changed + if ( (flags & AREA_DISABLED) != (aasworld.areasettings[areanum].areaflags & AREA_DISABLED) ) + { + //remove all routing cache involving this area + AAS_RemoveRoutingCacheUsingArea( areanum ); + } //end if + return !flags; +} //end of the function AAS_EnableRoutingArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +__inline float AAS_RoutingTime(void) +{ + return AAS_Time(); +} //end of the function AAS_RoutingTime +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_GetAreaContentsTravelFlags(int areanum) +{ + int contents, tfl; + + contents = aasworld.areasettings[areanum].contents; + tfl = 0; + if (contents & AREACONTENTS_WATER) + tfl |= TFL_WATER; + else if (contents & AREACONTENTS_SLIME) + tfl |= TFL_SLIME; + else if (contents & AREACONTENTS_LAVA) + tfl |= TFL_LAVA; + else + tfl |= TFL_AIR; + if (contents & AREACONTENTS_DONOTENTER) + tfl |= TFL_DONOTENTER; + if (contents & AREACONTENTS_NOTTEAM1) + tfl |= TFL_NOTTEAM1; + if (contents & AREACONTENTS_NOTTEAM2) + tfl |= TFL_NOTTEAM2; + if (aasworld.areasettings[areanum].areaflags & AREA_BRIDGE) + tfl |= TFL_BRIDGE; + return tfl; +} //end of the function AAS_GetAreaContentsTravelFlags +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +__inline int AAS_AreaContentsTravelFlags_inline(int areanum) +{ + return aasworld.areacontentstravelflags[areanum]; +} //end of the function AAS_AreaContentsTravelFlags +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaContentsTravelFlags(int areanum) +{ + return aasworld.areacontentstravelflags[areanum]; +} //end of the function AAS_AreaContentsTravelFlags +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitAreaContentsTravelFlags(void) +{ + int i; + + if (aasworld.areacontentstravelflags) FreeMemory(aasworld.areacontentstravelflags); + aasworld.areacontentstravelflags = (int *) GetClearedMemory(aasworld.numareas * sizeof(int)); + // + for (i = 0; i < aasworld.numareas; i++) { + aasworld.areacontentstravelflags[i] = AAS_GetAreaContentsTravelFlags(i); + } +} //end of the function AAS_InitAreaContentsTravelFlags +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CreateReversedReachability(void) +{ + int i, n; + aas_reversedlink_t *revlink; + aas_reachability_t *reach; + aas_areasettings_t *settings; + char *ptr; +#ifdef DEBUG + int starttime; + + starttime = Sys_MilliSeconds(); +#endif + //free reversed links that have already been created + if (aasworld.reversedreachability) FreeMemory(aasworld.reversedreachability); + //allocate memory for the reversed reachability links + ptr = (char *) GetClearedMemory(aasworld.numareas * sizeof(aas_reversedreachability_t) + + aasworld.reachabilitysize * sizeof(aas_reversedlink_t)); + // + aasworld.reversedreachability = (aas_reversedreachability_t *) ptr; + //pointer to the memory for the reversed links + ptr += aasworld.numareas * sizeof(aas_reversedreachability_t); + //check all reachabilities of all areas + for (i = 1; i < aasworld.numareas; i++) + { + //settings of the area + settings = &aasworld.areasettings[i]; + // + if (settings->numreachableareas >= 128) + botimport.Print(PRT_WARNING, "area %d has more than 128 reachabilities\n", i); + //create reversed links for the reachabilities + for (n = 0; n < settings->numreachableareas && n < 128; n++) + { + //reachability link + reach = &aasworld.reachability[settings->firstreachablearea + n]; + // + revlink = (aas_reversedlink_t *) ptr; + ptr += sizeof(aas_reversedlink_t); + // + revlink->areanum = i; + revlink->linknum = settings->firstreachablearea + n; + revlink->next = aasworld.reversedreachability[reach->areanum].first; + aasworld.reversedreachability[reach->areanum].first = revlink; + aasworld.reversedreachability[reach->areanum].numlinks++; + } //end for + } //end for +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "reversed reachability %d msec\n", Sys_MilliSeconds() - starttime); +#endif +} //end of the function AAS_CreateReversedReachability +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +unsigned short int AAS_AreaTravelTime(int areanum, vec3_t start, vec3_t end) +{ + int intdist; + float dist; + vec3_t dir; + + VectorSubtract(start, end, dir); + dist = VectorLength(dir); + //if crouch only area + if (AAS_AreaCrouch(areanum)) dist *= DISTANCEFACTOR_CROUCH; + //if swim area + else if (AAS_AreaSwim(areanum)) dist *= DISTANCEFACTOR_SWIM; + //normal walk area + else dist *= DISTANCEFACTOR_WALK; + // + intdist = (int) dist; + //make sure the distance isn't zero + if (intdist <= 0) intdist = 1; + return intdist; +} //end of the function AAS_AreaTravelTime +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CalculateAreaTravelTimes(void) +{ + int i, l, n, size; + char *ptr; + vec3_t end; + aas_reversedreachability_t *revreach; + aas_reversedlink_t *revlink; + aas_reachability_t *reach; + aas_areasettings_t *settings; + int starttime; + + starttime = Sys_MilliSeconds(); + //if there are still area travel times, free the memory + if (aasworld.areatraveltimes) FreeMemory(aasworld.areatraveltimes); + //get the total size of all the area travel times + size = aasworld.numareas * sizeof(unsigned short **); + for (i = 0; i < aasworld.numareas; i++) + { + revreach = &aasworld.reversedreachability[i]; + //settings of the area + settings = &aasworld.areasettings[i]; + // + size += settings->numreachableareas * sizeof(unsigned short *); + // + size += settings->numreachableareas * revreach->numlinks * sizeof(unsigned short); + } //end for + //allocate memory for the area travel times + ptr = (char *) GetClearedMemory(size); + aasworld.areatraveltimes = (unsigned short ***) ptr; + ptr += aasworld.numareas * sizeof(unsigned short **); + //calcluate the travel times for all the areas + for (i = 0; i < aasworld.numareas; i++) + { + //reversed reachabilities of this area + revreach = &aasworld.reversedreachability[i]; + //settings of the area + settings = &aasworld.areasettings[i]; + // + aasworld.areatraveltimes[i] = (unsigned short **) ptr; + ptr += settings->numreachableareas * sizeof(unsigned short *); + // + for (l = 0; l < settings->numreachableareas; l++) + { + aasworld.areatraveltimes[i][l] = (unsigned short *) ptr; + ptr += revreach->numlinks * sizeof(unsigned short); + //reachability link + reach = &aasworld.reachability[settings->firstreachablearea + l]; + // + for (n = 0, revlink = revreach->first; revlink; revlink = revlink->next, n++) + { + VectorCopy(aasworld.reachability[revlink->linknum].end, end); + // + aasworld.areatraveltimes[i][l][n] = AAS_AreaTravelTime(i, end, reach->start); + } //end for + } //end for + } //end for +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "area travel times %d msec\n", Sys_MilliSeconds() - starttime); +#endif +} //end of the function AAS_CalculateAreaTravelTimes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_PortalMaxTravelTime(int portalnum) +{ + int l, n, t, maxt; + aas_portal_t *portal; + aas_reversedreachability_t *revreach; + aas_reversedlink_t *revlink; + aas_areasettings_t *settings; + + portal = &aasworld.portals[portalnum]; + //reversed reachabilities of this portal area + revreach = &aasworld.reversedreachability[portal->areanum]; + //settings of the portal area + settings = &aasworld.areasettings[portal->areanum]; + // + maxt = 0; + for (l = 0; l < settings->numreachableareas; l++) + { + for (n = 0, revlink = revreach->first; revlink; revlink = revlink->next, n++) + { + t = aasworld.areatraveltimes[portal->areanum][l][n]; + if (t > maxt) + { + maxt = t; + } //end if + } //end for + } //end for + return maxt; +} //end of the function AAS_PortalMaxTravelTime +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitPortalMaxTravelTimes(void) +{ + int i; + + if (aasworld.portalmaxtraveltimes) FreeMemory(aasworld.portalmaxtraveltimes); + + aasworld.portalmaxtraveltimes = (int *) GetClearedMemory(aasworld.numportals * sizeof(int)); + + for (i = 0; i < aasworld.numportals; i++) + { + aasworld.portalmaxtraveltimes[i] = AAS_PortalMaxTravelTime(i); + //botimport.Print(PRT_MESSAGE, "portal %d max tt = %d\n", i, aasworld.portalmaxtraveltimes[i]); + } //end for +} //end of the function AAS_InitPortalMaxTravelTimes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +/* +int AAS_FreeOldestCache(void) +{ + int i, j, bestcluster, bestarea, freed; + float besttime; + aas_routingcache_t *cache, *bestcache; + + freed = qfalse; + besttime = 999999999; + bestcache = NULL; + bestcluster = 0; + bestarea = 0; + //refresh cluster cache + for (i = 0; i < aasworld.numclusters; i++) + { + for (j = 0; j < aasworld.clusters[i].numareas; j++) + { + for (cache = aasworld.clusterareacache[i][j]; cache; cache = cache->next) + { + //never remove cache leading towards a portal + if (aasworld.areasettings[cache->areanum].cluster < 0) continue; + //if this cache is older than the cache we found so far + if (cache->time < besttime) + { + bestcache = cache; + bestcluster = i; + bestarea = j; + besttime = cache->time; + } //end if + } //end for + } //end for + } //end for + if (bestcache) + { + cache = bestcache; + if (cache->prev) cache->prev->next = cache->next; + else aasworld.clusterareacache[bestcluster][bestarea] = cache->next; + if (cache->next) cache->next->prev = cache->prev; + AAS_FreeRoutingCache(cache); + freed = qtrue; + } //end if + besttime = 999999999; + bestcache = NULL; + bestarea = 0; + for (i = 0; i < aasworld.numareas; i++) + { + //refresh portal cache + for (cache = aasworld.portalcache[i]; cache; cache = cache->next) + { + if (cache->time < besttime) + { + bestcache = cache; + bestarea = i; + besttime = cache->time; + } //end if + } //end for + } //end for + if (bestcache) + { + cache = bestcache; + if (cache->prev) cache->prev->next = cache->next; + else aasworld.portalcache[bestarea] = cache->next; + if (cache->next) cache->next->prev = cache->prev; + AAS_FreeRoutingCache(cache); + freed = qtrue; + } //end if + return freed; +} //end of the function AAS_FreeOldestCache +*/ +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_FreeOldestCache(void) +{ + int clusterareanum; + aas_routingcache_t *cache; + + for (cache = aasworld.oldestcache; cache; cache = cache->time_next) { + // never free area cache leading towards a portal + if (cache->type == CACHETYPE_AREA && aasworld.areasettings[cache->areanum].cluster < 0) { + continue; + } + break; + } + if (cache) { + // unlink the cache + if (cache->type == CACHETYPE_AREA) { + //number of the area in the cluster + clusterareanum = AAS_ClusterAreaNum(cache->cluster, cache->areanum); + // unlink from cluster area cache + if (cache->prev) cache->prev->next = cache->next; + else aasworld.clusterareacache[cache->cluster][clusterareanum] = cache->next; + if (cache->next) cache->next->prev = cache->prev; + } + else { + // unlink from portal cache + if (cache->prev) cache->prev->next = cache->next; + else aasworld.portalcache[cache->areanum] = cache->next; + if (cache->next) cache->next->prev = cache->prev; + } + AAS_FreeRoutingCache(cache); + return qtrue; + } + return qfalse; +} //end of the function AAS_FreeOldestCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_routingcache_t *AAS_AllocRoutingCache(int numtraveltimes) +{ + aas_routingcache_t *cache; + int size; + + // + size = sizeof(aas_routingcache_t) + + numtraveltimes * sizeof(unsigned short int) + + numtraveltimes * sizeof(unsigned char); + // + routingcachesize += size; + // + cache = (aas_routingcache_t *) GetClearedMemory(size); + cache->reachabilities = (unsigned char *) cache + sizeof(aas_routingcache_t) + + numtraveltimes * sizeof(unsigned short int); + cache->size = size; + return cache; +} //end of the function AAS_AllocRoutingCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeAllClusterAreaCache(void) +{ + int i, j; + aas_routingcache_t *cache, *nextcache; + aas_cluster_t *cluster; + + //free all cluster cache if existing + if (!aasworld.clusterareacache) return; + //free caches + for (i = 0; i < aasworld.numclusters; i++) + { + cluster = &aasworld.clusters[i]; + for (j = 0; j < cluster->numareas; j++) + { + for (cache = aasworld.clusterareacache[i][j]; cache; cache = nextcache) + { + nextcache = cache->next; + AAS_FreeRoutingCache(cache); + } //end for + aasworld.clusterareacache[i][j] = NULL; + } //end for + } //end for + //free the cluster cache array + FreeMemory(aasworld.clusterareacache); + aasworld.clusterareacache = NULL; +} //end of the function AAS_FreeAllClusterAreaCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitClusterAreaCache(void) +{ + int i, size; + char *ptr; + + // + for (size = 0, i = 0; i < aasworld.numclusters; i++) + { + size += aasworld.clusters[i].numareas; + } //end for + //two dimensional array with pointers for every cluster to routing cache + //for every area in that cluster + ptr = (char *) GetClearedMemory( + aasworld.numclusters * sizeof(aas_routingcache_t **) + + size * sizeof(aas_routingcache_t *)); + aasworld.clusterareacache = (aas_routingcache_t ***) ptr; + ptr += aasworld.numclusters * sizeof(aas_routingcache_t **); + for (i = 0; i < aasworld.numclusters; i++) + { + aasworld.clusterareacache[i] = (aas_routingcache_t **) ptr; + ptr += aasworld.clusters[i].numareas * sizeof(aas_routingcache_t *); + } //end for +} //end of the function AAS_InitClusterAreaCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeAllPortalCache(void) +{ + int i; + aas_routingcache_t *cache, *nextcache; + + //free all portal cache if existing + if (!aasworld.portalcache) return; + //free portal caches + for (i = 0; i < aasworld.numareas; i++) + { + for (cache = aasworld.portalcache[i]; cache; cache = nextcache) + { + nextcache = cache->next; + AAS_FreeRoutingCache(cache); + } //end for + aasworld.portalcache[i] = NULL; + } //end for + FreeMemory(aasworld.portalcache); + aasworld.portalcache = NULL; +} //end of the function AAS_FreeAllPortalCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitPortalCache(void) +{ + // + aasworld.portalcache = (aas_routingcache_t **) GetClearedMemory( + aasworld.numareas * sizeof(aas_routingcache_t *)); +} //end of the function AAS_InitPortalCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitRoutingUpdate(void) +{ + int i, maxreachabilityareas; + + //free routing update fields if already existing + if (aasworld.areaupdate) FreeMemory(aasworld.areaupdate); + // + maxreachabilityareas = 0; + for (i = 0; i < aasworld.numclusters; i++) + { + if (aasworld.clusters[i].numreachabilityareas > maxreachabilityareas) + { + maxreachabilityareas = aasworld.clusters[i].numreachabilityareas; + } //end if + } //end for + //allocate memory for the routing update fields + aasworld.areaupdate = (aas_routingupdate_t *) GetClearedMemory( + maxreachabilityareas * sizeof(aas_routingupdate_t)); + // + if (aasworld.portalupdate) FreeMemory(aasworld.portalupdate); + //allocate memory for the portal update fields + aasworld.portalupdate = (aas_routingupdate_t *) GetClearedMemory( + (aasworld.numportals+1) * sizeof(aas_routingupdate_t)); +} //end of the function AAS_InitRoutingUpdate +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CreateAllRoutingCache(void) +{ + int i, j, t; + + aasworld.initialized = qtrue; + botimport.Print(PRT_MESSAGE, "AAS_CreateAllRoutingCache\n"); + for (i = 1; i < aasworld.numareas; i++) + { + if (!AAS_AreaReachability(i)) continue; + for (j = 1; j < aasworld.numareas; j++) + { + if (i == j) continue; + if (!AAS_AreaReachability(j)) continue; + t = AAS_AreaTravelTimeToGoalArea(i, aasworld.areas[i].center, j, TFL_DEFAULT); + //Log_Write("traveltime from %d to %d is %d", i, j, t); + } //end for + } //end for + aasworld.initialized = qfalse; +} //end of the function AAS_CreateAllRoutingCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== + +//the route cache header +//this header is followed by numportalcache + numareacache aas_routingcache_t +//structures that store routing cache +typedef struct routecacheheader_s +{ + int ident; + int version; + int numareas; + int numclusters; + int areacrc; + int clustercrc; + int numportalcache; + int numareacache; +} routecacheheader_t; + +#define RCID (('C'<<24)+('R'<<16)+('E'<<8)+'M') +#define RCVERSION 2 + +//void AAS_DecompressVis(byte *in, int numareas, byte *decompressed); +//int AAS_CompressVis(byte *vis, int numareas, byte *dest); + +void AAS_WriteRouteCache(void) +{ + int i, j, numportalcache, numareacache, totalsize; + aas_routingcache_t *cache; + aas_cluster_t *cluster; + fileHandle_t fp; + char filename[MAX_QPATH]; + routecacheheader_t routecacheheader; + + numportalcache = 0; + for (i = 0; i < aasworld.numareas; i++) + { + for (cache = aasworld.portalcache[i]; cache; cache = cache->next) + { + numportalcache++; + } //end for + } //end for + numareacache = 0; + for (i = 0; i < aasworld.numclusters; i++) + { + cluster = &aasworld.clusters[i]; + for (j = 0; j < cluster->numareas; j++) + { + for (cache = aasworld.clusterareacache[i][j]; cache; cache = cache->next) + { + numareacache++; + } //end for + } //end for + } //end for + // open the file for writing + Com_sprintf(filename, MAX_QPATH, "maps/%s.rcd", aasworld.mapname); + botimport.FS_FOpenFile( filename, &fp, FS_WRITE ); + if (!fp) + { + AAS_Error("Unable to open file: %s\n", filename); + return; + } //end if + //create the header + routecacheheader.ident = RCID; + routecacheheader.version = RCVERSION; + routecacheheader.numareas = aasworld.numareas; + routecacheheader.numclusters = aasworld.numclusters; + routecacheheader.areacrc = CRC_ProcessString( (unsigned char *)aasworld.areas, sizeof(aas_area_t) * aasworld.numareas ); + routecacheheader.clustercrc = CRC_ProcessString( (unsigned char *)aasworld.clusters, sizeof(aas_cluster_t) * aasworld.numclusters ); + routecacheheader.numportalcache = numportalcache; + routecacheheader.numareacache = numareacache; + //write the header + botimport.FS_Write(&routecacheheader, sizeof(routecacheheader_t), fp); + // + totalsize = 0; + //write all the cache + for (i = 0; i < aasworld.numareas; i++) + { + for (cache = aasworld.portalcache[i]; cache; cache = cache->next) + { + botimport.FS_Write(cache, cache->size, fp); + totalsize += cache->size; + } //end for + } //end for + for (i = 0; i < aasworld.numclusters; i++) + { + cluster = &aasworld.clusters[i]; + for (j = 0; j < cluster->numareas; j++) + { + for (cache = aasworld.clusterareacache[i][j]; cache; cache = cache->next) + { + botimport.FS_Write(cache, cache->size, fp); + totalsize += cache->size; + } //end for + } //end for + } //end for + // write the visareas + /* + for (i = 0; i < aasworld.numareas; i++) + { + if (!aasworld.areavisibility[i]) { + size = 0; + botimport.FS_Write(&size, sizeof(int), fp); + continue; + } + AAS_DecompressVis( aasworld.areavisibility[i], aasworld.numareas, aasworld.decompressedvis ); + size = AAS_CompressVis( aasworld.decompressedvis, aasworld.numareas, aasworld.decompressedvis ); + botimport.FS_Write(&size, sizeof(int), fp); + botimport.FS_Write(aasworld.decompressedvis, size, fp); + } + */ + // + botimport.FS_FCloseFile(fp); + botimport.Print(PRT_MESSAGE, "\nroute cache written to %s\n", filename); + botimport.Print(PRT_MESSAGE, "written %d bytes of routing cache\n", totalsize); +} //end of the function AAS_WriteRouteCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_routingcache_t *AAS_ReadCache(fileHandle_t fp) +{ + int size; + aas_routingcache_t *cache; + + botimport.FS_Read(&size, sizeof(size), fp); + cache = (aas_routingcache_t *) GetMemory(size); + cache->size = size; + botimport.FS_Read((unsigned char *)cache + sizeof(size), size - sizeof(size), fp); + cache->reachabilities = (unsigned char *) cache + sizeof(aas_routingcache_t) - sizeof(unsigned short) + + (size - sizeof(aas_routingcache_t) + sizeof(unsigned short)) / 3 * 2; + return cache; +} //end of the function AAS_ReadCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_ReadRouteCache(void) +{ + int i, clusterareanum;//, size; + fileHandle_t fp; + char filename[MAX_QPATH]; + routecacheheader_t routecacheheader; + aas_routingcache_t *cache; + + Com_sprintf(filename, MAX_QPATH, "maps/%s.rcd", aasworld.mapname); + botimport.FS_FOpenFile( filename, &fp, FS_READ ); + if (!fp) + { + return qfalse; + } //end if + botimport.FS_Read(&routecacheheader, sizeof(routecacheheader_t), fp ); + if (routecacheheader.ident != RCID) + { + AAS_Error("%s is not a route cache dump\n"); + return qfalse; + } //end if + if (routecacheheader.version != RCVERSION) + { + AAS_Error("route cache dump has wrong version %d, should be %d", routecacheheader.version, RCVERSION); + return qfalse; + } //end if + if (routecacheheader.numareas != aasworld.numareas) + { + //AAS_Error("route cache dump has wrong number of areas\n"); + return qfalse; + } //end if + if (routecacheheader.numclusters != aasworld.numclusters) + { + //AAS_Error("route cache dump has wrong number of clusters\n"); + return qfalse; + } //end if + if (routecacheheader.areacrc != + CRC_ProcessString( (unsigned char *)aasworld.areas, sizeof(aas_area_t) * aasworld.numareas )) + { + //AAS_Error("route cache dump area CRC incorrect\n"); + return qfalse; + } //end if + if (routecacheheader.clustercrc != + CRC_ProcessString( (unsigned char *)aasworld.clusters, sizeof(aas_cluster_t) * aasworld.numclusters )) + { + //AAS_Error("route cache dump cluster CRC incorrect\n"); + return qfalse; + } //end if + //read all the portal cache + for (i = 0; i < routecacheheader.numportalcache; i++) + { + cache = AAS_ReadCache(fp); + cache->next = aasworld.portalcache[cache->areanum]; + cache->prev = NULL; + if (aasworld.portalcache[cache->areanum]) + aasworld.portalcache[cache->areanum]->prev = cache; + aasworld.portalcache[cache->areanum] = cache; + } //end for + //read all the cluster area cache + for (i = 0; i < routecacheheader.numareacache; i++) + { + cache = AAS_ReadCache(fp); + clusterareanum = AAS_ClusterAreaNum(cache->cluster, cache->areanum); + cache->next = aasworld.clusterareacache[cache->cluster][clusterareanum]; + cache->prev = NULL; + if (aasworld.clusterareacache[cache->cluster][clusterareanum]) + aasworld.clusterareacache[cache->cluster][clusterareanum]->prev = cache; + aasworld.clusterareacache[cache->cluster][clusterareanum] = cache; + } //end for + // read the visareas + /* + aasworld.areavisibility = (byte **) GetClearedMemory(aasworld.numareas * sizeof(byte *)); + aasworld.decompressedvis = (byte *) GetClearedMemory(aasworld.numareas * sizeof(byte)); + for (i = 0; i < aasworld.numareas; i++) + { + botimport.FS_Read(&size, sizeof(size), fp ); + if (size) { + aasworld.areavisibility[i] = (byte *) GetMemory(size); + botimport.FS_Read(aasworld.areavisibility[i], size, fp ); + } + } + */ + // + botimport.FS_FCloseFile(fp); + return qtrue; +} //end of the function AAS_ReadRouteCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#define MAX_REACHABILITYPASSAREAS 32 + +void AAS_InitReachabilityAreas(void) +{ + int i, j, numareas, areas[MAX_REACHABILITYPASSAREAS]; + int numreachareas; + aas_reachability_t *reach; + vec3_t start, end; + + if (aasworld.reachabilityareas) + FreeMemory(aasworld.reachabilityareas); + if (aasworld.reachabilityareaindex) + FreeMemory(aasworld.reachabilityareaindex); + + aasworld.reachabilityareas = (aas_reachabilityareas_t *) + GetClearedMemory(aasworld.reachabilitysize * sizeof(aas_reachabilityareas_t)); + aasworld.reachabilityareaindex = (int *) + GetClearedMemory(aasworld.reachabilitysize * MAX_REACHABILITYPASSAREAS * sizeof(int)); + numreachareas = 0; + for (i = 0; i < aasworld.reachabilitysize; i++) + { + reach = &aasworld.reachability[i]; + numareas = 0; + switch(reach->traveltype & TRAVELTYPE_MASK) + { + //trace areas from start to end + case TRAVEL_BARRIERJUMP: + case TRAVEL_WATERJUMP: + VectorCopy(reach->start, end); + end[2] = reach->end[2]; + numareas = AAS_TraceAreas(reach->start, end, areas, NULL, MAX_REACHABILITYPASSAREAS); + break; + case TRAVEL_WALKOFFLEDGE: + VectorCopy(reach->end, start); + start[2] = reach->start[2]; + numareas = AAS_TraceAreas(start, reach->end, areas, NULL, MAX_REACHABILITYPASSAREAS); + break; + case TRAVEL_GRAPPLEHOOK: + numareas = AAS_TraceAreas(reach->start, reach->end, areas, NULL, MAX_REACHABILITYPASSAREAS); + break; + + //trace arch + case TRAVEL_JUMP: break; + case TRAVEL_ROCKETJUMP: break; + case TRAVEL_BFGJUMP: break; + case TRAVEL_JUMPPAD: break; + + //trace from reach->start to entity center, along entity movement + //and from entity center to reach->end + case TRAVEL_ELEVATOR: break; + case TRAVEL_FUNCBOB: break; + + //no areas in between + case TRAVEL_WALK: break; + case TRAVEL_CROUCH: break; + case TRAVEL_LADDER: break; + case TRAVEL_SWIM: break; + case TRAVEL_TELEPORT: break; + default: break; + } //end switch + aasworld.reachabilityareas[i].firstarea = numreachareas; + aasworld.reachabilityareas[i].numareas = numareas; + for (j = 0; j < numareas; j++) + { + aasworld.reachabilityareaindex[numreachareas++] = areas[j]; + } //end for + } //end for +} //end of the function AAS_InitReachabilityAreas +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitRouting(void) +{ + AAS_InitTravelFlagFromType(); + // + AAS_InitAreaContentsTravelFlags(); + //initialize the routing update fields + AAS_InitRoutingUpdate(); + //create reversed reachability links used by the routing update algorithm + AAS_CreateReversedReachability(); + //initialize the cluster cache + AAS_InitClusterAreaCache(); + //initialize portal cache + AAS_InitPortalCache(); + //initialize the area travel times + AAS_CalculateAreaTravelTimes(); + //calculate the maximum travel times through portals + AAS_InitPortalMaxTravelTimes(); + //get the areas reachabilities go through + AAS_InitReachabilityAreas(); + // +#ifdef ROUTING_DEBUG + numareacacheupdates = 0; + numportalcacheupdates = 0; +#endif //ROUTING_DEBUG + // + routingcachesize = 0; + max_routingcachesize = 1024 * (int) LibVarValue("max_routingcache", "4096"); + // read any routing cache if available + AAS_ReadRouteCache(); +} //end of the function AAS_InitRouting +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeRoutingCaches(void) +{ + // free all the existing cluster area cache + AAS_FreeAllClusterAreaCache(); + // free all the existing portal cache + AAS_FreeAllPortalCache(); + // free cached travel times within areas + if (aasworld.areatraveltimes) FreeMemory(aasworld.areatraveltimes); + aasworld.areatraveltimes = NULL; + // free cached maximum travel time through cluster portals + if (aasworld.portalmaxtraveltimes) FreeMemory(aasworld.portalmaxtraveltimes); + aasworld.portalmaxtraveltimes = NULL; + // free reversed reachability links + if (aasworld.reversedreachability) FreeMemory(aasworld.reversedreachability); + aasworld.reversedreachability = NULL; + // free routing algorithm memory + if (aasworld.areaupdate) FreeMemory(aasworld.areaupdate); + aasworld.areaupdate = NULL; + if (aasworld.portalupdate) FreeMemory(aasworld.portalupdate); + aasworld.portalupdate = NULL; + // free lists with areas the reachabilities go through + if (aasworld.reachabilityareas) FreeMemory(aasworld.reachabilityareas); + aasworld.reachabilityareas = NULL; + // free the reachability area index + if (aasworld.reachabilityareaindex) FreeMemory(aasworld.reachabilityareaindex); + aasworld.reachabilityareaindex = NULL; + // free area contents travel flags look up table + if (aasworld.areacontentstravelflags) FreeMemory(aasworld.areacontentstravelflags); + aasworld.areacontentstravelflags = NULL; +} //end of the function AAS_FreeRoutingCaches +//=========================================================================== +// update the given routing cache +// +// Parameter: areacache : routing cache to update +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_UpdateAreaRoutingCache(aas_routingcache_t *areacache) +{ + int i, nextareanum, cluster, badtravelflags, clusterareanum, linknum; + int numreachabilityareas; + unsigned short int t, startareatraveltimes[128]; //NOTE: not more than 128 reachabilities per area allowed + aas_routingupdate_t *updateliststart, *updatelistend, *curupdate, *nextupdate; + aas_reachability_t *reach; + aas_reversedreachability_t *revreach; + aas_reversedlink_t *revlink; + +#ifdef ROUTING_DEBUG + numareacacheupdates++; +#endif //ROUTING_DEBUG + //number of reachability areas within this cluster + numreachabilityareas = aasworld.clusters[areacache->cluster].numreachabilityareas; + // + aasworld.frameroutingupdates++; + //clear the routing update fields +// Com_Memset(aasworld.areaupdate, 0, aasworld.numareas * sizeof(aas_routingupdate_t)); + // + badtravelflags = ~areacache->travelflags; + // + clusterareanum = AAS_ClusterAreaNum(areacache->cluster, areacache->areanum); + if (clusterareanum >= numreachabilityareas) return; + // + Com_Memset(startareatraveltimes, 0, sizeof(startareatraveltimes)); + // + curupdate = &aasworld.areaupdate[clusterareanum]; + curupdate->areanum = areacache->areanum; + //VectorCopy(areacache->origin, curupdate->start); + curupdate->areatraveltimes = startareatraveltimes; + curupdate->tmptraveltime = areacache->starttraveltime; + // + areacache->traveltimes[clusterareanum] = areacache->starttraveltime; + //put the area to start with in the current read list + curupdate->next = NULL; + curupdate->prev = NULL; + updateliststart = curupdate; + updatelistend = curupdate; + //while there are updates in the current list + while (updateliststart) + { + curupdate = updateliststart; + // + if (curupdate->next) curupdate->next->prev = NULL; + else updatelistend = NULL; + updateliststart = curupdate->next; + // + curupdate->inlist = qfalse; + //check all reversed reachability links + revreach = &aasworld.reversedreachability[curupdate->areanum]; + // + for (i = 0, revlink = revreach->first; revlink; revlink = revlink->next, i++) + { + linknum = revlink->linknum; + reach = &aasworld.reachability[linknum]; + //if there is used an undesired travel type + if (AAS_TravelFlagForType_inline(reach->traveltype) & badtravelflags) continue; + //if not allowed to enter the next area + if (aasworld.areasettings[reach->areanum].areaflags & AREA_DISABLED) continue; + //if the next area has a not allowed travel flag + if (AAS_AreaContentsTravelFlags_inline(reach->areanum) & badtravelflags) continue; + //number of the area the reversed reachability leads to + nextareanum = revlink->areanum; + //get the cluster number of the area + cluster = aasworld.areasettings[nextareanum].cluster; + //don't leave the cluster + if (cluster > 0 && cluster != areacache->cluster) continue; + //get the number of the area in the cluster + clusterareanum = AAS_ClusterAreaNum(areacache->cluster, nextareanum); + if (clusterareanum >= numreachabilityareas) continue; + //time already travelled plus the traveltime through + //the current area plus the travel time from the reachability + t = curupdate->tmptraveltime + + //AAS_AreaTravelTime(curupdate->areanum, curupdate->start, reach->end) + + curupdate->areatraveltimes[i] + + reach->traveltime; + // + if (!areacache->traveltimes[clusterareanum] || + areacache->traveltimes[clusterareanum] > t) + { + areacache->traveltimes[clusterareanum] = t; + areacache->reachabilities[clusterareanum] = linknum - aasworld.areasettings[nextareanum].firstreachablearea; + nextupdate = &aasworld.areaupdate[clusterareanum]; + nextupdate->areanum = nextareanum; + nextupdate->tmptraveltime = t; + //VectorCopy(reach->start, nextupdate->start); + nextupdate->areatraveltimes = aasworld.areatraveltimes[nextareanum][linknum - + aasworld.areasettings[nextareanum].firstreachablearea]; + if (!nextupdate->inlist) + { + // we add the update to the end of the list + // we could also use a B+ tree to have a real sorted list + // on travel time which makes for faster routing updates + nextupdate->next = NULL; + nextupdate->prev = updatelistend; + if (updatelistend) updatelistend->next = nextupdate; + else updateliststart = nextupdate; + updatelistend = nextupdate; + nextupdate->inlist = qtrue; + } //end if + } //end if + } //end for + } //end while +} //end of the function AAS_UpdateAreaRoutingCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_routingcache_t *AAS_GetAreaRoutingCache(int clusternum, int areanum, int travelflags) +{ + int clusterareanum; + aas_routingcache_t *cache, *clustercache; + + //number of the area in the cluster + clusterareanum = AAS_ClusterAreaNum(clusternum, areanum); + //pointer to the cache for the area in the cluster + clustercache = aasworld.clusterareacache[clusternum][clusterareanum]; + //find the cache without undesired travel flags + for (cache = clustercache; cache; cache = cache->next) + { + //if there aren't used any undesired travel types for the cache + if (cache->travelflags == travelflags) break; + } //end for + //if there was no cache + if (!cache) + { + cache = AAS_AllocRoutingCache(aasworld.clusters[clusternum].numreachabilityareas); + cache->cluster = clusternum; + cache->areanum = areanum; + VectorCopy(aasworld.areas[areanum].center, cache->origin); + cache->starttraveltime = 1; + cache->travelflags = travelflags; + cache->prev = NULL; + cache->next = clustercache; + if (clustercache) clustercache->prev = cache; + aasworld.clusterareacache[clusternum][clusterareanum] = cache; + AAS_UpdateAreaRoutingCache(cache); + } //end if + else + { + AAS_UnlinkCache(cache); + } //end else + //the cache has been accessed + cache->time = AAS_RoutingTime(); + cache->type = CACHETYPE_AREA; + AAS_LinkCache(cache); + return cache; +} //end of the function AAS_GetAreaRoutingCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_UpdatePortalRoutingCache(aas_routingcache_t *portalcache) +{ + int i, portalnum, clusterareanum, clusternum; + unsigned short int t; + aas_portal_t *portal; + aas_cluster_t *cluster; + aas_routingcache_t *cache; + aas_routingupdate_t *updateliststart, *updatelistend, *curupdate, *nextupdate; + +#ifdef ROUTING_DEBUG + numportalcacheupdates++; +#endif //ROUTING_DEBUG + //clear the routing update fields +// Com_Memset(aasworld.portalupdate, 0, (aasworld.numportals+1) * sizeof(aas_routingupdate_t)); + // + curupdate = &aasworld.portalupdate[aasworld.numportals]; + curupdate->cluster = portalcache->cluster; + curupdate->areanum = portalcache->areanum; + curupdate->tmptraveltime = portalcache->starttraveltime; + //if the start area is a cluster portal, store the travel time for that portal + clusternum = aasworld.areasettings[portalcache->areanum].cluster; + if (clusternum < 0) + { + portalcache->traveltimes[-clusternum] = portalcache->starttraveltime; + } //end if + //put the area to start with in the current read list + curupdate->next = NULL; + curupdate->prev = NULL; + updateliststart = curupdate; + updatelistend = curupdate; + //while there are updates in the current list + while (updateliststart) + { + curupdate = updateliststart; + //remove the current update from the list + if (curupdate->next) curupdate->next->prev = NULL; + else updatelistend = NULL; + updateliststart = curupdate->next; + //current update is removed from the list + curupdate->inlist = qfalse; + // + cluster = &aasworld.clusters[curupdate->cluster]; + // + cache = AAS_GetAreaRoutingCache(curupdate->cluster, + curupdate->areanum, portalcache->travelflags); + //take all portals of the cluster + for (i = 0; i < cluster->numportals; i++) + { + portalnum = aasworld.portalindex[cluster->firstportal + i]; + portal = &aasworld.portals[portalnum]; + //if this is the portal of the current update continue + if (portal->areanum == curupdate->areanum) continue; + // + clusterareanum = AAS_ClusterAreaNum(curupdate->cluster, portal->areanum); + if (clusterareanum >= cluster->numreachabilityareas) continue; + // + t = cache->traveltimes[clusterareanum]; + if (!t) continue; + t += curupdate->tmptraveltime; + // + if (!portalcache->traveltimes[portalnum] || + portalcache->traveltimes[portalnum] > t) + { + portalcache->traveltimes[portalnum] = t; + nextupdate = &aasworld.portalupdate[portalnum]; + if (portal->frontcluster == curupdate->cluster) + { + nextupdate->cluster = portal->backcluster; + } //end if + else + { + nextupdate->cluster = portal->frontcluster; + } //end else + nextupdate->areanum = portal->areanum; + //add travel time through the actual portal area for the next update + nextupdate->tmptraveltime = t + aasworld.portalmaxtraveltimes[portalnum]; + if (!nextupdate->inlist) + { + // we add the update to the end of the list + // we could also use a B+ tree to have a real sorted list + // on travel time which makes for faster routing updates + nextupdate->next = NULL; + nextupdate->prev = updatelistend; + if (updatelistend) updatelistend->next = nextupdate; + else updateliststart = nextupdate; + updatelistend = nextupdate; + nextupdate->inlist = qtrue; + } //end if + } //end if + } //end for + } //end while +} //end of the function AAS_UpdatePortalRoutingCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_routingcache_t *AAS_GetPortalRoutingCache(int clusternum, int areanum, int travelflags) +{ + aas_routingcache_t *cache; + + //find the cached portal routing if existing + for (cache = aasworld.portalcache[areanum]; cache; cache = cache->next) + { + if (cache->travelflags == travelflags) break; + } //end for + //if the portal routing isn't cached + if (!cache) + { + cache = AAS_AllocRoutingCache(aasworld.numportals); + cache->cluster = clusternum; + cache->areanum = areanum; + VectorCopy(aasworld.areas[areanum].center, cache->origin); + cache->starttraveltime = 1; + cache->travelflags = travelflags; + //add the cache to the cache list + cache->prev = NULL; + cache->next = aasworld.portalcache[areanum]; + if (aasworld.portalcache[areanum]) aasworld.portalcache[areanum]->prev = cache; + aasworld.portalcache[areanum] = cache; + //update the cache + AAS_UpdatePortalRoutingCache(cache); + } //end if + else + { + AAS_UnlinkCache(cache); + } //end else + //the cache has been accessed + cache->time = AAS_RoutingTime(); + cache->type = CACHETYPE_PORTAL; + AAS_LinkCache(cache); + return cache; +} //end of the function AAS_GetPortalRoutingCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaRouteToGoalArea(int areanum, vec3_t origin, int goalareanum, int travelflags, int *traveltime, int *reachnum) +{ + int clusternum, goalclusternum, portalnum, i, clusterareanum, bestreachnum; + unsigned short int t, besttime; + aas_portal_t *portal; + aas_cluster_t *cluster; + aas_routingcache_t *areacache, *portalcache; + aas_reachability_t *reach; + + if (!aasworld.initialized) return qfalse; + + if (areanum == goalareanum) + { + *traveltime = 1; + *reachnum = 0; + return qtrue; + } + // + if (areanum <= 0 || areanum >= aasworld.numareas) + { + if (bot_developer) + { + botimport.Print(PRT_ERROR, "AAS_AreaTravelTimeToGoalArea: areanum %d out of range\n", areanum); + } //end if + return qfalse; + } //end if + if (goalareanum <= 0 || goalareanum >= aasworld.numareas) + { + if (bot_developer) + { + botimport.Print(PRT_ERROR, "AAS_AreaTravelTimeToGoalArea: goalareanum %d out of range\n", goalareanum); + } //end if + return qfalse; + } //end if + // make sure the routing cache doesn't grow to large + while(AvailableMemory() < 1 * 1024 * 1024) { + if (!AAS_FreeOldestCache()) break; + } + // + if (AAS_AreaDoNotEnter(areanum) || AAS_AreaDoNotEnter(goalareanum)) + { + travelflags |= TFL_DONOTENTER; + } //end if + //NOTE: the number of routing updates is limited per frame + /* + if (aasworld.frameroutingupdates > MAX_FRAMEROUTINGUPDATES) + { +#ifdef DEBUG + //Log_Write("WARNING: AAS_AreaTravelTimeToGoalArea: frame routing updates overflowed"); +#endif + return 0; + } //end if + */ + // + clusternum = aasworld.areasettings[areanum].cluster; + goalclusternum = aasworld.areasettings[goalareanum].cluster; + //check if the area is a portal of the goal area cluster + if (clusternum < 0 && goalclusternum > 0) + { + portal = &aasworld.portals[-clusternum]; + if (portal->frontcluster == goalclusternum || + portal->backcluster == goalclusternum) + { + clusternum = goalclusternum; + } //end if + } //end if + //check if the goalarea is a portal of the area cluster + else if (clusternum > 0 && goalclusternum < 0) + { + portal = &aasworld.portals[-goalclusternum]; + if (portal->frontcluster == clusternum || + portal->backcluster == clusternum) + { + goalclusternum = clusternum; + } //end if + } //end if + //if both areas are in the same cluster + //NOTE: there might be a shorter route via another cluster!!! but we don't care + if (clusternum > 0 && goalclusternum > 0 && clusternum == goalclusternum) + { + // + areacache = AAS_GetAreaRoutingCache(clusternum, goalareanum, travelflags); + //the number of the area in the cluster + clusterareanum = AAS_ClusterAreaNum(clusternum, areanum); + //the cluster the area is in + cluster = &aasworld.clusters[clusternum]; + //if the area is NOT a reachability area + if (clusterareanum >= cluster->numreachabilityareas) return 0; + //if it is possible to travel to the goal area through this cluster + if (areacache->traveltimes[clusterareanum] != 0) + { + *reachnum = aasworld.areasettings[areanum].firstreachablearea + + areacache->reachabilities[clusterareanum]; + if (!origin) { + *traveltime = areacache->traveltimes[clusterareanum]; + return qtrue; + } + reach = &aasworld.reachability[*reachnum]; + *traveltime = areacache->traveltimes[clusterareanum] + + AAS_AreaTravelTime(areanum, origin, reach->start); + // + return qtrue; + } //end if + } //end if + // + clusternum = aasworld.areasettings[areanum].cluster; + goalclusternum = aasworld.areasettings[goalareanum].cluster; + //if the goal area is a portal + if (goalclusternum < 0) + { + //just assume the goal area is part of the front cluster + portal = &aasworld.portals[-goalclusternum]; + goalclusternum = portal->frontcluster; + } //end if + //get the portal routing cache + portalcache = AAS_GetPortalRoutingCache(goalclusternum, goalareanum, travelflags); + //if the area is a cluster portal, read directly from the portal cache + if (clusternum < 0) + { + *traveltime = portalcache->traveltimes[-clusternum]; + *reachnum = aasworld.areasettings[areanum].firstreachablearea + + portalcache->reachabilities[-clusternum]; + return qtrue; + } //end if + // + besttime = 0; + bestreachnum = -1; + //the cluster the area is in + cluster = &aasworld.clusters[clusternum]; + //find the portal of the area cluster leading towards the goal area + for (i = 0; i < cluster->numportals; i++) + { + portalnum = aasworld.portalindex[cluster->firstportal + i]; + //if the goal area isn't reachable from the portal + if (!portalcache->traveltimes[portalnum]) continue; + // + portal = &aasworld.portals[portalnum]; + //get the cache of the portal area + areacache = AAS_GetAreaRoutingCache(clusternum, portal->areanum, travelflags); + //current area inside the current cluster + clusterareanum = AAS_ClusterAreaNum(clusternum, areanum); + //if the area is NOT a reachability area + if (clusterareanum >= cluster->numreachabilityareas) continue; + //if the portal is NOT reachable from this area + if (!areacache->traveltimes[clusterareanum]) continue; + //total travel time is the travel time the portal area is from + //the goal area plus the travel time towards the portal area + t = portalcache->traveltimes[portalnum] + areacache->traveltimes[clusterareanum]; + //FIXME: add the exact travel time through the actual portal area + //NOTE: for now we just add the largest travel time through the portal area + // because we can't directly calculate the exact travel time + // to be more specific we don't know which reachability was used to travel + // into the portal area + t += aasworld.portalmaxtraveltimes[portalnum]; + // + if (origin) + { + *reachnum = aasworld.areasettings[areanum].firstreachablearea + + areacache->reachabilities[clusterareanum]; + reach = aasworld.reachability + *reachnum; + t += AAS_AreaTravelTime(areanum, origin, reach->start); + } //end if + //if the time is better than the one already found + if (!besttime || t < besttime) + { + bestreachnum = *reachnum; + besttime = t; + } //end if + } //end for + if (bestreachnum < 0) { + return qfalse; + } + *reachnum = bestreachnum; + *traveltime = besttime; + return qtrue; +} //end of the function AAS_AreaRouteToGoalArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaTravelTimeToGoalArea(int areanum, vec3_t origin, int goalareanum, int travelflags) +{ + int traveltime, reachnum; + + if (AAS_AreaRouteToGoalArea(areanum, origin, goalareanum, travelflags, &traveltime, &reachnum)) + { + return traveltime; + } + return 0; +} //end of the function AAS_AreaTravelTimeToGoalArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaReachabilityToGoalArea(int areanum, vec3_t origin, int goalareanum, int travelflags) +{ + int traveltime, reachnum; + + if (AAS_AreaRouteToGoalArea(areanum, origin, goalareanum, travelflags, &traveltime, &reachnum)) + { + return reachnum; + } + return 0; +} //end of the function AAS_AreaReachabilityToGoalArea +//=========================================================================== +// predict the route and stop on one of the stop events +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_PredictRoute(struct aas_predictroute_s *route, int areanum, vec3_t origin, + int goalareanum, int travelflags, int maxareas, int maxtime, + int stopevent, int stopcontents, int stoptfl, int stopareanum) +{ + int curareanum, reachnum, i, j, testareanum; + vec3_t curorigin; + aas_reachability_t *reach; + aas_reachabilityareas_t *reachareas; + + //init output + route->stopevent = RSE_NONE; + route->endarea = goalareanum; + route->endcontents = 0; + route->endtravelflags = 0; + VectorCopy(origin, route->endpos); + route->time = 0; + + curareanum = areanum; + VectorCopy(origin, curorigin); + + for (i = 0; curareanum != goalareanum && (!maxareas || i < maxareas) && i < aasworld.numareas; i++) + { + reachnum = AAS_AreaReachabilityToGoalArea(curareanum, curorigin, goalareanum, travelflags); + if (!reachnum) + { + route->stopevent = RSE_NOROUTE; + return qfalse; + } //end if + reach = &aasworld.reachability[reachnum]; + // + if (stopevent & RSE_USETRAVELTYPE) + { + if (AAS_TravelFlagForType_inline(reach->traveltype) & stoptfl) + { + route->stopevent = RSE_USETRAVELTYPE; + route->endarea = curareanum; + route->endcontents = aasworld.areasettings[curareanum].contents; + route->endtravelflags = AAS_TravelFlagForType_inline(reach->traveltype); + VectorCopy(reach->start, route->endpos); + return qtrue; + } //end if + if (AAS_AreaContentsTravelFlags_inline(reach->areanum) & stoptfl) + { + route->stopevent = RSE_USETRAVELTYPE; + route->endarea = reach->areanum; + route->endcontents = aasworld.areasettings[reach->areanum].contents; + route->endtravelflags = AAS_AreaContentsTravelFlags_inline(reach->areanum); + VectorCopy(reach->end, route->endpos); + route->time += AAS_AreaTravelTime(areanum, origin, reach->start); + route->time += reach->traveltime; + return qtrue; + } //end if + } //end if + reachareas = &aasworld.reachabilityareas[reachnum]; + for (j = 0; j < reachareas->numareas + 1; j++) + { + if (j >= reachareas->numareas) + testareanum = reach->areanum; + else + testareanum = aasworld.reachabilityareaindex[reachareas->firstarea + j]; + if (stopevent & RSE_ENTERCONTENTS) + { + if (aasworld.areasettings[testareanum].contents & stopcontents) + { + route->stopevent = RSE_ENTERCONTENTS; + route->endarea = testareanum; + route->endcontents = aasworld.areasettings[testareanum].contents; + VectorCopy(reach->end, route->endpos); + route->time += AAS_AreaTravelTime(areanum, origin, reach->start); + route->time += reach->traveltime; + return qtrue; + } //end if + } //end if + if (stopevent & RSE_ENTERAREA) + { + if (testareanum == stopareanum) + { + route->stopevent = RSE_ENTERAREA; + route->endarea = testareanum; + route->endcontents = aasworld.areasettings[testareanum].contents; + VectorCopy(reach->start, route->endpos); + return qtrue; + } //end if + } //end if + } //end for + + route->time += AAS_AreaTravelTime(areanum, origin, reach->start); + route->time += reach->traveltime; + route->endarea = reach->areanum; + route->endcontents = aasworld.areasettings[reach->areanum].contents; + route->endtravelflags = AAS_TravelFlagForType_inline(reach->traveltype); + VectorCopy(reach->end, route->endpos); + // + curareanum = reach->areanum; + VectorCopy(reach->end, curorigin); + // + if (maxtime && route->time > maxtime) + break; + } //end while + if (curareanum != goalareanum) + return qfalse; + return qtrue; +} //end of the function AAS_PredictRoute +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BridgeWalkable(int areanum) +{ + return qfalse; +} //end of the function AAS_BridgeWalkable +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ReachabilityFromNum(int num, struct aas_reachability_s *reach) +{ + if (!aasworld.initialized) + { + Com_Memset(reach, 0, sizeof(aas_reachability_t)); + return; + } //end if + if (num < 0 || num >= aasworld.reachabilitysize) + { + Com_Memset(reach, 0, sizeof(aas_reachability_t)); + return; + } //end if + Com_Memcpy(reach, &aasworld.reachability[num], sizeof(aas_reachability_t));; +} //end of the function AAS_ReachabilityFromNum +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_NextAreaReachability(int areanum, int reachnum) +{ + aas_areasettings_t *settings; + + if (!aasworld.initialized) return 0; + + if (areanum <= 0 || areanum >= aasworld.numareas) + { + botimport.Print(PRT_ERROR, "AAS_NextAreaReachability: areanum %d out of range\n", areanum); + return 0; + } //end if + + settings = &aasworld.areasettings[areanum]; + if (!reachnum) + { + return settings->firstreachablearea; + } //end if + if (reachnum < settings->firstreachablearea) + { + botimport.Print(PRT_FATAL, "AAS_NextAreaReachability: reachnum < settings->firstreachableara"); + return 0; + } //end if + reachnum++; + if (reachnum >= settings->firstreachablearea + settings->numreachableareas) + { + return 0; + } //end if + return reachnum; +} //end of the function AAS_NextAreaReachability +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_NextModelReachability(int num, int modelnum) +{ + int i; + + if (num <= 0) num = 1; + else if (num >= aasworld.reachabilitysize) return 0; + else num++; + // + for (i = num; i < aasworld.reachabilitysize; i++) + { + if ((aasworld.reachability[i].traveltype & TRAVELTYPE_MASK) == TRAVEL_ELEVATOR) + { + if (aasworld.reachability[i].facenum == modelnum) return i; + } //end if + else if ((aasworld.reachability[i].traveltype & TRAVELTYPE_MASK) == TRAVEL_FUNCBOB) + { + if ((aasworld.reachability[i].facenum & 0x0000FFFF) == modelnum) return i; + } //end if + } //end for + return 0; +} //end of the function AAS_NextModelReachability +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_RandomGoalArea(int areanum, int travelflags, int *goalareanum, vec3_t goalorigin) +{ + int i, n, t; + vec3_t start, end; + aas_trace_t trace; + + //if the area has no reachabilities + if (!AAS_AreaReachability(areanum)) return qfalse; + // + n = aasworld.numareas * random(); + for (i = 0; i < aasworld.numareas; i++) + { + if (n <= 0) n = 1; + if (n >= aasworld.numareas) n = 1; + if (AAS_AreaReachability(n)) + { + t = AAS_AreaTravelTimeToGoalArea(areanum, aasworld.areas[areanum].center, n, travelflags); + //if the goal is reachable + if (t > 0) + { + if (AAS_AreaSwim(n)) + { + *goalareanum = n; + VectorCopy(aasworld.areas[n].center, goalorigin); + //botimport.Print(PRT_MESSAGE, "found random goal area %d\n", *goalareanum); + return qtrue; + } //end if + VectorCopy(aasworld.areas[n].center, start); + if (!AAS_PointAreaNum(start)) + Log_Write("area %d center %f %f %f in solid?", n, start[0], start[1], start[2]); + VectorCopy(start, end); + end[2] -= 300; + trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, -1); + if (!trace.startsolid && trace.fraction < 1 && AAS_PointAreaNum(trace.endpos) == n) + { + if (AAS_AreaGroundFaceArea(n) > 300) + { + *goalareanum = n; + VectorCopy(trace.endpos, goalorigin); + //botimport.Print(PRT_MESSAGE, "found random goal area %d\n", *goalareanum); + return qtrue; + } //end if + } //end if + } //end if + } //end if + n++; + } //end for + return qfalse; +} //end of the function AAS_RandomGoalArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaVisible(int srcarea, int destarea) +{ + return qfalse; +} //end of the function AAS_AreaVisible +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float DistancePointToLine(vec3_t v1, vec3_t v2, vec3_t point) +{ + vec3_t vec, p2; + + AAS_ProjectPointOntoVector(point, v1, v2, p2); + VectorSubtract(point, p2, vec); + return VectorLength(vec); +} //end of the function DistancePointToLine +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_NearestHideArea(int srcnum, vec3_t origin, int areanum, int enemynum, vec3_t enemyorigin, int enemyareanum, int travelflags) +{ + int i, j, nextareanum, badtravelflags, numreach, bestarea; + unsigned short int t, besttraveltime; + static unsigned short int *hidetraveltimes; + aas_routingupdate_t *updateliststart, *updatelistend, *curupdate, *nextupdate; + aas_reachability_t *reach; + float dist1, dist2; + vec3_t v1, v2, p; + qboolean startVisible; + + // + if (!hidetraveltimes) + { + hidetraveltimes = (unsigned short int *) GetClearedMemory(aasworld.numareas * sizeof(unsigned short int)); + } //end if + else + { + Com_Memset(hidetraveltimes, 0, aasworld.numareas * sizeof(unsigned short int)); + } //end else + besttraveltime = 0; + bestarea = 0; + //assume visible + startVisible = qtrue; + // + badtravelflags = ~travelflags; + // + curupdate = &aasworld.areaupdate[areanum]; + curupdate->areanum = areanum; + VectorCopy(origin, curupdate->start); + curupdate->areatraveltimes = aasworld.areatraveltimes[areanum][0]; + curupdate->tmptraveltime = 0; + //put the area to start with in the current read list + curupdate->next = NULL; + curupdate->prev = NULL; + updateliststart = curupdate; + updatelistend = curupdate; + //while there are updates in the list + while (updateliststart) + { + curupdate = updateliststart; + // + if (curupdate->next) curupdate->next->prev = NULL; + else updatelistend = NULL; + updateliststart = curupdate->next; + // + curupdate->inlist = qfalse; + //check all reversed reachability links + numreach = aasworld.areasettings[curupdate->areanum].numreachableareas; + reach = &aasworld.reachability[aasworld.areasettings[curupdate->areanum].firstreachablearea]; + // + for (i = 0; i < numreach; i++, reach++) + { + //if an undesired travel type is used + if (AAS_TravelFlagForType_inline(reach->traveltype) & badtravelflags) continue; + // + if (AAS_AreaContentsTravelFlags_inline(reach->areanum) & badtravelflags) continue; + //number of the area the reachability leads to + nextareanum = reach->areanum; + // if this moves us into the enemies area, skip it + if (nextareanum == enemyareanum) continue; + //time already travelled plus the traveltime through + //the current area plus the travel time from the reachability + t = curupdate->tmptraveltime + + AAS_AreaTravelTime(curupdate->areanum, curupdate->start, reach->start) + + reach->traveltime; + + //avoid going near the enemy + AAS_ProjectPointOntoVector(enemyorigin, curupdate->start, reach->end, p); + for (j = 0; j < 3; j++) + if ((p[j] > curupdate->start[j] && p[j] > reach->end[j]) || + (p[j] < curupdate->start[j] && p[j] < reach->end[j])) + break; + if (j < 3) + { + VectorSubtract(enemyorigin, reach->end, v2); + } //end if + else + { + VectorSubtract(enemyorigin, p, v2); + } //end else + dist2 = VectorLength(v2); + //never go through the enemy + if (dist2 < 40) continue; + // + VectorSubtract(enemyorigin, curupdate->start, v1); + dist1 = VectorLength(v1); + // + if (dist2 < dist1) + { + t += (dist1 - dist2) * 10; + } + // if we weren't visible when starting, make sure we don't move into their view + if (!startVisible && AAS_AreaVisible(enemyareanum, nextareanum)) { + continue; + } + // + if (besttraveltime && t >= besttraveltime) continue; + // + if (!hidetraveltimes[nextareanum] || + hidetraveltimes[nextareanum] > t) + { + //if the nextarea is not visible from the enemy area + if (!AAS_AreaVisible(enemyareanum, nextareanum)) + { + besttraveltime = t; + bestarea = nextareanum; + } //end if + hidetraveltimes[nextareanum] = t; + nextupdate = &aasworld.areaupdate[nextareanum]; + nextupdate->areanum = nextareanum; + nextupdate->tmptraveltime = t; + //remember where we entered this area + VectorCopy(reach->end, nextupdate->start); + //if this update is not in the list yet + if (!nextupdate->inlist) + { + //add the new update to the end of the list + nextupdate->next = NULL; + nextupdate->prev = updatelistend; + if (updatelistend) updatelistend->next = nextupdate; + else updateliststart = nextupdate; + updatelistend = nextupdate; + nextupdate->inlist = qtrue; + } //end if + } //end if + } //end for + } //end while + return bestarea; +} //end of the function AAS_NearestHideArea diff --git a/code/botlib/be_aas_route.h b/code/botlib/be_aas_route.h index 8b92ac1..547336c 100755 --- a/code/botlib/be_aas_route.h +++ b/code/botlib/be_aas_route.h @@ -1,67 +1,67 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: be_aas_route.h - * - * desc: AAS - * - * $Archive: /source/code/botlib/be_aas_route.h $ - * - *****************************************************************************/ - -#ifdef AASINTERN -//initialize the AAS routing -void AAS_InitRouting(void); -//free the AAS routing caches -void AAS_FreeRoutingCaches(void); -//returns the travel time from start to end in the given area -unsigned short int AAS_AreaTravelTime(int areanum, vec3_t start, vec3_t end); -// -void AAS_CreateAllRoutingCache(void); -void AAS_WriteRouteCache(void); -// -void AAS_RoutingInfo(void); -#endif //AASINTERN - -//returns the travel flag for the given travel type -int AAS_TravelFlagForType(int traveltype); -//return the travel flag(s) for traveling through this area -int AAS_AreaContentsTravelFlags(int areanum); -//returns the index of the next reachability for the given area -int AAS_NextAreaReachability(int areanum, int reachnum); -//returns the reachability with the given index -void AAS_ReachabilityFromNum(int num, struct aas_reachability_s *reach); -//returns a random goal area and goal origin -int AAS_RandomGoalArea(int areanum, int travelflags, int *goalareanum, vec3_t goalorigin); -//enable or disable an area for routing -int AAS_EnableRoutingArea(int areanum, int enable); -//returns the travel time within the given area from start to end -unsigned short int AAS_AreaTravelTime(int areanum, vec3_t start, vec3_t end); -//returns the travel time from the area to the goal area using the given travel flags -int AAS_AreaTravelTimeToGoalArea(int areanum, vec3_t origin, int goalareanum, int travelflags); -//predict a route up to a stop event -int AAS_PredictRoute(struct aas_predictroute_s *route, int areanum, vec3_t origin, - int goalareanum, int travelflags, int maxareas, int maxtime, - int stopevent, int stopcontents, int stoptfl, int stopareanum); - - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_route.h + * + * desc: AAS + * + * $Archive: /source/code/botlib/be_aas_route.h $ + * + *****************************************************************************/ + +#ifdef AASINTERN +//initialize the AAS routing +void AAS_InitRouting(void); +//free the AAS routing caches +void AAS_FreeRoutingCaches(void); +//returns the travel time from start to end in the given area +unsigned short int AAS_AreaTravelTime(int areanum, vec3_t start, vec3_t end); +// +void AAS_CreateAllRoutingCache(void); +void AAS_WriteRouteCache(void); +// +void AAS_RoutingInfo(void); +#endif //AASINTERN + +//returns the travel flag for the given travel type +int AAS_TravelFlagForType(int traveltype); +//return the travel flag(s) for traveling through this area +int AAS_AreaContentsTravelFlags(int areanum); +//returns the index of the next reachability for the given area +int AAS_NextAreaReachability(int areanum, int reachnum); +//returns the reachability with the given index +void AAS_ReachabilityFromNum(int num, struct aas_reachability_s *reach); +//returns a random goal area and goal origin +int AAS_RandomGoalArea(int areanum, int travelflags, int *goalareanum, vec3_t goalorigin); +//enable or disable an area for routing +int AAS_EnableRoutingArea(int areanum, int enable); +//returns the travel time within the given area from start to end +unsigned short int AAS_AreaTravelTime(int areanum, vec3_t start, vec3_t end); +//returns the travel time from the area to the goal area using the given travel flags +int AAS_AreaTravelTimeToGoalArea(int areanum, vec3_t origin, int goalareanum, int travelflags); +//predict a route up to a stop event +int AAS_PredictRoute(struct aas_predictroute_s *route, int areanum, vec3_t origin, + int goalareanum, int travelflags, int maxareas, int maxtime, + int stopevent, int stopcontents, int stoptfl, int stopareanum); + + diff --git a/code/botlib/be_aas_routealt.c b/code/botlib/be_aas_routealt.c index fd7cb8d..956302d 100755 --- a/code/botlib/be_aas_routealt.c +++ b/code/botlib/be_aas_routealt.c @@ -1,240 +1,240 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: be_aas_routealt.c - * - * desc: AAS - * - * $Archive: /MissionPack/code/botlib/be_aas_routealt.c $ - * - *****************************************************************************/ - -#include "../game/q_shared.h" -#include "l_utils.h" -#include "l_memory.h" -#include "l_log.h" -#include "l_script.h" -#include "l_precomp.h" -#include "l_struct.h" -#include "aasfile.h" -#include "../game/botlib.h" -#include "../game/be_aas.h" -#include "be_aas_funcs.h" -#include "be_interface.h" -#include "be_aas_def.h" - -#define ENABLE_ALTROUTING -//#define ALTROUTE_DEBUG - -typedef struct midrangearea_s -{ - int valid; - unsigned short starttime; - unsigned short goaltime; -} midrangearea_t; - -midrangearea_t *midrangeareas; -int *clusterareas; -int numclusterareas; - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_AltRoutingFloodCluster_r(int areanum) -{ - int i, otherareanum; - aas_area_t *area; - aas_face_t *face; - - //add the current area to the areas of the current cluster - clusterareas[numclusterareas] = areanum; - numclusterareas++; - //remove the area from the mid range areas - midrangeareas[areanum].valid = qfalse; - //flood to other areas through the faces of this area - area = &aasworld.areas[areanum]; - for (i = 0; i < area->numfaces; i++) - { - face = &aasworld.faces[abs(aasworld.faceindex[area->firstface + i])]; - //get the area at the other side of the face - if (face->frontarea == areanum) otherareanum = face->backarea; - else otherareanum = face->frontarea; - //if there is an area at the other side of this face - if (!otherareanum) continue; - //if the other area is not a midrange area - if (!midrangeareas[otherareanum].valid) continue; - // - AAS_AltRoutingFloodCluster_r(otherareanum); - } //end for -} //end of the function AAS_AltRoutingFloodCluster_r -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_AlternativeRouteGoals(vec3_t start, int startareanum, vec3_t goal, int goalareanum, int travelflags, - aas_altroutegoal_t *altroutegoals, int maxaltroutegoals, - int type) -{ -#ifndef ENABLE_ALTROUTING - return 0; -#else - int i, j, bestareanum; - int numaltroutegoals, nummidrangeareas; - int starttime, goaltime, goaltraveltime; - float dist, bestdist; - vec3_t mid, dir; -#ifdef ALTROUTE_DEBUG - int startmillisecs; - - startmillisecs = Sys_MilliSeconds(); -#endif - - if (!startareanum || !goalareanum) - return 0; - //travel time towards the goal area - goaltraveltime = AAS_AreaTravelTimeToGoalArea(startareanum, start, goalareanum, travelflags); - //clear the midrange areas - Com_Memset(midrangeareas, 0, aasworld.numareas * sizeof(midrangearea_t)); - numaltroutegoals = 0; - // - nummidrangeareas = 0; - // - for (i = 1; i < aasworld.numareas; i++) - { - // - if (!(type & ALTROUTEGOAL_ALL)) - { - if (!(type & ALTROUTEGOAL_CLUSTERPORTALS && (aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL))) - { - if (!(type & ALTROUTEGOAL_VIEWPORTALS && (aasworld.areasettings[i].contents & AREACONTENTS_VIEWPORTAL))) - { - continue; - } //end if - } //end if - } //end if - //if the area has no reachabilities - if (!AAS_AreaReachability(i)) continue; - //tavel time from the area to the start area - starttime = AAS_AreaTravelTimeToGoalArea(startareanum, start, i, travelflags); - if (!starttime) continue; - //if the travel time from the start to the area is greater than the shortest goal travel time - if (starttime > (float) 1.1 * goaltraveltime) continue; - //travel time from the area to the goal area - goaltime = AAS_AreaTravelTimeToGoalArea(i, NULL, goalareanum, travelflags); - if (!goaltime) continue; - //if the travel time from the area to the goal is greater than the shortest goal travel time - if (goaltime > (float) 0.8 * goaltraveltime) continue; - //this is a mid range area - midrangeareas[i].valid = qtrue; - midrangeareas[i].starttime = starttime; - midrangeareas[i].goaltime = goaltime; - Log_Write("%d midrange area %d", nummidrangeareas, i); - nummidrangeareas++; - } //end for - // - for (i = 1; i < aasworld.numareas; i++) - { - if (!midrangeareas[i].valid) continue; - //get the areas in one cluster - numclusterareas = 0; - AAS_AltRoutingFloodCluster_r(i); - //now we've got a cluster with areas through which an alternative route could go - //get the 'center' of the cluster - VectorClear(mid); - for (j = 0; j < numclusterareas; j++) - { - VectorAdd(mid, aasworld.areas[clusterareas[j]].center, mid); - } //end for - VectorScale(mid, 1.0 / numclusterareas, mid); - //get the area closest to the center of the cluster - bestdist = 999999; - bestareanum = 0; - for (j = 0; j < numclusterareas; j++) - { - VectorSubtract(mid, aasworld.areas[clusterareas[j]].center, dir); - dist = VectorLength(dir); - if (dist < bestdist) - { - bestdist = dist; - bestareanum = clusterareas[j]; - } //end if - } //end for - //now we've got an area for an alternative route - //FIXME: add alternative goal origin - VectorCopy(aasworld.areas[bestareanum].center, altroutegoals[numaltroutegoals].origin); - altroutegoals[numaltroutegoals].areanum = bestareanum; - altroutegoals[numaltroutegoals].starttraveltime = midrangeareas[bestareanum].starttime; - altroutegoals[numaltroutegoals].goaltraveltime = midrangeareas[bestareanum].goaltime; - altroutegoals[numaltroutegoals].extratraveltime = - (midrangeareas[bestareanum].starttime + midrangeareas[bestareanum].goaltime) - - goaltraveltime; - numaltroutegoals++; - // -#ifdef ALTROUTE_DEBUG - AAS_ShowAreaPolygons(bestareanum, 1, qtrue); -#endif - //don't return more than the maximum alternative route goals - if (numaltroutegoals >= maxaltroutegoals) break; - } //end for -#ifdef ALTROUTE_DEBUG - botimport.Print(PRT_MESSAGE, "alternative route goals in %d msec\n", Sys_MilliSeconds() - startmillisecs); -#endif - return numaltroutegoals; -#endif -} //end of the function AAS_AlternativeRouteGoals -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_InitAlternativeRouting(void) -{ -#ifdef ENABLE_ALTROUTING - if (midrangeareas) FreeMemory(midrangeareas); - midrangeareas = (midrangearea_t *) GetMemory(aasworld.numareas * sizeof(midrangearea_t)); - if (clusterareas) FreeMemory(clusterareas); - clusterareas = (int *) GetMemory(aasworld.numareas * sizeof(int)); -#endif -} //end of the function AAS_InitAlternativeRouting -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_ShutdownAlternativeRouting(void) -{ -#ifdef ENABLE_ALTROUTING - if (midrangeareas) FreeMemory(midrangeareas); - midrangeareas = NULL; - if (clusterareas) FreeMemory(clusterareas); - clusterareas = NULL; - numclusterareas = 0; -#endif -} //end of the function AAS_ShutdownAlternativeRouting +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_routealt.c + * + * desc: AAS + * + * $Archive: /MissionPack/code/botlib/be_aas_routealt.c $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_utils.h" +#include "l_memory.h" +#include "l_log.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_aas_def.h" + +#define ENABLE_ALTROUTING +//#define ALTROUTE_DEBUG + +typedef struct midrangearea_s +{ + int valid; + unsigned short starttime; + unsigned short goaltime; +} midrangearea_t; + +midrangearea_t *midrangeareas; +int *clusterareas; +int numclusterareas; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_AltRoutingFloodCluster_r(int areanum) +{ + int i, otherareanum; + aas_area_t *area; + aas_face_t *face; + + //add the current area to the areas of the current cluster + clusterareas[numclusterareas] = areanum; + numclusterareas++; + //remove the area from the mid range areas + midrangeareas[areanum].valid = qfalse; + //flood to other areas through the faces of this area + area = &aasworld.areas[areanum]; + for (i = 0; i < area->numfaces; i++) + { + face = &aasworld.faces[abs(aasworld.faceindex[area->firstface + i])]; + //get the area at the other side of the face + if (face->frontarea == areanum) otherareanum = face->backarea; + else otherareanum = face->frontarea; + //if there is an area at the other side of this face + if (!otherareanum) continue; + //if the other area is not a midrange area + if (!midrangeareas[otherareanum].valid) continue; + // + AAS_AltRoutingFloodCluster_r(otherareanum); + } //end for +} //end of the function AAS_AltRoutingFloodCluster_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AlternativeRouteGoals(vec3_t start, int startareanum, vec3_t goal, int goalareanum, int travelflags, + aas_altroutegoal_t *altroutegoals, int maxaltroutegoals, + int type) +{ +#ifndef ENABLE_ALTROUTING + return 0; +#else + int i, j, bestareanum; + int numaltroutegoals, nummidrangeareas; + int starttime, goaltime, goaltraveltime; + float dist, bestdist; + vec3_t mid, dir; +#ifdef ALTROUTE_DEBUG + int startmillisecs; + + startmillisecs = Sys_MilliSeconds(); +#endif + + if (!startareanum || !goalareanum) + return 0; + //travel time towards the goal area + goaltraveltime = AAS_AreaTravelTimeToGoalArea(startareanum, start, goalareanum, travelflags); + //clear the midrange areas + Com_Memset(midrangeareas, 0, aasworld.numareas * sizeof(midrangearea_t)); + numaltroutegoals = 0; + // + nummidrangeareas = 0; + // + for (i = 1; i < aasworld.numareas; i++) + { + // + if (!(type & ALTROUTEGOAL_ALL)) + { + if (!(type & ALTROUTEGOAL_CLUSTERPORTALS && (aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL))) + { + if (!(type & ALTROUTEGOAL_VIEWPORTALS && (aasworld.areasettings[i].contents & AREACONTENTS_VIEWPORTAL))) + { + continue; + } //end if + } //end if + } //end if + //if the area has no reachabilities + if (!AAS_AreaReachability(i)) continue; + //tavel time from the area to the start area + starttime = AAS_AreaTravelTimeToGoalArea(startareanum, start, i, travelflags); + if (!starttime) continue; + //if the travel time from the start to the area is greater than the shortest goal travel time + if (starttime > (float) 1.1 * goaltraveltime) continue; + //travel time from the area to the goal area + goaltime = AAS_AreaTravelTimeToGoalArea(i, NULL, goalareanum, travelflags); + if (!goaltime) continue; + //if the travel time from the area to the goal is greater than the shortest goal travel time + if (goaltime > (float) 0.8 * goaltraveltime) continue; + //this is a mid range area + midrangeareas[i].valid = qtrue; + midrangeareas[i].starttime = starttime; + midrangeareas[i].goaltime = goaltime; + Log_Write("%d midrange area %d", nummidrangeareas, i); + nummidrangeareas++; + } //end for + // + for (i = 1; i < aasworld.numareas; i++) + { + if (!midrangeareas[i].valid) continue; + //get the areas in one cluster + numclusterareas = 0; + AAS_AltRoutingFloodCluster_r(i); + //now we've got a cluster with areas through which an alternative route could go + //get the 'center' of the cluster + VectorClear(mid); + for (j = 0; j < numclusterareas; j++) + { + VectorAdd(mid, aasworld.areas[clusterareas[j]].center, mid); + } //end for + VectorScale(mid, 1.0 / numclusterareas, mid); + //get the area closest to the center of the cluster + bestdist = 999999; + bestareanum = 0; + for (j = 0; j < numclusterareas; j++) + { + VectorSubtract(mid, aasworld.areas[clusterareas[j]].center, dir); + dist = VectorLength(dir); + if (dist < bestdist) + { + bestdist = dist; + bestareanum = clusterareas[j]; + } //end if + } //end for + //now we've got an area for an alternative route + //FIXME: add alternative goal origin + VectorCopy(aasworld.areas[bestareanum].center, altroutegoals[numaltroutegoals].origin); + altroutegoals[numaltroutegoals].areanum = bestareanum; + altroutegoals[numaltroutegoals].starttraveltime = midrangeareas[bestareanum].starttime; + altroutegoals[numaltroutegoals].goaltraveltime = midrangeareas[bestareanum].goaltime; + altroutegoals[numaltroutegoals].extratraveltime = + (midrangeareas[bestareanum].starttime + midrangeareas[bestareanum].goaltime) - + goaltraveltime; + numaltroutegoals++; + // +#ifdef ALTROUTE_DEBUG + AAS_ShowAreaPolygons(bestareanum, 1, qtrue); +#endif + //don't return more than the maximum alternative route goals + if (numaltroutegoals >= maxaltroutegoals) break; + } //end for +#ifdef ALTROUTE_DEBUG + botimport.Print(PRT_MESSAGE, "alternative route goals in %d msec\n", Sys_MilliSeconds() - startmillisecs); +#endif + return numaltroutegoals; +#endif +} //end of the function AAS_AlternativeRouteGoals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitAlternativeRouting(void) +{ +#ifdef ENABLE_ALTROUTING + if (midrangeareas) FreeMemory(midrangeareas); + midrangeareas = (midrangearea_t *) GetMemory(aasworld.numareas * sizeof(midrangearea_t)); + if (clusterareas) FreeMemory(clusterareas); + clusterareas = (int *) GetMemory(aasworld.numareas * sizeof(int)); +#endif +} //end of the function AAS_InitAlternativeRouting +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShutdownAlternativeRouting(void) +{ +#ifdef ENABLE_ALTROUTING + if (midrangeareas) FreeMemory(midrangeareas); + midrangeareas = NULL; + if (clusterareas) FreeMemory(clusterareas); + clusterareas = NULL; + numclusterareas = 0; +#endif +} //end of the function AAS_ShutdownAlternativeRouting diff --git a/code/botlib/be_aas_routealt.h b/code/botlib/be_aas_routealt.h index da4ef67..a9e1657 100755 --- a/code/botlib/be_aas_routealt.h +++ b/code/botlib/be_aas_routealt.h @@ -1,40 +1,40 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: be_aas_routealt.h - * - * desc: AAS - * - * $Archive: /source/code/botlib/be_aas_routealt.h $ - * - *****************************************************************************/ - -#ifdef AASINTERN -void AAS_InitAlternativeRouting(void); -void AAS_ShutdownAlternativeRouting(void); -#endif //AASINTERN - - -int AAS_AlternativeRouteGoals(vec3_t start, int startareanum, vec3_t goal, int goalareanum, int travelflags, - aas_altroutegoal_t *altroutegoals, int maxaltroutegoals, - int type); +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_routealt.h + * + * desc: AAS + * + * $Archive: /source/code/botlib/be_aas_routealt.h $ + * + *****************************************************************************/ + +#ifdef AASINTERN +void AAS_InitAlternativeRouting(void); +void AAS_ShutdownAlternativeRouting(void); +#endif //AASINTERN + + +int AAS_AlternativeRouteGoals(vec3_t start, int startareanum, vec3_t goal, int goalareanum, int travelflags, + aas_altroutegoal_t *altroutegoals, int maxaltroutegoals, + int type); diff --git a/code/botlib/be_aas_sample.c b/code/botlib/be_aas_sample.c index c0adb47..68806eb 100755 --- a/code/botlib/be_aas_sample.c +++ b/code/botlib/be_aas_sample.c @@ -1,1394 +1,1394 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: be_aas_sample.c - * - * desc: AAS environment sampling - * - * $Archive: /MissionPack/code/botlib/be_aas_sample.c $ - * - *****************************************************************************/ - -#include "../game/q_shared.h" -#include "l_memory.h" -#include "l_script.h" -#include "l_precomp.h" -#include "l_struct.h" -#ifndef BSPC -#include "l_libvar.h" -#endif -#include "aasfile.h" -#include "../game/botlib.h" -#include "../game/be_aas.h" -#include "be_interface.h" -#include "be_aas_funcs.h" -#include "be_aas_def.h" - -extern botlib_import_t botimport; - -//#define AAS_SAMPLE_DEBUG - -#define BBOX_NORMAL_EPSILON 0.001 - -#define ON_EPSILON 0 //0.0005 - -#define TRACEPLANE_EPSILON 0.125 - -typedef struct aas_tracestack_s -{ - vec3_t start; //start point of the piece of line to trace - vec3_t end; //end point of the piece of line to trace - int planenum; //last plane used as splitter - int nodenum; //node found after splitting with planenum -} aas_tracestack_t; - -int numaaslinks; - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_PresenceTypeBoundingBox(int presencetype, vec3_t mins, vec3_t maxs) -{ - int index; - //bounding box size for each presence type - vec3_t boxmins[3] = {{0, 0, 0}, {-15, -15, -24}, {-15, -15, -24}}; - vec3_t boxmaxs[3] = {{0, 0, 0}, { 15, 15, 32}, { 15, 15, 8}}; - - if (presencetype == PRESENCE_NORMAL) index = 1; - else if (presencetype == PRESENCE_CROUCH) index = 2; - else - { - botimport.Print(PRT_FATAL, "AAS_PresenceTypeBoundingBox: unknown presence type\n"); - index = 2; - } //end if - VectorCopy(boxmins[index], mins); - VectorCopy(boxmaxs[index], maxs); -} //end of the function AAS_PresenceTypeBoundingBox -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_InitAASLinkHeap(void) -{ - int i, max_aaslinks; - - max_aaslinks = aasworld.linkheapsize; - //if there's no link heap present - if (!aasworld.linkheap) - { -#ifdef BSPC - max_aaslinks = 6144; -#else - max_aaslinks = (int) LibVarValue("max_aaslinks", "6144"); -#endif - if (max_aaslinks < 0) max_aaslinks = 0; - aasworld.linkheapsize = max_aaslinks; - aasworld.linkheap = (aas_link_t *) GetHunkMemory(max_aaslinks * sizeof(aas_link_t)); - } //end if - //link the links on the heap - aasworld.linkheap[0].prev_ent = NULL; - aasworld.linkheap[0].next_ent = &aasworld.linkheap[1]; - for (i = 1; i < max_aaslinks-1; i++) - { - aasworld.linkheap[i].prev_ent = &aasworld.linkheap[i - 1]; - aasworld.linkheap[i].next_ent = &aasworld.linkheap[i + 1]; - } //end for - aasworld.linkheap[max_aaslinks-1].prev_ent = &aasworld.linkheap[max_aaslinks-2]; - aasworld.linkheap[max_aaslinks-1].next_ent = NULL; - //pointer to the first free link - aasworld.freelinks = &aasworld.linkheap[0]; - // - numaaslinks = max_aaslinks; -} //end of the function AAS_InitAASLinkHeap -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_FreeAASLinkHeap(void) -{ - if (aasworld.linkheap) FreeMemory(aasworld.linkheap); - aasworld.linkheap = NULL; - aasworld.linkheapsize = 0; -} //end of the function AAS_FreeAASLinkHeap -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -aas_link_t *AAS_AllocAASLink(void) -{ - aas_link_t *link; - - link = aasworld.freelinks; - if (!link) - { -#ifndef BSPC - if (bot_developer) -#endif - { - botimport.Print(PRT_FATAL, "empty aas link heap\n"); - } //end if - return NULL; - } //end if - if (aasworld.freelinks) aasworld.freelinks = aasworld.freelinks->next_ent; - if (aasworld.freelinks) aasworld.freelinks->prev_ent = NULL; - numaaslinks--; - return link; -} //end of the function AAS_AllocAASLink -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_DeAllocAASLink(aas_link_t *link) -{ - if (aasworld.freelinks) aasworld.freelinks->prev_ent = link; - link->prev_ent = NULL; - link->next_ent = aasworld.freelinks; - link->prev_area = NULL; - link->next_area = NULL; - aasworld.freelinks = link; - numaaslinks++; -} //end of the function AAS_DeAllocAASLink -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_InitAASLinkedEntities(void) -{ - if (!aasworld.loaded) return; - if (aasworld.arealinkedentities) FreeMemory(aasworld.arealinkedentities); - aasworld.arealinkedentities = (aas_link_t **) GetClearedHunkMemory( - aasworld.numareas * sizeof(aas_link_t *)); -} //end of the function AAS_InitAASLinkedEntities -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_FreeAASLinkedEntities(void) -{ - if (aasworld.arealinkedentities) FreeMemory(aasworld.arealinkedentities); - aasworld.arealinkedentities = NULL; -} //end of the function AAS_InitAASLinkedEntities -//=========================================================================== -// returns the AAS area the point is in -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_PointAreaNum(vec3_t point) -{ - int nodenum; - vec_t dist; - aas_node_t *node; - aas_plane_t *plane; - - if (!aasworld.loaded) - { - botimport.Print(PRT_ERROR, "AAS_PointAreaNum: aas not loaded\n"); - return 0; - } //end if - - //start with node 1 because node zero is a dummy used for solid leafs - nodenum = 1; - while (nodenum > 0) - { -// botimport.Print(PRT_MESSAGE, "[%d]", nodenum); -#ifdef AAS_SAMPLE_DEBUG - if (nodenum >= aasworld.numnodes) - { - botimport.Print(PRT_ERROR, "nodenum = %d >= aasworld.numnodes = %d\n", nodenum, aasworld.numnodes); - return 0; - } //end if -#endif //AAS_SAMPLE_DEBUG - node = &aasworld.nodes[nodenum]; -#ifdef AAS_SAMPLE_DEBUG - if (node->planenum < 0 || node->planenum >= aasworld.numplanes) - { - botimport.Print(PRT_ERROR, "node->planenum = %d >= aasworld.numplanes = %d\n", node->planenum, aasworld.numplanes); - return 0; - } //end if -#endif //AAS_SAMPLE_DEBUG - plane = &aasworld.planes[node->planenum]; - dist = DotProduct(point, plane->normal) - plane->dist; - if (dist > 0) nodenum = node->children[0]; - else nodenum = node->children[1]; - } //end while - if (!nodenum) - { -#ifdef AAS_SAMPLE_DEBUG - botimport.Print(PRT_MESSAGE, "in solid\n"); -#endif //AAS_SAMPLE_DEBUG - return 0; - } //end if - return -nodenum; -} //end of the function AAS_PointAreaNum -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_PointReachabilityAreaIndex( vec3_t origin ) -{ - int areanum, cluster, i, index; - - if (!aasworld.initialized) - return 0; - - if ( !origin ) - { - index = 0; - for (i = 0; i < aasworld.numclusters; i++) - { - index += aasworld.clusters[i].numreachabilityareas; - } //end for - return index; - } //end if - - areanum = AAS_PointAreaNum( origin ); - if ( !areanum || !AAS_AreaReachability(areanum) ) - return 0; - cluster = aasworld.areasettings[areanum].cluster; - areanum = aasworld.areasettings[areanum].clusterareanum; - if (cluster < 0) - { - cluster = aasworld.portals[-cluster].frontcluster; - areanum = aasworld.portals[-cluster].clusterareanum[0]; - } //end if - - index = 0; - for (i = 0; i < cluster; i++) - { - index += aasworld.clusters[i].numreachabilityareas; - } //end for - index += areanum; - return index; -} //end of the function AAS_PointReachabilityAreaIndex -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_AreaCluster(int areanum) -{ - if (areanum <= 0 || areanum >= aasworld.numareas) - { - botimport.Print(PRT_ERROR, "AAS_AreaCluster: invalid area number\n"); - return 0; - } //end if - return aasworld.areasettings[areanum].cluster; -} //end of the function AAS_AreaCluster -//=========================================================================== -// returns the presence types of the given area -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_AreaPresenceType(int areanum) -{ - if (!aasworld.loaded) return 0; - if (areanum <= 0 || areanum >= aasworld.numareas) - { - botimport.Print(PRT_ERROR, "AAS_AreaPresenceType: invalid area number\n"); - return 0; - } //end if - return aasworld.areasettings[areanum].presencetype; -} //end of the function AAS_AreaPresenceType -//=========================================================================== -// returns the presence type at the given point -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_PointPresenceType(vec3_t point) -{ - int areanum; - - if (!aasworld.loaded) return 0; - - areanum = AAS_PointAreaNum(point); - if (!areanum) return PRESENCE_NONE; - return aasworld.areasettings[areanum].presencetype; -} //end of the function AAS_PointPresenceType -//=========================================================================== -// calculates the minimum distance between the origin of the box and the -// given plane when both will collide on the given side of the plane -// -// normal = normal vector of plane to calculate distance from -// mins = minimums of box relative to origin -// maxs = maximums of box relative to origin -// side = side of the plane we want to calculate the distance from -// 0 normal vector side -// 1 not normal vector side -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -vec_t AAS_BoxOriginDistanceFromPlane(vec3_t normal, vec3_t mins, vec3_t maxs, int side) -{ - vec3_t v1, v2; - int i; - - //swap maxs and mins when on the other side of the plane - if (side) - { - //get a point of the box that would be one of the first - //to collide with the plane - for (i = 0; i < 3; i++) - { - if (normal[i] > BBOX_NORMAL_EPSILON) v1[i] = maxs[i]; - else if (normal[i] < -BBOX_NORMAL_EPSILON) v1[i] = mins[i]; - else v1[i] = 0; - } //end for - } //end if - else - { - //get a point of the box that would be one of the first - //to collide with the plane - for (i = 0; i < 3; i++) - { - if (normal[i] > BBOX_NORMAL_EPSILON) v1[i] = mins[i]; - else if (normal[i] < -BBOX_NORMAL_EPSILON) v1[i] = maxs[i]; - else v1[i] = 0; - } //end for - } //end else - // - VectorCopy(normal, v2); - VectorInverse(v2); -// VectorNegate(normal, v2); - return DotProduct(v1, v2); -} //end of the function AAS_BoxOriginDistanceFromPlane -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean AAS_AreaEntityCollision(int areanum, vec3_t start, vec3_t end, - int presencetype, int passent, aas_trace_t *trace) -{ - int collision; - vec3_t boxmins, boxmaxs; - aas_link_t *link; - bsp_trace_t bsptrace; - - AAS_PresenceTypeBoundingBox(presencetype, boxmins, boxmaxs); - - Com_Memset(&bsptrace, 0, sizeof(bsp_trace_t)); //make compiler happy - //assume no collision - bsptrace.fraction = 1; - collision = qfalse; - for (link = aasworld.arealinkedentities[areanum]; link; link = link->next_ent) - { - //ignore the pass entity - if (link->entnum == passent) continue; - // - if (AAS_EntityCollision(link->entnum, start, boxmins, boxmaxs, end, - CONTENTS_SOLID|CONTENTS_PLAYERCLIP, &bsptrace)) - { - collision = qtrue; - } //end if - } //end for - if (collision) - { - trace->startsolid = bsptrace.startsolid; - trace->ent = bsptrace.ent; - VectorCopy(bsptrace.endpos, trace->endpos); - trace->area = 0; - trace->planenum = 0; - return qtrue; - } //end if - return qfalse; -} //end of the function AAS_AreaEntityCollision -//=========================================================================== -// recursive subdivision of the line by the BSP tree. -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -aas_trace_t AAS_TraceClientBBox(vec3_t start, vec3_t end, int presencetype, - int passent) -{ - int side, nodenum, tmpplanenum; - float front, back, frac; - vec3_t cur_start, cur_end, cur_mid, v1, v2; - aas_tracestack_t tracestack[127]; - aas_tracestack_t *tstack_p; - aas_node_t *aasnode; - aas_plane_t *plane; - aas_trace_t trace; - - //clear the trace structure - Com_Memset(&trace, 0, sizeof(aas_trace_t)); - - if (!aasworld.loaded) return trace; - - tstack_p = tracestack; - //we start with the whole line on the stack - VectorCopy(start, tstack_p->start); - VectorCopy(end, tstack_p->end); - tstack_p->planenum = 0; - //start with node 1 because node zero is a dummy for a solid leaf - tstack_p->nodenum = 1; //starting at the root of the tree - tstack_p++; - - while (1) - { - //pop up the stack - tstack_p--; - //if the trace stack is empty (ended up with a piece of the - //line to be traced in an area) - if (tstack_p < tracestack) - { - tstack_p++; - //nothing was hit - trace.startsolid = qfalse; - trace.fraction = 1.0; - //endpos is the end of the line - VectorCopy(end, trace.endpos); - //nothing hit - trace.ent = 0; - trace.area = 0; - trace.planenum = 0; - return trace; - } //end if - //number of the current node to test the line against - nodenum = tstack_p->nodenum; - //if it is an area - if (nodenum < 0) - { -#ifdef AAS_SAMPLE_DEBUG - if (-nodenum > aasworld.numareasettings) - { - botimport.Print(PRT_ERROR, "AAS_TraceBoundingBox: -nodenum out of range\n"); - return trace; - } //end if -#endif //AAS_SAMPLE_DEBUG - //botimport.Print(PRT_MESSAGE, "areanum = %d, must be %d\n", -nodenum, AAS_PointAreaNum(start)); - //if can't enter the area because it hasn't got the right presence type - if (!(aasworld.areasettings[-nodenum].presencetype & presencetype)) - { - //if the start point is still the initial start point - //NOTE: no need for epsilons because the points will be - //exactly the same when they're both the start point - if (tstack_p->start[0] == start[0] && - tstack_p->start[1] == start[1] && - tstack_p->start[2] == start[2]) - { - trace.startsolid = qtrue; - trace.fraction = 0.0; - VectorClear(v1); - } //end if - else - { - trace.startsolid = qfalse; - VectorSubtract(end, start, v1); - VectorSubtract(tstack_p->start, start, v2); - trace.fraction = VectorLength(v2) / VectorNormalize(v1); - VectorMA(tstack_p->start, -0.125, v1, tstack_p->start); - } //end else - VectorCopy(tstack_p->start, trace.endpos); - trace.ent = 0; - trace.area = -nodenum; -// VectorSubtract(end, start, v1); - trace.planenum = tstack_p->planenum; - //always take the plane with normal facing towards the trace start - plane = &aasworld.planes[trace.planenum]; - if (DotProduct(v1, plane->normal) > 0) trace.planenum ^= 1; - return trace; - } //end if - else - { - if (passent >= 0) - { - if (AAS_AreaEntityCollision(-nodenum, tstack_p->start, - tstack_p->end, presencetype, passent, - &trace)) - { - if (!trace.startsolid) - { - VectorSubtract(end, start, v1); - VectorSubtract(trace.endpos, start, v2); - trace.fraction = VectorLength(v2) / VectorLength(v1); - } //end if - return trace; - } //end if - } //end if - } //end else - trace.lastarea = -nodenum; - continue; - } //end if - //if it is a solid leaf - if (!nodenum) - { - //if the start point is still the initial start point - //NOTE: no need for epsilons because the points will be - //exactly the same when they're both the start point - if (tstack_p->start[0] == start[0] && - tstack_p->start[1] == start[1] && - tstack_p->start[2] == start[2]) - { - trace.startsolid = qtrue; - trace.fraction = 0.0; - VectorClear(v1); - } //end if - else - { - trace.startsolid = qfalse; - VectorSubtract(end, start, v1); - VectorSubtract(tstack_p->start, start, v2); - trace.fraction = VectorLength(v2) / VectorNormalize(v1); - VectorMA(tstack_p->start, -0.125, v1, tstack_p->start); - } //end else - VectorCopy(tstack_p->start, trace.endpos); - trace.ent = 0; - trace.area = 0; //hit solid leaf -// VectorSubtract(end, start, v1); - trace.planenum = tstack_p->planenum; - //always take the plane with normal facing towards the trace start - plane = &aasworld.planes[trace.planenum]; - if (DotProduct(v1, plane->normal) > 0) trace.planenum ^= 1; - return trace; - } //end if -#ifdef AAS_SAMPLE_DEBUG - if (nodenum > aasworld.numnodes) - { - botimport.Print(PRT_ERROR, "AAS_TraceBoundingBox: nodenum out of range\n"); - return trace; - } //end if -#endif //AAS_SAMPLE_DEBUG - //the node to test against - aasnode = &aasworld.nodes[nodenum]; - //start point of current line to test against node - VectorCopy(tstack_p->start, cur_start); - //end point of the current line to test against node - VectorCopy(tstack_p->end, cur_end); - //the current node plane - plane = &aasworld.planes[aasnode->planenum]; - - switch(plane->type) - {/*FIXME: wtf doesn't this work? obviously the axial node planes aren't always facing positive!!! - //check for axial planes - case PLANE_X: - { - front = cur_start[0] - plane->dist; - back = cur_end[0] - plane->dist; - break; - } //end case - case PLANE_Y: - { - front = cur_start[1] - plane->dist; - back = cur_end[1] - plane->dist; - break; - } //end case - case PLANE_Z: - { - front = cur_start[2] - plane->dist; - back = cur_end[2] - plane->dist; - break; - } //end case*/ - default: //gee it's not an axial plane - { - front = DotProduct(cur_start, plane->normal) - plane->dist; - back = DotProduct(cur_end, plane->normal) - plane->dist; - break; - } //end default - } //end switch - // bk010221 - old location of FPE hack and divide by zero expression - //if the whole to be traced line is totally at the front of this node - //only go down the tree with the front child - if ((front >= -ON_EPSILON && back >= -ON_EPSILON)) - { - //keep the current start and end point on the stack - //and go down the tree with the front child - tstack_p->nodenum = aasnode->children[0]; - tstack_p++; - if (tstack_p >= &tracestack[127]) - { - botimport.Print(PRT_ERROR, "AAS_TraceBoundingBox: stack overflow\n"); - return trace; - } //end if - } //end if - //if the whole to be traced line is totally at the back of this node - //only go down the tree with the back child - else if ((front < ON_EPSILON && back < ON_EPSILON)) - { - //keep the current start and end point on the stack - //and go down the tree with the back child - tstack_p->nodenum = aasnode->children[1]; - tstack_p++; - if (tstack_p >= &tracestack[127]) - { - botimport.Print(PRT_ERROR, "AAS_TraceBoundingBox: stack overflow\n"); - return trace; - } //end if - } //end if - //go down the tree both at the front and back of the node - else - { - tmpplanenum = tstack_p->planenum; - // bk010221 - new location of divide by zero (see above) - if ( front == back ) front -= 0.001f; // bk0101022 - hack/FPE - //calculate the hitpoint with the node (split point of the line) - //put the crosspoint TRACEPLANE_EPSILON pixels on the near side - if (front < 0) frac = (front + TRACEPLANE_EPSILON)/(front-back); - else frac = (front - TRACEPLANE_EPSILON)/(front-back); // bk010221 - // - if (frac < 0) - frac = 0.001f; //0 - else if (frac > 1) - frac = 0.999f; //1 - //frac = front / (front-back); - // - cur_mid[0] = cur_start[0] + (cur_end[0] - cur_start[0]) * frac; - cur_mid[1] = cur_start[1] + (cur_end[1] - cur_start[1]) * frac; - cur_mid[2] = cur_start[2] + (cur_end[2] - cur_start[2]) * frac; - -// AAS_DrawPlaneCross(cur_mid, plane->normal, plane->dist, plane->type, LINECOLOR_RED); - //side the front part of the line is on - side = front < 0; - //first put the end part of the line on the stack (back side) - VectorCopy(cur_mid, tstack_p->start); - //not necesary to store because still on stack - //VectorCopy(cur_end, tstack_p->end); - tstack_p->planenum = aasnode->planenum; - tstack_p->nodenum = aasnode->children[!side]; - tstack_p++; - if (tstack_p >= &tracestack[127]) - { - botimport.Print(PRT_ERROR, "AAS_TraceBoundingBox: stack overflow\n"); - return trace; - } //end if - //now put the part near the start of the line on the stack so we will - //continue with thats part first. This way we'll find the first - //hit of the bbox - VectorCopy(cur_start, tstack_p->start); - VectorCopy(cur_mid, tstack_p->end); - tstack_p->planenum = tmpplanenum; - tstack_p->nodenum = aasnode->children[side]; - tstack_p++; - if (tstack_p >= &tracestack[127]) - { - botimport.Print(PRT_ERROR, "AAS_TraceBoundingBox: stack overflow\n"); - return trace; - } //end if - } //end else - } //end while -// return trace; -} //end of the function AAS_TraceClientBBox -//=========================================================================== -// recursive subdivision of the line by the BSP tree. -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_TraceAreas(vec3_t start, vec3_t end, int *areas, vec3_t *points, int maxareas) -{ - int side, nodenum, tmpplanenum; - int numareas; - float front, back, frac; - vec3_t cur_start, cur_end, cur_mid; - aas_tracestack_t tracestack[127]; - aas_tracestack_t *tstack_p; - aas_node_t *aasnode; - aas_plane_t *plane; - - numareas = 0; - areas[0] = 0; - if (!aasworld.loaded) return numareas; - - tstack_p = tracestack; - //we start with the whole line on the stack - VectorCopy(start, tstack_p->start); - VectorCopy(end, tstack_p->end); - tstack_p->planenum = 0; - //start with node 1 because node zero is a dummy for a solid leaf - tstack_p->nodenum = 1; //starting at the root of the tree - tstack_p++; - - while (1) - { - //pop up the stack - tstack_p--; - //if the trace stack is empty (ended up with a piece of the - //line to be traced in an area) - if (tstack_p < tracestack) - { - return numareas; - } //end if - //number of the current node to test the line against - nodenum = tstack_p->nodenum; - //if it is an area - if (nodenum < 0) - { -#ifdef AAS_SAMPLE_DEBUG - if (-nodenum > aasworld.numareasettings) - { - botimport.Print(PRT_ERROR, "AAS_TraceAreas: -nodenum = %d out of range\n", -nodenum); - return numareas; - } //end if -#endif //AAS_SAMPLE_DEBUG - //botimport.Print(PRT_MESSAGE, "areanum = %d, must be %d\n", -nodenum, AAS_PointAreaNum(start)); - areas[numareas] = -nodenum; - if (points) VectorCopy(tstack_p->start, points[numareas]); - numareas++; - if (numareas >= maxareas) return numareas; - continue; - } //end if - //if it is a solid leaf - if (!nodenum) - { - continue; - } //end if -#ifdef AAS_SAMPLE_DEBUG - if (nodenum > aasworld.numnodes) - { - botimport.Print(PRT_ERROR, "AAS_TraceAreas: nodenum out of range\n"); - return numareas; - } //end if -#endif //AAS_SAMPLE_DEBUG - //the node to test against - aasnode = &aasworld.nodes[nodenum]; - //start point of current line to test against node - VectorCopy(tstack_p->start, cur_start); - //end point of the current line to test against node - VectorCopy(tstack_p->end, cur_end); - //the current node plane - plane = &aasworld.planes[aasnode->planenum]; - - switch(plane->type) - {/*FIXME: wtf doesn't this work? obviously the node planes aren't always facing positive!!! - //check for axial planes - case PLANE_X: - { - front = cur_start[0] - plane->dist; - back = cur_end[0] - plane->dist; - break; - } //end case - case PLANE_Y: - { - front = cur_start[1] - plane->dist; - back = cur_end[1] - plane->dist; - break; - } //end case - case PLANE_Z: - { - front = cur_start[2] - plane->dist; - back = cur_end[2] - plane->dist; - break; - } //end case*/ - default: //gee it's not an axial plane - { - front = DotProduct(cur_start, plane->normal) - plane->dist; - back = DotProduct(cur_end, plane->normal) - plane->dist; - break; - } //end default - } //end switch - - //if the whole to be traced line is totally at the front of this node - //only go down the tree with the front child - if (front > 0 && back > 0) - { - //keep the current start and end point on the stack - //and go down the tree with the front child - tstack_p->nodenum = aasnode->children[0]; - tstack_p++; - if (tstack_p >= &tracestack[127]) - { - botimport.Print(PRT_ERROR, "AAS_TraceAreas: stack overflow\n"); - return numareas; - } //end if - } //end if - //if the whole to be traced line is totally at the back of this node - //only go down the tree with the back child - else if (front <= 0 && back <= 0) - { - //keep the current start and end point on the stack - //and go down the tree with the back child - tstack_p->nodenum = aasnode->children[1]; - tstack_p++; - if (tstack_p >= &tracestack[127]) - { - botimport.Print(PRT_ERROR, "AAS_TraceAreas: stack overflow\n"); - return numareas; - } //end if - } //end if - //go down the tree both at the front and back of the node - else - { - tmpplanenum = tstack_p->planenum; - //calculate the hitpoint with the node (split point of the line) - //put the crosspoint TRACEPLANE_EPSILON pixels on the near side - if (front < 0) frac = (front)/(front-back); - else frac = (front)/(front-back); - if (frac < 0) frac = 0; - else if (frac > 1) frac = 1; - //frac = front / (front-back); - // - cur_mid[0] = cur_start[0] + (cur_end[0] - cur_start[0]) * frac; - cur_mid[1] = cur_start[1] + (cur_end[1] - cur_start[1]) * frac; - cur_mid[2] = cur_start[2] + (cur_end[2] - cur_start[2]) * frac; - -// AAS_DrawPlaneCross(cur_mid, plane->normal, plane->dist, plane->type, LINECOLOR_RED); - //side the front part of the line is on - side = front < 0; - //first put the end part of the line on the stack (back side) - VectorCopy(cur_mid, tstack_p->start); - //not necesary to store because still on stack - //VectorCopy(cur_end, tstack_p->end); - tstack_p->planenum = aasnode->planenum; - tstack_p->nodenum = aasnode->children[!side]; - tstack_p++; - if (tstack_p >= &tracestack[127]) - { - botimport.Print(PRT_ERROR, "AAS_TraceAreas: stack overflow\n"); - return numareas; - } //end if - //now put the part near the start of the line on the stack so we will - //continue with thats part first. This way we'll find the first - //hit of the bbox - VectorCopy(cur_start, tstack_p->start); - VectorCopy(cur_mid, tstack_p->end); - tstack_p->planenum = tmpplanenum; - tstack_p->nodenum = aasnode->children[side]; - tstack_p++; - if (tstack_p >= &tracestack[127]) - { - botimport.Print(PRT_ERROR, "AAS_TraceAreas: stack overflow\n"); - return numareas; - } //end if - } //end else - } //end while -// return numareas; -} //end of the function AAS_TraceAreas -//=========================================================================== -// a simple cross product -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -// void AAS_OrthogonalToVectors(vec3_t v1, vec3_t v2, vec3_t res) -#define AAS_OrthogonalToVectors(v1, v2, res) \ - (res)[0] = ((v1)[1] * (v2)[2]) - ((v1)[2] * (v2)[1]);\ - (res)[1] = ((v1)[2] * (v2)[0]) - ((v1)[0] * (v2)[2]);\ - (res)[2] = ((v1)[0] * (v2)[1]) - ((v1)[1] * (v2)[0]); -//=========================================================================== -// tests if the given point is within the face boundaries -// -// Parameter: face : face to test if the point is in it -// pnormal : normal of the plane to use for the face -// point : point to test if inside face boundaries -// Returns: qtrue if the point is within the face boundaries -// Changes Globals: - -//=========================================================================== -qboolean AAS_InsideFace(aas_face_t *face, vec3_t pnormal, vec3_t point, float epsilon) -{ - int i, firstvertex, edgenum; - vec3_t v0; - vec3_t edgevec, pointvec, sepnormal; - aas_edge_t *edge; -#ifdef AAS_SAMPLE_DEBUG - int lastvertex = 0; -#endif //AAS_SAMPLE_DEBUG - - if (!aasworld.loaded) return qfalse; - - for (i = 0; i < face->numedges; i++) - { - edgenum = aasworld.edgeindex[face->firstedge + i]; - edge = &aasworld.edges[abs(edgenum)]; - //get the first vertex of the edge - firstvertex = edgenum < 0; - VectorCopy(aasworld.vertexes[edge->v[firstvertex]], v0); - //edge vector - VectorSubtract(aasworld.vertexes[edge->v[!firstvertex]], v0, edgevec); - // -#ifdef AAS_SAMPLE_DEBUG - if (lastvertex && lastvertex != edge->v[firstvertex]) - { - botimport.Print(PRT_MESSAGE, "winding not counter clockwise\n"); - } //end if - lastvertex = edge->v[!firstvertex]; -#endif //AAS_SAMPLE_DEBUG - //vector from first edge point to point possible in face - VectorSubtract(point, v0, pointvec); - //get a vector pointing inside the face orthogonal to both the - //edge vector and the normal vector of the plane the face is in - //this vector defines a plane through the origin (first vertex of - //edge) and through both the edge vector and the normal vector - //of the plane - AAS_OrthogonalToVectors(edgevec, pnormal, sepnormal); - //check on wich side of the above plane the point is - //this is done by checking the sign of the dot product of the - //vector orthogonal vector from above and the vector from the - //origin (first vertex of edge) to the point - //if the dotproduct is smaller than zero the point is outside the face - if (DotProduct(pointvec, sepnormal) < -epsilon) return qfalse; - } //end for - return qtrue; -} //end of the function AAS_InsideFace -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean AAS_PointInsideFace(int facenum, vec3_t point, float epsilon) -{ - int i, firstvertex, edgenum; - vec_t *v1, *v2; - vec3_t edgevec, pointvec, sepnormal; - aas_edge_t *edge; - aas_plane_t *plane; - aas_face_t *face; - - if (!aasworld.loaded) return qfalse; - - face = &aasworld.faces[facenum]; - plane = &aasworld.planes[face->planenum]; - // - for (i = 0; i < face->numedges; i++) - { - edgenum = aasworld.edgeindex[face->firstedge + i]; - edge = &aasworld.edges[abs(edgenum)]; - //get the first vertex of the edge - firstvertex = edgenum < 0; - v1 = aasworld.vertexes[edge->v[firstvertex]]; - v2 = aasworld.vertexes[edge->v[!firstvertex]]; - //edge vector - VectorSubtract(v2, v1, edgevec); - //vector from first edge point to point possible in face - VectorSubtract(point, v1, pointvec); - // - CrossProduct(edgevec, plane->normal, sepnormal); - // - if (DotProduct(pointvec, sepnormal) < -epsilon) return qfalse; - } //end for - return qtrue; -} //end of the function AAS_PointInsideFace -//=========================================================================== -// returns the ground face the given point is above in the given area -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -aas_face_t *AAS_AreaGroundFace(int areanum, vec3_t point) -{ - int i, facenum; - vec3_t up = {0, 0, 1}; - vec3_t normal; - aas_area_t *area; - aas_face_t *face; - - if (!aasworld.loaded) return NULL; - - area = &aasworld.areas[areanum]; - for (i = 0; i < area->numfaces; i++) - { - facenum = aasworld.faceindex[area->firstface + i]; - face = &aasworld.faces[abs(facenum)]; - //if this is a ground face - if (face->faceflags & FACE_GROUND) - { - //get the up or down normal - if (aasworld.planes[face->planenum].normal[2] < 0) VectorNegate(up, normal); - else VectorCopy(up, normal); - //check if the point is in the face - if (AAS_InsideFace(face, normal, point, 0.01f)) return face; - } //end if - } //end for - return NULL; -} //end of the function AAS_AreaGroundFace -//=========================================================================== -// returns the face the trace end position is situated in -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_FacePlane(int facenum, vec3_t normal, float *dist) -{ - aas_plane_t *plane; - - plane = &aasworld.planes[aasworld.faces[facenum].planenum]; - VectorCopy(plane->normal, normal); - *dist = plane->dist; -} //end of the function AAS_FacePlane -//=========================================================================== -// returns the face the trace end position is situated in -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -aas_face_t *AAS_TraceEndFace(aas_trace_t *trace) -{ - int i, facenum; - aas_area_t *area; - aas_face_t *face, *firstface = NULL; - - if (!aasworld.loaded) return NULL; - - //if started in solid no face was hit - if (trace->startsolid) return NULL; - //trace->lastarea is the last area the trace was in - area = &aasworld.areas[trace->lastarea]; - //check which face the trace.endpos was in - for (i = 0; i < area->numfaces; i++) - { - facenum = aasworld.faceindex[area->firstface + i]; - face = &aasworld.faces[abs(facenum)]; - //if the face is in the same plane as the trace end point - if ((face->planenum & ~1) == (trace->planenum & ~1)) - { - //firstface is used for optimization, if theres only one - //face in the plane then it has to be the good one - //if there are more faces in the same plane then always - //check the one with the fewest edges first -/* if (firstface) - { - if (firstface->numedges < face->numedges) - { - if (AAS_InsideFace(firstface, - aasworld.planes[face->planenum].normal, trace->endpos)) - { - return firstface; - } //end if - firstface = face; - } //end if - else - { - if (AAS_InsideFace(face, - aasworld.planes[face->planenum].normal, trace->endpos)) - { - return face; - } //end if - } //end else - } //end if - else - { - firstface = face; - } //end else*/ - if (AAS_InsideFace(face, - aasworld.planes[face->planenum].normal, trace->endpos, 0.01f)) return face; - } //end if - } //end for - return firstface; -} //end of the function AAS_TraceEndFace -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_BoxOnPlaneSide2(vec3_t absmins, vec3_t absmaxs, aas_plane_t *p) -{ - int i, sides; - float dist1, dist2; - vec3_t corners[2]; - - for (i = 0; i < 3; i++) - { - if (p->normal[i] < 0) - { - corners[0][i] = absmins[i]; - corners[1][i] = absmaxs[i]; - } //end if - else - { - corners[1][i] = absmins[i]; - corners[0][i] = absmaxs[i]; - } //end else - } //end for - dist1 = DotProduct(p->normal, corners[0]) - p->dist; - dist2 = DotProduct(p->normal, corners[1]) - p->dist; - sides = 0; - if (dist1 >= 0) sides = 1; - if (dist2 < 0) sides |= 2; - - return sides; -} //end of the function AAS_BoxOnPlaneSide2 -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -//int AAS_BoxOnPlaneSide(vec3_t absmins, vec3_t absmaxs, aas_plane_t *p) -#define AAS_BoxOnPlaneSide(absmins, absmaxs, p) (\ - ( (p)->type < 3) ?\ - (\ - ( (p)->dist <= (absmins)[(p)->type]) ?\ - (\ - 1\ - )\ - :\ - (\ - ( (p)->dist >= (absmaxs)[(p)->type]) ?\ - (\ - 2\ - )\ - :\ - (\ - 3\ - )\ - )\ - )\ - :\ - (\ - AAS_BoxOnPlaneSide2((absmins), (absmaxs), (p))\ - )\ -) //end of the function AAS_BoxOnPlaneSide -//=========================================================================== -// remove the links to this entity from all areas -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_UnlinkFromAreas(aas_link_t *areas) -{ - aas_link_t *link, *nextlink; - - for (link = areas; link; link = nextlink) - { - //next area the entity is linked in - nextlink = link->next_area; - //remove the entity from the linked list of this area - if (link->prev_ent) link->prev_ent->next_ent = link->next_ent; - else aasworld.arealinkedentities[link->areanum] = link->next_ent; - if (link->next_ent) link->next_ent->prev_ent = link->prev_ent; - //deallocate the link structure - AAS_DeAllocAASLink(link); - } //end for -} //end of the function AAS_UnlinkFromAreas -//=========================================================================== -// link the entity to the areas the bounding box is totally or partly -// situated in. This is done with recursion down the tree using the -// bounding box to test for plane sides -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== - -typedef struct -{ - int nodenum; //node found after splitting -} aas_linkstack_t; - -aas_link_t *AAS_AASLinkEntity(vec3_t absmins, vec3_t absmaxs, int entnum) -{ - int side, nodenum; - aas_linkstack_t linkstack[128]; - aas_linkstack_t *lstack_p; - aas_node_t *aasnode; - aas_plane_t *plane; - aas_link_t *link, *areas; - - if (!aasworld.loaded) - { - botimport.Print(PRT_ERROR, "AAS_LinkEntity: aas not loaded\n"); - return NULL; - } //end if - - areas = NULL; - // - lstack_p = linkstack; - //we start with the whole line on the stack - //start with node 1 because node zero is a dummy used for solid leafs - lstack_p->nodenum = 1; //starting at the root of the tree - lstack_p++; - - while (1) - { - //pop up the stack - lstack_p--; - //if the trace stack is empty (ended up with a piece of the - //line to be traced in an area) - if (lstack_p < linkstack) break; - //number of the current node to test the line against - nodenum = lstack_p->nodenum; - //if it is an area - if (nodenum < 0) - { - //NOTE: the entity might have already been linked into this area - // because several node children can point to the same area - for (link = aasworld.arealinkedentities[-nodenum]; link; link = link->next_ent) - { - if (link->entnum == entnum) break; - } //end for - if (link) continue; - // - link = AAS_AllocAASLink(); - if (!link) return areas; - link->entnum = entnum; - link->areanum = -nodenum; - //put the link into the double linked area list of the entity - link->prev_area = NULL; - link->next_area = areas; - if (areas) areas->prev_area = link; - areas = link; - //put the link into the double linked entity list of the area - link->prev_ent = NULL; - link->next_ent = aasworld.arealinkedentities[-nodenum]; - if (aasworld.arealinkedentities[-nodenum]) - aasworld.arealinkedentities[-nodenum]->prev_ent = link; - aasworld.arealinkedentities[-nodenum] = link; - // - continue; - } //end if - //if solid leaf - if (!nodenum) continue; - //the node to test against - aasnode = &aasworld.nodes[nodenum]; - //the current node plane - plane = &aasworld.planes[aasnode->planenum]; - //get the side(s) the box is situated relative to the plane - side = AAS_BoxOnPlaneSide2(absmins, absmaxs, plane); - //if on the front side of the node - if (side & 1) - { - lstack_p->nodenum = aasnode->children[0]; - lstack_p++; - } //end if - if (lstack_p >= &linkstack[127]) - { - botimport.Print(PRT_ERROR, "AAS_LinkEntity: stack overflow\n"); - break; - } //end if - //if on the back side of the node - if (side & 2) - { - lstack_p->nodenum = aasnode->children[1]; - lstack_p++; - } //end if - if (lstack_p >= &linkstack[127]) - { - botimport.Print(PRT_ERROR, "AAS_LinkEntity: stack overflow\n"); - break; - } //end if - } //end while - return areas; -} //end of the function AAS_AASLinkEntity -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -aas_link_t *AAS_LinkEntityClientBBox(vec3_t absmins, vec3_t absmaxs, int entnum, int presencetype) -{ - vec3_t mins, maxs; - vec3_t newabsmins, newabsmaxs; - - AAS_PresenceTypeBoundingBox(presencetype, mins, maxs); - VectorSubtract(absmins, maxs, newabsmins); - VectorSubtract(absmaxs, mins, newabsmaxs); - //relink the entity - return AAS_AASLinkEntity(newabsmins, newabsmaxs, entnum); -} //end of the function AAS_LinkEntityClientBBox -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_BBoxAreas(vec3_t absmins, vec3_t absmaxs, int *areas, int maxareas) -{ - aas_link_t *linkedareas, *link; - int num; - - linkedareas = AAS_AASLinkEntity(absmins, absmaxs, -1); - num = 0; - for (link = linkedareas; link; link = link->next_area) - { - areas[num] = link->areanum; - num++; - if (num >= maxareas) - break; - } //end for - AAS_UnlinkFromAreas(linkedareas); - return num; -} //end of the function AAS_BBoxAreas -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_AreaInfo( int areanum, aas_areainfo_t *info ) -{ - aas_areasettings_t *settings; - if (!info) - return 0; - if (areanum <= 0 || areanum >= aasworld.numareas) - { - botimport.Print(PRT_ERROR, "AAS_AreaInfo: areanum %d out of range\n", areanum); - return 0; - } //end if - settings = &aasworld.areasettings[areanum]; - info->cluster = settings->cluster; - info->contents = settings->contents; - info->flags = settings->areaflags; - info->presencetype = settings->presencetype; - VectorCopy(aasworld.areas[areanum].mins, info->mins); - VectorCopy(aasworld.areas[areanum].maxs, info->maxs); - VectorCopy(aasworld.areas[areanum].center, info->center); - return sizeof(aas_areainfo_t); -} //end of the function AAS_AreaInfo -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -aas_plane_t *AAS_PlaneFromNum(int planenum) -{ - if (!aasworld.loaded) return 0; - - return &aasworld.planes[planenum]; -} //end of the function AAS_PlaneFromNum +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_sample.c + * + * desc: AAS environment sampling + * + * $Archive: /MissionPack/code/botlib/be_aas_sample.c $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#ifndef BSPC +#include "l_libvar.h" +#endif +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_interface.h" +#include "be_aas_funcs.h" +#include "be_aas_def.h" + +extern botlib_import_t botimport; + +//#define AAS_SAMPLE_DEBUG + +#define BBOX_NORMAL_EPSILON 0.001 + +#define ON_EPSILON 0 //0.0005 + +#define TRACEPLANE_EPSILON 0.125 + +typedef struct aas_tracestack_s +{ + vec3_t start; //start point of the piece of line to trace + vec3_t end; //end point of the piece of line to trace + int planenum; //last plane used as splitter + int nodenum; //node found after splitting with planenum +} aas_tracestack_t; + +int numaaslinks; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_PresenceTypeBoundingBox(int presencetype, vec3_t mins, vec3_t maxs) +{ + int index; + //bounding box size for each presence type + vec3_t boxmins[3] = {{0, 0, 0}, {-15, -15, -24}, {-15, -15, -24}}; + vec3_t boxmaxs[3] = {{0, 0, 0}, { 15, 15, 32}, { 15, 15, 8}}; + + if (presencetype == PRESENCE_NORMAL) index = 1; + else if (presencetype == PRESENCE_CROUCH) index = 2; + else + { + botimport.Print(PRT_FATAL, "AAS_PresenceTypeBoundingBox: unknown presence type\n"); + index = 2; + } //end if + VectorCopy(boxmins[index], mins); + VectorCopy(boxmaxs[index], maxs); +} //end of the function AAS_PresenceTypeBoundingBox +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitAASLinkHeap(void) +{ + int i, max_aaslinks; + + max_aaslinks = aasworld.linkheapsize; + //if there's no link heap present + if (!aasworld.linkheap) + { +#ifdef BSPC + max_aaslinks = 6144; +#else + max_aaslinks = (int) LibVarValue("max_aaslinks", "6144"); +#endif + if (max_aaslinks < 0) max_aaslinks = 0; + aasworld.linkheapsize = max_aaslinks; + aasworld.linkheap = (aas_link_t *) GetHunkMemory(max_aaslinks * sizeof(aas_link_t)); + } //end if + //link the links on the heap + aasworld.linkheap[0].prev_ent = NULL; + aasworld.linkheap[0].next_ent = &aasworld.linkheap[1]; + for (i = 1; i < max_aaslinks-1; i++) + { + aasworld.linkheap[i].prev_ent = &aasworld.linkheap[i - 1]; + aasworld.linkheap[i].next_ent = &aasworld.linkheap[i + 1]; + } //end for + aasworld.linkheap[max_aaslinks-1].prev_ent = &aasworld.linkheap[max_aaslinks-2]; + aasworld.linkheap[max_aaslinks-1].next_ent = NULL; + //pointer to the first free link + aasworld.freelinks = &aasworld.linkheap[0]; + // + numaaslinks = max_aaslinks; +} //end of the function AAS_InitAASLinkHeap +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeAASLinkHeap(void) +{ + if (aasworld.linkheap) FreeMemory(aasworld.linkheap); + aasworld.linkheap = NULL; + aasworld.linkheapsize = 0; +} //end of the function AAS_FreeAASLinkHeap +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_link_t *AAS_AllocAASLink(void) +{ + aas_link_t *link; + + link = aasworld.freelinks; + if (!link) + { +#ifndef BSPC + if (bot_developer) +#endif + { + botimport.Print(PRT_FATAL, "empty aas link heap\n"); + } //end if + return NULL; + } //end if + if (aasworld.freelinks) aasworld.freelinks = aasworld.freelinks->next_ent; + if (aasworld.freelinks) aasworld.freelinks->prev_ent = NULL; + numaaslinks--; + return link; +} //end of the function AAS_AllocAASLink +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DeAllocAASLink(aas_link_t *link) +{ + if (aasworld.freelinks) aasworld.freelinks->prev_ent = link; + link->prev_ent = NULL; + link->next_ent = aasworld.freelinks; + link->prev_area = NULL; + link->next_area = NULL; + aasworld.freelinks = link; + numaaslinks++; +} //end of the function AAS_DeAllocAASLink +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitAASLinkedEntities(void) +{ + if (!aasworld.loaded) return; + if (aasworld.arealinkedentities) FreeMemory(aasworld.arealinkedentities); + aasworld.arealinkedentities = (aas_link_t **) GetClearedHunkMemory( + aasworld.numareas * sizeof(aas_link_t *)); +} //end of the function AAS_InitAASLinkedEntities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeAASLinkedEntities(void) +{ + if (aasworld.arealinkedentities) FreeMemory(aasworld.arealinkedentities); + aasworld.arealinkedentities = NULL; +} //end of the function AAS_InitAASLinkedEntities +//=========================================================================== +// returns the AAS area the point is in +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_PointAreaNum(vec3_t point) +{ + int nodenum; + vec_t dist; + aas_node_t *node; + aas_plane_t *plane; + + if (!aasworld.loaded) + { + botimport.Print(PRT_ERROR, "AAS_PointAreaNum: aas not loaded\n"); + return 0; + } //end if + + //start with node 1 because node zero is a dummy used for solid leafs + nodenum = 1; + while (nodenum > 0) + { +// botimport.Print(PRT_MESSAGE, "[%d]", nodenum); +#ifdef AAS_SAMPLE_DEBUG + if (nodenum >= aasworld.numnodes) + { + botimport.Print(PRT_ERROR, "nodenum = %d >= aasworld.numnodes = %d\n", nodenum, aasworld.numnodes); + return 0; + } //end if +#endif //AAS_SAMPLE_DEBUG + node = &aasworld.nodes[nodenum]; +#ifdef AAS_SAMPLE_DEBUG + if (node->planenum < 0 || node->planenum >= aasworld.numplanes) + { + botimport.Print(PRT_ERROR, "node->planenum = %d >= aasworld.numplanes = %d\n", node->planenum, aasworld.numplanes); + return 0; + } //end if +#endif //AAS_SAMPLE_DEBUG + plane = &aasworld.planes[node->planenum]; + dist = DotProduct(point, plane->normal) - plane->dist; + if (dist > 0) nodenum = node->children[0]; + else nodenum = node->children[1]; + } //end while + if (!nodenum) + { +#ifdef AAS_SAMPLE_DEBUG + botimport.Print(PRT_MESSAGE, "in solid\n"); +#endif //AAS_SAMPLE_DEBUG + return 0; + } //end if + return -nodenum; +} //end of the function AAS_PointAreaNum +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_PointReachabilityAreaIndex( vec3_t origin ) +{ + int areanum, cluster, i, index; + + if (!aasworld.initialized) + return 0; + + if ( !origin ) + { + index = 0; + for (i = 0; i < aasworld.numclusters; i++) + { + index += aasworld.clusters[i].numreachabilityareas; + } //end for + return index; + } //end if + + areanum = AAS_PointAreaNum( origin ); + if ( !areanum || !AAS_AreaReachability(areanum) ) + return 0; + cluster = aasworld.areasettings[areanum].cluster; + areanum = aasworld.areasettings[areanum].clusterareanum; + if (cluster < 0) + { + cluster = aasworld.portals[-cluster].frontcluster; + areanum = aasworld.portals[-cluster].clusterareanum[0]; + } //end if + + index = 0; + for (i = 0; i < cluster; i++) + { + index += aasworld.clusters[i].numreachabilityareas; + } //end for + index += areanum; + return index; +} //end of the function AAS_PointReachabilityAreaIndex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaCluster(int areanum) +{ + if (areanum <= 0 || areanum >= aasworld.numareas) + { + botimport.Print(PRT_ERROR, "AAS_AreaCluster: invalid area number\n"); + return 0; + } //end if + return aasworld.areasettings[areanum].cluster; +} //end of the function AAS_AreaCluster +//=========================================================================== +// returns the presence types of the given area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaPresenceType(int areanum) +{ + if (!aasworld.loaded) return 0; + if (areanum <= 0 || areanum >= aasworld.numareas) + { + botimport.Print(PRT_ERROR, "AAS_AreaPresenceType: invalid area number\n"); + return 0; + } //end if + return aasworld.areasettings[areanum].presencetype; +} //end of the function AAS_AreaPresenceType +//=========================================================================== +// returns the presence type at the given point +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_PointPresenceType(vec3_t point) +{ + int areanum; + + if (!aasworld.loaded) return 0; + + areanum = AAS_PointAreaNum(point); + if (!areanum) return PRESENCE_NONE; + return aasworld.areasettings[areanum].presencetype; +} //end of the function AAS_PointPresenceType +//=========================================================================== +// calculates the minimum distance between the origin of the box and the +// given plane when both will collide on the given side of the plane +// +// normal = normal vector of plane to calculate distance from +// mins = minimums of box relative to origin +// maxs = maximums of box relative to origin +// side = side of the plane we want to calculate the distance from +// 0 normal vector side +// 1 not normal vector side +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +vec_t AAS_BoxOriginDistanceFromPlane(vec3_t normal, vec3_t mins, vec3_t maxs, int side) +{ + vec3_t v1, v2; + int i; + + //swap maxs and mins when on the other side of the plane + if (side) + { + //get a point of the box that would be one of the first + //to collide with the plane + for (i = 0; i < 3; i++) + { + if (normal[i] > BBOX_NORMAL_EPSILON) v1[i] = maxs[i]; + else if (normal[i] < -BBOX_NORMAL_EPSILON) v1[i] = mins[i]; + else v1[i] = 0; + } //end for + } //end if + else + { + //get a point of the box that would be one of the first + //to collide with the plane + for (i = 0; i < 3; i++) + { + if (normal[i] > BBOX_NORMAL_EPSILON) v1[i] = mins[i]; + else if (normal[i] < -BBOX_NORMAL_EPSILON) v1[i] = maxs[i]; + else v1[i] = 0; + } //end for + } //end else + // + VectorCopy(normal, v2); + VectorInverse(v2); +// VectorNegate(normal, v2); + return DotProduct(v1, v2); +} //end of the function AAS_BoxOriginDistanceFromPlane +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_AreaEntityCollision(int areanum, vec3_t start, vec3_t end, + int presencetype, int passent, aas_trace_t *trace) +{ + int collision; + vec3_t boxmins, boxmaxs; + aas_link_t *link; + bsp_trace_t bsptrace; + + AAS_PresenceTypeBoundingBox(presencetype, boxmins, boxmaxs); + + Com_Memset(&bsptrace, 0, sizeof(bsp_trace_t)); //make compiler happy + //assume no collision + bsptrace.fraction = 1; + collision = qfalse; + for (link = aasworld.arealinkedentities[areanum]; link; link = link->next_ent) + { + //ignore the pass entity + if (link->entnum == passent) continue; + // + if (AAS_EntityCollision(link->entnum, start, boxmins, boxmaxs, end, + CONTENTS_SOLID|CONTENTS_PLAYERCLIP, &bsptrace)) + { + collision = qtrue; + } //end if + } //end for + if (collision) + { + trace->startsolid = bsptrace.startsolid; + trace->ent = bsptrace.ent; + VectorCopy(bsptrace.endpos, trace->endpos); + trace->area = 0; + trace->planenum = 0; + return qtrue; + } //end if + return qfalse; +} //end of the function AAS_AreaEntityCollision +//=========================================================================== +// recursive subdivision of the line by the BSP tree. +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_trace_t AAS_TraceClientBBox(vec3_t start, vec3_t end, int presencetype, + int passent) +{ + int side, nodenum, tmpplanenum; + float front, back, frac; + vec3_t cur_start, cur_end, cur_mid, v1, v2; + aas_tracestack_t tracestack[127]; + aas_tracestack_t *tstack_p; + aas_node_t *aasnode; + aas_plane_t *plane; + aas_trace_t trace; + + //clear the trace structure + Com_Memset(&trace, 0, sizeof(aas_trace_t)); + + if (!aasworld.loaded) return trace; + + tstack_p = tracestack; + //we start with the whole line on the stack + VectorCopy(start, tstack_p->start); + VectorCopy(end, tstack_p->end); + tstack_p->planenum = 0; + //start with node 1 because node zero is a dummy for a solid leaf + tstack_p->nodenum = 1; //starting at the root of the tree + tstack_p++; + + while (1) + { + //pop up the stack + tstack_p--; + //if the trace stack is empty (ended up with a piece of the + //line to be traced in an area) + if (tstack_p < tracestack) + { + tstack_p++; + //nothing was hit + trace.startsolid = qfalse; + trace.fraction = 1.0; + //endpos is the end of the line + VectorCopy(end, trace.endpos); + //nothing hit + trace.ent = 0; + trace.area = 0; + trace.planenum = 0; + return trace; + } //end if + //number of the current node to test the line against + nodenum = tstack_p->nodenum; + //if it is an area + if (nodenum < 0) + { +#ifdef AAS_SAMPLE_DEBUG + if (-nodenum > aasworld.numareasettings) + { + botimport.Print(PRT_ERROR, "AAS_TraceBoundingBox: -nodenum out of range\n"); + return trace; + } //end if +#endif //AAS_SAMPLE_DEBUG + //botimport.Print(PRT_MESSAGE, "areanum = %d, must be %d\n", -nodenum, AAS_PointAreaNum(start)); + //if can't enter the area because it hasn't got the right presence type + if (!(aasworld.areasettings[-nodenum].presencetype & presencetype)) + { + //if the start point is still the initial start point + //NOTE: no need for epsilons because the points will be + //exactly the same when they're both the start point + if (tstack_p->start[0] == start[0] && + tstack_p->start[1] == start[1] && + tstack_p->start[2] == start[2]) + { + trace.startsolid = qtrue; + trace.fraction = 0.0; + VectorClear(v1); + } //end if + else + { + trace.startsolid = qfalse; + VectorSubtract(end, start, v1); + VectorSubtract(tstack_p->start, start, v2); + trace.fraction = VectorLength(v2) / VectorNormalize(v1); + VectorMA(tstack_p->start, -0.125, v1, tstack_p->start); + } //end else + VectorCopy(tstack_p->start, trace.endpos); + trace.ent = 0; + trace.area = -nodenum; +// VectorSubtract(end, start, v1); + trace.planenum = tstack_p->planenum; + //always take the plane with normal facing towards the trace start + plane = &aasworld.planes[trace.planenum]; + if (DotProduct(v1, plane->normal) > 0) trace.planenum ^= 1; + return trace; + } //end if + else + { + if (passent >= 0) + { + if (AAS_AreaEntityCollision(-nodenum, tstack_p->start, + tstack_p->end, presencetype, passent, + &trace)) + { + if (!trace.startsolid) + { + VectorSubtract(end, start, v1); + VectorSubtract(trace.endpos, start, v2); + trace.fraction = VectorLength(v2) / VectorLength(v1); + } //end if + return trace; + } //end if + } //end if + } //end else + trace.lastarea = -nodenum; + continue; + } //end if + //if it is a solid leaf + if (!nodenum) + { + //if the start point is still the initial start point + //NOTE: no need for epsilons because the points will be + //exactly the same when they're both the start point + if (tstack_p->start[0] == start[0] && + tstack_p->start[1] == start[1] && + tstack_p->start[2] == start[2]) + { + trace.startsolid = qtrue; + trace.fraction = 0.0; + VectorClear(v1); + } //end if + else + { + trace.startsolid = qfalse; + VectorSubtract(end, start, v1); + VectorSubtract(tstack_p->start, start, v2); + trace.fraction = VectorLength(v2) / VectorNormalize(v1); + VectorMA(tstack_p->start, -0.125, v1, tstack_p->start); + } //end else + VectorCopy(tstack_p->start, trace.endpos); + trace.ent = 0; + trace.area = 0; //hit solid leaf +// VectorSubtract(end, start, v1); + trace.planenum = tstack_p->planenum; + //always take the plane with normal facing towards the trace start + plane = &aasworld.planes[trace.planenum]; + if (DotProduct(v1, plane->normal) > 0) trace.planenum ^= 1; + return trace; + } //end if +#ifdef AAS_SAMPLE_DEBUG + if (nodenum > aasworld.numnodes) + { + botimport.Print(PRT_ERROR, "AAS_TraceBoundingBox: nodenum out of range\n"); + return trace; + } //end if +#endif //AAS_SAMPLE_DEBUG + //the node to test against + aasnode = &aasworld.nodes[nodenum]; + //start point of current line to test against node + VectorCopy(tstack_p->start, cur_start); + //end point of the current line to test against node + VectorCopy(tstack_p->end, cur_end); + //the current node plane + plane = &aasworld.planes[aasnode->planenum]; + + switch(plane->type) + {/*FIXME: wtf doesn't this work? obviously the axial node planes aren't always facing positive!!! + //check for axial planes + case PLANE_X: + { + front = cur_start[0] - plane->dist; + back = cur_end[0] - plane->dist; + break; + } //end case + case PLANE_Y: + { + front = cur_start[1] - plane->dist; + back = cur_end[1] - plane->dist; + break; + } //end case + case PLANE_Z: + { + front = cur_start[2] - plane->dist; + back = cur_end[2] - plane->dist; + break; + } //end case*/ + default: //gee it's not an axial plane + { + front = DotProduct(cur_start, plane->normal) - plane->dist; + back = DotProduct(cur_end, plane->normal) - plane->dist; + break; + } //end default + } //end switch + // bk010221 - old location of FPE hack and divide by zero expression + //if the whole to be traced line is totally at the front of this node + //only go down the tree with the front child + if ((front >= -ON_EPSILON && back >= -ON_EPSILON)) + { + //keep the current start and end point on the stack + //and go down the tree with the front child + tstack_p->nodenum = aasnode->children[0]; + tstack_p++; + if (tstack_p >= &tracestack[127]) + { + botimport.Print(PRT_ERROR, "AAS_TraceBoundingBox: stack overflow\n"); + return trace; + } //end if + } //end if + //if the whole to be traced line is totally at the back of this node + //only go down the tree with the back child + else if ((front < ON_EPSILON && back < ON_EPSILON)) + { + //keep the current start and end point on the stack + //and go down the tree with the back child + tstack_p->nodenum = aasnode->children[1]; + tstack_p++; + if (tstack_p >= &tracestack[127]) + { + botimport.Print(PRT_ERROR, "AAS_TraceBoundingBox: stack overflow\n"); + return trace; + } //end if + } //end if + //go down the tree both at the front and back of the node + else + { + tmpplanenum = tstack_p->planenum; + // bk010221 - new location of divide by zero (see above) + if ( front == back ) front -= 0.001f; // bk0101022 - hack/FPE + //calculate the hitpoint with the node (split point of the line) + //put the crosspoint TRACEPLANE_EPSILON pixels on the near side + if (front < 0) frac = (front + TRACEPLANE_EPSILON)/(front-back); + else frac = (front - TRACEPLANE_EPSILON)/(front-back); // bk010221 + // + if (frac < 0) + frac = 0.001f; //0 + else if (frac > 1) + frac = 0.999f; //1 + //frac = front / (front-back); + // + cur_mid[0] = cur_start[0] + (cur_end[0] - cur_start[0]) * frac; + cur_mid[1] = cur_start[1] + (cur_end[1] - cur_start[1]) * frac; + cur_mid[2] = cur_start[2] + (cur_end[2] - cur_start[2]) * frac; + +// AAS_DrawPlaneCross(cur_mid, plane->normal, plane->dist, plane->type, LINECOLOR_RED); + //side the front part of the line is on + side = front < 0; + //first put the end part of the line on the stack (back side) + VectorCopy(cur_mid, tstack_p->start); + //not necesary to store because still on stack + //VectorCopy(cur_end, tstack_p->end); + tstack_p->planenum = aasnode->planenum; + tstack_p->nodenum = aasnode->children[!side]; + tstack_p++; + if (tstack_p >= &tracestack[127]) + { + botimport.Print(PRT_ERROR, "AAS_TraceBoundingBox: stack overflow\n"); + return trace; + } //end if + //now put the part near the start of the line on the stack so we will + //continue with thats part first. This way we'll find the first + //hit of the bbox + VectorCopy(cur_start, tstack_p->start); + VectorCopy(cur_mid, tstack_p->end); + tstack_p->planenum = tmpplanenum; + tstack_p->nodenum = aasnode->children[side]; + tstack_p++; + if (tstack_p >= &tracestack[127]) + { + botimport.Print(PRT_ERROR, "AAS_TraceBoundingBox: stack overflow\n"); + return trace; + } //end if + } //end else + } //end while +// return trace; +} //end of the function AAS_TraceClientBBox +//=========================================================================== +// recursive subdivision of the line by the BSP tree. +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_TraceAreas(vec3_t start, vec3_t end, int *areas, vec3_t *points, int maxareas) +{ + int side, nodenum, tmpplanenum; + int numareas; + float front, back, frac; + vec3_t cur_start, cur_end, cur_mid; + aas_tracestack_t tracestack[127]; + aas_tracestack_t *tstack_p; + aas_node_t *aasnode; + aas_plane_t *plane; + + numareas = 0; + areas[0] = 0; + if (!aasworld.loaded) return numareas; + + tstack_p = tracestack; + //we start with the whole line on the stack + VectorCopy(start, tstack_p->start); + VectorCopy(end, tstack_p->end); + tstack_p->planenum = 0; + //start with node 1 because node zero is a dummy for a solid leaf + tstack_p->nodenum = 1; //starting at the root of the tree + tstack_p++; + + while (1) + { + //pop up the stack + tstack_p--; + //if the trace stack is empty (ended up with a piece of the + //line to be traced in an area) + if (tstack_p < tracestack) + { + return numareas; + } //end if + //number of the current node to test the line against + nodenum = tstack_p->nodenum; + //if it is an area + if (nodenum < 0) + { +#ifdef AAS_SAMPLE_DEBUG + if (-nodenum > aasworld.numareasettings) + { + botimport.Print(PRT_ERROR, "AAS_TraceAreas: -nodenum = %d out of range\n", -nodenum); + return numareas; + } //end if +#endif //AAS_SAMPLE_DEBUG + //botimport.Print(PRT_MESSAGE, "areanum = %d, must be %d\n", -nodenum, AAS_PointAreaNum(start)); + areas[numareas] = -nodenum; + if (points) VectorCopy(tstack_p->start, points[numareas]); + numareas++; + if (numareas >= maxareas) return numareas; + continue; + } //end if + //if it is a solid leaf + if (!nodenum) + { + continue; + } //end if +#ifdef AAS_SAMPLE_DEBUG + if (nodenum > aasworld.numnodes) + { + botimport.Print(PRT_ERROR, "AAS_TraceAreas: nodenum out of range\n"); + return numareas; + } //end if +#endif //AAS_SAMPLE_DEBUG + //the node to test against + aasnode = &aasworld.nodes[nodenum]; + //start point of current line to test against node + VectorCopy(tstack_p->start, cur_start); + //end point of the current line to test against node + VectorCopy(tstack_p->end, cur_end); + //the current node plane + plane = &aasworld.planes[aasnode->planenum]; + + switch(plane->type) + {/*FIXME: wtf doesn't this work? obviously the node planes aren't always facing positive!!! + //check for axial planes + case PLANE_X: + { + front = cur_start[0] - plane->dist; + back = cur_end[0] - plane->dist; + break; + } //end case + case PLANE_Y: + { + front = cur_start[1] - plane->dist; + back = cur_end[1] - plane->dist; + break; + } //end case + case PLANE_Z: + { + front = cur_start[2] - plane->dist; + back = cur_end[2] - plane->dist; + break; + } //end case*/ + default: //gee it's not an axial plane + { + front = DotProduct(cur_start, plane->normal) - plane->dist; + back = DotProduct(cur_end, plane->normal) - plane->dist; + break; + } //end default + } //end switch + + //if the whole to be traced line is totally at the front of this node + //only go down the tree with the front child + if (front > 0 && back > 0) + { + //keep the current start and end point on the stack + //and go down the tree with the front child + tstack_p->nodenum = aasnode->children[0]; + tstack_p++; + if (tstack_p >= &tracestack[127]) + { + botimport.Print(PRT_ERROR, "AAS_TraceAreas: stack overflow\n"); + return numareas; + } //end if + } //end if + //if the whole to be traced line is totally at the back of this node + //only go down the tree with the back child + else if (front <= 0 && back <= 0) + { + //keep the current start and end point on the stack + //and go down the tree with the back child + tstack_p->nodenum = aasnode->children[1]; + tstack_p++; + if (tstack_p >= &tracestack[127]) + { + botimport.Print(PRT_ERROR, "AAS_TraceAreas: stack overflow\n"); + return numareas; + } //end if + } //end if + //go down the tree both at the front and back of the node + else + { + tmpplanenum = tstack_p->planenum; + //calculate the hitpoint with the node (split point of the line) + //put the crosspoint TRACEPLANE_EPSILON pixels on the near side + if (front < 0) frac = (front)/(front-back); + else frac = (front)/(front-back); + if (frac < 0) frac = 0; + else if (frac > 1) frac = 1; + //frac = front / (front-back); + // + cur_mid[0] = cur_start[0] + (cur_end[0] - cur_start[0]) * frac; + cur_mid[1] = cur_start[1] + (cur_end[1] - cur_start[1]) * frac; + cur_mid[2] = cur_start[2] + (cur_end[2] - cur_start[2]) * frac; + +// AAS_DrawPlaneCross(cur_mid, plane->normal, plane->dist, plane->type, LINECOLOR_RED); + //side the front part of the line is on + side = front < 0; + //first put the end part of the line on the stack (back side) + VectorCopy(cur_mid, tstack_p->start); + //not necesary to store because still on stack + //VectorCopy(cur_end, tstack_p->end); + tstack_p->planenum = aasnode->planenum; + tstack_p->nodenum = aasnode->children[!side]; + tstack_p++; + if (tstack_p >= &tracestack[127]) + { + botimport.Print(PRT_ERROR, "AAS_TraceAreas: stack overflow\n"); + return numareas; + } //end if + //now put the part near the start of the line on the stack so we will + //continue with thats part first. This way we'll find the first + //hit of the bbox + VectorCopy(cur_start, tstack_p->start); + VectorCopy(cur_mid, tstack_p->end); + tstack_p->planenum = tmpplanenum; + tstack_p->nodenum = aasnode->children[side]; + tstack_p++; + if (tstack_p >= &tracestack[127]) + { + botimport.Print(PRT_ERROR, "AAS_TraceAreas: stack overflow\n"); + return numareas; + } //end if + } //end else + } //end while +// return numareas; +} //end of the function AAS_TraceAreas +//=========================================================================== +// a simple cross product +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +// void AAS_OrthogonalToVectors(vec3_t v1, vec3_t v2, vec3_t res) +#define AAS_OrthogonalToVectors(v1, v2, res) \ + (res)[0] = ((v1)[1] * (v2)[2]) - ((v1)[2] * (v2)[1]);\ + (res)[1] = ((v1)[2] * (v2)[0]) - ((v1)[0] * (v2)[2]);\ + (res)[2] = ((v1)[0] * (v2)[1]) - ((v1)[1] * (v2)[0]); +//=========================================================================== +// tests if the given point is within the face boundaries +// +// Parameter: face : face to test if the point is in it +// pnormal : normal of the plane to use for the face +// point : point to test if inside face boundaries +// Returns: qtrue if the point is within the face boundaries +// Changes Globals: - +//=========================================================================== +qboolean AAS_InsideFace(aas_face_t *face, vec3_t pnormal, vec3_t point, float epsilon) +{ + int i, firstvertex, edgenum; + vec3_t v0; + vec3_t edgevec, pointvec, sepnormal; + aas_edge_t *edge; +#ifdef AAS_SAMPLE_DEBUG + int lastvertex = 0; +#endif //AAS_SAMPLE_DEBUG + + if (!aasworld.loaded) return qfalse; + + for (i = 0; i < face->numedges; i++) + { + edgenum = aasworld.edgeindex[face->firstedge + i]; + edge = &aasworld.edges[abs(edgenum)]; + //get the first vertex of the edge + firstvertex = edgenum < 0; + VectorCopy(aasworld.vertexes[edge->v[firstvertex]], v0); + //edge vector + VectorSubtract(aasworld.vertexes[edge->v[!firstvertex]], v0, edgevec); + // +#ifdef AAS_SAMPLE_DEBUG + if (lastvertex && lastvertex != edge->v[firstvertex]) + { + botimport.Print(PRT_MESSAGE, "winding not counter clockwise\n"); + } //end if + lastvertex = edge->v[!firstvertex]; +#endif //AAS_SAMPLE_DEBUG + //vector from first edge point to point possible in face + VectorSubtract(point, v0, pointvec); + //get a vector pointing inside the face orthogonal to both the + //edge vector and the normal vector of the plane the face is in + //this vector defines a plane through the origin (first vertex of + //edge) and through both the edge vector and the normal vector + //of the plane + AAS_OrthogonalToVectors(edgevec, pnormal, sepnormal); + //check on wich side of the above plane the point is + //this is done by checking the sign of the dot product of the + //vector orthogonal vector from above and the vector from the + //origin (first vertex of edge) to the point + //if the dotproduct is smaller than zero the point is outside the face + if (DotProduct(pointvec, sepnormal) < -epsilon) return qfalse; + } //end for + return qtrue; +} //end of the function AAS_InsideFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_PointInsideFace(int facenum, vec3_t point, float epsilon) +{ + int i, firstvertex, edgenum; + vec_t *v1, *v2; + vec3_t edgevec, pointvec, sepnormal; + aas_edge_t *edge; + aas_plane_t *plane; + aas_face_t *face; + + if (!aasworld.loaded) return qfalse; + + face = &aasworld.faces[facenum]; + plane = &aasworld.planes[face->planenum]; + // + for (i = 0; i < face->numedges; i++) + { + edgenum = aasworld.edgeindex[face->firstedge + i]; + edge = &aasworld.edges[abs(edgenum)]; + //get the first vertex of the edge + firstvertex = edgenum < 0; + v1 = aasworld.vertexes[edge->v[firstvertex]]; + v2 = aasworld.vertexes[edge->v[!firstvertex]]; + //edge vector + VectorSubtract(v2, v1, edgevec); + //vector from first edge point to point possible in face + VectorSubtract(point, v1, pointvec); + // + CrossProduct(edgevec, plane->normal, sepnormal); + // + if (DotProduct(pointvec, sepnormal) < -epsilon) return qfalse; + } //end for + return qtrue; +} //end of the function AAS_PointInsideFace +//=========================================================================== +// returns the ground face the given point is above in the given area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_face_t *AAS_AreaGroundFace(int areanum, vec3_t point) +{ + int i, facenum; + vec3_t up = {0, 0, 1}; + vec3_t normal; + aas_area_t *area; + aas_face_t *face; + + if (!aasworld.loaded) return NULL; + + area = &aasworld.areas[areanum]; + for (i = 0; i < area->numfaces; i++) + { + facenum = aasworld.faceindex[area->firstface + i]; + face = &aasworld.faces[abs(facenum)]; + //if this is a ground face + if (face->faceflags & FACE_GROUND) + { + //get the up or down normal + if (aasworld.planes[face->planenum].normal[2] < 0) VectorNegate(up, normal); + else VectorCopy(up, normal); + //check if the point is in the face + if (AAS_InsideFace(face, normal, point, 0.01f)) return face; + } //end if + } //end for + return NULL; +} //end of the function AAS_AreaGroundFace +//=========================================================================== +// returns the face the trace end position is situated in +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FacePlane(int facenum, vec3_t normal, float *dist) +{ + aas_plane_t *plane; + + plane = &aasworld.planes[aasworld.faces[facenum].planenum]; + VectorCopy(plane->normal, normal); + *dist = plane->dist; +} //end of the function AAS_FacePlane +//=========================================================================== +// returns the face the trace end position is situated in +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_face_t *AAS_TraceEndFace(aas_trace_t *trace) +{ + int i, facenum; + aas_area_t *area; + aas_face_t *face, *firstface = NULL; + + if (!aasworld.loaded) return NULL; + + //if started in solid no face was hit + if (trace->startsolid) return NULL; + //trace->lastarea is the last area the trace was in + area = &aasworld.areas[trace->lastarea]; + //check which face the trace.endpos was in + for (i = 0; i < area->numfaces; i++) + { + facenum = aasworld.faceindex[area->firstface + i]; + face = &aasworld.faces[abs(facenum)]; + //if the face is in the same plane as the trace end point + if ((face->planenum & ~1) == (trace->planenum & ~1)) + { + //firstface is used for optimization, if theres only one + //face in the plane then it has to be the good one + //if there are more faces in the same plane then always + //check the one with the fewest edges first +/* if (firstface) + { + if (firstface->numedges < face->numedges) + { + if (AAS_InsideFace(firstface, + aasworld.planes[face->planenum].normal, trace->endpos)) + { + return firstface; + } //end if + firstface = face; + } //end if + else + { + if (AAS_InsideFace(face, + aasworld.planes[face->planenum].normal, trace->endpos)) + { + return face; + } //end if + } //end else + } //end if + else + { + firstface = face; + } //end else*/ + if (AAS_InsideFace(face, + aasworld.planes[face->planenum].normal, trace->endpos, 0.01f)) return face; + } //end if + } //end for + return firstface; +} //end of the function AAS_TraceEndFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BoxOnPlaneSide2(vec3_t absmins, vec3_t absmaxs, aas_plane_t *p) +{ + int i, sides; + float dist1, dist2; + vec3_t corners[2]; + + for (i = 0; i < 3; i++) + { + if (p->normal[i] < 0) + { + corners[0][i] = absmins[i]; + corners[1][i] = absmaxs[i]; + } //end if + else + { + corners[1][i] = absmins[i]; + corners[0][i] = absmaxs[i]; + } //end else + } //end for + dist1 = DotProduct(p->normal, corners[0]) - p->dist; + dist2 = DotProduct(p->normal, corners[1]) - p->dist; + sides = 0; + if (dist1 >= 0) sides = 1; + if (dist2 < 0) sides |= 2; + + return sides; +} //end of the function AAS_BoxOnPlaneSide2 +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +//int AAS_BoxOnPlaneSide(vec3_t absmins, vec3_t absmaxs, aas_plane_t *p) +#define AAS_BoxOnPlaneSide(absmins, absmaxs, p) (\ + ( (p)->type < 3) ?\ + (\ + ( (p)->dist <= (absmins)[(p)->type]) ?\ + (\ + 1\ + )\ + :\ + (\ + ( (p)->dist >= (absmaxs)[(p)->type]) ?\ + (\ + 2\ + )\ + :\ + (\ + 3\ + )\ + )\ + )\ + :\ + (\ + AAS_BoxOnPlaneSide2((absmins), (absmaxs), (p))\ + )\ +) //end of the function AAS_BoxOnPlaneSide +//=========================================================================== +// remove the links to this entity from all areas +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_UnlinkFromAreas(aas_link_t *areas) +{ + aas_link_t *link, *nextlink; + + for (link = areas; link; link = nextlink) + { + //next area the entity is linked in + nextlink = link->next_area; + //remove the entity from the linked list of this area + if (link->prev_ent) link->prev_ent->next_ent = link->next_ent; + else aasworld.arealinkedentities[link->areanum] = link->next_ent; + if (link->next_ent) link->next_ent->prev_ent = link->prev_ent; + //deallocate the link structure + AAS_DeAllocAASLink(link); + } //end for +} //end of the function AAS_UnlinkFromAreas +//=========================================================================== +// link the entity to the areas the bounding box is totally or partly +// situated in. This is done with recursion down the tree using the +// bounding box to test for plane sides +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== + +typedef struct +{ + int nodenum; //node found after splitting +} aas_linkstack_t; + +aas_link_t *AAS_AASLinkEntity(vec3_t absmins, vec3_t absmaxs, int entnum) +{ + int side, nodenum; + aas_linkstack_t linkstack[128]; + aas_linkstack_t *lstack_p; + aas_node_t *aasnode; + aas_plane_t *plane; + aas_link_t *link, *areas; + + if (!aasworld.loaded) + { + botimport.Print(PRT_ERROR, "AAS_LinkEntity: aas not loaded\n"); + return NULL; + } //end if + + areas = NULL; + // + lstack_p = linkstack; + //we start with the whole line on the stack + //start with node 1 because node zero is a dummy used for solid leafs + lstack_p->nodenum = 1; //starting at the root of the tree + lstack_p++; + + while (1) + { + //pop up the stack + lstack_p--; + //if the trace stack is empty (ended up with a piece of the + //line to be traced in an area) + if (lstack_p < linkstack) break; + //number of the current node to test the line against + nodenum = lstack_p->nodenum; + //if it is an area + if (nodenum < 0) + { + //NOTE: the entity might have already been linked into this area + // because several node children can point to the same area + for (link = aasworld.arealinkedentities[-nodenum]; link; link = link->next_ent) + { + if (link->entnum == entnum) break; + } //end for + if (link) continue; + // + link = AAS_AllocAASLink(); + if (!link) return areas; + link->entnum = entnum; + link->areanum = -nodenum; + //put the link into the double linked area list of the entity + link->prev_area = NULL; + link->next_area = areas; + if (areas) areas->prev_area = link; + areas = link; + //put the link into the double linked entity list of the area + link->prev_ent = NULL; + link->next_ent = aasworld.arealinkedentities[-nodenum]; + if (aasworld.arealinkedentities[-nodenum]) + aasworld.arealinkedentities[-nodenum]->prev_ent = link; + aasworld.arealinkedentities[-nodenum] = link; + // + continue; + } //end if + //if solid leaf + if (!nodenum) continue; + //the node to test against + aasnode = &aasworld.nodes[nodenum]; + //the current node plane + plane = &aasworld.planes[aasnode->planenum]; + //get the side(s) the box is situated relative to the plane + side = AAS_BoxOnPlaneSide2(absmins, absmaxs, plane); + //if on the front side of the node + if (side & 1) + { + lstack_p->nodenum = aasnode->children[0]; + lstack_p++; + } //end if + if (lstack_p >= &linkstack[127]) + { + botimport.Print(PRT_ERROR, "AAS_LinkEntity: stack overflow\n"); + break; + } //end if + //if on the back side of the node + if (side & 2) + { + lstack_p->nodenum = aasnode->children[1]; + lstack_p++; + } //end if + if (lstack_p >= &linkstack[127]) + { + botimport.Print(PRT_ERROR, "AAS_LinkEntity: stack overflow\n"); + break; + } //end if + } //end while + return areas; +} //end of the function AAS_AASLinkEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_link_t *AAS_LinkEntityClientBBox(vec3_t absmins, vec3_t absmaxs, int entnum, int presencetype) +{ + vec3_t mins, maxs; + vec3_t newabsmins, newabsmaxs; + + AAS_PresenceTypeBoundingBox(presencetype, mins, maxs); + VectorSubtract(absmins, maxs, newabsmins); + VectorSubtract(absmaxs, mins, newabsmaxs); + //relink the entity + return AAS_AASLinkEntity(newabsmins, newabsmaxs, entnum); +} //end of the function AAS_LinkEntityClientBBox +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BBoxAreas(vec3_t absmins, vec3_t absmaxs, int *areas, int maxareas) +{ + aas_link_t *linkedareas, *link; + int num; + + linkedareas = AAS_AASLinkEntity(absmins, absmaxs, -1); + num = 0; + for (link = linkedareas; link; link = link->next_area) + { + areas[num] = link->areanum; + num++; + if (num >= maxareas) + break; + } //end for + AAS_UnlinkFromAreas(linkedareas); + return num; +} //end of the function AAS_BBoxAreas +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaInfo( int areanum, aas_areainfo_t *info ) +{ + aas_areasettings_t *settings; + if (!info) + return 0; + if (areanum <= 0 || areanum >= aasworld.numareas) + { + botimport.Print(PRT_ERROR, "AAS_AreaInfo: areanum %d out of range\n", areanum); + return 0; + } //end if + settings = &aasworld.areasettings[areanum]; + info->cluster = settings->cluster; + info->contents = settings->contents; + info->flags = settings->areaflags; + info->presencetype = settings->presencetype; + VectorCopy(aasworld.areas[areanum].mins, info->mins); + VectorCopy(aasworld.areas[areanum].maxs, info->maxs); + VectorCopy(aasworld.areas[areanum].center, info->center); + return sizeof(aas_areainfo_t); +} //end of the function AAS_AreaInfo +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_plane_t *AAS_PlaneFromNum(int planenum) +{ + if (!aasworld.loaded) return 0; + + return &aasworld.planes[planenum]; +} //end of the function AAS_PlaneFromNum diff --git a/code/botlib/be_aas_sample.h b/code/botlib/be_aas_sample.h index 6c2fc7e..5c121a0 100755 --- a/code/botlib/be_aas_sample.h +++ b/code/botlib/be_aas_sample.h @@ -1,69 +1,69 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: be_aas_sample.h - * - * desc: AAS - * - * $Archive: /source/code/botlib/be_aas_sample.h $ - * - *****************************************************************************/ - -#ifdef AASINTERN -void AAS_InitAASLinkHeap(void); -void AAS_InitAASLinkedEntities(void); -void AAS_FreeAASLinkHeap(void); -void AAS_FreeAASLinkedEntities(void); -aas_face_t *AAS_AreaGroundFace(int areanum, vec3_t point); -aas_face_t *AAS_TraceEndFace(aas_trace_t *trace); -aas_plane_t *AAS_PlaneFromNum(int planenum); -aas_link_t *AAS_AASLinkEntity(vec3_t absmins, vec3_t absmaxs, int entnum); -aas_link_t *AAS_LinkEntityClientBBox(vec3_t absmins, vec3_t absmaxs, int entnum, int presencetype); -qboolean AAS_PointInsideFace(int facenum, vec3_t point, float epsilon); -qboolean AAS_InsideFace(aas_face_t *face, vec3_t pnormal, vec3_t point, float epsilon); -void AAS_UnlinkFromAreas(aas_link_t *areas); -#endif //AASINTERN - -//returns the mins and maxs of the bounding box for the given presence type -void AAS_PresenceTypeBoundingBox(int presencetype, vec3_t mins, vec3_t maxs); -//returns the cluster the area is in (negative portal number if the area is a portal) -int AAS_AreaCluster(int areanum); -//returns the presence type(s) of the area -int AAS_AreaPresenceType(int areanum); -//returns the presence type(s) at the given point -int AAS_PointPresenceType(vec3_t point); -//returns the result of the trace of a client bbox -aas_trace_t AAS_TraceClientBBox(vec3_t start, vec3_t end, int presencetype, int passent); -//stores the areas the trace went through and returns the number of passed areas -int AAS_TraceAreas(vec3_t start, vec3_t end, int *areas, vec3_t *points, int maxareas); -//returns the areas the bounding box is in -int AAS_BBoxAreas(vec3_t absmins, vec3_t absmaxs, int *areas, int maxareas); -//return area information -int AAS_AreaInfo( int areanum, aas_areainfo_t *info ); -//returns the area the point is in -int AAS_PointAreaNum(vec3_t point); -// -int AAS_PointReachabilityAreaIndex( vec3_t point ); -//returns the plane the given face is in -void AAS_FacePlane(int facenum, vec3_t normal, float *dist); - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_sample.h + * + * desc: AAS + * + * $Archive: /source/code/botlib/be_aas_sample.h $ + * + *****************************************************************************/ + +#ifdef AASINTERN +void AAS_InitAASLinkHeap(void); +void AAS_InitAASLinkedEntities(void); +void AAS_FreeAASLinkHeap(void); +void AAS_FreeAASLinkedEntities(void); +aas_face_t *AAS_AreaGroundFace(int areanum, vec3_t point); +aas_face_t *AAS_TraceEndFace(aas_trace_t *trace); +aas_plane_t *AAS_PlaneFromNum(int planenum); +aas_link_t *AAS_AASLinkEntity(vec3_t absmins, vec3_t absmaxs, int entnum); +aas_link_t *AAS_LinkEntityClientBBox(vec3_t absmins, vec3_t absmaxs, int entnum, int presencetype); +qboolean AAS_PointInsideFace(int facenum, vec3_t point, float epsilon); +qboolean AAS_InsideFace(aas_face_t *face, vec3_t pnormal, vec3_t point, float epsilon); +void AAS_UnlinkFromAreas(aas_link_t *areas); +#endif //AASINTERN + +//returns the mins and maxs of the bounding box for the given presence type +void AAS_PresenceTypeBoundingBox(int presencetype, vec3_t mins, vec3_t maxs); +//returns the cluster the area is in (negative portal number if the area is a portal) +int AAS_AreaCluster(int areanum); +//returns the presence type(s) of the area +int AAS_AreaPresenceType(int areanum); +//returns the presence type(s) at the given point +int AAS_PointPresenceType(vec3_t point); +//returns the result of the trace of a client bbox +aas_trace_t AAS_TraceClientBBox(vec3_t start, vec3_t end, int presencetype, int passent); +//stores the areas the trace went through and returns the number of passed areas +int AAS_TraceAreas(vec3_t start, vec3_t end, int *areas, vec3_t *points, int maxareas); +//returns the areas the bounding box is in +int AAS_BBoxAreas(vec3_t absmins, vec3_t absmaxs, int *areas, int maxareas); +//return area information +int AAS_AreaInfo( int areanum, aas_areainfo_t *info ); +//returns the area the point is in +int AAS_PointAreaNum(vec3_t point); +// +int AAS_PointReachabilityAreaIndex( vec3_t point ); +//returns the plane the given face is in +void AAS_FacePlane(int facenum, vec3_t normal, float *dist); + diff --git a/code/botlib/be_ai_char.c b/code/botlib/be_ai_char.c index 042adb4..ea34030 100755 --- a/code/botlib/be_ai_char.c +++ b/code/botlib/be_ai_char.c @@ -1,790 +1,790 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: be_ai_char.c - * - * desc: bot characters - * - * $Archive: /MissionPack/code/botlib/be_ai_char.c $ - * - *****************************************************************************/ - -#include "../game/q_shared.h" -#include "l_log.h" -#include "l_memory.h" -#include "l_utils.h" -#include "l_script.h" -#include "l_precomp.h" -#include "l_struct.h" -#include "l_libvar.h" -#include "aasfile.h" -#include "../game/botlib.h" -#include "../game/be_aas.h" -#include "be_aas_funcs.h" -#include "be_interface.h" -#include "../game/be_ai_char.h" - -#define MAX_CHARACTERISTICS 80 - -#define CT_INTEGER 1 -#define CT_FLOAT 2 -#define CT_STRING 3 - -#define DEFAULT_CHARACTER "bots/default_c.c" - -//characteristic value -union cvalue -{ - int integer; - float _float; - char *string; -}; -//a characteristic -typedef struct bot_characteristic_s -{ - char type; //characteristic type - union cvalue value; //characteristic value -} bot_characteristic_t; - -//a bot character -typedef struct bot_character_s -{ - char filename[MAX_QPATH]; - float skill; - bot_characteristic_t c[1]; //variable sized -} bot_character_t; - -bot_character_t *botcharacters[MAX_CLIENTS + 1]; - -//======================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//======================================================================== -bot_character_t *BotCharacterFromHandle(int handle) -{ - if (handle <= 0 || handle > MAX_CLIENTS) - { - botimport.Print(PRT_FATAL, "character handle %d out of range\n", handle); - return NULL; - } //end if - if (!botcharacters[handle]) - { - botimport.Print(PRT_FATAL, "invalid character %d\n", handle); - return NULL; - } //end if - return botcharacters[handle]; -} //end of the function BotCharacterFromHandle -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotDumpCharacter(bot_character_t *ch) -{ - int i; - - Log_Write("%s", ch->filename); - Log_Write("skill %d\n", ch->skill); - Log_Write("{\n"); - for (i = 0; i < MAX_CHARACTERISTICS; i++) - { - switch(ch->c[i].type) - { - case CT_INTEGER: Log_Write(" %4d %d\n", i, ch->c[i].value.integer); break; - case CT_FLOAT: Log_Write(" %4d %f\n", i, ch->c[i].value._float); break; - case CT_STRING: Log_Write(" %4d %s\n", i, ch->c[i].value.string); break; - } //end case - } //end for - Log_Write("}\n"); -} //end of the function BotDumpCharacter -//======================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//======================================================================== -void BotFreeCharacterStrings(bot_character_t *ch) -{ - int i; - - for (i = 0; i < MAX_CHARACTERISTICS; i++) - { - if (ch->c[i].type == CT_STRING) - { - FreeMemory(ch->c[i].value.string); - } //end if - } //end for -} //end of the function BotFreeCharacterStrings -//======================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//======================================================================== -void BotFreeCharacter2(int handle) -{ - if (handle <= 0 || handle > MAX_CLIENTS) - { - botimport.Print(PRT_FATAL, "character handle %d out of range\n", handle); - return; - } //end if - if (!botcharacters[handle]) - { - botimport.Print(PRT_FATAL, "invalid character %d\n", handle); - return; - } //end if - BotFreeCharacterStrings(botcharacters[handle]); - FreeMemory(botcharacters[handle]); - botcharacters[handle] = NULL; -} //end of the function BotFreeCharacter2 -//======================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//======================================================================== -void BotFreeCharacter(int handle) -{ - if (!LibVarGetValue("bot_reloadcharacters")) return; - BotFreeCharacter2(handle); -} //end of the function BotFreeCharacter -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotDefaultCharacteristics(bot_character_t *ch, bot_character_t *defaultch) -{ - int i; - - for (i = 0; i < MAX_CHARACTERISTICS; i++) - { - if (ch->c[i].type) continue; - // - if (defaultch->c[i].type == CT_FLOAT) - { - ch->c[i].type = CT_FLOAT; - ch->c[i].value._float = defaultch->c[i].value._float; - } //end if - else if (defaultch->c[i].type == CT_INTEGER) - { - ch->c[i].type = CT_INTEGER; - ch->c[i].value.integer = defaultch->c[i].value.integer; - } //end else if - else if (defaultch->c[i].type == CT_STRING) - { - ch->c[i].type = CT_STRING; - ch->c[i].value.string = (char *) GetMemory(strlen(defaultch->c[i].value.string)+1); - strcpy(ch->c[i].value.string, defaultch->c[i].value.string); - } //end else if - } //end for -} //end of the function BotDefaultCharacteristics -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bot_character_t *BotLoadCharacterFromFile(char *charfile, int skill) -{ - int indent, index, foundcharacter; - bot_character_t *ch; - source_t *source; - token_t token; - - foundcharacter = qfalse; - //a bot character is parsed in two phases - PC_SetBaseFolder(BOTFILESBASEFOLDER); - source = LoadSourceFile(charfile); - if (!source) - { - botimport.Print(PRT_ERROR, "counldn't load %s\n", charfile); - return NULL; - } //end if - ch = (bot_character_t *) GetClearedMemory(sizeof(bot_character_t) + - MAX_CHARACTERISTICS * sizeof(bot_characteristic_t)); - strcpy(ch->filename, charfile); - while(PC_ReadToken(source, &token)) - { - if (!strcmp(token.string, "skill")) - { - if (!PC_ExpectTokenType(source, TT_NUMBER, 0, &token)) - { - FreeSource(source); - BotFreeCharacterStrings(ch); - FreeMemory(ch); - return NULL; - } //end if - if (!PC_ExpectTokenString(source, "{")) - { - FreeSource(source); - BotFreeCharacterStrings(ch); - FreeMemory(ch); - return NULL; - } //end if - //if it's the correct skill - if (skill < 0 || token.intvalue == skill) - { - foundcharacter = qtrue; - ch->skill = token.intvalue; - while(PC_ExpectAnyToken(source, &token)) - { - if (!strcmp(token.string, "}")) break; - if (token.type != TT_NUMBER || !(token.subtype & TT_INTEGER)) - { - SourceError(source, "expected integer index, found %s\n", token.string); - FreeSource(source); - BotFreeCharacterStrings(ch); - FreeMemory(ch); - return NULL; - } //end if - index = token.intvalue; - if (index < 0 || index > MAX_CHARACTERISTICS) - { - SourceError(source, "characteristic index out of range [0, %d]\n", MAX_CHARACTERISTICS); - FreeSource(source); - BotFreeCharacterStrings(ch); - FreeMemory(ch); - return NULL; - } //end if - if (ch->c[index].type) - { - SourceError(source, "characteristic %d already initialized\n", index); - FreeSource(source); - BotFreeCharacterStrings(ch); - FreeMemory(ch); - return NULL; - } //end if - if (!PC_ExpectAnyToken(source, &token)) - { - FreeSource(source); - BotFreeCharacterStrings(ch); - FreeMemory(ch); - return NULL; - } //end if - if (token.type == TT_NUMBER) - { - if (token.subtype & TT_FLOAT) - { - ch->c[index].value._float = token.floatvalue; - ch->c[index].type = CT_FLOAT; - } //end if - else - { - ch->c[index].value.integer = token.intvalue; - ch->c[index].type = CT_INTEGER; - } //end else - } //end if - else if (token.type == TT_STRING) - { - StripDoubleQuotes(token.string); - ch->c[index].value.string = GetMemory(strlen(token.string)+1); - strcpy(ch->c[index].value.string, token.string); - ch->c[index].type = CT_STRING; - } //end else if - else - { - SourceError(source, "expected integer, float or string, found %s\n", token.string); - FreeSource(source); - BotFreeCharacterStrings(ch); - FreeMemory(ch); - return NULL; - } //end else - } //end if - break; - } //end if - else - { - indent = 1; - while(indent) - { - if (!PC_ExpectAnyToken(source, &token)) - { - FreeSource(source); - BotFreeCharacterStrings(ch); - FreeMemory(ch); - return NULL; - } //end if - if (!strcmp(token.string, "{")) indent++; - else if (!strcmp(token.string, "}")) indent--; - } //end while - } //end else - } //end if - else - { - SourceError(source, "unknown definition %s\n", token.string); - FreeSource(source); - BotFreeCharacterStrings(ch); - FreeMemory(ch); - return NULL; - } //end else - } //end while - FreeSource(source); - // - if (!foundcharacter) - { - BotFreeCharacterStrings(ch); - FreeMemory(ch); - return NULL; - } //end if - return ch; -} //end of the function BotLoadCharacterFromFile -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotFindCachedCharacter(char *charfile, float skill) -{ - int handle; - - for (handle = 1; handle <= MAX_CLIENTS; handle++) - { - if ( !botcharacters[handle] ) continue; - if ( strcmp( botcharacters[handle]->filename, charfile ) == 0 && - (skill < 0 || fabs(botcharacters[handle]->skill - skill) < 0.01) ) - { - return handle; - } //end if - } //end for - return 0; -} //end of the function BotFindCachedCharacter -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotLoadCachedCharacter(char *charfile, float skill, int reload) -{ - int handle, cachedhandle, intskill; - bot_character_t *ch = NULL; -#ifdef DEBUG - int starttime; - - starttime = Sys_MilliSeconds(); -#endif //DEBUG - - //find a free spot for a character - for (handle = 1; handle <= MAX_CLIENTS; handle++) - { - if (!botcharacters[handle]) break; - } //end for - if (handle > MAX_CLIENTS) return 0; - //try to load a cached character with the given skill - if (!reload) - { - cachedhandle = BotFindCachedCharacter(charfile, skill); - if (cachedhandle) - { - botimport.Print(PRT_MESSAGE, "loaded cached skill %f from %s\n", skill, charfile); - return cachedhandle; - } //end if - } //end else - // - intskill = (int) (skill + 0.5); - //try to load the character with the given skill - ch = BotLoadCharacterFromFile(charfile, intskill); - if (ch) - { - botcharacters[handle] = ch; - // - botimport.Print(PRT_MESSAGE, "loaded skill %d from %s\n", intskill, charfile); -#ifdef DEBUG - if (bot_developer) - { - botimport.Print(PRT_MESSAGE, "skill %d loaded in %d msec from %s\n", intskill, Sys_MilliSeconds() - starttime, charfile); - } //end if -#endif //DEBUG - return handle; - } //end if - // - botimport.Print(PRT_WARNING, "couldn't find skill %d in %s\n", intskill, charfile); - // - if (!reload) - { - //try to load a cached default character with the given skill - cachedhandle = BotFindCachedCharacter(DEFAULT_CHARACTER, skill); - if (cachedhandle) - { - botimport.Print(PRT_MESSAGE, "loaded cached default skill %d from %s\n", intskill, charfile); - return cachedhandle; - } //end if - } //end if - //try to load the default character with the given skill - ch = BotLoadCharacterFromFile(DEFAULT_CHARACTER, intskill); - if (ch) - { - botcharacters[handle] = ch; - botimport.Print(PRT_MESSAGE, "loaded default skill %d from %s\n", intskill, charfile); - return handle; - } //end if - // - if (!reload) - { - //try to load a cached character with any skill - cachedhandle = BotFindCachedCharacter(charfile, -1); - if (cachedhandle) - { - botimport.Print(PRT_MESSAGE, "loaded cached skill %f from %s\n", botcharacters[cachedhandle]->skill, charfile); - return cachedhandle; - } //end if - } //end if - //try to load a character with any skill - ch = BotLoadCharacterFromFile(charfile, -1); - if (ch) - { - botcharacters[handle] = ch; - botimport.Print(PRT_MESSAGE, "loaded skill %f from %s\n", ch->skill, charfile); - return handle; - } //end if - // - if (!reload) - { - //try to load a cached character with any skill - cachedhandle = BotFindCachedCharacter(DEFAULT_CHARACTER, -1); - if (cachedhandle) - { - botimport.Print(PRT_MESSAGE, "loaded cached default skill %f from %s\n", botcharacters[cachedhandle]->skill, charfile); - return cachedhandle; - } //end if - } //end if - //try to load a character with any skill - ch = BotLoadCharacterFromFile(DEFAULT_CHARACTER, -1); - if (ch) - { - botcharacters[handle] = ch; - botimport.Print(PRT_MESSAGE, "loaded default skill %f from %s\n", ch->skill, charfile); - return handle; - } //end if - // - botimport.Print(PRT_WARNING, "couldn't load any skill from %s\n", charfile); - //couldn't load any character - return 0; -} //end of the function BotLoadCachedCharacter -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotLoadCharacterSkill(char *charfile, float skill) -{ - int ch, defaultch; - - defaultch = BotLoadCachedCharacter(DEFAULT_CHARACTER, skill, qfalse); - ch = BotLoadCachedCharacter(charfile, skill, LibVarGetValue("bot_reloadcharacters")); - - if (defaultch && ch) - { - BotDefaultCharacteristics(botcharacters[ch], botcharacters[defaultch]); - } //end if - - return ch; -} //end of the function BotLoadCharacterSkill -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotInterpolateCharacters(int handle1, int handle2, float desiredskill) -{ - bot_character_t *ch1, *ch2, *out; - int i, handle; - float scale; - - ch1 = BotCharacterFromHandle(handle1); - ch2 = BotCharacterFromHandle(handle2); - if (!ch1 || !ch2) - return 0; - //find a free spot for a character - for (handle = 1; handle <= MAX_CLIENTS; handle++) - { - if (!botcharacters[handle]) break; - } //end for - if (handle > MAX_CLIENTS) return 0; - out = (bot_character_t *) GetClearedMemory(sizeof(bot_character_t) + - MAX_CHARACTERISTICS * sizeof(bot_characteristic_t)); - out->skill = desiredskill; - strcpy(out->filename, ch1->filename); - botcharacters[handle] = out; - - scale = (float) (desiredskill - ch1->skill) / (ch2->skill - ch1->skill); - for (i = 0; i < MAX_CHARACTERISTICS; i++) - { - // - if (ch1->c[i].type == CT_FLOAT && ch2->c[i].type == CT_FLOAT) - { - out->c[i].type = CT_FLOAT; - out->c[i].value._float = ch1->c[i].value._float + - (ch2->c[i].value._float - ch1->c[i].value._float) * scale; - } //end if - else if (ch1->c[i].type == CT_INTEGER) - { - out->c[i].type = CT_INTEGER; - out->c[i].value.integer = ch1->c[i].value.integer; - } //end else if - else if (ch1->c[i].type == CT_STRING) - { - out->c[i].type = CT_STRING; - out->c[i].value.string = (char *) GetMemory(strlen(ch1->c[i].value.string)+1); - strcpy(out->c[i].value.string, ch1->c[i].value.string); - } //end else if - } //end for - return handle; -} //end of the function BotInterpolateCharacters -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotLoadCharacter(char *charfile, float skill) -{ - int firstskill, secondskill, handle; - - //make sure the skill is in the valid range - if (skill < 1.0) skill = 1.0; - else if (skill > 5.0) skill = 5.0; - //skill 1, 4 and 5 should be available in the character files - if (skill == 1.0 || skill == 4.0 || skill == 5.0) - { - return BotLoadCharacterSkill(charfile, skill); - } //end if - //check if there's a cached skill - handle = BotFindCachedCharacter(charfile, skill); - if (handle) - { - botimport.Print(PRT_MESSAGE, "loaded cached skill %f from %s\n", skill, charfile); - return handle; - } //end if - if (skill < 4.0) - { - //load skill 1 and 4 - firstskill = BotLoadCharacterSkill(charfile, 1); - if (!firstskill) return 0; - secondskill = BotLoadCharacterSkill(charfile, 4); - if (!secondskill) return firstskill; - } //end if - else - { - //load skill 4 and 5 - firstskill = BotLoadCharacterSkill(charfile, 4); - if (!firstskill) return 0; - secondskill = BotLoadCharacterSkill(charfile, 5); - if (!secondskill) return firstskill; - } //end else - //interpolate between the two skills - handle = BotInterpolateCharacters(firstskill, secondskill, skill); - if (!handle) return 0; - //write the character to the log file - BotDumpCharacter(botcharacters[handle]); - // - return handle; -} //end of the function BotLoadCharacter -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int CheckCharacteristicIndex(int character, int index) -{ - bot_character_t *ch; - - ch = BotCharacterFromHandle(character); - if (!ch) return qfalse; - if (index < 0 || index >= MAX_CHARACTERISTICS) - { - botimport.Print(PRT_ERROR, "characteristic %d does not exist\n", index); - return qfalse; - } //end if - if (!ch->c[index].type) - { - botimport.Print(PRT_ERROR, "characteristic %d is not initialized\n", index); - return qfalse; - } //end if - return qtrue; -} //end of the function CheckCharacteristicIndex -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -float Characteristic_Float(int character, int index) -{ - bot_character_t *ch; - - ch = BotCharacterFromHandle(character); - if (!ch) return 0; - //check if the index is in range - if (!CheckCharacteristicIndex(character, index)) return 0; - //an integer will be converted to a float - if (ch->c[index].type == CT_INTEGER) - { - return (float) ch->c[index].value.integer; - } //end if - //floats are just returned - else if (ch->c[index].type == CT_FLOAT) - { - return ch->c[index].value._float; - } //end else if - //cannot convert a string pointer to a float - else - { - botimport.Print(PRT_ERROR, "characteristic %d is not a float\n", index); - return 0; - } //end else if -// return 0; -} //end of the function Characteristic_Float -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -float Characteristic_BFloat(int character, int index, float min, float max) -{ - float value; - bot_character_t *ch; - - ch = BotCharacterFromHandle(character); - if (!ch) return 0; - if (min > max) - { - botimport.Print(PRT_ERROR, "cannot bound characteristic %d between %f and %f\n", index, min, max); - return 0; - } //end if - value = Characteristic_Float(character, index); - if (value < min) return min; - if (value > max) return max; - return value; -} //end of the function Characteristic_BFloat -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int Characteristic_Integer(int character, int index) -{ - bot_character_t *ch; - - ch = BotCharacterFromHandle(character); - if (!ch) return 0; - //check if the index is in range - if (!CheckCharacteristicIndex(character, index)) return 0; - //an integer will just be returned - if (ch->c[index].type == CT_INTEGER) - { - return ch->c[index].value.integer; - } //end if - //floats are casted to integers - else if (ch->c[index].type == CT_FLOAT) - { - return (int) ch->c[index].value._float; - } //end else if - else - { - botimport.Print(PRT_ERROR, "characteristic %d is not a integer\n", index); - return 0; - } //end else if -// return 0; -} //end of the function Characteristic_Integer -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int Characteristic_BInteger(int character, int index, int min, int max) -{ - int value; - bot_character_t *ch; - - ch = BotCharacterFromHandle(character); - if (!ch) return 0; - if (min > max) - { - botimport.Print(PRT_ERROR, "cannot bound characteristic %d between %d and %d\n", index, min, max); - return 0; - } //end if - value = Characteristic_Integer(character, index); - if (value < min) return min; - if (value > max) return max; - return value; -} //end of the function Characteristic_BInteger -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Characteristic_String(int character, int index, char *buf, int size) -{ - bot_character_t *ch; - - ch = BotCharacterFromHandle(character); - if (!ch) return; - //check if the index is in range - if (!CheckCharacteristicIndex(character, index)) return; - //an integer will be converted to a float - if (ch->c[index].type == CT_STRING) - { - strncpy(buf, ch->c[index].value.string, size-1); - buf[size-1] = '\0'; - return; - } //end if - else - { - botimport.Print(PRT_ERROR, "characteristic %d is not a string\n", index); - return; - } //end else if - return; -} //end of the function Characteristic_String -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotShutdownCharacters(void) -{ - int handle; - - for (handle = 1; handle <= MAX_CLIENTS; handle++) - { - if (botcharacters[handle]) - { - BotFreeCharacter2(handle); - } //end if - } //end for -} //end of the function BotShutdownCharacters - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_ai_char.c + * + * desc: bot characters + * + * $Archive: /MissionPack/code/botlib/be_ai_char.c $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_log.h" +#include "l_memory.h" +#include "l_utils.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_libvar.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "../game/be_ai_char.h" + +#define MAX_CHARACTERISTICS 80 + +#define CT_INTEGER 1 +#define CT_FLOAT 2 +#define CT_STRING 3 + +#define DEFAULT_CHARACTER "bots/default_c.c" + +//characteristic value +union cvalue +{ + int integer; + float _float; + char *string; +}; +//a characteristic +typedef struct bot_characteristic_s +{ + char type; //characteristic type + union cvalue value; //characteristic value +} bot_characteristic_t; + +//a bot character +typedef struct bot_character_s +{ + char filename[MAX_QPATH]; + float skill; + bot_characteristic_t c[1]; //variable sized +} bot_character_t; + +bot_character_t *botcharacters[MAX_CLIENTS + 1]; + +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +bot_character_t *BotCharacterFromHandle(int handle) +{ + if (handle <= 0 || handle > MAX_CLIENTS) + { + botimport.Print(PRT_FATAL, "character handle %d out of range\n", handle); + return NULL; + } //end if + if (!botcharacters[handle]) + { + botimport.Print(PRT_FATAL, "invalid character %d\n", handle); + return NULL; + } //end if + return botcharacters[handle]; +} //end of the function BotCharacterFromHandle +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDumpCharacter(bot_character_t *ch) +{ + int i; + + Log_Write("%s", ch->filename); + Log_Write("skill %d\n", ch->skill); + Log_Write("{\n"); + for (i = 0; i < MAX_CHARACTERISTICS; i++) + { + switch(ch->c[i].type) + { + case CT_INTEGER: Log_Write(" %4d %d\n", i, ch->c[i].value.integer); break; + case CT_FLOAT: Log_Write(" %4d %f\n", i, ch->c[i].value._float); break; + case CT_STRING: Log_Write(" %4d %s\n", i, ch->c[i].value.string); break; + } //end case + } //end for + Log_Write("}\n"); +} //end of the function BotDumpCharacter +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotFreeCharacterStrings(bot_character_t *ch) +{ + int i; + + for (i = 0; i < MAX_CHARACTERISTICS; i++) + { + if (ch->c[i].type == CT_STRING) + { + FreeMemory(ch->c[i].value.string); + } //end if + } //end for +} //end of the function BotFreeCharacterStrings +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotFreeCharacter2(int handle) +{ + if (handle <= 0 || handle > MAX_CLIENTS) + { + botimport.Print(PRT_FATAL, "character handle %d out of range\n", handle); + return; + } //end if + if (!botcharacters[handle]) + { + botimport.Print(PRT_FATAL, "invalid character %d\n", handle); + return; + } //end if + BotFreeCharacterStrings(botcharacters[handle]); + FreeMemory(botcharacters[handle]); + botcharacters[handle] = NULL; +} //end of the function BotFreeCharacter2 +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotFreeCharacter(int handle) +{ + if (!LibVarGetValue("bot_reloadcharacters")) return; + BotFreeCharacter2(handle); +} //end of the function BotFreeCharacter +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDefaultCharacteristics(bot_character_t *ch, bot_character_t *defaultch) +{ + int i; + + for (i = 0; i < MAX_CHARACTERISTICS; i++) + { + if (ch->c[i].type) continue; + // + if (defaultch->c[i].type == CT_FLOAT) + { + ch->c[i].type = CT_FLOAT; + ch->c[i].value._float = defaultch->c[i].value._float; + } //end if + else if (defaultch->c[i].type == CT_INTEGER) + { + ch->c[i].type = CT_INTEGER; + ch->c[i].value.integer = defaultch->c[i].value.integer; + } //end else if + else if (defaultch->c[i].type == CT_STRING) + { + ch->c[i].type = CT_STRING; + ch->c[i].value.string = (char *) GetMemory(strlen(defaultch->c[i].value.string)+1); + strcpy(ch->c[i].value.string, defaultch->c[i].value.string); + } //end else if + } //end for +} //end of the function BotDefaultCharacteristics +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_character_t *BotLoadCharacterFromFile(char *charfile, int skill) +{ + int indent, index, foundcharacter; + bot_character_t *ch; + source_t *source; + token_t token; + + foundcharacter = qfalse; + //a bot character is parsed in two phases + PC_SetBaseFolder(BOTFILESBASEFOLDER); + source = LoadSourceFile(charfile); + if (!source) + { + botimport.Print(PRT_ERROR, "counldn't load %s\n", charfile); + return NULL; + } //end if + ch = (bot_character_t *) GetClearedMemory(sizeof(bot_character_t) + + MAX_CHARACTERISTICS * sizeof(bot_characteristic_t)); + strcpy(ch->filename, charfile); + while(PC_ReadToken(source, &token)) + { + if (!strcmp(token.string, "skill")) + { + if (!PC_ExpectTokenType(source, TT_NUMBER, 0, &token)) + { + FreeSource(source); + BotFreeCharacterStrings(ch); + FreeMemory(ch); + return NULL; + } //end if + if (!PC_ExpectTokenString(source, "{")) + { + FreeSource(source); + BotFreeCharacterStrings(ch); + FreeMemory(ch); + return NULL; + } //end if + //if it's the correct skill + if (skill < 0 || token.intvalue == skill) + { + foundcharacter = qtrue; + ch->skill = token.intvalue; + while(PC_ExpectAnyToken(source, &token)) + { + if (!strcmp(token.string, "}")) break; + if (token.type != TT_NUMBER || !(token.subtype & TT_INTEGER)) + { + SourceError(source, "expected integer index, found %s\n", token.string); + FreeSource(source); + BotFreeCharacterStrings(ch); + FreeMemory(ch); + return NULL; + } //end if + index = token.intvalue; + if (index < 0 || index > MAX_CHARACTERISTICS) + { + SourceError(source, "characteristic index out of range [0, %d]\n", MAX_CHARACTERISTICS); + FreeSource(source); + BotFreeCharacterStrings(ch); + FreeMemory(ch); + return NULL; + } //end if + if (ch->c[index].type) + { + SourceError(source, "characteristic %d already initialized\n", index); + FreeSource(source); + BotFreeCharacterStrings(ch); + FreeMemory(ch); + return NULL; + } //end if + if (!PC_ExpectAnyToken(source, &token)) + { + FreeSource(source); + BotFreeCharacterStrings(ch); + FreeMemory(ch); + return NULL; + } //end if + if (token.type == TT_NUMBER) + { + if (token.subtype & TT_FLOAT) + { + ch->c[index].value._float = token.floatvalue; + ch->c[index].type = CT_FLOAT; + } //end if + else + { + ch->c[index].value.integer = token.intvalue; + ch->c[index].type = CT_INTEGER; + } //end else + } //end if + else if (token.type == TT_STRING) + { + StripDoubleQuotes(token.string); + ch->c[index].value.string = GetMemory(strlen(token.string)+1); + strcpy(ch->c[index].value.string, token.string); + ch->c[index].type = CT_STRING; + } //end else if + else + { + SourceError(source, "expected integer, float or string, found %s\n", token.string); + FreeSource(source); + BotFreeCharacterStrings(ch); + FreeMemory(ch); + return NULL; + } //end else + } //end if + break; + } //end if + else + { + indent = 1; + while(indent) + { + if (!PC_ExpectAnyToken(source, &token)) + { + FreeSource(source); + BotFreeCharacterStrings(ch); + FreeMemory(ch); + return NULL; + } //end if + if (!strcmp(token.string, "{")) indent++; + else if (!strcmp(token.string, "}")) indent--; + } //end while + } //end else + } //end if + else + { + SourceError(source, "unknown definition %s\n", token.string); + FreeSource(source); + BotFreeCharacterStrings(ch); + FreeMemory(ch); + return NULL; + } //end else + } //end while + FreeSource(source); + // + if (!foundcharacter) + { + BotFreeCharacterStrings(ch); + FreeMemory(ch); + return NULL; + } //end if + return ch; +} //end of the function BotLoadCharacterFromFile +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotFindCachedCharacter(char *charfile, float skill) +{ + int handle; + + for (handle = 1; handle <= MAX_CLIENTS; handle++) + { + if ( !botcharacters[handle] ) continue; + if ( strcmp( botcharacters[handle]->filename, charfile ) == 0 && + (skill < 0 || fabs(botcharacters[handle]->skill - skill) < 0.01) ) + { + return handle; + } //end if + } //end for + return 0; +} //end of the function BotFindCachedCharacter +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotLoadCachedCharacter(char *charfile, float skill, int reload) +{ + int handle, cachedhandle, intskill; + bot_character_t *ch = NULL; +#ifdef DEBUG + int starttime; + + starttime = Sys_MilliSeconds(); +#endif //DEBUG + + //find a free spot for a character + for (handle = 1; handle <= MAX_CLIENTS; handle++) + { + if (!botcharacters[handle]) break; + } //end for + if (handle > MAX_CLIENTS) return 0; + //try to load a cached character with the given skill + if (!reload) + { + cachedhandle = BotFindCachedCharacter(charfile, skill); + if (cachedhandle) + { + botimport.Print(PRT_MESSAGE, "loaded cached skill %f from %s\n", skill, charfile); + return cachedhandle; + } //end if + } //end else + // + intskill = (int) (skill + 0.5); + //try to load the character with the given skill + ch = BotLoadCharacterFromFile(charfile, intskill); + if (ch) + { + botcharacters[handle] = ch; + // + botimport.Print(PRT_MESSAGE, "loaded skill %d from %s\n", intskill, charfile); +#ifdef DEBUG + if (bot_developer) + { + botimport.Print(PRT_MESSAGE, "skill %d loaded in %d msec from %s\n", intskill, Sys_MilliSeconds() - starttime, charfile); + } //end if +#endif //DEBUG + return handle; + } //end if + // + botimport.Print(PRT_WARNING, "couldn't find skill %d in %s\n", intskill, charfile); + // + if (!reload) + { + //try to load a cached default character with the given skill + cachedhandle = BotFindCachedCharacter(DEFAULT_CHARACTER, skill); + if (cachedhandle) + { + botimport.Print(PRT_MESSAGE, "loaded cached default skill %d from %s\n", intskill, charfile); + return cachedhandle; + } //end if + } //end if + //try to load the default character with the given skill + ch = BotLoadCharacterFromFile(DEFAULT_CHARACTER, intskill); + if (ch) + { + botcharacters[handle] = ch; + botimport.Print(PRT_MESSAGE, "loaded default skill %d from %s\n", intskill, charfile); + return handle; + } //end if + // + if (!reload) + { + //try to load a cached character with any skill + cachedhandle = BotFindCachedCharacter(charfile, -1); + if (cachedhandle) + { + botimport.Print(PRT_MESSAGE, "loaded cached skill %f from %s\n", botcharacters[cachedhandle]->skill, charfile); + return cachedhandle; + } //end if + } //end if + //try to load a character with any skill + ch = BotLoadCharacterFromFile(charfile, -1); + if (ch) + { + botcharacters[handle] = ch; + botimport.Print(PRT_MESSAGE, "loaded skill %f from %s\n", ch->skill, charfile); + return handle; + } //end if + // + if (!reload) + { + //try to load a cached character with any skill + cachedhandle = BotFindCachedCharacter(DEFAULT_CHARACTER, -1); + if (cachedhandle) + { + botimport.Print(PRT_MESSAGE, "loaded cached default skill %f from %s\n", botcharacters[cachedhandle]->skill, charfile); + return cachedhandle; + } //end if + } //end if + //try to load a character with any skill + ch = BotLoadCharacterFromFile(DEFAULT_CHARACTER, -1); + if (ch) + { + botcharacters[handle] = ch; + botimport.Print(PRT_MESSAGE, "loaded default skill %f from %s\n", ch->skill, charfile); + return handle; + } //end if + // + botimport.Print(PRT_WARNING, "couldn't load any skill from %s\n", charfile); + //couldn't load any character + return 0; +} //end of the function BotLoadCachedCharacter +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotLoadCharacterSkill(char *charfile, float skill) +{ + int ch, defaultch; + + defaultch = BotLoadCachedCharacter(DEFAULT_CHARACTER, skill, qfalse); + ch = BotLoadCachedCharacter(charfile, skill, LibVarGetValue("bot_reloadcharacters")); + + if (defaultch && ch) + { + BotDefaultCharacteristics(botcharacters[ch], botcharacters[defaultch]); + } //end if + + return ch; +} //end of the function BotLoadCharacterSkill +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotInterpolateCharacters(int handle1, int handle2, float desiredskill) +{ + bot_character_t *ch1, *ch2, *out; + int i, handle; + float scale; + + ch1 = BotCharacterFromHandle(handle1); + ch2 = BotCharacterFromHandle(handle2); + if (!ch1 || !ch2) + return 0; + //find a free spot for a character + for (handle = 1; handle <= MAX_CLIENTS; handle++) + { + if (!botcharacters[handle]) break; + } //end for + if (handle > MAX_CLIENTS) return 0; + out = (bot_character_t *) GetClearedMemory(sizeof(bot_character_t) + + MAX_CHARACTERISTICS * sizeof(bot_characteristic_t)); + out->skill = desiredskill; + strcpy(out->filename, ch1->filename); + botcharacters[handle] = out; + + scale = (float) (desiredskill - ch1->skill) / (ch2->skill - ch1->skill); + for (i = 0; i < MAX_CHARACTERISTICS; i++) + { + // + if (ch1->c[i].type == CT_FLOAT && ch2->c[i].type == CT_FLOAT) + { + out->c[i].type = CT_FLOAT; + out->c[i].value._float = ch1->c[i].value._float + + (ch2->c[i].value._float - ch1->c[i].value._float) * scale; + } //end if + else if (ch1->c[i].type == CT_INTEGER) + { + out->c[i].type = CT_INTEGER; + out->c[i].value.integer = ch1->c[i].value.integer; + } //end else if + else if (ch1->c[i].type == CT_STRING) + { + out->c[i].type = CT_STRING; + out->c[i].value.string = (char *) GetMemory(strlen(ch1->c[i].value.string)+1); + strcpy(out->c[i].value.string, ch1->c[i].value.string); + } //end else if + } //end for + return handle; +} //end of the function BotInterpolateCharacters +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotLoadCharacter(char *charfile, float skill) +{ + int firstskill, secondskill, handle; + + //make sure the skill is in the valid range + if (skill < 1.0) skill = 1.0; + else if (skill > 5.0) skill = 5.0; + //skill 1, 4 and 5 should be available in the character files + if (skill == 1.0 || skill == 4.0 || skill == 5.0) + { + return BotLoadCharacterSkill(charfile, skill); + } //end if + //check if there's a cached skill + handle = BotFindCachedCharacter(charfile, skill); + if (handle) + { + botimport.Print(PRT_MESSAGE, "loaded cached skill %f from %s\n", skill, charfile); + return handle; + } //end if + if (skill < 4.0) + { + //load skill 1 and 4 + firstskill = BotLoadCharacterSkill(charfile, 1); + if (!firstskill) return 0; + secondskill = BotLoadCharacterSkill(charfile, 4); + if (!secondskill) return firstskill; + } //end if + else + { + //load skill 4 and 5 + firstskill = BotLoadCharacterSkill(charfile, 4); + if (!firstskill) return 0; + secondskill = BotLoadCharacterSkill(charfile, 5); + if (!secondskill) return firstskill; + } //end else + //interpolate between the two skills + handle = BotInterpolateCharacters(firstskill, secondskill, skill); + if (!handle) return 0; + //write the character to the log file + BotDumpCharacter(botcharacters[handle]); + // + return handle; +} //end of the function BotLoadCharacter +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int CheckCharacteristicIndex(int character, int index) +{ + bot_character_t *ch; + + ch = BotCharacterFromHandle(character); + if (!ch) return qfalse; + if (index < 0 || index >= MAX_CHARACTERISTICS) + { + botimport.Print(PRT_ERROR, "characteristic %d does not exist\n", index); + return qfalse; + } //end if + if (!ch->c[index].type) + { + botimport.Print(PRT_ERROR, "characteristic %d is not initialized\n", index); + return qfalse; + } //end if + return qtrue; +} //end of the function CheckCharacteristicIndex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float Characteristic_Float(int character, int index) +{ + bot_character_t *ch; + + ch = BotCharacterFromHandle(character); + if (!ch) return 0; + //check if the index is in range + if (!CheckCharacteristicIndex(character, index)) return 0; + //an integer will be converted to a float + if (ch->c[index].type == CT_INTEGER) + { + return (float) ch->c[index].value.integer; + } //end if + //floats are just returned + else if (ch->c[index].type == CT_FLOAT) + { + return ch->c[index].value._float; + } //end else if + //cannot convert a string pointer to a float + else + { + botimport.Print(PRT_ERROR, "characteristic %d is not a float\n", index); + return 0; + } //end else if +// return 0; +} //end of the function Characteristic_Float +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float Characteristic_BFloat(int character, int index, float min, float max) +{ + float value; + bot_character_t *ch; + + ch = BotCharacterFromHandle(character); + if (!ch) return 0; + if (min > max) + { + botimport.Print(PRT_ERROR, "cannot bound characteristic %d between %f and %f\n", index, min, max); + return 0; + } //end if + value = Characteristic_Float(character, index); + if (value < min) return min; + if (value > max) return max; + return value; +} //end of the function Characteristic_BFloat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Characteristic_Integer(int character, int index) +{ + bot_character_t *ch; + + ch = BotCharacterFromHandle(character); + if (!ch) return 0; + //check if the index is in range + if (!CheckCharacteristicIndex(character, index)) return 0; + //an integer will just be returned + if (ch->c[index].type == CT_INTEGER) + { + return ch->c[index].value.integer; + } //end if + //floats are casted to integers + else if (ch->c[index].type == CT_FLOAT) + { + return (int) ch->c[index].value._float; + } //end else if + else + { + botimport.Print(PRT_ERROR, "characteristic %d is not a integer\n", index); + return 0; + } //end else if +// return 0; +} //end of the function Characteristic_Integer +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Characteristic_BInteger(int character, int index, int min, int max) +{ + int value; + bot_character_t *ch; + + ch = BotCharacterFromHandle(character); + if (!ch) return 0; + if (min > max) + { + botimport.Print(PRT_ERROR, "cannot bound characteristic %d between %d and %d\n", index, min, max); + return 0; + } //end if + value = Characteristic_Integer(character, index); + if (value < min) return min; + if (value > max) return max; + return value; +} //end of the function Characteristic_BInteger +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Characteristic_String(int character, int index, char *buf, int size) +{ + bot_character_t *ch; + + ch = BotCharacterFromHandle(character); + if (!ch) return; + //check if the index is in range + if (!CheckCharacteristicIndex(character, index)) return; + //an integer will be converted to a float + if (ch->c[index].type == CT_STRING) + { + strncpy(buf, ch->c[index].value.string, size-1); + buf[size-1] = '\0'; + return; + } //end if + else + { + botimport.Print(PRT_ERROR, "characteristic %d is not a string\n", index); + return; + } //end else if + return; +} //end of the function Characteristic_String +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotShutdownCharacters(void) +{ + int handle; + + for (handle = 1; handle <= MAX_CLIENTS; handle++) + { + if (botcharacters[handle]) + { + BotFreeCharacter2(handle); + } //end if + } //end for +} //end of the function BotShutdownCharacters + diff --git a/code/botlib/be_ai_chat.c b/code/botlib/be_ai_chat.c index 9483e9b..7c719dd 100755 --- a/code/botlib/be_ai_chat.c +++ b/code/botlib/be_ai_chat.c @@ -1,3017 +1,3017 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: be_ai_chat.c - * - * desc: bot chat AI - * - * $Archive: /MissionPack/code/botlib/be_ai_chat.c $ - * - *****************************************************************************/ - -#include "../game/q_shared.h" -#include "l_memory.h" -#include "l_libvar.h" -#include "l_script.h" -#include "l_precomp.h" -#include "l_struct.h" -#include "l_utils.h" -#include "l_log.h" -#include "aasfile.h" -#include "../game/botlib.h" -#include "../game/be_aas.h" -#include "be_aas_funcs.h" -#include "be_interface.h" -#include "../game/be_ea.h" -#include "../game/be_ai_chat.h" - - -//escape character -#define ESCAPE_CHAR 0x01 //'_' -// -// "hi ", people, " ", 0, " entered the game" -//becomes: -// "hi _rpeople_ _v0_ entered the game" -// - -//match piece types -#define MT_VARIABLE 1 //variable match piece -#define MT_STRING 2 //string match piece -//reply chat key flags -#define RCKFL_AND 1 //key must be present -#define RCKFL_NOT 2 //key must be absent -#define RCKFL_NAME 4 //name of bot must be present -#define RCKFL_STRING 8 //key is a string -#define RCKFL_VARIABLES 16 //key is a match template -#define RCKFL_BOTNAMES 32 //key is a series of botnames -#define RCKFL_GENDERFEMALE 64 //bot must be female -#define RCKFL_GENDERMALE 128 //bot must be male -#define RCKFL_GENDERLESS 256 //bot must be genderless -//time to ignore a chat message after using it -#define CHATMESSAGE_RECENTTIME 20 - -//the actuall chat messages -typedef struct bot_chatmessage_s -{ - char *chatmessage; //chat message string - float time; //last time used - struct bot_chatmessage_s *next; //next chat message in a list -} bot_chatmessage_t; -//bot chat type with chat lines -typedef struct bot_chattype_s -{ - char name[MAX_CHATTYPE_NAME]; - int numchatmessages; - bot_chatmessage_t *firstchatmessage; - struct bot_chattype_s *next; -} bot_chattype_t; -//bot chat lines -typedef struct bot_chat_s -{ - bot_chattype_t *types; -} bot_chat_t; - -//random string -typedef struct bot_randomstring_s -{ - char *string; - struct bot_randomstring_s *next; -} bot_randomstring_t; -//list with random strings -typedef struct bot_randomlist_s -{ - char *string; - int numstrings; - bot_randomstring_t *firstrandomstring; - struct bot_randomlist_s *next; -} bot_randomlist_t; - -//synonym -typedef struct bot_synonym_s -{ - char *string; - float weight; - struct bot_synonym_s *next; -} bot_synonym_t; -//list with synonyms -typedef struct bot_synonymlist_s -{ - unsigned long int context; - float totalweight; - bot_synonym_t *firstsynonym; - struct bot_synonymlist_s *next; -} bot_synonymlist_t; - -//fixed match string -typedef struct bot_matchstring_s -{ - char *string; - struct bot_matchstring_s *next; -} bot_matchstring_t; - -//piece of a match template -typedef struct bot_matchpiece_s -{ - int type; - bot_matchstring_t *firststring; - int variable; - struct bot_matchpiece_s *next; -} bot_matchpiece_t; -//match template -typedef struct bot_matchtemplate_s -{ - unsigned long int context; - int type; - int subtype; - bot_matchpiece_t *first; - struct bot_matchtemplate_s *next; -} bot_matchtemplate_t; - -//reply chat key -typedef struct bot_replychatkey_s -{ - int flags; - char *string; - bot_matchpiece_t *match; - struct bot_replychatkey_s *next; -} bot_replychatkey_t; -//reply chat -typedef struct bot_replychat_s -{ - bot_replychatkey_t *keys; - float priority; - int numchatmessages; - bot_chatmessage_t *firstchatmessage; - struct bot_replychat_s *next; -} bot_replychat_t; - -//string list -typedef struct bot_stringlist_s -{ - char *string; - struct bot_stringlist_s *next; -} bot_stringlist_t; - -//chat state of a bot -typedef struct bot_chatstate_s -{ - int gender; //0=it, 1=female, 2=male - int client; //client number - char name[32]; //name of the bot - char chatmessage[MAX_MESSAGE_SIZE]; - int handle; - //the console messages visible to the bot - bot_consolemessage_t *firstmessage; //first message is the first typed message - bot_consolemessage_t *lastmessage; //last message is the last typed message, bottom of console - //number of console messages stored in the state - int numconsolemessages; - //the bot chat lines - bot_chat_t *chat; -} bot_chatstate_t; - -typedef struct { - bot_chat_t *chat; - char filename[MAX_QPATH]; - char chatname[MAX_QPATH]; -} bot_ichatdata_t; - -bot_ichatdata_t *ichatdata[MAX_CLIENTS]; - -bot_chatstate_t *botchatstates[MAX_CLIENTS+1]; -//console message heap -bot_consolemessage_t *consolemessageheap = NULL; -bot_consolemessage_t *freeconsolemessages = NULL; -//list with match strings -bot_matchtemplate_t *matchtemplates = NULL; -//list with synonyms -bot_synonymlist_t *synonyms = NULL; -//list with random strings -bot_randomlist_t *randomstrings = NULL; -//reply chats -bot_replychat_t *replychats = NULL; - -//======================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//======================================================================== -bot_chatstate_t *BotChatStateFromHandle(int handle) -{ - if (handle <= 0 || handle > MAX_CLIENTS) - { - botimport.Print(PRT_FATAL, "chat state handle %d out of range\n", handle); - return NULL; - } //end if - if (!botchatstates[handle]) - { - botimport.Print(PRT_FATAL, "invalid chat state %d\n", handle); - return NULL; - } //end if - return botchatstates[handle]; -} //end of the function BotChatStateFromHandle -//=========================================================================== -// initialize the heap with unused console messages -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void InitConsoleMessageHeap(void) -{ - int i, max_messages; - - if (consolemessageheap) FreeMemory(consolemessageheap); - // - max_messages = (int) LibVarValue("max_messages", "1024"); - consolemessageheap = (bot_consolemessage_t *) GetClearedHunkMemory(max_messages * - sizeof(bot_consolemessage_t)); - consolemessageheap[0].prev = NULL; - consolemessageheap[0].next = &consolemessageheap[1]; - for (i = 1; i < max_messages-1; i++) - { - consolemessageheap[i].prev = &consolemessageheap[i - 1]; - consolemessageheap[i].next = &consolemessageheap[i + 1]; - } //end for - consolemessageheap[max_messages-1].prev = &consolemessageheap[max_messages-2]; - consolemessageheap[max_messages-1].next = NULL; - //pointer to the free console messages - freeconsolemessages = consolemessageheap; -} //end of the function InitConsoleMessageHeap -//=========================================================================== -// allocate one console message from the heap -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bot_consolemessage_t *AllocConsoleMessage(void) -{ - bot_consolemessage_t *message; - message = freeconsolemessages; - if (freeconsolemessages) freeconsolemessages = freeconsolemessages->next; - if (freeconsolemessages) freeconsolemessages->prev = NULL; - return message; -} //end of the function AllocConsoleMessage -//=========================================================================== -// deallocate one console message from the heap -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void FreeConsoleMessage(bot_consolemessage_t *message) -{ - if (freeconsolemessages) freeconsolemessages->prev = message; - message->prev = NULL; - message->next = freeconsolemessages; - freeconsolemessages = message; -} //end of the function FreeConsoleMessage -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotRemoveConsoleMessage(int chatstate, int handle) -{ - bot_consolemessage_t *m, *nextm; - bot_chatstate_t *cs; - - cs = BotChatStateFromHandle(chatstate); - if (!cs) return; - - for (m = cs->firstmessage; m; m = nextm) - { - nextm = m->next; - if (m->handle == handle) - { - if (m->next) m->next->prev = m->prev; - else cs->lastmessage = m->prev; - if (m->prev) m->prev->next = m->next; - else cs->firstmessage = m->next; - - FreeConsoleMessage(m); - cs->numconsolemessages--; - break; - } //end if - } //end for -} //end of the function BotRemoveConsoleMessage -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotQueueConsoleMessage(int chatstate, int type, char *message) -{ - bot_consolemessage_t *m; - bot_chatstate_t *cs; - - cs = BotChatStateFromHandle(chatstate); - if (!cs) return; - - m = AllocConsoleMessage(); - if (!m) - { - botimport.Print(PRT_ERROR, "empty console message heap\n"); - return; - } //end if - cs->handle++; - if (cs->handle <= 0 || cs->handle > 8192) cs->handle = 1; - m->handle = cs->handle; - m->time = AAS_Time(); - m->type = type; - strncpy(m->message, message, MAX_MESSAGE_SIZE); - m->next = NULL; - if (cs->lastmessage) - { - cs->lastmessage->next = m; - m->prev = cs->lastmessage; - cs->lastmessage = m; - } //end if - else - { - cs->lastmessage = m; - cs->firstmessage = m; - m->prev = NULL; - } //end if - cs->numconsolemessages++; -} //end of the function BotQueueConsoleMessage -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotNextConsoleMessage(int chatstate, bot_consolemessage_t *cm) -{ - bot_chatstate_t *cs; - - cs = BotChatStateFromHandle(chatstate); - if (!cs) return 0; - if (cs->firstmessage) - { - Com_Memcpy(cm, cs->firstmessage, sizeof(bot_consolemessage_t)); - cm->next = cm->prev = NULL; - return cm->handle; - } //end if - return 0; -} //end of the function BotConsoleMessage -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotNumConsoleMessages(int chatstate) -{ - bot_chatstate_t *cs; - - cs = BotChatStateFromHandle(chatstate); - if (!cs) return 0; - return cs->numconsolemessages; -} //end of the function BotNumConsoleMessages -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int IsWhiteSpace(char c) -{ - if ((c >= 'a' && c <= 'z') - || (c >= 'A' && c <= 'Z') - || (c >= '0' && c <= '9') - || c == '(' || c == ')' - || c == '?' || c == ':' - || c == '\''|| c == '/' - || c == ',' || c == '.' - || c == '[' || c == ']' - || c == '-' || c == '_' - || c == '+' || c == '=') return qfalse; - return qtrue; -} //end of the function IsWhiteSpace -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotRemoveTildes(char *message) -{ - int i; - - //remove all tildes from the chat message - for (i = 0; message[i]; i++) - { - if (message[i] == '~') - { - memmove(&message[i], &message[i+1], strlen(&message[i+1])+1); - } //end if - } //end for -} //end of the function BotRemoveTildes -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void UnifyWhiteSpaces(char *string) -{ - char *ptr, *oldptr; - - for (ptr = oldptr = string; *ptr; oldptr = ptr) - { - while(*ptr && IsWhiteSpace(*ptr)) ptr++; - if (ptr > oldptr) - { - //if not at the start and not at the end of the string - //write only one space - if (oldptr > string && *ptr) *oldptr++ = ' '; - //remove all other white spaces - if (ptr > oldptr) memmove(oldptr, ptr, strlen(ptr)+1); - } //end if - while(*ptr && !IsWhiteSpace(*ptr)) ptr++; - } //end while -} //end of the function UnifyWhiteSpaces -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int StringContains(char *str1, char *str2, int casesensitive) -{ - int len, i, j, index; - - if (str1 == NULL || str2 == NULL) return -1; - - len = strlen(str1) - strlen(str2); - index = 0; - for (i = 0; i <= len; i++, str1++, index++) - { - for (j = 0; str2[j]; j++) - { - if (casesensitive) - { - if (str1[j] != str2[j]) break; - } //end if - else - { - if (toupper(str1[j]) != toupper(str2[j])) break; - } //end else - } //end for - if (!str2[j]) return index; - } //end for - return -1; -} //end of the function StringContains -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -char *StringContainsWord(char *str1, char *str2, int casesensitive) -{ - int len, i, j; - - len = strlen(str1) - strlen(str2); - for (i = 0; i <= len; i++, str1++) - { - //if not at the start of the string - if (i) - { - //skip to the start of the next word - while(*str1 && *str1 != ' ' && *str1 != '.' && *str1 != ',' && *str1 != '!') str1++; - if (!*str1) break; - str1++; - } //end for - //compare the word - for (j = 0; str2[j]; j++) - { - if (casesensitive) - { - if (str1[j] != str2[j]) break; - } //end if - else - { - if (toupper(str1[j]) != toupper(str2[j])) break; - } //end else - } //end for - //if there was a word match - if (!str2[j]) - { - //if the first string has an end of word - if (!str1[j] || str1[j] == ' ' || str1[j] == '.' || str1[j] == ',' || str1[j] == '!') return str1; - } //end if - } //end for - return NULL; -} //end of the function StringContainsWord -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void StringReplaceWords(char *string, char *synonym, char *replacement) -{ - char *str, *str2; - - //find the synonym in the string - str = StringContainsWord(string, synonym, qfalse); - //if the synonym occured in the string - while(str) - { - //if the synonym isn't part of the replacement which is already in the string - //usefull for abreviations - str2 = StringContainsWord(string, replacement, qfalse); - while(str2) - { - if (str2 <= str && str < str2 + strlen(replacement)) break; - str2 = StringContainsWord(str2+1, replacement, qfalse); - } //end while - if (!str2) - { - memmove(str + strlen(replacement), str+strlen(synonym), strlen(str+strlen(synonym))+1); - //append the synonum replacement - Com_Memcpy(str, replacement, strlen(replacement)); - } //end if - //find the next synonym in the string - str = StringContainsWord(str+strlen(replacement), synonym, qfalse); - } //end if -} //end of the function StringReplaceWords -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotDumpSynonymList(bot_synonymlist_t *synlist) -{ - FILE *fp; - bot_synonymlist_t *syn; - bot_synonym_t *synonym; - - fp = Log_FilePointer(); - if (!fp) return; - for (syn = synlist; syn; syn = syn->next) - { - fprintf(fp, "%ld : [", syn->context); - for (synonym = syn->firstsynonym; synonym; synonym = synonym->next) - { - fprintf(fp, "(\"%s\", %1.2f)", synonym->string, synonym->weight); - if (synonym->next) fprintf(fp, ", "); - } //end for - fprintf(fp, "]\n"); - } //end for -} //end of the function BotDumpSynonymList -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bot_synonymlist_t *BotLoadSynonyms(char *filename) -{ - int pass, size, contextlevel, numsynonyms; - unsigned long int context, contextstack[32]; - char *ptr = NULL; - source_t *source; - token_t token; - bot_synonymlist_t *synlist, *lastsyn, *syn; - bot_synonym_t *synonym, *lastsynonym; - - size = 0; - synlist = NULL; //make compiler happy - syn = NULL; //make compiler happy - synonym = NULL; //make compiler happy - //the synonyms are parsed in two phases - for (pass = 0; pass < 2; pass++) - { - // - if (pass && size) ptr = (char *) GetClearedHunkMemory(size); - // - PC_SetBaseFolder(BOTFILESBASEFOLDER); - source = LoadSourceFile(filename); - if (!source) - { - botimport.Print(PRT_ERROR, "counldn't load %s\n", filename); - return NULL; - } //end if - // - context = 0; - contextlevel = 0; - synlist = NULL; //list synonyms - lastsyn = NULL; //last synonym in the list - // - while(PC_ReadToken(source, &token)) - { - if (token.type == TT_NUMBER) - { - context |= token.intvalue; - contextstack[contextlevel] = token.intvalue; - contextlevel++; - if (contextlevel >= 32) - { - SourceError(source, "more than 32 context levels"); - FreeSource(source); - return NULL; - } //end if - if (!PC_ExpectTokenString(source, "{")) - { - FreeSource(source); - return NULL; - } //end if - } //end if - else if (token.type == TT_PUNCTUATION) - { - if (!strcmp(token.string, "}")) - { - contextlevel--; - if (contextlevel < 0) - { - SourceError(source, "too many }"); - FreeSource(source); - return NULL; - } //end if - context &= ~contextstack[contextlevel]; - } //end if - else if (!strcmp(token.string, "[")) - { - size += sizeof(bot_synonymlist_t); - if (pass) - { - syn = (bot_synonymlist_t *) ptr; - ptr += sizeof(bot_synonymlist_t); - syn->context = context; - syn->firstsynonym = NULL; - syn->next = NULL; - if (lastsyn) lastsyn->next = syn; - else synlist = syn; - lastsyn = syn; - } //end if - numsynonyms = 0; - lastsynonym = NULL; - while(1) - { - if (!PC_ExpectTokenString(source, "(") || - !PC_ExpectTokenType(source, TT_STRING, 0, &token)) - { - FreeSource(source); - return NULL; - } //end if - StripDoubleQuotes(token.string); - if (strlen(token.string) <= 0) - { - SourceError(source, "empty string", token.string); - FreeSource(source); - return NULL; - } //end if - size += sizeof(bot_synonym_t) + strlen(token.string) + 1; - if (pass) - { - synonym = (bot_synonym_t *) ptr; - ptr += sizeof(bot_synonym_t); - synonym->string = ptr; - ptr += strlen(token.string) + 1; - strcpy(synonym->string, token.string); - // - if (lastsynonym) lastsynonym->next = synonym; - else syn->firstsynonym = synonym; - lastsynonym = synonym; - } //end if - numsynonyms++; - if (!PC_ExpectTokenString(source, ",") || - !PC_ExpectTokenType(source, TT_NUMBER, 0, &token) || - !PC_ExpectTokenString(source, ")")) - { - FreeSource(source); - return NULL; - } //end if - if (pass) - { - synonym->weight = token.floatvalue; - syn->totalweight += synonym->weight; - } //end if - if (PC_CheckTokenString(source, "]")) break; - if (!PC_ExpectTokenString(source, ",")) - { - FreeSource(source); - return NULL; - } //end if - } //end while - if (numsynonyms < 2) - { - SourceError(source, "synonym must have at least two entries\n"); - FreeSource(source); - return NULL; - } //end if - } //end else - else - { - SourceError(source, "unexpected %s", token.string); - FreeSource(source); - return NULL; - } //end if - } //end else if - } //end while - // - FreeSource(source); - // - if (contextlevel > 0) - { - SourceError(source, "missing }"); - return NULL; - } //end if - } //end for - botimport.Print(PRT_MESSAGE, "loaded %s\n", filename); - // - //BotDumpSynonymList(synlist); - // - return synlist; -} //end of the function BotLoadSynonyms -//=========================================================================== -// replace all the synonyms in the string -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotReplaceSynonyms(char *string, unsigned long int context) -{ - bot_synonymlist_t *syn; - bot_synonym_t *synonym; - - for (syn = synonyms; syn; syn = syn->next) - { - if (!(syn->context & context)) continue; - for (synonym = syn->firstsynonym->next; synonym; synonym = synonym->next) - { - StringReplaceWords(string, synonym->string, syn->firstsynonym->string); - } //end for - } //end for -} //end of the function BotReplaceSynonyms -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotReplaceWeightedSynonyms(char *string, unsigned long int context) -{ - bot_synonymlist_t *syn; - bot_synonym_t *synonym, *replacement; - float weight, curweight; - - for (syn = synonyms; syn; syn = syn->next) - { - if (!(syn->context & context)) continue; - //choose a weighted random replacement synonym - weight = random() * syn->totalweight; - if (!weight) continue; - curweight = 0; - for (replacement = syn->firstsynonym; replacement; replacement = replacement->next) - { - curweight += replacement->weight; - if (weight < curweight) break; - } //end for - if (!replacement) continue; - //replace all synonyms with the replacement - for (synonym = syn->firstsynonym; synonym; synonym = synonym->next) - { - if (synonym == replacement) continue; - StringReplaceWords(string, synonym->string, replacement->string); - } //end for - } //end for -} //end of the function BotReplaceWeightedSynonyms -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotReplaceReplySynonyms(char *string, unsigned long int context) -{ - char *str1, *str2, *replacement; - bot_synonymlist_t *syn; - bot_synonym_t *synonym; - - for (str1 = string; *str1; ) - { - //go to the start of the next word - while(*str1 && *str1 <= ' ') str1++; - if (!*str1) break; - // - for (syn = synonyms; syn; syn = syn->next) - { - if (!(syn->context & context)) continue; - for (synonym = syn->firstsynonym->next; synonym; synonym = synonym->next) - { - str2 = synonym->string; - //if the synonym is not at the front of the string continue - str2 = StringContainsWord(str1, synonym->string, qfalse); - if (!str2 || str2 != str1) continue; - // - replacement = syn->firstsynonym->string; - //if the replacement IS in front of the string continue - str2 = StringContainsWord(str1, replacement, qfalse); - if (str2 && str2 == str1) continue; - // - memmove(str1 + strlen(replacement), str1+strlen(synonym->string), - strlen(str1+strlen(synonym->string)) + 1); - //append the synonum replacement - Com_Memcpy(str1, replacement, strlen(replacement)); - // - break; - } //end for - //if a synonym has been replaced - if (synonym) break; - } //end for - //skip over this word - while(*str1 && *str1 > ' ') str1++; - if (!*str1) break; - } //end while -} //end of the function BotReplaceReplySynonyms -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotLoadChatMessage(source_t *source, char *chatmessagestring) -{ - char *ptr; - token_t token; - - ptr = chatmessagestring; - *ptr = 0; - // - while(1) - { - if (!PC_ExpectAnyToken(source, &token)) return qfalse; - //fixed string - if (token.type == TT_STRING) - { - StripDoubleQuotes(token.string); - if (strlen(ptr) + strlen(token.string) + 1 > MAX_MESSAGE_SIZE) - { - SourceError(source, "chat message too long\n"); - return qfalse; - } //end if - strcat(ptr, token.string); - } //end else if - //variable string - else if (token.type == TT_NUMBER && (token.subtype & TT_INTEGER)) - { - if (strlen(ptr) + 7 > MAX_MESSAGE_SIZE) - { - SourceError(source, "chat message too long\n"); - return qfalse; - } //end if - sprintf(&ptr[strlen(ptr)], "%cv%ld%c", ESCAPE_CHAR, token.intvalue, ESCAPE_CHAR); - } //end if - //random string - else if (token.type == TT_NAME) - { - if (strlen(ptr) + 7 > MAX_MESSAGE_SIZE) - { - SourceError(source, "chat message too long\n"); - return qfalse; - } //end if - sprintf(&ptr[strlen(ptr)], "%cr%s%c", ESCAPE_CHAR, token.string, ESCAPE_CHAR); - } //end else if - else - { - SourceError(source, "unknown message component %s\n", token.string); - return qfalse; - } //end else - if (PC_CheckTokenString(source, ";")) break; - if (!PC_ExpectTokenString(source, ",")) return qfalse; - } //end while - // - return qtrue; -} //end of the function BotLoadChatMessage -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotDumpRandomStringList(bot_randomlist_t *randomlist) -{ - FILE *fp; - bot_randomlist_t *random; - bot_randomstring_t *rs; - - fp = Log_FilePointer(); - if (!fp) return; - for (random = randomlist; random; random = random->next) - { - fprintf(fp, "%s = {", random->string); - for (rs = random->firstrandomstring; rs; rs = rs->next) - { - fprintf(fp, "\"%s\"", rs->string); - if (rs->next) fprintf(fp, ", "); - else fprintf(fp, "}\n"); - } //end for - } //end for -} //end of the function BotDumpRandomStringList -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bot_randomlist_t *BotLoadRandomStrings(char *filename) -{ - int pass, size; - char *ptr = NULL, chatmessagestring[MAX_MESSAGE_SIZE]; - source_t *source; - token_t token; - bot_randomlist_t *randomlist, *lastrandom, *random; - bot_randomstring_t *randomstring; - -#ifdef DEBUG - int starttime = Sys_MilliSeconds(); -#endif //DEBUG - - size = 0; - randomlist = NULL; - random = NULL; - //the synonyms are parsed in two phases - for (pass = 0; pass < 2; pass++) - { - // - if (pass && size) ptr = (char *) GetClearedHunkMemory(size); - // - PC_SetBaseFolder(BOTFILESBASEFOLDER); - source = LoadSourceFile(filename); - if (!source) - { - botimport.Print(PRT_ERROR, "counldn't load %s\n", filename); - return NULL; - } //end if - // - randomlist = NULL; //list - lastrandom = NULL; //last - // - while(PC_ReadToken(source, &token)) - { - if (token.type != TT_NAME) - { - SourceError(source, "unknown random %s", token.string); - FreeSource(source); - return NULL; - } //end if - size += sizeof(bot_randomlist_t) + strlen(token.string) + 1; - if (pass) - { - random = (bot_randomlist_t *) ptr; - ptr += sizeof(bot_randomlist_t); - random->string = ptr; - ptr += strlen(token.string) + 1; - strcpy(random->string, token.string); - random->firstrandomstring = NULL; - random->numstrings = 0; - // - if (lastrandom) lastrandom->next = random; - else randomlist = random; - lastrandom = random; - } //end if - if (!PC_ExpectTokenString(source, "=") || - !PC_ExpectTokenString(source, "{")) - { - FreeSource(source); - return NULL; - } //end if - while(!PC_CheckTokenString(source, "}")) - { - if (!BotLoadChatMessage(source, chatmessagestring)) - { - FreeSource(source); - return NULL; - } //end if - size += sizeof(bot_randomstring_t) + strlen(chatmessagestring) + 1; - if (pass) - { - randomstring = (bot_randomstring_t *) ptr; - ptr += sizeof(bot_randomstring_t); - randomstring->string = ptr; - ptr += strlen(chatmessagestring) + 1; - strcpy(randomstring->string, chatmessagestring); - // - random->numstrings++; - randomstring->next = random->firstrandomstring; - random->firstrandomstring = randomstring; - } //end if - } //end while - } //end while - //free the source after one pass - FreeSource(source); - } //end for - botimport.Print(PRT_MESSAGE, "loaded %s\n", filename); - // -#ifdef DEBUG - botimport.Print(PRT_MESSAGE, "random strings %d msec\n", Sys_MilliSeconds() - starttime); - //BotDumpRandomStringList(randomlist); -#endif //DEBUG - // - return randomlist; -} //end of the function BotLoadRandomStrings -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -char *RandomString(char *name) -{ - bot_randomlist_t *random; - bot_randomstring_t *rs; - int i; - - for (random = randomstrings; random; random = random->next) - { - if (!strcmp(random->string, name)) - { - i = random() * random->numstrings; - for (rs = random->firstrandomstring; rs; rs = rs->next) - { - if (--i < 0) break; - } //end for - if (rs) - { - return rs->string; - } //end if - } //end for - } //end for - return NULL; -} //end of the function RandomString -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotDumpMatchTemplates(bot_matchtemplate_t *matches) -{ - FILE *fp; - bot_matchtemplate_t *mt; - bot_matchpiece_t *mp; - bot_matchstring_t *ms; - - fp = Log_FilePointer(); - if (!fp) return; - for (mt = matches; mt; mt = mt->next) - { - fprintf(fp, "{ " ); - for (mp = mt->first; mp; mp = mp->next) - { - if (mp->type == MT_STRING) - { - for (ms = mp->firststring; ms; ms = ms->next) - { - fprintf(fp, "\"%s\"", ms->string); - if (ms->next) fprintf(fp, "|"); - } //end for - } //end if - else if (mp->type == MT_VARIABLE) - { - fprintf(fp, "%d", mp->variable); - } //end else if - if (mp->next) fprintf(fp, ", "); - } //end for - fprintf(fp, " = (%d, %d);}\n", mt->type, mt->subtype); - } //end for -} //end of the function BotDumpMatchTemplates -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotFreeMatchPieces(bot_matchpiece_t *matchpieces) -{ - bot_matchpiece_t *mp, *nextmp; - bot_matchstring_t *ms, *nextms; - - for (mp = matchpieces; mp; mp = nextmp) - { - nextmp = mp->next; - if (mp->type == MT_STRING) - { - for (ms = mp->firststring; ms; ms = nextms) - { - nextms = ms->next; - FreeMemory(ms); - } //end for - } //end if - FreeMemory(mp); - } //end for -} //end of the function BotFreeMatchPieces -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bot_matchpiece_t *BotLoadMatchPieces(source_t *source, char *endtoken) -{ - int lastwasvariable, emptystring; - token_t token; - bot_matchpiece_t *matchpiece, *firstpiece, *lastpiece; - bot_matchstring_t *matchstring, *lastmatchstring; - - firstpiece = NULL; - lastpiece = NULL; - // - lastwasvariable = qfalse; - // - while(PC_ReadToken(source, &token)) - { - if (token.type == TT_NUMBER && (token.subtype & TT_INTEGER)) - { - if (token.intvalue < 0 || token.intvalue >= MAX_MATCHVARIABLES) - { - SourceError(source, "can't have more than %d match variables\n", MAX_MATCHVARIABLES); - FreeSource(source); - BotFreeMatchPieces(firstpiece); - return NULL; - } //end if - if (lastwasvariable) - { - SourceError(source, "not allowed to have adjacent variables\n"); - FreeSource(source); - BotFreeMatchPieces(firstpiece); - return NULL; - } //end if - lastwasvariable = qtrue; - // - matchpiece = (bot_matchpiece_t *) GetClearedHunkMemory(sizeof(bot_matchpiece_t)); - matchpiece->type = MT_VARIABLE; - matchpiece->variable = token.intvalue; - matchpiece->next = NULL; - if (lastpiece) lastpiece->next = matchpiece; - else firstpiece = matchpiece; - lastpiece = matchpiece; - } //end if - else if (token.type == TT_STRING) - { - // - matchpiece = (bot_matchpiece_t *) GetClearedHunkMemory(sizeof(bot_matchpiece_t)); - matchpiece->firststring = NULL; - matchpiece->type = MT_STRING; - matchpiece->variable = 0; - matchpiece->next = NULL; - if (lastpiece) lastpiece->next = matchpiece; - else firstpiece = matchpiece; - lastpiece = matchpiece; - // - lastmatchstring = NULL; - emptystring = qfalse; - // - do - { - if (matchpiece->firststring) - { - if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) - { - FreeSource(source); - BotFreeMatchPieces(firstpiece); - return NULL; - } //end if - } //end if - StripDoubleQuotes(token.string); - matchstring = (bot_matchstring_t *) GetClearedHunkMemory(sizeof(bot_matchstring_t) + strlen(token.string) + 1); - matchstring->string = (char *) matchstring + sizeof(bot_matchstring_t); - strcpy(matchstring->string, token.string); - if (!strlen(token.string)) emptystring = qtrue; - matchstring->next = NULL; - if (lastmatchstring) lastmatchstring->next = matchstring; - else matchpiece->firststring = matchstring; - lastmatchstring = matchstring; - } while(PC_CheckTokenString(source, "|")); - //if there was no empty string found - if (!emptystring) lastwasvariable = qfalse; - } //end if - else - { - SourceError(source, "invalid token %s\n", token.string); - FreeSource(source); - BotFreeMatchPieces(firstpiece); - return NULL; - } //end else - if (PC_CheckTokenString(source, endtoken)) break; - if (!PC_ExpectTokenString(source, ",")) - { - FreeSource(source); - BotFreeMatchPieces(firstpiece); - return NULL; - } //end if - } //end while - return firstpiece; -} //end of the function BotLoadMatchPieces -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotFreeMatchTemplates(bot_matchtemplate_t *mt) -{ - bot_matchtemplate_t *nextmt; - - for (; mt; mt = nextmt) - { - nextmt = mt->next; - BotFreeMatchPieces(mt->first); - FreeMemory(mt); - } //end for -} //end of the function BotFreeMatchTemplates -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bot_matchtemplate_t *BotLoadMatchTemplates(char *matchfile) -{ - source_t *source; - token_t token; - bot_matchtemplate_t *matchtemplate, *matches, *lastmatch; - unsigned long int context; - - PC_SetBaseFolder(BOTFILESBASEFOLDER); - source = LoadSourceFile(matchfile); - if (!source) - { - botimport.Print(PRT_ERROR, "counldn't load %s\n", matchfile); - return NULL; - } //end if - // - matches = NULL; //list with matches - lastmatch = NULL; //last match in the list - - while(PC_ReadToken(source, &token)) - { - if (token.type != TT_NUMBER || !(token.subtype & TT_INTEGER)) - { - SourceError(source, "expected integer, found %s\n", token.string); - BotFreeMatchTemplates(matches); - FreeSource(source); - return NULL; - } //end if - //the context - context = token.intvalue; - // - if (!PC_ExpectTokenString(source, "{")) - { - BotFreeMatchTemplates(matches); - FreeSource(source); - return NULL; - } //end if - // - while(PC_ReadToken(source, &token)) - { - if (!strcmp(token.string, "}")) break; - // - PC_UnreadLastToken(source); - // - matchtemplate = (bot_matchtemplate_t *) GetClearedHunkMemory(sizeof(bot_matchtemplate_t)); - matchtemplate->context = context; - matchtemplate->next = NULL; - //add the match template to the list - if (lastmatch) lastmatch->next = matchtemplate; - else matches = matchtemplate; - lastmatch = matchtemplate; - //load the match template - matchtemplate->first = BotLoadMatchPieces(source, "="); - if (!matchtemplate->first) - { - BotFreeMatchTemplates(matches); - return NULL; - } //end if - //read the match type - if (!PC_ExpectTokenString(source, "(") || - !PC_ExpectTokenType(source, TT_NUMBER, TT_INTEGER, &token)) - { - BotFreeMatchTemplates(matches); - FreeSource(source); - return NULL; - } //end if - matchtemplate->type = token.intvalue; - //read the match subtype - if (!PC_ExpectTokenString(source, ",") || - !PC_ExpectTokenType(source, TT_NUMBER, TT_INTEGER, &token)) - { - BotFreeMatchTemplates(matches); - FreeSource(source); - return NULL; - } //end if - matchtemplate->subtype = token.intvalue; - //read trailing punctuations - if (!PC_ExpectTokenString(source, ")") || - !PC_ExpectTokenString(source, ";")) - { - BotFreeMatchTemplates(matches); - FreeSource(source); - return NULL; - } //end if - } //end while - } //end while - //free the source - FreeSource(source); - botimport.Print(PRT_MESSAGE, "loaded %s\n", matchfile); - // - //BotDumpMatchTemplates(matches); - // - return matches; -} //end of the function BotLoadMatchTemplates -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int StringsMatch(bot_matchpiece_t *pieces, bot_match_t *match) -{ - int lastvariable, index; - char *strptr, *newstrptr; - bot_matchpiece_t *mp; - bot_matchstring_t *ms; - - //no last variable - lastvariable = -1; - //pointer to the string to compare the match string with - strptr = match->string; - //Log_Write("match: %s", strptr); - //compare the string with the current match string - for (mp = pieces; mp; mp = mp->next) - { - //if it is a piece of string - if (mp->type == MT_STRING) - { - newstrptr = NULL; - for (ms = mp->firststring; ms; ms = ms->next) - { - if (!strlen(ms->string)) - { - newstrptr = strptr; - break; - } //end if - //Log_Write("MT_STRING: %s", mp->string); - index = StringContains(strptr, ms->string, qfalse); - if (index >= 0) - { - newstrptr = strptr + index; - if (lastvariable >= 0) - { - match->variables[lastvariable].length = - (newstrptr - match->string) - match->variables[lastvariable].offset; - //newstrptr - match->variables[lastvariable].ptr; - lastvariable = -1; - break; - } //end if - else if (index == 0) - { - break; - } //end else - newstrptr = NULL; - } //end if - } //end for - if (!newstrptr) return qfalse; - strptr = newstrptr + strlen(ms->string); - } //end if - //if it is a variable piece of string - else if (mp->type == MT_VARIABLE) - { - //Log_Write("MT_VARIABLE"); - match->variables[mp->variable].offset = strptr - match->string; - lastvariable = mp->variable; - } //end else if - } //end for - //if a match was found - if (!mp && (lastvariable >= 0 || !strlen(strptr))) - { - //if the last piece was a variable string - if (lastvariable >= 0) - { - assert( match->variables[lastvariable].offset >= 0 ); // bk001204 - match->variables[lastvariable].length = - strlen(&match->string[ (int) match->variables[lastvariable].offset]); - } //end if - return qtrue; - } //end if - return qfalse; -} //end of the function StringsMatch -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotFindMatch(char *str, bot_match_t *match, unsigned long int context) -{ - int i; - bot_matchtemplate_t *ms; - - strncpy(match->string, str, MAX_MESSAGE_SIZE); - //remove any trailing enters - while(strlen(match->string) && - match->string[strlen(match->string)-1] == '\n') - { - match->string[strlen(match->string)-1] = '\0'; - } //end while - //compare the string with all the match strings - for (ms = matchtemplates; ms; ms = ms->next) - { - if (!(ms->context & context)) continue; - //reset the match variable offsets - for (i = 0; i < MAX_MATCHVARIABLES; i++) match->variables[i].offset = -1; - // - if (StringsMatch(ms->first, match)) - { - match->type = ms->type; - match->subtype = ms->subtype; - return qtrue; - } //end if - } //end for - return qfalse; -} //end of the function BotFindMatch -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotMatchVariable(bot_match_t *match, int variable, char *buf, int size) -{ - if (variable < 0 || variable >= MAX_MATCHVARIABLES) - { - botimport.Print(PRT_FATAL, "BotMatchVariable: variable out of range\n"); - strcpy(buf, ""); - return; - } //end if - - if (match->variables[variable].offset >= 0) - { - if (match->variables[variable].length < size) - size = match->variables[variable].length+1; - assert( match->variables[variable].offset >= 0 ); // bk001204 - strncpy(buf, &match->string[ (int) match->variables[variable].offset], size-1); - buf[size-1] = '\0'; - } //end if - else - { - strcpy(buf, ""); - } //end else - return; -} //end of the function BotMatchVariable -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bot_stringlist_t *BotFindStringInList(bot_stringlist_t *list, char *string) -{ - bot_stringlist_t *s; - - for (s = list; s; s = s->next) - { - if (!strcmp(s->string, string)) return s; - } //end for - return NULL; -} //end of the function BotFindStringInList -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bot_stringlist_t *BotCheckChatMessageIntegrety(char *message, bot_stringlist_t *stringlist) -{ - int i; - char *msgptr; - char temp[MAX_MESSAGE_SIZE]; - bot_stringlist_t *s; - - msgptr = message; - // - while(*msgptr) - { - if (*msgptr == ESCAPE_CHAR) - { - msgptr++; - switch(*msgptr) - { - case 'v': //variable - { - //step over the 'v' - msgptr++; - while(*msgptr && *msgptr != ESCAPE_CHAR) msgptr++; - //step over the trailing escape char - if (*msgptr) msgptr++; - break; - } //end case - case 'r': //random - { - //step over the 'r' - msgptr++; - for (i = 0; (*msgptr && *msgptr != ESCAPE_CHAR); i++) - { - temp[i] = *msgptr++; - } //end while - temp[i] = '\0'; - //step over the trailing escape char - if (*msgptr) msgptr++; - //find the random keyword - if (!RandomString(temp)) - { - if (!BotFindStringInList(stringlist, temp)) - { - Log_Write("%s = {\"%s\"} //MISSING RANDOM\r\n", temp, temp); - s = GetClearedMemory(sizeof(bot_stringlist_t) + strlen(temp) + 1); - s->string = (char *) s + sizeof(bot_stringlist_t); - strcpy(s->string, temp); - s->next = stringlist; - stringlist = s; - } //end if - } //end if - break; - } //end case - default: - { - botimport.Print(PRT_FATAL, "BotCheckChatMessageIntegrety: message \"%s\" invalid escape char\n", message); - break; - } //end default - } //end switch - } //end if - else - { - msgptr++; - } //end else - } //end while - return stringlist; -} //end of the function BotCheckChatMessageIntegrety -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotCheckInitialChatIntegrety(bot_chat_t *chat) -{ - bot_chattype_t *t; - bot_chatmessage_t *cm; - bot_stringlist_t *stringlist, *s, *nexts; - - stringlist = NULL; - for (t = chat->types; t; t = t->next) - { - for (cm = t->firstchatmessage; cm; cm = cm->next) - { - stringlist = BotCheckChatMessageIntegrety(cm->chatmessage, stringlist); - } //end for - } //end for - for (s = stringlist; s; s = nexts) - { - nexts = s->next; - FreeMemory(s); - } //end for -} //end of the function BotCheckInitialChatIntegrety -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotCheckReplyChatIntegrety(bot_replychat_t *replychat) -{ - bot_replychat_t *rp; - bot_chatmessage_t *cm; - bot_stringlist_t *stringlist, *s, *nexts; - - stringlist = NULL; - for (rp = replychat; rp; rp = rp->next) - { - for (cm = rp->firstchatmessage; cm; cm = cm->next) - { - stringlist = BotCheckChatMessageIntegrety(cm->chatmessage, stringlist); - } //end for - } //end for - for (s = stringlist; s; s = nexts) - { - nexts = s->next; - FreeMemory(s); - } //end for -} //end of the function BotCheckReplyChatIntegrety -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotDumpReplyChat(bot_replychat_t *replychat) -{ - FILE *fp; - bot_replychat_t *rp; - bot_replychatkey_t *key; - bot_chatmessage_t *cm; - bot_matchpiece_t *mp; - - fp = Log_FilePointer(); - if (!fp) return; - fprintf(fp, "BotDumpReplyChat:\n"); - for (rp = replychat; rp; rp = rp->next) - { - fprintf(fp, "["); - for (key = rp->keys; key; key = key->next) - { - if (key->flags & RCKFL_AND) fprintf(fp, "&"); - else if (key->flags & RCKFL_NOT) fprintf(fp, "!"); - // - if (key->flags & RCKFL_NAME) fprintf(fp, "name"); - else if (key->flags & RCKFL_GENDERFEMALE) fprintf(fp, "female"); - else if (key->flags & RCKFL_GENDERMALE) fprintf(fp, "male"); - else if (key->flags & RCKFL_GENDERLESS) fprintf(fp, "it"); - else if (key->flags & RCKFL_VARIABLES) - { - fprintf(fp, "("); - for (mp = key->match; mp; mp = mp->next) - { - if (mp->type == MT_STRING) fprintf(fp, "\"%s\"", mp->firststring->string); - else fprintf(fp, "%d", mp->variable); - if (mp->next) fprintf(fp, ", "); - } //end for - fprintf(fp, ")"); - } //end if - else if (key->flags & RCKFL_STRING) - { - fprintf(fp, "\"%s\"", key->string); - } //end if - if (key->next) fprintf(fp, ", "); - else fprintf(fp, "] = %1.0f\n", rp->priority); - } //end for - fprintf(fp, "{\n"); - for (cm = rp->firstchatmessage; cm; cm = cm->next) - { - fprintf(fp, "\t\"%s\";\n", cm->chatmessage); - } //end for - fprintf(fp, "}\n"); - } //end for -} //end of the function BotDumpReplyChat -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotFreeReplyChat(bot_replychat_t *replychat) -{ - bot_replychat_t *rp, *nextrp; - bot_replychatkey_t *key, *nextkey; - bot_chatmessage_t *cm, *nextcm; - - for (rp = replychat; rp; rp = nextrp) - { - nextrp = rp->next; - for (key = rp->keys; key; key = nextkey) - { - nextkey = key->next; - if (key->match) BotFreeMatchPieces(key->match); - if (key->string) FreeMemory(key->string); - FreeMemory(key); - } //end for - for (cm = rp->firstchatmessage; cm; cm = nextcm) - { - nextcm = cm->next; - FreeMemory(cm); - } //end for - FreeMemory(rp); - } //end for -} //end of the function BotFreeReplyChat -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotCheckValidReplyChatKeySet(source_t *source, bot_replychatkey_t *keys) -{ - int allprefixed, hasvariableskey, hasstringkey; - bot_matchpiece_t *m; - bot_matchstring_t *ms; - bot_replychatkey_t *key, *key2; - - // - allprefixed = qtrue; - hasvariableskey = hasstringkey = qfalse; - for (key = keys; key; key = key->next) - { - if (!(key->flags & (RCKFL_AND|RCKFL_NOT))) - { - allprefixed = qfalse; - if (key->flags & RCKFL_VARIABLES) - { - for (m = key->match; m; m = m->next) - { - if (m->type == MT_VARIABLE) hasvariableskey = qtrue; - } //end for - } //end if - else if (key->flags & RCKFL_STRING) - { - hasstringkey = qtrue; - } //end else if - } //end if - else if ((key->flags & RCKFL_AND) && (key->flags & RCKFL_STRING)) - { - for (key2 = keys; key2; key2 = key2->next) - { - if (key2 == key) continue; - if (key2->flags & RCKFL_NOT) continue; - if (key2->flags & RCKFL_VARIABLES) - { - for (m = key2->match; m; m = m->next) - { - if (m->type == MT_STRING) - { - for (ms = m->firststring; ms; ms = ms->next) - { - if (StringContains(ms->string, key->string, qfalse) != -1) - { - break; - } //end if - } //end for - if (ms) break; - } //end if - else if (m->type == MT_VARIABLE) - { - break; - } //end if - } //end for - if (!m) - { - SourceWarning(source, "one of the match templates does not " - "leave space for the key %s with the & prefix", key->string); - } //end if - } //end if - } //end for - } //end else - if ((key->flags & RCKFL_NOT) && (key->flags & RCKFL_STRING)) - { - for (key2 = keys; key2; key2 = key2->next) - { - if (key2 == key) continue; - if (key2->flags & RCKFL_NOT) continue; - if (key2->flags & RCKFL_STRING) - { - if (StringContains(key2->string, key->string, qfalse) != -1) - { - SourceWarning(source, "the key %s with prefix ! is inside the key %s", key->string, key2->string); - } //end if - } //end if - else if (key2->flags & RCKFL_VARIABLES) - { - for (m = key2->match; m; m = m->next) - { - if (m->type == MT_STRING) - { - for (ms = m->firststring; ms; ms = ms->next) - { - if (StringContains(ms->string, key->string, qfalse) != -1) - { - SourceWarning(source, "the key %s with prefix ! is inside " - "the match template string %s", key->string, ms->string); - } //end if - } //end for - } //end if - } //end for - } //end else if - } //end for - } //end if - } //end for - if (allprefixed) SourceWarning(source, "all keys have a & or ! prefix"); - if (hasvariableskey && hasstringkey) - { - SourceWarning(source, "variables from the match template(s) could be " - "invalid when outputting one of the chat messages"); - } //end if -} //end of the function BotCheckValidReplyChatKeySet -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bot_replychat_t *BotLoadReplyChat(char *filename) -{ - char chatmessagestring[MAX_MESSAGE_SIZE]; - char namebuffer[MAX_MESSAGE_SIZE]; - source_t *source; - token_t token; - bot_chatmessage_t *chatmessage = NULL; - bot_replychat_t *replychat, *replychatlist; - bot_replychatkey_t *key; - - PC_SetBaseFolder(BOTFILESBASEFOLDER); - source = LoadSourceFile(filename); - if (!source) - { - botimport.Print(PRT_ERROR, "counldn't load %s\n", filename); - return NULL; - } //end if - // - replychatlist = NULL; - // - while(PC_ReadToken(source, &token)) - { - if (strcmp(token.string, "[")) - { - SourceError(source, "expected [, found %s", token.string); - BotFreeReplyChat(replychatlist); - FreeSource(source); - return NULL; - } //end if - // - replychat = GetClearedHunkMemory(sizeof(bot_replychat_t)); - replychat->keys = NULL; - replychat->next = replychatlist; - replychatlist = replychat; - //read the keys, there must be at least one key - do - { - //allocate a key - key = (bot_replychatkey_t *) GetClearedHunkMemory(sizeof(bot_replychatkey_t)); - key->flags = 0; - key->string = NULL; - key->match = NULL; - key->next = replychat->keys; - replychat->keys = key; - //check for MUST BE PRESENT and MUST BE ABSENT keys - if (PC_CheckTokenString(source, "&")) key->flags |= RCKFL_AND; - else if (PC_CheckTokenString(source, "!")) key->flags |= RCKFL_NOT; - //special keys - if (PC_CheckTokenString(source, "name")) key->flags |= RCKFL_NAME; - else if (PC_CheckTokenString(source, "female")) key->flags |= RCKFL_GENDERFEMALE; - else if (PC_CheckTokenString(source, "male")) key->flags |= RCKFL_GENDERMALE; - else if (PC_CheckTokenString(source, "it")) key->flags |= RCKFL_GENDERLESS; - else if (PC_CheckTokenString(source, "(")) //match key - { - key->flags |= RCKFL_VARIABLES; - key->match = BotLoadMatchPieces(source, ")"); - if (!key->match) - { - BotFreeReplyChat(replychatlist); - return NULL; - } //end if - } //end else if - else if (PC_CheckTokenString(source, "<")) //bot names - { - key->flags |= RCKFL_BOTNAMES; - strcpy(namebuffer, ""); - do - { - if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) - { - BotFreeReplyChat(replychatlist); - FreeSource(source); - return NULL; - } //end if - StripDoubleQuotes(token.string); - if (strlen(namebuffer)) strcat(namebuffer, "\\"); - strcat(namebuffer, token.string); - } while(PC_CheckTokenString(source, ",")); - if (!PC_ExpectTokenString(source, ">")) - { - BotFreeReplyChat(replychatlist); - FreeSource(source); - return NULL; - } //end if - key->string = (char *) GetClearedHunkMemory(strlen(namebuffer) + 1); - strcpy(key->string, namebuffer); - } //end else if - else //normal string key - { - key->flags |= RCKFL_STRING; - if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) - { - BotFreeReplyChat(replychatlist); - FreeSource(source); - return NULL; - } //end if - StripDoubleQuotes(token.string); - key->string = (char *) GetClearedHunkMemory(strlen(token.string) + 1); - strcpy(key->string, token.string); - } //end else - // - PC_CheckTokenString(source, ","); - } while(!PC_CheckTokenString(source, "]")); - // - BotCheckValidReplyChatKeySet(source, replychat->keys); - //read the = sign and the priority - if (!PC_ExpectTokenString(source, "=") || - !PC_ExpectTokenType(source, TT_NUMBER, 0, &token)) - { - BotFreeReplyChat(replychatlist); - FreeSource(source); - return NULL; - } //end if - replychat->priority = token.floatvalue; - //read the leading { - if (!PC_ExpectTokenString(source, "{")) - { - BotFreeReplyChat(replychatlist); - FreeSource(source); - return NULL; - } //end if - replychat->numchatmessages = 0; - //while the trailing } is not found - while(!PC_CheckTokenString(source, "}")) - { - if (!BotLoadChatMessage(source, chatmessagestring)) - { - BotFreeReplyChat(replychatlist); - FreeSource(source); - return NULL; - } //end if - chatmessage = (bot_chatmessage_t *) GetClearedHunkMemory(sizeof(bot_chatmessage_t) + strlen(chatmessagestring) + 1); - chatmessage->chatmessage = (char *) chatmessage + sizeof(bot_chatmessage_t); - strcpy(chatmessage->chatmessage, chatmessagestring); - chatmessage->time = -2*CHATMESSAGE_RECENTTIME; - chatmessage->next = replychat->firstchatmessage; - //add the chat message to the reply chat - replychat->firstchatmessage = chatmessage; - replychat->numchatmessages++; - } //end while - } //end while - FreeSource(source); - botimport.Print(PRT_MESSAGE, "loaded %s\n", filename); - // - //BotDumpReplyChat(replychatlist); - if (bot_developer) - { - BotCheckReplyChatIntegrety(replychatlist); - } //end if - // - if (!replychatlist) botimport.Print(PRT_MESSAGE, "no rchats\n"); - // - return replychatlist; -} //end of the function BotLoadReplyChat -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotDumpInitialChat(bot_chat_t *chat) -{ - bot_chattype_t *t; - bot_chatmessage_t *m; - - Log_Write("{"); - for (t = chat->types; t; t = t->next) - { - Log_Write(" type \"%s\"", t->name); - Log_Write(" {"); - Log_Write(" numchatmessages = %d", t->numchatmessages); - for (m = t->firstchatmessage; m; m = m->next) - { - Log_Write(" \"%s\"", m->chatmessage); - } //end for - Log_Write(" }"); - } //end for - Log_Write("}"); -} //end of the function BotDumpInitialChat -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bot_chat_t *BotLoadInitialChat(char *chatfile, char *chatname) -{ - int pass, foundchat, indent, size; - char *ptr = NULL; - char chatmessagestring[MAX_MESSAGE_SIZE]; - source_t *source; - token_t token; - bot_chat_t *chat = NULL; - bot_chattype_t *chattype = NULL; - bot_chatmessage_t *chatmessage = NULL; -#ifdef DEBUG - int starttime; - - starttime = Sys_MilliSeconds(); -#endif //DEBUG - // - size = 0; - foundchat = qfalse; - //a bot chat is parsed in two phases - for (pass = 0; pass < 2; pass++) - { - //allocate memory - if (pass && size) ptr = (char *) GetClearedMemory(size); - //load the source file - PC_SetBaseFolder(BOTFILESBASEFOLDER); - source = LoadSourceFile(chatfile); - if (!source) - { - botimport.Print(PRT_ERROR, "counldn't load %s\n", chatfile); - return NULL; - } //end if - //chat structure - if (pass) - { - chat = (bot_chat_t *) ptr; - ptr += sizeof(bot_chat_t); - } //end if - size = sizeof(bot_chat_t); - // - while(PC_ReadToken(source, &token)) - { - if (!strcmp(token.string, "chat")) - { - if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) - { - FreeSource(source); - return NULL; - } //end if - StripDoubleQuotes(token.string); - //after the chat name we expect a opening brace - if (!PC_ExpectTokenString(source, "{")) - { - FreeSource(source); - return NULL; - } //end if - //if the chat name is found - if (!Q_stricmp(token.string, chatname)) - { - foundchat = qtrue; - //read the chat types - while(1) - { - if (!PC_ExpectAnyToken(source, &token)) - { - FreeSource(source); - return NULL; - } //end if - if (!strcmp(token.string, "}")) break; - if (strcmp(token.string, "type")) - { - SourceError(source, "expected type found %s\n", token.string); - FreeSource(source); - return NULL; - } //end if - //expect the chat type name - if (!PC_ExpectTokenType(source, TT_STRING, 0, &token) || - !PC_ExpectTokenString(source, "{")) - { - FreeSource(source); - return NULL; - } //end if - StripDoubleQuotes(token.string); - if (pass) - { - chattype = (bot_chattype_t *) ptr; - strncpy(chattype->name, token.string, MAX_CHATTYPE_NAME); - chattype->firstchatmessage = NULL; - //add the chat type to the chat - chattype->next = chat->types; - chat->types = chattype; - // - ptr += sizeof(bot_chattype_t); - } //end if - size += sizeof(bot_chattype_t); - //read the chat messages - while(!PC_CheckTokenString(source, "}")) - { - if (!BotLoadChatMessage(source, chatmessagestring)) - { - FreeSource(source); - return NULL; - } //end if - if (pass) - { - chatmessage = (bot_chatmessage_t *) ptr; - chatmessage->time = -2*CHATMESSAGE_RECENTTIME; - //put the chat message in the list - chatmessage->next = chattype->firstchatmessage; - chattype->firstchatmessage = chatmessage; - //store the chat message - ptr += sizeof(bot_chatmessage_t); - chatmessage->chatmessage = ptr; - strcpy(chatmessage->chatmessage, chatmessagestring); - ptr += strlen(chatmessagestring) + 1; - //the number of chat messages increased - chattype->numchatmessages++; - } //end if - size += sizeof(bot_chatmessage_t) + strlen(chatmessagestring) + 1; - } //end if - } //end while - } //end if - else //skip the bot chat - { - indent = 1; - while(indent) - { - if (!PC_ExpectAnyToken(source, &token)) - { - FreeSource(source); - return NULL; - } //end if - if (!strcmp(token.string, "{")) indent++; - else if (!strcmp(token.string, "}")) indent--; - } //end while - } //end else - } //end if - else - { - SourceError(source, "unknown definition %s\n", token.string); - FreeSource(source); - return NULL; - } //end else - } //end while - //free the source - FreeSource(source); - //if the requested character is not found - if (!foundchat) - { - botimport.Print(PRT_ERROR, "couldn't find chat %s in %s\n", chatname, chatfile); - return NULL; - } //end if - } //end for - // - botimport.Print(PRT_MESSAGE, "loaded %s from %s\n", chatname, chatfile); - // - //BotDumpInitialChat(chat); - if (bot_developer) - { - BotCheckInitialChatIntegrety(chat); - } //end if -#ifdef DEBUG - botimport.Print(PRT_MESSAGE, "initial chats loaded in %d msec\n", Sys_MilliSeconds() - starttime); -#endif //DEBUG - //character was read succesfully - return chat; -} //end of the function BotLoadInitialChat -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotFreeChatFile(int chatstate) -{ - bot_chatstate_t *cs; - - cs = BotChatStateFromHandle(chatstate); - if (!cs) return; - if (cs->chat) FreeMemory(cs->chat); - cs->chat = NULL; -} //end of the function BotFreeChatFile -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotLoadChatFile(int chatstate, char *chatfile, char *chatname) -{ - bot_chatstate_t *cs; - int n, avail = 0; - - cs = BotChatStateFromHandle(chatstate); - if (!cs) return BLERR_CANNOTLOADICHAT; - BotFreeChatFile(chatstate); - - if (!LibVarGetValue("bot_reloadcharacters")) - { - avail = -1; - for( n = 0; n < MAX_CLIENTS; n++ ) { - if( !ichatdata[n] ) { - if( avail == -1 ) { - avail = n; - } - continue; - } - if( strcmp( chatfile, ichatdata[n]->filename ) != 0 ) { - continue; - } - if( strcmp( chatname, ichatdata[n]->chatname ) != 0 ) { - continue; - } - cs->chat = ichatdata[n]->chat; - // botimport.Print( PRT_MESSAGE, "retained %s from %s\n", chatname, chatfile ); - return BLERR_NOERROR; - } - - if( avail == -1 ) { - botimport.Print(PRT_FATAL, "ichatdata table full; couldn't load chat %s from %s\n", chatname, chatfile); - return BLERR_CANNOTLOADICHAT; - } - } - - cs->chat = BotLoadInitialChat(chatfile, chatname); - if (!cs->chat) - { - botimport.Print(PRT_FATAL, "couldn't load chat %s from %s\n", chatname, chatfile); - return BLERR_CANNOTLOADICHAT; - } //end if - if (!LibVarGetValue("bot_reloadcharacters")) - { - ichatdata[avail] = GetClearedMemory( sizeof(bot_ichatdata_t) ); - ichatdata[avail]->chat = cs->chat; - Q_strncpyz( ichatdata[avail]->chatname, chatname, sizeof(ichatdata[avail]->chatname) ); - Q_strncpyz( ichatdata[avail]->filename, chatfile, sizeof(ichatdata[avail]->filename) ); - } //end if - - return BLERR_NOERROR; -} //end of the function BotLoadChatFile -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotExpandChatMessage(char *outmessage, char *message, unsigned long mcontext, - bot_match_t *match, unsigned long vcontext, int reply) -{ - int num, len, i, expansion; - char *outputbuf, *ptr, *msgptr; - char temp[MAX_MESSAGE_SIZE]; - - expansion = qfalse; - msgptr = message; - outputbuf = outmessage; - len = 0; - // - while(*msgptr) - { - if (*msgptr == ESCAPE_CHAR) - { - msgptr++; - switch(*msgptr) - { - case 'v': //variable - { - msgptr++; - num = 0; - while(*msgptr && *msgptr != ESCAPE_CHAR) - { - num = num * 10 + (*msgptr++) - '0'; - } //end while - //step over the trailing escape char - if (*msgptr) msgptr++; - if (num > MAX_MATCHVARIABLES) - { - botimport.Print(PRT_ERROR, "BotConstructChat: message %s variable %d out of range\n", message, num); - return qfalse; - } //end if - if (match->variables[num].offset >= 0) - { - assert( match->variables[num].offset >= 0 ); // bk001204 - ptr = &match->string[ (int) match->variables[num].offset]; - for (i = 0; i < match->variables[num].length; i++) - { - temp[i] = ptr[i]; - } //end for - temp[i] = 0; - //if it's a reply message - if (reply) - { - //replace the reply synonyms in the variables - BotReplaceReplySynonyms(temp, vcontext); - } //end if - else - { - //replace synonyms in the variable context - BotReplaceSynonyms(temp, vcontext); - } //end else - // - if (len + strlen(temp) >= MAX_MESSAGE_SIZE) - { - botimport.Print(PRT_ERROR, "BotConstructChat: message %s too long\n", message); - return qfalse; - } //end if - strcpy(&outputbuf[len], temp); - len += strlen(temp); - } //end if - break; - } //end case - case 'r': //random - { - msgptr++; - for (i = 0; (*msgptr && *msgptr != ESCAPE_CHAR); i++) - { - temp[i] = *msgptr++; - } //end while - temp[i] = '\0'; - //step over the trailing escape char - if (*msgptr) msgptr++; - //find the random keyword - ptr = RandomString(temp); - if (!ptr) - { - botimport.Print(PRT_ERROR, "BotConstructChat: unknown random string %s\n", temp); - return qfalse; - } //end if - if (len + strlen(ptr) >= MAX_MESSAGE_SIZE) - { - botimport.Print(PRT_ERROR, "BotConstructChat: message \"%s\" too long\n", message); - return qfalse; - } //end if - strcpy(&outputbuf[len], ptr); - len += strlen(ptr); - expansion = qtrue; - break; - } //end case - default: - { - botimport.Print(PRT_FATAL, "BotConstructChat: message \"%s\" invalid escape char\n", message); - break; - } //end default - } //end switch - } //end if - else - { - outputbuf[len++] = *msgptr++; - if (len >= MAX_MESSAGE_SIZE) - { - botimport.Print(PRT_ERROR, "BotConstructChat: message \"%s\" too long\n", message); - break; - } //end if - } //end else - } //end while - outputbuf[len] = '\0'; - //replace synonyms weighted in the message context - BotReplaceWeightedSynonyms(outputbuf, mcontext); - //return true if a random was expanded - return expansion; -} //end of the function BotExpandChatMessage -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotConstructChatMessage(bot_chatstate_t *chatstate, char *message, unsigned long mcontext, - bot_match_t *match, unsigned long vcontext, int reply) -{ - int i; - char srcmessage[MAX_MESSAGE_SIZE]; - - strcpy(srcmessage, message); - for (i = 0; i < 10; i++) - { - if (!BotExpandChatMessage(chatstate->chatmessage, srcmessage, mcontext, match, vcontext, reply)) - { - break; - } //end if - strcpy(srcmessage, chatstate->chatmessage); - } //end for - if (i >= 10) - { - botimport.Print(PRT_WARNING, "too many expansions in chat message\n"); - botimport.Print(PRT_WARNING, "%s\n", chatstate->chatmessage); - } //end if -} //end of the function BotConstructChatMessage -//=========================================================================== -// randomly chooses one of the chat message of the given type -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -char *BotChooseInitialChatMessage(bot_chatstate_t *cs, char *type) -{ - int n, numchatmessages; - float besttime; - bot_chattype_t *t; - bot_chatmessage_t *m, *bestchatmessage; - bot_chat_t *chat; - - chat = cs->chat; - for (t = chat->types; t; t = t->next) - { - if (!Q_stricmp(t->name, type)) - { - numchatmessages = 0; - for (m = t->firstchatmessage; m; m = m->next) - { - if (m->time > AAS_Time()) continue; - numchatmessages++; - } //end if - //if all chat messages have been used recently - if (numchatmessages <= 0) - { - besttime = 0; - bestchatmessage = NULL; - for (m = t->firstchatmessage; m; m = m->next) - { - if (!besttime || m->time < besttime) - { - bestchatmessage = m; - besttime = m->time; - } //end if - } //end for - if (bestchatmessage) return bestchatmessage->chatmessage; - } //end if - else //choose a chat message randomly - { - n = random() * numchatmessages; - for (m = t->firstchatmessage; m; m = m->next) - { - if (m->time > AAS_Time()) continue; - if (--n < 0) - { - m->time = AAS_Time() + CHATMESSAGE_RECENTTIME; - return m->chatmessage; - } //end if - } //end for - } //end else - return NULL; - } //end if - } //end for - return NULL; -} //end of the function BotChooseInitialChatMessage -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotNumInitialChats(int chatstate, char *type) -{ - bot_chatstate_t *cs; - bot_chattype_t *t; - - cs = BotChatStateFromHandle(chatstate); - if (!cs) return 0; - - for (t = cs->chat->types; t; t = t->next) - { - if (!Q_stricmp(t->name, type)) - { - if (LibVarGetValue("bot_testichat")) { - botimport.Print(PRT_MESSAGE, "%s has %d chat lines\n", type, t->numchatmessages); - botimport.Print(PRT_MESSAGE, "-------------------\n"); - } - return t->numchatmessages; - } //end if - } //end for - return 0; -} //end of the function BotNumInitialChats -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotInitialChat(int chatstate, char *type, int mcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7) -{ - char *message; - int index; - bot_match_t match; - bot_chatstate_t *cs; - - cs = BotChatStateFromHandle(chatstate); - if (!cs) return; - //if no chat file is loaded - if (!cs->chat) return; - //choose a chat message randomly of the given type - message = BotChooseInitialChatMessage(cs, type); - //if there's no message of the given type - if (!message) - { -#ifdef DEBUG - botimport.Print(PRT_MESSAGE, "no chat messages of type %s\n", type); -#endif //DEBUG - return; - } //end if - // - Com_Memset(&match, 0, sizeof(match)); - index = 0; - if( var0 ) { - strcat(match.string, var0); - match.variables[0].offset = index; - match.variables[0].length = strlen(var0); - index += strlen(var0); - } - if( var1 ) { - strcat(match.string, var1); - match.variables[1].offset = index; - match.variables[1].length = strlen(var1); - index += strlen(var1); - } - if( var2 ) { - strcat(match.string, var2); - match.variables[2].offset = index; - match.variables[2].length = strlen(var2); - index += strlen(var2); - } - if( var3 ) { - strcat(match.string, var3); - match.variables[3].offset = index; - match.variables[3].length = strlen(var3); - index += strlen(var3); - } - if( var4 ) { - strcat(match.string, var4); - match.variables[4].offset = index; - match.variables[4].length = strlen(var4); - index += strlen(var4); - } - if( var5 ) { - strcat(match.string, var5); - match.variables[5].offset = index; - match.variables[5].length = strlen(var5); - index += strlen(var5); - } - if( var6 ) { - strcat(match.string, var6); - match.variables[6].offset = index; - match.variables[6].length = strlen(var6); - index += strlen(var6); - } - if( var7 ) { - strcat(match.string, var7); - match.variables[7].offset = index; - match.variables[7].length = strlen(var7); - index += strlen(var7); - } - // - BotConstructChatMessage(cs, message, mcontext, &match, 0, qfalse); -} //end of the function BotInitialChat -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotPrintReplyChatKeys(bot_replychat_t *replychat) -{ - bot_replychatkey_t *key; - bot_matchpiece_t *mp; - - botimport.Print(PRT_MESSAGE, "["); - for (key = replychat->keys; key; key = key->next) - { - if (key->flags & RCKFL_AND) botimport.Print(PRT_MESSAGE, "&"); - else if (key->flags & RCKFL_NOT) botimport.Print(PRT_MESSAGE, "!"); - // - if (key->flags & RCKFL_NAME) botimport.Print(PRT_MESSAGE, "name"); - else if (key->flags & RCKFL_GENDERFEMALE) botimport.Print(PRT_MESSAGE, "female"); - else if (key->flags & RCKFL_GENDERMALE) botimport.Print(PRT_MESSAGE, "male"); - else if (key->flags & RCKFL_GENDERLESS) botimport.Print(PRT_MESSAGE, "it"); - else if (key->flags & RCKFL_VARIABLES) - { - botimport.Print(PRT_MESSAGE, "("); - for (mp = key->match; mp; mp = mp->next) - { - if (mp->type == MT_STRING) botimport.Print(PRT_MESSAGE, "\"%s\"", mp->firststring->string); - else botimport.Print(PRT_MESSAGE, "%d", mp->variable); - if (mp->next) botimport.Print(PRT_MESSAGE, ", "); - } //end for - botimport.Print(PRT_MESSAGE, ")"); - } //end if - else if (key->flags & RCKFL_STRING) - { - botimport.Print(PRT_MESSAGE, "\"%s\"", key->string); - } //end if - if (key->next) botimport.Print(PRT_MESSAGE, ", "); - else botimport.Print(PRT_MESSAGE, "] = %1.0f\n", replychat->priority); - } //end for - botimport.Print(PRT_MESSAGE, "{\n"); -} //end of the function BotPrintReplyChatKeys -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotReplyChat(int chatstate, char *message, int mcontext, int vcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7) -{ - bot_replychat_t *rchat, *bestrchat; - bot_replychatkey_t *key; - bot_chatmessage_t *m, *bestchatmessage; - bot_match_t match, bestmatch; - int bestpriority, num, found, res, numchatmessages, index; - bot_chatstate_t *cs; - - cs = BotChatStateFromHandle(chatstate); - if (!cs) return qfalse; - Com_Memset(&match, 0, sizeof(bot_match_t)); - strcpy(match.string, message); - bestpriority = -1; - bestchatmessage = NULL; - bestrchat = NULL; - //go through all the reply chats - for (rchat = replychats; rchat; rchat = rchat->next) - { - found = qfalse; - for (key = rchat->keys; key; key = key->next) - { - res = qfalse; - //get the match result - if (key->flags & RCKFL_NAME) res = (StringContains(message, cs->name, qfalse) != -1); - else if (key->flags & RCKFL_BOTNAMES) res = (StringContains(key->string, cs->name, qfalse) != -1); - else if (key->flags & RCKFL_GENDERFEMALE) res = (cs->gender == CHAT_GENDERFEMALE); - else if (key->flags & RCKFL_GENDERMALE) res = (cs->gender == CHAT_GENDERMALE); - else if (key->flags & RCKFL_GENDERLESS) res = (cs->gender == CHAT_GENDERLESS); - else if (key->flags & RCKFL_VARIABLES) res = StringsMatch(key->match, &match); - else if (key->flags & RCKFL_STRING) res = (StringContainsWord(message, key->string, qfalse) != NULL); - //if the key must be present - if (key->flags & RCKFL_AND) - { - if (!res) - { - found = qfalse; - break; - } //end if - } //end else if - //if the key must be absent - else if (key->flags & RCKFL_NOT) - { - if (res) - { - found = qfalse; - break; - } //end if - } //end if - else if (res) - { - found = qtrue; - } //end else - } //end for - // - if (found) - { - if (rchat->priority > bestpriority) - { - numchatmessages = 0; - for (m = rchat->firstchatmessage; m; m = m->next) - { - if (m->time > AAS_Time()) continue; - numchatmessages++; - } //end if - num = random() * numchatmessages; - for (m = rchat->firstchatmessage; m; m = m->next) - { - if (--num < 0) break; - if (m->time > AAS_Time()) continue; - } //end for - //if the reply chat has a message - if (m) - { - Com_Memcpy(&bestmatch, &match, sizeof(bot_match_t)); - bestchatmessage = m; - bestrchat = rchat; - bestpriority = rchat->priority; - } //end if - } //end if - } //end if - } //end for - if (bestchatmessage) - { - index = strlen(bestmatch.string); - if( var0 ) { - strcat(bestmatch.string, var0); - bestmatch.variables[0].offset = index; - bestmatch.variables[0].length = strlen(var0); - index += strlen(var0); - } - if( var1 ) { - strcat(bestmatch.string, var1); - bestmatch.variables[1].offset = index; - bestmatch.variables[1].length = strlen(var1); - index += strlen(var1); - } - if( var2 ) { - strcat(bestmatch.string, var2); - bestmatch.variables[2].offset = index; - bestmatch.variables[2].length = strlen(var2); - index += strlen(var2); - } - if( var3 ) { - strcat(bestmatch.string, var3); - bestmatch.variables[3].offset = index; - bestmatch.variables[3].length = strlen(var3); - index += strlen(var3); - } - if( var4 ) { - strcat(bestmatch.string, var4); - bestmatch.variables[4].offset = index; - bestmatch.variables[4].length = strlen(var4); - index += strlen(var4); - } - if( var5 ) { - strcat(bestmatch.string, var5); - bestmatch.variables[5].offset = index; - bestmatch.variables[5].length = strlen(var5); - index += strlen(var5); - } - if( var6 ) { - strcat(bestmatch.string, var6); - bestmatch.variables[6].offset = index; - bestmatch.variables[6].length = strlen(var6); - index += strlen(var6); - } - if( var7 ) { - strcat(bestmatch.string, var7); - bestmatch.variables[7].offset = index; - bestmatch.variables[7].length = strlen(var7); - index += strlen(var7); - } - if (LibVarGetValue("bot_testrchat")) - { - for (m = bestrchat->firstchatmessage; m; m = m->next) - { - BotConstructChatMessage(cs, m->chatmessage, mcontext, &bestmatch, vcontext, qtrue); - BotRemoveTildes(cs->chatmessage); - botimport.Print(PRT_MESSAGE, "%s\n", cs->chatmessage); - } //end if - } //end if - else - { - bestchatmessage->time = AAS_Time() + CHATMESSAGE_RECENTTIME; - BotConstructChatMessage(cs, bestchatmessage->chatmessage, mcontext, &bestmatch, vcontext, qtrue); - } //end else - return qtrue; - } //end if - return qfalse; -} //end of the function BotReplyChat -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotChatLength(int chatstate) -{ - bot_chatstate_t *cs; - - cs = BotChatStateFromHandle(chatstate); - if (!cs) return 0; - return strlen(cs->chatmessage); -} //end of the function BotChatLength -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotEnterChat(int chatstate, int clientto, int sendto) -{ - bot_chatstate_t *cs; - - cs = BotChatStateFromHandle(chatstate); - if (!cs) return; - - if (strlen(cs->chatmessage)) - { - BotRemoveTildes(cs->chatmessage); - if (LibVarGetValue("bot_testichat")) { - botimport.Print(PRT_MESSAGE, "%s\n", cs->chatmessage); - } - else { - switch(sendto) { - case CHAT_TEAM: - EA_Command(cs->client, va("say_team %s", cs->chatmessage)); - break; - case CHAT_TELL: - EA_Command(cs->client, va("tell %d %s", clientto, cs->chatmessage)); - break; - default: //CHAT_ALL - EA_Command(cs->client, va("say %s", cs->chatmessage)); - break; - } - } - //clear the chat message from the state - strcpy(cs->chatmessage, ""); - } //end if -} //end of the function BotEnterChat -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotGetChatMessage(int chatstate, char *buf, int size) -{ - bot_chatstate_t *cs; - - cs = BotChatStateFromHandle(chatstate); - if (!cs) return; - - BotRemoveTildes(cs->chatmessage); - strncpy(buf, cs->chatmessage, size-1); - buf[size-1] = '\0'; - //clear the chat message from the state - strcpy(cs->chatmessage, ""); -} //end of the function BotGetChatMessage -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotSetChatGender(int chatstate, int gender) -{ - bot_chatstate_t *cs; - - cs = BotChatStateFromHandle(chatstate); - if (!cs) return; - switch(gender) - { - case CHAT_GENDERFEMALE: cs->gender = CHAT_GENDERFEMALE; break; - case CHAT_GENDERMALE: cs->gender = CHAT_GENDERMALE; break; - default: cs->gender = CHAT_GENDERLESS; break; - } //end switch -} //end of the function BotSetChatGender -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotSetChatName(int chatstate, char *name, int client) -{ - bot_chatstate_t *cs; - - cs = BotChatStateFromHandle(chatstate); - if (!cs) return; - cs->client = client; - Com_Memset(cs->name, 0, sizeof(cs->name)); - strncpy(cs->name, name, sizeof(cs->name)); - cs->name[sizeof(cs->name)-1] = '\0'; -} //end of the function BotSetChatName -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotResetChatAI(void) -{ - bot_replychat_t *rchat; - bot_chatmessage_t *m; - - for (rchat = replychats; rchat; rchat = rchat->next) - { - for (m = rchat->firstchatmessage; m; m = m->next) - { - m->time = 0; - } //end for - } //end for -} //end of the function BotResetChatAI -//======================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//======================================================================== -int BotAllocChatState(void) -{ - int i; - - for (i = 1; i <= MAX_CLIENTS; i++) - { - if (!botchatstates[i]) - { - botchatstates[i] = GetClearedMemory(sizeof(bot_chatstate_t)); - return i; - } //end if - } //end for - return 0; -} //end of the function BotAllocChatState -//======================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//======================================================================== -void BotFreeChatState(int handle) -{ - bot_chatstate_t *cs; - bot_consolemessage_t m; - int h; - - if (handle <= 0 || handle > MAX_CLIENTS) - { - botimport.Print(PRT_FATAL, "chat state handle %d out of range\n", handle); - return; - } //end if - if (!botchatstates[handle]) - { - botimport.Print(PRT_FATAL, "invalid chat state %d\n", handle); - return; - } //end if - cs = botchatstates[handle]; - if (LibVarGetValue("bot_reloadcharacters")) - { - BotFreeChatFile(handle); - } //end if - //free all the console messages left in the chat state - for (h = BotNextConsoleMessage(handle, &m); h; h = BotNextConsoleMessage(handle, &m)) - { - //remove the console message - BotRemoveConsoleMessage(handle, h); - } //end for - FreeMemory(botchatstates[handle]); - botchatstates[handle] = NULL; -} //end of the function BotFreeChatState -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotSetupChatAI(void) -{ - char *file; - -#ifdef DEBUG - int starttime = Sys_MilliSeconds(); -#endif //DEBUG - - file = LibVarString("synfile", "syn.c"); - synonyms = BotLoadSynonyms(file); - file = LibVarString("rndfile", "rnd.c"); - randomstrings = BotLoadRandomStrings(file); - file = LibVarString("matchfile", "match.c"); - matchtemplates = BotLoadMatchTemplates(file); - // - if (!LibVarValue("nochat", "0")) - { - file = LibVarString("rchatfile", "rchat.c"); - replychats = BotLoadReplyChat(file); - } //end if - - InitConsoleMessageHeap(); - -#ifdef DEBUG - botimport.Print(PRT_MESSAGE, "setup chat AI %d msec\n", Sys_MilliSeconds() - starttime); -#endif //DEBUG - return BLERR_NOERROR; -} //end of the function BotSetupChatAI -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotShutdownChatAI(void) -{ - int i; - - //free all remaining chat states - for(i = 0; i < MAX_CLIENTS; i++) - { - if (botchatstates[i]) - { - BotFreeChatState(i); - } //end if - } //end for - //free all cached chats - for(i = 0; i < MAX_CLIENTS; i++) - { - if (ichatdata[i]) - { - FreeMemory(ichatdata[i]->chat); - FreeMemory(ichatdata[i]); - ichatdata[i] = NULL; - } //end if - } //end for - if (consolemessageheap) FreeMemory(consolemessageheap); - consolemessageheap = NULL; - if (matchtemplates) BotFreeMatchTemplates(matchtemplates); - matchtemplates = NULL; - if (randomstrings) FreeMemory(randomstrings); - randomstrings = NULL; - if (synonyms) FreeMemory(synonyms); - synonyms = NULL; - if (replychats) BotFreeReplyChat(replychats); - replychats = NULL; -} //end of the function BotShutdownChatAI +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_ai_chat.c + * + * desc: bot chat AI + * + * $Archive: /MissionPack/code/botlib/be_ai_chat.c $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_libvar.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_utils.h" +#include "l_log.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "../game/be_ea.h" +#include "../game/be_ai_chat.h" + + +//escape character +#define ESCAPE_CHAR 0x01 //'_' +// +// "hi ", people, " ", 0, " entered the game" +//becomes: +// "hi _rpeople_ _v0_ entered the game" +// + +//match piece types +#define MT_VARIABLE 1 //variable match piece +#define MT_STRING 2 //string match piece +//reply chat key flags +#define RCKFL_AND 1 //key must be present +#define RCKFL_NOT 2 //key must be absent +#define RCKFL_NAME 4 //name of bot must be present +#define RCKFL_STRING 8 //key is a string +#define RCKFL_VARIABLES 16 //key is a match template +#define RCKFL_BOTNAMES 32 //key is a series of botnames +#define RCKFL_GENDERFEMALE 64 //bot must be female +#define RCKFL_GENDERMALE 128 //bot must be male +#define RCKFL_GENDERLESS 256 //bot must be genderless +//time to ignore a chat message after using it +#define CHATMESSAGE_RECENTTIME 20 + +//the actuall chat messages +typedef struct bot_chatmessage_s +{ + char *chatmessage; //chat message string + float time; //last time used + struct bot_chatmessage_s *next; //next chat message in a list +} bot_chatmessage_t; +//bot chat type with chat lines +typedef struct bot_chattype_s +{ + char name[MAX_CHATTYPE_NAME]; + int numchatmessages; + bot_chatmessage_t *firstchatmessage; + struct bot_chattype_s *next; +} bot_chattype_t; +//bot chat lines +typedef struct bot_chat_s +{ + bot_chattype_t *types; +} bot_chat_t; + +//random string +typedef struct bot_randomstring_s +{ + char *string; + struct bot_randomstring_s *next; +} bot_randomstring_t; +//list with random strings +typedef struct bot_randomlist_s +{ + char *string; + int numstrings; + bot_randomstring_t *firstrandomstring; + struct bot_randomlist_s *next; +} bot_randomlist_t; + +//synonym +typedef struct bot_synonym_s +{ + char *string; + float weight; + struct bot_synonym_s *next; +} bot_synonym_t; +//list with synonyms +typedef struct bot_synonymlist_s +{ + unsigned long int context; + float totalweight; + bot_synonym_t *firstsynonym; + struct bot_synonymlist_s *next; +} bot_synonymlist_t; + +//fixed match string +typedef struct bot_matchstring_s +{ + char *string; + struct bot_matchstring_s *next; +} bot_matchstring_t; + +//piece of a match template +typedef struct bot_matchpiece_s +{ + int type; + bot_matchstring_t *firststring; + int variable; + struct bot_matchpiece_s *next; +} bot_matchpiece_t; +//match template +typedef struct bot_matchtemplate_s +{ + unsigned long int context; + int type; + int subtype; + bot_matchpiece_t *first; + struct bot_matchtemplate_s *next; +} bot_matchtemplate_t; + +//reply chat key +typedef struct bot_replychatkey_s +{ + int flags; + char *string; + bot_matchpiece_t *match; + struct bot_replychatkey_s *next; +} bot_replychatkey_t; +//reply chat +typedef struct bot_replychat_s +{ + bot_replychatkey_t *keys; + float priority; + int numchatmessages; + bot_chatmessage_t *firstchatmessage; + struct bot_replychat_s *next; +} bot_replychat_t; + +//string list +typedef struct bot_stringlist_s +{ + char *string; + struct bot_stringlist_s *next; +} bot_stringlist_t; + +//chat state of a bot +typedef struct bot_chatstate_s +{ + int gender; //0=it, 1=female, 2=male + int client; //client number + char name[32]; //name of the bot + char chatmessage[MAX_MESSAGE_SIZE]; + int handle; + //the console messages visible to the bot + bot_consolemessage_t *firstmessage; //first message is the first typed message + bot_consolemessage_t *lastmessage; //last message is the last typed message, bottom of console + //number of console messages stored in the state + int numconsolemessages; + //the bot chat lines + bot_chat_t *chat; +} bot_chatstate_t; + +typedef struct { + bot_chat_t *chat; + char filename[MAX_QPATH]; + char chatname[MAX_QPATH]; +} bot_ichatdata_t; + +bot_ichatdata_t *ichatdata[MAX_CLIENTS]; + +bot_chatstate_t *botchatstates[MAX_CLIENTS+1]; +//console message heap +bot_consolemessage_t *consolemessageheap = NULL; +bot_consolemessage_t *freeconsolemessages = NULL; +//list with match strings +bot_matchtemplate_t *matchtemplates = NULL; +//list with synonyms +bot_synonymlist_t *synonyms = NULL; +//list with random strings +bot_randomlist_t *randomstrings = NULL; +//reply chats +bot_replychat_t *replychats = NULL; + +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +bot_chatstate_t *BotChatStateFromHandle(int handle) +{ + if (handle <= 0 || handle > MAX_CLIENTS) + { + botimport.Print(PRT_FATAL, "chat state handle %d out of range\n", handle); + return NULL; + } //end if + if (!botchatstates[handle]) + { + botimport.Print(PRT_FATAL, "invalid chat state %d\n", handle); + return NULL; + } //end if + return botchatstates[handle]; +} //end of the function BotChatStateFromHandle +//=========================================================================== +// initialize the heap with unused console messages +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void InitConsoleMessageHeap(void) +{ + int i, max_messages; + + if (consolemessageheap) FreeMemory(consolemessageheap); + // + max_messages = (int) LibVarValue("max_messages", "1024"); + consolemessageheap = (bot_consolemessage_t *) GetClearedHunkMemory(max_messages * + sizeof(bot_consolemessage_t)); + consolemessageheap[0].prev = NULL; + consolemessageheap[0].next = &consolemessageheap[1]; + for (i = 1; i < max_messages-1; i++) + { + consolemessageheap[i].prev = &consolemessageheap[i - 1]; + consolemessageheap[i].next = &consolemessageheap[i + 1]; + } //end for + consolemessageheap[max_messages-1].prev = &consolemessageheap[max_messages-2]; + consolemessageheap[max_messages-1].next = NULL; + //pointer to the free console messages + freeconsolemessages = consolemessageheap; +} //end of the function InitConsoleMessageHeap +//=========================================================================== +// allocate one console message from the heap +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_consolemessage_t *AllocConsoleMessage(void) +{ + bot_consolemessage_t *message; + message = freeconsolemessages; + if (freeconsolemessages) freeconsolemessages = freeconsolemessages->next; + if (freeconsolemessages) freeconsolemessages->prev = NULL; + return message; +} //end of the function AllocConsoleMessage +//=========================================================================== +// deallocate one console message from the heap +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeConsoleMessage(bot_consolemessage_t *message) +{ + if (freeconsolemessages) freeconsolemessages->prev = message; + message->prev = NULL; + message->next = freeconsolemessages; + freeconsolemessages = message; +} //end of the function FreeConsoleMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotRemoveConsoleMessage(int chatstate, int handle) +{ + bot_consolemessage_t *m, *nextm; + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return; + + for (m = cs->firstmessage; m; m = nextm) + { + nextm = m->next; + if (m->handle == handle) + { + if (m->next) m->next->prev = m->prev; + else cs->lastmessage = m->prev; + if (m->prev) m->prev->next = m->next; + else cs->firstmessage = m->next; + + FreeConsoleMessage(m); + cs->numconsolemessages--; + break; + } //end if + } //end for +} //end of the function BotRemoveConsoleMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotQueueConsoleMessage(int chatstate, int type, char *message) +{ + bot_consolemessage_t *m; + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return; + + m = AllocConsoleMessage(); + if (!m) + { + botimport.Print(PRT_ERROR, "empty console message heap\n"); + return; + } //end if + cs->handle++; + if (cs->handle <= 0 || cs->handle > 8192) cs->handle = 1; + m->handle = cs->handle; + m->time = AAS_Time(); + m->type = type; + strncpy(m->message, message, MAX_MESSAGE_SIZE); + m->next = NULL; + if (cs->lastmessage) + { + cs->lastmessage->next = m; + m->prev = cs->lastmessage; + cs->lastmessage = m; + } //end if + else + { + cs->lastmessage = m; + cs->firstmessage = m; + m->prev = NULL; + } //end if + cs->numconsolemessages++; +} //end of the function BotQueueConsoleMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotNextConsoleMessage(int chatstate, bot_consolemessage_t *cm) +{ + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return 0; + if (cs->firstmessage) + { + Com_Memcpy(cm, cs->firstmessage, sizeof(bot_consolemessage_t)); + cm->next = cm->prev = NULL; + return cm->handle; + } //end if + return 0; +} //end of the function BotConsoleMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotNumConsoleMessages(int chatstate) +{ + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return 0; + return cs->numconsolemessages; +} //end of the function BotNumConsoleMessages +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int IsWhiteSpace(char c) +{ + if ((c >= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z') + || (c >= '0' && c <= '9') + || c == '(' || c == ')' + || c == '?' || c == ':' + || c == '\''|| c == '/' + || c == ',' || c == '.' + || c == '[' || c == ']' + || c == '-' || c == '_' + || c == '+' || c == '=') return qfalse; + return qtrue; +} //end of the function IsWhiteSpace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotRemoveTildes(char *message) +{ + int i; + + //remove all tildes from the chat message + for (i = 0; message[i]; i++) + { + if (message[i] == '~') + { + memmove(&message[i], &message[i+1], strlen(&message[i+1])+1); + } //end if + } //end for +} //end of the function BotRemoveTildes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void UnifyWhiteSpaces(char *string) +{ + char *ptr, *oldptr; + + for (ptr = oldptr = string; *ptr; oldptr = ptr) + { + while(*ptr && IsWhiteSpace(*ptr)) ptr++; + if (ptr > oldptr) + { + //if not at the start and not at the end of the string + //write only one space + if (oldptr > string && *ptr) *oldptr++ = ' '; + //remove all other white spaces + if (ptr > oldptr) memmove(oldptr, ptr, strlen(ptr)+1); + } //end if + while(*ptr && !IsWhiteSpace(*ptr)) ptr++; + } //end while +} //end of the function UnifyWhiteSpaces +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int StringContains(char *str1, char *str2, int casesensitive) +{ + int len, i, j, index; + + if (str1 == NULL || str2 == NULL) return -1; + + len = strlen(str1) - strlen(str2); + index = 0; + for (i = 0; i <= len; i++, str1++, index++) + { + for (j = 0; str2[j]; j++) + { + if (casesensitive) + { + if (str1[j] != str2[j]) break; + } //end if + else + { + if (toupper(str1[j]) != toupper(str2[j])) break; + } //end else + } //end for + if (!str2[j]) return index; + } //end for + return -1; +} //end of the function StringContains +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *StringContainsWord(char *str1, char *str2, int casesensitive) +{ + int len, i, j; + + len = strlen(str1) - strlen(str2); + for (i = 0; i <= len; i++, str1++) + { + //if not at the start of the string + if (i) + { + //skip to the start of the next word + while(*str1 && *str1 != ' ' && *str1 != '.' && *str1 != ',' && *str1 != '!') str1++; + if (!*str1) break; + str1++; + } //end for + //compare the word + for (j = 0; str2[j]; j++) + { + if (casesensitive) + { + if (str1[j] != str2[j]) break; + } //end if + else + { + if (toupper(str1[j]) != toupper(str2[j])) break; + } //end else + } //end for + //if there was a word match + if (!str2[j]) + { + //if the first string has an end of word + if (!str1[j] || str1[j] == ' ' || str1[j] == '.' || str1[j] == ',' || str1[j] == '!') return str1; + } //end if + } //end for + return NULL; +} //end of the function StringContainsWord +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void StringReplaceWords(char *string, char *synonym, char *replacement) +{ + char *str, *str2; + + //find the synonym in the string + str = StringContainsWord(string, synonym, qfalse); + //if the synonym occured in the string + while(str) + { + //if the synonym isn't part of the replacement which is already in the string + //usefull for abreviations + str2 = StringContainsWord(string, replacement, qfalse); + while(str2) + { + if (str2 <= str && str < str2 + strlen(replacement)) break; + str2 = StringContainsWord(str2+1, replacement, qfalse); + } //end while + if (!str2) + { + memmove(str + strlen(replacement), str+strlen(synonym), strlen(str+strlen(synonym))+1); + //append the synonum replacement + Com_Memcpy(str, replacement, strlen(replacement)); + } //end if + //find the next synonym in the string + str = StringContainsWord(str+strlen(replacement), synonym, qfalse); + } //end if +} //end of the function StringReplaceWords +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDumpSynonymList(bot_synonymlist_t *synlist) +{ + FILE *fp; + bot_synonymlist_t *syn; + bot_synonym_t *synonym; + + fp = Log_FilePointer(); + if (!fp) return; + for (syn = synlist; syn; syn = syn->next) + { + fprintf(fp, "%ld : [", syn->context); + for (synonym = syn->firstsynonym; synonym; synonym = synonym->next) + { + fprintf(fp, "(\"%s\", %1.2f)", synonym->string, synonym->weight); + if (synonym->next) fprintf(fp, ", "); + } //end for + fprintf(fp, "]\n"); + } //end for +} //end of the function BotDumpSynonymList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_synonymlist_t *BotLoadSynonyms(char *filename) +{ + int pass, size, contextlevel, numsynonyms; + unsigned long int context, contextstack[32]; + char *ptr = NULL; + source_t *source; + token_t token; + bot_synonymlist_t *synlist, *lastsyn, *syn; + bot_synonym_t *synonym, *lastsynonym; + + size = 0; + synlist = NULL; //make compiler happy + syn = NULL; //make compiler happy + synonym = NULL; //make compiler happy + //the synonyms are parsed in two phases + for (pass = 0; pass < 2; pass++) + { + // + if (pass && size) ptr = (char *) GetClearedHunkMemory(size); + // + PC_SetBaseFolder(BOTFILESBASEFOLDER); + source = LoadSourceFile(filename); + if (!source) + { + botimport.Print(PRT_ERROR, "counldn't load %s\n", filename); + return NULL; + } //end if + // + context = 0; + contextlevel = 0; + synlist = NULL; //list synonyms + lastsyn = NULL; //last synonym in the list + // + while(PC_ReadToken(source, &token)) + { + if (token.type == TT_NUMBER) + { + context |= token.intvalue; + contextstack[contextlevel] = token.intvalue; + contextlevel++; + if (contextlevel >= 32) + { + SourceError(source, "more than 32 context levels"); + FreeSource(source); + return NULL; + } //end if + if (!PC_ExpectTokenString(source, "{")) + { + FreeSource(source); + return NULL; + } //end if + } //end if + else if (token.type == TT_PUNCTUATION) + { + if (!strcmp(token.string, "}")) + { + contextlevel--; + if (contextlevel < 0) + { + SourceError(source, "too many }"); + FreeSource(source); + return NULL; + } //end if + context &= ~contextstack[contextlevel]; + } //end if + else if (!strcmp(token.string, "[")) + { + size += sizeof(bot_synonymlist_t); + if (pass) + { + syn = (bot_synonymlist_t *) ptr; + ptr += sizeof(bot_synonymlist_t); + syn->context = context; + syn->firstsynonym = NULL; + syn->next = NULL; + if (lastsyn) lastsyn->next = syn; + else synlist = syn; + lastsyn = syn; + } //end if + numsynonyms = 0; + lastsynonym = NULL; + while(1) + { + if (!PC_ExpectTokenString(source, "(") || + !PC_ExpectTokenType(source, TT_STRING, 0, &token)) + { + FreeSource(source); + return NULL; + } //end if + StripDoubleQuotes(token.string); + if (strlen(token.string) <= 0) + { + SourceError(source, "empty string", token.string); + FreeSource(source); + return NULL; + } //end if + size += sizeof(bot_synonym_t) + strlen(token.string) + 1; + if (pass) + { + synonym = (bot_synonym_t *) ptr; + ptr += sizeof(bot_synonym_t); + synonym->string = ptr; + ptr += strlen(token.string) + 1; + strcpy(synonym->string, token.string); + // + if (lastsynonym) lastsynonym->next = synonym; + else syn->firstsynonym = synonym; + lastsynonym = synonym; + } //end if + numsynonyms++; + if (!PC_ExpectTokenString(source, ",") || + !PC_ExpectTokenType(source, TT_NUMBER, 0, &token) || + !PC_ExpectTokenString(source, ")")) + { + FreeSource(source); + return NULL; + } //end if + if (pass) + { + synonym->weight = token.floatvalue; + syn->totalweight += synonym->weight; + } //end if + if (PC_CheckTokenString(source, "]")) break; + if (!PC_ExpectTokenString(source, ",")) + { + FreeSource(source); + return NULL; + } //end if + } //end while + if (numsynonyms < 2) + { + SourceError(source, "synonym must have at least two entries\n"); + FreeSource(source); + return NULL; + } //end if + } //end else + else + { + SourceError(source, "unexpected %s", token.string); + FreeSource(source); + return NULL; + } //end if + } //end else if + } //end while + // + FreeSource(source); + // + if (contextlevel > 0) + { + SourceError(source, "missing }"); + return NULL; + } //end if + } //end for + botimport.Print(PRT_MESSAGE, "loaded %s\n", filename); + // + //BotDumpSynonymList(synlist); + // + return synlist; +} //end of the function BotLoadSynonyms +//=========================================================================== +// replace all the synonyms in the string +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotReplaceSynonyms(char *string, unsigned long int context) +{ + bot_synonymlist_t *syn; + bot_synonym_t *synonym; + + for (syn = synonyms; syn; syn = syn->next) + { + if (!(syn->context & context)) continue; + for (synonym = syn->firstsynonym->next; synonym; synonym = synonym->next) + { + StringReplaceWords(string, synonym->string, syn->firstsynonym->string); + } //end for + } //end for +} //end of the function BotReplaceSynonyms +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotReplaceWeightedSynonyms(char *string, unsigned long int context) +{ + bot_synonymlist_t *syn; + bot_synonym_t *synonym, *replacement; + float weight, curweight; + + for (syn = synonyms; syn; syn = syn->next) + { + if (!(syn->context & context)) continue; + //choose a weighted random replacement synonym + weight = random() * syn->totalweight; + if (!weight) continue; + curweight = 0; + for (replacement = syn->firstsynonym; replacement; replacement = replacement->next) + { + curweight += replacement->weight; + if (weight < curweight) break; + } //end for + if (!replacement) continue; + //replace all synonyms with the replacement + for (synonym = syn->firstsynonym; synonym; synonym = synonym->next) + { + if (synonym == replacement) continue; + StringReplaceWords(string, synonym->string, replacement->string); + } //end for + } //end for +} //end of the function BotReplaceWeightedSynonyms +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotReplaceReplySynonyms(char *string, unsigned long int context) +{ + char *str1, *str2, *replacement; + bot_synonymlist_t *syn; + bot_synonym_t *synonym; + + for (str1 = string; *str1; ) + { + //go to the start of the next word + while(*str1 && *str1 <= ' ') str1++; + if (!*str1) break; + // + for (syn = synonyms; syn; syn = syn->next) + { + if (!(syn->context & context)) continue; + for (synonym = syn->firstsynonym->next; synonym; synonym = synonym->next) + { + str2 = synonym->string; + //if the synonym is not at the front of the string continue + str2 = StringContainsWord(str1, synonym->string, qfalse); + if (!str2 || str2 != str1) continue; + // + replacement = syn->firstsynonym->string; + //if the replacement IS in front of the string continue + str2 = StringContainsWord(str1, replacement, qfalse); + if (str2 && str2 == str1) continue; + // + memmove(str1 + strlen(replacement), str1+strlen(synonym->string), + strlen(str1+strlen(synonym->string)) + 1); + //append the synonum replacement + Com_Memcpy(str1, replacement, strlen(replacement)); + // + break; + } //end for + //if a synonym has been replaced + if (synonym) break; + } //end for + //skip over this word + while(*str1 && *str1 > ' ') str1++; + if (!*str1) break; + } //end while +} //end of the function BotReplaceReplySynonyms +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotLoadChatMessage(source_t *source, char *chatmessagestring) +{ + char *ptr; + token_t token; + + ptr = chatmessagestring; + *ptr = 0; + // + while(1) + { + if (!PC_ExpectAnyToken(source, &token)) return qfalse; + //fixed string + if (token.type == TT_STRING) + { + StripDoubleQuotes(token.string); + if (strlen(ptr) + strlen(token.string) + 1 > MAX_MESSAGE_SIZE) + { + SourceError(source, "chat message too long\n"); + return qfalse; + } //end if + strcat(ptr, token.string); + } //end else if + //variable string + else if (token.type == TT_NUMBER && (token.subtype & TT_INTEGER)) + { + if (strlen(ptr) + 7 > MAX_MESSAGE_SIZE) + { + SourceError(source, "chat message too long\n"); + return qfalse; + } //end if + sprintf(&ptr[strlen(ptr)], "%cv%ld%c", ESCAPE_CHAR, token.intvalue, ESCAPE_CHAR); + } //end if + //random string + else if (token.type == TT_NAME) + { + if (strlen(ptr) + 7 > MAX_MESSAGE_SIZE) + { + SourceError(source, "chat message too long\n"); + return qfalse; + } //end if + sprintf(&ptr[strlen(ptr)], "%cr%s%c", ESCAPE_CHAR, token.string, ESCAPE_CHAR); + } //end else if + else + { + SourceError(source, "unknown message component %s\n", token.string); + return qfalse; + } //end else + if (PC_CheckTokenString(source, ";")) break; + if (!PC_ExpectTokenString(source, ",")) return qfalse; + } //end while + // + return qtrue; +} //end of the function BotLoadChatMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDumpRandomStringList(bot_randomlist_t *randomlist) +{ + FILE *fp; + bot_randomlist_t *random; + bot_randomstring_t *rs; + + fp = Log_FilePointer(); + if (!fp) return; + for (random = randomlist; random; random = random->next) + { + fprintf(fp, "%s = {", random->string); + for (rs = random->firstrandomstring; rs; rs = rs->next) + { + fprintf(fp, "\"%s\"", rs->string); + if (rs->next) fprintf(fp, ", "); + else fprintf(fp, "}\n"); + } //end for + } //end for +} //end of the function BotDumpRandomStringList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_randomlist_t *BotLoadRandomStrings(char *filename) +{ + int pass, size; + char *ptr = NULL, chatmessagestring[MAX_MESSAGE_SIZE]; + source_t *source; + token_t token; + bot_randomlist_t *randomlist, *lastrandom, *random; + bot_randomstring_t *randomstring; + +#ifdef DEBUG + int starttime = Sys_MilliSeconds(); +#endif //DEBUG + + size = 0; + randomlist = NULL; + random = NULL; + //the synonyms are parsed in two phases + for (pass = 0; pass < 2; pass++) + { + // + if (pass && size) ptr = (char *) GetClearedHunkMemory(size); + // + PC_SetBaseFolder(BOTFILESBASEFOLDER); + source = LoadSourceFile(filename); + if (!source) + { + botimport.Print(PRT_ERROR, "counldn't load %s\n", filename); + return NULL; + } //end if + // + randomlist = NULL; //list + lastrandom = NULL; //last + // + while(PC_ReadToken(source, &token)) + { + if (token.type != TT_NAME) + { + SourceError(source, "unknown random %s", token.string); + FreeSource(source); + return NULL; + } //end if + size += sizeof(bot_randomlist_t) + strlen(token.string) + 1; + if (pass) + { + random = (bot_randomlist_t *) ptr; + ptr += sizeof(bot_randomlist_t); + random->string = ptr; + ptr += strlen(token.string) + 1; + strcpy(random->string, token.string); + random->firstrandomstring = NULL; + random->numstrings = 0; + // + if (lastrandom) lastrandom->next = random; + else randomlist = random; + lastrandom = random; + } //end if + if (!PC_ExpectTokenString(source, "=") || + !PC_ExpectTokenString(source, "{")) + { + FreeSource(source); + return NULL; + } //end if + while(!PC_CheckTokenString(source, "}")) + { + if (!BotLoadChatMessage(source, chatmessagestring)) + { + FreeSource(source); + return NULL; + } //end if + size += sizeof(bot_randomstring_t) + strlen(chatmessagestring) + 1; + if (pass) + { + randomstring = (bot_randomstring_t *) ptr; + ptr += sizeof(bot_randomstring_t); + randomstring->string = ptr; + ptr += strlen(chatmessagestring) + 1; + strcpy(randomstring->string, chatmessagestring); + // + random->numstrings++; + randomstring->next = random->firstrandomstring; + random->firstrandomstring = randomstring; + } //end if + } //end while + } //end while + //free the source after one pass + FreeSource(source); + } //end for + botimport.Print(PRT_MESSAGE, "loaded %s\n", filename); + // +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "random strings %d msec\n", Sys_MilliSeconds() - starttime); + //BotDumpRandomStringList(randomlist); +#endif //DEBUG + // + return randomlist; +} //end of the function BotLoadRandomStrings +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *RandomString(char *name) +{ + bot_randomlist_t *random; + bot_randomstring_t *rs; + int i; + + for (random = randomstrings; random; random = random->next) + { + if (!strcmp(random->string, name)) + { + i = random() * random->numstrings; + for (rs = random->firstrandomstring; rs; rs = rs->next) + { + if (--i < 0) break; + } //end for + if (rs) + { + return rs->string; + } //end if + } //end for + } //end for + return NULL; +} //end of the function RandomString +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDumpMatchTemplates(bot_matchtemplate_t *matches) +{ + FILE *fp; + bot_matchtemplate_t *mt; + bot_matchpiece_t *mp; + bot_matchstring_t *ms; + + fp = Log_FilePointer(); + if (!fp) return; + for (mt = matches; mt; mt = mt->next) + { + fprintf(fp, "{ " ); + for (mp = mt->first; mp; mp = mp->next) + { + if (mp->type == MT_STRING) + { + for (ms = mp->firststring; ms; ms = ms->next) + { + fprintf(fp, "\"%s\"", ms->string); + if (ms->next) fprintf(fp, "|"); + } //end for + } //end if + else if (mp->type == MT_VARIABLE) + { + fprintf(fp, "%d", mp->variable); + } //end else if + if (mp->next) fprintf(fp, ", "); + } //end for + fprintf(fp, " = (%d, %d);}\n", mt->type, mt->subtype); + } //end for +} //end of the function BotDumpMatchTemplates +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFreeMatchPieces(bot_matchpiece_t *matchpieces) +{ + bot_matchpiece_t *mp, *nextmp; + bot_matchstring_t *ms, *nextms; + + for (mp = matchpieces; mp; mp = nextmp) + { + nextmp = mp->next; + if (mp->type == MT_STRING) + { + for (ms = mp->firststring; ms; ms = nextms) + { + nextms = ms->next; + FreeMemory(ms); + } //end for + } //end if + FreeMemory(mp); + } //end for +} //end of the function BotFreeMatchPieces +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_matchpiece_t *BotLoadMatchPieces(source_t *source, char *endtoken) +{ + int lastwasvariable, emptystring; + token_t token; + bot_matchpiece_t *matchpiece, *firstpiece, *lastpiece; + bot_matchstring_t *matchstring, *lastmatchstring; + + firstpiece = NULL; + lastpiece = NULL; + // + lastwasvariable = qfalse; + // + while(PC_ReadToken(source, &token)) + { + if (token.type == TT_NUMBER && (token.subtype & TT_INTEGER)) + { + if (token.intvalue < 0 || token.intvalue >= MAX_MATCHVARIABLES) + { + SourceError(source, "can't have more than %d match variables\n", MAX_MATCHVARIABLES); + FreeSource(source); + BotFreeMatchPieces(firstpiece); + return NULL; + } //end if + if (lastwasvariable) + { + SourceError(source, "not allowed to have adjacent variables\n"); + FreeSource(source); + BotFreeMatchPieces(firstpiece); + return NULL; + } //end if + lastwasvariable = qtrue; + // + matchpiece = (bot_matchpiece_t *) GetClearedHunkMemory(sizeof(bot_matchpiece_t)); + matchpiece->type = MT_VARIABLE; + matchpiece->variable = token.intvalue; + matchpiece->next = NULL; + if (lastpiece) lastpiece->next = matchpiece; + else firstpiece = matchpiece; + lastpiece = matchpiece; + } //end if + else if (token.type == TT_STRING) + { + // + matchpiece = (bot_matchpiece_t *) GetClearedHunkMemory(sizeof(bot_matchpiece_t)); + matchpiece->firststring = NULL; + matchpiece->type = MT_STRING; + matchpiece->variable = 0; + matchpiece->next = NULL; + if (lastpiece) lastpiece->next = matchpiece; + else firstpiece = matchpiece; + lastpiece = matchpiece; + // + lastmatchstring = NULL; + emptystring = qfalse; + // + do + { + if (matchpiece->firststring) + { + if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) + { + FreeSource(source); + BotFreeMatchPieces(firstpiece); + return NULL; + } //end if + } //end if + StripDoubleQuotes(token.string); + matchstring = (bot_matchstring_t *) GetClearedHunkMemory(sizeof(bot_matchstring_t) + strlen(token.string) + 1); + matchstring->string = (char *) matchstring + sizeof(bot_matchstring_t); + strcpy(matchstring->string, token.string); + if (!strlen(token.string)) emptystring = qtrue; + matchstring->next = NULL; + if (lastmatchstring) lastmatchstring->next = matchstring; + else matchpiece->firststring = matchstring; + lastmatchstring = matchstring; + } while(PC_CheckTokenString(source, "|")); + //if there was no empty string found + if (!emptystring) lastwasvariable = qfalse; + } //end if + else + { + SourceError(source, "invalid token %s\n", token.string); + FreeSource(source); + BotFreeMatchPieces(firstpiece); + return NULL; + } //end else + if (PC_CheckTokenString(source, endtoken)) break; + if (!PC_ExpectTokenString(source, ",")) + { + FreeSource(source); + BotFreeMatchPieces(firstpiece); + return NULL; + } //end if + } //end while + return firstpiece; +} //end of the function BotLoadMatchPieces +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFreeMatchTemplates(bot_matchtemplate_t *mt) +{ + bot_matchtemplate_t *nextmt; + + for (; mt; mt = nextmt) + { + nextmt = mt->next; + BotFreeMatchPieces(mt->first); + FreeMemory(mt); + } //end for +} //end of the function BotFreeMatchTemplates +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_matchtemplate_t *BotLoadMatchTemplates(char *matchfile) +{ + source_t *source; + token_t token; + bot_matchtemplate_t *matchtemplate, *matches, *lastmatch; + unsigned long int context; + + PC_SetBaseFolder(BOTFILESBASEFOLDER); + source = LoadSourceFile(matchfile); + if (!source) + { + botimport.Print(PRT_ERROR, "counldn't load %s\n", matchfile); + return NULL; + } //end if + // + matches = NULL; //list with matches + lastmatch = NULL; //last match in the list + + while(PC_ReadToken(source, &token)) + { + if (token.type != TT_NUMBER || !(token.subtype & TT_INTEGER)) + { + SourceError(source, "expected integer, found %s\n", token.string); + BotFreeMatchTemplates(matches); + FreeSource(source); + return NULL; + } //end if + //the context + context = token.intvalue; + // + if (!PC_ExpectTokenString(source, "{")) + { + BotFreeMatchTemplates(matches); + FreeSource(source); + return NULL; + } //end if + // + while(PC_ReadToken(source, &token)) + { + if (!strcmp(token.string, "}")) break; + // + PC_UnreadLastToken(source); + // + matchtemplate = (bot_matchtemplate_t *) GetClearedHunkMemory(sizeof(bot_matchtemplate_t)); + matchtemplate->context = context; + matchtemplate->next = NULL; + //add the match template to the list + if (lastmatch) lastmatch->next = matchtemplate; + else matches = matchtemplate; + lastmatch = matchtemplate; + //load the match template + matchtemplate->first = BotLoadMatchPieces(source, "="); + if (!matchtemplate->first) + { + BotFreeMatchTemplates(matches); + return NULL; + } //end if + //read the match type + if (!PC_ExpectTokenString(source, "(") || + !PC_ExpectTokenType(source, TT_NUMBER, TT_INTEGER, &token)) + { + BotFreeMatchTemplates(matches); + FreeSource(source); + return NULL; + } //end if + matchtemplate->type = token.intvalue; + //read the match subtype + if (!PC_ExpectTokenString(source, ",") || + !PC_ExpectTokenType(source, TT_NUMBER, TT_INTEGER, &token)) + { + BotFreeMatchTemplates(matches); + FreeSource(source); + return NULL; + } //end if + matchtemplate->subtype = token.intvalue; + //read trailing punctuations + if (!PC_ExpectTokenString(source, ")") || + !PC_ExpectTokenString(source, ";")) + { + BotFreeMatchTemplates(matches); + FreeSource(source); + return NULL; + } //end if + } //end while + } //end while + //free the source + FreeSource(source); + botimport.Print(PRT_MESSAGE, "loaded %s\n", matchfile); + // + //BotDumpMatchTemplates(matches); + // + return matches; +} //end of the function BotLoadMatchTemplates +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int StringsMatch(bot_matchpiece_t *pieces, bot_match_t *match) +{ + int lastvariable, index; + char *strptr, *newstrptr; + bot_matchpiece_t *mp; + bot_matchstring_t *ms; + + //no last variable + lastvariable = -1; + //pointer to the string to compare the match string with + strptr = match->string; + //Log_Write("match: %s", strptr); + //compare the string with the current match string + for (mp = pieces; mp; mp = mp->next) + { + //if it is a piece of string + if (mp->type == MT_STRING) + { + newstrptr = NULL; + for (ms = mp->firststring; ms; ms = ms->next) + { + if (!strlen(ms->string)) + { + newstrptr = strptr; + break; + } //end if + //Log_Write("MT_STRING: %s", mp->string); + index = StringContains(strptr, ms->string, qfalse); + if (index >= 0) + { + newstrptr = strptr + index; + if (lastvariable >= 0) + { + match->variables[lastvariable].length = + (newstrptr - match->string) - match->variables[lastvariable].offset; + //newstrptr - match->variables[lastvariable].ptr; + lastvariable = -1; + break; + } //end if + else if (index == 0) + { + break; + } //end else + newstrptr = NULL; + } //end if + } //end for + if (!newstrptr) return qfalse; + strptr = newstrptr + strlen(ms->string); + } //end if + //if it is a variable piece of string + else if (mp->type == MT_VARIABLE) + { + //Log_Write("MT_VARIABLE"); + match->variables[mp->variable].offset = strptr - match->string; + lastvariable = mp->variable; + } //end else if + } //end for + //if a match was found + if (!mp && (lastvariable >= 0 || !strlen(strptr))) + { + //if the last piece was a variable string + if (lastvariable >= 0) + { + assert( match->variables[lastvariable].offset >= 0 ); // bk001204 + match->variables[lastvariable].length = + strlen(&match->string[ (int) match->variables[lastvariable].offset]); + } //end if + return qtrue; + } //end if + return qfalse; +} //end of the function StringsMatch +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotFindMatch(char *str, bot_match_t *match, unsigned long int context) +{ + int i; + bot_matchtemplate_t *ms; + + strncpy(match->string, str, MAX_MESSAGE_SIZE); + //remove any trailing enters + while(strlen(match->string) && + match->string[strlen(match->string)-1] == '\n') + { + match->string[strlen(match->string)-1] = '\0'; + } //end while + //compare the string with all the match strings + for (ms = matchtemplates; ms; ms = ms->next) + { + if (!(ms->context & context)) continue; + //reset the match variable offsets + for (i = 0; i < MAX_MATCHVARIABLES; i++) match->variables[i].offset = -1; + // + if (StringsMatch(ms->first, match)) + { + match->type = ms->type; + match->subtype = ms->subtype; + return qtrue; + } //end if + } //end for + return qfalse; +} //end of the function BotFindMatch +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotMatchVariable(bot_match_t *match, int variable, char *buf, int size) +{ + if (variable < 0 || variable >= MAX_MATCHVARIABLES) + { + botimport.Print(PRT_FATAL, "BotMatchVariable: variable out of range\n"); + strcpy(buf, ""); + return; + } //end if + + if (match->variables[variable].offset >= 0) + { + if (match->variables[variable].length < size) + size = match->variables[variable].length+1; + assert( match->variables[variable].offset >= 0 ); // bk001204 + strncpy(buf, &match->string[ (int) match->variables[variable].offset], size-1); + buf[size-1] = '\0'; + } //end if + else + { + strcpy(buf, ""); + } //end else + return; +} //end of the function BotMatchVariable +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_stringlist_t *BotFindStringInList(bot_stringlist_t *list, char *string) +{ + bot_stringlist_t *s; + + for (s = list; s; s = s->next) + { + if (!strcmp(s->string, string)) return s; + } //end for + return NULL; +} //end of the function BotFindStringInList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_stringlist_t *BotCheckChatMessageIntegrety(char *message, bot_stringlist_t *stringlist) +{ + int i; + char *msgptr; + char temp[MAX_MESSAGE_SIZE]; + bot_stringlist_t *s; + + msgptr = message; + // + while(*msgptr) + { + if (*msgptr == ESCAPE_CHAR) + { + msgptr++; + switch(*msgptr) + { + case 'v': //variable + { + //step over the 'v' + msgptr++; + while(*msgptr && *msgptr != ESCAPE_CHAR) msgptr++; + //step over the trailing escape char + if (*msgptr) msgptr++; + break; + } //end case + case 'r': //random + { + //step over the 'r' + msgptr++; + for (i = 0; (*msgptr && *msgptr != ESCAPE_CHAR); i++) + { + temp[i] = *msgptr++; + } //end while + temp[i] = '\0'; + //step over the trailing escape char + if (*msgptr) msgptr++; + //find the random keyword + if (!RandomString(temp)) + { + if (!BotFindStringInList(stringlist, temp)) + { + Log_Write("%s = {\"%s\"} //MISSING RANDOM\r\n", temp, temp); + s = GetClearedMemory(sizeof(bot_stringlist_t) + strlen(temp) + 1); + s->string = (char *) s + sizeof(bot_stringlist_t); + strcpy(s->string, temp); + s->next = stringlist; + stringlist = s; + } //end if + } //end if + break; + } //end case + default: + { + botimport.Print(PRT_FATAL, "BotCheckChatMessageIntegrety: message \"%s\" invalid escape char\n", message); + break; + } //end default + } //end switch + } //end if + else + { + msgptr++; + } //end else + } //end while + return stringlist; +} //end of the function BotCheckChatMessageIntegrety +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotCheckInitialChatIntegrety(bot_chat_t *chat) +{ + bot_chattype_t *t; + bot_chatmessage_t *cm; + bot_stringlist_t *stringlist, *s, *nexts; + + stringlist = NULL; + for (t = chat->types; t; t = t->next) + { + for (cm = t->firstchatmessage; cm; cm = cm->next) + { + stringlist = BotCheckChatMessageIntegrety(cm->chatmessage, stringlist); + } //end for + } //end for + for (s = stringlist; s; s = nexts) + { + nexts = s->next; + FreeMemory(s); + } //end for +} //end of the function BotCheckInitialChatIntegrety +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotCheckReplyChatIntegrety(bot_replychat_t *replychat) +{ + bot_replychat_t *rp; + bot_chatmessage_t *cm; + bot_stringlist_t *stringlist, *s, *nexts; + + stringlist = NULL; + for (rp = replychat; rp; rp = rp->next) + { + for (cm = rp->firstchatmessage; cm; cm = cm->next) + { + stringlist = BotCheckChatMessageIntegrety(cm->chatmessage, stringlist); + } //end for + } //end for + for (s = stringlist; s; s = nexts) + { + nexts = s->next; + FreeMemory(s); + } //end for +} //end of the function BotCheckReplyChatIntegrety +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDumpReplyChat(bot_replychat_t *replychat) +{ + FILE *fp; + bot_replychat_t *rp; + bot_replychatkey_t *key; + bot_chatmessage_t *cm; + bot_matchpiece_t *mp; + + fp = Log_FilePointer(); + if (!fp) return; + fprintf(fp, "BotDumpReplyChat:\n"); + for (rp = replychat; rp; rp = rp->next) + { + fprintf(fp, "["); + for (key = rp->keys; key; key = key->next) + { + if (key->flags & RCKFL_AND) fprintf(fp, "&"); + else if (key->flags & RCKFL_NOT) fprintf(fp, "!"); + // + if (key->flags & RCKFL_NAME) fprintf(fp, "name"); + else if (key->flags & RCKFL_GENDERFEMALE) fprintf(fp, "female"); + else if (key->flags & RCKFL_GENDERMALE) fprintf(fp, "male"); + else if (key->flags & RCKFL_GENDERLESS) fprintf(fp, "it"); + else if (key->flags & RCKFL_VARIABLES) + { + fprintf(fp, "("); + for (mp = key->match; mp; mp = mp->next) + { + if (mp->type == MT_STRING) fprintf(fp, "\"%s\"", mp->firststring->string); + else fprintf(fp, "%d", mp->variable); + if (mp->next) fprintf(fp, ", "); + } //end for + fprintf(fp, ")"); + } //end if + else if (key->flags & RCKFL_STRING) + { + fprintf(fp, "\"%s\"", key->string); + } //end if + if (key->next) fprintf(fp, ", "); + else fprintf(fp, "] = %1.0f\n", rp->priority); + } //end for + fprintf(fp, "{\n"); + for (cm = rp->firstchatmessage; cm; cm = cm->next) + { + fprintf(fp, "\t\"%s\";\n", cm->chatmessage); + } //end for + fprintf(fp, "}\n"); + } //end for +} //end of the function BotDumpReplyChat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFreeReplyChat(bot_replychat_t *replychat) +{ + bot_replychat_t *rp, *nextrp; + bot_replychatkey_t *key, *nextkey; + bot_chatmessage_t *cm, *nextcm; + + for (rp = replychat; rp; rp = nextrp) + { + nextrp = rp->next; + for (key = rp->keys; key; key = nextkey) + { + nextkey = key->next; + if (key->match) BotFreeMatchPieces(key->match); + if (key->string) FreeMemory(key->string); + FreeMemory(key); + } //end for + for (cm = rp->firstchatmessage; cm; cm = nextcm) + { + nextcm = cm->next; + FreeMemory(cm); + } //end for + FreeMemory(rp); + } //end for +} //end of the function BotFreeReplyChat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotCheckValidReplyChatKeySet(source_t *source, bot_replychatkey_t *keys) +{ + int allprefixed, hasvariableskey, hasstringkey; + bot_matchpiece_t *m; + bot_matchstring_t *ms; + bot_replychatkey_t *key, *key2; + + // + allprefixed = qtrue; + hasvariableskey = hasstringkey = qfalse; + for (key = keys; key; key = key->next) + { + if (!(key->flags & (RCKFL_AND|RCKFL_NOT))) + { + allprefixed = qfalse; + if (key->flags & RCKFL_VARIABLES) + { + for (m = key->match; m; m = m->next) + { + if (m->type == MT_VARIABLE) hasvariableskey = qtrue; + } //end for + } //end if + else if (key->flags & RCKFL_STRING) + { + hasstringkey = qtrue; + } //end else if + } //end if + else if ((key->flags & RCKFL_AND) && (key->flags & RCKFL_STRING)) + { + for (key2 = keys; key2; key2 = key2->next) + { + if (key2 == key) continue; + if (key2->flags & RCKFL_NOT) continue; + if (key2->flags & RCKFL_VARIABLES) + { + for (m = key2->match; m; m = m->next) + { + if (m->type == MT_STRING) + { + for (ms = m->firststring; ms; ms = ms->next) + { + if (StringContains(ms->string, key->string, qfalse) != -1) + { + break; + } //end if + } //end for + if (ms) break; + } //end if + else if (m->type == MT_VARIABLE) + { + break; + } //end if + } //end for + if (!m) + { + SourceWarning(source, "one of the match templates does not " + "leave space for the key %s with the & prefix", key->string); + } //end if + } //end if + } //end for + } //end else + if ((key->flags & RCKFL_NOT) && (key->flags & RCKFL_STRING)) + { + for (key2 = keys; key2; key2 = key2->next) + { + if (key2 == key) continue; + if (key2->flags & RCKFL_NOT) continue; + if (key2->flags & RCKFL_STRING) + { + if (StringContains(key2->string, key->string, qfalse) != -1) + { + SourceWarning(source, "the key %s with prefix ! is inside the key %s", key->string, key2->string); + } //end if + } //end if + else if (key2->flags & RCKFL_VARIABLES) + { + for (m = key2->match; m; m = m->next) + { + if (m->type == MT_STRING) + { + for (ms = m->firststring; ms; ms = ms->next) + { + if (StringContains(ms->string, key->string, qfalse) != -1) + { + SourceWarning(source, "the key %s with prefix ! is inside " + "the match template string %s", key->string, ms->string); + } //end if + } //end for + } //end if + } //end for + } //end else if + } //end for + } //end if + } //end for + if (allprefixed) SourceWarning(source, "all keys have a & or ! prefix"); + if (hasvariableskey && hasstringkey) + { + SourceWarning(source, "variables from the match template(s) could be " + "invalid when outputting one of the chat messages"); + } //end if +} //end of the function BotCheckValidReplyChatKeySet +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_replychat_t *BotLoadReplyChat(char *filename) +{ + char chatmessagestring[MAX_MESSAGE_SIZE]; + char namebuffer[MAX_MESSAGE_SIZE]; + source_t *source; + token_t token; + bot_chatmessage_t *chatmessage = NULL; + bot_replychat_t *replychat, *replychatlist; + bot_replychatkey_t *key; + + PC_SetBaseFolder(BOTFILESBASEFOLDER); + source = LoadSourceFile(filename); + if (!source) + { + botimport.Print(PRT_ERROR, "counldn't load %s\n", filename); + return NULL; + } //end if + // + replychatlist = NULL; + // + while(PC_ReadToken(source, &token)) + { + if (strcmp(token.string, "[")) + { + SourceError(source, "expected [, found %s", token.string); + BotFreeReplyChat(replychatlist); + FreeSource(source); + return NULL; + } //end if + // + replychat = GetClearedHunkMemory(sizeof(bot_replychat_t)); + replychat->keys = NULL; + replychat->next = replychatlist; + replychatlist = replychat; + //read the keys, there must be at least one key + do + { + //allocate a key + key = (bot_replychatkey_t *) GetClearedHunkMemory(sizeof(bot_replychatkey_t)); + key->flags = 0; + key->string = NULL; + key->match = NULL; + key->next = replychat->keys; + replychat->keys = key; + //check for MUST BE PRESENT and MUST BE ABSENT keys + if (PC_CheckTokenString(source, "&")) key->flags |= RCKFL_AND; + else if (PC_CheckTokenString(source, "!")) key->flags |= RCKFL_NOT; + //special keys + if (PC_CheckTokenString(source, "name")) key->flags |= RCKFL_NAME; + else if (PC_CheckTokenString(source, "female")) key->flags |= RCKFL_GENDERFEMALE; + else if (PC_CheckTokenString(source, "male")) key->flags |= RCKFL_GENDERMALE; + else if (PC_CheckTokenString(source, "it")) key->flags |= RCKFL_GENDERLESS; + else if (PC_CheckTokenString(source, "(")) //match key + { + key->flags |= RCKFL_VARIABLES; + key->match = BotLoadMatchPieces(source, ")"); + if (!key->match) + { + BotFreeReplyChat(replychatlist); + return NULL; + } //end if + } //end else if + else if (PC_CheckTokenString(source, "<")) //bot names + { + key->flags |= RCKFL_BOTNAMES; + strcpy(namebuffer, ""); + do + { + if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) + { + BotFreeReplyChat(replychatlist); + FreeSource(source); + return NULL; + } //end if + StripDoubleQuotes(token.string); + if (strlen(namebuffer)) strcat(namebuffer, "\\"); + strcat(namebuffer, token.string); + } while(PC_CheckTokenString(source, ",")); + if (!PC_ExpectTokenString(source, ">")) + { + BotFreeReplyChat(replychatlist); + FreeSource(source); + return NULL; + } //end if + key->string = (char *) GetClearedHunkMemory(strlen(namebuffer) + 1); + strcpy(key->string, namebuffer); + } //end else if + else //normal string key + { + key->flags |= RCKFL_STRING; + if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) + { + BotFreeReplyChat(replychatlist); + FreeSource(source); + return NULL; + } //end if + StripDoubleQuotes(token.string); + key->string = (char *) GetClearedHunkMemory(strlen(token.string) + 1); + strcpy(key->string, token.string); + } //end else + // + PC_CheckTokenString(source, ","); + } while(!PC_CheckTokenString(source, "]")); + // + BotCheckValidReplyChatKeySet(source, replychat->keys); + //read the = sign and the priority + if (!PC_ExpectTokenString(source, "=") || + !PC_ExpectTokenType(source, TT_NUMBER, 0, &token)) + { + BotFreeReplyChat(replychatlist); + FreeSource(source); + return NULL; + } //end if + replychat->priority = token.floatvalue; + //read the leading { + if (!PC_ExpectTokenString(source, "{")) + { + BotFreeReplyChat(replychatlist); + FreeSource(source); + return NULL; + } //end if + replychat->numchatmessages = 0; + //while the trailing } is not found + while(!PC_CheckTokenString(source, "}")) + { + if (!BotLoadChatMessage(source, chatmessagestring)) + { + BotFreeReplyChat(replychatlist); + FreeSource(source); + return NULL; + } //end if + chatmessage = (bot_chatmessage_t *) GetClearedHunkMemory(sizeof(bot_chatmessage_t) + strlen(chatmessagestring) + 1); + chatmessage->chatmessage = (char *) chatmessage + sizeof(bot_chatmessage_t); + strcpy(chatmessage->chatmessage, chatmessagestring); + chatmessage->time = -2*CHATMESSAGE_RECENTTIME; + chatmessage->next = replychat->firstchatmessage; + //add the chat message to the reply chat + replychat->firstchatmessage = chatmessage; + replychat->numchatmessages++; + } //end while + } //end while + FreeSource(source); + botimport.Print(PRT_MESSAGE, "loaded %s\n", filename); + // + //BotDumpReplyChat(replychatlist); + if (bot_developer) + { + BotCheckReplyChatIntegrety(replychatlist); + } //end if + // + if (!replychatlist) botimport.Print(PRT_MESSAGE, "no rchats\n"); + // + return replychatlist; +} //end of the function BotLoadReplyChat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDumpInitialChat(bot_chat_t *chat) +{ + bot_chattype_t *t; + bot_chatmessage_t *m; + + Log_Write("{"); + for (t = chat->types; t; t = t->next) + { + Log_Write(" type \"%s\"", t->name); + Log_Write(" {"); + Log_Write(" numchatmessages = %d", t->numchatmessages); + for (m = t->firstchatmessage; m; m = m->next) + { + Log_Write(" \"%s\"", m->chatmessage); + } //end for + Log_Write(" }"); + } //end for + Log_Write("}"); +} //end of the function BotDumpInitialChat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_chat_t *BotLoadInitialChat(char *chatfile, char *chatname) +{ + int pass, foundchat, indent, size; + char *ptr = NULL; + char chatmessagestring[MAX_MESSAGE_SIZE]; + source_t *source; + token_t token; + bot_chat_t *chat = NULL; + bot_chattype_t *chattype = NULL; + bot_chatmessage_t *chatmessage = NULL; +#ifdef DEBUG + int starttime; + + starttime = Sys_MilliSeconds(); +#endif //DEBUG + // + size = 0; + foundchat = qfalse; + //a bot chat is parsed in two phases + for (pass = 0; pass < 2; pass++) + { + //allocate memory + if (pass && size) ptr = (char *) GetClearedMemory(size); + //load the source file + PC_SetBaseFolder(BOTFILESBASEFOLDER); + source = LoadSourceFile(chatfile); + if (!source) + { + botimport.Print(PRT_ERROR, "counldn't load %s\n", chatfile); + return NULL; + } //end if + //chat structure + if (pass) + { + chat = (bot_chat_t *) ptr; + ptr += sizeof(bot_chat_t); + } //end if + size = sizeof(bot_chat_t); + // + while(PC_ReadToken(source, &token)) + { + if (!strcmp(token.string, "chat")) + { + if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) + { + FreeSource(source); + return NULL; + } //end if + StripDoubleQuotes(token.string); + //after the chat name we expect a opening brace + if (!PC_ExpectTokenString(source, "{")) + { + FreeSource(source); + return NULL; + } //end if + //if the chat name is found + if (!Q_stricmp(token.string, chatname)) + { + foundchat = qtrue; + //read the chat types + while(1) + { + if (!PC_ExpectAnyToken(source, &token)) + { + FreeSource(source); + return NULL; + } //end if + if (!strcmp(token.string, "}")) break; + if (strcmp(token.string, "type")) + { + SourceError(source, "expected type found %s\n", token.string); + FreeSource(source); + return NULL; + } //end if + //expect the chat type name + if (!PC_ExpectTokenType(source, TT_STRING, 0, &token) || + !PC_ExpectTokenString(source, "{")) + { + FreeSource(source); + return NULL; + } //end if + StripDoubleQuotes(token.string); + if (pass) + { + chattype = (bot_chattype_t *) ptr; + strncpy(chattype->name, token.string, MAX_CHATTYPE_NAME); + chattype->firstchatmessage = NULL; + //add the chat type to the chat + chattype->next = chat->types; + chat->types = chattype; + // + ptr += sizeof(bot_chattype_t); + } //end if + size += sizeof(bot_chattype_t); + //read the chat messages + while(!PC_CheckTokenString(source, "}")) + { + if (!BotLoadChatMessage(source, chatmessagestring)) + { + FreeSource(source); + return NULL; + } //end if + if (pass) + { + chatmessage = (bot_chatmessage_t *) ptr; + chatmessage->time = -2*CHATMESSAGE_RECENTTIME; + //put the chat message in the list + chatmessage->next = chattype->firstchatmessage; + chattype->firstchatmessage = chatmessage; + //store the chat message + ptr += sizeof(bot_chatmessage_t); + chatmessage->chatmessage = ptr; + strcpy(chatmessage->chatmessage, chatmessagestring); + ptr += strlen(chatmessagestring) + 1; + //the number of chat messages increased + chattype->numchatmessages++; + } //end if + size += sizeof(bot_chatmessage_t) + strlen(chatmessagestring) + 1; + } //end if + } //end while + } //end if + else //skip the bot chat + { + indent = 1; + while(indent) + { + if (!PC_ExpectAnyToken(source, &token)) + { + FreeSource(source); + return NULL; + } //end if + if (!strcmp(token.string, "{")) indent++; + else if (!strcmp(token.string, "}")) indent--; + } //end while + } //end else + } //end if + else + { + SourceError(source, "unknown definition %s\n", token.string); + FreeSource(source); + return NULL; + } //end else + } //end while + //free the source + FreeSource(source); + //if the requested character is not found + if (!foundchat) + { + botimport.Print(PRT_ERROR, "couldn't find chat %s in %s\n", chatname, chatfile); + return NULL; + } //end if + } //end for + // + botimport.Print(PRT_MESSAGE, "loaded %s from %s\n", chatname, chatfile); + // + //BotDumpInitialChat(chat); + if (bot_developer) + { + BotCheckInitialChatIntegrety(chat); + } //end if +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "initial chats loaded in %d msec\n", Sys_MilliSeconds() - starttime); +#endif //DEBUG + //character was read succesfully + return chat; +} //end of the function BotLoadInitialChat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFreeChatFile(int chatstate) +{ + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return; + if (cs->chat) FreeMemory(cs->chat); + cs->chat = NULL; +} //end of the function BotFreeChatFile +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotLoadChatFile(int chatstate, char *chatfile, char *chatname) +{ + bot_chatstate_t *cs; + int n, avail = 0; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return BLERR_CANNOTLOADICHAT; + BotFreeChatFile(chatstate); + + if (!LibVarGetValue("bot_reloadcharacters")) + { + avail = -1; + for( n = 0; n < MAX_CLIENTS; n++ ) { + if( !ichatdata[n] ) { + if( avail == -1 ) { + avail = n; + } + continue; + } + if( strcmp( chatfile, ichatdata[n]->filename ) != 0 ) { + continue; + } + if( strcmp( chatname, ichatdata[n]->chatname ) != 0 ) { + continue; + } + cs->chat = ichatdata[n]->chat; + // botimport.Print( PRT_MESSAGE, "retained %s from %s\n", chatname, chatfile ); + return BLERR_NOERROR; + } + + if( avail == -1 ) { + botimport.Print(PRT_FATAL, "ichatdata table full; couldn't load chat %s from %s\n", chatname, chatfile); + return BLERR_CANNOTLOADICHAT; + } + } + + cs->chat = BotLoadInitialChat(chatfile, chatname); + if (!cs->chat) + { + botimport.Print(PRT_FATAL, "couldn't load chat %s from %s\n", chatname, chatfile); + return BLERR_CANNOTLOADICHAT; + } //end if + if (!LibVarGetValue("bot_reloadcharacters")) + { + ichatdata[avail] = GetClearedMemory( sizeof(bot_ichatdata_t) ); + ichatdata[avail]->chat = cs->chat; + Q_strncpyz( ichatdata[avail]->chatname, chatname, sizeof(ichatdata[avail]->chatname) ); + Q_strncpyz( ichatdata[avail]->filename, chatfile, sizeof(ichatdata[avail]->filename) ); + } //end if + + return BLERR_NOERROR; +} //end of the function BotLoadChatFile +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotExpandChatMessage(char *outmessage, char *message, unsigned long mcontext, + bot_match_t *match, unsigned long vcontext, int reply) +{ + int num, len, i, expansion; + char *outputbuf, *ptr, *msgptr; + char temp[MAX_MESSAGE_SIZE]; + + expansion = qfalse; + msgptr = message; + outputbuf = outmessage; + len = 0; + // + while(*msgptr) + { + if (*msgptr == ESCAPE_CHAR) + { + msgptr++; + switch(*msgptr) + { + case 'v': //variable + { + msgptr++; + num = 0; + while(*msgptr && *msgptr != ESCAPE_CHAR) + { + num = num * 10 + (*msgptr++) - '0'; + } //end while + //step over the trailing escape char + if (*msgptr) msgptr++; + if (num > MAX_MATCHVARIABLES) + { + botimport.Print(PRT_ERROR, "BotConstructChat: message %s variable %d out of range\n", message, num); + return qfalse; + } //end if + if (match->variables[num].offset >= 0) + { + assert( match->variables[num].offset >= 0 ); // bk001204 + ptr = &match->string[ (int) match->variables[num].offset]; + for (i = 0; i < match->variables[num].length; i++) + { + temp[i] = ptr[i]; + } //end for + temp[i] = 0; + //if it's a reply message + if (reply) + { + //replace the reply synonyms in the variables + BotReplaceReplySynonyms(temp, vcontext); + } //end if + else + { + //replace synonyms in the variable context + BotReplaceSynonyms(temp, vcontext); + } //end else + // + if (len + strlen(temp) >= MAX_MESSAGE_SIZE) + { + botimport.Print(PRT_ERROR, "BotConstructChat: message %s too long\n", message); + return qfalse; + } //end if + strcpy(&outputbuf[len], temp); + len += strlen(temp); + } //end if + break; + } //end case + case 'r': //random + { + msgptr++; + for (i = 0; (*msgptr && *msgptr != ESCAPE_CHAR); i++) + { + temp[i] = *msgptr++; + } //end while + temp[i] = '\0'; + //step over the trailing escape char + if (*msgptr) msgptr++; + //find the random keyword + ptr = RandomString(temp); + if (!ptr) + { + botimport.Print(PRT_ERROR, "BotConstructChat: unknown random string %s\n", temp); + return qfalse; + } //end if + if (len + strlen(ptr) >= MAX_MESSAGE_SIZE) + { + botimport.Print(PRT_ERROR, "BotConstructChat: message \"%s\" too long\n", message); + return qfalse; + } //end if + strcpy(&outputbuf[len], ptr); + len += strlen(ptr); + expansion = qtrue; + break; + } //end case + default: + { + botimport.Print(PRT_FATAL, "BotConstructChat: message \"%s\" invalid escape char\n", message); + break; + } //end default + } //end switch + } //end if + else + { + outputbuf[len++] = *msgptr++; + if (len >= MAX_MESSAGE_SIZE) + { + botimport.Print(PRT_ERROR, "BotConstructChat: message \"%s\" too long\n", message); + break; + } //end if + } //end else + } //end while + outputbuf[len] = '\0'; + //replace synonyms weighted in the message context + BotReplaceWeightedSynonyms(outputbuf, mcontext); + //return true if a random was expanded + return expansion; +} //end of the function BotExpandChatMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotConstructChatMessage(bot_chatstate_t *chatstate, char *message, unsigned long mcontext, + bot_match_t *match, unsigned long vcontext, int reply) +{ + int i; + char srcmessage[MAX_MESSAGE_SIZE]; + + strcpy(srcmessage, message); + for (i = 0; i < 10; i++) + { + if (!BotExpandChatMessage(chatstate->chatmessage, srcmessage, mcontext, match, vcontext, reply)) + { + break; + } //end if + strcpy(srcmessage, chatstate->chatmessage); + } //end for + if (i >= 10) + { + botimport.Print(PRT_WARNING, "too many expansions in chat message\n"); + botimport.Print(PRT_WARNING, "%s\n", chatstate->chatmessage); + } //end if +} //end of the function BotConstructChatMessage +//=========================================================================== +// randomly chooses one of the chat message of the given type +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *BotChooseInitialChatMessage(bot_chatstate_t *cs, char *type) +{ + int n, numchatmessages; + float besttime; + bot_chattype_t *t; + bot_chatmessage_t *m, *bestchatmessage; + bot_chat_t *chat; + + chat = cs->chat; + for (t = chat->types; t; t = t->next) + { + if (!Q_stricmp(t->name, type)) + { + numchatmessages = 0; + for (m = t->firstchatmessage; m; m = m->next) + { + if (m->time > AAS_Time()) continue; + numchatmessages++; + } //end if + //if all chat messages have been used recently + if (numchatmessages <= 0) + { + besttime = 0; + bestchatmessage = NULL; + for (m = t->firstchatmessage; m; m = m->next) + { + if (!besttime || m->time < besttime) + { + bestchatmessage = m; + besttime = m->time; + } //end if + } //end for + if (bestchatmessage) return bestchatmessage->chatmessage; + } //end if + else //choose a chat message randomly + { + n = random() * numchatmessages; + for (m = t->firstchatmessage; m; m = m->next) + { + if (m->time > AAS_Time()) continue; + if (--n < 0) + { + m->time = AAS_Time() + CHATMESSAGE_RECENTTIME; + return m->chatmessage; + } //end if + } //end for + } //end else + return NULL; + } //end if + } //end for + return NULL; +} //end of the function BotChooseInitialChatMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotNumInitialChats(int chatstate, char *type) +{ + bot_chatstate_t *cs; + bot_chattype_t *t; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return 0; + + for (t = cs->chat->types; t; t = t->next) + { + if (!Q_stricmp(t->name, type)) + { + if (LibVarGetValue("bot_testichat")) { + botimport.Print(PRT_MESSAGE, "%s has %d chat lines\n", type, t->numchatmessages); + botimport.Print(PRT_MESSAGE, "-------------------\n"); + } + return t->numchatmessages; + } //end if + } //end for + return 0; +} //end of the function BotNumInitialChats +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotInitialChat(int chatstate, char *type, int mcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7) +{ + char *message; + int index; + bot_match_t match; + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return; + //if no chat file is loaded + if (!cs->chat) return; + //choose a chat message randomly of the given type + message = BotChooseInitialChatMessage(cs, type); + //if there's no message of the given type + if (!message) + { +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "no chat messages of type %s\n", type); +#endif //DEBUG + return; + } //end if + // + Com_Memset(&match, 0, sizeof(match)); + index = 0; + if( var0 ) { + strcat(match.string, var0); + match.variables[0].offset = index; + match.variables[0].length = strlen(var0); + index += strlen(var0); + } + if( var1 ) { + strcat(match.string, var1); + match.variables[1].offset = index; + match.variables[1].length = strlen(var1); + index += strlen(var1); + } + if( var2 ) { + strcat(match.string, var2); + match.variables[2].offset = index; + match.variables[2].length = strlen(var2); + index += strlen(var2); + } + if( var3 ) { + strcat(match.string, var3); + match.variables[3].offset = index; + match.variables[3].length = strlen(var3); + index += strlen(var3); + } + if( var4 ) { + strcat(match.string, var4); + match.variables[4].offset = index; + match.variables[4].length = strlen(var4); + index += strlen(var4); + } + if( var5 ) { + strcat(match.string, var5); + match.variables[5].offset = index; + match.variables[5].length = strlen(var5); + index += strlen(var5); + } + if( var6 ) { + strcat(match.string, var6); + match.variables[6].offset = index; + match.variables[6].length = strlen(var6); + index += strlen(var6); + } + if( var7 ) { + strcat(match.string, var7); + match.variables[7].offset = index; + match.variables[7].length = strlen(var7); + index += strlen(var7); + } + // + BotConstructChatMessage(cs, message, mcontext, &match, 0, qfalse); +} //end of the function BotInitialChat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotPrintReplyChatKeys(bot_replychat_t *replychat) +{ + bot_replychatkey_t *key; + bot_matchpiece_t *mp; + + botimport.Print(PRT_MESSAGE, "["); + for (key = replychat->keys; key; key = key->next) + { + if (key->flags & RCKFL_AND) botimport.Print(PRT_MESSAGE, "&"); + else if (key->flags & RCKFL_NOT) botimport.Print(PRT_MESSAGE, "!"); + // + if (key->flags & RCKFL_NAME) botimport.Print(PRT_MESSAGE, "name"); + else if (key->flags & RCKFL_GENDERFEMALE) botimport.Print(PRT_MESSAGE, "female"); + else if (key->flags & RCKFL_GENDERMALE) botimport.Print(PRT_MESSAGE, "male"); + else if (key->flags & RCKFL_GENDERLESS) botimport.Print(PRT_MESSAGE, "it"); + else if (key->flags & RCKFL_VARIABLES) + { + botimport.Print(PRT_MESSAGE, "("); + for (mp = key->match; mp; mp = mp->next) + { + if (mp->type == MT_STRING) botimport.Print(PRT_MESSAGE, "\"%s\"", mp->firststring->string); + else botimport.Print(PRT_MESSAGE, "%d", mp->variable); + if (mp->next) botimport.Print(PRT_MESSAGE, ", "); + } //end for + botimport.Print(PRT_MESSAGE, ")"); + } //end if + else if (key->flags & RCKFL_STRING) + { + botimport.Print(PRT_MESSAGE, "\"%s\"", key->string); + } //end if + if (key->next) botimport.Print(PRT_MESSAGE, ", "); + else botimport.Print(PRT_MESSAGE, "] = %1.0f\n", replychat->priority); + } //end for + botimport.Print(PRT_MESSAGE, "{\n"); +} //end of the function BotPrintReplyChatKeys +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotReplyChat(int chatstate, char *message, int mcontext, int vcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7) +{ + bot_replychat_t *rchat, *bestrchat; + bot_replychatkey_t *key; + bot_chatmessage_t *m, *bestchatmessage; + bot_match_t match, bestmatch; + int bestpriority, num, found, res, numchatmessages, index; + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return qfalse; + Com_Memset(&match, 0, sizeof(bot_match_t)); + strcpy(match.string, message); + bestpriority = -1; + bestchatmessage = NULL; + bestrchat = NULL; + //go through all the reply chats + for (rchat = replychats; rchat; rchat = rchat->next) + { + found = qfalse; + for (key = rchat->keys; key; key = key->next) + { + res = qfalse; + //get the match result + if (key->flags & RCKFL_NAME) res = (StringContains(message, cs->name, qfalse) != -1); + else if (key->flags & RCKFL_BOTNAMES) res = (StringContains(key->string, cs->name, qfalse) != -1); + else if (key->flags & RCKFL_GENDERFEMALE) res = (cs->gender == CHAT_GENDERFEMALE); + else if (key->flags & RCKFL_GENDERMALE) res = (cs->gender == CHAT_GENDERMALE); + else if (key->flags & RCKFL_GENDERLESS) res = (cs->gender == CHAT_GENDERLESS); + else if (key->flags & RCKFL_VARIABLES) res = StringsMatch(key->match, &match); + else if (key->flags & RCKFL_STRING) res = (StringContainsWord(message, key->string, qfalse) != NULL); + //if the key must be present + if (key->flags & RCKFL_AND) + { + if (!res) + { + found = qfalse; + break; + } //end if + } //end else if + //if the key must be absent + else if (key->flags & RCKFL_NOT) + { + if (res) + { + found = qfalse; + break; + } //end if + } //end if + else if (res) + { + found = qtrue; + } //end else + } //end for + // + if (found) + { + if (rchat->priority > bestpriority) + { + numchatmessages = 0; + for (m = rchat->firstchatmessage; m; m = m->next) + { + if (m->time > AAS_Time()) continue; + numchatmessages++; + } //end if + num = random() * numchatmessages; + for (m = rchat->firstchatmessage; m; m = m->next) + { + if (--num < 0) break; + if (m->time > AAS_Time()) continue; + } //end for + //if the reply chat has a message + if (m) + { + Com_Memcpy(&bestmatch, &match, sizeof(bot_match_t)); + bestchatmessage = m; + bestrchat = rchat; + bestpriority = rchat->priority; + } //end if + } //end if + } //end if + } //end for + if (bestchatmessage) + { + index = strlen(bestmatch.string); + if( var0 ) { + strcat(bestmatch.string, var0); + bestmatch.variables[0].offset = index; + bestmatch.variables[0].length = strlen(var0); + index += strlen(var0); + } + if( var1 ) { + strcat(bestmatch.string, var1); + bestmatch.variables[1].offset = index; + bestmatch.variables[1].length = strlen(var1); + index += strlen(var1); + } + if( var2 ) { + strcat(bestmatch.string, var2); + bestmatch.variables[2].offset = index; + bestmatch.variables[2].length = strlen(var2); + index += strlen(var2); + } + if( var3 ) { + strcat(bestmatch.string, var3); + bestmatch.variables[3].offset = index; + bestmatch.variables[3].length = strlen(var3); + index += strlen(var3); + } + if( var4 ) { + strcat(bestmatch.string, var4); + bestmatch.variables[4].offset = index; + bestmatch.variables[4].length = strlen(var4); + index += strlen(var4); + } + if( var5 ) { + strcat(bestmatch.string, var5); + bestmatch.variables[5].offset = index; + bestmatch.variables[5].length = strlen(var5); + index += strlen(var5); + } + if( var6 ) { + strcat(bestmatch.string, var6); + bestmatch.variables[6].offset = index; + bestmatch.variables[6].length = strlen(var6); + index += strlen(var6); + } + if( var7 ) { + strcat(bestmatch.string, var7); + bestmatch.variables[7].offset = index; + bestmatch.variables[7].length = strlen(var7); + index += strlen(var7); + } + if (LibVarGetValue("bot_testrchat")) + { + for (m = bestrchat->firstchatmessage; m; m = m->next) + { + BotConstructChatMessage(cs, m->chatmessage, mcontext, &bestmatch, vcontext, qtrue); + BotRemoveTildes(cs->chatmessage); + botimport.Print(PRT_MESSAGE, "%s\n", cs->chatmessage); + } //end if + } //end if + else + { + bestchatmessage->time = AAS_Time() + CHATMESSAGE_RECENTTIME; + BotConstructChatMessage(cs, bestchatmessage->chatmessage, mcontext, &bestmatch, vcontext, qtrue); + } //end else + return qtrue; + } //end if + return qfalse; +} //end of the function BotReplyChat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotChatLength(int chatstate) +{ + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return 0; + return strlen(cs->chatmessage); +} //end of the function BotChatLength +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotEnterChat(int chatstate, int clientto, int sendto) +{ + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return; + + if (strlen(cs->chatmessage)) + { + BotRemoveTildes(cs->chatmessage); + if (LibVarGetValue("bot_testichat")) { + botimport.Print(PRT_MESSAGE, "%s\n", cs->chatmessage); + } + else { + switch(sendto) { + case CHAT_TEAM: + EA_Command(cs->client, va("say_team %s", cs->chatmessage)); + break; + case CHAT_TELL: + EA_Command(cs->client, va("tell %d %s", clientto, cs->chatmessage)); + break; + default: //CHAT_ALL + EA_Command(cs->client, va("say %s", cs->chatmessage)); + break; + } + } + //clear the chat message from the state + strcpy(cs->chatmessage, ""); + } //end if +} //end of the function BotEnterChat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotGetChatMessage(int chatstate, char *buf, int size) +{ + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return; + + BotRemoveTildes(cs->chatmessage); + strncpy(buf, cs->chatmessage, size-1); + buf[size-1] = '\0'; + //clear the chat message from the state + strcpy(cs->chatmessage, ""); +} //end of the function BotGetChatMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotSetChatGender(int chatstate, int gender) +{ + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return; + switch(gender) + { + case CHAT_GENDERFEMALE: cs->gender = CHAT_GENDERFEMALE; break; + case CHAT_GENDERMALE: cs->gender = CHAT_GENDERMALE; break; + default: cs->gender = CHAT_GENDERLESS; break; + } //end switch +} //end of the function BotSetChatGender +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotSetChatName(int chatstate, char *name, int client) +{ + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return; + cs->client = client; + Com_Memset(cs->name, 0, sizeof(cs->name)); + strncpy(cs->name, name, sizeof(cs->name)); + cs->name[sizeof(cs->name)-1] = '\0'; +} //end of the function BotSetChatName +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotResetChatAI(void) +{ + bot_replychat_t *rchat; + bot_chatmessage_t *m; + + for (rchat = replychats; rchat; rchat = rchat->next) + { + for (m = rchat->firstchatmessage; m; m = m->next) + { + m->time = 0; + } //end for + } //end for +} //end of the function BotResetChatAI +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +int BotAllocChatState(void) +{ + int i; + + for (i = 1; i <= MAX_CLIENTS; i++) + { + if (!botchatstates[i]) + { + botchatstates[i] = GetClearedMemory(sizeof(bot_chatstate_t)); + return i; + } //end if + } //end for + return 0; +} //end of the function BotAllocChatState +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotFreeChatState(int handle) +{ + bot_chatstate_t *cs; + bot_consolemessage_t m; + int h; + + if (handle <= 0 || handle > MAX_CLIENTS) + { + botimport.Print(PRT_FATAL, "chat state handle %d out of range\n", handle); + return; + } //end if + if (!botchatstates[handle]) + { + botimport.Print(PRT_FATAL, "invalid chat state %d\n", handle); + return; + } //end if + cs = botchatstates[handle]; + if (LibVarGetValue("bot_reloadcharacters")) + { + BotFreeChatFile(handle); + } //end if + //free all the console messages left in the chat state + for (h = BotNextConsoleMessage(handle, &m); h; h = BotNextConsoleMessage(handle, &m)) + { + //remove the console message + BotRemoveConsoleMessage(handle, h); + } //end for + FreeMemory(botchatstates[handle]); + botchatstates[handle] = NULL; +} //end of the function BotFreeChatState +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotSetupChatAI(void) +{ + char *file; + +#ifdef DEBUG + int starttime = Sys_MilliSeconds(); +#endif //DEBUG + + file = LibVarString("synfile", "syn.c"); + synonyms = BotLoadSynonyms(file); + file = LibVarString("rndfile", "rnd.c"); + randomstrings = BotLoadRandomStrings(file); + file = LibVarString("matchfile", "match.c"); + matchtemplates = BotLoadMatchTemplates(file); + // + if (!LibVarValue("nochat", "0")) + { + file = LibVarString("rchatfile", "rchat.c"); + replychats = BotLoadReplyChat(file); + } //end if + + InitConsoleMessageHeap(); + +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "setup chat AI %d msec\n", Sys_MilliSeconds() - starttime); +#endif //DEBUG + return BLERR_NOERROR; +} //end of the function BotSetupChatAI +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotShutdownChatAI(void) +{ + int i; + + //free all remaining chat states + for(i = 0; i < MAX_CLIENTS; i++) + { + if (botchatstates[i]) + { + BotFreeChatState(i); + } //end if + } //end for + //free all cached chats + for(i = 0; i < MAX_CLIENTS; i++) + { + if (ichatdata[i]) + { + FreeMemory(ichatdata[i]->chat); + FreeMemory(ichatdata[i]); + ichatdata[i] = NULL; + } //end if + } //end for + if (consolemessageheap) FreeMemory(consolemessageheap); + consolemessageheap = NULL; + if (matchtemplates) BotFreeMatchTemplates(matchtemplates); + matchtemplates = NULL; + if (randomstrings) FreeMemory(randomstrings); + randomstrings = NULL; + if (synonyms) FreeMemory(synonyms); + synonyms = NULL; + if (replychats) BotFreeReplyChat(replychats); + replychats = NULL; +} //end of the function BotShutdownChatAI diff --git a/code/botlib/be_ai_gen.c b/code/botlib/be_ai_gen.c index 510d6b0..b897738 100755 --- a/code/botlib/be_ai_gen.c +++ b/code/botlib/be_ai_gen.c @@ -1,134 +1,134 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: be_ai_gen.c - * - * desc: genetic selection - * - * $Archive: /MissionPack/code/botlib/be_ai_gen.c $ - * - *****************************************************************************/ - -#include "../game/q_shared.h" -#include "l_memory.h" -#include "l_log.h" -#include "l_utils.h" -#include "l_script.h" -#include "l_precomp.h" -#include "l_struct.h" -#include "aasfile.h" -#include "../game/botlib.h" -#include "../game/be_aas.h" -#include "be_aas_funcs.h" -#include "be_interface.h" -#include "../game/be_ai_gen.h" - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int GeneticSelection(int numranks, float *rankings) -{ - float sum, select; - int i, index; - - sum = 0; - for (i = 0; i < numranks; i++) - { - if (rankings[i] < 0) continue; - sum += rankings[i]; - } //end for - if (sum > 0) - { - //select a bot where the ones with the higest rankings have - //the highest chance of being selected - select = random() * sum; - for (i = 0; i < numranks; i++) - { - if (rankings[i] < 0) continue; - sum -= rankings[i]; - if (sum <= 0) return i; - } //end for - } //end if - //select a bot randomly - index = random() * numranks; - for (i = 0; i < numranks; i++) - { - if (rankings[index] >= 0) return index; - index = (index + 1) % numranks; - } //end for - return 0; -} //end of the function GeneticSelection -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int GeneticParentsAndChildSelection(int numranks, float *ranks, int *parent1, int *parent2, int *child) -{ - float rankings[256], max; - int i; - - if (numranks > 256) - { - botimport.Print(PRT_WARNING, "GeneticParentsAndChildSelection: too many bots\n"); - *parent1 = *parent2 = *child = 0; - return qfalse; - } //end if - for (max = 0, i = 0; i < numranks; i++) - { - if (ranks[i] < 0) continue; - max++; - } //end for - if (max < 3) - { - botimport.Print(PRT_WARNING, "GeneticParentsAndChildSelection: too few valid bots\n"); - *parent1 = *parent2 = *child = 0; - return qfalse; - } //end if - Com_Memcpy(rankings, ranks, sizeof(float) * numranks); - //select first parent - *parent1 = GeneticSelection(numranks, rankings); - rankings[*parent1] = -1; - //select second parent - *parent2 = GeneticSelection(numranks, rankings); - rankings[*parent2] = -1; - //reverse the rankings - max = 0; - for (i = 0; i < numranks; i++) - { - if (rankings[i] < 0) continue; - if (rankings[i] > max) max = rankings[i]; - } //end for - for (i = 0; i < numranks; i++) - { - if (rankings[i] < 0) continue; - rankings[i] = max - rankings[i]; - } //end for - //select child - *child = GeneticSelection(numranks, rankings); - return qtrue; -} //end of the function GeneticParentsAndChildSelection +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_ai_gen.c + * + * desc: genetic selection + * + * $Archive: /MissionPack/code/botlib/be_ai_gen.c $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_log.h" +#include "l_utils.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "../game/be_ai_gen.h" + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int GeneticSelection(int numranks, float *rankings) +{ + float sum, select; + int i, index; + + sum = 0; + for (i = 0; i < numranks; i++) + { + if (rankings[i] < 0) continue; + sum += rankings[i]; + } //end for + if (sum > 0) + { + //select a bot where the ones with the higest rankings have + //the highest chance of being selected + select = random() * sum; + for (i = 0; i < numranks; i++) + { + if (rankings[i] < 0) continue; + sum -= rankings[i]; + if (sum <= 0) return i; + } //end for + } //end if + //select a bot randomly + index = random() * numranks; + for (i = 0; i < numranks; i++) + { + if (rankings[index] >= 0) return index; + index = (index + 1) % numranks; + } //end for + return 0; +} //end of the function GeneticSelection +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int GeneticParentsAndChildSelection(int numranks, float *ranks, int *parent1, int *parent2, int *child) +{ + float rankings[256], max; + int i; + + if (numranks > 256) + { + botimport.Print(PRT_WARNING, "GeneticParentsAndChildSelection: too many bots\n"); + *parent1 = *parent2 = *child = 0; + return qfalse; + } //end if + for (max = 0, i = 0; i < numranks; i++) + { + if (ranks[i] < 0) continue; + max++; + } //end for + if (max < 3) + { + botimport.Print(PRT_WARNING, "GeneticParentsAndChildSelection: too few valid bots\n"); + *parent1 = *parent2 = *child = 0; + return qfalse; + } //end if + Com_Memcpy(rankings, ranks, sizeof(float) * numranks); + //select first parent + *parent1 = GeneticSelection(numranks, rankings); + rankings[*parent1] = -1; + //select second parent + *parent2 = GeneticSelection(numranks, rankings); + rankings[*parent2] = -1; + //reverse the rankings + max = 0; + for (i = 0; i < numranks; i++) + { + if (rankings[i] < 0) continue; + if (rankings[i] > max) max = rankings[i]; + } //end for + for (i = 0; i < numranks; i++) + { + if (rankings[i] < 0) continue; + rankings[i] = max - rankings[i]; + } //end for + //select child + *child = GeneticSelection(numranks, rankings); + return qtrue; +} //end of the function GeneticParentsAndChildSelection diff --git a/code/botlib/be_ai_goal.c b/code/botlib/be_ai_goal.c index 2735c57..f71bc43 100755 --- a/code/botlib/be_ai_goal.c +++ b/code/botlib/be_ai_goal.c @@ -1,1821 +1,1821 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: be_ai_goal.c - * - * desc: goal AI - * - * $Archive: /MissionPack/code/botlib/be_ai_goal.c $ - * - *****************************************************************************/ - -#include "../game/q_shared.h" -#include "l_utils.h" -#include "l_libvar.h" -#include "l_memory.h" -#include "l_log.h" -#include "l_script.h" -#include "l_precomp.h" -#include "l_struct.h" -#include "aasfile.h" -#include "../game/botlib.h" -#include "../game/be_aas.h" -#include "be_aas_funcs.h" -#include "be_interface.h" -#include "be_ai_weight.h" -#include "../game/be_ai_goal.h" -#include "../game/be_ai_move.h" - -//#define DEBUG_AI_GOAL -#ifdef RANDOMIZE -#define UNDECIDEDFUZZY -#endif //RANDOMIZE -#define DROPPEDWEIGHT -//minimum avoid goal time -#define AVOID_MINIMUM_TIME 10 -//default avoid goal time -#define AVOID_DEFAULT_TIME 30 -//avoid dropped goal time -#define AVOID_DROPPED_TIME 10 -// -#define TRAVELTIME_SCALE 0.01 -//item flags -#define IFL_NOTFREE 1 //not in free for all -#define IFL_NOTTEAM 2 //not in team play -#define IFL_NOTSINGLE 4 //not in single player -#define IFL_NOTBOT 8 //bot should never go for this -#define IFL_ROAM 16 //bot roam goal - -//location in the map "target_location" -typedef struct maplocation_s -{ - vec3_t origin; - int areanum; - char name[MAX_EPAIRKEY]; - struct maplocation_s *next; -} maplocation_t; - -//camp spots "info_camp" -typedef struct campspot_s -{ - vec3_t origin; - int areanum; - char name[MAX_EPAIRKEY]; - float range; - float weight; - float wait; - float random; - struct campspot_s *next; -} campspot_t; - -//FIXME: these are game specific -typedef enum { - GT_FFA, // free for all - GT_TOURNAMENT, // one on one tournament - GT_SINGLE_PLAYER, // single player tournament - - //-- team games go after this -- - - GT_TEAM, // team deathmatch - GT_CTF, // capture the flag -#ifdef MISSIONPACK - GT_1FCTF, - GT_OBELISK, - GT_HARVESTER, -#endif - GT_MAX_GAME_TYPE -} gametype_t; - -typedef struct levelitem_s -{ - int number; //number of the level item - int iteminfo; //index into the item info - int flags; //item flags - float weight; //fixed roam weight - vec3_t origin; //origin of the item - int goalareanum; //area the item is in - vec3_t goalorigin; //goal origin within the area - int entitynum; //entity number - float timeout; //item is removed after this time - struct levelitem_s *prev, *next; -} levelitem_t; - -typedef struct iteminfo_s -{ - char classname[32]; //classname of the item - char name[MAX_STRINGFIELD]; //name of the item - char model[MAX_STRINGFIELD]; //model of the item - int modelindex; //model index - int type; //item type - int index; //index in the inventory - float respawntime; //respawn time - vec3_t mins; //mins of the item - vec3_t maxs; //maxs of the item - int number; //number of the item info -} iteminfo_t; - -#define ITEMINFO_OFS(x) (int)&(((iteminfo_t *)0)->x) - -fielddef_t iteminfo_fields[] = -{ -{"name", ITEMINFO_OFS(name), FT_STRING}, -{"model", ITEMINFO_OFS(model), FT_STRING}, -{"modelindex", ITEMINFO_OFS(modelindex), FT_INT}, -{"type", ITEMINFO_OFS(type), FT_INT}, -{"index", ITEMINFO_OFS(index), FT_INT}, -{"respawntime", ITEMINFO_OFS(respawntime), FT_FLOAT}, -{"mins", ITEMINFO_OFS(mins), FT_FLOAT|FT_ARRAY, 3}, -{"maxs", ITEMINFO_OFS(maxs), FT_FLOAT|FT_ARRAY, 3}, -{0, 0, 0} -}; - -structdef_t iteminfo_struct = -{ - sizeof(iteminfo_t), iteminfo_fields -}; - -typedef struct itemconfig_s -{ - int numiteminfo; - iteminfo_t *iteminfo; -} itemconfig_t; - -//goal state -typedef struct bot_goalstate_s -{ - struct weightconfig_s *itemweightconfig; //weight config - int *itemweightindex; //index from item to weight - // - int client; //client using this goal state - int lastreachabilityarea; //last area with reachabilities the bot was in - // - bot_goal_t goalstack[MAX_GOALSTACK]; //goal stack - int goalstacktop; //the top of the goal stack - // - int avoidgoals[MAX_AVOIDGOALS]; //goals to avoid - float avoidgoaltimes[MAX_AVOIDGOALS]; //times to avoid the goals -} bot_goalstate_t; - -bot_goalstate_t *botgoalstates[MAX_CLIENTS + 1]; // bk001206 - FIXME: init? -//item configuration -itemconfig_t *itemconfig = NULL; // bk001206 - init -//level items -levelitem_t *levelitemheap = NULL; // bk001206 - init -levelitem_t *freelevelitems = NULL; // bk001206 - init -levelitem_t *levelitems = NULL; // bk001206 - init -int numlevelitems = 0; -//map locations -maplocation_t *maplocations = NULL; // bk001206 - init -//camp spots -campspot_t *campspots = NULL; // bk001206 - init -//the game type -int g_gametype = 0; // bk001206 - init -//additional dropped item weight -libvar_t *droppedweight = NULL; // bk001206 - init - -//======================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//======================================================================== -bot_goalstate_t *BotGoalStateFromHandle(int handle) -{ - if (handle <= 0 || handle > MAX_CLIENTS) - { - botimport.Print(PRT_FATAL, "goal state handle %d out of range\n", handle); - return NULL; - } //end if - if (!botgoalstates[handle]) - { - botimport.Print(PRT_FATAL, "invalid goal state %d\n", handle); - return NULL; - } //end if - return botgoalstates[handle]; -} //end of the function BotGoalStateFromHandle -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotInterbreedGoalFuzzyLogic(int parent1, int parent2, int child) -{ - bot_goalstate_t *p1, *p2, *c; - - p1 = BotGoalStateFromHandle(parent1); - p2 = BotGoalStateFromHandle(parent2); - c = BotGoalStateFromHandle(child); - - InterbreedWeightConfigs(p1->itemweightconfig, p2->itemweightconfig, - c->itemweightconfig); -} //end of the function BotInterbreedingGoalFuzzyLogic -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotSaveGoalFuzzyLogic(int goalstate, char *filename) -{ - bot_goalstate_t *gs; - - gs = BotGoalStateFromHandle(goalstate); - - //WriteWeightConfig(filename, gs->itemweightconfig); -} //end of the function BotSaveGoalFuzzyLogic -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotMutateGoalFuzzyLogic(int goalstate, float range) -{ - bot_goalstate_t *gs; - - gs = BotGoalStateFromHandle(goalstate); - - EvolveWeightConfig(gs->itemweightconfig); -} //end of the function BotMutateGoalFuzzyLogic -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -itemconfig_t *LoadItemConfig(char *filename) -{ - int max_iteminfo; - token_t token; - char path[MAX_PATH]; - source_t *source; - itemconfig_t *ic; - iteminfo_t *ii; - - max_iteminfo = (int) LibVarValue("max_iteminfo", "256"); - if (max_iteminfo < 0) - { - botimport.Print(PRT_ERROR, "max_iteminfo = %d\n", max_iteminfo); - max_iteminfo = 256; - LibVarSet( "max_iteminfo", "256" ); - } - - strncpy( path, filename, MAX_PATH ); - PC_SetBaseFolder(BOTFILESBASEFOLDER); - source = LoadSourceFile( path ); - if( !source ) { - botimport.Print( PRT_ERROR, "counldn't load %s\n", path ); - return NULL; - } //end if - //initialize item config - ic = (itemconfig_t *) GetClearedHunkMemory(sizeof(itemconfig_t) + - max_iteminfo * sizeof(iteminfo_t)); - ic->iteminfo = (iteminfo_t *) ((char *) ic + sizeof(itemconfig_t)); - ic->numiteminfo = 0; - //parse the item config file - while(PC_ReadToken(source, &token)) - { - if (!strcmp(token.string, "iteminfo")) - { - if (ic->numiteminfo >= max_iteminfo) - { - SourceError(source, "more than %d item info defined\n", max_iteminfo); - FreeMemory(ic); - FreeSource(source); - return NULL; - } //end if - ii = &ic->iteminfo[ic->numiteminfo]; - Com_Memset(ii, 0, sizeof(iteminfo_t)); - if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) - { - FreeMemory(ic); - FreeMemory(source); - return NULL; - } //end if - StripDoubleQuotes(token.string); - strncpy(ii->classname, token.string, sizeof(ii->classname)-1); - if (!ReadStructure(source, &iteminfo_struct, (char *) ii)) - { - FreeMemory(ic); - FreeSource(source); - return NULL; - } //end if - ii->number = ic->numiteminfo; - ic->numiteminfo++; - } //end if - else - { - SourceError(source, "unknown definition %s\n", token.string); - FreeMemory(ic); - FreeSource(source); - return NULL; - } //end else - } //end while - FreeSource(source); - // - if (!ic->numiteminfo) botimport.Print(PRT_WARNING, "no item info loaded\n"); - botimport.Print(PRT_MESSAGE, "loaded %s\n", path); - return ic; -} //end of the function LoadItemConfig -//=========================================================================== -// index to find the weight function of an iteminfo -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int *ItemWeightIndex(weightconfig_t *iwc, itemconfig_t *ic) -{ - int *index, i; - - //initialize item weight index - index = (int *) GetClearedMemory(sizeof(int) * ic->numiteminfo); - - for (i = 0; i < ic->numiteminfo; i++) - { - index[i] = FindFuzzyWeight(iwc, ic->iteminfo[i].classname); - if (index[i] < 0) - { - Log_Write("item info %d \"%s\" has no fuzzy weight\r\n", i, ic->iteminfo[i].classname); - } //end if - } //end for - return index; -} //end of the function ItemWeightIndex -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void InitLevelItemHeap(void) -{ - int i, max_levelitems; - - if (levelitemheap) FreeMemory(levelitemheap); - - max_levelitems = (int) LibVarValue("max_levelitems", "256"); - levelitemheap = (levelitem_t *) GetClearedMemory(max_levelitems * sizeof(levelitem_t)); - - for (i = 0; i < max_levelitems-1; i++) - { - levelitemheap[i].next = &levelitemheap[i + 1]; - } //end for - levelitemheap[max_levelitems-1].next = NULL; - // - freelevelitems = levelitemheap; -} //end of the function InitLevelItemHeap -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -levelitem_t *AllocLevelItem(void) -{ - levelitem_t *li; - - li = freelevelitems; - if (!li) - { - botimport.Print(PRT_FATAL, "out of level items\n"); - return NULL; - } //end if - // - freelevelitems = freelevelitems->next; - Com_Memset(li, 0, sizeof(levelitem_t)); - return li; -} //end of the function AllocLevelItem -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void FreeLevelItem(levelitem_t *li) -{ - li->next = freelevelitems; - freelevelitems = li; -} //end of the function FreeLevelItem -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AddLevelItemToList(levelitem_t *li) -{ - if (levelitems) levelitems->prev = li; - li->prev = NULL; - li->next = levelitems; - levelitems = li; -} //end of the function AddLevelItemToList -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void RemoveLevelItemFromList(levelitem_t *li) -{ - if (li->prev) li->prev->next = li->next; - else levelitems = li->next; - if (li->next) li->next->prev = li->prev; -} //end of the function RemoveLevelItemFromList -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotFreeInfoEntities(void) -{ - maplocation_t *ml, *nextml; - campspot_t *cs, *nextcs; - - for (ml = maplocations; ml; ml = nextml) - { - nextml = ml->next; - FreeMemory(ml); - } //end for - maplocations = NULL; - for (cs = campspots; cs; cs = nextcs) - { - nextcs = cs->next; - FreeMemory(cs); - } //end for - campspots = NULL; -} //end of the function BotFreeInfoEntities -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotInitInfoEntities(void) -{ - char classname[MAX_EPAIRKEY]; - maplocation_t *ml; - campspot_t *cs; - int ent, numlocations, numcampspots; - - BotFreeInfoEntities(); - // - numlocations = 0; - numcampspots = 0; - for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) - { - if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; - - //map locations - if (!strcmp(classname, "target_location")) - { - ml = (maplocation_t *) GetClearedMemory(sizeof(maplocation_t)); - AAS_VectorForBSPEpairKey(ent, "origin", ml->origin); - AAS_ValueForBSPEpairKey(ent, "message", ml->name, sizeof(ml->name)); - ml->areanum = AAS_PointAreaNum(ml->origin); - ml->next = maplocations; - maplocations = ml; - numlocations++; - } //end if - //camp spots - else if (!strcmp(classname, "info_camp")) - { - cs = (campspot_t *) GetClearedMemory(sizeof(campspot_t)); - AAS_VectorForBSPEpairKey(ent, "origin", cs->origin); - //cs->origin[2] += 16; - AAS_ValueForBSPEpairKey(ent, "message", cs->name, sizeof(cs->name)); - AAS_FloatForBSPEpairKey(ent, "range", &cs->range); - AAS_FloatForBSPEpairKey(ent, "weight", &cs->weight); - AAS_FloatForBSPEpairKey(ent, "wait", &cs->wait); - AAS_FloatForBSPEpairKey(ent, "random", &cs->random); - cs->areanum = AAS_PointAreaNum(cs->origin); - if (!cs->areanum) - { - botimport.Print(PRT_MESSAGE, "camp spot at %1.1f %1.1f %1.1f in solid\n", cs->origin[0], cs->origin[1], cs->origin[2]); - FreeMemory(cs); - continue; - } //end if - cs->next = campspots; - campspots = cs; - //AAS_DrawPermanentCross(cs->origin, 4, LINECOLOR_YELLOW); - numcampspots++; - } //end else if - } //end for - if (bot_developer) - { - botimport.Print(PRT_MESSAGE, "%d map locations\n", numlocations); - botimport.Print(PRT_MESSAGE, "%d camp spots\n", numcampspots); - } //end if -} //end of the function BotInitInfoEntities -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotInitLevelItems(void) -{ - int i, spawnflags, value; - char classname[MAX_EPAIRKEY]; - vec3_t origin, end; - int ent, goalareanum; - itemconfig_t *ic; - levelitem_t *li; - bsp_trace_t trace; - - //initialize the map locations and camp spots - BotInitInfoEntities(); - - //initialize the level item heap - InitLevelItemHeap(); - levelitems = NULL; - numlevelitems = 0; - // - ic = itemconfig; - if (!ic) return; - - //if there's no AAS file loaded - if (!AAS_Loaded()) return; - - //update the modelindexes of the item info - for (i = 0; i < ic->numiteminfo; i++) - { - //ic->iteminfo[i].modelindex = AAS_IndexFromModel(ic->iteminfo[i].model); - if (!ic->iteminfo[i].modelindex) - { - Log_Write("item %s has modelindex 0", ic->iteminfo[i].classname); - } //end if - } //end for - - for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) - { - if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; - // - spawnflags = 0; - AAS_IntForBSPEpairKey(ent, "spawnflags", &spawnflags); - // - for (i = 0; i < ic->numiteminfo; i++) - { - if (!strcmp(classname, ic->iteminfo[i].classname)) break; - } //end for - if (i >= ic->numiteminfo) - { - Log_Write("entity %s unknown item\r\n", classname); - continue; - } //end if - //get the origin of the item - if (!AAS_VectorForBSPEpairKey(ent, "origin", origin)) - { - botimport.Print(PRT_ERROR, "item %s without origin\n", classname); - continue; - } //end else - // - goalareanum = 0; - //if it is a floating item - if (spawnflags & 1) - { - //if the item is not floating in water - if (!(AAS_PointContents(origin) & CONTENTS_WATER)) - { - VectorCopy(origin, end); - end[2] -= 32; - trace = AAS_Trace(origin, ic->iteminfo[i].mins, ic->iteminfo[i].maxs, end, -1, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); - //if the item not near the ground - if (trace.fraction >= 1) - { - //if the item is not reachable from a jumppad - goalareanum = AAS_BestReachableFromJumpPadArea(origin, ic->iteminfo[i].mins, ic->iteminfo[i].maxs); - Log_Write("item %s reachable from jumppad area %d\r\n", ic->iteminfo[i].classname, goalareanum); - //botimport.Print(PRT_MESSAGE, "item %s reachable from jumppad area %d\r\n", ic->iteminfo[i].classname, goalareanum); - if (!goalareanum) continue; - } //end if - } //end if - } //end if - - li = AllocLevelItem(); - if (!li) return; - // - li->number = ++numlevelitems; - li->timeout = 0; - li->entitynum = 0; - // - li->flags = 0; - AAS_IntForBSPEpairKey(ent, "notfree", &value); - if (value) li->flags |= IFL_NOTFREE; - AAS_IntForBSPEpairKey(ent, "notteam", &value); - if (value) li->flags |= IFL_NOTTEAM; - AAS_IntForBSPEpairKey(ent, "notsingle", &value); - if (value) li->flags |= IFL_NOTSINGLE; - AAS_IntForBSPEpairKey(ent, "notbot", &value); - if (value) li->flags |= IFL_NOTBOT; - if (!strcmp(classname, "item_botroam")) - { - li->flags |= IFL_ROAM; - AAS_FloatForBSPEpairKey(ent, "weight", &li->weight); - } //end if - //if not a stationary item - if (!(spawnflags & 1)) - { - if (!AAS_DropToFloor(origin, ic->iteminfo[i].mins, ic->iteminfo[i].maxs)) - { - botimport.Print(PRT_MESSAGE, "%s in solid at (%1.1f %1.1f %1.1f)\n", - classname, origin[0], origin[1], origin[2]); - } //end if - } //end if - //item info of the level item - li->iteminfo = i; - //origin of the item - VectorCopy(origin, li->origin); - // - if (goalareanum) - { - li->goalareanum = goalareanum; - VectorCopy(origin, li->goalorigin); - } //end if - else - { - //get the item goal area and goal origin - li->goalareanum = AAS_BestReachableArea(origin, - ic->iteminfo[i].mins, ic->iteminfo[i].maxs, - li->goalorigin); - if (!li->goalareanum) - { - botimport.Print(PRT_MESSAGE, "%s not reachable for bots at (%1.1f %1.1f %1.1f)\n", - classname, origin[0], origin[1], origin[2]); - } //end if - } //end else - // - AddLevelItemToList(li); - } //end for - botimport.Print(PRT_MESSAGE, "found %d level items\n", numlevelitems); -} //end of the function BotInitLevelItems -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotGoalName(int number, char *name, int size) -{ - levelitem_t *li; - - if (!itemconfig) return; - // - for (li = levelitems; li; li = li->next) - { - if (li->number == number) - { - strncpy(name, itemconfig->iteminfo[li->iteminfo].name, size-1); - name[size-1] = '\0'; - return; - } //end for - } //end for - strcpy(name, ""); - return; -} //end of the function BotGoalName -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotResetAvoidGoals(int goalstate) -{ - bot_goalstate_t *gs; - - gs = BotGoalStateFromHandle(goalstate); - if (!gs) return; - Com_Memset(gs->avoidgoals, 0, MAX_AVOIDGOALS * sizeof(int)); - Com_Memset(gs->avoidgoaltimes, 0, MAX_AVOIDGOALS * sizeof(float)); -} //end of the function BotResetAvoidGoals -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotDumpAvoidGoals(int goalstate) -{ - int i; - bot_goalstate_t *gs; - char name[32]; - - gs = BotGoalStateFromHandle(goalstate); - if (!gs) return; - for (i = 0; i < MAX_AVOIDGOALS; i++) - { - if (gs->avoidgoaltimes[i] >= AAS_Time()) - { - BotGoalName(gs->avoidgoals[i], name, 32); - Log_Write("avoid goal %s, number %d for %f seconds", name, - gs->avoidgoals[i], gs->avoidgoaltimes[i] - AAS_Time()); - } //end if - } //end for -} //end of the function BotDumpAvoidGoals -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotAddToAvoidGoals(bot_goalstate_t *gs, int number, float avoidtime) -{ - int i; - - for (i = 0; i < MAX_AVOIDGOALS; i++) - { - //if the avoid goal is already stored - if (gs->avoidgoals[i] == number) - { - gs->avoidgoals[i] = number; - gs->avoidgoaltimes[i] = AAS_Time() + avoidtime; - return; - } //end if - } //end for - - for (i = 0; i < MAX_AVOIDGOALS; i++) - { - //if this avoid goal has expired - if (gs->avoidgoaltimes[i] < AAS_Time()) - { - gs->avoidgoals[i] = number; - gs->avoidgoaltimes[i] = AAS_Time() + avoidtime; - return; - } //end if - } //end for -} //end of the function BotAddToAvoidGoals -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotRemoveFromAvoidGoals(int goalstate, int number) -{ - int i; - bot_goalstate_t *gs; - - gs = BotGoalStateFromHandle(goalstate); - if (!gs) return; - //don't use the goals the bot wants to avoid - for (i = 0; i < MAX_AVOIDGOALS; i++) - { - if (gs->avoidgoals[i] == number && gs->avoidgoaltimes[i] >= AAS_Time()) - { - gs->avoidgoaltimes[i] = 0; - return; - } //end if - } //end for -} //end of the function BotRemoveFromAvoidGoals -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -float BotAvoidGoalTime(int goalstate, int number) -{ - int i; - bot_goalstate_t *gs; - - gs = BotGoalStateFromHandle(goalstate); - if (!gs) return 0; - //don't use the goals the bot wants to avoid - for (i = 0; i < MAX_AVOIDGOALS; i++) - { - if (gs->avoidgoals[i] == number && gs->avoidgoaltimes[i] >= AAS_Time()) - { - return gs->avoidgoaltimes[i] - AAS_Time(); - } //end if - } //end for - return 0; -} //end of the function BotAvoidGoalTime -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotSetAvoidGoalTime(int goalstate, int number, float avoidtime) -{ - bot_goalstate_t *gs; - levelitem_t *li; - - gs = BotGoalStateFromHandle(goalstate); - if (!gs) - return; - if (avoidtime < 0) - { - if (!itemconfig) - return; - // - for (li = levelitems; li; li = li->next) - { - if (li->number == number) - { - avoidtime = itemconfig->iteminfo[li->iteminfo].respawntime; - if (!avoidtime) - avoidtime = AVOID_DEFAULT_TIME; - if (avoidtime < AVOID_MINIMUM_TIME) - avoidtime = AVOID_MINIMUM_TIME; - BotAddToAvoidGoals(gs, number, avoidtime); - return; - } //end for - } //end for - return; - } //end if - else - { - BotAddToAvoidGoals(gs, number, avoidtime); - } //end else -} //end of the function BotSetAvoidGoalTime -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotGetLevelItemGoal(int index, char *name, bot_goal_t *goal) -{ - levelitem_t *li; - - if (!itemconfig) return -1; - li = levelitems; - if (index >= 0) - { - for (; li; li = li->next) - { - if (li->number == index) - { - li = li->next; - break; - } //end if - } //end for - } //end for - for (; li; li = li->next) - { - // - if (g_gametype == GT_SINGLE_PLAYER) { - if (li->flags & IFL_NOTSINGLE) continue; - } - else if (g_gametype >= GT_TEAM) { - if (li->flags & IFL_NOTTEAM) continue; - } - else { - if (li->flags & IFL_NOTFREE) continue; - } - if (li->flags & IFL_NOTBOT) continue; - // - if (!Q_stricmp(name, itemconfig->iteminfo[li->iteminfo].name)) - { - goal->areanum = li->goalareanum; - VectorCopy(li->goalorigin, goal->origin); - goal->entitynum = li->entitynum; - VectorCopy(itemconfig->iteminfo[li->iteminfo].mins, goal->mins); - VectorCopy(itemconfig->iteminfo[li->iteminfo].maxs, goal->maxs); - goal->number = li->number; - goal->flags = GFL_ITEM; - if (li->timeout) goal->flags |= GFL_DROPPED; - //botimport.Print(PRT_MESSAGE, "found li %s\n", itemconfig->iteminfo[li->iteminfo].name); - return li->number; - } //end if - } //end for - return -1; -} //end of the function BotGetLevelItemGoal -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotGetMapLocationGoal(char *name, bot_goal_t *goal) -{ - maplocation_t *ml; - vec3_t mins = {-8, -8, -8}, maxs = {8, 8, 8}; - - for (ml = maplocations; ml; ml = ml->next) - { - if (!Q_stricmp(ml->name, name)) - { - goal->areanum = ml->areanum; - VectorCopy(ml->origin, goal->origin); - goal->entitynum = 0; - VectorCopy(mins, goal->mins); - VectorCopy(maxs, goal->maxs); - return qtrue; - } //end if - } //end for - return qfalse; -} //end of the function BotGetMapLocationGoal -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotGetNextCampSpotGoal(int num, bot_goal_t *goal) -{ - int i; - campspot_t *cs; - vec3_t mins = {-8, -8, -8}, maxs = {8, 8, 8}; - - if (num < 0) num = 0; - i = num; - for (cs = campspots; cs; cs = cs->next) - { - if (--i < 0) - { - goal->areanum = cs->areanum; - VectorCopy(cs->origin, goal->origin); - goal->entitynum = 0; - VectorCopy(mins, goal->mins); - VectorCopy(maxs, goal->maxs); - return num+1; - } //end if - } //end for - return 0; -} //end of the function BotGetNextCampSpotGoal -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotFindEntityForLevelItem(levelitem_t *li) -{ - int ent, modelindex; - itemconfig_t *ic; - aas_entityinfo_t entinfo; - vec3_t dir; - - ic = itemconfig; - if (!itemconfig) return; - for (ent = AAS_NextEntity(0); ent; ent = AAS_NextEntity(ent)) - { - //get the model index of the entity - modelindex = AAS_EntityModelindex(ent); - // - if (!modelindex) continue; - //get info about the entity - AAS_EntityInfo(ent, &entinfo); - //if the entity is still moving - if (entinfo.origin[0] != entinfo.lastvisorigin[0] || - entinfo.origin[1] != entinfo.lastvisorigin[1] || - entinfo.origin[2] != entinfo.lastvisorigin[2]) continue; - // - if (ic->iteminfo[li->iteminfo].modelindex == modelindex) - { - //check if the entity is very close - VectorSubtract(li->origin, entinfo.origin, dir); - if (VectorLength(dir) < 30) - { - //found an entity for this level item - li->entitynum = ent; - } //end if - } //end if - } //end for -} //end of the function BotFindEntityForLevelItem -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== - -//NOTE: enum entityType_t in bg_public.h -#define ET_ITEM 2 - -void BotUpdateEntityItems(void) -{ - int ent, i, modelindex; - vec3_t dir; - levelitem_t *li, *nextli; - aas_entityinfo_t entinfo; - itemconfig_t *ic; - - //timeout current entity items if necessary - for (li = levelitems; li; li = nextli) - { - nextli = li->next; - //if it is a item that will time out - if (li->timeout) - { - //timeout the item - if (li->timeout < AAS_Time()) - { - RemoveLevelItemFromList(li); - FreeLevelItem(li); - } //end if - } //end if - } //end for - //find new entity items - ic = itemconfig; - if (!itemconfig) return; - // - for (ent = AAS_NextEntity(0); ent; ent = AAS_NextEntity(ent)) - { - if (AAS_EntityType(ent) != ET_ITEM) continue; - //get the model index of the entity - modelindex = AAS_EntityModelindex(ent); - // - if (!modelindex) continue; - //get info about the entity - AAS_EntityInfo(ent, &entinfo); - //FIXME: don't do this - //skip all floating items for now - //if (entinfo.groundent != ENTITYNUM_WORLD) continue; - //if the entity is still moving - if (entinfo.origin[0] != entinfo.lastvisorigin[0] || - entinfo.origin[1] != entinfo.lastvisorigin[1] || - entinfo.origin[2] != entinfo.lastvisorigin[2]) continue; - //check if the entity is already stored as a level item - for (li = levelitems; li; li = li->next) - { - //if the level item is linked to an entity - if (li->entitynum && li->entitynum == ent) - { - //the entity is re-used if the models are different - if (ic->iteminfo[li->iteminfo].modelindex != modelindex) - { - //remove this level item - RemoveLevelItemFromList(li); - FreeLevelItem(li); - li = NULL; - break; - } //end if - else - { - if (entinfo.origin[0] != li->origin[0] || - entinfo.origin[1] != li->origin[1] || - entinfo.origin[2] != li->origin[2]) - { - VectorCopy(entinfo.origin, li->origin); - //also update the goal area number - li->goalareanum = AAS_BestReachableArea(li->origin, - ic->iteminfo[li->iteminfo].mins, ic->iteminfo[li->iteminfo].maxs, - li->goalorigin); - } //end if - break; - } //end else - } //end if - } //end for - if (li) continue; - //try to link the entity to a level item - for (li = levelitems; li; li = li->next) - { - //if this level item is already linked - if (li->entitynum) continue; - // - if (g_gametype == GT_SINGLE_PLAYER) { - if (li->flags & IFL_NOTSINGLE) continue; - } - else if (g_gametype >= GT_TEAM) { - if (li->flags & IFL_NOTTEAM) continue; - } - else { - if (li->flags & IFL_NOTFREE) continue; - } - //if the model of the level item and the entity are the same - if (ic->iteminfo[li->iteminfo].modelindex == modelindex) - { - //check if the entity is very close - VectorSubtract(li->origin, entinfo.origin, dir); - if (VectorLength(dir) < 30) - { - //found an entity for this level item - li->entitynum = ent; - //if the origin is different - if (entinfo.origin[0] != li->origin[0] || - entinfo.origin[1] != li->origin[1] || - entinfo.origin[2] != li->origin[2]) - { - //update the level item origin - VectorCopy(entinfo.origin, li->origin); - //also update the goal area number - li->goalareanum = AAS_BestReachableArea(li->origin, - ic->iteminfo[li->iteminfo].mins, ic->iteminfo[li->iteminfo].maxs, - li->goalorigin); - } //end if -#ifdef DEBUG - Log_Write("linked item %s to an entity", ic->iteminfo[li->iteminfo].classname); -#endif //DEBUG - break; - } //end if - } //end else - } //end for - if (li) continue; - //check if the model is from a known item - for (i = 0; i < ic->numiteminfo; i++) - { - if (ic->iteminfo[i].modelindex == modelindex) - { - break; - } //end if - } //end for - //if the model is not from a known item - if (i >= ic->numiteminfo) continue; - //allocate a new level item - li = AllocLevelItem(); - // - if (!li) continue; - //entity number of the level item - li->entitynum = ent; - //number for the level item - li->number = numlevelitems + ent; - //set the item info index for the level item - li->iteminfo = i; - //origin of the item - VectorCopy(entinfo.origin, li->origin); - //get the item goal area and goal origin - li->goalareanum = AAS_BestReachableArea(li->origin, - ic->iteminfo[i].mins, ic->iteminfo[i].maxs, - li->goalorigin); - //never go for items dropped into jumppads - if (AAS_AreaJumpPad(li->goalareanum)) - { - FreeLevelItem(li); - continue; - } //end if - //time this item out after 30 seconds - //dropped items disappear after 30 seconds - li->timeout = AAS_Time() + 30; - //add the level item to the list - AddLevelItemToList(li); - //botimport.Print(PRT_MESSAGE, "found new level item %s\n", ic->iteminfo[i].classname); - } //end for - /* - for (li = levelitems; li; li = li->next) - { - if (!li->entitynum) - { - BotFindEntityForLevelItem(li); - } //end if - } //end for*/ -} //end of the function BotUpdateEntityItems -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotDumpGoalStack(int goalstate) -{ - int i; - bot_goalstate_t *gs; - char name[32]; - - gs = BotGoalStateFromHandle(goalstate); - if (!gs) return; - for (i = 1; i <= gs->goalstacktop; i++) - { - BotGoalName(gs->goalstack[i].number, name, 32); - Log_Write("%d: %s", i, name); - } //end for -} //end of the function BotDumpGoalStack -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotPushGoal(int goalstate, bot_goal_t *goal) -{ - bot_goalstate_t *gs; - - gs = BotGoalStateFromHandle(goalstate); - if (!gs) return; - if (gs->goalstacktop >= MAX_GOALSTACK-1) - { - botimport.Print(PRT_ERROR, "goal heap overflow\n"); - BotDumpGoalStack(goalstate); - return; - } //end if - gs->goalstacktop++; - Com_Memcpy(&gs->goalstack[gs->goalstacktop], goal, sizeof(bot_goal_t)); -} //end of the function BotPushGoal -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotPopGoal(int goalstate) -{ - bot_goalstate_t *gs; - - gs = BotGoalStateFromHandle(goalstate); - if (!gs) return; - if (gs->goalstacktop > 0) gs->goalstacktop--; -} //end of the function BotPopGoal -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotEmptyGoalStack(int goalstate) -{ - bot_goalstate_t *gs; - - gs = BotGoalStateFromHandle(goalstate); - if (!gs) return; - gs->goalstacktop = 0; -} //end of the function BotEmptyGoalStack -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotGetTopGoal(int goalstate, bot_goal_t *goal) -{ - bot_goalstate_t *gs; - - gs = BotGoalStateFromHandle(goalstate); - if (!gs) return qfalse; - if (!gs->goalstacktop) return qfalse; - Com_Memcpy(goal, &gs->goalstack[gs->goalstacktop], sizeof(bot_goal_t)); - return qtrue; -} //end of the function BotGetTopGoal -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotGetSecondGoal(int goalstate, bot_goal_t *goal) -{ - bot_goalstate_t *gs; - - gs = BotGoalStateFromHandle(goalstate); - if (!gs) return qfalse; - if (gs->goalstacktop <= 1) return qfalse; - Com_Memcpy(goal, &gs->goalstack[gs->goalstacktop-1], sizeof(bot_goal_t)); - return qtrue; -} //end of the function BotGetSecondGoal -//=========================================================================== -// pops a new long term goal on the goal stack in the goalstate -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotChooseLTGItem(int goalstate, vec3_t origin, int *inventory, int travelflags) -{ - int areanum, t, weightnum; - float weight, bestweight, avoidtime; - iteminfo_t *iteminfo; - itemconfig_t *ic; - levelitem_t *li, *bestitem; - bot_goal_t goal; - bot_goalstate_t *gs; - - gs = BotGoalStateFromHandle(goalstate); - if (!gs) - return qfalse; - if (!gs->itemweightconfig) - return qfalse; - //get the area the bot is in - areanum = BotReachabilityArea(origin, gs->client); - //if the bot is in solid or if the area the bot is in has no reachability links - if (!areanum || !AAS_AreaReachability(areanum)) - { - //use the last valid area the bot was in - areanum = gs->lastreachabilityarea; - } //end if - //remember the last area with reachabilities the bot was in - gs->lastreachabilityarea = areanum; - //if still in solid - if (!areanum) - return qfalse; - //the item configuration - ic = itemconfig; - if (!itemconfig) - return qfalse; - //best weight and item so far - bestweight = 0; - bestitem = NULL; - Com_Memset(&goal, 0, sizeof(bot_goal_t)); - //go through the items in the level - for (li = levelitems; li; li = li->next) - { - if (g_gametype == GT_SINGLE_PLAYER) { - if (li->flags & IFL_NOTSINGLE) - continue; - } - else if (g_gametype >= GT_TEAM) { - if (li->flags & IFL_NOTTEAM) - continue; - } - else { - if (li->flags & IFL_NOTFREE) - continue; - } - if (li->flags & IFL_NOTBOT) - continue; - //if the item is not in a possible goal area - if (!li->goalareanum) - continue; - //FIXME: is this a good thing? added this for items that never spawned into the game (f.i. CTF flags in obelisk) - if (!li->entitynum && !(li->flags & IFL_ROAM)) - continue; - //get the fuzzy weight function for this item - iteminfo = &ic->iteminfo[li->iteminfo]; - weightnum = gs->itemweightindex[iteminfo->number]; - if (weightnum < 0) - continue; - -#ifdef UNDECIDEDFUZZY - weight = FuzzyWeightUndecided(inventory, gs->itemweightconfig, weightnum); -#else - weight = FuzzyWeight(inventory, gs->itemweightconfig, weightnum); -#endif //UNDECIDEDFUZZY -#ifdef DROPPEDWEIGHT - //HACK: to make dropped items more attractive - if (li->timeout) - weight += droppedweight->value; -#endif //DROPPEDWEIGHT - //use weight scale for item_botroam - if (li->flags & IFL_ROAM) weight *= li->weight; - // - if (weight > 0) - { - //get the travel time towards the goal area - t = AAS_AreaTravelTimeToGoalArea(areanum, origin, li->goalareanum, travelflags); - //if the goal is reachable - if (t > 0) - { - //if this item won't respawn before we get there - avoidtime = BotAvoidGoalTime(goalstate, li->number); - if (avoidtime - t * 0.009 > 0) - continue; - // - weight /= (float) t * TRAVELTIME_SCALE; - // - if (weight > bestweight) - { - bestweight = weight; - bestitem = li; - } //end if - } //end if - } //end if - } //end for - //if no goal item found - if (!bestitem) - { - /* - //if not in lava or slime - if (!AAS_AreaLava(areanum) && !AAS_AreaSlime(areanum)) - { - if (AAS_RandomGoalArea(areanum, travelflags, &goal.areanum, goal.origin)) - { - VectorSet(goal.mins, -15, -15, -15); - VectorSet(goal.maxs, 15, 15, 15); - goal.entitynum = 0; - goal.number = 0; - goal.flags = GFL_ROAM; - goal.iteminfo = 0; - //push the goal on the stack - BotPushGoal(goalstate, &goal); - // -#ifdef DEBUG - botimport.Print(PRT_MESSAGE, "chosen roam goal area %d\n", goal.areanum); -#endif //DEBUG - return qtrue; - } //end if - } //end if - */ - return qfalse; - } //end if - //create a bot goal for this item - iteminfo = &ic->iteminfo[bestitem->iteminfo]; - VectorCopy(bestitem->goalorigin, goal.origin); - VectorCopy(iteminfo->mins, goal.mins); - VectorCopy(iteminfo->maxs, goal.maxs); - goal.areanum = bestitem->goalareanum; - goal.entitynum = bestitem->entitynum; - goal.number = bestitem->number; - goal.flags = GFL_ITEM; - if (bestitem->timeout) - goal.flags |= GFL_DROPPED; - if (bestitem->flags & IFL_ROAM) - goal.flags |= GFL_ROAM; - goal.iteminfo = bestitem->iteminfo; - //if it's a dropped item - if (bestitem->timeout) - { - avoidtime = AVOID_DROPPED_TIME; - } //end if - else - { - avoidtime = iteminfo->respawntime; - if (!avoidtime) - avoidtime = AVOID_DEFAULT_TIME; - if (avoidtime < AVOID_MINIMUM_TIME) - avoidtime = AVOID_MINIMUM_TIME; - } //end else - //add the chosen goal to the goals to avoid for a while - BotAddToAvoidGoals(gs, bestitem->number, avoidtime); - //push the goal on the stack - BotPushGoal(goalstate, &goal); - // - return qtrue; -} //end of the function BotChooseLTGItem -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotChooseNBGItem(int goalstate, vec3_t origin, int *inventory, int travelflags, - bot_goal_t *ltg, float maxtime) -{ - int areanum, t, weightnum, ltg_time; - float weight, bestweight, avoidtime; - iteminfo_t *iteminfo; - itemconfig_t *ic; - levelitem_t *li, *bestitem; - bot_goal_t goal; - bot_goalstate_t *gs; - - gs = BotGoalStateFromHandle(goalstate); - if (!gs) - return qfalse; - if (!gs->itemweightconfig) - return qfalse; - //get the area the bot is in - areanum = BotReachabilityArea(origin, gs->client); - //if the bot is in solid or if the area the bot is in has no reachability links - if (!areanum || !AAS_AreaReachability(areanum)) - { - //use the last valid area the bot was in - areanum = gs->lastreachabilityarea; - } //end if - //remember the last area with reachabilities the bot was in - gs->lastreachabilityarea = areanum; - //if still in solid - if (!areanum) - return qfalse; - // - if (ltg) ltg_time = AAS_AreaTravelTimeToGoalArea(areanum, origin, ltg->areanum, travelflags); - else ltg_time = 99999; - //the item configuration - ic = itemconfig; - if (!itemconfig) - return qfalse; - //best weight and item so far - bestweight = 0; - bestitem = NULL; - Com_Memset(&goal, 0, sizeof(bot_goal_t)); - //go through the items in the level - for (li = levelitems; li; li = li->next) - { - if (g_gametype == GT_SINGLE_PLAYER) { - if (li->flags & IFL_NOTSINGLE) - continue; - } - else if (g_gametype >= GT_TEAM) { - if (li->flags & IFL_NOTTEAM) - continue; - } - else { - if (li->flags & IFL_NOTFREE) - continue; - } - if (li->flags & IFL_NOTBOT) - continue; - //if the item is in a possible goal area - if (!li->goalareanum) - continue; - //FIXME: is this a good thing? added this for items that never spawned into the game (f.i. CTF flags in obelisk) - if (!li->entitynum && !(li->flags & IFL_ROAM)) - continue; - //get the fuzzy weight function for this item - iteminfo = &ic->iteminfo[li->iteminfo]; - weightnum = gs->itemweightindex[iteminfo->number]; - if (weightnum < 0) - continue; - // -#ifdef UNDECIDEDFUZZY - weight = FuzzyWeightUndecided(inventory, gs->itemweightconfig, weightnum); -#else - weight = FuzzyWeight(inventory, gs->itemweightconfig, weightnum); -#endif //UNDECIDEDFUZZY -#ifdef DROPPEDWEIGHT - //HACK: to make dropped items more attractive - if (li->timeout) - weight += droppedweight->value; -#endif //DROPPEDWEIGHT - //use weight scale for item_botroam - if (li->flags & IFL_ROAM) weight *= li->weight; - // - if (weight > 0) - { - //get the travel time towards the goal area - t = AAS_AreaTravelTimeToGoalArea(areanum, origin, li->goalareanum, travelflags); - //if the goal is reachable - if (t > 0 && t < maxtime) - { - //if this item won't respawn before we get there - avoidtime = BotAvoidGoalTime(goalstate, li->number); - if (avoidtime - t * 0.009 > 0) - continue; - // - weight /= (float) t * TRAVELTIME_SCALE; - // - if (weight > bestweight) - { - t = 0; - if (ltg && !li->timeout) - { - //get the travel time from the goal to the long term goal - t = AAS_AreaTravelTimeToGoalArea(li->goalareanum, li->goalorigin, ltg->areanum, travelflags); - } //end if - //if the travel back is possible and doesn't take too long - if (t <= ltg_time) - { - bestweight = weight; - bestitem = li; - } //end if - } //end if - } //end if - } //end if - } //end for - //if no goal item found - if (!bestitem) - return qfalse; - //create a bot goal for this item - iteminfo = &ic->iteminfo[bestitem->iteminfo]; - VectorCopy(bestitem->goalorigin, goal.origin); - VectorCopy(iteminfo->mins, goal.mins); - VectorCopy(iteminfo->maxs, goal.maxs); - goal.areanum = bestitem->goalareanum; - goal.entitynum = bestitem->entitynum; - goal.number = bestitem->number; - goal.flags = GFL_ITEM; - if (bestitem->timeout) - goal.flags |= GFL_DROPPED; - if (bestitem->flags & IFL_ROAM) - goal.flags |= GFL_ROAM; - goal.iteminfo = bestitem->iteminfo; - //if it's a dropped item - if (bestitem->timeout) - { - avoidtime = AVOID_DROPPED_TIME; - } //end if - else - { - avoidtime = iteminfo->respawntime; - if (!avoidtime) - avoidtime = AVOID_DEFAULT_TIME; - if (avoidtime < AVOID_MINIMUM_TIME) - avoidtime = AVOID_MINIMUM_TIME; - } //end else - //add the chosen goal to the goals to avoid for a while - BotAddToAvoidGoals(gs, bestitem->number, avoidtime); - //push the goal on the stack - BotPushGoal(goalstate, &goal); - // - return qtrue; -} //end of the function BotChooseNBGItem -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotTouchingGoal(vec3_t origin, bot_goal_t *goal) -{ - int i; - vec3_t boxmins, boxmaxs; - vec3_t absmins, absmaxs; - vec3_t safety_maxs = {0, 0, 0}; //{4, 4, 10}; - vec3_t safety_mins = {0, 0, 0}; //{-4, -4, 0}; - - AAS_PresenceTypeBoundingBox(PRESENCE_NORMAL, boxmins, boxmaxs); - VectorSubtract(goal->mins, boxmaxs, absmins); - VectorSubtract(goal->maxs, boxmins, absmaxs); - VectorAdd(absmins, goal->origin, absmins); - VectorAdd(absmaxs, goal->origin, absmaxs); - //make the box a little smaller for safety - VectorSubtract(absmaxs, safety_maxs, absmaxs); - VectorSubtract(absmins, safety_mins, absmins); - - for (i = 0; i < 3; i++) - { - if (origin[i] < absmins[i] || origin[i] > absmaxs[i]) return qfalse; - } //end for - return qtrue; -} //end of the function BotTouchingGoal -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotItemGoalInVisButNotVisible(int viewer, vec3_t eye, vec3_t viewangles, bot_goal_t *goal) -{ - aas_entityinfo_t entinfo; - bsp_trace_t trace; - vec3_t middle; - - if (!(goal->flags & GFL_ITEM)) return qfalse; - // - VectorAdd(goal->mins, goal->mins, middle); - VectorScale(middle, 0.5, middle); - VectorAdd(goal->origin, middle, middle); - // - trace = AAS_Trace(eye, NULL, NULL, middle, viewer, CONTENTS_SOLID); - //if the goal middle point is visible - if (trace.fraction >= 1) - { - //the goal entity number doesn't have to be valid - //just assume it's valid - if (goal->entitynum <= 0) - return qfalse; - // - //if the entity data isn't valid - AAS_EntityInfo(goal->entitynum, &entinfo); - //NOTE: for some wacko reason entities are sometimes - // not updated - //if (!entinfo.valid) return qtrue; - if (entinfo.ltime < AAS_Time() - 0.5) - return qtrue; - } //end if - return qfalse; -} //end of the function BotItemGoalInVisButNotVisible -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotResetGoalState(int goalstate) -{ - bot_goalstate_t *gs; - - gs = BotGoalStateFromHandle(goalstate); - if (!gs) return; - Com_Memset(gs->goalstack, 0, MAX_GOALSTACK * sizeof(bot_goal_t)); - gs->goalstacktop = 0; - BotResetAvoidGoals(goalstate); -} //end of the function BotResetGoalState -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotLoadItemWeights(int goalstate, char *filename) -{ - bot_goalstate_t *gs; - - gs = BotGoalStateFromHandle(goalstate); - if (!gs) return BLERR_CANNOTLOADITEMWEIGHTS; - //load the weight configuration - gs->itemweightconfig = ReadWeightConfig(filename); - if (!gs->itemweightconfig) - { - botimport.Print(PRT_FATAL, "couldn't load weights\n"); - return BLERR_CANNOTLOADITEMWEIGHTS; - } //end if - //if there's no item configuration - if (!itemconfig) return BLERR_CANNOTLOADITEMWEIGHTS; - //create the item weight index - gs->itemweightindex = ItemWeightIndex(gs->itemweightconfig, itemconfig); - //everything went ok - return BLERR_NOERROR; -} //end of the function BotLoadItemWeights -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotFreeItemWeights(int goalstate) -{ - bot_goalstate_t *gs; - - gs = BotGoalStateFromHandle(goalstate); - if (!gs) return; - if (gs->itemweightconfig) FreeWeightConfig(gs->itemweightconfig); - if (gs->itemweightindex) FreeMemory(gs->itemweightindex); -} //end of the function BotFreeItemWeights -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotAllocGoalState(int client) -{ - int i; - - for (i = 1; i <= MAX_CLIENTS; i++) - { - if (!botgoalstates[i]) - { - botgoalstates[i] = GetClearedMemory(sizeof(bot_goalstate_t)); - botgoalstates[i]->client = client; - return i; - } //end if - } //end for - return 0; -} //end of the function BotAllocGoalState -//======================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//======================================================================== -void BotFreeGoalState(int handle) -{ - if (handle <= 0 || handle > MAX_CLIENTS) - { - botimport.Print(PRT_FATAL, "goal state handle %d out of range\n", handle); - return; - } //end if - if (!botgoalstates[handle]) - { - botimport.Print(PRT_FATAL, "invalid goal state handle %d\n", handle); - return; - } //end if - BotFreeItemWeights(handle); - FreeMemory(botgoalstates[handle]); - botgoalstates[handle] = NULL; -} //end of the function BotFreeGoalState -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotSetupGoalAI(void) -{ - char *filename; - - //check if teamplay is on - g_gametype = LibVarValue("g_gametype", "0"); - //item configuration file - filename = LibVarString("itemconfig", "items.c"); - //load the item configuration - itemconfig = LoadItemConfig(filename); - if (!itemconfig) - { - botimport.Print(PRT_FATAL, "couldn't load item config\n"); - return BLERR_CANNOTLOADITEMCONFIG; - } //end if - // - droppedweight = LibVar("droppedweight", "1000"); - //everything went ok - return BLERR_NOERROR; -} //end of the function BotSetupGoalAI -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotShutdownGoalAI(void) -{ - int i; - - if (itemconfig) FreeMemory(itemconfig); - itemconfig = NULL; - if (levelitemheap) FreeMemory(levelitemheap); - levelitemheap = NULL; - freelevelitems = NULL; - levelitems = NULL; - numlevelitems = 0; - - BotFreeInfoEntities(); - - for (i = 1; i <= MAX_CLIENTS; i++) - { - if (botgoalstates[i]) - { - BotFreeGoalState(i); - } //end if - } //end for -} //end of the function BotShutdownGoalAI +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_ai_goal.c + * + * desc: goal AI + * + * $Archive: /MissionPack/code/botlib/be_ai_goal.c $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_utils.h" +#include "l_libvar.h" +#include "l_memory.h" +#include "l_log.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_ai_weight.h" +#include "../game/be_ai_goal.h" +#include "../game/be_ai_move.h" + +//#define DEBUG_AI_GOAL +#ifdef RANDOMIZE +#define UNDECIDEDFUZZY +#endif //RANDOMIZE +#define DROPPEDWEIGHT +//minimum avoid goal time +#define AVOID_MINIMUM_TIME 10 +//default avoid goal time +#define AVOID_DEFAULT_TIME 30 +//avoid dropped goal time +#define AVOID_DROPPED_TIME 10 +// +#define TRAVELTIME_SCALE 0.01 +//item flags +#define IFL_NOTFREE 1 //not in free for all +#define IFL_NOTTEAM 2 //not in team play +#define IFL_NOTSINGLE 4 //not in single player +#define IFL_NOTBOT 8 //bot should never go for this +#define IFL_ROAM 16 //bot roam goal + +//location in the map "target_location" +typedef struct maplocation_s +{ + vec3_t origin; + int areanum; + char name[MAX_EPAIRKEY]; + struct maplocation_s *next; +} maplocation_t; + +//camp spots "info_camp" +typedef struct campspot_s +{ + vec3_t origin; + int areanum; + char name[MAX_EPAIRKEY]; + float range; + float weight; + float wait; + float random; + struct campspot_s *next; +} campspot_t; + +//FIXME: these are game specific +typedef enum { + GT_FFA, // free for all + GT_TOURNAMENT, // one on one tournament + GT_SINGLE_PLAYER, // single player tournament + + //-- team games go after this -- + + GT_TEAM, // team deathmatch + GT_CTF, // capture the flag +#ifdef MISSIONPACK + GT_1FCTF, + GT_OBELISK, + GT_HARVESTER, +#endif + GT_MAX_GAME_TYPE +} gametype_t; + +typedef struct levelitem_s +{ + int number; //number of the level item + int iteminfo; //index into the item info + int flags; //item flags + float weight; //fixed roam weight + vec3_t origin; //origin of the item + int goalareanum; //area the item is in + vec3_t goalorigin; //goal origin within the area + int entitynum; //entity number + float timeout; //item is removed after this time + struct levelitem_s *prev, *next; +} levelitem_t; + +typedef struct iteminfo_s +{ + char classname[32]; //classname of the item + char name[MAX_STRINGFIELD]; //name of the item + char model[MAX_STRINGFIELD]; //model of the item + int modelindex; //model index + int type; //item type + int index; //index in the inventory + float respawntime; //respawn time + vec3_t mins; //mins of the item + vec3_t maxs; //maxs of the item + int number; //number of the item info +} iteminfo_t; + +#define ITEMINFO_OFS(x) (int)&(((iteminfo_t *)0)->x) + +fielddef_t iteminfo_fields[] = +{ +{"name", ITEMINFO_OFS(name), FT_STRING}, +{"model", ITEMINFO_OFS(model), FT_STRING}, +{"modelindex", ITEMINFO_OFS(modelindex), FT_INT}, +{"type", ITEMINFO_OFS(type), FT_INT}, +{"index", ITEMINFO_OFS(index), FT_INT}, +{"respawntime", ITEMINFO_OFS(respawntime), FT_FLOAT}, +{"mins", ITEMINFO_OFS(mins), FT_FLOAT|FT_ARRAY, 3}, +{"maxs", ITEMINFO_OFS(maxs), FT_FLOAT|FT_ARRAY, 3}, +{0, 0, 0} +}; + +structdef_t iteminfo_struct = +{ + sizeof(iteminfo_t), iteminfo_fields +}; + +typedef struct itemconfig_s +{ + int numiteminfo; + iteminfo_t *iteminfo; +} itemconfig_t; + +//goal state +typedef struct bot_goalstate_s +{ + struct weightconfig_s *itemweightconfig; //weight config + int *itemweightindex; //index from item to weight + // + int client; //client using this goal state + int lastreachabilityarea; //last area with reachabilities the bot was in + // + bot_goal_t goalstack[MAX_GOALSTACK]; //goal stack + int goalstacktop; //the top of the goal stack + // + int avoidgoals[MAX_AVOIDGOALS]; //goals to avoid + float avoidgoaltimes[MAX_AVOIDGOALS]; //times to avoid the goals +} bot_goalstate_t; + +bot_goalstate_t *botgoalstates[MAX_CLIENTS + 1]; // bk001206 - FIXME: init? +//item configuration +itemconfig_t *itemconfig = NULL; // bk001206 - init +//level items +levelitem_t *levelitemheap = NULL; // bk001206 - init +levelitem_t *freelevelitems = NULL; // bk001206 - init +levelitem_t *levelitems = NULL; // bk001206 - init +int numlevelitems = 0; +//map locations +maplocation_t *maplocations = NULL; // bk001206 - init +//camp spots +campspot_t *campspots = NULL; // bk001206 - init +//the game type +int g_gametype = 0; // bk001206 - init +//additional dropped item weight +libvar_t *droppedweight = NULL; // bk001206 - init + +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +bot_goalstate_t *BotGoalStateFromHandle(int handle) +{ + if (handle <= 0 || handle > MAX_CLIENTS) + { + botimport.Print(PRT_FATAL, "goal state handle %d out of range\n", handle); + return NULL; + } //end if + if (!botgoalstates[handle]) + { + botimport.Print(PRT_FATAL, "invalid goal state %d\n", handle); + return NULL; + } //end if + return botgoalstates[handle]; +} //end of the function BotGoalStateFromHandle +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotInterbreedGoalFuzzyLogic(int parent1, int parent2, int child) +{ + bot_goalstate_t *p1, *p2, *c; + + p1 = BotGoalStateFromHandle(parent1); + p2 = BotGoalStateFromHandle(parent2); + c = BotGoalStateFromHandle(child); + + InterbreedWeightConfigs(p1->itemweightconfig, p2->itemweightconfig, + c->itemweightconfig); +} //end of the function BotInterbreedingGoalFuzzyLogic +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotSaveGoalFuzzyLogic(int goalstate, char *filename) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + + //WriteWeightConfig(filename, gs->itemweightconfig); +} //end of the function BotSaveGoalFuzzyLogic +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotMutateGoalFuzzyLogic(int goalstate, float range) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + + EvolveWeightConfig(gs->itemweightconfig); +} //end of the function BotMutateGoalFuzzyLogic +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +itemconfig_t *LoadItemConfig(char *filename) +{ + int max_iteminfo; + token_t token; + char path[MAX_PATH]; + source_t *source; + itemconfig_t *ic; + iteminfo_t *ii; + + max_iteminfo = (int) LibVarValue("max_iteminfo", "256"); + if (max_iteminfo < 0) + { + botimport.Print(PRT_ERROR, "max_iteminfo = %d\n", max_iteminfo); + max_iteminfo = 256; + LibVarSet( "max_iteminfo", "256" ); + } + + strncpy( path, filename, MAX_PATH ); + PC_SetBaseFolder(BOTFILESBASEFOLDER); + source = LoadSourceFile( path ); + if( !source ) { + botimport.Print( PRT_ERROR, "counldn't load %s\n", path ); + return NULL; + } //end if + //initialize item config + ic = (itemconfig_t *) GetClearedHunkMemory(sizeof(itemconfig_t) + + max_iteminfo * sizeof(iteminfo_t)); + ic->iteminfo = (iteminfo_t *) ((char *) ic + sizeof(itemconfig_t)); + ic->numiteminfo = 0; + //parse the item config file + while(PC_ReadToken(source, &token)) + { + if (!strcmp(token.string, "iteminfo")) + { + if (ic->numiteminfo >= max_iteminfo) + { + SourceError(source, "more than %d item info defined\n", max_iteminfo); + FreeMemory(ic); + FreeSource(source); + return NULL; + } //end if + ii = &ic->iteminfo[ic->numiteminfo]; + Com_Memset(ii, 0, sizeof(iteminfo_t)); + if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) + { + FreeMemory(ic); + FreeMemory(source); + return NULL; + } //end if + StripDoubleQuotes(token.string); + strncpy(ii->classname, token.string, sizeof(ii->classname)-1); + if (!ReadStructure(source, &iteminfo_struct, (char *) ii)) + { + FreeMemory(ic); + FreeSource(source); + return NULL; + } //end if + ii->number = ic->numiteminfo; + ic->numiteminfo++; + } //end if + else + { + SourceError(source, "unknown definition %s\n", token.string); + FreeMemory(ic); + FreeSource(source); + return NULL; + } //end else + } //end while + FreeSource(source); + // + if (!ic->numiteminfo) botimport.Print(PRT_WARNING, "no item info loaded\n"); + botimport.Print(PRT_MESSAGE, "loaded %s\n", path); + return ic; +} //end of the function LoadItemConfig +//=========================================================================== +// index to find the weight function of an iteminfo +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int *ItemWeightIndex(weightconfig_t *iwc, itemconfig_t *ic) +{ + int *index, i; + + //initialize item weight index + index = (int *) GetClearedMemory(sizeof(int) * ic->numiteminfo); + + for (i = 0; i < ic->numiteminfo; i++) + { + index[i] = FindFuzzyWeight(iwc, ic->iteminfo[i].classname); + if (index[i] < 0) + { + Log_Write("item info %d \"%s\" has no fuzzy weight\r\n", i, ic->iteminfo[i].classname); + } //end if + } //end for + return index; +} //end of the function ItemWeightIndex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void InitLevelItemHeap(void) +{ + int i, max_levelitems; + + if (levelitemheap) FreeMemory(levelitemheap); + + max_levelitems = (int) LibVarValue("max_levelitems", "256"); + levelitemheap = (levelitem_t *) GetClearedMemory(max_levelitems * sizeof(levelitem_t)); + + for (i = 0; i < max_levelitems-1; i++) + { + levelitemheap[i].next = &levelitemheap[i + 1]; + } //end for + levelitemheap[max_levelitems-1].next = NULL; + // + freelevelitems = levelitemheap; +} //end of the function InitLevelItemHeap +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +levelitem_t *AllocLevelItem(void) +{ + levelitem_t *li; + + li = freelevelitems; + if (!li) + { + botimport.Print(PRT_FATAL, "out of level items\n"); + return NULL; + } //end if + // + freelevelitems = freelevelitems->next; + Com_Memset(li, 0, sizeof(levelitem_t)); + return li; +} //end of the function AllocLevelItem +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeLevelItem(levelitem_t *li) +{ + li->next = freelevelitems; + freelevelitems = li; +} //end of the function FreeLevelItem +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AddLevelItemToList(levelitem_t *li) +{ + if (levelitems) levelitems->prev = li; + li->prev = NULL; + li->next = levelitems; + levelitems = li; +} //end of the function AddLevelItemToList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void RemoveLevelItemFromList(levelitem_t *li) +{ + if (li->prev) li->prev->next = li->next; + else levelitems = li->next; + if (li->next) li->next->prev = li->prev; +} //end of the function RemoveLevelItemFromList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFreeInfoEntities(void) +{ + maplocation_t *ml, *nextml; + campspot_t *cs, *nextcs; + + for (ml = maplocations; ml; ml = nextml) + { + nextml = ml->next; + FreeMemory(ml); + } //end for + maplocations = NULL; + for (cs = campspots; cs; cs = nextcs) + { + nextcs = cs->next; + FreeMemory(cs); + } //end for + campspots = NULL; +} //end of the function BotFreeInfoEntities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotInitInfoEntities(void) +{ + char classname[MAX_EPAIRKEY]; + maplocation_t *ml; + campspot_t *cs; + int ent, numlocations, numcampspots; + + BotFreeInfoEntities(); + // + numlocations = 0; + numcampspots = 0; + for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) + { + if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; + + //map locations + if (!strcmp(classname, "target_location")) + { + ml = (maplocation_t *) GetClearedMemory(sizeof(maplocation_t)); + AAS_VectorForBSPEpairKey(ent, "origin", ml->origin); + AAS_ValueForBSPEpairKey(ent, "message", ml->name, sizeof(ml->name)); + ml->areanum = AAS_PointAreaNum(ml->origin); + ml->next = maplocations; + maplocations = ml; + numlocations++; + } //end if + //camp spots + else if (!strcmp(classname, "info_camp")) + { + cs = (campspot_t *) GetClearedMemory(sizeof(campspot_t)); + AAS_VectorForBSPEpairKey(ent, "origin", cs->origin); + //cs->origin[2] += 16; + AAS_ValueForBSPEpairKey(ent, "message", cs->name, sizeof(cs->name)); + AAS_FloatForBSPEpairKey(ent, "range", &cs->range); + AAS_FloatForBSPEpairKey(ent, "weight", &cs->weight); + AAS_FloatForBSPEpairKey(ent, "wait", &cs->wait); + AAS_FloatForBSPEpairKey(ent, "random", &cs->random); + cs->areanum = AAS_PointAreaNum(cs->origin); + if (!cs->areanum) + { + botimport.Print(PRT_MESSAGE, "camp spot at %1.1f %1.1f %1.1f in solid\n", cs->origin[0], cs->origin[1], cs->origin[2]); + FreeMemory(cs); + continue; + } //end if + cs->next = campspots; + campspots = cs; + //AAS_DrawPermanentCross(cs->origin, 4, LINECOLOR_YELLOW); + numcampspots++; + } //end else if + } //end for + if (bot_developer) + { + botimport.Print(PRT_MESSAGE, "%d map locations\n", numlocations); + botimport.Print(PRT_MESSAGE, "%d camp spots\n", numcampspots); + } //end if +} //end of the function BotInitInfoEntities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotInitLevelItems(void) +{ + int i, spawnflags, value; + char classname[MAX_EPAIRKEY]; + vec3_t origin, end; + int ent, goalareanum; + itemconfig_t *ic; + levelitem_t *li; + bsp_trace_t trace; + + //initialize the map locations and camp spots + BotInitInfoEntities(); + + //initialize the level item heap + InitLevelItemHeap(); + levelitems = NULL; + numlevelitems = 0; + // + ic = itemconfig; + if (!ic) return; + + //if there's no AAS file loaded + if (!AAS_Loaded()) return; + + //update the modelindexes of the item info + for (i = 0; i < ic->numiteminfo; i++) + { + //ic->iteminfo[i].modelindex = AAS_IndexFromModel(ic->iteminfo[i].model); + if (!ic->iteminfo[i].modelindex) + { + Log_Write("item %s has modelindex 0", ic->iteminfo[i].classname); + } //end if + } //end for + + for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) + { + if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; + // + spawnflags = 0; + AAS_IntForBSPEpairKey(ent, "spawnflags", &spawnflags); + // + for (i = 0; i < ic->numiteminfo; i++) + { + if (!strcmp(classname, ic->iteminfo[i].classname)) break; + } //end for + if (i >= ic->numiteminfo) + { + Log_Write("entity %s unknown item\r\n", classname); + continue; + } //end if + //get the origin of the item + if (!AAS_VectorForBSPEpairKey(ent, "origin", origin)) + { + botimport.Print(PRT_ERROR, "item %s without origin\n", classname); + continue; + } //end else + // + goalareanum = 0; + //if it is a floating item + if (spawnflags & 1) + { + //if the item is not floating in water + if (!(AAS_PointContents(origin) & CONTENTS_WATER)) + { + VectorCopy(origin, end); + end[2] -= 32; + trace = AAS_Trace(origin, ic->iteminfo[i].mins, ic->iteminfo[i].maxs, end, -1, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); + //if the item not near the ground + if (trace.fraction >= 1) + { + //if the item is not reachable from a jumppad + goalareanum = AAS_BestReachableFromJumpPadArea(origin, ic->iteminfo[i].mins, ic->iteminfo[i].maxs); + Log_Write("item %s reachable from jumppad area %d\r\n", ic->iteminfo[i].classname, goalareanum); + //botimport.Print(PRT_MESSAGE, "item %s reachable from jumppad area %d\r\n", ic->iteminfo[i].classname, goalareanum); + if (!goalareanum) continue; + } //end if + } //end if + } //end if + + li = AllocLevelItem(); + if (!li) return; + // + li->number = ++numlevelitems; + li->timeout = 0; + li->entitynum = 0; + // + li->flags = 0; + AAS_IntForBSPEpairKey(ent, "notfree", &value); + if (value) li->flags |= IFL_NOTFREE; + AAS_IntForBSPEpairKey(ent, "notteam", &value); + if (value) li->flags |= IFL_NOTTEAM; + AAS_IntForBSPEpairKey(ent, "notsingle", &value); + if (value) li->flags |= IFL_NOTSINGLE; + AAS_IntForBSPEpairKey(ent, "notbot", &value); + if (value) li->flags |= IFL_NOTBOT; + if (!strcmp(classname, "item_botroam")) + { + li->flags |= IFL_ROAM; + AAS_FloatForBSPEpairKey(ent, "weight", &li->weight); + } //end if + //if not a stationary item + if (!(spawnflags & 1)) + { + if (!AAS_DropToFloor(origin, ic->iteminfo[i].mins, ic->iteminfo[i].maxs)) + { + botimport.Print(PRT_MESSAGE, "%s in solid at (%1.1f %1.1f %1.1f)\n", + classname, origin[0], origin[1], origin[2]); + } //end if + } //end if + //item info of the level item + li->iteminfo = i; + //origin of the item + VectorCopy(origin, li->origin); + // + if (goalareanum) + { + li->goalareanum = goalareanum; + VectorCopy(origin, li->goalorigin); + } //end if + else + { + //get the item goal area and goal origin + li->goalareanum = AAS_BestReachableArea(origin, + ic->iteminfo[i].mins, ic->iteminfo[i].maxs, + li->goalorigin); + if (!li->goalareanum) + { + botimport.Print(PRT_MESSAGE, "%s not reachable for bots at (%1.1f %1.1f %1.1f)\n", + classname, origin[0], origin[1], origin[2]); + } //end if + } //end else + // + AddLevelItemToList(li); + } //end for + botimport.Print(PRT_MESSAGE, "found %d level items\n", numlevelitems); +} //end of the function BotInitLevelItems +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotGoalName(int number, char *name, int size) +{ + levelitem_t *li; + + if (!itemconfig) return; + // + for (li = levelitems; li; li = li->next) + { + if (li->number == number) + { + strncpy(name, itemconfig->iteminfo[li->iteminfo].name, size-1); + name[size-1] = '\0'; + return; + } //end for + } //end for + strcpy(name, ""); + return; +} //end of the function BotGoalName +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotResetAvoidGoals(int goalstate) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) return; + Com_Memset(gs->avoidgoals, 0, MAX_AVOIDGOALS * sizeof(int)); + Com_Memset(gs->avoidgoaltimes, 0, MAX_AVOIDGOALS * sizeof(float)); +} //end of the function BotResetAvoidGoals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDumpAvoidGoals(int goalstate) +{ + int i; + bot_goalstate_t *gs; + char name[32]; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) return; + for (i = 0; i < MAX_AVOIDGOALS; i++) + { + if (gs->avoidgoaltimes[i] >= AAS_Time()) + { + BotGoalName(gs->avoidgoals[i], name, 32); + Log_Write("avoid goal %s, number %d for %f seconds", name, + gs->avoidgoals[i], gs->avoidgoaltimes[i] - AAS_Time()); + } //end if + } //end for +} //end of the function BotDumpAvoidGoals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotAddToAvoidGoals(bot_goalstate_t *gs, int number, float avoidtime) +{ + int i; + + for (i = 0; i < MAX_AVOIDGOALS; i++) + { + //if the avoid goal is already stored + if (gs->avoidgoals[i] == number) + { + gs->avoidgoals[i] = number; + gs->avoidgoaltimes[i] = AAS_Time() + avoidtime; + return; + } //end if + } //end for + + for (i = 0; i < MAX_AVOIDGOALS; i++) + { + //if this avoid goal has expired + if (gs->avoidgoaltimes[i] < AAS_Time()) + { + gs->avoidgoals[i] = number; + gs->avoidgoaltimes[i] = AAS_Time() + avoidtime; + return; + } //end if + } //end for +} //end of the function BotAddToAvoidGoals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotRemoveFromAvoidGoals(int goalstate, int number) +{ + int i; + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) return; + //don't use the goals the bot wants to avoid + for (i = 0; i < MAX_AVOIDGOALS; i++) + { + if (gs->avoidgoals[i] == number && gs->avoidgoaltimes[i] >= AAS_Time()) + { + gs->avoidgoaltimes[i] = 0; + return; + } //end if + } //end for +} //end of the function BotRemoveFromAvoidGoals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float BotAvoidGoalTime(int goalstate, int number) +{ + int i; + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) return 0; + //don't use the goals the bot wants to avoid + for (i = 0; i < MAX_AVOIDGOALS; i++) + { + if (gs->avoidgoals[i] == number && gs->avoidgoaltimes[i] >= AAS_Time()) + { + return gs->avoidgoaltimes[i] - AAS_Time(); + } //end if + } //end for + return 0; +} //end of the function BotAvoidGoalTime +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotSetAvoidGoalTime(int goalstate, int number, float avoidtime) +{ + bot_goalstate_t *gs; + levelitem_t *li; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) + return; + if (avoidtime < 0) + { + if (!itemconfig) + return; + // + for (li = levelitems; li; li = li->next) + { + if (li->number == number) + { + avoidtime = itemconfig->iteminfo[li->iteminfo].respawntime; + if (!avoidtime) + avoidtime = AVOID_DEFAULT_TIME; + if (avoidtime < AVOID_MINIMUM_TIME) + avoidtime = AVOID_MINIMUM_TIME; + BotAddToAvoidGoals(gs, number, avoidtime); + return; + } //end for + } //end for + return; + } //end if + else + { + BotAddToAvoidGoals(gs, number, avoidtime); + } //end else +} //end of the function BotSetAvoidGoalTime +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotGetLevelItemGoal(int index, char *name, bot_goal_t *goal) +{ + levelitem_t *li; + + if (!itemconfig) return -1; + li = levelitems; + if (index >= 0) + { + for (; li; li = li->next) + { + if (li->number == index) + { + li = li->next; + break; + } //end if + } //end for + } //end for + for (; li; li = li->next) + { + // + if (g_gametype == GT_SINGLE_PLAYER) { + if (li->flags & IFL_NOTSINGLE) continue; + } + else if (g_gametype >= GT_TEAM) { + if (li->flags & IFL_NOTTEAM) continue; + } + else { + if (li->flags & IFL_NOTFREE) continue; + } + if (li->flags & IFL_NOTBOT) continue; + // + if (!Q_stricmp(name, itemconfig->iteminfo[li->iteminfo].name)) + { + goal->areanum = li->goalareanum; + VectorCopy(li->goalorigin, goal->origin); + goal->entitynum = li->entitynum; + VectorCopy(itemconfig->iteminfo[li->iteminfo].mins, goal->mins); + VectorCopy(itemconfig->iteminfo[li->iteminfo].maxs, goal->maxs); + goal->number = li->number; + goal->flags = GFL_ITEM; + if (li->timeout) goal->flags |= GFL_DROPPED; + //botimport.Print(PRT_MESSAGE, "found li %s\n", itemconfig->iteminfo[li->iteminfo].name); + return li->number; + } //end if + } //end for + return -1; +} //end of the function BotGetLevelItemGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotGetMapLocationGoal(char *name, bot_goal_t *goal) +{ + maplocation_t *ml; + vec3_t mins = {-8, -8, -8}, maxs = {8, 8, 8}; + + for (ml = maplocations; ml; ml = ml->next) + { + if (!Q_stricmp(ml->name, name)) + { + goal->areanum = ml->areanum; + VectorCopy(ml->origin, goal->origin); + goal->entitynum = 0; + VectorCopy(mins, goal->mins); + VectorCopy(maxs, goal->maxs); + return qtrue; + } //end if + } //end for + return qfalse; +} //end of the function BotGetMapLocationGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotGetNextCampSpotGoal(int num, bot_goal_t *goal) +{ + int i; + campspot_t *cs; + vec3_t mins = {-8, -8, -8}, maxs = {8, 8, 8}; + + if (num < 0) num = 0; + i = num; + for (cs = campspots; cs; cs = cs->next) + { + if (--i < 0) + { + goal->areanum = cs->areanum; + VectorCopy(cs->origin, goal->origin); + goal->entitynum = 0; + VectorCopy(mins, goal->mins); + VectorCopy(maxs, goal->maxs); + return num+1; + } //end if + } //end for + return 0; +} //end of the function BotGetNextCampSpotGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFindEntityForLevelItem(levelitem_t *li) +{ + int ent, modelindex; + itemconfig_t *ic; + aas_entityinfo_t entinfo; + vec3_t dir; + + ic = itemconfig; + if (!itemconfig) return; + for (ent = AAS_NextEntity(0); ent; ent = AAS_NextEntity(ent)) + { + //get the model index of the entity + modelindex = AAS_EntityModelindex(ent); + // + if (!modelindex) continue; + //get info about the entity + AAS_EntityInfo(ent, &entinfo); + //if the entity is still moving + if (entinfo.origin[0] != entinfo.lastvisorigin[0] || + entinfo.origin[1] != entinfo.lastvisorigin[1] || + entinfo.origin[2] != entinfo.lastvisorigin[2]) continue; + // + if (ic->iteminfo[li->iteminfo].modelindex == modelindex) + { + //check if the entity is very close + VectorSubtract(li->origin, entinfo.origin, dir); + if (VectorLength(dir) < 30) + { + //found an entity for this level item + li->entitynum = ent; + } //end if + } //end if + } //end for +} //end of the function BotFindEntityForLevelItem +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== + +//NOTE: enum entityType_t in bg_public.h +#define ET_ITEM 2 + +void BotUpdateEntityItems(void) +{ + int ent, i, modelindex; + vec3_t dir; + levelitem_t *li, *nextli; + aas_entityinfo_t entinfo; + itemconfig_t *ic; + + //timeout current entity items if necessary + for (li = levelitems; li; li = nextli) + { + nextli = li->next; + //if it is a item that will time out + if (li->timeout) + { + //timeout the item + if (li->timeout < AAS_Time()) + { + RemoveLevelItemFromList(li); + FreeLevelItem(li); + } //end if + } //end if + } //end for + //find new entity items + ic = itemconfig; + if (!itemconfig) return; + // + for (ent = AAS_NextEntity(0); ent; ent = AAS_NextEntity(ent)) + { + if (AAS_EntityType(ent) != ET_ITEM) continue; + //get the model index of the entity + modelindex = AAS_EntityModelindex(ent); + // + if (!modelindex) continue; + //get info about the entity + AAS_EntityInfo(ent, &entinfo); + //FIXME: don't do this + //skip all floating items for now + //if (entinfo.groundent != ENTITYNUM_WORLD) continue; + //if the entity is still moving + if (entinfo.origin[0] != entinfo.lastvisorigin[0] || + entinfo.origin[1] != entinfo.lastvisorigin[1] || + entinfo.origin[2] != entinfo.lastvisorigin[2]) continue; + //check if the entity is already stored as a level item + for (li = levelitems; li; li = li->next) + { + //if the level item is linked to an entity + if (li->entitynum && li->entitynum == ent) + { + //the entity is re-used if the models are different + if (ic->iteminfo[li->iteminfo].modelindex != modelindex) + { + //remove this level item + RemoveLevelItemFromList(li); + FreeLevelItem(li); + li = NULL; + break; + } //end if + else + { + if (entinfo.origin[0] != li->origin[0] || + entinfo.origin[1] != li->origin[1] || + entinfo.origin[2] != li->origin[2]) + { + VectorCopy(entinfo.origin, li->origin); + //also update the goal area number + li->goalareanum = AAS_BestReachableArea(li->origin, + ic->iteminfo[li->iteminfo].mins, ic->iteminfo[li->iteminfo].maxs, + li->goalorigin); + } //end if + break; + } //end else + } //end if + } //end for + if (li) continue; + //try to link the entity to a level item + for (li = levelitems; li; li = li->next) + { + //if this level item is already linked + if (li->entitynum) continue; + // + if (g_gametype == GT_SINGLE_PLAYER) { + if (li->flags & IFL_NOTSINGLE) continue; + } + else if (g_gametype >= GT_TEAM) { + if (li->flags & IFL_NOTTEAM) continue; + } + else { + if (li->flags & IFL_NOTFREE) continue; + } + //if the model of the level item and the entity are the same + if (ic->iteminfo[li->iteminfo].modelindex == modelindex) + { + //check if the entity is very close + VectorSubtract(li->origin, entinfo.origin, dir); + if (VectorLength(dir) < 30) + { + //found an entity for this level item + li->entitynum = ent; + //if the origin is different + if (entinfo.origin[0] != li->origin[0] || + entinfo.origin[1] != li->origin[1] || + entinfo.origin[2] != li->origin[2]) + { + //update the level item origin + VectorCopy(entinfo.origin, li->origin); + //also update the goal area number + li->goalareanum = AAS_BestReachableArea(li->origin, + ic->iteminfo[li->iteminfo].mins, ic->iteminfo[li->iteminfo].maxs, + li->goalorigin); + } //end if +#ifdef DEBUG + Log_Write("linked item %s to an entity", ic->iteminfo[li->iteminfo].classname); +#endif //DEBUG + break; + } //end if + } //end else + } //end for + if (li) continue; + //check if the model is from a known item + for (i = 0; i < ic->numiteminfo; i++) + { + if (ic->iteminfo[i].modelindex == modelindex) + { + break; + } //end if + } //end for + //if the model is not from a known item + if (i >= ic->numiteminfo) continue; + //allocate a new level item + li = AllocLevelItem(); + // + if (!li) continue; + //entity number of the level item + li->entitynum = ent; + //number for the level item + li->number = numlevelitems + ent; + //set the item info index for the level item + li->iteminfo = i; + //origin of the item + VectorCopy(entinfo.origin, li->origin); + //get the item goal area and goal origin + li->goalareanum = AAS_BestReachableArea(li->origin, + ic->iteminfo[i].mins, ic->iteminfo[i].maxs, + li->goalorigin); + //never go for items dropped into jumppads + if (AAS_AreaJumpPad(li->goalareanum)) + { + FreeLevelItem(li); + continue; + } //end if + //time this item out after 30 seconds + //dropped items disappear after 30 seconds + li->timeout = AAS_Time() + 30; + //add the level item to the list + AddLevelItemToList(li); + //botimport.Print(PRT_MESSAGE, "found new level item %s\n", ic->iteminfo[i].classname); + } //end for + /* + for (li = levelitems; li; li = li->next) + { + if (!li->entitynum) + { + BotFindEntityForLevelItem(li); + } //end if + } //end for*/ +} //end of the function BotUpdateEntityItems +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDumpGoalStack(int goalstate) +{ + int i; + bot_goalstate_t *gs; + char name[32]; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) return; + for (i = 1; i <= gs->goalstacktop; i++) + { + BotGoalName(gs->goalstack[i].number, name, 32); + Log_Write("%d: %s", i, name); + } //end for +} //end of the function BotDumpGoalStack +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotPushGoal(int goalstate, bot_goal_t *goal) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) return; + if (gs->goalstacktop >= MAX_GOALSTACK-1) + { + botimport.Print(PRT_ERROR, "goal heap overflow\n"); + BotDumpGoalStack(goalstate); + return; + } //end if + gs->goalstacktop++; + Com_Memcpy(&gs->goalstack[gs->goalstacktop], goal, sizeof(bot_goal_t)); +} //end of the function BotPushGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotPopGoal(int goalstate) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) return; + if (gs->goalstacktop > 0) gs->goalstacktop--; +} //end of the function BotPopGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotEmptyGoalStack(int goalstate) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) return; + gs->goalstacktop = 0; +} //end of the function BotEmptyGoalStack +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotGetTopGoal(int goalstate, bot_goal_t *goal) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) return qfalse; + if (!gs->goalstacktop) return qfalse; + Com_Memcpy(goal, &gs->goalstack[gs->goalstacktop], sizeof(bot_goal_t)); + return qtrue; +} //end of the function BotGetTopGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotGetSecondGoal(int goalstate, bot_goal_t *goal) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) return qfalse; + if (gs->goalstacktop <= 1) return qfalse; + Com_Memcpy(goal, &gs->goalstack[gs->goalstacktop-1], sizeof(bot_goal_t)); + return qtrue; +} //end of the function BotGetSecondGoal +//=========================================================================== +// pops a new long term goal on the goal stack in the goalstate +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotChooseLTGItem(int goalstate, vec3_t origin, int *inventory, int travelflags) +{ + int areanum, t, weightnum; + float weight, bestweight, avoidtime; + iteminfo_t *iteminfo; + itemconfig_t *ic; + levelitem_t *li, *bestitem; + bot_goal_t goal; + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) + return qfalse; + if (!gs->itemweightconfig) + return qfalse; + //get the area the bot is in + areanum = BotReachabilityArea(origin, gs->client); + //if the bot is in solid or if the area the bot is in has no reachability links + if (!areanum || !AAS_AreaReachability(areanum)) + { + //use the last valid area the bot was in + areanum = gs->lastreachabilityarea; + } //end if + //remember the last area with reachabilities the bot was in + gs->lastreachabilityarea = areanum; + //if still in solid + if (!areanum) + return qfalse; + //the item configuration + ic = itemconfig; + if (!itemconfig) + return qfalse; + //best weight and item so far + bestweight = 0; + bestitem = NULL; + Com_Memset(&goal, 0, sizeof(bot_goal_t)); + //go through the items in the level + for (li = levelitems; li; li = li->next) + { + if (g_gametype == GT_SINGLE_PLAYER) { + if (li->flags & IFL_NOTSINGLE) + continue; + } + else if (g_gametype >= GT_TEAM) { + if (li->flags & IFL_NOTTEAM) + continue; + } + else { + if (li->flags & IFL_NOTFREE) + continue; + } + if (li->flags & IFL_NOTBOT) + continue; + //if the item is not in a possible goal area + if (!li->goalareanum) + continue; + //FIXME: is this a good thing? added this for items that never spawned into the game (f.i. CTF flags in obelisk) + if (!li->entitynum && !(li->flags & IFL_ROAM)) + continue; + //get the fuzzy weight function for this item + iteminfo = &ic->iteminfo[li->iteminfo]; + weightnum = gs->itemweightindex[iteminfo->number]; + if (weightnum < 0) + continue; + +#ifdef UNDECIDEDFUZZY + weight = FuzzyWeightUndecided(inventory, gs->itemweightconfig, weightnum); +#else + weight = FuzzyWeight(inventory, gs->itemweightconfig, weightnum); +#endif //UNDECIDEDFUZZY +#ifdef DROPPEDWEIGHT + //HACK: to make dropped items more attractive + if (li->timeout) + weight += droppedweight->value; +#endif //DROPPEDWEIGHT + //use weight scale for item_botroam + if (li->flags & IFL_ROAM) weight *= li->weight; + // + if (weight > 0) + { + //get the travel time towards the goal area + t = AAS_AreaTravelTimeToGoalArea(areanum, origin, li->goalareanum, travelflags); + //if the goal is reachable + if (t > 0) + { + //if this item won't respawn before we get there + avoidtime = BotAvoidGoalTime(goalstate, li->number); + if (avoidtime - t * 0.009 > 0) + continue; + // + weight /= (float) t * TRAVELTIME_SCALE; + // + if (weight > bestweight) + { + bestweight = weight; + bestitem = li; + } //end if + } //end if + } //end if + } //end for + //if no goal item found + if (!bestitem) + { + /* + //if not in lava or slime + if (!AAS_AreaLava(areanum) && !AAS_AreaSlime(areanum)) + { + if (AAS_RandomGoalArea(areanum, travelflags, &goal.areanum, goal.origin)) + { + VectorSet(goal.mins, -15, -15, -15); + VectorSet(goal.maxs, 15, 15, 15); + goal.entitynum = 0; + goal.number = 0; + goal.flags = GFL_ROAM; + goal.iteminfo = 0; + //push the goal on the stack + BotPushGoal(goalstate, &goal); + // +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "chosen roam goal area %d\n", goal.areanum); +#endif //DEBUG + return qtrue; + } //end if + } //end if + */ + return qfalse; + } //end if + //create a bot goal for this item + iteminfo = &ic->iteminfo[bestitem->iteminfo]; + VectorCopy(bestitem->goalorigin, goal.origin); + VectorCopy(iteminfo->mins, goal.mins); + VectorCopy(iteminfo->maxs, goal.maxs); + goal.areanum = bestitem->goalareanum; + goal.entitynum = bestitem->entitynum; + goal.number = bestitem->number; + goal.flags = GFL_ITEM; + if (bestitem->timeout) + goal.flags |= GFL_DROPPED; + if (bestitem->flags & IFL_ROAM) + goal.flags |= GFL_ROAM; + goal.iteminfo = bestitem->iteminfo; + //if it's a dropped item + if (bestitem->timeout) + { + avoidtime = AVOID_DROPPED_TIME; + } //end if + else + { + avoidtime = iteminfo->respawntime; + if (!avoidtime) + avoidtime = AVOID_DEFAULT_TIME; + if (avoidtime < AVOID_MINIMUM_TIME) + avoidtime = AVOID_MINIMUM_TIME; + } //end else + //add the chosen goal to the goals to avoid for a while + BotAddToAvoidGoals(gs, bestitem->number, avoidtime); + //push the goal on the stack + BotPushGoal(goalstate, &goal); + // + return qtrue; +} //end of the function BotChooseLTGItem +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotChooseNBGItem(int goalstate, vec3_t origin, int *inventory, int travelflags, + bot_goal_t *ltg, float maxtime) +{ + int areanum, t, weightnum, ltg_time; + float weight, bestweight, avoidtime; + iteminfo_t *iteminfo; + itemconfig_t *ic; + levelitem_t *li, *bestitem; + bot_goal_t goal; + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) + return qfalse; + if (!gs->itemweightconfig) + return qfalse; + //get the area the bot is in + areanum = BotReachabilityArea(origin, gs->client); + //if the bot is in solid or if the area the bot is in has no reachability links + if (!areanum || !AAS_AreaReachability(areanum)) + { + //use the last valid area the bot was in + areanum = gs->lastreachabilityarea; + } //end if + //remember the last area with reachabilities the bot was in + gs->lastreachabilityarea = areanum; + //if still in solid + if (!areanum) + return qfalse; + // + if (ltg) ltg_time = AAS_AreaTravelTimeToGoalArea(areanum, origin, ltg->areanum, travelflags); + else ltg_time = 99999; + //the item configuration + ic = itemconfig; + if (!itemconfig) + return qfalse; + //best weight and item so far + bestweight = 0; + bestitem = NULL; + Com_Memset(&goal, 0, sizeof(bot_goal_t)); + //go through the items in the level + for (li = levelitems; li; li = li->next) + { + if (g_gametype == GT_SINGLE_PLAYER) { + if (li->flags & IFL_NOTSINGLE) + continue; + } + else if (g_gametype >= GT_TEAM) { + if (li->flags & IFL_NOTTEAM) + continue; + } + else { + if (li->flags & IFL_NOTFREE) + continue; + } + if (li->flags & IFL_NOTBOT) + continue; + //if the item is in a possible goal area + if (!li->goalareanum) + continue; + //FIXME: is this a good thing? added this for items that never spawned into the game (f.i. CTF flags in obelisk) + if (!li->entitynum && !(li->flags & IFL_ROAM)) + continue; + //get the fuzzy weight function for this item + iteminfo = &ic->iteminfo[li->iteminfo]; + weightnum = gs->itemweightindex[iteminfo->number]; + if (weightnum < 0) + continue; + // +#ifdef UNDECIDEDFUZZY + weight = FuzzyWeightUndecided(inventory, gs->itemweightconfig, weightnum); +#else + weight = FuzzyWeight(inventory, gs->itemweightconfig, weightnum); +#endif //UNDECIDEDFUZZY +#ifdef DROPPEDWEIGHT + //HACK: to make dropped items more attractive + if (li->timeout) + weight += droppedweight->value; +#endif //DROPPEDWEIGHT + //use weight scale for item_botroam + if (li->flags & IFL_ROAM) weight *= li->weight; + // + if (weight > 0) + { + //get the travel time towards the goal area + t = AAS_AreaTravelTimeToGoalArea(areanum, origin, li->goalareanum, travelflags); + //if the goal is reachable + if (t > 0 && t < maxtime) + { + //if this item won't respawn before we get there + avoidtime = BotAvoidGoalTime(goalstate, li->number); + if (avoidtime - t * 0.009 > 0) + continue; + // + weight /= (float) t * TRAVELTIME_SCALE; + // + if (weight > bestweight) + { + t = 0; + if (ltg && !li->timeout) + { + //get the travel time from the goal to the long term goal + t = AAS_AreaTravelTimeToGoalArea(li->goalareanum, li->goalorigin, ltg->areanum, travelflags); + } //end if + //if the travel back is possible and doesn't take too long + if (t <= ltg_time) + { + bestweight = weight; + bestitem = li; + } //end if + } //end if + } //end if + } //end if + } //end for + //if no goal item found + if (!bestitem) + return qfalse; + //create a bot goal for this item + iteminfo = &ic->iteminfo[bestitem->iteminfo]; + VectorCopy(bestitem->goalorigin, goal.origin); + VectorCopy(iteminfo->mins, goal.mins); + VectorCopy(iteminfo->maxs, goal.maxs); + goal.areanum = bestitem->goalareanum; + goal.entitynum = bestitem->entitynum; + goal.number = bestitem->number; + goal.flags = GFL_ITEM; + if (bestitem->timeout) + goal.flags |= GFL_DROPPED; + if (bestitem->flags & IFL_ROAM) + goal.flags |= GFL_ROAM; + goal.iteminfo = bestitem->iteminfo; + //if it's a dropped item + if (bestitem->timeout) + { + avoidtime = AVOID_DROPPED_TIME; + } //end if + else + { + avoidtime = iteminfo->respawntime; + if (!avoidtime) + avoidtime = AVOID_DEFAULT_TIME; + if (avoidtime < AVOID_MINIMUM_TIME) + avoidtime = AVOID_MINIMUM_TIME; + } //end else + //add the chosen goal to the goals to avoid for a while + BotAddToAvoidGoals(gs, bestitem->number, avoidtime); + //push the goal on the stack + BotPushGoal(goalstate, &goal); + // + return qtrue; +} //end of the function BotChooseNBGItem +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotTouchingGoal(vec3_t origin, bot_goal_t *goal) +{ + int i; + vec3_t boxmins, boxmaxs; + vec3_t absmins, absmaxs; + vec3_t safety_maxs = {0, 0, 0}; //{4, 4, 10}; + vec3_t safety_mins = {0, 0, 0}; //{-4, -4, 0}; + + AAS_PresenceTypeBoundingBox(PRESENCE_NORMAL, boxmins, boxmaxs); + VectorSubtract(goal->mins, boxmaxs, absmins); + VectorSubtract(goal->maxs, boxmins, absmaxs); + VectorAdd(absmins, goal->origin, absmins); + VectorAdd(absmaxs, goal->origin, absmaxs); + //make the box a little smaller for safety + VectorSubtract(absmaxs, safety_maxs, absmaxs); + VectorSubtract(absmins, safety_mins, absmins); + + for (i = 0; i < 3; i++) + { + if (origin[i] < absmins[i] || origin[i] > absmaxs[i]) return qfalse; + } //end for + return qtrue; +} //end of the function BotTouchingGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotItemGoalInVisButNotVisible(int viewer, vec3_t eye, vec3_t viewangles, bot_goal_t *goal) +{ + aas_entityinfo_t entinfo; + bsp_trace_t trace; + vec3_t middle; + + if (!(goal->flags & GFL_ITEM)) return qfalse; + // + VectorAdd(goal->mins, goal->mins, middle); + VectorScale(middle, 0.5, middle); + VectorAdd(goal->origin, middle, middle); + // + trace = AAS_Trace(eye, NULL, NULL, middle, viewer, CONTENTS_SOLID); + //if the goal middle point is visible + if (trace.fraction >= 1) + { + //the goal entity number doesn't have to be valid + //just assume it's valid + if (goal->entitynum <= 0) + return qfalse; + // + //if the entity data isn't valid + AAS_EntityInfo(goal->entitynum, &entinfo); + //NOTE: for some wacko reason entities are sometimes + // not updated + //if (!entinfo.valid) return qtrue; + if (entinfo.ltime < AAS_Time() - 0.5) + return qtrue; + } //end if + return qfalse; +} //end of the function BotItemGoalInVisButNotVisible +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotResetGoalState(int goalstate) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) return; + Com_Memset(gs->goalstack, 0, MAX_GOALSTACK * sizeof(bot_goal_t)); + gs->goalstacktop = 0; + BotResetAvoidGoals(goalstate); +} //end of the function BotResetGoalState +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotLoadItemWeights(int goalstate, char *filename) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) return BLERR_CANNOTLOADITEMWEIGHTS; + //load the weight configuration + gs->itemweightconfig = ReadWeightConfig(filename); + if (!gs->itemweightconfig) + { + botimport.Print(PRT_FATAL, "couldn't load weights\n"); + return BLERR_CANNOTLOADITEMWEIGHTS; + } //end if + //if there's no item configuration + if (!itemconfig) return BLERR_CANNOTLOADITEMWEIGHTS; + //create the item weight index + gs->itemweightindex = ItemWeightIndex(gs->itemweightconfig, itemconfig); + //everything went ok + return BLERR_NOERROR; +} //end of the function BotLoadItemWeights +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFreeItemWeights(int goalstate) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) return; + if (gs->itemweightconfig) FreeWeightConfig(gs->itemweightconfig); + if (gs->itemweightindex) FreeMemory(gs->itemweightindex); +} //end of the function BotFreeItemWeights +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotAllocGoalState(int client) +{ + int i; + + for (i = 1; i <= MAX_CLIENTS; i++) + { + if (!botgoalstates[i]) + { + botgoalstates[i] = GetClearedMemory(sizeof(bot_goalstate_t)); + botgoalstates[i]->client = client; + return i; + } //end if + } //end for + return 0; +} //end of the function BotAllocGoalState +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotFreeGoalState(int handle) +{ + if (handle <= 0 || handle > MAX_CLIENTS) + { + botimport.Print(PRT_FATAL, "goal state handle %d out of range\n", handle); + return; + } //end if + if (!botgoalstates[handle]) + { + botimport.Print(PRT_FATAL, "invalid goal state handle %d\n", handle); + return; + } //end if + BotFreeItemWeights(handle); + FreeMemory(botgoalstates[handle]); + botgoalstates[handle] = NULL; +} //end of the function BotFreeGoalState +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotSetupGoalAI(void) +{ + char *filename; + + //check if teamplay is on + g_gametype = LibVarValue("g_gametype", "0"); + //item configuration file + filename = LibVarString("itemconfig", "items.c"); + //load the item configuration + itemconfig = LoadItemConfig(filename); + if (!itemconfig) + { + botimport.Print(PRT_FATAL, "couldn't load item config\n"); + return BLERR_CANNOTLOADITEMCONFIG; + } //end if + // + droppedweight = LibVar("droppedweight", "1000"); + //everything went ok + return BLERR_NOERROR; +} //end of the function BotSetupGoalAI +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotShutdownGoalAI(void) +{ + int i; + + if (itemconfig) FreeMemory(itemconfig); + itemconfig = NULL; + if (levelitemheap) FreeMemory(levelitemheap); + levelitemheap = NULL; + freelevelitems = NULL; + levelitems = NULL; + numlevelitems = 0; + + BotFreeInfoEntities(); + + for (i = 1; i <= MAX_CLIENTS; i++) + { + if (botgoalstates[i]) + { + BotFreeGoalState(i); + } //end if + } //end for +} //end of the function BotShutdownGoalAI diff --git a/code/botlib/be_ai_move.c b/code/botlib/be_ai_move.c index 210d4d9..1f3b8ee 100755 --- a/code/botlib/be_ai_move.c +++ b/code/botlib/be_ai_move.c @@ -1,3610 +1,3610 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: be_ai_move.c - * - * desc: bot movement AI - * - * $Archive: /MissionPack/code/botlib/be_ai_move.c $ - * - *****************************************************************************/ - -#include "../game/q_shared.h" -#include "l_memory.h" -#include "l_libvar.h" -#include "l_utils.h" -#include "l_script.h" -#include "l_precomp.h" -#include "l_struct.h" -#include "aasfile.h" -#include "../game/botlib.h" -#include "../game/be_aas.h" -#include "be_aas_funcs.h" -#include "be_interface.h" - -#include "../game/be_ea.h" -#include "../game/be_ai_goal.h" -#include "../game/be_ai_move.h" - - -//#define DEBUG_AI_MOVE -//#define DEBUG_ELEVATOR -//#define DEBUG_GRAPPLE - -// bk001204 - redundant bot_avoidspot_t, see ../game/be_ai_move.h - -//movement state -//NOTE: the moveflags MFL_ONGROUND, MFL_TELEPORTED, MFL_WATERJUMP and -// MFL_GRAPPLEPULL must be set outside the movement code -typedef struct bot_movestate_s -{ - //input vars (all set outside the movement code) - vec3_t origin; //origin of the bot - vec3_t velocity; //velocity of the bot - vec3_t viewoffset; //view offset - int entitynum; //entity number of the bot - int client; //client number of the bot - float thinktime; //time the bot thinks - int presencetype; //presencetype of the bot - vec3_t viewangles; //view angles of the bot - //state vars - int areanum; //area the bot is in - int lastareanum; //last area the bot was in - int lastgoalareanum; //last goal area number - int lastreachnum; //last reachability number - vec3_t lastorigin; //origin previous cycle - int reachareanum; //area number of the reachabilty - int moveflags; //movement flags - int jumpreach; //set when jumped - float grapplevisible_time; //last time the grapple was visible - float lastgrappledist; //last distance to the grapple end - float reachability_time; //time to use current reachability - int avoidreach[MAX_AVOIDREACH]; //reachabilities to avoid - float avoidreachtimes[MAX_AVOIDREACH]; //times to avoid the reachabilities - int avoidreachtries[MAX_AVOIDREACH]; //number of tries before avoiding - // - bot_avoidspot_t avoidspots[MAX_AVOIDSPOTS]; //spots to avoid - int numavoidspots; -} bot_movestate_t; - -//used to avoid reachability links for some time after being used -#define AVOIDREACH -#define AVOIDREACH_TIME 6 //avoid links for 6 seconds after use -#define AVOIDREACH_TRIES 4 -//prediction times -#define PREDICTIONTIME_JUMP 3 //in seconds -#define PREDICTIONTIME_MOVE 2 //in seconds -//weapon indexes for weapon jumping -#define WEAPONINDEX_ROCKET_LAUNCHER 5 -#define WEAPONINDEX_BFG 9 - -#define MODELTYPE_FUNC_PLAT 1 -#define MODELTYPE_FUNC_BOB 2 -#define MODELTYPE_FUNC_DOOR 3 -#define MODELTYPE_FUNC_STATIC 4 - -libvar_t *sv_maxstep; -libvar_t *sv_maxbarrier; -libvar_t *sv_gravity; -libvar_t *weapindex_rocketlauncher; -libvar_t *weapindex_bfg10k; -libvar_t *weapindex_grapple; -libvar_t *entitytypemissile; -libvar_t *offhandgrapple; -libvar_t *cmd_grappleoff; -libvar_t *cmd_grappleon; -//type of model, func_plat or func_bobbing -int modeltypes[MAX_MODELS]; - -bot_movestate_t *botmovestates[MAX_CLIENTS+1]; - -//======================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//======================================================================== -int BotAllocMoveState(void) -{ - int i; - - for (i = 1; i <= MAX_CLIENTS; i++) - { - if (!botmovestates[i]) - { - botmovestates[i] = GetClearedMemory(sizeof(bot_movestate_t)); - return i; - } //end if - } //end for - return 0; -} //end of the function BotAllocMoveState -//======================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//======================================================================== -void BotFreeMoveState(int handle) -{ - if (handle <= 0 || handle > MAX_CLIENTS) - { - botimport.Print(PRT_FATAL, "move state handle %d out of range\n", handle); - return; - } //end if - if (!botmovestates[handle]) - { - botimport.Print(PRT_FATAL, "invalid move state %d\n", handle); - return; - } //end if - FreeMemory(botmovestates[handle]); - botmovestates[handle] = NULL; -} //end of the function BotFreeMoveState -//======================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//======================================================================== -bot_movestate_t *BotMoveStateFromHandle(int handle) -{ - if (handle <= 0 || handle > MAX_CLIENTS) - { - botimport.Print(PRT_FATAL, "move state handle %d out of range\n", handle); - return NULL; - } //end if - if (!botmovestates[handle]) - { - botimport.Print(PRT_FATAL, "invalid move state %d\n", handle); - return NULL; - } //end if - return botmovestates[handle]; -} //end of the function BotMoveStateFromHandle -//======================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//======================================================================== -void BotInitMoveState(int handle, bot_initmove_t *initmove) -{ - bot_movestate_t *ms; - - ms = BotMoveStateFromHandle(handle); - if (!ms) return; - VectorCopy(initmove->origin, ms->origin); - VectorCopy(initmove->velocity, ms->velocity); - VectorCopy(initmove->viewoffset, ms->viewoffset); - ms->entitynum = initmove->entitynum; - ms->client = initmove->client; - ms->thinktime = initmove->thinktime; - ms->presencetype = initmove->presencetype; - VectorCopy(initmove->viewangles, ms->viewangles); - // - ms->moveflags &= ~MFL_ONGROUND; - if (initmove->or_moveflags & MFL_ONGROUND) ms->moveflags |= MFL_ONGROUND; - ms->moveflags &= ~MFL_TELEPORTED; - if (initmove->or_moveflags & MFL_TELEPORTED) ms->moveflags |= MFL_TELEPORTED; - ms->moveflags &= ~MFL_WATERJUMP; - if (initmove->or_moveflags & MFL_WATERJUMP) ms->moveflags |= MFL_WATERJUMP; - ms->moveflags &= ~MFL_WALK; - if (initmove->or_moveflags & MFL_WALK) ms->moveflags |= MFL_WALK; - ms->moveflags &= ~MFL_GRAPPLEPULL; - if (initmove->or_moveflags & MFL_GRAPPLEPULL) ms->moveflags |= MFL_GRAPPLEPULL; -} //end of the function BotInitMoveState -//======================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//======================================================================== -float AngleDiff(float ang1, float ang2) -{ - float diff; - - diff = ang1 - ang2; - if (ang1 > ang2) - { - if (diff > 180.0) diff -= 360.0; - } //end if - else - { - if (diff < -180.0) diff += 360.0; - } //end else - return diff; -} //end of the function AngleDiff -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotFuzzyPointReachabilityArea(vec3_t origin) -{ - int firstareanum, j, x, y, z; - int areas[10], numareas, areanum, bestareanum; - float dist, bestdist; - vec3_t points[10], v, end; - - firstareanum = 0; - areanum = AAS_PointAreaNum(origin); - if (areanum) - { - firstareanum = areanum; - if (AAS_AreaReachability(areanum)) return areanum; - } //end if - VectorCopy(origin, end); - end[2] += 4; - numareas = AAS_TraceAreas(origin, end, areas, points, 10); - for (j = 0; j < numareas; j++) - { - if (AAS_AreaReachability(areas[j])) return areas[j]; - } //end for - bestdist = 999999; - bestareanum = 0; - for (z = 1; z >= -1; z -= 1) - { - for (x = 1; x >= -1; x -= 1) - { - for (y = 1; y >= -1; y -= 1) - { - VectorCopy(origin, end); - end[0] += x * 8; - end[1] += y * 8; - end[2] += z * 12; - numareas = AAS_TraceAreas(origin, end, areas, points, 10); - for (j = 0; j < numareas; j++) - { - if (AAS_AreaReachability(areas[j])) - { - VectorSubtract(points[j], origin, v); - dist = VectorLength(v); - if (dist < bestdist) - { - bestareanum = areas[j]; - bestdist = dist; - } //end if - } //end if - if (!firstareanum) firstareanum = areas[j]; - } //end for - } //end for - } //end for - if (bestareanum) return bestareanum; - } //end for - return firstareanum; -} //end of the function BotFuzzyPointReachabilityArea -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotReachabilityArea(vec3_t origin, int client) -{ - int modelnum, modeltype, reachnum, areanum; - aas_reachability_t reach; - vec3_t org, end, mins, maxs, up = {0, 0, 1}; - bsp_trace_t bsptrace; - aas_trace_t trace; - - //check if the bot is standing on something - AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, mins, maxs); - VectorMA(origin, -3, up, end); - bsptrace = AAS_Trace(origin, mins, maxs, end, client, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); - if (!bsptrace.startsolid && bsptrace.fraction < 1 && bsptrace.ent != ENTITYNUM_NONE) - { - //if standing on the world the bot should be in a valid area - if (bsptrace.ent == ENTITYNUM_WORLD) - { - return BotFuzzyPointReachabilityArea(origin); - } //end if - - modelnum = AAS_EntityModelindex(bsptrace.ent); - modeltype = modeltypes[modelnum]; - - //if standing on a func_plat or func_bobbing then the bot is assumed to be - //in the area the reachability points to - if (modeltype == MODELTYPE_FUNC_PLAT || modeltype == MODELTYPE_FUNC_BOB) - { - reachnum = AAS_NextModelReachability(0, modelnum); - if (reachnum) - { - AAS_ReachabilityFromNum(reachnum, &reach); - return reach.areanum; - } //end if - } //end else if - - //if the bot is swimming the bot should be in a valid area - if (AAS_Swimming(origin)) - { - return BotFuzzyPointReachabilityArea(origin); - } //end if - // - areanum = BotFuzzyPointReachabilityArea(origin); - //if the bot is in an area with reachabilities - if (areanum && AAS_AreaReachability(areanum)) return areanum; - //trace down till the ground is hit because the bot is standing on some other entity - VectorCopy(origin, org); - VectorCopy(org, end); - end[2] -= 800; - trace = AAS_TraceClientBBox(org, end, PRESENCE_CROUCH, -1); - if (!trace.startsolid) - { - VectorCopy(trace.endpos, org); - } //end if - // - return BotFuzzyPointReachabilityArea(org); - } //end if - // - return BotFuzzyPointReachabilityArea(origin); -} //end of the function BotReachabilityArea -//=========================================================================== -// returns the reachability area the bot is in -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -/* -int BotReachabilityArea(vec3_t origin, int testground) -{ - int firstareanum, i, j, x, y, z; - int areas[10], numareas, areanum, bestareanum; - float dist, bestdist; - vec3_t org, end, points[10], v; - aas_trace_t trace; - - firstareanum = 0; - for (i = 0; i < 2; i++) - { - VectorCopy(origin, org); - //if test at the ground (used when bot is standing on an entity) - if (i > 0) - { - VectorCopy(origin, end); - end[2] -= 800; - trace = AAS_TraceClientBBox(origin, end, PRESENCE_CROUCH, -1); - if (!trace.startsolid) - { - VectorCopy(trace.endpos, org); - } //end if - } //end if - - firstareanum = 0; - areanum = AAS_PointAreaNum(org); - if (areanum) - { - firstareanum = areanum; - if (AAS_AreaReachability(areanum)) return areanum; - } //end if - bestdist = 999999; - bestareanum = 0; - for (z = 1; z >= -1; z -= 1) - { - for (x = 1; x >= -1; x -= 1) - { - for (y = 1; y >= -1; y -= 1) - { - VectorCopy(org, end); - end[0] += x * 8; - end[1] += y * 8; - end[2] += z * 12; - numareas = AAS_TraceAreas(org, end, areas, points, 10); - for (j = 0; j < numareas; j++) - { - if (AAS_AreaReachability(areas[j])) - { - VectorSubtract(points[j], org, v); - dist = VectorLength(v); - if (dist < bestdist) - { - bestareanum = areas[j]; - bestdist = dist; - } //end if - } //end if - } //end for - } //end for - } //end for - if (bestareanum) return bestareanum; - } //end for - if (!testground) break; - } //end for -//#ifdef DEBUG - //botimport.Print(PRT_MESSAGE, "no reachability area\n"); -//#endif //DEBUG - return firstareanum; -} //end of the function BotReachabilityArea*/ -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotOnMover(vec3_t origin, int entnum, aas_reachability_t *reach) -{ - int i, modelnum; - vec3_t mins, maxs, modelorigin, org, end; - vec3_t angles = {0, 0, 0}; - vec3_t boxmins = {-16, -16, -8}, boxmaxs = {16, 16, 8}; - bsp_trace_t trace; - - modelnum = reach->facenum & 0x0000FFFF; - //get some bsp model info - AAS_BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, NULL); - // - if (!AAS_OriginOfMoverWithModelNum(modelnum, modelorigin)) - { - botimport.Print(PRT_MESSAGE, "no entity with model %d\n", modelnum); - return qfalse; - } //end if - // - for (i = 0; i < 2; i++) - { - if (origin[i] > modelorigin[i] + maxs[i] + 16) return qfalse; - if (origin[i] < modelorigin[i] + mins[i] - 16) return qfalse; - } //end for - // - VectorCopy(origin, org); - org[2] += 24; - VectorCopy(origin, end); - end[2] -= 48; - // - trace = AAS_Trace(org, boxmins, boxmaxs, end, entnum, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); - if (!trace.startsolid && !trace.allsolid) - { - //NOTE: the reachability face number is the model number of the elevator - if (trace.ent != ENTITYNUM_NONE && AAS_EntityModelNum(trace.ent) == modelnum) - { - return qtrue; - } //end if - } //end if - return qfalse; -} //end of the function BotOnMover -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int MoverDown(aas_reachability_t *reach) -{ - int modelnum; - vec3_t mins, maxs, origin; - vec3_t angles = {0, 0, 0}; - - modelnum = reach->facenum & 0x0000FFFF; - //get some bsp model info - AAS_BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, origin); - // - if (!AAS_OriginOfMoverWithModelNum(modelnum, origin)) - { - botimport.Print(PRT_MESSAGE, "no entity with model %d\n", modelnum); - return qfalse; - } //end if - //if the top of the plat is below the reachability start point - if (origin[2] + maxs[2] < reach->start[2]) return qtrue; - return qfalse; -} //end of the function MoverDown -//======================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//======================================================================== -void BotSetBrushModelTypes(void) -{ - int ent, modelnum; - char classname[MAX_EPAIRKEY], model[MAX_EPAIRKEY]; - - Com_Memset(modeltypes, 0, MAX_MODELS * sizeof(int)); - // - for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) - { - if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; - if (!AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY)) continue; - if (model[0]) modelnum = atoi(model+1); - else modelnum = 0; - - if (modelnum < 0 || modelnum > MAX_MODELS) - { - botimport.Print(PRT_MESSAGE, "entity %s model number out of range\n", classname); - continue; - } //end if - - if (!Q_stricmp(classname, "func_bobbing")) - modeltypes[modelnum] = MODELTYPE_FUNC_BOB; - else if (!Q_stricmp(classname, "func_plat")) - modeltypes[modelnum] = MODELTYPE_FUNC_PLAT; - else if (!Q_stricmp(classname, "func_door")) - modeltypes[modelnum] = MODELTYPE_FUNC_DOOR; - else if (!Q_stricmp(classname, "func_static")) - modeltypes[modelnum] = MODELTYPE_FUNC_STATIC; - } //end for -} //end of the function BotSetBrushModelTypes -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotOnTopOfEntity(bot_movestate_t *ms) -{ - vec3_t mins, maxs, end, up = {0, 0, 1}; - bsp_trace_t trace; - - AAS_PresenceTypeBoundingBox(ms->presencetype, mins, maxs); - VectorMA(ms->origin, -3, up, end); - trace = AAS_Trace(ms->origin, mins, maxs, end, ms->entitynum, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); - if (!trace.startsolid && (trace.ent != ENTITYNUM_WORLD && trace.ent != ENTITYNUM_NONE) ) - { - return trace.ent; - } //end if - return -1; -} //end of the function BotOnTopOfEntity -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotValidTravel(vec3_t origin, aas_reachability_t *reach, int travelflags) -{ - //if the reachability uses an unwanted travel type - if (AAS_TravelFlagForType(reach->traveltype) & ~travelflags) return qfalse; - //don't go into areas with bad travel types - if (AAS_AreaContentsTravelFlags(reach->areanum) & ~travelflags) return qfalse; - return qtrue; -} //end of the function BotValidTravel -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotAddToAvoidReach(bot_movestate_t *ms, int number, float avoidtime) -{ - int i; - - for (i = 0; i < MAX_AVOIDREACH; i++) - { - if (ms->avoidreach[i] == number) - { - if (ms->avoidreachtimes[i] > AAS_Time()) ms->avoidreachtries[i]++; - else ms->avoidreachtries[i] = 1; - ms->avoidreachtimes[i] = AAS_Time() + avoidtime; - return; - } //end if - } //end for - //add the reachability to the reachabilities to avoid for a while - for (i = 0; i < MAX_AVOIDREACH; i++) - { - if (ms->avoidreachtimes[i] < AAS_Time()) - { - ms->avoidreach[i] = number; - ms->avoidreachtimes[i] = AAS_Time() + avoidtime; - ms->avoidreachtries[i] = 1; - return; - } //end if - } //end for -} //end of the function BotAddToAvoidReach -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -float DistanceFromLineSquared(vec3_t p, vec3_t lp1, vec3_t lp2) -{ - vec3_t proj, dir; - int j; - - AAS_ProjectPointOntoVector(p, lp1, lp2, proj); - for (j = 0; j < 3; j++) - if ((proj[j] > lp1[j] && proj[j] > lp2[j]) || - (proj[j] < lp1[j] && proj[j] < lp2[j])) - break; - if (j < 3) { - if (fabs(proj[j] - lp1[j]) < fabs(proj[j] - lp2[j])) - VectorSubtract(p, lp1, dir); - else - VectorSubtract(p, lp2, dir); - return VectorLengthSquared(dir); - } - VectorSubtract(p, proj, dir); - return VectorLengthSquared(dir); -} //end of the function DistanceFromLineSquared -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -float VectorDistanceSquared(vec3_t p1, vec3_t p2) -{ - vec3_t dir; - VectorSubtract(p2, p1, dir); - return VectorLengthSquared(dir); -} //end of the function VectorDistanceSquared -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotAvoidSpots(vec3_t origin, aas_reachability_t *reach, bot_avoidspot_t *avoidspots, int numavoidspots) -{ - int checkbetween, i, type; - float squareddist, squaredradius; - - switch(reach->traveltype & TRAVELTYPE_MASK) - { - case TRAVEL_WALK: checkbetween = qtrue; break; - case TRAVEL_CROUCH: checkbetween = qtrue; break; - case TRAVEL_BARRIERJUMP: checkbetween = qtrue; break; - case TRAVEL_LADDER: checkbetween = qtrue; break; - case TRAVEL_WALKOFFLEDGE: checkbetween = qfalse; break; - case TRAVEL_JUMP: checkbetween = qfalse; break; - case TRAVEL_SWIM: checkbetween = qtrue; break; - case TRAVEL_WATERJUMP: checkbetween = qtrue; break; - case TRAVEL_TELEPORT: checkbetween = qfalse; break; - case TRAVEL_ELEVATOR: checkbetween = qfalse; break; - case TRAVEL_GRAPPLEHOOK: checkbetween = qfalse; break; - case TRAVEL_ROCKETJUMP: checkbetween = qfalse; break; - case TRAVEL_BFGJUMP: checkbetween = qfalse; break; - case TRAVEL_JUMPPAD: checkbetween = qfalse; break; - case TRAVEL_FUNCBOB: checkbetween = qfalse; break; - default: checkbetween = qtrue; break; - } //end switch - - type = AVOID_CLEAR; - for (i = 0; i < numavoidspots; i++) - { - squaredradius = Square(avoidspots[i].radius); - squareddist = DistanceFromLineSquared(avoidspots[i].origin, origin, reach->start); - // if moving towards the avoid spot - if (squareddist < squaredradius && - VectorDistanceSquared(avoidspots[i].origin, origin) > squareddist) - { - type = avoidspots[i].type; - } //end if - else if (checkbetween) { - squareddist = DistanceFromLineSquared(avoidspots[i].origin, reach->start, reach->end); - // if moving towards the avoid spot - if (squareddist < squaredradius && - VectorDistanceSquared(avoidspots[i].origin, reach->start) > squareddist) - { - type = avoidspots[i].type; - } //end if - } //end if - else - { - VectorDistanceSquared(avoidspots[i].origin, reach->end); - // if the reachability leads closer to the avoid spot - if (squareddist < squaredradius && - VectorDistanceSquared(avoidspots[i].origin, reach->start) > squareddist) - { - type = avoidspots[i].type; - } //end if - } //end else - if (type == AVOID_ALWAYS) - return type; - } //end for - return type; -} //end of the function BotAvoidSpots -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotAddAvoidSpot(int movestate, vec3_t origin, float radius, int type) -{ - bot_movestate_t *ms; - - ms = BotMoveStateFromHandle(movestate); - if (!ms) return; - if (type == AVOID_CLEAR) - { - ms->numavoidspots = 0; - return; - } //end if - - if (ms->numavoidspots >= MAX_AVOIDSPOTS) - return; - VectorCopy(origin, ms->avoidspots[ms->numavoidspots].origin); - ms->avoidspots[ms->numavoidspots].radius = radius; - ms->avoidspots[ms->numavoidspots].type = type; - ms->numavoidspots++; -} //end of the function BotAddAvoidSpot -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotGetReachabilityToGoal(vec3_t origin, int areanum, - int lastgoalareanum, int lastareanum, - int *avoidreach, float *avoidreachtimes, int *avoidreachtries, - bot_goal_t *goal, int travelflags, int movetravelflags, - struct bot_avoidspot_s *avoidspots, int numavoidspots, int *flags) -{ - int i, t, besttime, bestreachnum, reachnum; - aas_reachability_t reach; - - //if not in a valid area - if (!areanum) return 0; - // - if (AAS_AreaDoNotEnter(areanum) || AAS_AreaDoNotEnter(goal->areanum)) - { - travelflags |= TFL_DONOTENTER; - movetravelflags |= TFL_DONOTENTER; - } //end if - //use the routing to find the next area to go to - besttime = 0; - bestreachnum = 0; - // - for (reachnum = AAS_NextAreaReachability(areanum, 0); reachnum; - reachnum = AAS_NextAreaReachability(areanum, reachnum)) - { -#ifdef AVOIDREACH - //check if it isn't an reachability to avoid - for (i = 0; i < MAX_AVOIDREACH; i++) - { - if (avoidreach[i] == reachnum && avoidreachtimes[i] >= AAS_Time()) break; - } //end for - if (i != MAX_AVOIDREACH && avoidreachtries[i] > AVOIDREACH_TRIES) - { -#ifdef DEBUG - if (bot_developer) - { - botimport.Print(PRT_MESSAGE, "avoiding reachability %d\n", avoidreach[i]); - } //end if -#endif //DEBUG - continue; - } //end if -#endif //AVOIDREACH - //get the reachability from the number - AAS_ReachabilityFromNum(reachnum, &reach); - //NOTE: do not go back to the previous area if the goal didn't change - //NOTE: is this actually avoidance of local routing minima between two areas??? - if (lastgoalareanum == goal->areanum && reach.areanum == lastareanum) continue; - //if (AAS_AreaContentsTravelFlags(reach.areanum) & ~travelflags) continue; - //if the travel isn't valid - if (!BotValidTravel(origin, &reach, movetravelflags)) continue; - //get the travel time - t = AAS_AreaTravelTimeToGoalArea(reach.areanum, reach.end, goal->areanum, travelflags); - //if the goal area isn't reachable from the reachable area - if (!t) continue; - //if the bot should not use this reachability to avoid bad spots - if (BotAvoidSpots(origin, &reach, avoidspots, numavoidspots)) { - if (flags) { - *flags |= MOVERESULT_BLOCKEDBYAVOIDSPOT; - } - continue; - } - //add the travel time towards the area - t += reach.traveltime;// + AAS_AreaTravelTime(areanum, origin, reach.start); - //if the travel time is better than the ones already found - if (!besttime || t < besttime) - { - besttime = t; - bestreachnum = reachnum; - } //end if - } //end for - // - return bestreachnum; -} //end of the function BotGetReachabilityToGoal -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotAddToTarget(vec3_t start, vec3_t end, float maxdist, float *dist, vec3_t target) -{ - vec3_t dir; - float curdist; - - VectorSubtract(end, start, dir); - curdist = VectorNormalize(dir); - if (*dist + curdist < maxdist) - { - VectorCopy(end, target); - *dist += curdist; - return qfalse; - } //end if - else - { - VectorMA(start, maxdist - *dist, dir, target); - *dist = maxdist; - return qtrue; - } //end else -} //end of the function BotAddToTarget - -int BotMovementViewTarget(int movestate, bot_goal_t *goal, int travelflags, float lookahead, vec3_t target) -{ - aas_reachability_t reach; - int reachnum, lastareanum; - bot_movestate_t *ms; - vec3_t end; - float dist; - - ms = BotMoveStateFromHandle(movestate); - if (!ms) return qfalse; - reachnum = 0; - //if the bot has no goal or no last reachability - if (!ms->lastreachnum || !goal) return qfalse; - - reachnum = ms->lastreachnum; - VectorCopy(ms->origin, end); - lastareanum = ms->lastareanum; - dist = 0; - while(reachnum && dist < lookahead) - { - AAS_ReachabilityFromNum(reachnum, &reach); - if (BotAddToTarget(end, reach.start, lookahead, &dist, target)) return qtrue; - //never look beyond teleporters - if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_TELEPORT) return qtrue; - //never look beyond the weapon jump point - if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_ROCKETJUMP) return qtrue; - if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_BFGJUMP) return qtrue; - //don't add jump pad distances - if ((reach.traveltype & TRAVELTYPE_MASK) != TRAVEL_JUMPPAD && - (reach.traveltype & TRAVELTYPE_MASK) != TRAVEL_ELEVATOR && - (reach.traveltype & TRAVELTYPE_MASK) != TRAVEL_FUNCBOB) - { - if (BotAddToTarget(reach.start, reach.end, lookahead, &dist, target)) return qtrue; - } //end if - reachnum = BotGetReachabilityToGoal(reach.end, reach.areanum, - ms->lastgoalareanum, lastareanum, - ms->avoidreach, ms->avoidreachtimes, ms->avoidreachtries, - goal, travelflags, travelflags, NULL, 0, NULL); - VectorCopy(reach.end, end); - lastareanum = reach.areanum; - if (lastareanum == goal->areanum) - { - BotAddToTarget(reach.end, goal->origin, lookahead, &dist, target); - return qtrue; - } //end if - } //end while - // - return qfalse; -} //end of the function BotMovementViewTarget -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotVisible(int ent, vec3_t eye, vec3_t target) -{ - bsp_trace_t trace; - - trace = AAS_Trace(eye, NULL, NULL, target, ent, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); - if (trace.fraction >= 1) return qtrue; - return qfalse; -} //end of the function BotVisible -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotPredictVisiblePosition(vec3_t origin, int areanum, bot_goal_t *goal, int travelflags, vec3_t target) -{ - aas_reachability_t reach; - int reachnum, lastgoalareanum, lastareanum, i; - int avoidreach[MAX_AVOIDREACH]; - float avoidreachtimes[MAX_AVOIDREACH]; - int avoidreachtries[MAX_AVOIDREACH]; - vec3_t end; - - //if the bot has no goal or no last reachability - if (!goal) return qfalse; - //if the areanum is not valid - if (!areanum) return qfalse; - //if the goal areanum is not valid - if (!goal->areanum) return qfalse; - - Com_Memset(avoidreach, 0, MAX_AVOIDREACH * sizeof(int)); - lastgoalareanum = goal->areanum; - lastareanum = areanum; - VectorCopy(origin, end); - //only do 20 hops - for (i = 0; i < 20 && (areanum != goal->areanum); i++) - { - // - reachnum = BotGetReachabilityToGoal(end, areanum, - lastgoalareanum, lastareanum, - avoidreach, avoidreachtimes, avoidreachtries, - goal, travelflags, travelflags, NULL, 0, NULL); - if (!reachnum) return qfalse; - AAS_ReachabilityFromNum(reachnum, &reach); - // - if (BotVisible(goal->entitynum, goal->origin, reach.start)) - { - VectorCopy(reach.start, target); - return qtrue; - } //end if - // - if (BotVisible(goal->entitynum, goal->origin, reach.end)) - { - VectorCopy(reach.end, target); - return qtrue; - } //end if - // - if (reach.areanum == goal->areanum) - { - VectorCopy(reach.end, target); - return qtrue; - } //end if - // - lastareanum = areanum; - areanum = reach.areanum; - VectorCopy(reach.end, end); - // - } //end while - // - return qfalse; -} //end of the function BotPredictVisiblePosition -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void MoverBottomCenter(aas_reachability_t *reach, vec3_t bottomcenter) -{ - int modelnum; - vec3_t mins, maxs, origin, mids; - vec3_t angles = {0, 0, 0}; - - modelnum = reach->facenum & 0x0000FFFF; - //get some bsp model info - AAS_BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, origin); - // - if (!AAS_OriginOfMoverWithModelNum(modelnum, origin)) - { - botimport.Print(PRT_MESSAGE, "no entity with model %d\n", modelnum); - } //end if - //get a point just above the plat in the bottom position - VectorAdd(mins, maxs, mids); - VectorMA(origin, 0.5, mids, bottomcenter); - bottomcenter[2] = reach->start[2]; -} //end of the function MoverBottomCenter -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -float BotGapDistance(vec3_t origin, vec3_t hordir, int entnum) -{ - float dist, startz; - vec3_t start, end; - aas_trace_t trace; - - //do gap checking - startz = origin[2]; - //this enables walking down stairs more fluidly - { - VectorCopy(origin, start); - VectorCopy(origin, end); - end[2] -= 60; - trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, entnum); - if (trace.fraction >= 1) return 1; - startz = trace.endpos[2] + 1; - } - // - for (dist = 8; dist <= 100; dist += 8) - { - VectorMA(origin, dist, hordir, start); - start[2] = startz + 24; - VectorCopy(start, end); - end[2] -= 48 + sv_maxbarrier->value; - trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, entnum); - //if solid is found the bot can't walk any further and fall into a gap - if (!trace.startsolid) - { - //if it is a gap - if (trace.endpos[2] < startz - sv_maxstep->value - 8) - { - VectorCopy(trace.endpos, end); - end[2] -= 20; - if (AAS_PointContents(end) & CONTENTS_WATER) break; - //if a gap is found slow down - //botimport.Print(PRT_MESSAGE, "gap at %f\n", dist); - return dist; - } //end if - startz = trace.endpos[2]; - } //end if - } //end for - return 0; -} //end of the function BotGapDistance -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotCheckBarrierJump(bot_movestate_t *ms, vec3_t dir, float speed) -{ - vec3_t start, hordir, end; - aas_trace_t trace; - - VectorCopy(ms->origin, end); - end[2] += sv_maxbarrier->value; - //trace right up - trace = AAS_TraceClientBBox(ms->origin, end, PRESENCE_NORMAL, ms->entitynum); - //this shouldn't happen... but we check anyway - if (trace.startsolid) return qfalse; - //if very low ceiling it isn't possible to jump up to a barrier - if (trace.endpos[2] - ms->origin[2] < sv_maxstep->value) return qfalse; - // - hordir[0] = dir[0]; - hordir[1] = dir[1]; - hordir[2] = 0; - VectorNormalize(hordir); - VectorMA(ms->origin, ms->thinktime * speed * 0.5, hordir, end); - VectorCopy(trace.endpos, start); - end[2] = trace.endpos[2]; - //trace from previous trace end pos horizontally in the move direction - trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, ms->entitynum); - //again this shouldn't happen - if (trace.startsolid) return qfalse; - // - VectorCopy(trace.endpos, start); - VectorCopy(trace.endpos, end); - end[2] = ms->origin[2]; - //trace down from the previous trace end pos - trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, ms->entitynum); - //if solid - if (trace.startsolid) return qfalse; - //if no obstacle at all - if (trace.fraction >= 1.0) return qfalse; - //if less than the maximum step height - if (trace.endpos[2] - ms->origin[2] < sv_maxstep->value) return qfalse; - // - EA_Jump(ms->client); - EA_Move(ms->client, hordir, speed); - ms->moveflags |= MFL_BARRIERJUMP; - //there is a barrier - return qtrue; -} //end of the function BotCheckBarrierJump -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotSwimInDirection(bot_movestate_t *ms, vec3_t dir, float speed, int type) -{ - vec3_t normdir; - - VectorCopy(dir, normdir); - VectorNormalize(normdir); - EA_Move(ms->client, normdir, speed); - return qtrue; -} //end of the function BotSwimInDirection -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotWalkInDirection(bot_movestate_t *ms, vec3_t dir, float speed, int type) -{ - vec3_t hordir, cmdmove, velocity, tmpdir, origin; - int presencetype, maxframes, cmdframes, stopevent; - aas_clientmove_t move; - float dist; - - if (AAS_OnGround(ms->origin, ms->presencetype, ms->entitynum)) ms->moveflags |= MFL_ONGROUND; - //if the bot is on the ground - if (ms->moveflags & MFL_ONGROUND) - { - //if there is a barrier the bot can jump on - if (BotCheckBarrierJump(ms, dir, speed)) return qtrue; - //remove barrier jump flag - ms->moveflags &= ~MFL_BARRIERJUMP; - //get the presence type for the movement - if ((type & MOVE_CROUCH) && !(type & MOVE_JUMP)) presencetype = PRESENCE_CROUCH; - else presencetype = PRESENCE_NORMAL; - //horizontal direction - hordir[0] = dir[0]; - hordir[1] = dir[1]; - hordir[2] = 0; - VectorNormalize(hordir); - //if the bot is not supposed to jump - if (!(type & MOVE_JUMP)) - { - //if there is a gap, try to jump over it - if (BotGapDistance(ms->origin, hordir, ms->entitynum) > 0) type |= MOVE_JUMP; - } //end if - //get command movement - VectorScale(hordir, speed, cmdmove); - VectorCopy(ms->velocity, velocity); - // - if (type & MOVE_JUMP) - { - //botimport.Print(PRT_MESSAGE, "trying jump\n"); - cmdmove[2] = 400; - maxframes = PREDICTIONTIME_JUMP / 0.1; - cmdframes = 1; - stopevent = SE_HITGROUND|SE_HITGROUNDDAMAGE| - SE_ENTERWATER|SE_ENTERSLIME|SE_ENTERLAVA; - } //end if - else - { - maxframes = 2; - cmdframes = 2; - stopevent = SE_HITGROUNDDAMAGE| - SE_ENTERWATER|SE_ENTERSLIME|SE_ENTERLAVA; - } //end else - //AAS_ClearShownDebugLines(); - // - VectorCopy(ms->origin, origin); - origin[2] += 0.5; - AAS_PredictClientMovement(&move, ms->entitynum, origin, presencetype, qtrue, - velocity, cmdmove, cmdframes, maxframes, 0.1f, - stopevent, 0, qfalse);//qtrue); - //if prediction time wasn't enough to fully predict the movement - if (move.frames >= maxframes && (type & MOVE_JUMP)) - { - //botimport.Print(PRT_MESSAGE, "client %d: max prediction frames\n", ms->client); - return qfalse; - } //end if - //don't enter slime or lava and don't fall from too high - if (move.stopevent & (SE_ENTERSLIME|SE_ENTERLAVA|SE_HITGROUNDDAMAGE)) - { - //botimport.Print(PRT_MESSAGE, "client %d: would be hurt ", ms->client); - //if (move.stopevent & SE_ENTERSLIME) botimport.Print(PRT_MESSAGE, "slime\n"); - //if (move.stopevent & SE_ENTERLAVA) botimport.Print(PRT_MESSAGE, "lava\n"); - //if (move.stopevent & SE_HITGROUNDDAMAGE) botimport.Print(PRT_MESSAGE, "hitground\n"); - return qfalse; - } //end if - //if ground was hit - if (move.stopevent & SE_HITGROUND) - { - //check for nearby gap - VectorNormalize2(move.velocity, tmpdir); - dist = BotGapDistance(move.endpos, tmpdir, ms->entitynum); - if (dist > 0) return qfalse; - // - dist = BotGapDistance(move.endpos, hordir, ms->entitynum); - if (dist > 0) return qfalse; - } //end if - //get horizontal movement - tmpdir[0] = move.endpos[0] - ms->origin[0]; - tmpdir[1] = move.endpos[1] - ms->origin[1]; - tmpdir[2] = 0; - // - //AAS_DrawCross(move.endpos, 4, LINECOLOR_BLUE); - //the bot is blocked by something - if (VectorLength(tmpdir) < speed * ms->thinktime * 0.5) return qfalse; - //perform the movement - if (type & MOVE_JUMP) EA_Jump(ms->client); - if (type & MOVE_CROUCH) EA_Crouch(ms->client); - EA_Move(ms->client, hordir, speed); - //movement was succesfull - return qtrue; - } //end if - else - { - if (ms->moveflags & MFL_BARRIERJUMP) - { - //if near the top or going down - if (ms->velocity[2] < 50) - { - EA_Move(ms->client, dir, speed); - } //end if - } //end if - //FIXME: do air control to avoid hazards - return qtrue; - } //end else -} //end of the function BotWalkInDirection -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotMoveInDirection(int movestate, vec3_t dir, float speed, int type) -{ - bot_movestate_t *ms; - - ms = BotMoveStateFromHandle(movestate); - if (!ms) return qfalse; - //if swimming - if (AAS_Swimming(ms->origin)) - { - return BotSwimInDirection(ms, dir, speed, type); - } //end if - else - { - return BotWalkInDirection(ms, dir, speed, type); - } //end else -} //end of the function BotMoveInDirection -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int Intersection(vec2_t p1, vec2_t p2, vec2_t p3, vec2_t p4, vec2_t out) -{ - float x1, dx1, dy1, x2, dx2, dy2, d; - - dx1 = p2[0] - p1[0]; - dy1 = p2[1] - p1[1]; - dx2 = p4[0] - p3[0]; - dy2 = p4[1] - p3[1]; - - d = dy1 * dx2 - dx1 * dy2; - if (d != 0) - { - x1 = p1[1] * dx1 - p1[0] * dy1; - x2 = p3[1] * dx2 - p3[0] * dy2; - out[0] = (int) ((dx1 * x2 - dx2 * x1) / d); - out[1] = (int) ((dy1 * x2 - dy2 * x1) / d); - return qtrue; - } //end if - else - { - return qfalse; - } //end else -} //end of the function Intersection -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotCheckBlocked(bot_movestate_t *ms, vec3_t dir, int checkbottom, bot_moveresult_t *result) -{ - vec3_t mins, maxs, end, up = {0, 0, 1}; - bsp_trace_t trace; - - //test for entities obstructing the bot's path - AAS_PresenceTypeBoundingBox(ms->presencetype, mins, maxs); - // - if (fabs(DotProduct(dir, up)) < 0.7) - { - mins[2] += sv_maxstep->value; //if the bot can step on - maxs[2] -= 10; //a little lower to avoid low ceiling - } //end if - VectorMA(ms->origin, 3, dir, end); - trace = AAS_Trace(ms->origin, mins, maxs, end, ms->entitynum, CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_BODY); - //if not started in solid and not hitting the world entity - if (!trace.startsolid && (trace.ent != ENTITYNUM_WORLD && trace.ent != ENTITYNUM_NONE) ) - { - result->blocked = qtrue; - result->blockentity = trace.ent; -#ifdef DEBUG - //botimport.Print(PRT_MESSAGE, "%d: BotCheckBlocked: I'm blocked\n", ms->client); -#endif //DEBUG - } //end if - //if not in an area with reachability - else if (checkbottom && !AAS_AreaReachability(ms->areanum)) - { - //check if the bot is standing on something - AAS_PresenceTypeBoundingBox(ms->presencetype, mins, maxs); - VectorMA(ms->origin, -3, up, end); - trace = AAS_Trace(ms->origin, mins, maxs, end, ms->entitynum, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); - if (!trace.startsolid && (trace.ent != ENTITYNUM_WORLD && trace.ent != ENTITYNUM_NONE) ) - { - result->blocked = qtrue; - result->blockentity = trace.ent; - result->flags |= MOVERESULT_ONTOPOFOBSTACLE; -#ifdef DEBUG - //botimport.Print(PRT_MESSAGE, "%d: BotCheckBlocked: I'm blocked\n", ms->client); -#endif //DEBUG - } //end if - } //end else -} //end of the function BotCheckBlocked -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotClearMoveResult(bot_moveresult_t *moveresult) -{ - moveresult->failure = qfalse; - moveresult->type = 0; - moveresult->blocked = qfalse; - moveresult->blockentity = 0; - moveresult->traveltype = 0; - moveresult->flags = 0; -} //end of the function BotClearMoveResult -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bot_moveresult_t BotTravel_Walk(bot_movestate_t *ms, aas_reachability_t *reach) -{ - float dist, speed; - vec3_t hordir; - bot_moveresult_t result; - - BotClearMoveResult(&result); - //first walk straight to the reachability start - hordir[0] = reach->start[0] - ms->origin[0]; - hordir[1] = reach->start[1] - ms->origin[1]; - hordir[2] = 0; - dist = VectorNormalize(hordir); - // - BotCheckBlocked(ms, hordir, qtrue, &result); - // - if (dist < 10) - { - //walk straight to the reachability end - hordir[0] = reach->end[0] - ms->origin[0]; - hordir[1] = reach->end[1] - ms->origin[1]; - hordir[2] = 0; - dist = VectorNormalize(hordir); - } //end if - //if going towards a crouch area - if (!(AAS_AreaPresenceType(reach->areanum) & PRESENCE_NORMAL)) - { - //if pretty close to the reachable area - if (dist < 20) EA_Crouch(ms->client); - } //end if - // - dist = BotGapDistance(ms->origin, hordir, ms->entitynum); - // - if (ms->moveflags & MFL_WALK) - { - if (dist > 0) speed = 200 - (180 - 1 * dist); - else speed = 200; - EA_Walk(ms->client); - } //end if - else - { - if (dist > 0) speed = 400 - (360 - 2 * dist); - else speed = 400; - } //end else - //elemantary action move in direction - EA_Move(ms->client, hordir, speed); - VectorCopy(hordir, result.movedir); - // - return result; -} //end of the function BotTravel_Walk -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bot_moveresult_t BotFinishTravel_Walk(bot_movestate_t *ms, aas_reachability_t *reach) -{ - vec3_t hordir; - float dist, speed; - bot_moveresult_t result; - - BotClearMoveResult(&result); - //if not on the ground and changed areas... don't walk back!! - //(doesn't seem to help) - /* - ms->areanum = BotFuzzyPointReachabilityArea(ms->origin); - if (ms->areanum == reach->areanum) - { -#ifdef DEBUG - botimport.Print(PRT_MESSAGE, "BotFinishTravel_Walk: already in reach area\n"); -#endif //DEBUG - return result; - } //end if*/ - //go straight to the reachability end - hordir[0] = reach->end[0] - ms->origin[0]; - hordir[1] = reach->end[1] - ms->origin[1]; - hordir[2] = 0; - dist = VectorNormalize(hordir); - // - if (dist > 100) dist = 100; - speed = 400 - (400 - 3 * dist); - // - EA_Move(ms->client, hordir, speed); - VectorCopy(hordir, result.movedir); - // - return result; -} //end of the function BotFinishTravel_Walk -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bot_moveresult_t BotTravel_Crouch(bot_movestate_t *ms, aas_reachability_t *reach) -{ - float speed; - vec3_t hordir; - bot_moveresult_t result; - - BotClearMoveResult(&result); - // - speed = 400; - //walk straight to reachability end - hordir[0] = reach->end[0] - ms->origin[0]; - hordir[1] = reach->end[1] - ms->origin[1]; - hordir[2] = 0; - VectorNormalize(hordir); - // - BotCheckBlocked(ms, hordir, qtrue, &result); - //elemantary actions - EA_Crouch(ms->client); - EA_Move(ms->client, hordir, speed); - // - VectorCopy(hordir, result.movedir); - // - return result; -} //end of the function BotTravel_Crouch -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bot_moveresult_t BotTravel_BarrierJump(bot_movestate_t *ms, aas_reachability_t *reach) -{ - float dist, speed; - vec3_t hordir; - bot_moveresult_t result; - - BotClearMoveResult(&result); - //walk straight to reachability start - hordir[0] = reach->start[0] - ms->origin[0]; - hordir[1] = reach->start[1] - ms->origin[1]; - hordir[2] = 0; - dist = VectorNormalize(hordir); - // - BotCheckBlocked(ms, hordir, qtrue, &result); - //if pretty close to the barrier - if (dist < 9) - { - EA_Jump(ms->client); - } //end if - else - { - if (dist > 60) dist = 60; - speed = 360 - (360 - 6 * dist); - EA_Move(ms->client, hordir, speed); - } //end else - VectorCopy(hordir, result.movedir); - // - return result; -} //end of the function BotTravel_BarrierJump -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bot_moveresult_t BotFinishTravel_BarrierJump(bot_movestate_t *ms, aas_reachability_t *reach) -{ - float dist; - vec3_t hordir; - bot_moveresult_t result; - - BotClearMoveResult(&result); - //if near the top or going down - if (ms->velocity[2] < 250) - { - hordir[0] = reach->end[0] - ms->origin[0]; - hordir[1] = reach->end[1] - ms->origin[1]; - hordir[2] = 0; - dist = VectorNormalize(hordir); - // - BotCheckBlocked(ms, hordir, qtrue, &result); - // - EA_Move(ms->client, hordir, 400); - VectorCopy(hordir, result.movedir); - } //end if - // - return result; -} //end of the function BotFinishTravel_BarrierJump -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bot_moveresult_t BotTravel_Swim(bot_movestate_t *ms, aas_reachability_t *reach) -{ - vec3_t dir; - bot_moveresult_t result; - - BotClearMoveResult(&result); - //swim straight to reachability end - VectorSubtract(reach->start, ms->origin, dir); - VectorNormalize(dir); - // - BotCheckBlocked(ms, dir, qtrue, &result); - //elemantary actions - EA_Move(ms->client, dir, 400); - // - VectorCopy(dir, result.movedir); - Vector2Angles(dir, result.ideal_viewangles); - result.flags |= MOVERESULT_SWIMVIEW; - // - return result; -} //end of the function BotTravel_Swim -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bot_moveresult_t BotTravel_WaterJump(bot_movestate_t *ms, aas_reachability_t *reach) -{ - vec3_t dir, hordir; - float dist; - bot_moveresult_t result; - - BotClearMoveResult(&result); - //swim straight to reachability end - VectorSubtract(reach->end, ms->origin, dir); - VectorCopy(dir, hordir); - hordir[2] = 0; - dir[2] += 15 + crandom() * 40; - //botimport.Print(PRT_MESSAGE, "BotTravel_WaterJump: dir[2] = %f\n", dir[2]); - VectorNormalize(dir); - dist = VectorNormalize(hordir); - //elemantary actions - //EA_Move(ms->client, dir, 400); - EA_MoveForward(ms->client); - //move up if close to the actual out of water jump spot - if (dist < 40) EA_MoveUp(ms->client); - //set the ideal view angles - Vector2Angles(dir, result.ideal_viewangles); - result.flags |= MOVERESULT_MOVEMENTVIEW; - // - VectorCopy(dir, result.movedir); - // - return result; -} //end of the function BotTravel_WaterJump -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bot_moveresult_t BotFinishTravel_WaterJump(bot_movestate_t *ms, aas_reachability_t *reach) -{ - vec3_t dir, pnt; - float dist; - bot_moveresult_t result; - - //botimport.Print(PRT_MESSAGE, "BotFinishTravel_WaterJump\n"); - BotClearMoveResult(&result); - //if waterjumping there's nothing to do - if (ms->moveflags & MFL_WATERJUMP) return result; - //if not touching any water anymore don't do anything - //otherwise the bot sometimes keeps jumping? - VectorCopy(ms->origin, pnt); - pnt[2] -= 32; //extra for q2dm4 near red armor/mega health - if (!(AAS_PointContents(pnt) & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER))) return result; - //swim straight to reachability end - VectorSubtract(reach->end, ms->origin, dir); - dir[0] += crandom() * 10; - dir[1] += crandom() * 10; - dir[2] += 70 + crandom() * 10; - dist = VectorNormalize(dir); - //elemantary actions - EA_Move(ms->client, dir, 400); - //set the ideal view angles - Vector2Angles(dir, result.ideal_viewangles); - result.flags |= MOVERESULT_MOVEMENTVIEW; - // - VectorCopy(dir, result.movedir); - // - return result; -} //end of the function BotFinishTravel_WaterJump -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bot_moveresult_t BotTravel_WalkOffLedge(bot_movestate_t *ms, aas_reachability_t *reach) -{ - vec3_t hordir, dir; - float dist, speed, reachhordist; - bot_moveresult_t result; - - BotClearMoveResult(&result); - //check if the bot is blocked by anything - VectorSubtract(reach->start, ms->origin, dir); - VectorNormalize(dir); - BotCheckBlocked(ms, dir, qtrue, &result); - //if the reachability start and end are practially above each other - VectorSubtract(reach->end, reach->start, dir); - dir[2] = 0; - reachhordist = VectorLength(dir); - //walk straight to the reachability start - hordir[0] = reach->start[0] - ms->origin[0]; - hordir[1] = reach->start[1] - ms->origin[1]; - hordir[2] = 0; - dist = VectorNormalize(hordir); - //if pretty close to the start focus on the reachability end - if (dist < 48) - { - hordir[0] = reach->end[0] - ms->origin[0]; - hordir[1] = reach->end[1] - ms->origin[1]; - hordir[2] = 0; - VectorNormalize(hordir); - // - if (reachhordist < 20) - { - speed = 100; - } //end if - else if (!AAS_HorizontalVelocityForJump(0, reach->start, reach->end, &speed)) - { - speed = 400; - } //end if - } //end if - else - { - if (reachhordist < 20) - { - if (dist > 64) dist = 64; - speed = 400 - (256 - 4 * dist); - } //end if - else - { - speed = 400; - } //end else - } //end else - // - BotCheckBlocked(ms, hordir, qtrue, &result); - //elemantary action - EA_Move(ms->client, hordir, speed); - VectorCopy(hordir, result.movedir); - // - return result; -} //end of the function BotTravel_WalkOffLedge -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotAirControl(vec3_t origin, vec3_t velocity, vec3_t goal, vec3_t dir, float *speed) -{ - vec3_t org, vel; - float dist; - int i; - - VectorCopy(origin, org); - VectorScale(velocity, 0.1, vel); - for (i = 0; i < 50; i++) - { - vel[2] -= sv_gravity->value * 0.01; - //if going down and next position would be below the goal - if (vel[2] < 0 && org[2] + vel[2] < goal[2]) - { - VectorScale(vel, (goal[2] - org[2]) / vel[2], vel); - VectorAdd(org, vel, org); - VectorSubtract(goal, org, dir); - dist = VectorNormalize(dir); - if (dist > 32) dist = 32; - *speed = 400 - (400 - 13 * dist); - return qtrue; - } //end if - else - { - VectorAdd(org, vel, org); - } //end else - } //end for - VectorSet(dir, 0, 0, 0); - *speed = 400; - return qfalse; -} //end of the function BotAirControl -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bot_moveresult_t BotFinishTravel_WalkOffLedge(bot_movestate_t *ms, aas_reachability_t *reach) -{ - vec3_t dir, hordir, end, v; - float dist, speed; - bot_moveresult_t result; - - BotClearMoveResult(&result); - // - VectorSubtract(reach->end, ms->origin, dir); - BotCheckBlocked(ms, dir, qtrue, &result); - // - VectorSubtract(reach->end, ms->origin, v); - v[2] = 0; - dist = VectorNormalize(v); - if (dist > 16) VectorMA(reach->end, 16, v, end); - else VectorCopy(reach->end, end); - // - if (!BotAirControl(ms->origin, ms->velocity, end, hordir, &speed)) - { - //go straight to the reachability end - VectorCopy(dir, hordir); - hordir[2] = 0; - // - dist = VectorNormalize(hordir); - speed = 400; - } //end if - // - EA_Move(ms->client, hordir, speed); - VectorCopy(hordir, result.movedir); - // - return result; -} //end of the function BotFinishTravel_WalkOffLedge -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -/* -bot_moveresult_t BotTravel_Jump(bot_movestate_t *ms, aas_reachability_t *reach) -{ - vec3_t hordir; - float dist, gapdist, speed, horspeed, sv_jumpvel; - bot_moveresult_t result; - - BotClearMoveResult(&result); - // - sv_jumpvel = botlibglobals.sv_jumpvel->value; - //walk straight to the reachability start - hordir[0] = reach->start[0] - ms->origin[0]; - hordir[1] = reach->start[1] - ms->origin[1]; - hordir[2] = 0; - dist = VectorNormalize(hordir); - // - speed = 350; - // - gapdist = BotGapDistance(ms, hordir, ms->entitynum); - //if pretty close to the start focus on the reachability end - if (dist < 50 || (gapdist && gapdist < 50)) - { - //NOTE: using max speed (400) works best - //if (AAS_HorizontalVelocityForJump(sv_jumpvel, ms->origin, reach->end, &horspeed)) - //{ - // speed = horspeed * 400 / botlibglobals.sv_maxwalkvelocity->value; - //} //end if - hordir[0] = reach->end[0] - ms->origin[0]; - hordir[1] = reach->end[1] - ms->origin[1]; - VectorNormalize(hordir); - //elemantary action jump - EA_Jump(ms->client); - // - ms->jumpreach = ms->lastreachnum; - speed = 600; - } //end if - else - { - if (AAS_HorizontalVelocityForJump(sv_jumpvel, reach->start, reach->end, &horspeed)) - { - speed = horspeed * 400 / botlibglobals.sv_maxwalkvelocity->value; - } //end if - } //end else - //elemantary action - EA_Move(ms->client, hordir, speed); - VectorCopy(hordir, result.movedir); - // - return result; -} //end of the function BotTravel_Jump*/ -/* -bot_moveresult_t BotTravel_Jump(bot_movestate_t *ms, aas_reachability_t *reach) -{ - vec3_t hordir, dir1, dir2, mins, maxs, start, end; - float dist1, dist2, speed; - bot_moveresult_t result; - bsp_trace_t trace; - - BotClearMoveResult(&result); - // - hordir[0] = reach->start[0] - reach->end[0]; - hordir[1] = reach->start[1] - reach->end[1]; - hordir[2] = 0; - VectorNormalize(hordir); - // - VectorCopy(reach->start, start); - start[2] += 1; - //minus back the bouding box size plus 16 - VectorMA(reach->start, 80, hordir, end); - // - AAS_PresenceTypeBoundingBox(PRESENCE_NORMAL, mins, maxs); - //check for solids - trace = AAS_Trace(start, mins, maxs, end, ms->entitynum, MASK_PLAYERSOLID); - if (trace.startsolid) VectorCopy(start, trace.endpos); - //check for a gap - for (dist1 = 0; dist1 < 80; dist1 += 10) - { - VectorMA(start, dist1+10, hordir, end); - end[2] += 1; - if (AAS_PointAreaNum(end) != ms->reachareanum) break; - } //end for - if (dist1 < 80) VectorMA(reach->start, dist1, hordir, trace.endpos); -// dist1 = BotGapDistance(start, hordir, ms->entitynum); -// if (dist1 && dist1 <= trace.fraction * 80) VectorMA(reach->start, dist1-20, hordir, trace.endpos); - // - VectorSubtract(ms->origin, reach->start, dir1); - dir1[2] = 0; - dist1 = VectorNormalize(dir1); - VectorSubtract(ms->origin, trace.endpos, dir2); - dir2[2] = 0; - dist2 = VectorNormalize(dir2); - //if just before the reachability start - if (DotProduct(dir1, dir2) < -0.8 || dist2 < 5) - { - //botimport.Print(PRT_MESSAGE, "between jump start and run to point\n"); - hordir[0] = reach->end[0] - ms->origin[0]; - hordir[1] = reach->end[1] - ms->origin[1]; - hordir[2] = 0; - VectorNormalize(hordir); - //elemantary action jump - if (dist1 < 24) EA_Jump(ms->client); - else if (dist1 < 32) EA_DelayedJump(ms->client); - EA_Move(ms->client, hordir, 600); - // - ms->jumpreach = ms->lastreachnum; - } //end if - else - { - //botimport.Print(PRT_MESSAGE, "going towards run to point\n"); - hordir[0] = trace.endpos[0] - ms->origin[0]; - hordir[1] = trace.endpos[1] - ms->origin[1]; - hordir[2] = 0; - VectorNormalize(hordir); - // - if (dist2 > 80) dist2 = 80; - speed = 400 - (400 - 5 * dist2); - EA_Move(ms->client, hordir, speed); - } //end else - VectorCopy(hordir, result.movedir); - // - return result; -} //end of the function BotTravel_Jump*/ -//* -bot_moveresult_t BotTravel_Jump(bot_movestate_t *ms, aas_reachability_t *reach) -{ - vec3_t hordir, dir1, dir2, start, end, runstart; -// vec3_t runstart, dir1, dir2, hordir; - float dist1, dist2, speed; - bot_moveresult_t result; - - BotClearMoveResult(&result); - // - AAS_JumpReachRunStart(reach, runstart); - //* - hordir[0] = runstart[0] - reach->start[0]; - hordir[1] = runstart[1] - reach->start[1]; - hordir[2] = 0; - VectorNormalize(hordir); - // - VectorCopy(reach->start, start); - start[2] += 1; - VectorMA(reach->start, 80, hordir, runstart); - //check for a gap - for (dist1 = 0; dist1 < 80; dist1 += 10) - { - VectorMA(start, dist1+10, hordir, end); - end[2] += 1; - if (AAS_PointAreaNum(end) != ms->reachareanum) break; - } //end for - if (dist1 < 80) VectorMA(reach->start, dist1, hordir, runstart); - // - VectorSubtract(ms->origin, reach->start, dir1); - dir1[2] = 0; - dist1 = VectorNormalize(dir1); - VectorSubtract(ms->origin, runstart, dir2); - dir2[2] = 0; - dist2 = VectorNormalize(dir2); - //if just before the reachability start - if (DotProduct(dir1, dir2) < -0.8 || dist2 < 5) - { -// botimport.Print(PRT_MESSAGE, "between jump start and run start point\n"); - hordir[0] = reach->end[0] - ms->origin[0]; - hordir[1] = reach->end[1] - ms->origin[1]; - hordir[2] = 0; - VectorNormalize(hordir); - //elemantary action jump - if (dist1 < 24) EA_Jump(ms->client); - else if (dist1 < 32) EA_DelayedJump(ms->client); - EA_Move(ms->client, hordir, 600); - // - ms->jumpreach = ms->lastreachnum; - } //end if - else - { -// botimport.Print(PRT_MESSAGE, "going towards run start point\n"); - hordir[0] = runstart[0] - ms->origin[0]; - hordir[1] = runstart[1] - ms->origin[1]; - hordir[2] = 0; - VectorNormalize(hordir); - // - if (dist2 > 80) dist2 = 80; - speed = 400 - (400 - 5 * dist2); - EA_Move(ms->client, hordir, speed); - } //end else - VectorCopy(hordir, result.movedir); - // - return result; -} //end of the function BotTravel_Jump*/ -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bot_moveresult_t BotFinishTravel_Jump(bot_movestate_t *ms, aas_reachability_t *reach) -{ - vec3_t hordir, hordir2; - float speed, dist; - bot_moveresult_t result; - - BotClearMoveResult(&result); - //if not jumped yet - if (!ms->jumpreach) return result; - //go straight to the reachability end - hordir[0] = reach->end[0] - ms->origin[0]; - hordir[1] = reach->end[1] - ms->origin[1]; - hordir[2] = 0; - dist = VectorNormalize(hordir); - // - hordir2[0] = reach->end[0] - reach->start[0]; - hordir2[1] = reach->end[1] - reach->start[1]; - hordir2[2] = 0; - VectorNormalize(hordir2); - // - if (DotProduct(hordir, hordir2) < -0.5 && dist < 24) return result; - //always use max speed when traveling through the air - speed = 800; - // - EA_Move(ms->client, hordir, speed); - VectorCopy(hordir, result.movedir); - // - return result; -} //end of the function BotFinishTravel_Jump -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bot_moveresult_t BotTravel_Ladder(bot_movestate_t *ms, aas_reachability_t *reach) -{ - //float dist, speed; - vec3_t dir, viewdir;//, hordir; - vec3_t origin = {0, 0, 0}; -// vec3_t up = {0, 0, 1}; - bot_moveresult_t result; - - BotClearMoveResult(&result); - // -// if ((ms->moveflags & MFL_AGAINSTLADDER)) - //NOTE: not a good idea for ladders starting in water - // || !(ms->moveflags & MFL_ONGROUND)) - { - //botimport.Print(PRT_MESSAGE, "against ladder or not on ground\n"); - VectorSubtract(reach->end, ms->origin, dir); - VectorNormalize(dir); - //set the ideal view angles, facing the ladder up or down - viewdir[0] = dir[0]; - viewdir[1] = dir[1]; - viewdir[2] = 3 * dir[2]; - Vector2Angles(viewdir, result.ideal_viewangles); - //elemantary action - EA_Move(ms->client, origin, 0); - EA_MoveForward(ms->client); - //set movement view flag so the AI can see the view is focussed - result.flags |= MOVERESULT_MOVEMENTVIEW; - } //end if -/* else - { - //botimport.Print(PRT_MESSAGE, "moving towards ladder\n"); - VectorSubtract(reach->end, ms->origin, dir); - //make sure the horizontal movement is large anough - VectorCopy(dir, hordir); - hordir[2] = 0; - dist = VectorNormalize(hordir); - // - dir[0] = hordir[0]; - dir[1] = hordir[1]; - if (dir[2] > 0) dir[2] = 1; - else dir[2] = -1; - if (dist > 50) dist = 50; - speed = 400 - (200 - 4 * dist); - EA_Move(ms->client, dir, speed); - } //end else*/ - //save the movement direction - VectorCopy(dir, result.movedir); - // - return result; -} //end of the function BotTravel_Ladder -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bot_moveresult_t BotTravel_Teleport(bot_movestate_t *ms, aas_reachability_t *reach) -{ - vec3_t hordir; - float dist; - bot_moveresult_t result; - - BotClearMoveResult(&result); - //if the bot is being teleported - if (ms->moveflags & MFL_TELEPORTED) return result; - - //walk straight to center of the teleporter - VectorSubtract(reach->start, ms->origin, hordir); - if (!(ms->moveflags & MFL_SWIMMING)) hordir[2] = 0; - dist = VectorNormalize(hordir); - // - BotCheckBlocked(ms, hordir, qtrue, &result); - - if (dist < 30) EA_Move(ms->client, hordir, 200); - else EA_Move(ms->client, hordir, 400); - - if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW; - - VectorCopy(hordir, result.movedir); - return result; -} //end of the function BotTravel_Teleport -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bot_moveresult_t BotTravel_Elevator(bot_movestate_t *ms, aas_reachability_t *reach) -{ - vec3_t dir, dir1, dir2, hordir, bottomcenter; - float dist, dist1, dist2, speed; - bot_moveresult_t result; - - BotClearMoveResult(&result); - //if standing on the plat - if (BotOnMover(ms->origin, ms->entitynum, reach)) - { -#ifdef DEBUG_ELEVATOR - botimport.Print(PRT_MESSAGE, "bot on elevator\n"); -#endif //DEBUG_ELEVATOR - //if vertically not too far from the end point - if (abs(ms->origin[2] - reach->end[2]) < sv_maxbarrier->value) - { -#ifdef DEBUG_ELEVATOR - botimport.Print(PRT_MESSAGE, "bot moving to end\n"); -#endif //DEBUG_ELEVATOR - //move to the end point - VectorSubtract(reach->end, ms->origin, hordir); - hordir[2] = 0; - VectorNormalize(hordir); - if (!BotCheckBarrierJump(ms, hordir, 100)) - { - EA_Move(ms->client, hordir, 400); - } //end if - VectorCopy(hordir, result.movedir); - } //end else - //if not really close to the center of the elevator - else - { - MoverBottomCenter(reach, bottomcenter); - VectorSubtract(bottomcenter, ms->origin, hordir); - hordir[2] = 0; - dist = VectorNormalize(hordir); - // - if (dist > 10) - { -#ifdef DEBUG_ELEVATOR - botimport.Print(PRT_MESSAGE, "bot moving to center\n"); -#endif //DEBUG_ELEVATOR - //move to the center of the plat - if (dist > 100) dist = 100; - speed = 400 - (400 - 4 * dist); - // - EA_Move(ms->client, hordir, speed); - VectorCopy(hordir, result.movedir); - } //end if - } //end else - } //end if - else - { -#ifdef DEBUG_ELEVATOR - botimport.Print(PRT_MESSAGE, "bot not on elevator\n"); -#endif //DEBUG_ELEVATOR - //if very near the reachability end - VectorSubtract(reach->end, ms->origin, dir); - dist = VectorLength(dir); - if (dist < 64) - { - if (dist > 60) dist = 60; - speed = 360 - (360 - 6 * dist); - // - if ((ms->moveflags & MFL_SWIMMING) || !BotCheckBarrierJump(ms, dir, 50)) - { - if (speed > 5) EA_Move(ms->client, dir, speed); - } //end if - VectorCopy(dir, result.movedir); - // - if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW; - //stop using this reachability - ms->reachability_time = 0; - return result; - } //end if - //get direction and distance to reachability start - VectorSubtract(reach->start, ms->origin, dir1); - if (!(ms->moveflags & MFL_SWIMMING)) dir1[2] = 0; - dist1 = VectorNormalize(dir1); - //if the elevator isn't down - if (!MoverDown(reach)) - { -#ifdef DEBUG_ELEVATOR - botimport.Print(PRT_MESSAGE, "elevator not down\n"); -#endif //DEBUG_ELEVATOR - dist = dist1; - VectorCopy(dir1, dir); - // - BotCheckBlocked(ms, dir, qfalse, &result); - // - if (dist > 60) dist = 60; - speed = 360 - (360 - 6 * dist); - // - if (!(ms->moveflags & MFL_SWIMMING) && !BotCheckBarrierJump(ms, dir, 50)) - { - if (speed > 5) EA_Move(ms->client, dir, speed); - } //end if - VectorCopy(dir, result.movedir); - // - if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW; - //this isn't a failure... just wait till the elevator comes down - result.type = RESULTTYPE_ELEVATORUP; - result.flags |= MOVERESULT_WAITING; - return result; - } //end if - //get direction and distance to elevator bottom center - MoverBottomCenter(reach, bottomcenter); - VectorSubtract(bottomcenter, ms->origin, dir2); - if (!(ms->moveflags & MFL_SWIMMING)) dir2[2] = 0; - dist2 = VectorNormalize(dir2); - //if very close to the reachability start or - //closer to the elevator center or - //between reachability start and elevator center - if (dist1 < 20 || dist2 < dist1 || DotProduct(dir1, dir2) < 0) - { -#ifdef DEBUG_ELEVATOR - botimport.Print(PRT_MESSAGE, "bot moving to center\n"); -#endif //DEBUG_ELEVATOR - dist = dist2; - VectorCopy(dir2, dir); - } //end if - else //closer to the reachability start - { -#ifdef DEBUG_ELEVATOR - botimport.Print(PRT_MESSAGE, "bot moving to start\n"); -#endif //DEBUG_ELEVATOR - dist = dist1; - VectorCopy(dir1, dir); - } //end else - // - BotCheckBlocked(ms, dir, qfalse, &result); - // - if (dist > 60) dist = 60; - speed = 400 - (400 - 6 * dist); - // - if (!(ms->moveflags & MFL_SWIMMING) && !BotCheckBarrierJump(ms, dir, 50)) - { - EA_Move(ms->client, dir, speed); - } //end if - VectorCopy(dir, result.movedir); - // - if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW; - } //end else - return result; -} //end of the function BotTravel_Elevator -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bot_moveresult_t BotFinishTravel_Elevator(bot_movestate_t *ms, aas_reachability_t *reach) -{ - vec3_t bottomcenter, bottomdir, topdir; - bot_moveresult_t result; - - BotClearMoveResult(&result); - // - MoverBottomCenter(reach, bottomcenter); - VectorSubtract(bottomcenter, ms->origin, bottomdir); - // - VectorSubtract(reach->end, ms->origin, topdir); - // - if (fabs(bottomdir[2]) < fabs(topdir[2])) - { - VectorNormalize(bottomdir); - EA_Move(ms->client, bottomdir, 300); - } //end if - else - { - VectorNormalize(topdir); - EA_Move(ms->client, topdir, 300); - } //end else - return result; -} //end of the function BotFinishTravel_Elevator -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotFuncBobStartEnd(aas_reachability_t *reach, vec3_t start, vec3_t end, vec3_t origin) -{ - int spawnflags, modelnum; - vec3_t mins, maxs, mid, angles = {0, 0, 0}; - int num0, num1; - - modelnum = reach->facenum & 0x0000FFFF; - if (!AAS_OriginOfMoverWithModelNum(modelnum, origin)) - { - botimport.Print(PRT_MESSAGE, "BotFuncBobStartEnd: no entity with model %d\n", modelnum); - VectorSet(start, 0, 0, 0); - VectorSet(end, 0, 0, 0); - return; - } //end if - AAS_BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, NULL); - VectorAdd(mins, maxs, mid); - VectorScale(mid, 0.5, mid); - VectorCopy(mid, start); - VectorCopy(mid, end); - spawnflags = reach->facenum >> 16; - num0 = reach->edgenum >> 16; - if (num0 > 0x00007FFF) num0 |= 0xFFFF0000; - num1 = reach->edgenum & 0x0000FFFF; - if (num1 > 0x00007FFF) num1 |= 0xFFFF0000; - if (spawnflags & 1) - { - start[0] = num0; - end[0] = num1; - // - origin[0] += mid[0]; - origin[1] = mid[1]; - origin[2] = mid[2]; - } //end if - else if (spawnflags & 2) - { - start[1] = num0; - end[1] = num1; - // - origin[0] = mid[0]; - origin[1] += mid[1]; - origin[2] = mid[2]; - } //end else if - else - { - start[2] = num0; - end[2] = num1; - // - origin[0] = mid[0]; - origin[1] = mid[1]; - origin[2] += mid[2]; - } //end else -} //end of the function BotFuncBobStartEnd -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bot_moveresult_t BotTravel_FuncBobbing(bot_movestate_t *ms, aas_reachability_t *reach) -{ - vec3_t dir, dir1, dir2, hordir, bottomcenter, bob_start, bob_end, bob_origin; - float dist, dist1, dist2, speed; - bot_moveresult_t result; - - BotClearMoveResult(&result); - // - BotFuncBobStartEnd(reach, bob_start, bob_end, bob_origin); - //if standing ontop of the func_bobbing - if (BotOnMover(ms->origin, ms->entitynum, reach)) - { -#ifdef DEBUG_FUNCBOB - botimport.Print(PRT_MESSAGE, "bot on func_bobbing\n"); -#endif - //if near end point of reachability - VectorSubtract(bob_origin, bob_end, dir); - if (VectorLength(dir) < 24) - { -#ifdef DEBUG_FUNCBOB - botimport.Print(PRT_MESSAGE, "bot moving to reachability end\n"); -#endif - //move to the end point - VectorSubtract(reach->end, ms->origin, hordir); - hordir[2] = 0; - VectorNormalize(hordir); - if (!BotCheckBarrierJump(ms, hordir, 100)) - { - EA_Move(ms->client, hordir, 400); - } //end if - VectorCopy(hordir, result.movedir); - } //end else - //if not really close to the center of the elevator - else - { - MoverBottomCenter(reach, bottomcenter); - VectorSubtract(bottomcenter, ms->origin, hordir); - hordir[2] = 0; - dist = VectorNormalize(hordir); - // - if (dist > 10) - { -#ifdef DEBUG_FUNCBOB - botimport.Print(PRT_MESSAGE, "bot moving to func_bobbing center\n"); -#endif - //move to the center of the plat - if (dist > 100) dist = 100; - speed = 400 - (400 - 4 * dist); - // - EA_Move(ms->client, hordir, speed); - VectorCopy(hordir, result.movedir); - } //end if - } //end else - } //end if - else - { -#ifdef DEBUG_FUNCBOB - botimport.Print(PRT_MESSAGE, "bot not ontop of func_bobbing\n"); -#endif - //if very near the reachability end - VectorSubtract(reach->end, ms->origin, dir); - dist = VectorLength(dir); - if (dist < 64) - { -#ifdef DEBUG_FUNCBOB - botimport.Print(PRT_MESSAGE, "bot moving to end\n"); -#endif - if (dist > 60) dist = 60; - speed = 360 - (360 - 6 * dist); - //if swimming or no barrier jump - if ((ms->moveflags & MFL_SWIMMING) || !BotCheckBarrierJump(ms, dir, 50)) - { - if (speed > 5) EA_Move(ms->client, dir, speed); - } //end if - VectorCopy(dir, result.movedir); - // - if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW; - //stop using this reachability - ms->reachability_time = 0; - return result; - } //end if - //get direction and distance to reachability start - VectorSubtract(reach->start, ms->origin, dir1); - if (!(ms->moveflags & MFL_SWIMMING)) dir1[2] = 0; - dist1 = VectorNormalize(dir1); - //if func_bobbing is Not it's start position - VectorSubtract(bob_origin, bob_start, dir); - if (VectorLength(dir) > 16) - { -#ifdef DEBUG_FUNCBOB - botimport.Print(PRT_MESSAGE, "func_bobbing not at start\n"); -#endif - dist = dist1; - VectorCopy(dir1, dir); - // - BotCheckBlocked(ms, dir, qfalse, &result); - // - if (dist > 60) dist = 60; - speed = 360 - (360 - 6 * dist); - // - if (!(ms->moveflags & MFL_SWIMMING) && !BotCheckBarrierJump(ms, dir, 50)) - { - if (speed > 5) EA_Move(ms->client, dir, speed); - } //end if - VectorCopy(dir, result.movedir); - // - if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW; - //this isn't a failure... just wait till the func_bobbing arrives - result.type = RESULTTYPE_WAITFORFUNCBOBBING; - result.flags |= MOVERESULT_WAITING; - return result; - } //end if - //get direction and distance to func_bob bottom center - MoverBottomCenter(reach, bottomcenter); - VectorSubtract(bottomcenter, ms->origin, dir2); - if (!(ms->moveflags & MFL_SWIMMING)) dir2[2] = 0; - dist2 = VectorNormalize(dir2); - //if very close to the reachability start or - //closer to the elevator center or - //between reachability start and func_bobbing center - if (dist1 < 20 || dist2 < dist1 || DotProduct(dir1, dir2) < 0) - { -#ifdef DEBUG_FUNCBOB - botimport.Print(PRT_MESSAGE, "bot moving to func_bobbing center\n"); -#endif - dist = dist2; - VectorCopy(dir2, dir); - } //end if - else //closer to the reachability start - { -#ifdef DEBUG_FUNCBOB - botimport.Print(PRT_MESSAGE, "bot moving to reachability start\n"); -#endif - dist = dist1; - VectorCopy(dir1, dir); - } //end else - // - BotCheckBlocked(ms, dir, qfalse, &result); - // - if (dist > 60) dist = 60; - speed = 400 - (400 - 6 * dist); - // - if (!(ms->moveflags & MFL_SWIMMING) && !BotCheckBarrierJump(ms, dir, 50)) - { - EA_Move(ms->client, dir, speed); - } //end if - VectorCopy(dir, result.movedir); - // - if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW; - } //end else - return result; -} //end of the function BotTravel_FuncBobbing -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bot_moveresult_t BotFinishTravel_FuncBobbing(bot_movestate_t *ms, aas_reachability_t *reach) -{ - vec3_t bob_origin, bob_start, bob_end, dir, hordir, bottomcenter; - bot_moveresult_t result; - float dist, speed; - - BotClearMoveResult(&result); - // - BotFuncBobStartEnd(reach, bob_start, bob_end, bob_origin); - // - VectorSubtract(bob_origin, bob_end, dir); - dist = VectorLength(dir); - //if the func_bobbing is near the end - if (dist < 16) - { - VectorSubtract(reach->end, ms->origin, hordir); - if (!(ms->moveflags & MFL_SWIMMING)) hordir[2] = 0; - dist = VectorNormalize(hordir); - // - if (dist > 60) dist = 60; - speed = 360 - (360 - 6 * dist); - // - if (speed > 5) EA_Move(ms->client, dir, speed); - VectorCopy(dir, result.movedir); - // - if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW; - } //end if - else - { - MoverBottomCenter(reach, bottomcenter); - VectorSubtract(bottomcenter, ms->origin, hordir); - if (!(ms->moveflags & MFL_SWIMMING)) hordir[2] = 0; - dist = VectorNormalize(hordir); - // - if (dist > 5) - { - //move to the center of the plat - if (dist > 100) dist = 100; - speed = 400 - (400 - 4 * dist); - // - EA_Move(ms->client, hordir, speed); - VectorCopy(hordir, result.movedir); - } //end if - } //end else - return result; -} //end of the function BotFinishTravel_FuncBobbing -//=========================================================================== -// 0 no valid grapple hook visible -// 1 the grapple hook is still flying -// 2 the grapple hooked into a wall -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int GrappleState(bot_movestate_t *ms, aas_reachability_t *reach) -{ - int i; - aas_entityinfo_t entinfo; - - //if the grapple hook is pulling - if (ms->moveflags & MFL_GRAPPLEPULL) - return 2; - //check for a visible grapple missile entity - //or visible grapple entity - for (i = AAS_NextEntity(0); i; i = AAS_NextEntity(i)) - { - if (AAS_EntityType(i) == (int) entitytypemissile->value) - { - AAS_EntityInfo(i, &entinfo); - if (entinfo.weapon == (int) weapindex_grapple->value) - { - return 1; - } //end if - } //end if - } //end for - //no valid grapple at all - return 0; -} //end of the function GrappleState -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotResetGrapple(bot_movestate_t *ms) -{ - aas_reachability_t reach; - - AAS_ReachabilityFromNum(ms->lastreachnum, &reach); - //if not using the grapple hook reachability anymore - if ((reach.traveltype & TRAVELTYPE_MASK) != TRAVEL_GRAPPLEHOOK) - { - if ((ms->moveflags & MFL_ACTIVEGRAPPLE) || ms->grapplevisible_time) - { - if (offhandgrapple->value) - EA_Command(ms->client, cmd_grappleoff->string); - ms->moveflags &= ~MFL_ACTIVEGRAPPLE; - ms->grapplevisible_time = 0; -#ifdef DEBUG_GRAPPLE - botimport.Print(PRT_MESSAGE, "reset grapple\n"); -#endif //DEBUG_GRAPPLE - } //end if - } //end if -} //end of the function BotResetGrapple -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bot_moveresult_t BotTravel_Grapple(bot_movestate_t *ms, aas_reachability_t *reach) -{ - bot_moveresult_t result; - float dist, speed; - vec3_t dir, viewdir, org; - int state, areanum; - bsp_trace_t trace; - -#ifdef DEBUG_GRAPPLE - static int debugline; - if (!debugline) debugline = botimport.DebugLineCreate(); - botimport.DebugLineShow(debugline, reach->start, reach->end, LINECOLOR_BLUE); -#endif //DEBUG_GRAPPLE - - BotClearMoveResult(&result); - // - if (ms->moveflags & MFL_GRAPPLERESET) - { - if (offhandgrapple->value) - EA_Command(ms->client, cmd_grappleoff->string); - ms->moveflags &= ~MFL_ACTIVEGRAPPLE; - return result; - } //end if - // - if (!(int) offhandgrapple->value) - { - result.weapon = weapindex_grapple->value; - result.flags |= MOVERESULT_MOVEMENTWEAPON; - } //end if - // - if (ms->moveflags & MFL_ACTIVEGRAPPLE) - { -#ifdef DEBUG_GRAPPLE - botimport.Print(PRT_MESSAGE, "BotTravel_Grapple: active grapple\n"); -#endif //DEBUG_GRAPPLE - // - state = GrappleState(ms, reach); - // - VectorSubtract(reach->end, ms->origin, dir); - dir[2] = 0; - dist = VectorLength(dir); - //if very close to the grapple end or the grappled is hooked and - //the bot doesn't get any closer - if (state && dist < 48) - { - if (ms->lastgrappledist - dist < 1) - { -#ifdef DEBUG_GRAPPLE - botimport.Print(PRT_ERROR, "grapple normal end\n"); -#endif //DEBUG_GRAPPLE - if (offhandgrapple->value) - EA_Command(ms->client, cmd_grappleoff->string); - ms->moveflags &= ~MFL_ACTIVEGRAPPLE; - ms->moveflags |= MFL_GRAPPLERESET; - ms->reachability_time = 0; //end the reachability - return result; - } //end if - } //end if - //if no valid grapple at all, or the grapple hooked and the bot - //isn't moving anymore - else if (!state || (state == 2 && dist > ms->lastgrappledist - 2)) - { - if (ms->grapplevisible_time < AAS_Time() - 0.4) - { -#ifdef DEBUG_GRAPPLE - botimport.Print(PRT_ERROR, "grapple not visible\n"); -#endif //DEBUG_GRAPPLE - if (offhandgrapple->value) - EA_Command(ms->client, cmd_grappleoff->string); - ms->moveflags &= ~MFL_ACTIVEGRAPPLE; - ms->moveflags |= MFL_GRAPPLERESET; - ms->reachability_time = 0; //end the reachability - return result; - } //end if - } //end if - else - { - ms->grapplevisible_time = AAS_Time(); - } //end else - // - if (!(int) offhandgrapple->value) - { - EA_Attack(ms->client); - } //end if - //remember the current grapple distance - ms->lastgrappledist = dist; - } //end if - else - { -#ifdef DEBUG_GRAPPLE - botimport.Print(PRT_MESSAGE, "BotTravel_Grapple: inactive grapple\n"); -#endif //DEBUG_GRAPPLE - // - ms->grapplevisible_time = AAS_Time(); - // - VectorSubtract(reach->start, ms->origin, dir); - if (!(ms->moveflags & MFL_SWIMMING)) dir[2] = 0; - VectorAdd(ms->origin, ms->viewoffset, org); - VectorSubtract(reach->end, org, viewdir); - // - dist = VectorNormalize(dir); - Vector2Angles(viewdir, result.ideal_viewangles); - result.flags |= MOVERESULT_MOVEMENTVIEW; - // - if (dist < 5 && - fabs(AngleDiff(result.ideal_viewangles[0], ms->viewangles[0])) < 2 && - fabs(AngleDiff(result.ideal_viewangles[1], ms->viewangles[1])) < 2) - { -#ifdef DEBUG_GRAPPLE - botimport.Print(PRT_MESSAGE, "BotTravel_Grapple: activating grapple\n"); -#endif //DEBUG_GRAPPLE - //check if the grapple missile path is clear - VectorAdd(ms->origin, ms->viewoffset, org); - trace = AAS_Trace(org, NULL, NULL, reach->end, ms->entitynum, CONTENTS_SOLID); - VectorSubtract(reach->end, trace.endpos, dir); - if (VectorLength(dir) > 16) - { - result.failure = qtrue; - return result; - } //end if - //activate the grapple - if (offhandgrapple->value) - { - EA_Command(ms->client, cmd_grappleon->string); - } //end if - else - { - EA_Attack(ms->client); - } //end else - ms->moveflags |= MFL_ACTIVEGRAPPLE; - ms->lastgrappledist = 999999; - } //end if - else - { - if (dist < 70) speed = 300 - (300 - 4 * dist); - else speed = 400; - // - BotCheckBlocked(ms, dir, qtrue, &result); - //elemantary action move in direction - EA_Move(ms->client, dir, speed); - VectorCopy(dir, result.movedir); - } //end else - //if in another area before actually grappling - areanum = AAS_PointAreaNum(ms->origin); - if (areanum && areanum != ms->reachareanum) ms->reachability_time = 0; - } //end else - return result; -} //end of the function BotTravel_Grapple -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bot_moveresult_t BotTravel_RocketJump(bot_movestate_t *ms, aas_reachability_t *reach) -{ - vec3_t hordir; - float dist, speed; - bot_moveresult_t result; - - //botimport.Print(PRT_MESSAGE, "BotTravel_RocketJump: bah\n"); - BotClearMoveResult(&result); - // - hordir[0] = reach->start[0] - ms->origin[0]; - hordir[1] = reach->start[1] - ms->origin[1]; - hordir[2] = 0; - // - dist = VectorNormalize(hordir); - //look in the movement direction - Vector2Angles(hordir, result.ideal_viewangles); - //look straight down - result.ideal_viewangles[PITCH] = 90; - // - if (dist < 5 && - fabs(AngleDiff(result.ideal_viewangles[0], ms->viewangles[0])) < 5 && - fabs(AngleDiff(result.ideal_viewangles[1], ms->viewangles[1])) < 5) - { - //botimport.Print(PRT_MESSAGE, "between jump start and run start point\n"); - hordir[0] = reach->end[0] - ms->origin[0]; - hordir[1] = reach->end[1] - ms->origin[1]; - hordir[2] = 0; - VectorNormalize(hordir); - //elemantary action jump - EA_Jump(ms->client); - EA_Attack(ms->client); - EA_Move(ms->client, hordir, 800); - // - ms->jumpreach = ms->lastreachnum; - } //end if - else - { - if (dist > 80) dist = 80; - speed = 400 - (400 - 5 * dist); - EA_Move(ms->client, hordir, speed); - } //end else - //look in the movement direction - Vector2Angles(hordir, result.ideal_viewangles); - //look straight down - result.ideal_viewangles[PITCH] = 90; - //set the view angles directly - EA_View(ms->client, result.ideal_viewangles); - //view is important for the movment - result.flags |= MOVERESULT_MOVEMENTVIEWSET; - //select the rocket launcher - EA_SelectWeapon(ms->client, (int) weapindex_rocketlauncher->value); - //weapon is used for movement - result.weapon = (int) weapindex_rocketlauncher->value; - result.flags |= MOVERESULT_MOVEMENTWEAPON; - // - VectorCopy(hordir, result.movedir); - // - return result; -} //end of the function BotTravel_RocketJump -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bot_moveresult_t BotTravel_BFGJump(bot_movestate_t *ms, aas_reachability_t *reach) -{ - vec3_t hordir; - float dist, speed; - bot_moveresult_t result; - - //botimport.Print(PRT_MESSAGE, "BotTravel_BFGJump: bah\n"); - BotClearMoveResult(&result); - // - hordir[0] = reach->start[0] - ms->origin[0]; - hordir[1] = reach->start[1] - ms->origin[1]; - hordir[2] = 0; - // - dist = VectorNormalize(hordir); - // - if (dist < 5 && - fabs(AngleDiff(result.ideal_viewangles[0], ms->viewangles[0])) < 5 && - fabs(AngleDiff(result.ideal_viewangles[1], ms->viewangles[1])) < 5) - { - //botimport.Print(PRT_MESSAGE, "between jump start and run start point\n"); - hordir[0] = reach->end[0] - ms->origin[0]; - hordir[1] = reach->end[1] - ms->origin[1]; - hordir[2] = 0; - VectorNormalize(hordir); - //elemantary action jump - EA_Jump(ms->client); - EA_Attack(ms->client); - EA_Move(ms->client, hordir, 800); - // - ms->jumpreach = ms->lastreachnum; - } //end if - else - { - if (dist > 80) dist = 80; - speed = 400 - (400 - 5 * dist); - EA_Move(ms->client, hordir, speed); - } //end else - //look in the movement direction - Vector2Angles(hordir, result.ideal_viewangles); - //look straight down - result.ideal_viewangles[PITCH] = 90; - //set the view angles directly - EA_View(ms->client, result.ideal_viewangles); - //view is important for the movment - result.flags |= MOVERESULT_MOVEMENTVIEWSET; - //select the rocket launcher - EA_SelectWeapon(ms->client, (int) weapindex_bfg10k->value); - //weapon is used for movement - result.weapon = (int) weapindex_bfg10k->value; - result.flags |= MOVERESULT_MOVEMENTWEAPON; - // - VectorCopy(hordir, result.movedir); - // - return result; -} //end of the function BotTravel_BFGJump -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bot_moveresult_t BotFinishTravel_WeaponJump(bot_movestate_t *ms, aas_reachability_t *reach) -{ - vec3_t hordir; - float speed; - bot_moveresult_t result; - - BotClearMoveResult(&result); - //if not jumped yet - if (!ms->jumpreach) return result; - /* - //go straight to the reachability end - hordir[0] = reach->end[0] - ms->origin[0]; - hordir[1] = reach->end[1] - ms->origin[1]; - hordir[2] = 0; - VectorNormalize(hordir); - //always use max speed when traveling through the air - EA_Move(ms->client, hordir, 800); - VectorCopy(hordir, result.movedir); - */ - // - if (!BotAirControl(ms->origin, ms->velocity, reach->end, hordir, &speed)) - { - //go straight to the reachability end - VectorSubtract(reach->end, ms->origin, hordir); - hordir[2] = 0; - VectorNormalize(hordir); - speed = 400; - } //end if - // - EA_Move(ms->client, hordir, speed); - VectorCopy(hordir, result.movedir); - // - return result; -} //end of the function BotFinishTravel_WeaponJump -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bot_moveresult_t BotTravel_JumpPad(bot_movestate_t *ms, aas_reachability_t *reach) -{ - float dist, speed; - vec3_t hordir; - bot_moveresult_t result; - - BotClearMoveResult(&result); - //first walk straight to the reachability start - hordir[0] = reach->start[0] - ms->origin[0]; - hordir[1] = reach->start[1] - ms->origin[1]; - hordir[2] = 0; - dist = VectorNormalize(hordir); - // - BotCheckBlocked(ms, hordir, qtrue, &result); - speed = 400; - //elemantary action move in direction - EA_Move(ms->client, hordir, speed); - VectorCopy(hordir, result.movedir); - // - return result; -} //end of the function BotTravel_JumpPad -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bot_moveresult_t BotFinishTravel_JumpPad(bot_movestate_t *ms, aas_reachability_t *reach) -{ - float speed; - vec3_t hordir; - bot_moveresult_t result; - - BotClearMoveResult(&result); - if (!BotAirControl(ms->origin, ms->velocity, reach->end, hordir, &speed)) - { - hordir[0] = reach->end[0] - ms->origin[0]; - hordir[1] = reach->end[1] - ms->origin[1]; - hordir[2] = 0; - VectorNormalize(hordir); - speed = 400; - } //end if - BotCheckBlocked(ms, hordir, qtrue, &result); - //elemantary action move in direction - EA_Move(ms->client, hordir, speed); - VectorCopy(hordir, result.movedir); - // - return result; -} //end of the function BotFinishTravel_JumpPad -//=========================================================================== -// time before the reachability times out -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotReachabilityTime(aas_reachability_t *reach) -{ - switch(reach->traveltype & TRAVELTYPE_MASK) - { - case TRAVEL_WALK: return 5; - case TRAVEL_CROUCH: return 5; - case TRAVEL_BARRIERJUMP: return 5; - case TRAVEL_LADDER: return 6; - case TRAVEL_WALKOFFLEDGE: return 5; - case TRAVEL_JUMP: return 5; - case TRAVEL_SWIM: return 5; - case TRAVEL_WATERJUMP: return 5; - case TRAVEL_TELEPORT: return 5; - case TRAVEL_ELEVATOR: return 10; - case TRAVEL_GRAPPLEHOOK: return 8; - case TRAVEL_ROCKETJUMP: return 6; - case TRAVEL_BFGJUMP: return 6; - case TRAVEL_JUMPPAD: return 10; - case TRAVEL_FUNCBOB: return 10; - default: - { - botimport.Print(PRT_ERROR, "travel type %d not implemented yet\n", reach->traveltype); - return 8; - } //end case - } //end switch -} //end of the function BotReachabilityTime -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bot_moveresult_t BotMoveInGoalArea(bot_movestate_t *ms, bot_goal_t *goal) -{ - bot_moveresult_t result; - vec3_t dir; - float dist, speed; - -#ifdef DEBUG - //botimport.Print(PRT_MESSAGE, "%s: moving straight to goal\n", ClientName(ms->entitynum-1)); - //AAS_ClearShownDebugLines(); - //AAS_DebugLine(ms->origin, goal->origin, LINECOLOR_RED); -#endif //DEBUG - BotClearMoveResult(&result); - //walk straight to the goal origin - dir[0] = goal->origin[0] - ms->origin[0]; - dir[1] = goal->origin[1] - ms->origin[1]; - if (ms->moveflags & MFL_SWIMMING) - { - dir[2] = goal->origin[2] - ms->origin[2]; - result.traveltype = TRAVEL_SWIM; - } //end if - else - { - dir[2] = 0; - result.traveltype = TRAVEL_WALK; - } //endif - // - dist = VectorNormalize(dir); - if (dist > 100) dist = 100; - speed = 400 - (400 - 4 * dist); - if (speed < 10) speed = 0; - // - BotCheckBlocked(ms, dir, qtrue, &result); - //elemantary action move in direction - EA_Move(ms->client, dir, speed); - VectorCopy(dir, result.movedir); - // - if (ms->moveflags & MFL_SWIMMING) - { - Vector2Angles(dir, result.ideal_viewangles); - result.flags |= MOVERESULT_SWIMVIEW; - } //end if - //if (!debugline) debugline = botimport.DebugLineCreate(); - //botimport.DebugLineShow(debugline, ms->origin, goal->origin, LINECOLOR_BLUE); - // - ms->lastreachnum = 0; - ms->lastareanum = 0; - ms->lastgoalareanum = goal->areanum; - VectorCopy(ms->origin, ms->lastorigin); - // - return result; -} //end of the function BotMoveInGoalArea -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotMoveToGoal(bot_moveresult_t *result, int movestate, bot_goal_t *goal, int travelflags) -{ - int reachnum, lastreachnum, foundjumppad, ent, resultflags; - aas_reachability_t reach, lastreach; - bot_movestate_t *ms; - //vec3_t mins, maxs, up = {0, 0, 1}; - //bsp_trace_t trace; - //static int debugline; - - - BotClearMoveResult(result); - // - ms = BotMoveStateFromHandle(movestate); - if (!ms) return; - //reset the grapple before testing if the bot has a valid goal - //because the bot could loose all it's goals when stuck to a wall - BotResetGrapple(ms); - // - if (!goal) - { -#ifdef DEBUG - botimport.Print(PRT_MESSAGE, "client %d: movetogoal -> no goal\n", ms->client); -#endif //DEBUG - result->failure = qtrue; - return; - } //end if - //botimport.Print(PRT_MESSAGE, "numavoidreach = %d\n", ms->numavoidreach); - //remove some of the move flags - ms->moveflags &= ~(MFL_SWIMMING|MFL_AGAINSTLADDER); - //set some of the move flags - //NOTE: the MFL_ONGROUND flag is also set in the higher AI - if (AAS_OnGround(ms->origin, ms->presencetype, ms->entitynum)) ms->moveflags |= MFL_ONGROUND; - // - if (ms->moveflags & MFL_ONGROUND) - { - int modeltype, modelnum; - - ent = BotOnTopOfEntity(ms); - - if (ent != -1) - { - modelnum = AAS_EntityModelindex(ent); - if (modelnum >= 0 && modelnum < MAX_MODELS) - { - modeltype = modeltypes[modelnum]; - - if (modeltype == MODELTYPE_FUNC_PLAT) - { - AAS_ReachabilityFromNum(ms->lastreachnum, &reach); - //if the bot is Not using the elevator - if ((reach.traveltype & TRAVELTYPE_MASK) != TRAVEL_ELEVATOR || - //NOTE: the face number is the plat model number - (reach.facenum & 0x0000FFFF) != modelnum) - { - reachnum = AAS_NextModelReachability(0, modelnum); - if (reachnum) - { - //botimport.Print(PRT_MESSAGE, "client %d: accidentally ended up on func_plat\n", ms->client); - AAS_ReachabilityFromNum(reachnum, &reach); - ms->lastreachnum = reachnum; - ms->reachability_time = AAS_Time() + BotReachabilityTime(&reach); - } //end if - else - { - if (bot_developer) - { - botimport.Print(PRT_MESSAGE, "client %d: on func_plat without reachability\n", ms->client); - } //end if - result->blocked = qtrue; - result->blockentity = ent; - result->flags |= MOVERESULT_ONTOPOFOBSTACLE; - return; - } //end else - } //end if - result->flags |= MOVERESULT_ONTOPOF_ELEVATOR; - } //end if - else if (modeltype == MODELTYPE_FUNC_BOB) - { - AAS_ReachabilityFromNum(ms->lastreachnum, &reach); - //if the bot is Not using the func bobbing - if ((reach.traveltype & TRAVELTYPE_MASK) != TRAVEL_FUNCBOB || - //NOTE: the face number is the func_bobbing model number - (reach.facenum & 0x0000FFFF) != modelnum) - { - reachnum = AAS_NextModelReachability(0, modelnum); - if (reachnum) - { - //botimport.Print(PRT_MESSAGE, "client %d: accidentally ended up on func_bobbing\n", ms->client); - AAS_ReachabilityFromNum(reachnum, &reach); - ms->lastreachnum = reachnum; - ms->reachability_time = AAS_Time() + BotReachabilityTime(&reach); - } //end if - else - { - if (bot_developer) - { - botimport.Print(PRT_MESSAGE, "client %d: on func_bobbing without reachability\n", ms->client); - } //end if - result->blocked = qtrue; - result->blockentity = ent; - result->flags |= MOVERESULT_ONTOPOFOBSTACLE; - return; - } //end else - } //end if - result->flags |= MOVERESULT_ONTOPOF_FUNCBOB; - } //end if - else if (modeltype == MODELTYPE_FUNC_STATIC || modeltype == MODELTYPE_FUNC_DOOR) - { - // check if ontop of a door bridge ? - ms->areanum = BotFuzzyPointReachabilityArea(ms->origin); - // if not in a reachability area - if (!AAS_AreaReachability(ms->areanum)) - { - result->blocked = qtrue; - result->blockentity = ent; - result->flags |= MOVERESULT_ONTOPOFOBSTACLE; - return; - } //end if - } //end else if - else - { - result->blocked = qtrue; - result->blockentity = ent; - result->flags |= MOVERESULT_ONTOPOFOBSTACLE; - return; - } //end else - } //end if - } //end if - } //end if - //if swimming - if (AAS_Swimming(ms->origin)) ms->moveflags |= MFL_SWIMMING; - //if against a ladder - if (AAS_AgainstLadder(ms->origin)) ms->moveflags |= MFL_AGAINSTLADDER; - //if the bot is on the ground, swimming or against a ladder - if (ms->moveflags & (MFL_ONGROUND|MFL_SWIMMING|MFL_AGAINSTLADDER)) - { - //botimport.Print(PRT_MESSAGE, "%s: onground, swimming or against ladder\n", ClientName(ms->entitynum-1)); - // - AAS_ReachabilityFromNum(ms->lastreachnum, &lastreach); - //reachability area the bot is in - //ms->areanum = BotReachabilityArea(ms->origin, ((lastreach.traveltype & TRAVELTYPE_MASK) != TRAVEL_ELEVATOR)); - ms->areanum = BotFuzzyPointReachabilityArea(ms->origin); - // - if ( !ms->areanum ) - { - result->failure = qtrue; - result->blocked = qtrue; - result->blockentity = 0; - result->type = RESULTTYPE_INSOLIDAREA; - return; - } //end if - //if the bot is in the goal area - if (ms->areanum == goal->areanum) - { - *result = BotMoveInGoalArea(ms, goal); - return; - } //end if - //assume we can use the reachability from the last frame - reachnum = ms->lastreachnum; - //if there is a last reachability - if (reachnum) - { - AAS_ReachabilityFromNum(reachnum, &reach); - //check if the reachability is still valid - if (!(AAS_TravelFlagForType(reach.traveltype) & travelflags)) - { - reachnum = 0; - } //end if - //special grapple hook case - else if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_GRAPPLEHOOK) - { - if (ms->reachability_time < AAS_Time() || - (ms->moveflags & MFL_GRAPPLERESET)) - { - reachnum = 0; - } //end if - } //end if - //special elevator case - else if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_ELEVATOR || - (reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_FUNCBOB) - { - if ((result->flags & MOVERESULT_ONTOPOF_FUNCBOB) || - (result->flags & MOVERESULT_ONTOPOF_FUNCBOB)) - { - ms->reachability_time = AAS_Time() + 5; - } //end if - //if the bot was going for an elevator and reached the reachability area - if (ms->areanum == reach.areanum || - ms->reachability_time < AAS_Time()) - { - reachnum = 0; - } //end if - } //end if - else - { -#ifdef DEBUG - if (bot_developer) - { - if (ms->reachability_time < AAS_Time()) - { - botimport.Print(PRT_MESSAGE, "client %d: reachability timeout in ", ms->client); - AAS_PrintTravelType(reach.traveltype & TRAVELTYPE_MASK); - botimport.Print(PRT_MESSAGE, "\n"); - } //end if - /* - if (ms->lastareanum != ms->areanum) - { - botimport.Print(PRT_MESSAGE, "changed from area %d to %d\n", ms->lastareanum, ms->areanum); - } //end if*/ - } //end if -#endif //DEBUG - //if the goal area changed or the reachability timed out - //or the area changed - if (ms->lastgoalareanum != goal->areanum || - ms->reachability_time < AAS_Time() || - ms->lastareanum != ms->areanum) - { - reachnum = 0; - //botimport.Print(PRT_MESSAGE, "area change or timeout\n"); - } //end else if - } //end else - } //end if - resultflags = 0; - //if the bot needs a new reachability - if (!reachnum) - { - //if the area has no reachability links - if (!AAS_AreaReachability(ms->areanum)) - { -#ifdef DEBUG - if (bot_developer) - { - botimport.Print(PRT_MESSAGE, "area %d no reachability\n", ms->areanum); - } //end if -#endif //DEBUG - } //end if - //get a new reachability leading towards the goal - reachnum = BotGetReachabilityToGoal(ms->origin, ms->areanum, - ms->lastgoalareanum, ms->lastareanum, - ms->avoidreach, ms->avoidreachtimes, ms->avoidreachtries, - goal, travelflags, travelflags, - ms->avoidspots, ms->numavoidspots, &resultflags); - //the area number the reachability starts in - ms->reachareanum = ms->areanum; - //reset some state variables - ms->jumpreach = 0; //for TRAVEL_JUMP - ms->moveflags &= ~MFL_GRAPPLERESET; //for TRAVEL_GRAPPLEHOOK - //if there is a reachability to the goal - if (reachnum) - { - AAS_ReachabilityFromNum(reachnum, &reach); - //set a timeout for this reachability - ms->reachability_time = AAS_Time() + BotReachabilityTime(&reach); - // -#ifdef AVOIDREACH - //add the reachability to the reachabilities to avoid for a while - BotAddToAvoidReach(ms, reachnum, AVOIDREACH_TIME); -#endif //AVOIDREACH - } //end if -#ifdef DEBUG - - else if (bot_developer) - { - botimport.Print(PRT_MESSAGE, "goal not reachable\n"); - Com_Memset(&reach, 0, sizeof(aas_reachability_t)); //make compiler happy - } //end else - if (bot_developer) - { - //if still going for the same goal - if (ms->lastgoalareanum == goal->areanum) - { - if (ms->lastareanum == reach.areanum) - { - botimport.Print(PRT_MESSAGE, "same goal, going back to previous area\n"); - } //end if - } //end if - } //end if -#endif //DEBUG - } //end else - // - ms->lastreachnum = reachnum; - ms->lastgoalareanum = goal->areanum; - ms->lastareanum = ms->areanum; - //if the bot has a reachability - if (reachnum) - { - //get the reachability from the number - AAS_ReachabilityFromNum(reachnum, &reach); - result->traveltype = reach.traveltype; - // -#ifdef DEBUG_AI_MOVE - AAS_ClearShownDebugLines(); - AAS_PrintTravelType(reach.traveltype & TRAVELTYPE_MASK); - AAS_ShowReachability(&reach); -#endif //DEBUG_AI_MOVE - // -#ifdef DEBUG - //botimport.Print(PRT_MESSAGE, "client %d: ", ms->client); - //AAS_PrintTravelType(reach.traveltype); - //botimport.Print(PRT_MESSAGE, "\n"); -#endif //DEBUG - switch(reach.traveltype & TRAVELTYPE_MASK) - { - case TRAVEL_WALK: *result = BotTravel_Walk(ms, &reach); break; - case TRAVEL_CROUCH: *result = BotTravel_Crouch(ms, &reach); break; - case TRAVEL_BARRIERJUMP: *result = BotTravel_BarrierJump(ms, &reach); break; - case TRAVEL_LADDER: *result = BotTravel_Ladder(ms, &reach); break; - case TRAVEL_WALKOFFLEDGE: *result = BotTravel_WalkOffLedge(ms, &reach); break; - case TRAVEL_JUMP: *result = BotTravel_Jump(ms, &reach); break; - case TRAVEL_SWIM: *result = BotTravel_Swim(ms, &reach); break; - case TRAVEL_WATERJUMP: *result = BotTravel_WaterJump(ms, &reach); break; - case TRAVEL_TELEPORT: *result = BotTravel_Teleport(ms, &reach); break; - case TRAVEL_ELEVATOR: *result = BotTravel_Elevator(ms, &reach); break; - case TRAVEL_GRAPPLEHOOK: *result = BotTravel_Grapple(ms, &reach); break; - case TRAVEL_ROCKETJUMP: *result = BotTravel_RocketJump(ms, &reach); break; - case TRAVEL_BFGJUMP: *result = BotTravel_BFGJump(ms, &reach); break; - case TRAVEL_JUMPPAD: *result = BotTravel_JumpPad(ms, &reach); break; - case TRAVEL_FUNCBOB: *result = BotTravel_FuncBobbing(ms, &reach); break; - default: - { - botimport.Print(PRT_FATAL, "travel type %d not implemented yet\n", (reach.traveltype & TRAVELTYPE_MASK)); - break; - } //end case - } //end switch - result->traveltype = reach.traveltype; - result->flags |= resultflags; - } //end if - else - { - result->failure = qtrue; - result->flags |= resultflags; - Com_Memset(&reach, 0, sizeof(aas_reachability_t)); - } //end else -#ifdef DEBUG - if (bot_developer) - { - if (result->failure) - { - botimport.Print(PRT_MESSAGE, "client %d: movement failure in ", ms->client); - AAS_PrintTravelType(reach.traveltype & TRAVELTYPE_MASK); - botimport.Print(PRT_MESSAGE, "\n"); - } //end if - } //end if -#endif //DEBUG - } //end if - else - { - int i, numareas, areas[16]; - vec3_t end; - - //special handling of jump pads when the bot uses a jump pad without knowing it - foundjumppad = qfalse; - VectorMA(ms->origin, -2 * ms->thinktime, ms->velocity, end); - numareas = AAS_TraceAreas(ms->origin, end, areas, NULL, 16); - for (i = numareas-1; i >= 0; i--) - { - if (AAS_AreaJumpPad(areas[i])) - { - //botimport.Print(PRT_MESSAGE, "client %d used a jumppad without knowing, area %d\n", ms->client, areas[i]); - foundjumppad = qtrue; - lastreachnum = BotGetReachabilityToGoal(end, areas[i], - ms->lastgoalareanum, ms->lastareanum, - ms->avoidreach, ms->avoidreachtimes, ms->avoidreachtries, - goal, travelflags, TFL_JUMPPAD, ms->avoidspots, ms->numavoidspots, NULL); - if (lastreachnum) - { - ms->lastreachnum = lastreachnum; - ms->lastareanum = areas[i]; - //botimport.Print(PRT_MESSAGE, "found jumppad reachability\n"); - break; - } //end if - else - { - for (lastreachnum = AAS_NextAreaReachability(areas[i], 0); lastreachnum; - lastreachnum = AAS_NextAreaReachability(areas[i], lastreachnum)) - { - //get the reachability from the number - AAS_ReachabilityFromNum(lastreachnum, &reach); - if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_JUMPPAD) - { - ms->lastreachnum = lastreachnum; - ms->lastareanum = areas[i]; - //botimport.Print(PRT_MESSAGE, "found jumppad reachability hard!!\n"); - break; - } //end if - } //end for - if (lastreachnum) break; - } //end else - } //end if - } //end for - if (bot_developer) - { - //if a jumppad is found with the trace but no reachability is found - if (foundjumppad && !ms->lastreachnum) - { - botimport.Print(PRT_MESSAGE, "client %d didn't find jumppad reachability\n", ms->client); - } //end if - } //end if - // - if (ms->lastreachnum) - { - //botimport.Print(PRT_MESSAGE, "%s: NOT onground, swimming or against ladder\n", ClientName(ms->entitynum-1)); - AAS_ReachabilityFromNum(ms->lastreachnum, &reach); - result->traveltype = reach.traveltype; -#ifdef DEBUG - //botimport.Print(PRT_MESSAGE, "client %d finish: ", ms->client); - //AAS_PrintTravelType(reach.traveltype & TRAVELTYPE_MASK); - //botimport.Print(PRT_MESSAGE, "\n"); -#endif //DEBUG - // - switch(reach.traveltype & TRAVELTYPE_MASK) - { - case TRAVEL_WALK: *result = BotTravel_Walk(ms, &reach); break;//BotFinishTravel_Walk(ms, &reach); break; - case TRAVEL_CROUCH: /*do nothing*/ break; - case TRAVEL_BARRIERJUMP: *result = BotFinishTravel_BarrierJump(ms, &reach); break; - case TRAVEL_LADDER: *result = BotTravel_Ladder(ms, &reach); break; - case TRAVEL_WALKOFFLEDGE: *result = BotFinishTravel_WalkOffLedge(ms, &reach); break; - case TRAVEL_JUMP: *result = BotFinishTravel_Jump(ms, &reach); break; - case TRAVEL_SWIM: *result = BotTravel_Swim(ms, &reach); break; - case TRAVEL_WATERJUMP: *result = BotFinishTravel_WaterJump(ms, &reach); break; - case TRAVEL_TELEPORT: /*do nothing*/ break; - case TRAVEL_ELEVATOR: *result = BotFinishTravel_Elevator(ms, &reach); break; - case TRAVEL_GRAPPLEHOOK: *result = BotTravel_Grapple(ms, &reach); break; - case TRAVEL_ROCKETJUMP: - case TRAVEL_BFGJUMP: *result = BotFinishTravel_WeaponJump(ms, &reach); break; - case TRAVEL_JUMPPAD: *result = BotFinishTravel_JumpPad(ms, &reach); break; - case TRAVEL_FUNCBOB: *result = BotFinishTravel_FuncBobbing(ms, &reach); break; - default: - { - botimport.Print(PRT_FATAL, "(last) travel type %d not implemented yet\n", (reach.traveltype & TRAVELTYPE_MASK)); - break; - } //end case - } //end switch - result->traveltype = reach.traveltype; -#ifdef DEBUG - if (bot_developer) - { - if (result->failure) - { - botimport.Print(PRT_MESSAGE, "client %d: movement failure in finish ", ms->client); - AAS_PrintTravelType(reach.traveltype & TRAVELTYPE_MASK); - botimport.Print(PRT_MESSAGE, "\n"); - } //end if - } //end if -#endif //DEBUG - } //end if - } //end else - //FIXME: is it right to do this here? - if (result->blocked) ms->reachability_time -= 10 * ms->thinktime; - //copy the last origin - VectorCopy(ms->origin, ms->lastorigin); - //return the movement result - return; -} //end of the function BotMoveToGoal -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotResetAvoidReach(int movestate) -{ - bot_movestate_t *ms; - - ms = BotMoveStateFromHandle(movestate); - if (!ms) return; - Com_Memset(ms->avoidreach, 0, MAX_AVOIDREACH * sizeof(int)); - Com_Memset(ms->avoidreachtimes, 0, MAX_AVOIDREACH * sizeof(float)); - Com_Memset(ms->avoidreachtries, 0, MAX_AVOIDREACH * sizeof(int)); -} //end of the function BotResetAvoidReach -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotResetLastAvoidReach(int movestate) -{ - int i, latest; - float latesttime; - bot_movestate_t *ms; - - ms = BotMoveStateFromHandle(movestate); - if (!ms) return; - latesttime = 0; - latest = 0; - for (i = 0; i < MAX_AVOIDREACH; i++) - { - if (ms->avoidreachtimes[i] > latesttime) - { - latesttime = ms->avoidreachtimes[i]; - latest = i; - } //end if - } //end for - if (latesttime) - { - ms->avoidreachtimes[latest] = 0; - if (ms->avoidreachtries[i] > 0) ms->avoidreachtries[latest]--; - } //end if -} //end of the function BotResetLastAvoidReach -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotResetMoveState(int movestate) -{ - bot_movestate_t *ms; - - ms = BotMoveStateFromHandle(movestate); - if (!ms) return; - Com_Memset(ms, 0, sizeof(bot_movestate_t)); -} //end of the function BotResetMoveState -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotSetupMoveAI(void) -{ - BotSetBrushModelTypes(); - sv_maxstep = LibVar("sv_step", "18"); - sv_maxbarrier = LibVar("sv_maxbarrier", "32"); - sv_gravity = LibVar("sv_gravity", "800"); - weapindex_rocketlauncher = LibVar("weapindex_rocketlauncher", "5"); - weapindex_bfg10k = LibVar("weapindex_bfg10k", "9"); - weapindex_grapple = LibVar("weapindex_grapple", "10"); - entitytypemissile = LibVar("entitytypemissile", "3"); - offhandgrapple = LibVar("offhandgrapple", "0"); - cmd_grappleon = LibVar("cmd_grappleon", "grappleon"); - cmd_grappleoff = LibVar("cmd_grappleoff", "grappleoff"); - return BLERR_NOERROR; -} //end of the function BotSetupMoveAI -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotShutdownMoveAI(void) -{ - int i; - - for (i = 1; i <= MAX_CLIENTS; i++) - { - if (botmovestates[i]) - { - FreeMemory(botmovestates[i]); - botmovestates[i] = NULL; - } //end if - } //end for -} //end of the function BotShutdownMoveAI - - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_ai_move.c + * + * desc: bot movement AI + * + * $Archive: /MissionPack/code/botlib/be_ai_move.c $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_libvar.h" +#include "l_utils.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" + +#include "../game/be_ea.h" +#include "../game/be_ai_goal.h" +#include "../game/be_ai_move.h" + + +//#define DEBUG_AI_MOVE +//#define DEBUG_ELEVATOR +//#define DEBUG_GRAPPLE + +// bk001204 - redundant bot_avoidspot_t, see ../game/be_ai_move.h + +//movement state +//NOTE: the moveflags MFL_ONGROUND, MFL_TELEPORTED, MFL_WATERJUMP and +// MFL_GRAPPLEPULL must be set outside the movement code +typedef struct bot_movestate_s +{ + //input vars (all set outside the movement code) + vec3_t origin; //origin of the bot + vec3_t velocity; //velocity of the bot + vec3_t viewoffset; //view offset + int entitynum; //entity number of the bot + int client; //client number of the bot + float thinktime; //time the bot thinks + int presencetype; //presencetype of the bot + vec3_t viewangles; //view angles of the bot + //state vars + int areanum; //area the bot is in + int lastareanum; //last area the bot was in + int lastgoalareanum; //last goal area number + int lastreachnum; //last reachability number + vec3_t lastorigin; //origin previous cycle + int reachareanum; //area number of the reachabilty + int moveflags; //movement flags + int jumpreach; //set when jumped + float grapplevisible_time; //last time the grapple was visible + float lastgrappledist; //last distance to the grapple end + float reachability_time; //time to use current reachability + int avoidreach[MAX_AVOIDREACH]; //reachabilities to avoid + float avoidreachtimes[MAX_AVOIDREACH]; //times to avoid the reachabilities + int avoidreachtries[MAX_AVOIDREACH]; //number of tries before avoiding + // + bot_avoidspot_t avoidspots[MAX_AVOIDSPOTS]; //spots to avoid + int numavoidspots; +} bot_movestate_t; + +//used to avoid reachability links for some time after being used +#define AVOIDREACH +#define AVOIDREACH_TIME 6 //avoid links for 6 seconds after use +#define AVOIDREACH_TRIES 4 +//prediction times +#define PREDICTIONTIME_JUMP 3 //in seconds +#define PREDICTIONTIME_MOVE 2 //in seconds +//weapon indexes for weapon jumping +#define WEAPONINDEX_ROCKET_LAUNCHER 5 +#define WEAPONINDEX_BFG 9 + +#define MODELTYPE_FUNC_PLAT 1 +#define MODELTYPE_FUNC_BOB 2 +#define MODELTYPE_FUNC_DOOR 3 +#define MODELTYPE_FUNC_STATIC 4 + +libvar_t *sv_maxstep; +libvar_t *sv_maxbarrier; +libvar_t *sv_gravity; +libvar_t *weapindex_rocketlauncher; +libvar_t *weapindex_bfg10k; +libvar_t *weapindex_grapple; +libvar_t *entitytypemissile; +libvar_t *offhandgrapple; +libvar_t *cmd_grappleoff; +libvar_t *cmd_grappleon; +//type of model, func_plat or func_bobbing +int modeltypes[MAX_MODELS]; + +bot_movestate_t *botmovestates[MAX_CLIENTS+1]; + +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +int BotAllocMoveState(void) +{ + int i; + + for (i = 1; i <= MAX_CLIENTS; i++) + { + if (!botmovestates[i]) + { + botmovestates[i] = GetClearedMemory(sizeof(bot_movestate_t)); + return i; + } //end if + } //end for + return 0; +} //end of the function BotAllocMoveState +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotFreeMoveState(int handle) +{ + if (handle <= 0 || handle > MAX_CLIENTS) + { + botimport.Print(PRT_FATAL, "move state handle %d out of range\n", handle); + return; + } //end if + if (!botmovestates[handle]) + { + botimport.Print(PRT_FATAL, "invalid move state %d\n", handle); + return; + } //end if + FreeMemory(botmovestates[handle]); + botmovestates[handle] = NULL; +} //end of the function BotFreeMoveState +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +bot_movestate_t *BotMoveStateFromHandle(int handle) +{ + if (handle <= 0 || handle > MAX_CLIENTS) + { + botimport.Print(PRT_FATAL, "move state handle %d out of range\n", handle); + return NULL; + } //end if + if (!botmovestates[handle]) + { + botimport.Print(PRT_FATAL, "invalid move state %d\n", handle); + return NULL; + } //end if + return botmovestates[handle]; +} //end of the function BotMoveStateFromHandle +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotInitMoveState(int handle, bot_initmove_t *initmove) +{ + bot_movestate_t *ms; + + ms = BotMoveStateFromHandle(handle); + if (!ms) return; + VectorCopy(initmove->origin, ms->origin); + VectorCopy(initmove->velocity, ms->velocity); + VectorCopy(initmove->viewoffset, ms->viewoffset); + ms->entitynum = initmove->entitynum; + ms->client = initmove->client; + ms->thinktime = initmove->thinktime; + ms->presencetype = initmove->presencetype; + VectorCopy(initmove->viewangles, ms->viewangles); + // + ms->moveflags &= ~MFL_ONGROUND; + if (initmove->or_moveflags & MFL_ONGROUND) ms->moveflags |= MFL_ONGROUND; + ms->moveflags &= ~MFL_TELEPORTED; + if (initmove->or_moveflags & MFL_TELEPORTED) ms->moveflags |= MFL_TELEPORTED; + ms->moveflags &= ~MFL_WATERJUMP; + if (initmove->or_moveflags & MFL_WATERJUMP) ms->moveflags |= MFL_WATERJUMP; + ms->moveflags &= ~MFL_WALK; + if (initmove->or_moveflags & MFL_WALK) ms->moveflags |= MFL_WALK; + ms->moveflags &= ~MFL_GRAPPLEPULL; + if (initmove->or_moveflags & MFL_GRAPPLEPULL) ms->moveflags |= MFL_GRAPPLEPULL; +} //end of the function BotInitMoveState +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +float AngleDiff(float ang1, float ang2) +{ + float diff; + + diff = ang1 - ang2; + if (ang1 > ang2) + { + if (diff > 180.0) diff -= 360.0; + } //end if + else + { + if (diff < -180.0) diff += 360.0; + } //end else + return diff; +} //end of the function AngleDiff +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotFuzzyPointReachabilityArea(vec3_t origin) +{ + int firstareanum, j, x, y, z; + int areas[10], numareas, areanum, bestareanum; + float dist, bestdist; + vec3_t points[10], v, end; + + firstareanum = 0; + areanum = AAS_PointAreaNum(origin); + if (areanum) + { + firstareanum = areanum; + if (AAS_AreaReachability(areanum)) return areanum; + } //end if + VectorCopy(origin, end); + end[2] += 4; + numareas = AAS_TraceAreas(origin, end, areas, points, 10); + for (j = 0; j < numareas; j++) + { + if (AAS_AreaReachability(areas[j])) return areas[j]; + } //end for + bestdist = 999999; + bestareanum = 0; + for (z = 1; z >= -1; z -= 1) + { + for (x = 1; x >= -1; x -= 1) + { + for (y = 1; y >= -1; y -= 1) + { + VectorCopy(origin, end); + end[0] += x * 8; + end[1] += y * 8; + end[2] += z * 12; + numareas = AAS_TraceAreas(origin, end, areas, points, 10); + for (j = 0; j < numareas; j++) + { + if (AAS_AreaReachability(areas[j])) + { + VectorSubtract(points[j], origin, v); + dist = VectorLength(v); + if (dist < bestdist) + { + bestareanum = areas[j]; + bestdist = dist; + } //end if + } //end if + if (!firstareanum) firstareanum = areas[j]; + } //end for + } //end for + } //end for + if (bestareanum) return bestareanum; + } //end for + return firstareanum; +} //end of the function BotFuzzyPointReachabilityArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotReachabilityArea(vec3_t origin, int client) +{ + int modelnum, modeltype, reachnum, areanum; + aas_reachability_t reach; + vec3_t org, end, mins, maxs, up = {0, 0, 1}; + bsp_trace_t bsptrace; + aas_trace_t trace; + + //check if the bot is standing on something + AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, mins, maxs); + VectorMA(origin, -3, up, end); + bsptrace = AAS_Trace(origin, mins, maxs, end, client, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); + if (!bsptrace.startsolid && bsptrace.fraction < 1 && bsptrace.ent != ENTITYNUM_NONE) + { + //if standing on the world the bot should be in a valid area + if (bsptrace.ent == ENTITYNUM_WORLD) + { + return BotFuzzyPointReachabilityArea(origin); + } //end if + + modelnum = AAS_EntityModelindex(bsptrace.ent); + modeltype = modeltypes[modelnum]; + + //if standing on a func_plat or func_bobbing then the bot is assumed to be + //in the area the reachability points to + if (modeltype == MODELTYPE_FUNC_PLAT || modeltype == MODELTYPE_FUNC_BOB) + { + reachnum = AAS_NextModelReachability(0, modelnum); + if (reachnum) + { + AAS_ReachabilityFromNum(reachnum, &reach); + return reach.areanum; + } //end if + } //end else if + + //if the bot is swimming the bot should be in a valid area + if (AAS_Swimming(origin)) + { + return BotFuzzyPointReachabilityArea(origin); + } //end if + // + areanum = BotFuzzyPointReachabilityArea(origin); + //if the bot is in an area with reachabilities + if (areanum && AAS_AreaReachability(areanum)) return areanum; + //trace down till the ground is hit because the bot is standing on some other entity + VectorCopy(origin, org); + VectorCopy(org, end); + end[2] -= 800; + trace = AAS_TraceClientBBox(org, end, PRESENCE_CROUCH, -1); + if (!trace.startsolid) + { + VectorCopy(trace.endpos, org); + } //end if + // + return BotFuzzyPointReachabilityArea(org); + } //end if + // + return BotFuzzyPointReachabilityArea(origin); +} //end of the function BotReachabilityArea +//=========================================================================== +// returns the reachability area the bot is in +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +/* +int BotReachabilityArea(vec3_t origin, int testground) +{ + int firstareanum, i, j, x, y, z; + int areas[10], numareas, areanum, bestareanum; + float dist, bestdist; + vec3_t org, end, points[10], v; + aas_trace_t trace; + + firstareanum = 0; + for (i = 0; i < 2; i++) + { + VectorCopy(origin, org); + //if test at the ground (used when bot is standing on an entity) + if (i > 0) + { + VectorCopy(origin, end); + end[2] -= 800; + trace = AAS_TraceClientBBox(origin, end, PRESENCE_CROUCH, -1); + if (!trace.startsolid) + { + VectorCopy(trace.endpos, org); + } //end if + } //end if + + firstareanum = 0; + areanum = AAS_PointAreaNum(org); + if (areanum) + { + firstareanum = areanum; + if (AAS_AreaReachability(areanum)) return areanum; + } //end if + bestdist = 999999; + bestareanum = 0; + for (z = 1; z >= -1; z -= 1) + { + for (x = 1; x >= -1; x -= 1) + { + for (y = 1; y >= -1; y -= 1) + { + VectorCopy(org, end); + end[0] += x * 8; + end[1] += y * 8; + end[2] += z * 12; + numareas = AAS_TraceAreas(org, end, areas, points, 10); + for (j = 0; j < numareas; j++) + { + if (AAS_AreaReachability(areas[j])) + { + VectorSubtract(points[j], org, v); + dist = VectorLength(v); + if (dist < bestdist) + { + bestareanum = areas[j]; + bestdist = dist; + } //end if + } //end if + } //end for + } //end for + } //end for + if (bestareanum) return bestareanum; + } //end for + if (!testground) break; + } //end for +//#ifdef DEBUG + //botimport.Print(PRT_MESSAGE, "no reachability area\n"); +//#endif //DEBUG + return firstareanum; +} //end of the function BotReachabilityArea*/ +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotOnMover(vec3_t origin, int entnum, aas_reachability_t *reach) +{ + int i, modelnum; + vec3_t mins, maxs, modelorigin, org, end; + vec3_t angles = {0, 0, 0}; + vec3_t boxmins = {-16, -16, -8}, boxmaxs = {16, 16, 8}; + bsp_trace_t trace; + + modelnum = reach->facenum & 0x0000FFFF; + //get some bsp model info + AAS_BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, NULL); + // + if (!AAS_OriginOfMoverWithModelNum(modelnum, modelorigin)) + { + botimport.Print(PRT_MESSAGE, "no entity with model %d\n", modelnum); + return qfalse; + } //end if + // + for (i = 0; i < 2; i++) + { + if (origin[i] > modelorigin[i] + maxs[i] + 16) return qfalse; + if (origin[i] < modelorigin[i] + mins[i] - 16) return qfalse; + } //end for + // + VectorCopy(origin, org); + org[2] += 24; + VectorCopy(origin, end); + end[2] -= 48; + // + trace = AAS_Trace(org, boxmins, boxmaxs, end, entnum, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); + if (!trace.startsolid && !trace.allsolid) + { + //NOTE: the reachability face number is the model number of the elevator + if (trace.ent != ENTITYNUM_NONE && AAS_EntityModelNum(trace.ent) == modelnum) + { + return qtrue; + } //end if + } //end if + return qfalse; +} //end of the function BotOnMover +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int MoverDown(aas_reachability_t *reach) +{ + int modelnum; + vec3_t mins, maxs, origin; + vec3_t angles = {0, 0, 0}; + + modelnum = reach->facenum & 0x0000FFFF; + //get some bsp model info + AAS_BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, origin); + // + if (!AAS_OriginOfMoverWithModelNum(modelnum, origin)) + { + botimport.Print(PRT_MESSAGE, "no entity with model %d\n", modelnum); + return qfalse; + } //end if + //if the top of the plat is below the reachability start point + if (origin[2] + maxs[2] < reach->start[2]) return qtrue; + return qfalse; +} //end of the function MoverDown +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotSetBrushModelTypes(void) +{ + int ent, modelnum; + char classname[MAX_EPAIRKEY], model[MAX_EPAIRKEY]; + + Com_Memset(modeltypes, 0, MAX_MODELS * sizeof(int)); + // + for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) + { + if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; + if (!AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY)) continue; + if (model[0]) modelnum = atoi(model+1); + else modelnum = 0; + + if (modelnum < 0 || modelnum > MAX_MODELS) + { + botimport.Print(PRT_MESSAGE, "entity %s model number out of range\n", classname); + continue; + } //end if + + if (!Q_stricmp(classname, "func_bobbing")) + modeltypes[modelnum] = MODELTYPE_FUNC_BOB; + else if (!Q_stricmp(classname, "func_plat")) + modeltypes[modelnum] = MODELTYPE_FUNC_PLAT; + else if (!Q_stricmp(classname, "func_door")) + modeltypes[modelnum] = MODELTYPE_FUNC_DOOR; + else if (!Q_stricmp(classname, "func_static")) + modeltypes[modelnum] = MODELTYPE_FUNC_STATIC; + } //end for +} //end of the function BotSetBrushModelTypes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotOnTopOfEntity(bot_movestate_t *ms) +{ + vec3_t mins, maxs, end, up = {0, 0, 1}; + bsp_trace_t trace; + + AAS_PresenceTypeBoundingBox(ms->presencetype, mins, maxs); + VectorMA(ms->origin, -3, up, end); + trace = AAS_Trace(ms->origin, mins, maxs, end, ms->entitynum, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); + if (!trace.startsolid && (trace.ent != ENTITYNUM_WORLD && trace.ent != ENTITYNUM_NONE) ) + { + return trace.ent; + } //end if + return -1; +} //end of the function BotOnTopOfEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotValidTravel(vec3_t origin, aas_reachability_t *reach, int travelflags) +{ + //if the reachability uses an unwanted travel type + if (AAS_TravelFlagForType(reach->traveltype) & ~travelflags) return qfalse; + //don't go into areas with bad travel types + if (AAS_AreaContentsTravelFlags(reach->areanum) & ~travelflags) return qfalse; + return qtrue; +} //end of the function BotValidTravel +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotAddToAvoidReach(bot_movestate_t *ms, int number, float avoidtime) +{ + int i; + + for (i = 0; i < MAX_AVOIDREACH; i++) + { + if (ms->avoidreach[i] == number) + { + if (ms->avoidreachtimes[i] > AAS_Time()) ms->avoidreachtries[i]++; + else ms->avoidreachtries[i] = 1; + ms->avoidreachtimes[i] = AAS_Time() + avoidtime; + return; + } //end if + } //end for + //add the reachability to the reachabilities to avoid for a while + for (i = 0; i < MAX_AVOIDREACH; i++) + { + if (ms->avoidreachtimes[i] < AAS_Time()) + { + ms->avoidreach[i] = number; + ms->avoidreachtimes[i] = AAS_Time() + avoidtime; + ms->avoidreachtries[i] = 1; + return; + } //end if + } //end for +} //end of the function BotAddToAvoidReach +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float DistanceFromLineSquared(vec3_t p, vec3_t lp1, vec3_t lp2) +{ + vec3_t proj, dir; + int j; + + AAS_ProjectPointOntoVector(p, lp1, lp2, proj); + for (j = 0; j < 3; j++) + if ((proj[j] > lp1[j] && proj[j] > lp2[j]) || + (proj[j] < lp1[j] && proj[j] < lp2[j])) + break; + if (j < 3) { + if (fabs(proj[j] - lp1[j]) < fabs(proj[j] - lp2[j])) + VectorSubtract(p, lp1, dir); + else + VectorSubtract(p, lp2, dir); + return VectorLengthSquared(dir); + } + VectorSubtract(p, proj, dir); + return VectorLengthSquared(dir); +} //end of the function DistanceFromLineSquared +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float VectorDistanceSquared(vec3_t p1, vec3_t p2) +{ + vec3_t dir; + VectorSubtract(p2, p1, dir); + return VectorLengthSquared(dir); +} //end of the function VectorDistanceSquared +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotAvoidSpots(vec3_t origin, aas_reachability_t *reach, bot_avoidspot_t *avoidspots, int numavoidspots) +{ + int checkbetween, i, type; + float squareddist, squaredradius; + + switch(reach->traveltype & TRAVELTYPE_MASK) + { + case TRAVEL_WALK: checkbetween = qtrue; break; + case TRAVEL_CROUCH: checkbetween = qtrue; break; + case TRAVEL_BARRIERJUMP: checkbetween = qtrue; break; + case TRAVEL_LADDER: checkbetween = qtrue; break; + case TRAVEL_WALKOFFLEDGE: checkbetween = qfalse; break; + case TRAVEL_JUMP: checkbetween = qfalse; break; + case TRAVEL_SWIM: checkbetween = qtrue; break; + case TRAVEL_WATERJUMP: checkbetween = qtrue; break; + case TRAVEL_TELEPORT: checkbetween = qfalse; break; + case TRAVEL_ELEVATOR: checkbetween = qfalse; break; + case TRAVEL_GRAPPLEHOOK: checkbetween = qfalse; break; + case TRAVEL_ROCKETJUMP: checkbetween = qfalse; break; + case TRAVEL_BFGJUMP: checkbetween = qfalse; break; + case TRAVEL_JUMPPAD: checkbetween = qfalse; break; + case TRAVEL_FUNCBOB: checkbetween = qfalse; break; + default: checkbetween = qtrue; break; + } //end switch + + type = AVOID_CLEAR; + for (i = 0; i < numavoidspots; i++) + { + squaredradius = Square(avoidspots[i].radius); + squareddist = DistanceFromLineSquared(avoidspots[i].origin, origin, reach->start); + // if moving towards the avoid spot + if (squareddist < squaredradius && + VectorDistanceSquared(avoidspots[i].origin, origin) > squareddist) + { + type = avoidspots[i].type; + } //end if + else if (checkbetween) { + squareddist = DistanceFromLineSquared(avoidspots[i].origin, reach->start, reach->end); + // if moving towards the avoid spot + if (squareddist < squaredradius && + VectorDistanceSquared(avoidspots[i].origin, reach->start) > squareddist) + { + type = avoidspots[i].type; + } //end if + } //end if + else + { + VectorDistanceSquared(avoidspots[i].origin, reach->end); + // if the reachability leads closer to the avoid spot + if (squareddist < squaredradius && + VectorDistanceSquared(avoidspots[i].origin, reach->start) > squareddist) + { + type = avoidspots[i].type; + } //end if + } //end else + if (type == AVOID_ALWAYS) + return type; + } //end for + return type; +} //end of the function BotAvoidSpots +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotAddAvoidSpot(int movestate, vec3_t origin, float radius, int type) +{ + bot_movestate_t *ms; + + ms = BotMoveStateFromHandle(movestate); + if (!ms) return; + if (type == AVOID_CLEAR) + { + ms->numavoidspots = 0; + return; + } //end if + + if (ms->numavoidspots >= MAX_AVOIDSPOTS) + return; + VectorCopy(origin, ms->avoidspots[ms->numavoidspots].origin); + ms->avoidspots[ms->numavoidspots].radius = radius; + ms->avoidspots[ms->numavoidspots].type = type; + ms->numavoidspots++; +} //end of the function BotAddAvoidSpot +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotGetReachabilityToGoal(vec3_t origin, int areanum, + int lastgoalareanum, int lastareanum, + int *avoidreach, float *avoidreachtimes, int *avoidreachtries, + bot_goal_t *goal, int travelflags, int movetravelflags, + struct bot_avoidspot_s *avoidspots, int numavoidspots, int *flags) +{ + int i, t, besttime, bestreachnum, reachnum; + aas_reachability_t reach; + + //if not in a valid area + if (!areanum) return 0; + // + if (AAS_AreaDoNotEnter(areanum) || AAS_AreaDoNotEnter(goal->areanum)) + { + travelflags |= TFL_DONOTENTER; + movetravelflags |= TFL_DONOTENTER; + } //end if + //use the routing to find the next area to go to + besttime = 0; + bestreachnum = 0; + // + for (reachnum = AAS_NextAreaReachability(areanum, 0); reachnum; + reachnum = AAS_NextAreaReachability(areanum, reachnum)) + { +#ifdef AVOIDREACH + //check if it isn't an reachability to avoid + for (i = 0; i < MAX_AVOIDREACH; i++) + { + if (avoidreach[i] == reachnum && avoidreachtimes[i] >= AAS_Time()) break; + } //end for + if (i != MAX_AVOIDREACH && avoidreachtries[i] > AVOIDREACH_TRIES) + { +#ifdef DEBUG + if (bot_developer) + { + botimport.Print(PRT_MESSAGE, "avoiding reachability %d\n", avoidreach[i]); + } //end if +#endif //DEBUG + continue; + } //end if +#endif //AVOIDREACH + //get the reachability from the number + AAS_ReachabilityFromNum(reachnum, &reach); + //NOTE: do not go back to the previous area if the goal didn't change + //NOTE: is this actually avoidance of local routing minima between two areas??? + if (lastgoalareanum == goal->areanum && reach.areanum == lastareanum) continue; + //if (AAS_AreaContentsTravelFlags(reach.areanum) & ~travelflags) continue; + //if the travel isn't valid + if (!BotValidTravel(origin, &reach, movetravelflags)) continue; + //get the travel time + t = AAS_AreaTravelTimeToGoalArea(reach.areanum, reach.end, goal->areanum, travelflags); + //if the goal area isn't reachable from the reachable area + if (!t) continue; + //if the bot should not use this reachability to avoid bad spots + if (BotAvoidSpots(origin, &reach, avoidspots, numavoidspots)) { + if (flags) { + *flags |= MOVERESULT_BLOCKEDBYAVOIDSPOT; + } + continue; + } + //add the travel time towards the area + t += reach.traveltime;// + AAS_AreaTravelTime(areanum, origin, reach.start); + //if the travel time is better than the ones already found + if (!besttime || t < besttime) + { + besttime = t; + bestreachnum = reachnum; + } //end if + } //end for + // + return bestreachnum; +} //end of the function BotGetReachabilityToGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotAddToTarget(vec3_t start, vec3_t end, float maxdist, float *dist, vec3_t target) +{ + vec3_t dir; + float curdist; + + VectorSubtract(end, start, dir); + curdist = VectorNormalize(dir); + if (*dist + curdist < maxdist) + { + VectorCopy(end, target); + *dist += curdist; + return qfalse; + } //end if + else + { + VectorMA(start, maxdist - *dist, dir, target); + *dist = maxdist; + return qtrue; + } //end else +} //end of the function BotAddToTarget + +int BotMovementViewTarget(int movestate, bot_goal_t *goal, int travelflags, float lookahead, vec3_t target) +{ + aas_reachability_t reach; + int reachnum, lastareanum; + bot_movestate_t *ms; + vec3_t end; + float dist; + + ms = BotMoveStateFromHandle(movestate); + if (!ms) return qfalse; + reachnum = 0; + //if the bot has no goal or no last reachability + if (!ms->lastreachnum || !goal) return qfalse; + + reachnum = ms->lastreachnum; + VectorCopy(ms->origin, end); + lastareanum = ms->lastareanum; + dist = 0; + while(reachnum && dist < lookahead) + { + AAS_ReachabilityFromNum(reachnum, &reach); + if (BotAddToTarget(end, reach.start, lookahead, &dist, target)) return qtrue; + //never look beyond teleporters + if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_TELEPORT) return qtrue; + //never look beyond the weapon jump point + if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_ROCKETJUMP) return qtrue; + if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_BFGJUMP) return qtrue; + //don't add jump pad distances + if ((reach.traveltype & TRAVELTYPE_MASK) != TRAVEL_JUMPPAD && + (reach.traveltype & TRAVELTYPE_MASK) != TRAVEL_ELEVATOR && + (reach.traveltype & TRAVELTYPE_MASK) != TRAVEL_FUNCBOB) + { + if (BotAddToTarget(reach.start, reach.end, lookahead, &dist, target)) return qtrue; + } //end if + reachnum = BotGetReachabilityToGoal(reach.end, reach.areanum, + ms->lastgoalareanum, lastareanum, + ms->avoidreach, ms->avoidreachtimes, ms->avoidreachtries, + goal, travelflags, travelflags, NULL, 0, NULL); + VectorCopy(reach.end, end); + lastareanum = reach.areanum; + if (lastareanum == goal->areanum) + { + BotAddToTarget(reach.end, goal->origin, lookahead, &dist, target); + return qtrue; + } //end if + } //end while + // + return qfalse; +} //end of the function BotMovementViewTarget +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotVisible(int ent, vec3_t eye, vec3_t target) +{ + bsp_trace_t trace; + + trace = AAS_Trace(eye, NULL, NULL, target, ent, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); + if (trace.fraction >= 1) return qtrue; + return qfalse; +} //end of the function BotVisible +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotPredictVisiblePosition(vec3_t origin, int areanum, bot_goal_t *goal, int travelflags, vec3_t target) +{ + aas_reachability_t reach; + int reachnum, lastgoalareanum, lastareanum, i; + int avoidreach[MAX_AVOIDREACH]; + float avoidreachtimes[MAX_AVOIDREACH]; + int avoidreachtries[MAX_AVOIDREACH]; + vec3_t end; + + //if the bot has no goal or no last reachability + if (!goal) return qfalse; + //if the areanum is not valid + if (!areanum) return qfalse; + //if the goal areanum is not valid + if (!goal->areanum) return qfalse; + + Com_Memset(avoidreach, 0, MAX_AVOIDREACH * sizeof(int)); + lastgoalareanum = goal->areanum; + lastareanum = areanum; + VectorCopy(origin, end); + //only do 20 hops + for (i = 0; i < 20 && (areanum != goal->areanum); i++) + { + // + reachnum = BotGetReachabilityToGoal(end, areanum, + lastgoalareanum, lastareanum, + avoidreach, avoidreachtimes, avoidreachtries, + goal, travelflags, travelflags, NULL, 0, NULL); + if (!reachnum) return qfalse; + AAS_ReachabilityFromNum(reachnum, &reach); + // + if (BotVisible(goal->entitynum, goal->origin, reach.start)) + { + VectorCopy(reach.start, target); + return qtrue; + } //end if + // + if (BotVisible(goal->entitynum, goal->origin, reach.end)) + { + VectorCopy(reach.end, target); + return qtrue; + } //end if + // + if (reach.areanum == goal->areanum) + { + VectorCopy(reach.end, target); + return qtrue; + } //end if + // + lastareanum = areanum; + areanum = reach.areanum; + VectorCopy(reach.end, end); + // + } //end while + // + return qfalse; +} //end of the function BotPredictVisiblePosition +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void MoverBottomCenter(aas_reachability_t *reach, vec3_t bottomcenter) +{ + int modelnum; + vec3_t mins, maxs, origin, mids; + vec3_t angles = {0, 0, 0}; + + modelnum = reach->facenum & 0x0000FFFF; + //get some bsp model info + AAS_BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, origin); + // + if (!AAS_OriginOfMoverWithModelNum(modelnum, origin)) + { + botimport.Print(PRT_MESSAGE, "no entity with model %d\n", modelnum); + } //end if + //get a point just above the plat in the bottom position + VectorAdd(mins, maxs, mids); + VectorMA(origin, 0.5, mids, bottomcenter); + bottomcenter[2] = reach->start[2]; +} //end of the function MoverBottomCenter +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float BotGapDistance(vec3_t origin, vec3_t hordir, int entnum) +{ + float dist, startz; + vec3_t start, end; + aas_trace_t trace; + + //do gap checking + startz = origin[2]; + //this enables walking down stairs more fluidly + { + VectorCopy(origin, start); + VectorCopy(origin, end); + end[2] -= 60; + trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, entnum); + if (trace.fraction >= 1) return 1; + startz = trace.endpos[2] + 1; + } + // + for (dist = 8; dist <= 100; dist += 8) + { + VectorMA(origin, dist, hordir, start); + start[2] = startz + 24; + VectorCopy(start, end); + end[2] -= 48 + sv_maxbarrier->value; + trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, entnum); + //if solid is found the bot can't walk any further and fall into a gap + if (!trace.startsolid) + { + //if it is a gap + if (trace.endpos[2] < startz - sv_maxstep->value - 8) + { + VectorCopy(trace.endpos, end); + end[2] -= 20; + if (AAS_PointContents(end) & CONTENTS_WATER) break; + //if a gap is found slow down + //botimport.Print(PRT_MESSAGE, "gap at %f\n", dist); + return dist; + } //end if + startz = trace.endpos[2]; + } //end if + } //end for + return 0; +} //end of the function BotGapDistance +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotCheckBarrierJump(bot_movestate_t *ms, vec3_t dir, float speed) +{ + vec3_t start, hordir, end; + aas_trace_t trace; + + VectorCopy(ms->origin, end); + end[2] += sv_maxbarrier->value; + //trace right up + trace = AAS_TraceClientBBox(ms->origin, end, PRESENCE_NORMAL, ms->entitynum); + //this shouldn't happen... but we check anyway + if (trace.startsolid) return qfalse; + //if very low ceiling it isn't possible to jump up to a barrier + if (trace.endpos[2] - ms->origin[2] < sv_maxstep->value) return qfalse; + // + hordir[0] = dir[0]; + hordir[1] = dir[1]; + hordir[2] = 0; + VectorNormalize(hordir); + VectorMA(ms->origin, ms->thinktime * speed * 0.5, hordir, end); + VectorCopy(trace.endpos, start); + end[2] = trace.endpos[2]; + //trace from previous trace end pos horizontally in the move direction + trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, ms->entitynum); + //again this shouldn't happen + if (trace.startsolid) return qfalse; + // + VectorCopy(trace.endpos, start); + VectorCopy(trace.endpos, end); + end[2] = ms->origin[2]; + //trace down from the previous trace end pos + trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, ms->entitynum); + //if solid + if (trace.startsolid) return qfalse; + //if no obstacle at all + if (trace.fraction >= 1.0) return qfalse; + //if less than the maximum step height + if (trace.endpos[2] - ms->origin[2] < sv_maxstep->value) return qfalse; + // + EA_Jump(ms->client); + EA_Move(ms->client, hordir, speed); + ms->moveflags |= MFL_BARRIERJUMP; + //there is a barrier + return qtrue; +} //end of the function BotCheckBarrierJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotSwimInDirection(bot_movestate_t *ms, vec3_t dir, float speed, int type) +{ + vec3_t normdir; + + VectorCopy(dir, normdir); + VectorNormalize(normdir); + EA_Move(ms->client, normdir, speed); + return qtrue; +} //end of the function BotSwimInDirection +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotWalkInDirection(bot_movestate_t *ms, vec3_t dir, float speed, int type) +{ + vec3_t hordir, cmdmove, velocity, tmpdir, origin; + int presencetype, maxframes, cmdframes, stopevent; + aas_clientmove_t move; + float dist; + + if (AAS_OnGround(ms->origin, ms->presencetype, ms->entitynum)) ms->moveflags |= MFL_ONGROUND; + //if the bot is on the ground + if (ms->moveflags & MFL_ONGROUND) + { + //if there is a barrier the bot can jump on + if (BotCheckBarrierJump(ms, dir, speed)) return qtrue; + //remove barrier jump flag + ms->moveflags &= ~MFL_BARRIERJUMP; + //get the presence type for the movement + if ((type & MOVE_CROUCH) && !(type & MOVE_JUMP)) presencetype = PRESENCE_CROUCH; + else presencetype = PRESENCE_NORMAL; + //horizontal direction + hordir[0] = dir[0]; + hordir[1] = dir[1]; + hordir[2] = 0; + VectorNormalize(hordir); + //if the bot is not supposed to jump + if (!(type & MOVE_JUMP)) + { + //if there is a gap, try to jump over it + if (BotGapDistance(ms->origin, hordir, ms->entitynum) > 0) type |= MOVE_JUMP; + } //end if + //get command movement + VectorScale(hordir, speed, cmdmove); + VectorCopy(ms->velocity, velocity); + // + if (type & MOVE_JUMP) + { + //botimport.Print(PRT_MESSAGE, "trying jump\n"); + cmdmove[2] = 400; + maxframes = PREDICTIONTIME_JUMP / 0.1; + cmdframes = 1; + stopevent = SE_HITGROUND|SE_HITGROUNDDAMAGE| + SE_ENTERWATER|SE_ENTERSLIME|SE_ENTERLAVA; + } //end if + else + { + maxframes = 2; + cmdframes = 2; + stopevent = SE_HITGROUNDDAMAGE| + SE_ENTERWATER|SE_ENTERSLIME|SE_ENTERLAVA; + } //end else + //AAS_ClearShownDebugLines(); + // + VectorCopy(ms->origin, origin); + origin[2] += 0.5; + AAS_PredictClientMovement(&move, ms->entitynum, origin, presencetype, qtrue, + velocity, cmdmove, cmdframes, maxframes, 0.1f, + stopevent, 0, qfalse);//qtrue); + //if prediction time wasn't enough to fully predict the movement + if (move.frames >= maxframes && (type & MOVE_JUMP)) + { + //botimport.Print(PRT_MESSAGE, "client %d: max prediction frames\n", ms->client); + return qfalse; + } //end if + //don't enter slime or lava and don't fall from too high + if (move.stopevent & (SE_ENTERSLIME|SE_ENTERLAVA|SE_HITGROUNDDAMAGE)) + { + //botimport.Print(PRT_MESSAGE, "client %d: would be hurt ", ms->client); + //if (move.stopevent & SE_ENTERSLIME) botimport.Print(PRT_MESSAGE, "slime\n"); + //if (move.stopevent & SE_ENTERLAVA) botimport.Print(PRT_MESSAGE, "lava\n"); + //if (move.stopevent & SE_HITGROUNDDAMAGE) botimport.Print(PRT_MESSAGE, "hitground\n"); + return qfalse; + } //end if + //if ground was hit + if (move.stopevent & SE_HITGROUND) + { + //check for nearby gap + VectorNormalize2(move.velocity, tmpdir); + dist = BotGapDistance(move.endpos, tmpdir, ms->entitynum); + if (dist > 0) return qfalse; + // + dist = BotGapDistance(move.endpos, hordir, ms->entitynum); + if (dist > 0) return qfalse; + } //end if + //get horizontal movement + tmpdir[0] = move.endpos[0] - ms->origin[0]; + tmpdir[1] = move.endpos[1] - ms->origin[1]; + tmpdir[2] = 0; + // + //AAS_DrawCross(move.endpos, 4, LINECOLOR_BLUE); + //the bot is blocked by something + if (VectorLength(tmpdir) < speed * ms->thinktime * 0.5) return qfalse; + //perform the movement + if (type & MOVE_JUMP) EA_Jump(ms->client); + if (type & MOVE_CROUCH) EA_Crouch(ms->client); + EA_Move(ms->client, hordir, speed); + //movement was succesfull + return qtrue; + } //end if + else + { + if (ms->moveflags & MFL_BARRIERJUMP) + { + //if near the top or going down + if (ms->velocity[2] < 50) + { + EA_Move(ms->client, dir, speed); + } //end if + } //end if + //FIXME: do air control to avoid hazards + return qtrue; + } //end else +} //end of the function BotWalkInDirection +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotMoveInDirection(int movestate, vec3_t dir, float speed, int type) +{ + bot_movestate_t *ms; + + ms = BotMoveStateFromHandle(movestate); + if (!ms) return qfalse; + //if swimming + if (AAS_Swimming(ms->origin)) + { + return BotSwimInDirection(ms, dir, speed, type); + } //end if + else + { + return BotWalkInDirection(ms, dir, speed, type); + } //end else +} //end of the function BotMoveInDirection +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Intersection(vec2_t p1, vec2_t p2, vec2_t p3, vec2_t p4, vec2_t out) +{ + float x1, dx1, dy1, x2, dx2, dy2, d; + + dx1 = p2[0] - p1[0]; + dy1 = p2[1] - p1[1]; + dx2 = p4[0] - p3[0]; + dy2 = p4[1] - p3[1]; + + d = dy1 * dx2 - dx1 * dy2; + if (d != 0) + { + x1 = p1[1] * dx1 - p1[0] * dy1; + x2 = p3[1] * dx2 - p3[0] * dy2; + out[0] = (int) ((dx1 * x2 - dx2 * x1) / d); + out[1] = (int) ((dy1 * x2 - dy2 * x1) / d); + return qtrue; + } //end if + else + { + return qfalse; + } //end else +} //end of the function Intersection +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotCheckBlocked(bot_movestate_t *ms, vec3_t dir, int checkbottom, bot_moveresult_t *result) +{ + vec3_t mins, maxs, end, up = {0, 0, 1}; + bsp_trace_t trace; + + //test for entities obstructing the bot's path + AAS_PresenceTypeBoundingBox(ms->presencetype, mins, maxs); + // + if (fabs(DotProduct(dir, up)) < 0.7) + { + mins[2] += sv_maxstep->value; //if the bot can step on + maxs[2] -= 10; //a little lower to avoid low ceiling + } //end if + VectorMA(ms->origin, 3, dir, end); + trace = AAS_Trace(ms->origin, mins, maxs, end, ms->entitynum, CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_BODY); + //if not started in solid and not hitting the world entity + if (!trace.startsolid && (trace.ent != ENTITYNUM_WORLD && trace.ent != ENTITYNUM_NONE) ) + { + result->blocked = qtrue; + result->blockentity = trace.ent; +#ifdef DEBUG + //botimport.Print(PRT_MESSAGE, "%d: BotCheckBlocked: I'm blocked\n", ms->client); +#endif //DEBUG + } //end if + //if not in an area with reachability + else if (checkbottom && !AAS_AreaReachability(ms->areanum)) + { + //check if the bot is standing on something + AAS_PresenceTypeBoundingBox(ms->presencetype, mins, maxs); + VectorMA(ms->origin, -3, up, end); + trace = AAS_Trace(ms->origin, mins, maxs, end, ms->entitynum, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); + if (!trace.startsolid && (trace.ent != ENTITYNUM_WORLD && trace.ent != ENTITYNUM_NONE) ) + { + result->blocked = qtrue; + result->blockentity = trace.ent; + result->flags |= MOVERESULT_ONTOPOFOBSTACLE; +#ifdef DEBUG + //botimport.Print(PRT_MESSAGE, "%d: BotCheckBlocked: I'm blocked\n", ms->client); +#endif //DEBUG + } //end if + } //end else +} //end of the function BotCheckBlocked +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotClearMoveResult(bot_moveresult_t *moveresult) +{ + moveresult->failure = qfalse; + moveresult->type = 0; + moveresult->blocked = qfalse; + moveresult->blockentity = 0; + moveresult->traveltype = 0; + moveresult->flags = 0; +} //end of the function BotClearMoveResult +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_Walk(bot_movestate_t *ms, aas_reachability_t *reach) +{ + float dist, speed; + vec3_t hordir; + bot_moveresult_t result; + + BotClearMoveResult(&result); + //first walk straight to the reachability start + hordir[0] = reach->start[0] - ms->origin[0]; + hordir[1] = reach->start[1] - ms->origin[1]; + hordir[2] = 0; + dist = VectorNormalize(hordir); + // + BotCheckBlocked(ms, hordir, qtrue, &result); + // + if (dist < 10) + { + //walk straight to the reachability end + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + dist = VectorNormalize(hordir); + } //end if + //if going towards a crouch area + if (!(AAS_AreaPresenceType(reach->areanum) & PRESENCE_NORMAL)) + { + //if pretty close to the reachable area + if (dist < 20) EA_Crouch(ms->client); + } //end if + // + dist = BotGapDistance(ms->origin, hordir, ms->entitynum); + // + if (ms->moveflags & MFL_WALK) + { + if (dist > 0) speed = 200 - (180 - 1 * dist); + else speed = 200; + EA_Walk(ms->client); + } //end if + else + { + if (dist > 0) speed = 400 - (360 - 2 * dist); + else speed = 400; + } //end else + //elemantary action move in direction + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotTravel_Walk +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_Walk(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir; + float dist, speed; + bot_moveresult_t result; + + BotClearMoveResult(&result); + //if not on the ground and changed areas... don't walk back!! + //(doesn't seem to help) + /* + ms->areanum = BotFuzzyPointReachabilityArea(ms->origin); + if (ms->areanum == reach->areanum) + { +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "BotFinishTravel_Walk: already in reach area\n"); +#endif //DEBUG + return result; + } //end if*/ + //go straight to the reachability end + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + dist = VectorNormalize(hordir); + // + if (dist > 100) dist = 100; + speed = 400 - (400 - 3 * dist); + // + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotFinishTravel_Walk +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_Crouch(bot_movestate_t *ms, aas_reachability_t *reach) +{ + float speed; + vec3_t hordir; + bot_moveresult_t result; + + BotClearMoveResult(&result); + // + speed = 400; + //walk straight to reachability end + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + // + BotCheckBlocked(ms, hordir, qtrue, &result); + //elemantary actions + EA_Crouch(ms->client); + EA_Move(ms->client, hordir, speed); + // + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotTravel_Crouch +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_BarrierJump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + float dist, speed; + vec3_t hordir; + bot_moveresult_t result; + + BotClearMoveResult(&result); + //walk straight to reachability start + hordir[0] = reach->start[0] - ms->origin[0]; + hordir[1] = reach->start[1] - ms->origin[1]; + hordir[2] = 0; + dist = VectorNormalize(hordir); + // + BotCheckBlocked(ms, hordir, qtrue, &result); + //if pretty close to the barrier + if (dist < 9) + { + EA_Jump(ms->client); + } //end if + else + { + if (dist > 60) dist = 60; + speed = 360 - (360 - 6 * dist); + EA_Move(ms->client, hordir, speed); + } //end else + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotTravel_BarrierJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_BarrierJump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + float dist; + vec3_t hordir; + bot_moveresult_t result; + + BotClearMoveResult(&result); + //if near the top or going down + if (ms->velocity[2] < 250) + { + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + dist = VectorNormalize(hordir); + // + BotCheckBlocked(ms, hordir, qtrue, &result); + // + EA_Move(ms->client, hordir, 400); + VectorCopy(hordir, result.movedir); + } //end if + // + return result; +} //end of the function BotFinishTravel_BarrierJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_Swim(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t dir; + bot_moveresult_t result; + + BotClearMoveResult(&result); + //swim straight to reachability end + VectorSubtract(reach->start, ms->origin, dir); + VectorNormalize(dir); + // + BotCheckBlocked(ms, dir, qtrue, &result); + //elemantary actions + EA_Move(ms->client, dir, 400); + // + VectorCopy(dir, result.movedir); + Vector2Angles(dir, result.ideal_viewangles); + result.flags |= MOVERESULT_SWIMVIEW; + // + return result; +} //end of the function BotTravel_Swim +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_WaterJump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t dir, hordir; + float dist; + bot_moveresult_t result; + + BotClearMoveResult(&result); + //swim straight to reachability end + VectorSubtract(reach->end, ms->origin, dir); + VectorCopy(dir, hordir); + hordir[2] = 0; + dir[2] += 15 + crandom() * 40; + //botimport.Print(PRT_MESSAGE, "BotTravel_WaterJump: dir[2] = %f\n", dir[2]); + VectorNormalize(dir); + dist = VectorNormalize(hordir); + //elemantary actions + //EA_Move(ms->client, dir, 400); + EA_MoveForward(ms->client); + //move up if close to the actual out of water jump spot + if (dist < 40) EA_MoveUp(ms->client); + //set the ideal view angles + Vector2Angles(dir, result.ideal_viewangles); + result.flags |= MOVERESULT_MOVEMENTVIEW; + // + VectorCopy(dir, result.movedir); + // + return result; +} //end of the function BotTravel_WaterJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_WaterJump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t dir, pnt; + float dist; + bot_moveresult_t result; + + //botimport.Print(PRT_MESSAGE, "BotFinishTravel_WaterJump\n"); + BotClearMoveResult(&result); + //if waterjumping there's nothing to do + if (ms->moveflags & MFL_WATERJUMP) return result; + //if not touching any water anymore don't do anything + //otherwise the bot sometimes keeps jumping? + VectorCopy(ms->origin, pnt); + pnt[2] -= 32; //extra for q2dm4 near red armor/mega health + if (!(AAS_PointContents(pnt) & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER))) return result; + //swim straight to reachability end + VectorSubtract(reach->end, ms->origin, dir); + dir[0] += crandom() * 10; + dir[1] += crandom() * 10; + dir[2] += 70 + crandom() * 10; + dist = VectorNormalize(dir); + //elemantary actions + EA_Move(ms->client, dir, 400); + //set the ideal view angles + Vector2Angles(dir, result.ideal_viewangles); + result.flags |= MOVERESULT_MOVEMENTVIEW; + // + VectorCopy(dir, result.movedir); + // + return result; +} //end of the function BotFinishTravel_WaterJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_WalkOffLedge(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir, dir; + float dist, speed, reachhordist; + bot_moveresult_t result; + + BotClearMoveResult(&result); + //check if the bot is blocked by anything + VectorSubtract(reach->start, ms->origin, dir); + VectorNormalize(dir); + BotCheckBlocked(ms, dir, qtrue, &result); + //if the reachability start and end are practially above each other + VectorSubtract(reach->end, reach->start, dir); + dir[2] = 0; + reachhordist = VectorLength(dir); + //walk straight to the reachability start + hordir[0] = reach->start[0] - ms->origin[0]; + hordir[1] = reach->start[1] - ms->origin[1]; + hordir[2] = 0; + dist = VectorNormalize(hordir); + //if pretty close to the start focus on the reachability end + if (dist < 48) + { + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + // + if (reachhordist < 20) + { + speed = 100; + } //end if + else if (!AAS_HorizontalVelocityForJump(0, reach->start, reach->end, &speed)) + { + speed = 400; + } //end if + } //end if + else + { + if (reachhordist < 20) + { + if (dist > 64) dist = 64; + speed = 400 - (256 - 4 * dist); + } //end if + else + { + speed = 400; + } //end else + } //end else + // + BotCheckBlocked(ms, hordir, qtrue, &result); + //elemantary action + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotTravel_WalkOffLedge +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotAirControl(vec3_t origin, vec3_t velocity, vec3_t goal, vec3_t dir, float *speed) +{ + vec3_t org, vel; + float dist; + int i; + + VectorCopy(origin, org); + VectorScale(velocity, 0.1, vel); + for (i = 0; i < 50; i++) + { + vel[2] -= sv_gravity->value * 0.01; + //if going down and next position would be below the goal + if (vel[2] < 0 && org[2] + vel[2] < goal[2]) + { + VectorScale(vel, (goal[2] - org[2]) / vel[2], vel); + VectorAdd(org, vel, org); + VectorSubtract(goal, org, dir); + dist = VectorNormalize(dir); + if (dist > 32) dist = 32; + *speed = 400 - (400 - 13 * dist); + return qtrue; + } //end if + else + { + VectorAdd(org, vel, org); + } //end else + } //end for + VectorSet(dir, 0, 0, 0); + *speed = 400; + return qfalse; +} //end of the function BotAirControl +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_WalkOffLedge(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t dir, hordir, end, v; + float dist, speed; + bot_moveresult_t result; + + BotClearMoveResult(&result); + // + VectorSubtract(reach->end, ms->origin, dir); + BotCheckBlocked(ms, dir, qtrue, &result); + // + VectorSubtract(reach->end, ms->origin, v); + v[2] = 0; + dist = VectorNormalize(v); + if (dist > 16) VectorMA(reach->end, 16, v, end); + else VectorCopy(reach->end, end); + // + if (!BotAirControl(ms->origin, ms->velocity, end, hordir, &speed)) + { + //go straight to the reachability end + VectorCopy(dir, hordir); + hordir[2] = 0; + // + dist = VectorNormalize(hordir); + speed = 400; + } //end if + // + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotFinishTravel_WalkOffLedge +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +/* +bot_moveresult_t BotTravel_Jump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir; + float dist, gapdist, speed, horspeed, sv_jumpvel; + bot_moveresult_t result; + + BotClearMoveResult(&result); + // + sv_jumpvel = botlibglobals.sv_jumpvel->value; + //walk straight to the reachability start + hordir[0] = reach->start[0] - ms->origin[0]; + hordir[1] = reach->start[1] - ms->origin[1]; + hordir[2] = 0; + dist = VectorNormalize(hordir); + // + speed = 350; + // + gapdist = BotGapDistance(ms, hordir, ms->entitynum); + //if pretty close to the start focus on the reachability end + if (dist < 50 || (gapdist && gapdist < 50)) + { + //NOTE: using max speed (400) works best + //if (AAS_HorizontalVelocityForJump(sv_jumpvel, ms->origin, reach->end, &horspeed)) + //{ + // speed = horspeed * 400 / botlibglobals.sv_maxwalkvelocity->value; + //} //end if + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + VectorNormalize(hordir); + //elemantary action jump + EA_Jump(ms->client); + // + ms->jumpreach = ms->lastreachnum; + speed = 600; + } //end if + else + { + if (AAS_HorizontalVelocityForJump(sv_jumpvel, reach->start, reach->end, &horspeed)) + { + speed = horspeed * 400 / botlibglobals.sv_maxwalkvelocity->value; + } //end if + } //end else + //elemantary action + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotTravel_Jump*/ +/* +bot_moveresult_t BotTravel_Jump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir, dir1, dir2, mins, maxs, start, end; + float dist1, dist2, speed; + bot_moveresult_t result; + bsp_trace_t trace; + + BotClearMoveResult(&result); + // + hordir[0] = reach->start[0] - reach->end[0]; + hordir[1] = reach->start[1] - reach->end[1]; + hordir[2] = 0; + VectorNormalize(hordir); + // + VectorCopy(reach->start, start); + start[2] += 1; + //minus back the bouding box size plus 16 + VectorMA(reach->start, 80, hordir, end); + // + AAS_PresenceTypeBoundingBox(PRESENCE_NORMAL, mins, maxs); + //check for solids + trace = AAS_Trace(start, mins, maxs, end, ms->entitynum, MASK_PLAYERSOLID); + if (trace.startsolid) VectorCopy(start, trace.endpos); + //check for a gap + for (dist1 = 0; dist1 < 80; dist1 += 10) + { + VectorMA(start, dist1+10, hordir, end); + end[2] += 1; + if (AAS_PointAreaNum(end) != ms->reachareanum) break; + } //end for + if (dist1 < 80) VectorMA(reach->start, dist1, hordir, trace.endpos); +// dist1 = BotGapDistance(start, hordir, ms->entitynum); +// if (dist1 && dist1 <= trace.fraction * 80) VectorMA(reach->start, dist1-20, hordir, trace.endpos); + // + VectorSubtract(ms->origin, reach->start, dir1); + dir1[2] = 0; + dist1 = VectorNormalize(dir1); + VectorSubtract(ms->origin, trace.endpos, dir2); + dir2[2] = 0; + dist2 = VectorNormalize(dir2); + //if just before the reachability start + if (DotProduct(dir1, dir2) < -0.8 || dist2 < 5) + { + //botimport.Print(PRT_MESSAGE, "between jump start and run to point\n"); + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + //elemantary action jump + if (dist1 < 24) EA_Jump(ms->client); + else if (dist1 < 32) EA_DelayedJump(ms->client); + EA_Move(ms->client, hordir, 600); + // + ms->jumpreach = ms->lastreachnum; + } //end if + else + { + //botimport.Print(PRT_MESSAGE, "going towards run to point\n"); + hordir[0] = trace.endpos[0] - ms->origin[0]; + hordir[1] = trace.endpos[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + // + if (dist2 > 80) dist2 = 80; + speed = 400 - (400 - 5 * dist2); + EA_Move(ms->client, hordir, speed); + } //end else + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotTravel_Jump*/ +//* +bot_moveresult_t BotTravel_Jump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir, dir1, dir2, start, end, runstart; +// vec3_t runstart, dir1, dir2, hordir; + float dist1, dist2, speed; + bot_moveresult_t result; + + BotClearMoveResult(&result); + // + AAS_JumpReachRunStart(reach, runstart); + //* + hordir[0] = runstart[0] - reach->start[0]; + hordir[1] = runstart[1] - reach->start[1]; + hordir[2] = 0; + VectorNormalize(hordir); + // + VectorCopy(reach->start, start); + start[2] += 1; + VectorMA(reach->start, 80, hordir, runstart); + //check for a gap + for (dist1 = 0; dist1 < 80; dist1 += 10) + { + VectorMA(start, dist1+10, hordir, end); + end[2] += 1; + if (AAS_PointAreaNum(end) != ms->reachareanum) break; + } //end for + if (dist1 < 80) VectorMA(reach->start, dist1, hordir, runstart); + // + VectorSubtract(ms->origin, reach->start, dir1); + dir1[2] = 0; + dist1 = VectorNormalize(dir1); + VectorSubtract(ms->origin, runstart, dir2); + dir2[2] = 0; + dist2 = VectorNormalize(dir2); + //if just before the reachability start + if (DotProduct(dir1, dir2) < -0.8 || dist2 < 5) + { +// botimport.Print(PRT_MESSAGE, "between jump start and run start point\n"); + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + //elemantary action jump + if (dist1 < 24) EA_Jump(ms->client); + else if (dist1 < 32) EA_DelayedJump(ms->client); + EA_Move(ms->client, hordir, 600); + // + ms->jumpreach = ms->lastreachnum; + } //end if + else + { +// botimport.Print(PRT_MESSAGE, "going towards run start point\n"); + hordir[0] = runstart[0] - ms->origin[0]; + hordir[1] = runstart[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + // + if (dist2 > 80) dist2 = 80; + speed = 400 - (400 - 5 * dist2); + EA_Move(ms->client, hordir, speed); + } //end else + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotTravel_Jump*/ +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_Jump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir, hordir2; + float speed, dist; + bot_moveresult_t result; + + BotClearMoveResult(&result); + //if not jumped yet + if (!ms->jumpreach) return result; + //go straight to the reachability end + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + dist = VectorNormalize(hordir); + // + hordir2[0] = reach->end[0] - reach->start[0]; + hordir2[1] = reach->end[1] - reach->start[1]; + hordir2[2] = 0; + VectorNormalize(hordir2); + // + if (DotProduct(hordir, hordir2) < -0.5 && dist < 24) return result; + //always use max speed when traveling through the air + speed = 800; + // + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotFinishTravel_Jump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_Ladder(bot_movestate_t *ms, aas_reachability_t *reach) +{ + //float dist, speed; + vec3_t dir, viewdir;//, hordir; + vec3_t origin = {0, 0, 0}; +// vec3_t up = {0, 0, 1}; + bot_moveresult_t result; + + BotClearMoveResult(&result); + // +// if ((ms->moveflags & MFL_AGAINSTLADDER)) + //NOTE: not a good idea for ladders starting in water + // || !(ms->moveflags & MFL_ONGROUND)) + { + //botimport.Print(PRT_MESSAGE, "against ladder or not on ground\n"); + VectorSubtract(reach->end, ms->origin, dir); + VectorNormalize(dir); + //set the ideal view angles, facing the ladder up or down + viewdir[0] = dir[0]; + viewdir[1] = dir[1]; + viewdir[2] = 3 * dir[2]; + Vector2Angles(viewdir, result.ideal_viewangles); + //elemantary action + EA_Move(ms->client, origin, 0); + EA_MoveForward(ms->client); + //set movement view flag so the AI can see the view is focussed + result.flags |= MOVERESULT_MOVEMENTVIEW; + } //end if +/* else + { + //botimport.Print(PRT_MESSAGE, "moving towards ladder\n"); + VectorSubtract(reach->end, ms->origin, dir); + //make sure the horizontal movement is large anough + VectorCopy(dir, hordir); + hordir[2] = 0; + dist = VectorNormalize(hordir); + // + dir[0] = hordir[0]; + dir[1] = hordir[1]; + if (dir[2] > 0) dir[2] = 1; + else dir[2] = -1; + if (dist > 50) dist = 50; + speed = 400 - (200 - 4 * dist); + EA_Move(ms->client, dir, speed); + } //end else*/ + //save the movement direction + VectorCopy(dir, result.movedir); + // + return result; +} //end of the function BotTravel_Ladder +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_Teleport(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir; + float dist; + bot_moveresult_t result; + + BotClearMoveResult(&result); + //if the bot is being teleported + if (ms->moveflags & MFL_TELEPORTED) return result; + + //walk straight to center of the teleporter + VectorSubtract(reach->start, ms->origin, hordir); + if (!(ms->moveflags & MFL_SWIMMING)) hordir[2] = 0; + dist = VectorNormalize(hordir); + // + BotCheckBlocked(ms, hordir, qtrue, &result); + + if (dist < 30) EA_Move(ms->client, hordir, 200); + else EA_Move(ms->client, hordir, 400); + + if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW; + + VectorCopy(hordir, result.movedir); + return result; +} //end of the function BotTravel_Teleport +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_Elevator(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t dir, dir1, dir2, hordir, bottomcenter; + float dist, dist1, dist2, speed; + bot_moveresult_t result; + + BotClearMoveResult(&result); + //if standing on the plat + if (BotOnMover(ms->origin, ms->entitynum, reach)) + { +#ifdef DEBUG_ELEVATOR + botimport.Print(PRT_MESSAGE, "bot on elevator\n"); +#endif //DEBUG_ELEVATOR + //if vertically not too far from the end point + if (abs(ms->origin[2] - reach->end[2]) < sv_maxbarrier->value) + { +#ifdef DEBUG_ELEVATOR + botimport.Print(PRT_MESSAGE, "bot moving to end\n"); +#endif //DEBUG_ELEVATOR + //move to the end point + VectorSubtract(reach->end, ms->origin, hordir); + hordir[2] = 0; + VectorNormalize(hordir); + if (!BotCheckBarrierJump(ms, hordir, 100)) + { + EA_Move(ms->client, hordir, 400); + } //end if + VectorCopy(hordir, result.movedir); + } //end else + //if not really close to the center of the elevator + else + { + MoverBottomCenter(reach, bottomcenter); + VectorSubtract(bottomcenter, ms->origin, hordir); + hordir[2] = 0; + dist = VectorNormalize(hordir); + // + if (dist > 10) + { +#ifdef DEBUG_ELEVATOR + botimport.Print(PRT_MESSAGE, "bot moving to center\n"); +#endif //DEBUG_ELEVATOR + //move to the center of the plat + if (dist > 100) dist = 100; + speed = 400 - (400 - 4 * dist); + // + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + } //end if + } //end else + } //end if + else + { +#ifdef DEBUG_ELEVATOR + botimport.Print(PRT_MESSAGE, "bot not on elevator\n"); +#endif //DEBUG_ELEVATOR + //if very near the reachability end + VectorSubtract(reach->end, ms->origin, dir); + dist = VectorLength(dir); + if (dist < 64) + { + if (dist > 60) dist = 60; + speed = 360 - (360 - 6 * dist); + // + if ((ms->moveflags & MFL_SWIMMING) || !BotCheckBarrierJump(ms, dir, 50)) + { + if (speed > 5) EA_Move(ms->client, dir, speed); + } //end if + VectorCopy(dir, result.movedir); + // + if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW; + //stop using this reachability + ms->reachability_time = 0; + return result; + } //end if + //get direction and distance to reachability start + VectorSubtract(reach->start, ms->origin, dir1); + if (!(ms->moveflags & MFL_SWIMMING)) dir1[2] = 0; + dist1 = VectorNormalize(dir1); + //if the elevator isn't down + if (!MoverDown(reach)) + { +#ifdef DEBUG_ELEVATOR + botimport.Print(PRT_MESSAGE, "elevator not down\n"); +#endif //DEBUG_ELEVATOR + dist = dist1; + VectorCopy(dir1, dir); + // + BotCheckBlocked(ms, dir, qfalse, &result); + // + if (dist > 60) dist = 60; + speed = 360 - (360 - 6 * dist); + // + if (!(ms->moveflags & MFL_SWIMMING) && !BotCheckBarrierJump(ms, dir, 50)) + { + if (speed > 5) EA_Move(ms->client, dir, speed); + } //end if + VectorCopy(dir, result.movedir); + // + if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW; + //this isn't a failure... just wait till the elevator comes down + result.type = RESULTTYPE_ELEVATORUP; + result.flags |= MOVERESULT_WAITING; + return result; + } //end if + //get direction and distance to elevator bottom center + MoverBottomCenter(reach, bottomcenter); + VectorSubtract(bottomcenter, ms->origin, dir2); + if (!(ms->moveflags & MFL_SWIMMING)) dir2[2] = 0; + dist2 = VectorNormalize(dir2); + //if very close to the reachability start or + //closer to the elevator center or + //between reachability start and elevator center + if (dist1 < 20 || dist2 < dist1 || DotProduct(dir1, dir2) < 0) + { +#ifdef DEBUG_ELEVATOR + botimport.Print(PRT_MESSAGE, "bot moving to center\n"); +#endif //DEBUG_ELEVATOR + dist = dist2; + VectorCopy(dir2, dir); + } //end if + else //closer to the reachability start + { +#ifdef DEBUG_ELEVATOR + botimport.Print(PRT_MESSAGE, "bot moving to start\n"); +#endif //DEBUG_ELEVATOR + dist = dist1; + VectorCopy(dir1, dir); + } //end else + // + BotCheckBlocked(ms, dir, qfalse, &result); + // + if (dist > 60) dist = 60; + speed = 400 - (400 - 6 * dist); + // + if (!(ms->moveflags & MFL_SWIMMING) && !BotCheckBarrierJump(ms, dir, 50)) + { + EA_Move(ms->client, dir, speed); + } //end if + VectorCopy(dir, result.movedir); + // + if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW; + } //end else + return result; +} //end of the function BotTravel_Elevator +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_Elevator(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t bottomcenter, bottomdir, topdir; + bot_moveresult_t result; + + BotClearMoveResult(&result); + // + MoverBottomCenter(reach, bottomcenter); + VectorSubtract(bottomcenter, ms->origin, bottomdir); + // + VectorSubtract(reach->end, ms->origin, topdir); + // + if (fabs(bottomdir[2]) < fabs(topdir[2])) + { + VectorNormalize(bottomdir); + EA_Move(ms->client, bottomdir, 300); + } //end if + else + { + VectorNormalize(topdir); + EA_Move(ms->client, topdir, 300); + } //end else + return result; +} //end of the function BotFinishTravel_Elevator +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFuncBobStartEnd(aas_reachability_t *reach, vec3_t start, vec3_t end, vec3_t origin) +{ + int spawnflags, modelnum; + vec3_t mins, maxs, mid, angles = {0, 0, 0}; + int num0, num1; + + modelnum = reach->facenum & 0x0000FFFF; + if (!AAS_OriginOfMoverWithModelNum(modelnum, origin)) + { + botimport.Print(PRT_MESSAGE, "BotFuncBobStartEnd: no entity with model %d\n", modelnum); + VectorSet(start, 0, 0, 0); + VectorSet(end, 0, 0, 0); + return; + } //end if + AAS_BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, NULL); + VectorAdd(mins, maxs, mid); + VectorScale(mid, 0.5, mid); + VectorCopy(mid, start); + VectorCopy(mid, end); + spawnflags = reach->facenum >> 16; + num0 = reach->edgenum >> 16; + if (num0 > 0x00007FFF) num0 |= 0xFFFF0000; + num1 = reach->edgenum & 0x0000FFFF; + if (num1 > 0x00007FFF) num1 |= 0xFFFF0000; + if (spawnflags & 1) + { + start[0] = num0; + end[0] = num1; + // + origin[0] += mid[0]; + origin[1] = mid[1]; + origin[2] = mid[2]; + } //end if + else if (spawnflags & 2) + { + start[1] = num0; + end[1] = num1; + // + origin[0] = mid[0]; + origin[1] += mid[1]; + origin[2] = mid[2]; + } //end else if + else + { + start[2] = num0; + end[2] = num1; + // + origin[0] = mid[0]; + origin[1] = mid[1]; + origin[2] += mid[2]; + } //end else +} //end of the function BotFuncBobStartEnd +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_FuncBobbing(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t dir, dir1, dir2, hordir, bottomcenter, bob_start, bob_end, bob_origin; + float dist, dist1, dist2, speed; + bot_moveresult_t result; + + BotClearMoveResult(&result); + // + BotFuncBobStartEnd(reach, bob_start, bob_end, bob_origin); + //if standing ontop of the func_bobbing + if (BotOnMover(ms->origin, ms->entitynum, reach)) + { +#ifdef DEBUG_FUNCBOB + botimport.Print(PRT_MESSAGE, "bot on func_bobbing\n"); +#endif + //if near end point of reachability + VectorSubtract(bob_origin, bob_end, dir); + if (VectorLength(dir) < 24) + { +#ifdef DEBUG_FUNCBOB + botimport.Print(PRT_MESSAGE, "bot moving to reachability end\n"); +#endif + //move to the end point + VectorSubtract(reach->end, ms->origin, hordir); + hordir[2] = 0; + VectorNormalize(hordir); + if (!BotCheckBarrierJump(ms, hordir, 100)) + { + EA_Move(ms->client, hordir, 400); + } //end if + VectorCopy(hordir, result.movedir); + } //end else + //if not really close to the center of the elevator + else + { + MoverBottomCenter(reach, bottomcenter); + VectorSubtract(bottomcenter, ms->origin, hordir); + hordir[2] = 0; + dist = VectorNormalize(hordir); + // + if (dist > 10) + { +#ifdef DEBUG_FUNCBOB + botimport.Print(PRT_MESSAGE, "bot moving to func_bobbing center\n"); +#endif + //move to the center of the plat + if (dist > 100) dist = 100; + speed = 400 - (400 - 4 * dist); + // + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + } //end if + } //end else + } //end if + else + { +#ifdef DEBUG_FUNCBOB + botimport.Print(PRT_MESSAGE, "bot not ontop of func_bobbing\n"); +#endif + //if very near the reachability end + VectorSubtract(reach->end, ms->origin, dir); + dist = VectorLength(dir); + if (dist < 64) + { +#ifdef DEBUG_FUNCBOB + botimport.Print(PRT_MESSAGE, "bot moving to end\n"); +#endif + if (dist > 60) dist = 60; + speed = 360 - (360 - 6 * dist); + //if swimming or no barrier jump + if ((ms->moveflags & MFL_SWIMMING) || !BotCheckBarrierJump(ms, dir, 50)) + { + if (speed > 5) EA_Move(ms->client, dir, speed); + } //end if + VectorCopy(dir, result.movedir); + // + if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW; + //stop using this reachability + ms->reachability_time = 0; + return result; + } //end if + //get direction and distance to reachability start + VectorSubtract(reach->start, ms->origin, dir1); + if (!(ms->moveflags & MFL_SWIMMING)) dir1[2] = 0; + dist1 = VectorNormalize(dir1); + //if func_bobbing is Not it's start position + VectorSubtract(bob_origin, bob_start, dir); + if (VectorLength(dir) > 16) + { +#ifdef DEBUG_FUNCBOB + botimport.Print(PRT_MESSAGE, "func_bobbing not at start\n"); +#endif + dist = dist1; + VectorCopy(dir1, dir); + // + BotCheckBlocked(ms, dir, qfalse, &result); + // + if (dist > 60) dist = 60; + speed = 360 - (360 - 6 * dist); + // + if (!(ms->moveflags & MFL_SWIMMING) && !BotCheckBarrierJump(ms, dir, 50)) + { + if (speed > 5) EA_Move(ms->client, dir, speed); + } //end if + VectorCopy(dir, result.movedir); + // + if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW; + //this isn't a failure... just wait till the func_bobbing arrives + result.type = RESULTTYPE_WAITFORFUNCBOBBING; + result.flags |= MOVERESULT_WAITING; + return result; + } //end if + //get direction and distance to func_bob bottom center + MoverBottomCenter(reach, bottomcenter); + VectorSubtract(bottomcenter, ms->origin, dir2); + if (!(ms->moveflags & MFL_SWIMMING)) dir2[2] = 0; + dist2 = VectorNormalize(dir2); + //if very close to the reachability start or + //closer to the elevator center or + //between reachability start and func_bobbing center + if (dist1 < 20 || dist2 < dist1 || DotProduct(dir1, dir2) < 0) + { +#ifdef DEBUG_FUNCBOB + botimport.Print(PRT_MESSAGE, "bot moving to func_bobbing center\n"); +#endif + dist = dist2; + VectorCopy(dir2, dir); + } //end if + else //closer to the reachability start + { +#ifdef DEBUG_FUNCBOB + botimport.Print(PRT_MESSAGE, "bot moving to reachability start\n"); +#endif + dist = dist1; + VectorCopy(dir1, dir); + } //end else + // + BotCheckBlocked(ms, dir, qfalse, &result); + // + if (dist > 60) dist = 60; + speed = 400 - (400 - 6 * dist); + // + if (!(ms->moveflags & MFL_SWIMMING) && !BotCheckBarrierJump(ms, dir, 50)) + { + EA_Move(ms->client, dir, speed); + } //end if + VectorCopy(dir, result.movedir); + // + if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW; + } //end else + return result; +} //end of the function BotTravel_FuncBobbing +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_FuncBobbing(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t bob_origin, bob_start, bob_end, dir, hordir, bottomcenter; + bot_moveresult_t result; + float dist, speed; + + BotClearMoveResult(&result); + // + BotFuncBobStartEnd(reach, bob_start, bob_end, bob_origin); + // + VectorSubtract(bob_origin, bob_end, dir); + dist = VectorLength(dir); + //if the func_bobbing is near the end + if (dist < 16) + { + VectorSubtract(reach->end, ms->origin, hordir); + if (!(ms->moveflags & MFL_SWIMMING)) hordir[2] = 0; + dist = VectorNormalize(hordir); + // + if (dist > 60) dist = 60; + speed = 360 - (360 - 6 * dist); + // + if (speed > 5) EA_Move(ms->client, dir, speed); + VectorCopy(dir, result.movedir); + // + if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW; + } //end if + else + { + MoverBottomCenter(reach, bottomcenter); + VectorSubtract(bottomcenter, ms->origin, hordir); + if (!(ms->moveflags & MFL_SWIMMING)) hordir[2] = 0; + dist = VectorNormalize(hordir); + // + if (dist > 5) + { + //move to the center of the plat + if (dist > 100) dist = 100; + speed = 400 - (400 - 4 * dist); + // + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + } //end if + } //end else + return result; +} //end of the function BotFinishTravel_FuncBobbing +//=========================================================================== +// 0 no valid grapple hook visible +// 1 the grapple hook is still flying +// 2 the grapple hooked into a wall +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int GrappleState(bot_movestate_t *ms, aas_reachability_t *reach) +{ + int i; + aas_entityinfo_t entinfo; + + //if the grapple hook is pulling + if (ms->moveflags & MFL_GRAPPLEPULL) + return 2; + //check for a visible grapple missile entity + //or visible grapple entity + for (i = AAS_NextEntity(0); i; i = AAS_NextEntity(i)) + { + if (AAS_EntityType(i) == (int) entitytypemissile->value) + { + AAS_EntityInfo(i, &entinfo); + if (entinfo.weapon == (int) weapindex_grapple->value) + { + return 1; + } //end if + } //end if + } //end for + //no valid grapple at all + return 0; +} //end of the function GrappleState +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotResetGrapple(bot_movestate_t *ms) +{ + aas_reachability_t reach; + + AAS_ReachabilityFromNum(ms->lastreachnum, &reach); + //if not using the grapple hook reachability anymore + if ((reach.traveltype & TRAVELTYPE_MASK) != TRAVEL_GRAPPLEHOOK) + { + if ((ms->moveflags & MFL_ACTIVEGRAPPLE) || ms->grapplevisible_time) + { + if (offhandgrapple->value) + EA_Command(ms->client, cmd_grappleoff->string); + ms->moveflags &= ~MFL_ACTIVEGRAPPLE; + ms->grapplevisible_time = 0; +#ifdef DEBUG_GRAPPLE + botimport.Print(PRT_MESSAGE, "reset grapple\n"); +#endif //DEBUG_GRAPPLE + } //end if + } //end if +} //end of the function BotResetGrapple +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_Grapple(bot_movestate_t *ms, aas_reachability_t *reach) +{ + bot_moveresult_t result; + float dist, speed; + vec3_t dir, viewdir, org; + int state, areanum; + bsp_trace_t trace; + +#ifdef DEBUG_GRAPPLE + static int debugline; + if (!debugline) debugline = botimport.DebugLineCreate(); + botimport.DebugLineShow(debugline, reach->start, reach->end, LINECOLOR_BLUE); +#endif //DEBUG_GRAPPLE + + BotClearMoveResult(&result); + // + if (ms->moveflags & MFL_GRAPPLERESET) + { + if (offhandgrapple->value) + EA_Command(ms->client, cmd_grappleoff->string); + ms->moveflags &= ~MFL_ACTIVEGRAPPLE; + return result; + } //end if + // + if (!(int) offhandgrapple->value) + { + result.weapon = weapindex_grapple->value; + result.flags |= MOVERESULT_MOVEMENTWEAPON; + } //end if + // + if (ms->moveflags & MFL_ACTIVEGRAPPLE) + { +#ifdef DEBUG_GRAPPLE + botimport.Print(PRT_MESSAGE, "BotTravel_Grapple: active grapple\n"); +#endif //DEBUG_GRAPPLE + // + state = GrappleState(ms, reach); + // + VectorSubtract(reach->end, ms->origin, dir); + dir[2] = 0; + dist = VectorLength(dir); + //if very close to the grapple end or the grappled is hooked and + //the bot doesn't get any closer + if (state && dist < 48) + { + if (ms->lastgrappledist - dist < 1) + { +#ifdef DEBUG_GRAPPLE + botimport.Print(PRT_ERROR, "grapple normal end\n"); +#endif //DEBUG_GRAPPLE + if (offhandgrapple->value) + EA_Command(ms->client, cmd_grappleoff->string); + ms->moveflags &= ~MFL_ACTIVEGRAPPLE; + ms->moveflags |= MFL_GRAPPLERESET; + ms->reachability_time = 0; //end the reachability + return result; + } //end if + } //end if + //if no valid grapple at all, or the grapple hooked and the bot + //isn't moving anymore + else if (!state || (state == 2 && dist > ms->lastgrappledist - 2)) + { + if (ms->grapplevisible_time < AAS_Time() - 0.4) + { +#ifdef DEBUG_GRAPPLE + botimport.Print(PRT_ERROR, "grapple not visible\n"); +#endif //DEBUG_GRAPPLE + if (offhandgrapple->value) + EA_Command(ms->client, cmd_grappleoff->string); + ms->moveflags &= ~MFL_ACTIVEGRAPPLE; + ms->moveflags |= MFL_GRAPPLERESET; + ms->reachability_time = 0; //end the reachability + return result; + } //end if + } //end if + else + { + ms->grapplevisible_time = AAS_Time(); + } //end else + // + if (!(int) offhandgrapple->value) + { + EA_Attack(ms->client); + } //end if + //remember the current grapple distance + ms->lastgrappledist = dist; + } //end if + else + { +#ifdef DEBUG_GRAPPLE + botimport.Print(PRT_MESSAGE, "BotTravel_Grapple: inactive grapple\n"); +#endif //DEBUG_GRAPPLE + // + ms->grapplevisible_time = AAS_Time(); + // + VectorSubtract(reach->start, ms->origin, dir); + if (!(ms->moveflags & MFL_SWIMMING)) dir[2] = 0; + VectorAdd(ms->origin, ms->viewoffset, org); + VectorSubtract(reach->end, org, viewdir); + // + dist = VectorNormalize(dir); + Vector2Angles(viewdir, result.ideal_viewangles); + result.flags |= MOVERESULT_MOVEMENTVIEW; + // + if (dist < 5 && + fabs(AngleDiff(result.ideal_viewangles[0], ms->viewangles[0])) < 2 && + fabs(AngleDiff(result.ideal_viewangles[1], ms->viewangles[1])) < 2) + { +#ifdef DEBUG_GRAPPLE + botimport.Print(PRT_MESSAGE, "BotTravel_Grapple: activating grapple\n"); +#endif //DEBUG_GRAPPLE + //check if the grapple missile path is clear + VectorAdd(ms->origin, ms->viewoffset, org); + trace = AAS_Trace(org, NULL, NULL, reach->end, ms->entitynum, CONTENTS_SOLID); + VectorSubtract(reach->end, trace.endpos, dir); + if (VectorLength(dir) > 16) + { + result.failure = qtrue; + return result; + } //end if + //activate the grapple + if (offhandgrapple->value) + { + EA_Command(ms->client, cmd_grappleon->string); + } //end if + else + { + EA_Attack(ms->client); + } //end else + ms->moveflags |= MFL_ACTIVEGRAPPLE; + ms->lastgrappledist = 999999; + } //end if + else + { + if (dist < 70) speed = 300 - (300 - 4 * dist); + else speed = 400; + // + BotCheckBlocked(ms, dir, qtrue, &result); + //elemantary action move in direction + EA_Move(ms->client, dir, speed); + VectorCopy(dir, result.movedir); + } //end else + //if in another area before actually grappling + areanum = AAS_PointAreaNum(ms->origin); + if (areanum && areanum != ms->reachareanum) ms->reachability_time = 0; + } //end else + return result; +} //end of the function BotTravel_Grapple +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_RocketJump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir; + float dist, speed; + bot_moveresult_t result; + + //botimport.Print(PRT_MESSAGE, "BotTravel_RocketJump: bah\n"); + BotClearMoveResult(&result); + // + hordir[0] = reach->start[0] - ms->origin[0]; + hordir[1] = reach->start[1] - ms->origin[1]; + hordir[2] = 0; + // + dist = VectorNormalize(hordir); + //look in the movement direction + Vector2Angles(hordir, result.ideal_viewangles); + //look straight down + result.ideal_viewangles[PITCH] = 90; + // + if (dist < 5 && + fabs(AngleDiff(result.ideal_viewangles[0], ms->viewangles[0])) < 5 && + fabs(AngleDiff(result.ideal_viewangles[1], ms->viewangles[1])) < 5) + { + //botimport.Print(PRT_MESSAGE, "between jump start and run start point\n"); + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + //elemantary action jump + EA_Jump(ms->client); + EA_Attack(ms->client); + EA_Move(ms->client, hordir, 800); + // + ms->jumpreach = ms->lastreachnum; + } //end if + else + { + if (dist > 80) dist = 80; + speed = 400 - (400 - 5 * dist); + EA_Move(ms->client, hordir, speed); + } //end else + //look in the movement direction + Vector2Angles(hordir, result.ideal_viewangles); + //look straight down + result.ideal_viewangles[PITCH] = 90; + //set the view angles directly + EA_View(ms->client, result.ideal_viewangles); + //view is important for the movment + result.flags |= MOVERESULT_MOVEMENTVIEWSET; + //select the rocket launcher + EA_SelectWeapon(ms->client, (int) weapindex_rocketlauncher->value); + //weapon is used for movement + result.weapon = (int) weapindex_rocketlauncher->value; + result.flags |= MOVERESULT_MOVEMENTWEAPON; + // + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotTravel_RocketJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_BFGJump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir; + float dist, speed; + bot_moveresult_t result; + + //botimport.Print(PRT_MESSAGE, "BotTravel_BFGJump: bah\n"); + BotClearMoveResult(&result); + // + hordir[0] = reach->start[0] - ms->origin[0]; + hordir[1] = reach->start[1] - ms->origin[1]; + hordir[2] = 0; + // + dist = VectorNormalize(hordir); + // + if (dist < 5 && + fabs(AngleDiff(result.ideal_viewangles[0], ms->viewangles[0])) < 5 && + fabs(AngleDiff(result.ideal_viewangles[1], ms->viewangles[1])) < 5) + { + //botimport.Print(PRT_MESSAGE, "between jump start and run start point\n"); + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + //elemantary action jump + EA_Jump(ms->client); + EA_Attack(ms->client); + EA_Move(ms->client, hordir, 800); + // + ms->jumpreach = ms->lastreachnum; + } //end if + else + { + if (dist > 80) dist = 80; + speed = 400 - (400 - 5 * dist); + EA_Move(ms->client, hordir, speed); + } //end else + //look in the movement direction + Vector2Angles(hordir, result.ideal_viewangles); + //look straight down + result.ideal_viewangles[PITCH] = 90; + //set the view angles directly + EA_View(ms->client, result.ideal_viewangles); + //view is important for the movment + result.flags |= MOVERESULT_MOVEMENTVIEWSET; + //select the rocket launcher + EA_SelectWeapon(ms->client, (int) weapindex_bfg10k->value); + //weapon is used for movement + result.weapon = (int) weapindex_bfg10k->value; + result.flags |= MOVERESULT_MOVEMENTWEAPON; + // + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotTravel_BFGJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_WeaponJump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir; + float speed; + bot_moveresult_t result; + + BotClearMoveResult(&result); + //if not jumped yet + if (!ms->jumpreach) return result; + /* + //go straight to the reachability end + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + //always use max speed when traveling through the air + EA_Move(ms->client, hordir, 800); + VectorCopy(hordir, result.movedir); + */ + // + if (!BotAirControl(ms->origin, ms->velocity, reach->end, hordir, &speed)) + { + //go straight to the reachability end + VectorSubtract(reach->end, ms->origin, hordir); + hordir[2] = 0; + VectorNormalize(hordir); + speed = 400; + } //end if + // + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotFinishTravel_WeaponJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_JumpPad(bot_movestate_t *ms, aas_reachability_t *reach) +{ + float dist, speed; + vec3_t hordir; + bot_moveresult_t result; + + BotClearMoveResult(&result); + //first walk straight to the reachability start + hordir[0] = reach->start[0] - ms->origin[0]; + hordir[1] = reach->start[1] - ms->origin[1]; + hordir[2] = 0; + dist = VectorNormalize(hordir); + // + BotCheckBlocked(ms, hordir, qtrue, &result); + speed = 400; + //elemantary action move in direction + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotTravel_JumpPad +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_JumpPad(bot_movestate_t *ms, aas_reachability_t *reach) +{ + float speed; + vec3_t hordir; + bot_moveresult_t result; + + BotClearMoveResult(&result); + if (!BotAirControl(ms->origin, ms->velocity, reach->end, hordir, &speed)) + { + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + speed = 400; + } //end if + BotCheckBlocked(ms, hordir, qtrue, &result); + //elemantary action move in direction + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotFinishTravel_JumpPad +//=========================================================================== +// time before the reachability times out +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotReachabilityTime(aas_reachability_t *reach) +{ + switch(reach->traveltype & TRAVELTYPE_MASK) + { + case TRAVEL_WALK: return 5; + case TRAVEL_CROUCH: return 5; + case TRAVEL_BARRIERJUMP: return 5; + case TRAVEL_LADDER: return 6; + case TRAVEL_WALKOFFLEDGE: return 5; + case TRAVEL_JUMP: return 5; + case TRAVEL_SWIM: return 5; + case TRAVEL_WATERJUMP: return 5; + case TRAVEL_TELEPORT: return 5; + case TRAVEL_ELEVATOR: return 10; + case TRAVEL_GRAPPLEHOOK: return 8; + case TRAVEL_ROCKETJUMP: return 6; + case TRAVEL_BFGJUMP: return 6; + case TRAVEL_JUMPPAD: return 10; + case TRAVEL_FUNCBOB: return 10; + default: + { + botimport.Print(PRT_ERROR, "travel type %d not implemented yet\n", reach->traveltype); + return 8; + } //end case + } //end switch +} //end of the function BotReachabilityTime +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotMoveInGoalArea(bot_movestate_t *ms, bot_goal_t *goal) +{ + bot_moveresult_t result; + vec3_t dir; + float dist, speed; + +#ifdef DEBUG + //botimport.Print(PRT_MESSAGE, "%s: moving straight to goal\n", ClientName(ms->entitynum-1)); + //AAS_ClearShownDebugLines(); + //AAS_DebugLine(ms->origin, goal->origin, LINECOLOR_RED); +#endif //DEBUG + BotClearMoveResult(&result); + //walk straight to the goal origin + dir[0] = goal->origin[0] - ms->origin[0]; + dir[1] = goal->origin[1] - ms->origin[1]; + if (ms->moveflags & MFL_SWIMMING) + { + dir[2] = goal->origin[2] - ms->origin[2]; + result.traveltype = TRAVEL_SWIM; + } //end if + else + { + dir[2] = 0; + result.traveltype = TRAVEL_WALK; + } //endif + // + dist = VectorNormalize(dir); + if (dist > 100) dist = 100; + speed = 400 - (400 - 4 * dist); + if (speed < 10) speed = 0; + // + BotCheckBlocked(ms, dir, qtrue, &result); + //elemantary action move in direction + EA_Move(ms->client, dir, speed); + VectorCopy(dir, result.movedir); + // + if (ms->moveflags & MFL_SWIMMING) + { + Vector2Angles(dir, result.ideal_viewangles); + result.flags |= MOVERESULT_SWIMVIEW; + } //end if + //if (!debugline) debugline = botimport.DebugLineCreate(); + //botimport.DebugLineShow(debugline, ms->origin, goal->origin, LINECOLOR_BLUE); + // + ms->lastreachnum = 0; + ms->lastareanum = 0; + ms->lastgoalareanum = goal->areanum; + VectorCopy(ms->origin, ms->lastorigin); + // + return result; +} //end of the function BotMoveInGoalArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotMoveToGoal(bot_moveresult_t *result, int movestate, bot_goal_t *goal, int travelflags) +{ + int reachnum, lastreachnum, foundjumppad, ent, resultflags; + aas_reachability_t reach, lastreach; + bot_movestate_t *ms; + //vec3_t mins, maxs, up = {0, 0, 1}; + //bsp_trace_t trace; + //static int debugline; + + + BotClearMoveResult(result); + // + ms = BotMoveStateFromHandle(movestate); + if (!ms) return; + //reset the grapple before testing if the bot has a valid goal + //because the bot could loose all it's goals when stuck to a wall + BotResetGrapple(ms); + // + if (!goal) + { +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "client %d: movetogoal -> no goal\n", ms->client); +#endif //DEBUG + result->failure = qtrue; + return; + } //end if + //botimport.Print(PRT_MESSAGE, "numavoidreach = %d\n", ms->numavoidreach); + //remove some of the move flags + ms->moveflags &= ~(MFL_SWIMMING|MFL_AGAINSTLADDER); + //set some of the move flags + //NOTE: the MFL_ONGROUND flag is also set in the higher AI + if (AAS_OnGround(ms->origin, ms->presencetype, ms->entitynum)) ms->moveflags |= MFL_ONGROUND; + // + if (ms->moveflags & MFL_ONGROUND) + { + int modeltype, modelnum; + + ent = BotOnTopOfEntity(ms); + + if (ent != -1) + { + modelnum = AAS_EntityModelindex(ent); + if (modelnum >= 0 && modelnum < MAX_MODELS) + { + modeltype = modeltypes[modelnum]; + + if (modeltype == MODELTYPE_FUNC_PLAT) + { + AAS_ReachabilityFromNum(ms->lastreachnum, &reach); + //if the bot is Not using the elevator + if ((reach.traveltype & TRAVELTYPE_MASK) != TRAVEL_ELEVATOR || + //NOTE: the face number is the plat model number + (reach.facenum & 0x0000FFFF) != modelnum) + { + reachnum = AAS_NextModelReachability(0, modelnum); + if (reachnum) + { + //botimport.Print(PRT_MESSAGE, "client %d: accidentally ended up on func_plat\n", ms->client); + AAS_ReachabilityFromNum(reachnum, &reach); + ms->lastreachnum = reachnum; + ms->reachability_time = AAS_Time() + BotReachabilityTime(&reach); + } //end if + else + { + if (bot_developer) + { + botimport.Print(PRT_MESSAGE, "client %d: on func_plat without reachability\n", ms->client); + } //end if + result->blocked = qtrue; + result->blockentity = ent; + result->flags |= MOVERESULT_ONTOPOFOBSTACLE; + return; + } //end else + } //end if + result->flags |= MOVERESULT_ONTOPOF_ELEVATOR; + } //end if + else if (modeltype == MODELTYPE_FUNC_BOB) + { + AAS_ReachabilityFromNum(ms->lastreachnum, &reach); + //if the bot is Not using the func bobbing + if ((reach.traveltype & TRAVELTYPE_MASK) != TRAVEL_FUNCBOB || + //NOTE: the face number is the func_bobbing model number + (reach.facenum & 0x0000FFFF) != modelnum) + { + reachnum = AAS_NextModelReachability(0, modelnum); + if (reachnum) + { + //botimport.Print(PRT_MESSAGE, "client %d: accidentally ended up on func_bobbing\n", ms->client); + AAS_ReachabilityFromNum(reachnum, &reach); + ms->lastreachnum = reachnum; + ms->reachability_time = AAS_Time() + BotReachabilityTime(&reach); + } //end if + else + { + if (bot_developer) + { + botimport.Print(PRT_MESSAGE, "client %d: on func_bobbing without reachability\n", ms->client); + } //end if + result->blocked = qtrue; + result->blockentity = ent; + result->flags |= MOVERESULT_ONTOPOFOBSTACLE; + return; + } //end else + } //end if + result->flags |= MOVERESULT_ONTOPOF_FUNCBOB; + } //end if + else if (modeltype == MODELTYPE_FUNC_STATIC || modeltype == MODELTYPE_FUNC_DOOR) + { + // check if ontop of a door bridge ? + ms->areanum = BotFuzzyPointReachabilityArea(ms->origin); + // if not in a reachability area + if (!AAS_AreaReachability(ms->areanum)) + { + result->blocked = qtrue; + result->blockentity = ent; + result->flags |= MOVERESULT_ONTOPOFOBSTACLE; + return; + } //end if + } //end else if + else + { + result->blocked = qtrue; + result->blockentity = ent; + result->flags |= MOVERESULT_ONTOPOFOBSTACLE; + return; + } //end else + } //end if + } //end if + } //end if + //if swimming + if (AAS_Swimming(ms->origin)) ms->moveflags |= MFL_SWIMMING; + //if against a ladder + if (AAS_AgainstLadder(ms->origin)) ms->moveflags |= MFL_AGAINSTLADDER; + //if the bot is on the ground, swimming or against a ladder + if (ms->moveflags & (MFL_ONGROUND|MFL_SWIMMING|MFL_AGAINSTLADDER)) + { + //botimport.Print(PRT_MESSAGE, "%s: onground, swimming or against ladder\n", ClientName(ms->entitynum-1)); + // + AAS_ReachabilityFromNum(ms->lastreachnum, &lastreach); + //reachability area the bot is in + //ms->areanum = BotReachabilityArea(ms->origin, ((lastreach.traveltype & TRAVELTYPE_MASK) != TRAVEL_ELEVATOR)); + ms->areanum = BotFuzzyPointReachabilityArea(ms->origin); + // + if ( !ms->areanum ) + { + result->failure = qtrue; + result->blocked = qtrue; + result->blockentity = 0; + result->type = RESULTTYPE_INSOLIDAREA; + return; + } //end if + //if the bot is in the goal area + if (ms->areanum == goal->areanum) + { + *result = BotMoveInGoalArea(ms, goal); + return; + } //end if + //assume we can use the reachability from the last frame + reachnum = ms->lastreachnum; + //if there is a last reachability + if (reachnum) + { + AAS_ReachabilityFromNum(reachnum, &reach); + //check if the reachability is still valid + if (!(AAS_TravelFlagForType(reach.traveltype) & travelflags)) + { + reachnum = 0; + } //end if + //special grapple hook case + else if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_GRAPPLEHOOK) + { + if (ms->reachability_time < AAS_Time() || + (ms->moveflags & MFL_GRAPPLERESET)) + { + reachnum = 0; + } //end if + } //end if + //special elevator case + else if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_ELEVATOR || + (reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_FUNCBOB) + { + if ((result->flags & MOVERESULT_ONTOPOF_FUNCBOB) || + (result->flags & MOVERESULT_ONTOPOF_FUNCBOB)) + { + ms->reachability_time = AAS_Time() + 5; + } //end if + //if the bot was going for an elevator and reached the reachability area + if (ms->areanum == reach.areanum || + ms->reachability_time < AAS_Time()) + { + reachnum = 0; + } //end if + } //end if + else + { +#ifdef DEBUG + if (bot_developer) + { + if (ms->reachability_time < AAS_Time()) + { + botimport.Print(PRT_MESSAGE, "client %d: reachability timeout in ", ms->client); + AAS_PrintTravelType(reach.traveltype & TRAVELTYPE_MASK); + botimport.Print(PRT_MESSAGE, "\n"); + } //end if + /* + if (ms->lastareanum != ms->areanum) + { + botimport.Print(PRT_MESSAGE, "changed from area %d to %d\n", ms->lastareanum, ms->areanum); + } //end if*/ + } //end if +#endif //DEBUG + //if the goal area changed or the reachability timed out + //or the area changed + if (ms->lastgoalareanum != goal->areanum || + ms->reachability_time < AAS_Time() || + ms->lastareanum != ms->areanum) + { + reachnum = 0; + //botimport.Print(PRT_MESSAGE, "area change or timeout\n"); + } //end else if + } //end else + } //end if + resultflags = 0; + //if the bot needs a new reachability + if (!reachnum) + { + //if the area has no reachability links + if (!AAS_AreaReachability(ms->areanum)) + { +#ifdef DEBUG + if (bot_developer) + { + botimport.Print(PRT_MESSAGE, "area %d no reachability\n", ms->areanum); + } //end if +#endif //DEBUG + } //end if + //get a new reachability leading towards the goal + reachnum = BotGetReachabilityToGoal(ms->origin, ms->areanum, + ms->lastgoalareanum, ms->lastareanum, + ms->avoidreach, ms->avoidreachtimes, ms->avoidreachtries, + goal, travelflags, travelflags, + ms->avoidspots, ms->numavoidspots, &resultflags); + //the area number the reachability starts in + ms->reachareanum = ms->areanum; + //reset some state variables + ms->jumpreach = 0; //for TRAVEL_JUMP + ms->moveflags &= ~MFL_GRAPPLERESET; //for TRAVEL_GRAPPLEHOOK + //if there is a reachability to the goal + if (reachnum) + { + AAS_ReachabilityFromNum(reachnum, &reach); + //set a timeout for this reachability + ms->reachability_time = AAS_Time() + BotReachabilityTime(&reach); + // +#ifdef AVOIDREACH + //add the reachability to the reachabilities to avoid for a while + BotAddToAvoidReach(ms, reachnum, AVOIDREACH_TIME); +#endif //AVOIDREACH + } //end if +#ifdef DEBUG + + else if (bot_developer) + { + botimport.Print(PRT_MESSAGE, "goal not reachable\n"); + Com_Memset(&reach, 0, sizeof(aas_reachability_t)); //make compiler happy + } //end else + if (bot_developer) + { + //if still going for the same goal + if (ms->lastgoalareanum == goal->areanum) + { + if (ms->lastareanum == reach.areanum) + { + botimport.Print(PRT_MESSAGE, "same goal, going back to previous area\n"); + } //end if + } //end if + } //end if +#endif //DEBUG + } //end else + // + ms->lastreachnum = reachnum; + ms->lastgoalareanum = goal->areanum; + ms->lastareanum = ms->areanum; + //if the bot has a reachability + if (reachnum) + { + //get the reachability from the number + AAS_ReachabilityFromNum(reachnum, &reach); + result->traveltype = reach.traveltype; + // +#ifdef DEBUG_AI_MOVE + AAS_ClearShownDebugLines(); + AAS_PrintTravelType(reach.traveltype & TRAVELTYPE_MASK); + AAS_ShowReachability(&reach); +#endif //DEBUG_AI_MOVE + // +#ifdef DEBUG + //botimport.Print(PRT_MESSAGE, "client %d: ", ms->client); + //AAS_PrintTravelType(reach.traveltype); + //botimport.Print(PRT_MESSAGE, "\n"); +#endif //DEBUG + switch(reach.traveltype & TRAVELTYPE_MASK) + { + case TRAVEL_WALK: *result = BotTravel_Walk(ms, &reach); break; + case TRAVEL_CROUCH: *result = BotTravel_Crouch(ms, &reach); break; + case TRAVEL_BARRIERJUMP: *result = BotTravel_BarrierJump(ms, &reach); break; + case TRAVEL_LADDER: *result = BotTravel_Ladder(ms, &reach); break; + case TRAVEL_WALKOFFLEDGE: *result = BotTravel_WalkOffLedge(ms, &reach); break; + case TRAVEL_JUMP: *result = BotTravel_Jump(ms, &reach); break; + case TRAVEL_SWIM: *result = BotTravel_Swim(ms, &reach); break; + case TRAVEL_WATERJUMP: *result = BotTravel_WaterJump(ms, &reach); break; + case TRAVEL_TELEPORT: *result = BotTravel_Teleport(ms, &reach); break; + case TRAVEL_ELEVATOR: *result = BotTravel_Elevator(ms, &reach); break; + case TRAVEL_GRAPPLEHOOK: *result = BotTravel_Grapple(ms, &reach); break; + case TRAVEL_ROCKETJUMP: *result = BotTravel_RocketJump(ms, &reach); break; + case TRAVEL_BFGJUMP: *result = BotTravel_BFGJump(ms, &reach); break; + case TRAVEL_JUMPPAD: *result = BotTravel_JumpPad(ms, &reach); break; + case TRAVEL_FUNCBOB: *result = BotTravel_FuncBobbing(ms, &reach); break; + default: + { + botimport.Print(PRT_FATAL, "travel type %d not implemented yet\n", (reach.traveltype & TRAVELTYPE_MASK)); + break; + } //end case + } //end switch + result->traveltype = reach.traveltype; + result->flags |= resultflags; + } //end if + else + { + result->failure = qtrue; + result->flags |= resultflags; + Com_Memset(&reach, 0, sizeof(aas_reachability_t)); + } //end else +#ifdef DEBUG + if (bot_developer) + { + if (result->failure) + { + botimport.Print(PRT_MESSAGE, "client %d: movement failure in ", ms->client); + AAS_PrintTravelType(reach.traveltype & TRAVELTYPE_MASK); + botimport.Print(PRT_MESSAGE, "\n"); + } //end if + } //end if +#endif //DEBUG + } //end if + else + { + int i, numareas, areas[16]; + vec3_t end; + + //special handling of jump pads when the bot uses a jump pad without knowing it + foundjumppad = qfalse; + VectorMA(ms->origin, -2 * ms->thinktime, ms->velocity, end); + numareas = AAS_TraceAreas(ms->origin, end, areas, NULL, 16); + for (i = numareas-1; i >= 0; i--) + { + if (AAS_AreaJumpPad(areas[i])) + { + //botimport.Print(PRT_MESSAGE, "client %d used a jumppad without knowing, area %d\n", ms->client, areas[i]); + foundjumppad = qtrue; + lastreachnum = BotGetReachabilityToGoal(end, areas[i], + ms->lastgoalareanum, ms->lastareanum, + ms->avoidreach, ms->avoidreachtimes, ms->avoidreachtries, + goal, travelflags, TFL_JUMPPAD, ms->avoidspots, ms->numavoidspots, NULL); + if (lastreachnum) + { + ms->lastreachnum = lastreachnum; + ms->lastareanum = areas[i]; + //botimport.Print(PRT_MESSAGE, "found jumppad reachability\n"); + break; + } //end if + else + { + for (lastreachnum = AAS_NextAreaReachability(areas[i], 0); lastreachnum; + lastreachnum = AAS_NextAreaReachability(areas[i], lastreachnum)) + { + //get the reachability from the number + AAS_ReachabilityFromNum(lastreachnum, &reach); + if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_JUMPPAD) + { + ms->lastreachnum = lastreachnum; + ms->lastareanum = areas[i]; + //botimport.Print(PRT_MESSAGE, "found jumppad reachability hard!!\n"); + break; + } //end if + } //end for + if (lastreachnum) break; + } //end else + } //end if + } //end for + if (bot_developer) + { + //if a jumppad is found with the trace but no reachability is found + if (foundjumppad && !ms->lastreachnum) + { + botimport.Print(PRT_MESSAGE, "client %d didn't find jumppad reachability\n", ms->client); + } //end if + } //end if + // + if (ms->lastreachnum) + { + //botimport.Print(PRT_MESSAGE, "%s: NOT onground, swimming or against ladder\n", ClientName(ms->entitynum-1)); + AAS_ReachabilityFromNum(ms->lastreachnum, &reach); + result->traveltype = reach.traveltype; +#ifdef DEBUG + //botimport.Print(PRT_MESSAGE, "client %d finish: ", ms->client); + //AAS_PrintTravelType(reach.traveltype & TRAVELTYPE_MASK); + //botimport.Print(PRT_MESSAGE, "\n"); +#endif //DEBUG + // + switch(reach.traveltype & TRAVELTYPE_MASK) + { + case TRAVEL_WALK: *result = BotTravel_Walk(ms, &reach); break;//BotFinishTravel_Walk(ms, &reach); break; + case TRAVEL_CROUCH: /*do nothing*/ break; + case TRAVEL_BARRIERJUMP: *result = BotFinishTravel_BarrierJump(ms, &reach); break; + case TRAVEL_LADDER: *result = BotTravel_Ladder(ms, &reach); break; + case TRAVEL_WALKOFFLEDGE: *result = BotFinishTravel_WalkOffLedge(ms, &reach); break; + case TRAVEL_JUMP: *result = BotFinishTravel_Jump(ms, &reach); break; + case TRAVEL_SWIM: *result = BotTravel_Swim(ms, &reach); break; + case TRAVEL_WATERJUMP: *result = BotFinishTravel_WaterJump(ms, &reach); break; + case TRAVEL_TELEPORT: /*do nothing*/ break; + case TRAVEL_ELEVATOR: *result = BotFinishTravel_Elevator(ms, &reach); break; + case TRAVEL_GRAPPLEHOOK: *result = BotTravel_Grapple(ms, &reach); break; + case TRAVEL_ROCKETJUMP: + case TRAVEL_BFGJUMP: *result = BotFinishTravel_WeaponJump(ms, &reach); break; + case TRAVEL_JUMPPAD: *result = BotFinishTravel_JumpPad(ms, &reach); break; + case TRAVEL_FUNCBOB: *result = BotFinishTravel_FuncBobbing(ms, &reach); break; + default: + { + botimport.Print(PRT_FATAL, "(last) travel type %d not implemented yet\n", (reach.traveltype & TRAVELTYPE_MASK)); + break; + } //end case + } //end switch + result->traveltype = reach.traveltype; +#ifdef DEBUG + if (bot_developer) + { + if (result->failure) + { + botimport.Print(PRT_MESSAGE, "client %d: movement failure in finish ", ms->client); + AAS_PrintTravelType(reach.traveltype & TRAVELTYPE_MASK); + botimport.Print(PRT_MESSAGE, "\n"); + } //end if + } //end if +#endif //DEBUG + } //end if + } //end else + //FIXME: is it right to do this here? + if (result->blocked) ms->reachability_time -= 10 * ms->thinktime; + //copy the last origin + VectorCopy(ms->origin, ms->lastorigin); + //return the movement result + return; +} //end of the function BotMoveToGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotResetAvoidReach(int movestate) +{ + bot_movestate_t *ms; + + ms = BotMoveStateFromHandle(movestate); + if (!ms) return; + Com_Memset(ms->avoidreach, 0, MAX_AVOIDREACH * sizeof(int)); + Com_Memset(ms->avoidreachtimes, 0, MAX_AVOIDREACH * sizeof(float)); + Com_Memset(ms->avoidreachtries, 0, MAX_AVOIDREACH * sizeof(int)); +} //end of the function BotResetAvoidReach +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotResetLastAvoidReach(int movestate) +{ + int i, latest; + float latesttime; + bot_movestate_t *ms; + + ms = BotMoveStateFromHandle(movestate); + if (!ms) return; + latesttime = 0; + latest = 0; + for (i = 0; i < MAX_AVOIDREACH; i++) + { + if (ms->avoidreachtimes[i] > latesttime) + { + latesttime = ms->avoidreachtimes[i]; + latest = i; + } //end if + } //end for + if (latesttime) + { + ms->avoidreachtimes[latest] = 0; + if (ms->avoidreachtries[i] > 0) ms->avoidreachtries[latest]--; + } //end if +} //end of the function BotResetLastAvoidReach +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotResetMoveState(int movestate) +{ + bot_movestate_t *ms; + + ms = BotMoveStateFromHandle(movestate); + if (!ms) return; + Com_Memset(ms, 0, sizeof(bot_movestate_t)); +} //end of the function BotResetMoveState +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotSetupMoveAI(void) +{ + BotSetBrushModelTypes(); + sv_maxstep = LibVar("sv_step", "18"); + sv_maxbarrier = LibVar("sv_maxbarrier", "32"); + sv_gravity = LibVar("sv_gravity", "800"); + weapindex_rocketlauncher = LibVar("weapindex_rocketlauncher", "5"); + weapindex_bfg10k = LibVar("weapindex_bfg10k", "9"); + weapindex_grapple = LibVar("weapindex_grapple", "10"); + entitytypemissile = LibVar("entitytypemissile", "3"); + offhandgrapple = LibVar("offhandgrapple", "0"); + cmd_grappleon = LibVar("cmd_grappleon", "grappleon"); + cmd_grappleoff = LibVar("cmd_grappleoff", "grappleoff"); + return BLERR_NOERROR; +} //end of the function BotSetupMoveAI +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotShutdownMoveAI(void) +{ + int i; + + for (i = 1; i <= MAX_CLIENTS; i++) + { + if (botmovestates[i]) + { + FreeMemory(botmovestates[i]); + botmovestates[i] = NULL; + } //end if + } //end for +} //end of the function BotShutdownMoveAI + + diff --git a/code/botlib/be_ai_weap.c b/code/botlib/be_ai_weap.c index 435292d..22d989e 100755 --- a/code/botlib/be_ai_weap.c +++ b/code/botlib/be_ai_weap.c @@ -1,543 +1,543 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: be_ai_weap.c - * - * desc: weapon AI - * - * $Archive: /MissionPack/code/botlib/be_ai_weap.c $ - * - *****************************************************************************/ - -#include "../game/q_shared.h" -#include "l_libvar.h" -#include "l_log.h" -#include "l_memory.h" -#include "l_utils.h" -#include "l_script.h" -#include "l_precomp.h" -#include "l_struct.h" -#include "aasfile.h" -#include "../game/botlib.h" -#include "../game/be_aas.h" -#include "be_aas_funcs.h" -#include "be_interface.h" -#include "be_ai_weight.h" //fuzzy weights -#include "../game/be_ai_weap.h" - -//#define DEBUG_AI_WEAP - -//structure field offsets -#define WEAPON_OFS(x) (int)&(((weaponinfo_t *)0)->x) -#define PROJECTILE_OFS(x) (int)&(((projectileinfo_t *)0)->x) - -//weapon definition // bk001212 - static -static fielddef_t weaponinfo_fields[] = -{ -{"number", WEAPON_OFS(number), FT_INT}, //weapon number -{"name", WEAPON_OFS(name), FT_STRING}, //name of the weapon -{"level", WEAPON_OFS(level), FT_INT}, -{"model", WEAPON_OFS(model), FT_STRING}, //model of the weapon -{"weaponindex", WEAPON_OFS(weaponindex), FT_INT}, //index of weapon in inventory -{"flags", WEAPON_OFS(flags), FT_INT}, //special flags -{"projectile", WEAPON_OFS(projectile), FT_STRING}, //projectile used by the weapon -{"numprojectiles", WEAPON_OFS(numprojectiles), FT_INT}, //number of projectiles -{"hspread", WEAPON_OFS(hspread), FT_FLOAT}, //horizontal spread of projectiles (degrees from middle) -{"vspread", WEAPON_OFS(vspread), FT_FLOAT}, //vertical spread of projectiles (degrees from middle) -{"speed", WEAPON_OFS(speed), FT_FLOAT}, //speed of the projectile (0 = instant hit) -{"acceleration", WEAPON_OFS(acceleration), FT_FLOAT}, //"acceleration" * time (in seconds) + "speed" = projectile speed -{"recoil", WEAPON_OFS(recoil), FT_FLOAT|FT_ARRAY, 3}, //amount of recoil the player gets from the weapon -{"offset", WEAPON_OFS(offset), FT_FLOAT|FT_ARRAY, 3}, //projectile start offset relative to eye and view angles -{"angleoffset", WEAPON_OFS(angleoffset), FT_FLOAT|FT_ARRAY, 3},//offset of the shoot angles relative to the view angles -{"extrazvelocity", WEAPON_OFS(extrazvelocity), FT_FLOAT},//extra z velocity the projectile gets -{"ammoamount", WEAPON_OFS(ammoamount), FT_INT}, //ammo amount used per shot -{"ammoindex", WEAPON_OFS(ammoindex), FT_INT}, //index of ammo in inventory -{"activate", WEAPON_OFS(activate), FT_FLOAT}, //time it takes to select the weapon -{"reload", WEAPON_OFS(reload), FT_FLOAT}, //time it takes to reload the weapon -{"spinup", WEAPON_OFS(spinup), FT_FLOAT}, //time it takes before first shot -{"spindown", WEAPON_OFS(spindown), FT_FLOAT}, //time it takes before weapon stops firing -{NULL, 0, 0, 0} -}; - -//projectile definition -static fielddef_t projectileinfo_fields[] = -{ -{"name", PROJECTILE_OFS(name), FT_STRING}, //name of the projectile -{"model", WEAPON_OFS(model), FT_STRING}, //model of the projectile -{"flags", PROJECTILE_OFS(flags), FT_INT}, //special flags -{"gravity", PROJECTILE_OFS(gravity), FT_FLOAT}, //amount of gravity applied to the projectile [0,1] -{"damage", PROJECTILE_OFS(damage), FT_INT}, //damage of the projectile -{"radius", PROJECTILE_OFS(radius), FT_FLOAT}, //radius of damage -{"visdamage", PROJECTILE_OFS(visdamage), FT_INT}, //damage of the projectile to visible entities -{"damagetype", PROJECTILE_OFS(damagetype), FT_INT}, //type of damage (combination of the DAMAGETYPE_? flags) -{"healthinc", PROJECTILE_OFS(healthinc), FT_INT}, //health increase the owner gets -{"push", PROJECTILE_OFS(push), FT_FLOAT}, //amount a player is pushed away from the projectile impact -{"detonation", PROJECTILE_OFS(detonation), FT_FLOAT}, //time before projectile explodes after fire pressed -{"bounce", PROJECTILE_OFS(bounce), FT_FLOAT}, //amount the projectile bounces -{"bouncefric", PROJECTILE_OFS(bouncefric), FT_FLOAT}, //amount the bounce decreases per bounce -{"bouncestop", PROJECTILE_OFS(bouncestop), FT_FLOAT}, //minimum bounce value before bouncing stops -//recurive projectile definition?? -{NULL, 0, 0, 0} -}; - -static structdef_t weaponinfo_struct = -{ - sizeof(weaponinfo_t), weaponinfo_fields -}; -static structdef_t projectileinfo_struct = -{ - sizeof(projectileinfo_t), projectileinfo_fields -}; - -//weapon configuration: set of weapons with projectiles -typedef struct weaponconfig_s -{ - int numweapons; - int numprojectiles; - projectileinfo_t *projectileinfo; - weaponinfo_t *weaponinfo; -} weaponconfig_t; - -//the bot weapon state -typedef struct bot_weaponstate_s -{ - struct weightconfig_s *weaponweightconfig; //weapon weight configuration - int *weaponweightindex; //weapon weight index -} bot_weaponstate_t; - -static bot_weaponstate_t *botweaponstates[MAX_CLIENTS+1]; -static weaponconfig_t *weaponconfig; - -//======================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//======================================================================== -int BotValidWeaponNumber(int weaponnum) -{ - if (weaponnum <= 0 || weaponnum > weaponconfig->numweapons) - { - botimport.Print(PRT_ERROR, "weapon number out of range\n"); - return qfalse; - } //end if - return qtrue; -} //end of the function BotValidWeaponNumber -//======================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//======================================================================== -bot_weaponstate_t *BotWeaponStateFromHandle(int handle) -{ - if (handle <= 0 || handle > MAX_CLIENTS) - { - botimport.Print(PRT_FATAL, "move state handle %d out of range\n", handle); - return NULL; - } //end if - if (!botweaponstates[handle]) - { - botimport.Print(PRT_FATAL, "invalid move state %d\n", handle); - return NULL; - } //end if - return botweaponstates[handle]; -} //end of the function BotWeaponStateFromHandle -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -#ifdef DEBUG_AI_WEAP -void DumpWeaponConfig(weaponconfig_t *wc) -{ - FILE *fp; - int i; - - fp = Log_FileStruct(); - if (!fp) return; - for (i = 0; i < wc->numprojectiles; i++) - { - WriteStructure(fp, &projectileinfo_struct, (char *) &wc->projectileinfo[i]); - Log_Flush(); - } //end for - for (i = 0; i < wc->numweapons; i++) - { - WriteStructure(fp, &weaponinfo_struct, (char *) &wc->weaponinfo[i]); - Log_Flush(); - } //end for -} //end of the function DumpWeaponConfig -#endif //DEBUG_AI_WEAP -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -weaponconfig_t *LoadWeaponConfig(char *filename) -{ - int max_weaponinfo, max_projectileinfo; - token_t token; - char path[MAX_PATH]; - int i, j; - source_t *source; - weaponconfig_t *wc; - weaponinfo_t weaponinfo; - - max_weaponinfo = (int) LibVarValue("max_weaponinfo", "32"); - if (max_weaponinfo < 0) - { - botimport.Print(PRT_ERROR, "max_weaponinfo = %d\n", max_weaponinfo); - max_weaponinfo = 32; - LibVarSet("max_weaponinfo", "32"); - } //end if - max_projectileinfo = (int) LibVarValue("max_projectileinfo", "32"); - if (max_projectileinfo < 0) - { - botimport.Print(PRT_ERROR, "max_projectileinfo = %d\n", max_projectileinfo); - max_projectileinfo = 32; - LibVarSet("max_projectileinfo", "32"); - } //end if - strncpy(path, filename, MAX_PATH); - PC_SetBaseFolder(BOTFILESBASEFOLDER); - source = LoadSourceFile(path); - if (!source) - { - botimport.Print(PRT_ERROR, "counldn't load %s\n", path); - return NULL; - } //end if - //initialize weapon config - wc = (weaponconfig_t *) GetClearedHunkMemory(sizeof(weaponconfig_t) + - max_weaponinfo * sizeof(weaponinfo_t) + - max_projectileinfo * sizeof(projectileinfo_t)); - wc->weaponinfo = (weaponinfo_t *) ((char *) wc + sizeof(weaponconfig_t)); - wc->projectileinfo = (projectileinfo_t *) ((char *) wc->weaponinfo + - max_weaponinfo * sizeof(weaponinfo_t)); - wc->numweapons = max_weaponinfo; - wc->numprojectiles = 0; - //parse the source file - while(PC_ReadToken(source, &token)) - { - if (!strcmp(token.string, "weaponinfo")) - { - Com_Memset(&weaponinfo, 0, sizeof(weaponinfo_t)); - if (!ReadStructure(source, &weaponinfo_struct, (char *) &weaponinfo)) - { - FreeMemory(wc); - FreeSource(source); - return NULL; - } //end if - if (weaponinfo.number < 0 || weaponinfo.number >= max_weaponinfo) - { - botimport.Print(PRT_ERROR, "weapon info number %d out of range in %s\n", weaponinfo.number, path); - FreeMemory(wc); - FreeSource(source); - return NULL; - } //end if - Com_Memcpy(&wc->weaponinfo[weaponinfo.number], &weaponinfo, sizeof(weaponinfo_t)); - wc->weaponinfo[weaponinfo.number].valid = qtrue; - } //end if - else if (!strcmp(token.string, "projectileinfo")) - { - if (wc->numprojectiles >= max_projectileinfo) - { - botimport.Print(PRT_ERROR, "more than %d projectiles defined in %s\n", max_projectileinfo, path); - FreeMemory(wc); - FreeSource(source); - return NULL; - } //end if - Com_Memset(&wc->projectileinfo[wc->numprojectiles], 0, sizeof(projectileinfo_t)); - if (!ReadStructure(source, &projectileinfo_struct, (char *) &wc->projectileinfo[wc->numprojectiles])) - { - FreeMemory(wc); - FreeSource(source); - return NULL; - } //end if - wc->numprojectiles++; - } //end if - else - { - botimport.Print(PRT_ERROR, "unknown definition %s in %s\n", token.string, path); - FreeMemory(wc); - FreeSource(source); - return NULL; - } //end else - } //end while - FreeSource(source); - //fix up weapons - for (i = 0; i < wc->numweapons; i++) - { - if (!wc->weaponinfo[i].valid) continue; - if (!wc->weaponinfo[i].name[0]) - { - botimport.Print(PRT_ERROR, "weapon %d has no name in %s\n", i, path); - FreeMemory(wc); - return NULL; - } //end if - if (!wc->weaponinfo[i].projectile[0]) - { - botimport.Print(PRT_ERROR, "weapon %s has no projectile in %s\n", wc->weaponinfo[i].name, path); - FreeMemory(wc); - return NULL; - } //end if - //find the projectile info and copy it to the weapon info - for (j = 0; j < wc->numprojectiles; j++) - { - if (!strcmp(wc->projectileinfo[j].name, wc->weaponinfo[i].projectile)) - { - Com_Memcpy(&wc->weaponinfo[i].proj, &wc->projectileinfo[j], sizeof(projectileinfo_t)); - break; - } //end if - } //end for - if (j == wc->numprojectiles) - { - botimport.Print(PRT_ERROR, "weapon %s uses undefined projectile in %s\n", wc->weaponinfo[i].name, path); - FreeMemory(wc); - return NULL; - } //end if - } //end for - if (!wc->numweapons) botimport.Print(PRT_WARNING, "no weapon info loaded\n"); - botimport.Print(PRT_MESSAGE, "loaded %s\n", path); - return wc; -} //end of the function LoadWeaponConfig -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int *WeaponWeightIndex(weightconfig_t *wwc, weaponconfig_t *wc) -{ - int *index, i; - - //initialize item weight index - index = (int *) GetClearedMemory(sizeof(int) * wc->numweapons); - - for (i = 0; i < wc->numweapons; i++) - { - index[i] = FindFuzzyWeight(wwc, wc->weaponinfo[i].name); - } //end for - return index; -} //end of the function WeaponWeightIndex -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotFreeWeaponWeights(int weaponstate) -{ - bot_weaponstate_t *ws; - - ws = BotWeaponStateFromHandle(weaponstate); - if (!ws) return; - if (ws->weaponweightconfig) FreeWeightConfig(ws->weaponweightconfig); - if (ws->weaponweightindex) FreeMemory(ws->weaponweightindex); -} //end of the function BotFreeWeaponWeights -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotLoadWeaponWeights(int weaponstate, char *filename) -{ - bot_weaponstate_t *ws; - - ws = BotWeaponStateFromHandle(weaponstate); - if (!ws) return BLERR_CANNOTLOADWEAPONWEIGHTS; - BotFreeWeaponWeights(weaponstate); - // - ws->weaponweightconfig = ReadWeightConfig(filename); - if (!ws->weaponweightconfig) - { - botimport.Print(PRT_FATAL, "couldn't load weapon config %s\n", filename); - return BLERR_CANNOTLOADWEAPONWEIGHTS; - } //end if - if (!weaponconfig) return BLERR_CANNOTLOADWEAPONCONFIG; - ws->weaponweightindex = WeaponWeightIndex(ws->weaponweightconfig, weaponconfig); - return BLERR_NOERROR; -} //end of the function BotLoadWeaponWeights -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotGetWeaponInfo(int weaponstate, int weapon, weaponinfo_t *weaponinfo) -{ - bot_weaponstate_t *ws; - - if (!BotValidWeaponNumber(weapon)) return; - ws = BotWeaponStateFromHandle(weaponstate); - if (!ws) return; - if (!weaponconfig) return; - Com_Memcpy(weaponinfo, &weaponconfig->weaponinfo[weapon], sizeof(weaponinfo_t)); -} //end of the function BotGetWeaponInfo -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotChooseBestFightWeapon(int weaponstate, int *inventory) -{ - int i, index, bestweapon; - float weight, bestweight; - weaponconfig_t *wc; - bot_weaponstate_t *ws; - - ws = BotWeaponStateFromHandle(weaponstate); - if (!ws) return 0; - wc = weaponconfig; - if (!weaponconfig) return 0; - - //if the bot has no weapon weight configuration - if (!ws->weaponweightconfig) return 0; - - bestweight = 0; - bestweapon = 0; - for (i = 0; i < wc->numweapons; i++) - { - if (!wc->weaponinfo[i].valid) continue; - index = ws->weaponweightindex[i]; - if (index < 0) continue; - weight = FuzzyWeight(inventory, ws->weaponweightconfig, index); - if (weight > bestweight) - { - bestweight = weight; - bestweapon = i; - } //end if - } //end for - return bestweapon; -} //end of the function BotChooseBestFightWeapon -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotResetWeaponState(int weaponstate) -{ - struct weightconfig_s *weaponweightconfig; - int *weaponweightindex; - bot_weaponstate_t *ws; - - ws = BotWeaponStateFromHandle(weaponstate); - if (!ws) return; - weaponweightconfig = ws->weaponweightconfig; - weaponweightindex = ws->weaponweightindex; - - //Com_Memset(ws, 0, sizeof(bot_weaponstate_t)); - ws->weaponweightconfig = weaponweightconfig; - ws->weaponweightindex = weaponweightindex; -} //end of the function BotResetWeaponState -//======================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//======================================================================== -int BotAllocWeaponState(void) -{ - int i; - - for (i = 1; i <= MAX_CLIENTS; i++) - { - if (!botweaponstates[i]) - { - botweaponstates[i] = GetClearedMemory(sizeof(bot_weaponstate_t)); - return i; - } //end if - } //end for - return 0; -} //end of the function BotAllocWeaponState -//======================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//======================================================================== -void BotFreeWeaponState(int handle) -{ - if (handle <= 0 || handle > MAX_CLIENTS) - { - botimport.Print(PRT_FATAL, "move state handle %d out of range\n", handle); - return; - } //end if - if (!botweaponstates[handle]) - { - botimport.Print(PRT_FATAL, "invalid move state %d\n", handle); - return; - } //end if - BotFreeWeaponWeights(handle); - FreeMemory(botweaponstates[handle]); - botweaponstates[handle] = NULL; -} //end of the function BotFreeWeaponState -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotSetupWeaponAI(void) -{ - char *file; - - file = LibVarString("weaponconfig", "weapons.c"); - weaponconfig = LoadWeaponConfig(file); - if (!weaponconfig) - { - botimport.Print(PRT_FATAL, "couldn't load the weapon config\n"); - return BLERR_CANNOTLOADWEAPONCONFIG; - } //end if - -#ifdef DEBUG_AI_WEAP - DumpWeaponConfig(weaponconfig); -#endif //DEBUG_AI_WEAP - // - return BLERR_NOERROR; -} //end of the function BotSetupWeaponAI -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotShutdownWeaponAI(void) -{ - int i; - - if (weaponconfig) FreeMemory(weaponconfig); - weaponconfig = NULL; - - for (i = 1; i <= MAX_CLIENTS; i++) - { - if (botweaponstates[i]) - { - BotFreeWeaponState(i); - } //end if - } //end for -} //end of the function BotShutdownWeaponAI - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_ai_weap.c + * + * desc: weapon AI + * + * $Archive: /MissionPack/code/botlib/be_ai_weap.c $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_libvar.h" +#include "l_log.h" +#include "l_memory.h" +#include "l_utils.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_ai_weight.h" //fuzzy weights +#include "../game/be_ai_weap.h" + +//#define DEBUG_AI_WEAP + +//structure field offsets +#define WEAPON_OFS(x) (int)&(((weaponinfo_t *)0)->x) +#define PROJECTILE_OFS(x) (int)&(((projectileinfo_t *)0)->x) + +//weapon definition // bk001212 - static +static fielddef_t weaponinfo_fields[] = +{ +{"number", WEAPON_OFS(number), FT_INT}, //weapon number +{"name", WEAPON_OFS(name), FT_STRING}, //name of the weapon +{"level", WEAPON_OFS(level), FT_INT}, +{"model", WEAPON_OFS(model), FT_STRING}, //model of the weapon +{"weaponindex", WEAPON_OFS(weaponindex), FT_INT}, //index of weapon in inventory +{"flags", WEAPON_OFS(flags), FT_INT}, //special flags +{"projectile", WEAPON_OFS(projectile), FT_STRING}, //projectile used by the weapon +{"numprojectiles", WEAPON_OFS(numprojectiles), FT_INT}, //number of projectiles +{"hspread", WEAPON_OFS(hspread), FT_FLOAT}, //horizontal spread of projectiles (degrees from middle) +{"vspread", WEAPON_OFS(vspread), FT_FLOAT}, //vertical spread of projectiles (degrees from middle) +{"speed", WEAPON_OFS(speed), FT_FLOAT}, //speed of the projectile (0 = instant hit) +{"acceleration", WEAPON_OFS(acceleration), FT_FLOAT}, //"acceleration" * time (in seconds) + "speed" = projectile speed +{"recoil", WEAPON_OFS(recoil), FT_FLOAT|FT_ARRAY, 3}, //amount of recoil the player gets from the weapon +{"offset", WEAPON_OFS(offset), FT_FLOAT|FT_ARRAY, 3}, //projectile start offset relative to eye and view angles +{"angleoffset", WEAPON_OFS(angleoffset), FT_FLOAT|FT_ARRAY, 3},//offset of the shoot angles relative to the view angles +{"extrazvelocity", WEAPON_OFS(extrazvelocity), FT_FLOAT},//extra z velocity the projectile gets +{"ammoamount", WEAPON_OFS(ammoamount), FT_INT}, //ammo amount used per shot +{"ammoindex", WEAPON_OFS(ammoindex), FT_INT}, //index of ammo in inventory +{"activate", WEAPON_OFS(activate), FT_FLOAT}, //time it takes to select the weapon +{"reload", WEAPON_OFS(reload), FT_FLOAT}, //time it takes to reload the weapon +{"spinup", WEAPON_OFS(spinup), FT_FLOAT}, //time it takes before first shot +{"spindown", WEAPON_OFS(spindown), FT_FLOAT}, //time it takes before weapon stops firing +{NULL, 0, 0, 0} +}; + +//projectile definition +static fielddef_t projectileinfo_fields[] = +{ +{"name", PROJECTILE_OFS(name), FT_STRING}, //name of the projectile +{"model", WEAPON_OFS(model), FT_STRING}, //model of the projectile +{"flags", PROJECTILE_OFS(flags), FT_INT}, //special flags +{"gravity", PROJECTILE_OFS(gravity), FT_FLOAT}, //amount of gravity applied to the projectile [0,1] +{"damage", PROJECTILE_OFS(damage), FT_INT}, //damage of the projectile +{"radius", PROJECTILE_OFS(radius), FT_FLOAT}, //radius of damage +{"visdamage", PROJECTILE_OFS(visdamage), FT_INT}, //damage of the projectile to visible entities +{"damagetype", PROJECTILE_OFS(damagetype), FT_INT}, //type of damage (combination of the DAMAGETYPE_? flags) +{"healthinc", PROJECTILE_OFS(healthinc), FT_INT}, //health increase the owner gets +{"push", PROJECTILE_OFS(push), FT_FLOAT}, //amount a player is pushed away from the projectile impact +{"detonation", PROJECTILE_OFS(detonation), FT_FLOAT}, //time before projectile explodes after fire pressed +{"bounce", PROJECTILE_OFS(bounce), FT_FLOAT}, //amount the projectile bounces +{"bouncefric", PROJECTILE_OFS(bouncefric), FT_FLOAT}, //amount the bounce decreases per bounce +{"bouncestop", PROJECTILE_OFS(bouncestop), FT_FLOAT}, //minimum bounce value before bouncing stops +//recurive projectile definition?? +{NULL, 0, 0, 0} +}; + +static structdef_t weaponinfo_struct = +{ + sizeof(weaponinfo_t), weaponinfo_fields +}; +static structdef_t projectileinfo_struct = +{ + sizeof(projectileinfo_t), projectileinfo_fields +}; + +//weapon configuration: set of weapons with projectiles +typedef struct weaponconfig_s +{ + int numweapons; + int numprojectiles; + projectileinfo_t *projectileinfo; + weaponinfo_t *weaponinfo; +} weaponconfig_t; + +//the bot weapon state +typedef struct bot_weaponstate_s +{ + struct weightconfig_s *weaponweightconfig; //weapon weight configuration + int *weaponweightindex; //weapon weight index +} bot_weaponstate_t; + +static bot_weaponstate_t *botweaponstates[MAX_CLIENTS+1]; +static weaponconfig_t *weaponconfig; + +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +int BotValidWeaponNumber(int weaponnum) +{ + if (weaponnum <= 0 || weaponnum > weaponconfig->numweapons) + { + botimport.Print(PRT_ERROR, "weapon number out of range\n"); + return qfalse; + } //end if + return qtrue; +} //end of the function BotValidWeaponNumber +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +bot_weaponstate_t *BotWeaponStateFromHandle(int handle) +{ + if (handle <= 0 || handle > MAX_CLIENTS) + { + botimport.Print(PRT_FATAL, "move state handle %d out of range\n", handle); + return NULL; + } //end if + if (!botweaponstates[handle]) + { + botimport.Print(PRT_FATAL, "invalid move state %d\n", handle); + return NULL; + } //end if + return botweaponstates[handle]; +} //end of the function BotWeaponStateFromHandle +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef DEBUG_AI_WEAP +void DumpWeaponConfig(weaponconfig_t *wc) +{ + FILE *fp; + int i; + + fp = Log_FileStruct(); + if (!fp) return; + for (i = 0; i < wc->numprojectiles; i++) + { + WriteStructure(fp, &projectileinfo_struct, (char *) &wc->projectileinfo[i]); + Log_Flush(); + } //end for + for (i = 0; i < wc->numweapons; i++) + { + WriteStructure(fp, &weaponinfo_struct, (char *) &wc->weaponinfo[i]); + Log_Flush(); + } //end for +} //end of the function DumpWeaponConfig +#endif //DEBUG_AI_WEAP +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +weaponconfig_t *LoadWeaponConfig(char *filename) +{ + int max_weaponinfo, max_projectileinfo; + token_t token; + char path[MAX_PATH]; + int i, j; + source_t *source; + weaponconfig_t *wc; + weaponinfo_t weaponinfo; + + max_weaponinfo = (int) LibVarValue("max_weaponinfo", "32"); + if (max_weaponinfo < 0) + { + botimport.Print(PRT_ERROR, "max_weaponinfo = %d\n", max_weaponinfo); + max_weaponinfo = 32; + LibVarSet("max_weaponinfo", "32"); + } //end if + max_projectileinfo = (int) LibVarValue("max_projectileinfo", "32"); + if (max_projectileinfo < 0) + { + botimport.Print(PRT_ERROR, "max_projectileinfo = %d\n", max_projectileinfo); + max_projectileinfo = 32; + LibVarSet("max_projectileinfo", "32"); + } //end if + strncpy(path, filename, MAX_PATH); + PC_SetBaseFolder(BOTFILESBASEFOLDER); + source = LoadSourceFile(path); + if (!source) + { + botimport.Print(PRT_ERROR, "counldn't load %s\n", path); + return NULL; + } //end if + //initialize weapon config + wc = (weaponconfig_t *) GetClearedHunkMemory(sizeof(weaponconfig_t) + + max_weaponinfo * sizeof(weaponinfo_t) + + max_projectileinfo * sizeof(projectileinfo_t)); + wc->weaponinfo = (weaponinfo_t *) ((char *) wc + sizeof(weaponconfig_t)); + wc->projectileinfo = (projectileinfo_t *) ((char *) wc->weaponinfo + + max_weaponinfo * sizeof(weaponinfo_t)); + wc->numweapons = max_weaponinfo; + wc->numprojectiles = 0; + //parse the source file + while(PC_ReadToken(source, &token)) + { + if (!strcmp(token.string, "weaponinfo")) + { + Com_Memset(&weaponinfo, 0, sizeof(weaponinfo_t)); + if (!ReadStructure(source, &weaponinfo_struct, (char *) &weaponinfo)) + { + FreeMemory(wc); + FreeSource(source); + return NULL; + } //end if + if (weaponinfo.number < 0 || weaponinfo.number >= max_weaponinfo) + { + botimport.Print(PRT_ERROR, "weapon info number %d out of range in %s\n", weaponinfo.number, path); + FreeMemory(wc); + FreeSource(source); + return NULL; + } //end if + Com_Memcpy(&wc->weaponinfo[weaponinfo.number], &weaponinfo, sizeof(weaponinfo_t)); + wc->weaponinfo[weaponinfo.number].valid = qtrue; + } //end if + else if (!strcmp(token.string, "projectileinfo")) + { + if (wc->numprojectiles >= max_projectileinfo) + { + botimport.Print(PRT_ERROR, "more than %d projectiles defined in %s\n", max_projectileinfo, path); + FreeMemory(wc); + FreeSource(source); + return NULL; + } //end if + Com_Memset(&wc->projectileinfo[wc->numprojectiles], 0, sizeof(projectileinfo_t)); + if (!ReadStructure(source, &projectileinfo_struct, (char *) &wc->projectileinfo[wc->numprojectiles])) + { + FreeMemory(wc); + FreeSource(source); + return NULL; + } //end if + wc->numprojectiles++; + } //end if + else + { + botimport.Print(PRT_ERROR, "unknown definition %s in %s\n", token.string, path); + FreeMemory(wc); + FreeSource(source); + return NULL; + } //end else + } //end while + FreeSource(source); + //fix up weapons + for (i = 0; i < wc->numweapons; i++) + { + if (!wc->weaponinfo[i].valid) continue; + if (!wc->weaponinfo[i].name[0]) + { + botimport.Print(PRT_ERROR, "weapon %d has no name in %s\n", i, path); + FreeMemory(wc); + return NULL; + } //end if + if (!wc->weaponinfo[i].projectile[0]) + { + botimport.Print(PRT_ERROR, "weapon %s has no projectile in %s\n", wc->weaponinfo[i].name, path); + FreeMemory(wc); + return NULL; + } //end if + //find the projectile info and copy it to the weapon info + for (j = 0; j < wc->numprojectiles; j++) + { + if (!strcmp(wc->projectileinfo[j].name, wc->weaponinfo[i].projectile)) + { + Com_Memcpy(&wc->weaponinfo[i].proj, &wc->projectileinfo[j], sizeof(projectileinfo_t)); + break; + } //end if + } //end for + if (j == wc->numprojectiles) + { + botimport.Print(PRT_ERROR, "weapon %s uses undefined projectile in %s\n", wc->weaponinfo[i].name, path); + FreeMemory(wc); + return NULL; + } //end if + } //end for + if (!wc->numweapons) botimport.Print(PRT_WARNING, "no weapon info loaded\n"); + botimport.Print(PRT_MESSAGE, "loaded %s\n", path); + return wc; +} //end of the function LoadWeaponConfig +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int *WeaponWeightIndex(weightconfig_t *wwc, weaponconfig_t *wc) +{ + int *index, i; + + //initialize item weight index + index = (int *) GetClearedMemory(sizeof(int) * wc->numweapons); + + for (i = 0; i < wc->numweapons; i++) + { + index[i] = FindFuzzyWeight(wwc, wc->weaponinfo[i].name); + } //end for + return index; +} //end of the function WeaponWeightIndex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFreeWeaponWeights(int weaponstate) +{ + bot_weaponstate_t *ws; + + ws = BotWeaponStateFromHandle(weaponstate); + if (!ws) return; + if (ws->weaponweightconfig) FreeWeightConfig(ws->weaponweightconfig); + if (ws->weaponweightindex) FreeMemory(ws->weaponweightindex); +} //end of the function BotFreeWeaponWeights +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotLoadWeaponWeights(int weaponstate, char *filename) +{ + bot_weaponstate_t *ws; + + ws = BotWeaponStateFromHandle(weaponstate); + if (!ws) return BLERR_CANNOTLOADWEAPONWEIGHTS; + BotFreeWeaponWeights(weaponstate); + // + ws->weaponweightconfig = ReadWeightConfig(filename); + if (!ws->weaponweightconfig) + { + botimport.Print(PRT_FATAL, "couldn't load weapon config %s\n", filename); + return BLERR_CANNOTLOADWEAPONWEIGHTS; + } //end if + if (!weaponconfig) return BLERR_CANNOTLOADWEAPONCONFIG; + ws->weaponweightindex = WeaponWeightIndex(ws->weaponweightconfig, weaponconfig); + return BLERR_NOERROR; +} //end of the function BotLoadWeaponWeights +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotGetWeaponInfo(int weaponstate, int weapon, weaponinfo_t *weaponinfo) +{ + bot_weaponstate_t *ws; + + if (!BotValidWeaponNumber(weapon)) return; + ws = BotWeaponStateFromHandle(weaponstate); + if (!ws) return; + if (!weaponconfig) return; + Com_Memcpy(weaponinfo, &weaponconfig->weaponinfo[weapon], sizeof(weaponinfo_t)); +} //end of the function BotGetWeaponInfo +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotChooseBestFightWeapon(int weaponstate, int *inventory) +{ + int i, index, bestweapon; + float weight, bestweight; + weaponconfig_t *wc; + bot_weaponstate_t *ws; + + ws = BotWeaponStateFromHandle(weaponstate); + if (!ws) return 0; + wc = weaponconfig; + if (!weaponconfig) return 0; + + //if the bot has no weapon weight configuration + if (!ws->weaponweightconfig) return 0; + + bestweight = 0; + bestweapon = 0; + for (i = 0; i < wc->numweapons; i++) + { + if (!wc->weaponinfo[i].valid) continue; + index = ws->weaponweightindex[i]; + if (index < 0) continue; + weight = FuzzyWeight(inventory, ws->weaponweightconfig, index); + if (weight > bestweight) + { + bestweight = weight; + bestweapon = i; + } //end if + } //end for + return bestweapon; +} //end of the function BotChooseBestFightWeapon +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotResetWeaponState(int weaponstate) +{ + struct weightconfig_s *weaponweightconfig; + int *weaponweightindex; + bot_weaponstate_t *ws; + + ws = BotWeaponStateFromHandle(weaponstate); + if (!ws) return; + weaponweightconfig = ws->weaponweightconfig; + weaponweightindex = ws->weaponweightindex; + + //Com_Memset(ws, 0, sizeof(bot_weaponstate_t)); + ws->weaponweightconfig = weaponweightconfig; + ws->weaponweightindex = weaponweightindex; +} //end of the function BotResetWeaponState +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +int BotAllocWeaponState(void) +{ + int i; + + for (i = 1; i <= MAX_CLIENTS; i++) + { + if (!botweaponstates[i]) + { + botweaponstates[i] = GetClearedMemory(sizeof(bot_weaponstate_t)); + return i; + } //end if + } //end for + return 0; +} //end of the function BotAllocWeaponState +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotFreeWeaponState(int handle) +{ + if (handle <= 0 || handle > MAX_CLIENTS) + { + botimport.Print(PRT_FATAL, "move state handle %d out of range\n", handle); + return; + } //end if + if (!botweaponstates[handle]) + { + botimport.Print(PRT_FATAL, "invalid move state %d\n", handle); + return; + } //end if + BotFreeWeaponWeights(handle); + FreeMemory(botweaponstates[handle]); + botweaponstates[handle] = NULL; +} //end of the function BotFreeWeaponState +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotSetupWeaponAI(void) +{ + char *file; + + file = LibVarString("weaponconfig", "weapons.c"); + weaponconfig = LoadWeaponConfig(file); + if (!weaponconfig) + { + botimport.Print(PRT_FATAL, "couldn't load the weapon config\n"); + return BLERR_CANNOTLOADWEAPONCONFIG; + } //end if + +#ifdef DEBUG_AI_WEAP + DumpWeaponConfig(weaponconfig); +#endif //DEBUG_AI_WEAP + // + return BLERR_NOERROR; +} //end of the function BotSetupWeaponAI +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotShutdownWeaponAI(void) +{ + int i; + + if (weaponconfig) FreeMemory(weaponconfig); + weaponconfig = NULL; + + for (i = 1; i <= MAX_CLIENTS; i++) + { + if (botweaponstates[i]) + { + BotFreeWeaponState(i); + } //end if + } //end for +} //end of the function BotShutdownWeaponAI + diff --git a/code/botlib/be_ai_weight.c b/code/botlib/be_ai_weight.c index c501593..9212519 100755 --- a/code/botlib/be_ai_weight.c +++ b/code/botlib/be_ai_weight.c @@ -1,912 +1,912 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: be_ai_weight.c - * - * desc: fuzzy logic - * - * $Archive: /MissionPack/code/botlib/be_ai_weight.c $ - * - *****************************************************************************/ - -#include "../game/q_shared.h" -#include "l_memory.h" -#include "l_log.h" -#include "l_utils.h" -#include "l_script.h" -#include "l_precomp.h" -#include "l_struct.h" -#include "l_libvar.h" -#include "aasfile.h" -#include "../game/botlib.h" -#include "../game/be_aas.h" -#include "be_aas_funcs.h" -#include "be_interface.h" -#include "be_ai_weight.h" - -#define MAX_INVENTORYVALUE 999999 -#define EVALUATERECURSIVELY - -#define MAX_WEIGHT_FILES 128 -weightconfig_t *weightFileList[MAX_WEIGHT_FILES]; - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int ReadValue(source_t *source, float *value) -{ - token_t token; - - if (!PC_ExpectAnyToken(source, &token)) return qfalse; - if (!strcmp(token.string, "-")) - { - SourceWarning(source, "negative value set to zero\n"); - if (!PC_ExpectTokenType(source, TT_NUMBER, 0, &token)) return qfalse; - } //end if - if (token.type != TT_NUMBER) - { - SourceError(source, "invalid return value %s\n", token.string); - return qfalse; - } //end if - *value = token.floatvalue; - return qtrue; -} //end of the function ReadValue -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int ReadFuzzyWeight(source_t *source, fuzzyseperator_t *fs) -{ - if (PC_CheckTokenString(source, "balance")) - { - fs->type = WT_BALANCE; - if (!PC_ExpectTokenString(source, "(")) return qfalse; - if (!ReadValue(source, &fs->weight)) return qfalse; - if (!PC_ExpectTokenString(source, ",")) return qfalse; - if (!ReadValue(source, &fs->minweight)) return qfalse; - if (!PC_ExpectTokenString(source, ",")) return qfalse; - if (!ReadValue(source, &fs->maxweight)) return qfalse; - if (!PC_ExpectTokenString(source, ")")) return qfalse; - } //end if - else - { - fs->type = 0; - if (!ReadValue(source, &fs->weight)) return qfalse; - fs->minweight = fs->weight; - fs->maxweight = fs->weight; - } //end if - if (!PC_ExpectTokenString(source, ";")) return qfalse; - return qtrue; -} //end of the function ReadFuzzyWeight -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void FreeFuzzySeperators_r(fuzzyseperator_t *fs) -{ - if (!fs) return; - if (fs->child) FreeFuzzySeperators_r(fs->child); - if (fs->next) FreeFuzzySeperators_r(fs->next); - FreeMemory(fs); -} //end of the function FreeFuzzySeperators -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void FreeWeightConfig2(weightconfig_t *config) -{ - int i; - - for (i = 0; i < config->numweights; i++) - { - FreeFuzzySeperators_r(config->weights[i].firstseperator); - if (config->weights[i].name) FreeMemory(config->weights[i].name); - } //end for - FreeMemory(config); -} //end of the function FreeWeightConfig2 -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void FreeWeightConfig(weightconfig_t *config) -{ - if (!LibVarGetValue("bot_reloadcharacters")) return; - FreeWeightConfig2(config); -} //end of the function FreeWeightConfig -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -fuzzyseperator_t *ReadFuzzySeperators_r(source_t *source) -{ - int newindent, index, def, founddefault; - token_t token; - fuzzyseperator_t *fs, *lastfs, *firstfs; - - founddefault = qfalse; - firstfs = NULL; - lastfs = NULL; - if (!PC_ExpectTokenString(source, "(")) return NULL; - if (!PC_ExpectTokenType(source, TT_NUMBER, TT_INTEGER, &token)) return NULL; - index = token.intvalue; - if (!PC_ExpectTokenString(source, ")")) return NULL; - if (!PC_ExpectTokenString(source, "{")) return NULL; - if (!PC_ExpectAnyToken(source, &token)) return NULL; - do - { - def = !strcmp(token.string, "default"); - if (def || !strcmp(token.string, "case")) - { - fs = (fuzzyseperator_t *) GetClearedMemory(sizeof(fuzzyseperator_t)); - fs->index = index; - if (lastfs) lastfs->next = fs; - else firstfs = fs; - lastfs = fs; - if (def) - { - if (founddefault) - { - SourceError(source, "switch already has a default\n"); - FreeFuzzySeperators_r(firstfs); - return NULL; - } //end if - fs->value = MAX_INVENTORYVALUE; - founddefault = qtrue; - } //end if - else - { - if (!PC_ExpectTokenType(source, TT_NUMBER, TT_INTEGER, &token)) - { - FreeFuzzySeperators_r(firstfs); - return NULL; - } //end if - fs->value = token.intvalue; - } //end else - if (!PC_ExpectTokenString(source, ":") || !PC_ExpectAnyToken(source, &token)) - { - FreeFuzzySeperators_r(firstfs); - return NULL; - } //end if - newindent = qfalse; - if (!strcmp(token.string, "{")) - { - newindent = qtrue; - if (!PC_ExpectAnyToken(source, &token)) - { - FreeFuzzySeperators_r(firstfs); - return NULL; - } //end if - } //end if - if (!strcmp(token.string, "return")) - { - if (!ReadFuzzyWeight(source, fs)) - { - FreeFuzzySeperators_r(firstfs); - return NULL; - } //end if - } //end if - else if (!strcmp(token.string, "switch")) - { - fs->child = ReadFuzzySeperators_r(source); - if (!fs->child) - { - FreeFuzzySeperators_r(firstfs); - return NULL; - } //end if - } //end else if - else - { - SourceError(source, "invalid name %s\n", token.string); - return NULL; - } //end else - if (newindent) - { - if (!PC_ExpectTokenString(source, "}")) - { - FreeFuzzySeperators_r(firstfs); - return NULL; - } //end if - } //end if - } //end if - else - { - FreeFuzzySeperators_r(firstfs); - SourceError(source, "invalid name %s\n", token.string); - return NULL; - } //end else - if (!PC_ExpectAnyToken(source, &token)) - { - FreeFuzzySeperators_r(firstfs); - return NULL; - } //end if - } while(strcmp(token.string, "}")); - // - if (!founddefault) - { - SourceWarning(source, "switch without default\n"); - fs = (fuzzyseperator_t *) GetClearedMemory(sizeof(fuzzyseperator_t)); - fs->index = index; - fs->value = MAX_INVENTORYVALUE; - fs->weight = 0; - fs->next = NULL; - fs->child = NULL; - if (lastfs) lastfs->next = fs; - else firstfs = fs; - lastfs = fs; - } //end if - // - return firstfs; -} //end of the function ReadFuzzySeperators_r -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -weightconfig_t *ReadWeightConfig(char *filename) -{ - int newindent, avail = 0, n; - token_t token; - source_t *source; - fuzzyseperator_t *fs; - weightconfig_t *config = NULL; -#ifdef DEBUG - int starttime; - - starttime = Sys_MilliSeconds(); -#endif //DEBUG - - if (!LibVarGetValue("bot_reloadcharacters")) - { - avail = -1; - for( n = 0; n < MAX_WEIGHT_FILES; n++ ) - { - config = weightFileList[n]; - if( !config ) - { - if( avail == -1 ) - { - avail = n; - } //end if - continue; - } //end if - if( strcmp( filename, config->filename ) == 0 ) - { - //botimport.Print( PRT_MESSAGE, "retained %s\n", filename ); - return config; - } //end if - } //end for - - if( avail == -1 ) - { - botimport.Print( PRT_ERROR, "weightFileList was full trying to load %s\n", filename ); - return NULL; - } //end if - } //end if - - PC_SetBaseFolder(BOTFILESBASEFOLDER); - source = LoadSourceFile(filename); - if (!source) - { - botimport.Print(PRT_ERROR, "counldn't load %s\n", filename); - return NULL; - } //end if - // - config = (weightconfig_t *) GetClearedMemory(sizeof(weightconfig_t)); - config->numweights = 0; - Q_strncpyz( config->filename, filename, sizeof(config->filename) ); - //parse the item config file - while(PC_ReadToken(source, &token)) - { - if (!strcmp(token.string, "weight")) - { - if (config->numweights >= MAX_WEIGHTS) - { - SourceWarning(source, "too many fuzzy weights\n"); - break; - } //end if - if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) - { - FreeWeightConfig(config); - FreeSource(source); - return NULL; - } //end if - StripDoubleQuotes(token.string); - config->weights[config->numweights].name = (char *) GetClearedMemory(strlen(token.string) + 1); - strcpy(config->weights[config->numweights].name, token.string); - if (!PC_ExpectAnyToken(source, &token)) - { - FreeWeightConfig(config); - FreeSource(source); - return NULL; - } //end if - newindent = qfalse; - if (!strcmp(token.string, "{")) - { - newindent = qtrue; - if (!PC_ExpectAnyToken(source, &token)) - { - FreeWeightConfig(config); - FreeSource(source); - return NULL; - } //end if - } //end if - if (!strcmp(token.string, "switch")) - { - fs = ReadFuzzySeperators_r(source); - if (!fs) - { - FreeWeightConfig(config); - FreeSource(source); - return NULL; - } //end if - config->weights[config->numweights].firstseperator = fs; - } //end if - else if (!strcmp(token.string, "return")) - { - fs = (fuzzyseperator_t *) GetClearedMemory(sizeof(fuzzyseperator_t)); - fs->index = 0; - fs->value = MAX_INVENTORYVALUE; - fs->next = NULL; - fs->child = NULL; - if (!ReadFuzzyWeight(source, fs)) - { - FreeMemory(fs); - FreeWeightConfig(config); - FreeSource(source); - return NULL; - } //end if - config->weights[config->numweights].firstseperator = fs; - } //end else if - else - { - SourceError(source, "invalid name %s\n", token.string); - FreeWeightConfig(config); - FreeSource(source); - return NULL; - } //end else - if (newindent) - { - if (!PC_ExpectTokenString(source, "}")) - { - FreeWeightConfig(config); - FreeSource(source); - return NULL; - } //end if - } //end if - config->numweights++; - } //end if - else - { - SourceError(source, "invalid name %s\n", token.string); - FreeWeightConfig(config); - FreeSource(source); - return NULL; - } //end else - } //end while - //free the source at the end of a pass - FreeSource(source); - //if the file was located in a pak file - botimport.Print(PRT_MESSAGE, "loaded %s\n", filename); -#ifdef DEBUG - if (bot_developer) - { - botimport.Print(PRT_MESSAGE, "weights loaded in %d msec\n", Sys_MilliSeconds() - starttime); - } //end if -#endif //DEBUG - // - if (!LibVarGetValue("bot_reloadcharacters")) - { - weightFileList[avail] = config; - } //end if - // - return config; -} //end of the function ReadWeightConfig -#if 0 -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean WriteFuzzyWeight(FILE *fp, fuzzyseperator_t *fs) -{ - if (fs->type == WT_BALANCE) - { - if (fprintf(fp, " return balance(") < 0) return qfalse; - if (!WriteFloat(fp, fs->weight)) return qfalse; - if (fprintf(fp, ",") < 0) return qfalse; - if (!WriteFloat(fp, fs->minweight)) return qfalse; - if (fprintf(fp, ",") < 0) return qfalse; - if (!WriteFloat(fp, fs->maxweight)) return qfalse; - if (fprintf(fp, ");\n") < 0) return qfalse; - } //end if - else - { - if (fprintf(fp, " return ") < 0) return qfalse; - if (!WriteFloat(fp, fs->weight)) return qfalse; - if (fprintf(fp, ";\n") < 0) return qfalse; - } //end else - return qtrue; -} //end of the function WriteFuzzyWeight -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean WriteFuzzySeperators_r(FILE *fp, fuzzyseperator_t *fs, int indent) -{ - if (!WriteIndent(fp, indent)) return qfalse; - if (fprintf(fp, "switch(%d)\n", fs->index) < 0) return qfalse; - if (!WriteIndent(fp, indent)) return qfalse; - if (fprintf(fp, "{\n") < 0) return qfalse; - indent++; - do - { - if (!WriteIndent(fp, indent)) return qfalse; - if (fs->next) - { - if (fprintf(fp, "case %d:", fs->value) < 0) return qfalse; - } //end if - else - { - if (fprintf(fp, "default:") < 0) return qfalse; - } //end else - if (fs->child) - { - if (fprintf(fp, "\n") < 0) return qfalse; - if (!WriteIndent(fp, indent)) return qfalse; - if (fprintf(fp, "{\n") < 0) return qfalse; - if (!WriteFuzzySeperators_r(fp, fs->child, indent + 1)) return qfalse; - if (!WriteIndent(fp, indent)) return qfalse; - if (fs->next) - { - if (fprintf(fp, "} //end case\n") < 0) return qfalse; - } //end if - else - { - if (fprintf(fp, "} //end default\n") < 0) return qfalse; - } //end else - } //end if - else - { - if (!WriteFuzzyWeight(fp, fs)) return qfalse; - } //end else - fs = fs->next; - } while(fs); - indent--; - if (!WriteIndent(fp, indent)) return qfalse; - if (fprintf(fp, "} //end switch\n") < 0) return qfalse; - return qtrue; -} //end of the function WriteItemFuzzyWeights_r -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean WriteWeightConfig(char *filename, weightconfig_t *config) -{ - int i; - FILE *fp; - weight_t *ifw; - - fp = fopen(filename, "wb"); - if (!fp) return qfalse; - - for (i = 0; i < config->numweights; i++) - { - ifw = &config->weights[i]; - if (fprintf(fp, "\nweight \"%s\"\n", ifw->name) < 0) return qfalse; - if (fprintf(fp, "{\n") < 0) return qfalse; - if (ifw->firstseperator->index > 0) - { - if (!WriteFuzzySeperators_r(fp, ifw->firstseperator, 1)) return qfalse; - } //end if - else - { - if (!WriteIndent(fp, 1)) return qfalse; - if (!WriteFuzzyWeight(fp, ifw->firstseperator)) return qfalse; - } //end else - if (fprintf(fp, "} //end weight\n") < 0) return qfalse; - } //end for - fclose(fp); - return qtrue; -} //end of the function WriteWeightConfig -#endif -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int FindFuzzyWeight(weightconfig_t *wc, char *name) -{ - int i; - - for (i = 0; i < wc->numweights; i++) - { - if (!strcmp(wc->weights[i].name, name)) - { - return i; - } //end if - } //end if - return -1; -} //end of the function FindFuzzyWeight -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -float FuzzyWeight_r(int *inventory, fuzzyseperator_t *fs) -{ - float scale, w1, w2; - - if (inventory[fs->index] < fs->value) - { - if (fs->child) return FuzzyWeight_r(inventory, fs->child); - else return fs->weight; - } //end if - else if (fs->next) - { - if (inventory[fs->index] < fs->next->value) - { - //first weight - if (fs->child) w1 = FuzzyWeight_r(inventory, fs->child); - else w1 = fs->weight; - //second weight - if (fs->next->child) w2 = FuzzyWeight_r(inventory, fs->next->child); - else w2 = fs->next->weight; - //the scale factor - scale = (inventory[fs->index] - fs->value) / (fs->next->value - fs->value); - //scale between the two weights - return scale * w1 + (1 - scale) * w2; - } //end if - return FuzzyWeight_r(inventory, fs->next); - } //end else if - return fs->weight; -} //end of the function FuzzyWeight_r -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -float FuzzyWeightUndecided_r(int *inventory, fuzzyseperator_t *fs) -{ - float scale, w1, w2; - - if (inventory[fs->index] < fs->value) - { - if (fs->child) return FuzzyWeightUndecided_r(inventory, fs->child); - else return fs->minweight + random() * (fs->maxweight - fs->minweight); - } //end if - else if (fs->next) - { - if (inventory[fs->index] < fs->next->value) - { - //first weight - if (fs->child) w1 = FuzzyWeightUndecided_r(inventory, fs->child); - else w1 = fs->minweight + random() * (fs->maxweight - fs->minweight); - //second weight - if (fs->next->child) w2 = FuzzyWeight_r(inventory, fs->next->child); - else w2 = fs->next->minweight + random() * (fs->next->maxweight - fs->next->minweight); - //the scale factor - scale = (inventory[fs->index] - fs->value) / (fs->next->value - fs->value); - //scale between the two weights - return scale * w1 + (1 - scale) * w2; - } //end if - return FuzzyWeightUndecided_r(inventory, fs->next); - } //end else if - return fs->weight; -} //end of the function FuzzyWeightUndecided_r -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -float FuzzyWeight(int *inventory, weightconfig_t *wc, int weightnum) -{ -#ifdef EVALUATERECURSIVELY - return FuzzyWeight_r(inventory, wc->weights[weightnum].firstseperator); -#else - fuzzyseperator_t *s; - - s = wc->weights[weightnum].firstseperator; - if (!s) return 0; - while(1) - { - if (inventory[s->index] < s->value) - { - if (s->child) s = s->child; - else return s->weight; - } //end if - else - { - if (s->next) s = s->next; - else return s->weight; - } //end else - } //end if - return 0; -#endif -} //end of the function FuzzyWeight -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -float FuzzyWeightUndecided(int *inventory, weightconfig_t *wc, int weightnum) -{ -#ifdef EVALUATERECURSIVELY - return FuzzyWeightUndecided_r(inventory, wc->weights[weightnum].firstseperator); -#else - fuzzyseperator_t *s; - - s = wc->weights[weightnum].firstseperator; - if (!s) return 0; - while(1) - { - if (inventory[s->index] < s->value) - { - if (s->child) s = s->child; - else return s->minweight + random() * (s->maxweight - s->minweight); - } //end if - else - { - if (s->next) s = s->next; - else return s->minweight + random() * (s->maxweight - s->minweight); - } //end else - } //end if - return 0; -#endif -} //end of the function FuzzyWeightUndecided -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void EvolveFuzzySeperator_r(fuzzyseperator_t *fs) -{ - if (fs->child) - { - EvolveFuzzySeperator_r(fs->child); - } //end if - else if (fs->type == WT_BALANCE) - { - //every once in a while an evolution leap occurs, mutation - if (random() < 0.01) fs->weight += crandom() * (fs->maxweight - fs->minweight); - else fs->weight += crandom() * (fs->maxweight - fs->minweight) * 0.5; - //modify bounds if necesary because of mutation - if (fs->weight < fs->minweight) fs->minweight = fs->weight; - else if (fs->weight > fs->maxweight) fs->maxweight = fs->weight; - } //end else if - if (fs->next) EvolveFuzzySeperator_r(fs->next); -} //end of the function EvolveFuzzySeperator_r -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void EvolveWeightConfig(weightconfig_t *config) -{ - int i; - - for (i = 0; i < config->numweights; i++) - { - EvolveFuzzySeperator_r(config->weights[i].firstseperator); - } //end for -} //end of the function EvolveWeightConfig -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ScaleFuzzySeperator_r(fuzzyseperator_t *fs, float scale) -{ - if (fs->child) - { - ScaleFuzzySeperator_r(fs->child, scale); - } //end if - else if (fs->type == WT_BALANCE) - { - // - fs->weight = (fs->maxweight + fs->minweight) * scale; - //get the weight between bounds - if (fs->weight < fs->minweight) fs->weight = fs->minweight; - else if (fs->weight > fs->maxweight) fs->weight = fs->maxweight; - } //end else if - if (fs->next) ScaleFuzzySeperator_r(fs->next, scale); -} //end of the function ScaleFuzzySeperator_r -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ScaleWeight(weightconfig_t *config, char *name, float scale) -{ - int i; - - if (scale < 0) scale = 0; - else if (scale > 1) scale = 1; - for (i = 0; i < config->numweights; i++) - { - if (!strcmp(name, config->weights[i].name)) - { - ScaleFuzzySeperator_r(config->weights[i].firstseperator, scale); - break; - } //end if - } //end for -} //end of the function ScaleWeight -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ScaleFuzzySeperatorBalanceRange_r(fuzzyseperator_t *fs, float scale) -{ - if (fs->child) - { - ScaleFuzzySeperatorBalanceRange_r(fs->child, scale); - } //end if - else if (fs->type == WT_BALANCE) - { - float mid = (fs->minweight + fs->maxweight) * 0.5; - //get the weight between bounds - fs->maxweight = mid + (fs->maxweight - mid) * scale; - fs->minweight = mid + (fs->minweight - mid) * scale; - if (fs->maxweight < fs->minweight) - { - fs->maxweight = fs->minweight; - } //end if - } //end else if - if (fs->next) ScaleFuzzySeperatorBalanceRange_r(fs->next, scale); -} //end of the function ScaleFuzzySeperatorBalanceRange_r -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ScaleFuzzyBalanceRange(weightconfig_t *config, float scale) -{ - int i; - - if (scale < 0) scale = 0; - else if (scale > 100) scale = 100; - for (i = 0; i < config->numweights; i++) - { - ScaleFuzzySeperatorBalanceRange_r(config->weights[i].firstseperator, scale); - } //end for -} //end of the function ScaleFuzzyBalanceRange -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int InterbreedFuzzySeperator_r(fuzzyseperator_t *fs1, fuzzyseperator_t *fs2, - fuzzyseperator_t *fsout) -{ - if (fs1->child) - { - if (!fs2->child || !fsout->child) - { - botimport.Print(PRT_ERROR, "cannot interbreed weight configs, unequal child\n"); - return qfalse; - } //end if - if (!InterbreedFuzzySeperator_r(fs2->child, fs2->child, fsout->child)) - { - return qfalse; - } //end if - } //end if - else if (fs1->type == WT_BALANCE) - { - if (fs2->type != WT_BALANCE || fsout->type != WT_BALANCE) - { - botimport.Print(PRT_ERROR, "cannot interbreed weight configs, unequal balance\n"); - return qfalse; - } //end if - fsout->weight = (fs1->weight + fs2->weight) / 2; - if (fsout->weight > fsout->maxweight) fsout->maxweight = fsout->weight; - if (fsout->weight > fsout->minweight) fsout->minweight = fsout->weight; - } //end else if - if (fs1->next) - { - if (!fs2->next || !fsout->next) - { - botimport.Print(PRT_ERROR, "cannot interbreed weight configs, unequal next\n"); - return qfalse; - } //end if - if (!InterbreedFuzzySeperator_r(fs1->next, fs2->next, fsout->next)) - { - return qfalse; - } //end if - } //end if - return qtrue; -} //end of the function InterbreedFuzzySeperator_r -//=========================================================================== -// config1 and config2 are interbreeded and stored in configout -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void InterbreedWeightConfigs(weightconfig_t *config1, weightconfig_t *config2, - weightconfig_t *configout) -{ - int i; - - if (config1->numweights != config2->numweights || - config1->numweights != configout->numweights) - { - botimport.Print(PRT_ERROR, "cannot interbreed weight configs, unequal numweights\n"); - return; - } //end if - for (i = 0; i < config1->numweights; i++) - { - InterbreedFuzzySeperator_r(config1->weights[i].firstseperator, - config2->weights[i].firstseperator, - configout->weights[i].firstseperator); - } //end for -} //end of the function InterbreedWeightConfigs -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotShutdownWeights(void) -{ - int i; - - for( i = 0; i < MAX_WEIGHT_FILES; i++ ) - { - if (weightFileList[i]) - { - FreeWeightConfig2(weightFileList[i]); - weightFileList[i] = NULL; - } //end if - } //end for -} //end of the function BotShutdownWeights +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_ai_weight.c + * + * desc: fuzzy logic + * + * $Archive: /MissionPack/code/botlib/be_ai_weight.c $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_log.h" +#include "l_utils.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_libvar.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_ai_weight.h" + +#define MAX_INVENTORYVALUE 999999 +#define EVALUATERECURSIVELY + +#define MAX_WEIGHT_FILES 128 +weightconfig_t *weightFileList[MAX_WEIGHT_FILES]; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int ReadValue(source_t *source, float *value) +{ + token_t token; + + if (!PC_ExpectAnyToken(source, &token)) return qfalse; + if (!strcmp(token.string, "-")) + { + SourceWarning(source, "negative value set to zero\n"); + if (!PC_ExpectTokenType(source, TT_NUMBER, 0, &token)) return qfalse; + } //end if + if (token.type != TT_NUMBER) + { + SourceError(source, "invalid return value %s\n", token.string); + return qfalse; + } //end if + *value = token.floatvalue; + return qtrue; +} //end of the function ReadValue +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int ReadFuzzyWeight(source_t *source, fuzzyseperator_t *fs) +{ + if (PC_CheckTokenString(source, "balance")) + { + fs->type = WT_BALANCE; + if (!PC_ExpectTokenString(source, "(")) return qfalse; + if (!ReadValue(source, &fs->weight)) return qfalse; + if (!PC_ExpectTokenString(source, ",")) return qfalse; + if (!ReadValue(source, &fs->minweight)) return qfalse; + if (!PC_ExpectTokenString(source, ",")) return qfalse; + if (!ReadValue(source, &fs->maxweight)) return qfalse; + if (!PC_ExpectTokenString(source, ")")) return qfalse; + } //end if + else + { + fs->type = 0; + if (!ReadValue(source, &fs->weight)) return qfalse; + fs->minweight = fs->weight; + fs->maxweight = fs->weight; + } //end if + if (!PC_ExpectTokenString(source, ";")) return qfalse; + return qtrue; +} //end of the function ReadFuzzyWeight +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeFuzzySeperators_r(fuzzyseperator_t *fs) +{ + if (!fs) return; + if (fs->child) FreeFuzzySeperators_r(fs->child); + if (fs->next) FreeFuzzySeperators_r(fs->next); + FreeMemory(fs); +} //end of the function FreeFuzzySeperators +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeWeightConfig2(weightconfig_t *config) +{ + int i; + + for (i = 0; i < config->numweights; i++) + { + FreeFuzzySeperators_r(config->weights[i].firstseperator); + if (config->weights[i].name) FreeMemory(config->weights[i].name); + } //end for + FreeMemory(config); +} //end of the function FreeWeightConfig2 +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeWeightConfig(weightconfig_t *config) +{ + if (!LibVarGetValue("bot_reloadcharacters")) return; + FreeWeightConfig2(config); +} //end of the function FreeWeightConfig +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +fuzzyseperator_t *ReadFuzzySeperators_r(source_t *source) +{ + int newindent, index, def, founddefault; + token_t token; + fuzzyseperator_t *fs, *lastfs, *firstfs; + + founddefault = qfalse; + firstfs = NULL; + lastfs = NULL; + if (!PC_ExpectTokenString(source, "(")) return NULL; + if (!PC_ExpectTokenType(source, TT_NUMBER, TT_INTEGER, &token)) return NULL; + index = token.intvalue; + if (!PC_ExpectTokenString(source, ")")) return NULL; + if (!PC_ExpectTokenString(source, "{")) return NULL; + if (!PC_ExpectAnyToken(source, &token)) return NULL; + do + { + def = !strcmp(token.string, "default"); + if (def || !strcmp(token.string, "case")) + { + fs = (fuzzyseperator_t *) GetClearedMemory(sizeof(fuzzyseperator_t)); + fs->index = index; + if (lastfs) lastfs->next = fs; + else firstfs = fs; + lastfs = fs; + if (def) + { + if (founddefault) + { + SourceError(source, "switch already has a default\n"); + FreeFuzzySeperators_r(firstfs); + return NULL; + } //end if + fs->value = MAX_INVENTORYVALUE; + founddefault = qtrue; + } //end if + else + { + if (!PC_ExpectTokenType(source, TT_NUMBER, TT_INTEGER, &token)) + { + FreeFuzzySeperators_r(firstfs); + return NULL; + } //end if + fs->value = token.intvalue; + } //end else + if (!PC_ExpectTokenString(source, ":") || !PC_ExpectAnyToken(source, &token)) + { + FreeFuzzySeperators_r(firstfs); + return NULL; + } //end if + newindent = qfalse; + if (!strcmp(token.string, "{")) + { + newindent = qtrue; + if (!PC_ExpectAnyToken(source, &token)) + { + FreeFuzzySeperators_r(firstfs); + return NULL; + } //end if + } //end if + if (!strcmp(token.string, "return")) + { + if (!ReadFuzzyWeight(source, fs)) + { + FreeFuzzySeperators_r(firstfs); + return NULL; + } //end if + } //end if + else if (!strcmp(token.string, "switch")) + { + fs->child = ReadFuzzySeperators_r(source); + if (!fs->child) + { + FreeFuzzySeperators_r(firstfs); + return NULL; + } //end if + } //end else if + else + { + SourceError(source, "invalid name %s\n", token.string); + return NULL; + } //end else + if (newindent) + { + if (!PC_ExpectTokenString(source, "}")) + { + FreeFuzzySeperators_r(firstfs); + return NULL; + } //end if + } //end if + } //end if + else + { + FreeFuzzySeperators_r(firstfs); + SourceError(source, "invalid name %s\n", token.string); + return NULL; + } //end else + if (!PC_ExpectAnyToken(source, &token)) + { + FreeFuzzySeperators_r(firstfs); + return NULL; + } //end if + } while(strcmp(token.string, "}")); + // + if (!founddefault) + { + SourceWarning(source, "switch without default\n"); + fs = (fuzzyseperator_t *) GetClearedMemory(sizeof(fuzzyseperator_t)); + fs->index = index; + fs->value = MAX_INVENTORYVALUE; + fs->weight = 0; + fs->next = NULL; + fs->child = NULL; + if (lastfs) lastfs->next = fs; + else firstfs = fs; + lastfs = fs; + } //end if + // + return firstfs; +} //end of the function ReadFuzzySeperators_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +weightconfig_t *ReadWeightConfig(char *filename) +{ + int newindent, avail = 0, n; + token_t token; + source_t *source; + fuzzyseperator_t *fs; + weightconfig_t *config = NULL; +#ifdef DEBUG + int starttime; + + starttime = Sys_MilliSeconds(); +#endif //DEBUG + + if (!LibVarGetValue("bot_reloadcharacters")) + { + avail = -1; + for( n = 0; n < MAX_WEIGHT_FILES; n++ ) + { + config = weightFileList[n]; + if( !config ) + { + if( avail == -1 ) + { + avail = n; + } //end if + continue; + } //end if + if( strcmp( filename, config->filename ) == 0 ) + { + //botimport.Print( PRT_MESSAGE, "retained %s\n", filename ); + return config; + } //end if + } //end for + + if( avail == -1 ) + { + botimport.Print( PRT_ERROR, "weightFileList was full trying to load %s\n", filename ); + return NULL; + } //end if + } //end if + + PC_SetBaseFolder(BOTFILESBASEFOLDER); + source = LoadSourceFile(filename); + if (!source) + { + botimport.Print(PRT_ERROR, "counldn't load %s\n", filename); + return NULL; + } //end if + // + config = (weightconfig_t *) GetClearedMemory(sizeof(weightconfig_t)); + config->numweights = 0; + Q_strncpyz( config->filename, filename, sizeof(config->filename) ); + //parse the item config file + while(PC_ReadToken(source, &token)) + { + if (!strcmp(token.string, "weight")) + { + if (config->numweights >= MAX_WEIGHTS) + { + SourceWarning(source, "too many fuzzy weights\n"); + break; + } //end if + if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) + { + FreeWeightConfig(config); + FreeSource(source); + return NULL; + } //end if + StripDoubleQuotes(token.string); + config->weights[config->numweights].name = (char *) GetClearedMemory(strlen(token.string) + 1); + strcpy(config->weights[config->numweights].name, token.string); + if (!PC_ExpectAnyToken(source, &token)) + { + FreeWeightConfig(config); + FreeSource(source); + return NULL; + } //end if + newindent = qfalse; + if (!strcmp(token.string, "{")) + { + newindent = qtrue; + if (!PC_ExpectAnyToken(source, &token)) + { + FreeWeightConfig(config); + FreeSource(source); + return NULL; + } //end if + } //end if + if (!strcmp(token.string, "switch")) + { + fs = ReadFuzzySeperators_r(source); + if (!fs) + { + FreeWeightConfig(config); + FreeSource(source); + return NULL; + } //end if + config->weights[config->numweights].firstseperator = fs; + } //end if + else if (!strcmp(token.string, "return")) + { + fs = (fuzzyseperator_t *) GetClearedMemory(sizeof(fuzzyseperator_t)); + fs->index = 0; + fs->value = MAX_INVENTORYVALUE; + fs->next = NULL; + fs->child = NULL; + if (!ReadFuzzyWeight(source, fs)) + { + FreeMemory(fs); + FreeWeightConfig(config); + FreeSource(source); + return NULL; + } //end if + config->weights[config->numweights].firstseperator = fs; + } //end else if + else + { + SourceError(source, "invalid name %s\n", token.string); + FreeWeightConfig(config); + FreeSource(source); + return NULL; + } //end else + if (newindent) + { + if (!PC_ExpectTokenString(source, "}")) + { + FreeWeightConfig(config); + FreeSource(source); + return NULL; + } //end if + } //end if + config->numweights++; + } //end if + else + { + SourceError(source, "invalid name %s\n", token.string); + FreeWeightConfig(config); + FreeSource(source); + return NULL; + } //end else + } //end while + //free the source at the end of a pass + FreeSource(source); + //if the file was located in a pak file + botimport.Print(PRT_MESSAGE, "loaded %s\n", filename); +#ifdef DEBUG + if (bot_developer) + { + botimport.Print(PRT_MESSAGE, "weights loaded in %d msec\n", Sys_MilliSeconds() - starttime); + } //end if +#endif //DEBUG + // + if (!LibVarGetValue("bot_reloadcharacters")) + { + weightFileList[avail] = config; + } //end if + // + return config; +} //end of the function ReadWeightConfig +#if 0 +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean WriteFuzzyWeight(FILE *fp, fuzzyseperator_t *fs) +{ + if (fs->type == WT_BALANCE) + { + if (fprintf(fp, " return balance(") < 0) return qfalse; + if (!WriteFloat(fp, fs->weight)) return qfalse; + if (fprintf(fp, ",") < 0) return qfalse; + if (!WriteFloat(fp, fs->minweight)) return qfalse; + if (fprintf(fp, ",") < 0) return qfalse; + if (!WriteFloat(fp, fs->maxweight)) return qfalse; + if (fprintf(fp, ");\n") < 0) return qfalse; + } //end if + else + { + if (fprintf(fp, " return ") < 0) return qfalse; + if (!WriteFloat(fp, fs->weight)) return qfalse; + if (fprintf(fp, ";\n") < 0) return qfalse; + } //end else + return qtrue; +} //end of the function WriteFuzzyWeight +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean WriteFuzzySeperators_r(FILE *fp, fuzzyseperator_t *fs, int indent) +{ + if (!WriteIndent(fp, indent)) return qfalse; + if (fprintf(fp, "switch(%d)\n", fs->index) < 0) return qfalse; + if (!WriteIndent(fp, indent)) return qfalse; + if (fprintf(fp, "{\n") < 0) return qfalse; + indent++; + do + { + if (!WriteIndent(fp, indent)) return qfalse; + if (fs->next) + { + if (fprintf(fp, "case %d:", fs->value) < 0) return qfalse; + } //end if + else + { + if (fprintf(fp, "default:") < 0) return qfalse; + } //end else + if (fs->child) + { + if (fprintf(fp, "\n") < 0) return qfalse; + if (!WriteIndent(fp, indent)) return qfalse; + if (fprintf(fp, "{\n") < 0) return qfalse; + if (!WriteFuzzySeperators_r(fp, fs->child, indent + 1)) return qfalse; + if (!WriteIndent(fp, indent)) return qfalse; + if (fs->next) + { + if (fprintf(fp, "} //end case\n") < 0) return qfalse; + } //end if + else + { + if (fprintf(fp, "} //end default\n") < 0) return qfalse; + } //end else + } //end if + else + { + if (!WriteFuzzyWeight(fp, fs)) return qfalse; + } //end else + fs = fs->next; + } while(fs); + indent--; + if (!WriteIndent(fp, indent)) return qfalse; + if (fprintf(fp, "} //end switch\n") < 0) return qfalse; + return qtrue; +} //end of the function WriteItemFuzzyWeights_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean WriteWeightConfig(char *filename, weightconfig_t *config) +{ + int i; + FILE *fp; + weight_t *ifw; + + fp = fopen(filename, "wb"); + if (!fp) return qfalse; + + for (i = 0; i < config->numweights; i++) + { + ifw = &config->weights[i]; + if (fprintf(fp, "\nweight \"%s\"\n", ifw->name) < 0) return qfalse; + if (fprintf(fp, "{\n") < 0) return qfalse; + if (ifw->firstseperator->index > 0) + { + if (!WriteFuzzySeperators_r(fp, ifw->firstseperator, 1)) return qfalse; + } //end if + else + { + if (!WriteIndent(fp, 1)) return qfalse; + if (!WriteFuzzyWeight(fp, ifw->firstseperator)) return qfalse; + } //end else + if (fprintf(fp, "} //end weight\n") < 0) return qfalse; + } //end for + fclose(fp); + return qtrue; +} //end of the function WriteWeightConfig +#endif +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int FindFuzzyWeight(weightconfig_t *wc, char *name) +{ + int i; + + for (i = 0; i < wc->numweights; i++) + { + if (!strcmp(wc->weights[i].name, name)) + { + return i; + } //end if + } //end if + return -1; +} //end of the function FindFuzzyWeight +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float FuzzyWeight_r(int *inventory, fuzzyseperator_t *fs) +{ + float scale, w1, w2; + + if (inventory[fs->index] < fs->value) + { + if (fs->child) return FuzzyWeight_r(inventory, fs->child); + else return fs->weight; + } //end if + else if (fs->next) + { + if (inventory[fs->index] < fs->next->value) + { + //first weight + if (fs->child) w1 = FuzzyWeight_r(inventory, fs->child); + else w1 = fs->weight; + //second weight + if (fs->next->child) w2 = FuzzyWeight_r(inventory, fs->next->child); + else w2 = fs->next->weight; + //the scale factor + scale = (inventory[fs->index] - fs->value) / (fs->next->value - fs->value); + //scale between the two weights + return scale * w1 + (1 - scale) * w2; + } //end if + return FuzzyWeight_r(inventory, fs->next); + } //end else if + return fs->weight; +} //end of the function FuzzyWeight_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float FuzzyWeightUndecided_r(int *inventory, fuzzyseperator_t *fs) +{ + float scale, w1, w2; + + if (inventory[fs->index] < fs->value) + { + if (fs->child) return FuzzyWeightUndecided_r(inventory, fs->child); + else return fs->minweight + random() * (fs->maxweight - fs->minweight); + } //end if + else if (fs->next) + { + if (inventory[fs->index] < fs->next->value) + { + //first weight + if (fs->child) w1 = FuzzyWeightUndecided_r(inventory, fs->child); + else w1 = fs->minweight + random() * (fs->maxweight - fs->minweight); + //second weight + if (fs->next->child) w2 = FuzzyWeight_r(inventory, fs->next->child); + else w2 = fs->next->minweight + random() * (fs->next->maxweight - fs->next->minweight); + //the scale factor + scale = (inventory[fs->index] - fs->value) / (fs->next->value - fs->value); + //scale between the two weights + return scale * w1 + (1 - scale) * w2; + } //end if + return FuzzyWeightUndecided_r(inventory, fs->next); + } //end else if + return fs->weight; +} //end of the function FuzzyWeightUndecided_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float FuzzyWeight(int *inventory, weightconfig_t *wc, int weightnum) +{ +#ifdef EVALUATERECURSIVELY + return FuzzyWeight_r(inventory, wc->weights[weightnum].firstseperator); +#else + fuzzyseperator_t *s; + + s = wc->weights[weightnum].firstseperator; + if (!s) return 0; + while(1) + { + if (inventory[s->index] < s->value) + { + if (s->child) s = s->child; + else return s->weight; + } //end if + else + { + if (s->next) s = s->next; + else return s->weight; + } //end else + } //end if + return 0; +#endif +} //end of the function FuzzyWeight +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float FuzzyWeightUndecided(int *inventory, weightconfig_t *wc, int weightnum) +{ +#ifdef EVALUATERECURSIVELY + return FuzzyWeightUndecided_r(inventory, wc->weights[weightnum].firstseperator); +#else + fuzzyseperator_t *s; + + s = wc->weights[weightnum].firstseperator; + if (!s) return 0; + while(1) + { + if (inventory[s->index] < s->value) + { + if (s->child) s = s->child; + else return s->minweight + random() * (s->maxweight - s->minweight); + } //end if + else + { + if (s->next) s = s->next; + else return s->minweight + random() * (s->maxweight - s->minweight); + } //end else + } //end if + return 0; +#endif +} //end of the function FuzzyWeightUndecided +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EvolveFuzzySeperator_r(fuzzyseperator_t *fs) +{ + if (fs->child) + { + EvolveFuzzySeperator_r(fs->child); + } //end if + else if (fs->type == WT_BALANCE) + { + //every once in a while an evolution leap occurs, mutation + if (random() < 0.01) fs->weight += crandom() * (fs->maxweight - fs->minweight); + else fs->weight += crandom() * (fs->maxweight - fs->minweight) * 0.5; + //modify bounds if necesary because of mutation + if (fs->weight < fs->minweight) fs->minweight = fs->weight; + else if (fs->weight > fs->maxweight) fs->maxweight = fs->weight; + } //end else if + if (fs->next) EvolveFuzzySeperator_r(fs->next); +} //end of the function EvolveFuzzySeperator_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EvolveWeightConfig(weightconfig_t *config) +{ + int i; + + for (i = 0; i < config->numweights; i++) + { + EvolveFuzzySeperator_r(config->weights[i].firstseperator); + } //end for +} //end of the function EvolveWeightConfig +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ScaleFuzzySeperator_r(fuzzyseperator_t *fs, float scale) +{ + if (fs->child) + { + ScaleFuzzySeperator_r(fs->child, scale); + } //end if + else if (fs->type == WT_BALANCE) + { + // + fs->weight = (fs->maxweight + fs->minweight) * scale; + //get the weight between bounds + if (fs->weight < fs->minweight) fs->weight = fs->minweight; + else if (fs->weight > fs->maxweight) fs->weight = fs->maxweight; + } //end else if + if (fs->next) ScaleFuzzySeperator_r(fs->next, scale); +} //end of the function ScaleFuzzySeperator_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ScaleWeight(weightconfig_t *config, char *name, float scale) +{ + int i; + + if (scale < 0) scale = 0; + else if (scale > 1) scale = 1; + for (i = 0; i < config->numweights; i++) + { + if (!strcmp(name, config->weights[i].name)) + { + ScaleFuzzySeperator_r(config->weights[i].firstseperator, scale); + break; + } //end if + } //end for +} //end of the function ScaleWeight +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ScaleFuzzySeperatorBalanceRange_r(fuzzyseperator_t *fs, float scale) +{ + if (fs->child) + { + ScaleFuzzySeperatorBalanceRange_r(fs->child, scale); + } //end if + else if (fs->type == WT_BALANCE) + { + float mid = (fs->minweight + fs->maxweight) * 0.5; + //get the weight between bounds + fs->maxweight = mid + (fs->maxweight - mid) * scale; + fs->minweight = mid + (fs->minweight - mid) * scale; + if (fs->maxweight < fs->minweight) + { + fs->maxweight = fs->minweight; + } //end if + } //end else if + if (fs->next) ScaleFuzzySeperatorBalanceRange_r(fs->next, scale); +} //end of the function ScaleFuzzySeperatorBalanceRange_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ScaleFuzzyBalanceRange(weightconfig_t *config, float scale) +{ + int i; + + if (scale < 0) scale = 0; + else if (scale > 100) scale = 100; + for (i = 0; i < config->numweights; i++) + { + ScaleFuzzySeperatorBalanceRange_r(config->weights[i].firstseperator, scale); + } //end for +} //end of the function ScaleFuzzyBalanceRange +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int InterbreedFuzzySeperator_r(fuzzyseperator_t *fs1, fuzzyseperator_t *fs2, + fuzzyseperator_t *fsout) +{ + if (fs1->child) + { + if (!fs2->child || !fsout->child) + { + botimport.Print(PRT_ERROR, "cannot interbreed weight configs, unequal child\n"); + return qfalse; + } //end if + if (!InterbreedFuzzySeperator_r(fs2->child, fs2->child, fsout->child)) + { + return qfalse; + } //end if + } //end if + else if (fs1->type == WT_BALANCE) + { + if (fs2->type != WT_BALANCE || fsout->type != WT_BALANCE) + { + botimport.Print(PRT_ERROR, "cannot interbreed weight configs, unequal balance\n"); + return qfalse; + } //end if + fsout->weight = (fs1->weight + fs2->weight) / 2; + if (fsout->weight > fsout->maxweight) fsout->maxweight = fsout->weight; + if (fsout->weight > fsout->minweight) fsout->minweight = fsout->weight; + } //end else if + if (fs1->next) + { + if (!fs2->next || !fsout->next) + { + botimport.Print(PRT_ERROR, "cannot interbreed weight configs, unequal next\n"); + return qfalse; + } //end if + if (!InterbreedFuzzySeperator_r(fs1->next, fs2->next, fsout->next)) + { + return qfalse; + } //end if + } //end if + return qtrue; +} //end of the function InterbreedFuzzySeperator_r +//=========================================================================== +// config1 and config2 are interbreeded and stored in configout +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void InterbreedWeightConfigs(weightconfig_t *config1, weightconfig_t *config2, + weightconfig_t *configout) +{ + int i; + + if (config1->numweights != config2->numweights || + config1->numweights != configout->numweights) + { + botimport.Print(PRT_ERROR, "cannot interbreed weight configs, unequal numweights\n"); + return; + } //end if + for (i = 0; i < config1->numweights; i++) + { + InterbreedFuzzySeperator_r(config1->weights[i].firstseperator, + config2->weights[i].firstseperator, + configout->weights[i].firstseperator); + } //end for +} //end of the function InterbreedWeightConfigs +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotShutdownWeights(void) +{ + int i; + + for( i = 0; i < MAX_WEIGHT_FILES; i++ ) + { + if (weightFileList[i]) + { + FreeWeightConfig2(weightFileList[i]); + weightFileList[i] = NULL; + } //end if + } //end for +} //end of the function BotShutdownWeights diff --git a/code/botlib/be_ai_weight.h b/code/botlib/be_ai_weight.h index 4fc2f6b..ebbf92f 100755 --- a/code/botlib/be_ai_weight.h +++ b/code/botlib/be_ai_weight.h @@ -1,83 +1,83 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: be_ai_weight.h - * - * desc: fuzzy weights - * - * $Archive: /source/code/botlib/be_ai_weight.h $ - * - *****************************************************************************/ - -#define WT_BALANCE 1 -#define MAX_WEIGHTS 128 - -//fuzzy seperator -typedef struct fuzzyseperator_s -{ - int index; - int value; - int type; - float weight; - float minweight; - float maxweight; - struct fuzzyseperator_s *child; - struct fuzzyseperator_s *next; -} fuzzyseperator_t; - -//fuzzy weight -typedef struct weight_s -{ - char *name; - struct fuzzyseperator_s *firstseperator; -} weight_t; - -//weight configuration -typedef struct weightconfig_s -{ - int numweights; - weight_t weights[MAX_WEIGHTS]; - char filename[MAX_QPATH]; -} weightconfig_t; - -//reads a weight configuration -weightconfig_t *ReadWeightConfig(char *filename); -//free a weight configuration -void FreeWeightConfig(weightconfig_t *config); -//writes a weight configuration, returns true if successfull -qboolean WriteWeightConfig(char *filename, weightconfig_t *config); -//find the fuzzy weight with the given name -int FindFuzzyWeight(weightconfig_t *wc, char *name); -//returns the fuzzy weight for the given inventory and weight -float FuzzyWeight(int *inventory, weightconfig_t *wc, int weightnum); -float FuzzyWeightUndecided(int *inventory, weightconfig_t *wc, int weightnum); -//scales the weight with the given name -void ScaleWeight(weightconfig_t *config, char *name, float scale); -//scale the balance range -void ScaleBalanceRange(weightconfig_t *config, float scale); -//evolves the weight configuration -void EvolveWeightConfig(weightconfig_t *config); -//interbreed the weight configurations and stores the interbreeded one in configout -void InterbreedWeightConfigs(weightconfig_t *config1, weightconfig_t *config2, weightconfig_t *configout); -//frees cached weight configurations -void BotShutdownWeights(void); +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_ai_weight.h + * + * desc: fuzzy weights + * + * $Archive: /source/code/botlib/be_ai_weight.h $ + * + *****************************************************************************/ + +#define WT_BALANCE 1 +#define MAX_WEIGHTS 128 + +//fuzzy seperator +typedef struct fuzzyseperator_s +{ + int index; + int value; + int type; + float weight; + float minweight; + float maxweight; + struct fuzzyseperator_s *child; + struct fuzzyseperator_s *next; +} fuzzyseperator_t; + +//fuzzy weight +typedef struct weight_s +{ + char *name; + struct fuzzyseperator_s *firstseperator; +} weight_t; + +//weight configuration +typedef struct weightconfig_s +{ + int numweights; + weight_t weights[MAX_WEIGHTS]; + char filename[MAX_QPATH]; +} weightconfig_t; + +//reads a weight configuration +weightconfig_t *ReadWeightConfig(char *filename); +//free a weight configuration +void FreeWeightConfig(weightconfig_t *config); +//writes a weight configuration, returns true if successfull +qboolean WriteWeightConfig(char *filename, weightconfig_t *config); +//find the fuzzy weight with the given name +int FindFuzzyWeight(weightconfig_t *wc, char *name); +//returns the fuzzy weight for the given inventory and weight +float FuzzyWeight(int *inventory, weightconfig_t *wc, int weightnum); +float FuzzyWeightUndecided(int *inventory, weightconfig_t *wc, int weightnum); +//scales the weight with the given name +void ScaleWeight(weightconfig_t *config, char *name, float scale); +//scale the balance range +void ScaleBalanceRange(weightconfig_t *config, float scale); +//evolves the weight configuration +void EvolveWeightConfig(weightconfig_t *config); +//interbreed the weight configurations and stores the interbreeded one in configout +void InterbreedWeightConfigs(weightconfig_t *config1, weightconfig_t *config2, weightconfig_t *configout); +//frees cached weight configurations +void BotShutdownWeights(void); diff --git a/code/botlib/be_ea.c b/code/botlib/be_ea.c index 2c57099..5c0dd6c 100755 --- a/code/botlib/be_ea.c +++ b/code/botlib/be_ea.c @@ -1,508 +1,508 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: be_ea.c - * - * desc: elementary actions - * - * $Archive: /MissionPack/code/botlib/be_ea.c $ - * - *****************************************************************************/ - -#include "../game/q_shared.h" -#include "l_memory.h" -#include "l_script.h" -#include "l_precomp.h" -#include "l_struct.h" -#include "../game/botlib.h" -#include "be_interface.h" - -#define MAX_USERMOVE 400 -#define MAX_COMMANDARGUMENTS 10 -#define ACTION_JUMPEDLASTFRAME 128 - -bot_input_t *botinputs; - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void EA_Say(int client, char *str) -{ - botimport.BotClientCommand(client, va("say %s", str) ); -} //end of the function EA_Say -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void EA_SayTeam(int client, char *str) -{ - botimport.BotClientCommand(client, va("say_team %s", str)); -} //end of the function EA_SayTeam -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void EA_Tell(int client, int clientto, char *str) -{ - botimport.BotClientCommand(client, va("tell %d, %s", clientto, str)); -} //end of the function EA_SayTeam -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void EA_UseItem(int client, char *it) -{ - botimport.BotClientCommand(client, va("use %s", it)); -} //end of the function EA_UseItem -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void EA_DropItem(int client, char *it) -{ - botimport.BotClientCommand(client, va("drop %s", it)); -} //end of the function EA_DropItem -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void EA_UseInv(int client, char *inv) -{ - botimport.BotClientCommand(client, va("invuse %s", inv)); -} //end of the function EA_UseInv -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void EA_DropInv(int client, char *inv) -{ - botimport.BotClientCommand(client, va("invdrop %s", inv)); -} //end of the function EA_DropInv -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void EA_Gesture(int client) -{ - bot_input_t *bi; - - bi = &botinputs[client]; - - bi->actionflags |= ACTION_GESTURE; -} //end of the function EA_Gesture -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void EA_Command(int client, char *command) -{ - botimport.BotClientCommand(client, command); -} //end of the function EA_Command -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void EA_SelectWeapon(int client, int weapon) -{ - bot_input_t *bi; - - bi = &botinputs[client]; - - bi->weapon = weapon; -} //end of the function EA_SelectWeapon -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void EA_Attack(int client) -{ - bot_input_t *bi; - - bi = &botinputs[client]; - - bi->actionflags |= ACTION_ATTACK; -} //end of the function EA_Attack -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void EA_Talk(int client) -{ - bot_input_t *bi; - - bi = &botinputs[client]; - - bi->actionflags |= ACTION_TALK; -} //end of the function EA_Talk -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void EA_Use(int client) -{ - bot_input_t *bi; - - bi = &botinputs[client]; - - bi->actionflags |= ACTION_USE; -} //end of the function EA_Use -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void EA_Respawn(int client) -{ - bot_input_t *bi; - - bi = &botinputs[client]; - - bi->actionflags |= ACTION_RESPAWN; -} //end of the function EA_Respawn -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void EA_Jump(int client) -{ - bot_input_t *bi; - - bi = &botinputs[client]; - - if (bi->actionflags & ACTION_JUMPEDLASTFRAME) - { - bi->actionflags &= ~ACTION_JUMP; - } //end if - else - { - bi->actionflags |= ACTION_JUMP; - } //end if -} //end of the function EA_Jump -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void EA_DelayedJump(int client) -{ - bot_input_t *bi; - - bi = &botinputs[client]; - - if (bi->actionflags & ACTION_JUMPEDLASTFRAME) - { - bi->actionflags &= ~ACTION_DELAYEDJUMP; - } //end if - else - { - bi->actionflags |= ACTION_DELAYEDJUMP; - } //end if -} //end of the function EA_DelayedJump -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void EA_Crouch(int client) -{ - bot_input_t *bi; - - bi = &botinputs[client]; - - bi->actionflags |= ACTION_CROUCH; -} //end of the function EA_Crouch -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void EA_Walk(int client) -{ - bot_input_t *bi; - - bi = &botinputs[client]; - - bi->actionflags |= ACTION_WALK; -} //end of the function EA_Walk -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void EA_Action(int client, int action) -{ - bot_input_t *bi; - - bi = &botinputs[client]; - - bi->actionflags |= action; -} //end of function EA_Action -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void EA_MoveUp(int client) -{ - bot_input_t *bi; - - bi = &botinputs[client]; - - bi->actionflags |= ACTION_MOVEUP; -} //end of the function EA_MoveUp -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void EA_MoveDown(int client) -{ - bot_input_t *bi; - - bi = &botinputs[client]; - - bi->actionflags |= ACTION_MOVEDOWN; -} //end of the function EA_MoveDown -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void EA_MoveForward(int client) -{ - bot_input_t *bi; - - bi = &botinputs[client]; - - bi->actionflags |= ACTION_MOVEFORWARD; -} //end of the function EA_MoveForward -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void EA_MoveBack(int client) -{ - bot_input_t *bi; - - bi = &botinputs[client]; - - bi->actionflags |= ACTION_MOVEBACK; -} //end of the function EA_MoveBack -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void EA_MoveLeft(int client) -{ - bot_input_t *bi; - - bi = &botinputs[client]; - - bi->actionflags |= ACTION_MOVELEFT; -} //end of the function EA_MoveLeft -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void EA_MoveRight(int client) -{ - bot_input_t *bi; - - bi = &botinputs[client]; - - bi->actionflags |= ACTION_MOVERIGHT; -} //end of the function EA_MoveRight -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void EA_Move(int client, vec3_t dir, float speed) -{ - bot_input_t *bi; - - bi = &botinputs[client]; - - VectorCopy(dir, bi->dir); - //cap speed - if (speed > MAX_USERMOVE) speed = MAX_USERMOVE; - else if (speed < -MAX_USERMOVE) speed = -MAX_USERMOVE; - bi->speed = speed; -} //end of the function EA_Move -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void EA_View(int client, vec3_t viewangles) -{ - bot_input_t *bi; - - bi = &botinputs[client]; - - VectorCopy(viewangles, bi->viewangles); -} //end of the function EA_View -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void EA_EndRegular(int client, float thinktime) -{ -/* - bot_input_t *bi; - int jumped = qfalse; - - bi = &botinputs[client]; - - bi->actionflags &= ~ACTION_JUMPEDLASTFRAME; - - bi->thinktime = thinktime; - botimport.BotInput(client, bi); - - bi->thinktime = 0; - VectorClear(bi->dir); - bi->speed = 0; - jumped = bi->actionflags & ACTION_JUMP; - bi->actionflags = 0; - if (jumped) bi->actionflags |= ACTION_JUMPEDLASTFRAME; -*/ -} //end of the function EA_EndRegular -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void EA_GetInput(int client, float thinktime, bot_input_t *input) -{ - bot_input_t *bi; -// int jumped = qfalse; - - bi = &botinputs[client]; - -// bi->actionflags &= ~ACTION_JUMPEDLASTFRAME; - - bi->thinktime = thinktime; - Com_Memcpy(input, bi, sizeof(bot_input_t)); - - /* - bi->thinktime = 0; - VectorClear(bi->dir); - bi->speed = 0; - jumped = bi->actionflags & ACTION_JUMP; - bi->actionflags = 0; - if (jumped) bi->actionflags |= ACTION_JUMPEDLASTFRAME; - */ -} //end of the function EA_GetInput -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void EA_ResetInput(int client) -{ - bot_input_t *bi; - int jumped = qfalse; - - bi = &botinputs[client]; - bi->actionflags &= ~ACTION_JUMPEDLASTFRAME; - - bi->thinktime = 0; - VectorClear(bi->dir); - bi->speed = 0; - jumped = bi->actionflags & ACTION_JUMP; - bi->actionflags = 0; - if (jumped) bi->actionflags |= ACTION_JUMPEDLASTFRAME; -} //end of the function EA_ResetInput -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int EA_Setup(void) -{ - //initialize the bot inputs - botinputs = (bot_input_t *) GetClearedHunkMemory( - botlibglobals.maxclients * sizeof(bot_input_t)); - return BLERR_NOERROR; -} //end of the function EA_Setup -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void EA_Shutdown(void) -{ - FreeMemory(botinputs); - botinputs = NULL; -} //end of the function EA_Shutdown +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_ea.c + * + * desc: elementary actions + * + * $Archive: /MissionPack/code/botlib/be_ea.c $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "../game/botlib.h" +#include "be_interface.h" + +#define MAX_USERMOVE 400 +#define MAX_COMMANDARGUMENTS 10 +#define ACTION_JUMPEDLASTFRAME 128 + +bot_input_t *botinputs; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Say(int client, char *str) +{ + botimport.BotClientCommand(client, va("say %s", str) ); +} //end of the function EA_Say +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_SayTeam(int client, char *str) +{ + botimport.BotClientCommand(client, va("say_team %s", str)); +} //end of the function EA_SayTeam +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Tell(int client, int clientto, char *str) +{ + botimport.BotClientCommand(client, va("tell %d, %s", clientto, str)); +} //end of the function EA_SayTeam +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_UseItem(int client, char *it) +{ + botimport.BotClientCommand(client, va("use %s", it)); +} //end of the function EA_UseItem +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_DropItem(int client, char *it) +{ + botimport.BotClientCommand(client, va("drop %s", it)); +} //end of the function EA_DropItem +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_UseInv(int client, char *inv) +{ + botimport.BotClientCommand(client, va("invuse %s", inv)); +} //end of the function EA_UseInv +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_DropInv(int client, char *inv) +{ + botimport.BotClientCommand(client, va("invdrop %s", inv)); +} //end of the function EA_DropInv +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Gesture(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_GESTURE; +} //end of the function EA_Gesture +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Command(int client, char *command) +{ + botimport.BotClientCommand(client, command); +} //end of the function EA_Command +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_SelectWeapon(int client, int weapon) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->weapon = weapon; +} //end of the function EA_SelectWeapon +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Attack(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_ATTACK; +} //end of the function EA_Attack +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Talk(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_TALK; +} //end of the function EA_Talk +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Use(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_USE; +} //end of the function EA_Use +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Respawn(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_RESPAWN; +} //end of the function EA_Respawn +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Jump(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + if (bi->actionflags & ACTION_JUMPEDLASTFRAME) + { + bi->actionflags &= ~ACTION_JUMP; + } //end if + else + { + bi->actionflags |= ACTION_JUMP; + } //end if +} //end of the function EA_Jump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_DelayedJump(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + if (bi->actionflags & ACTION_JUMPEDLASTFRAME) + { + bi->actionflags &= ~ACTION_DELAYEDJUMP; + } //end if + else + { + bi->actionflags |= ACTION_DELAYEDJUMP; + } //end if +} //end of the function EA_DelayedJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Crouch(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_CROUCH; +} //end of the function EA_Crouch +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Walk(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_WALK; +} //end of the function EA_Walk +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Action(int client, int action) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= action; +} //end of function EA_Action +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_MoveUp(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_MOVEUP; +} //end of the function EA_MoveUp +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_MoveDown(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_MOVEDOWN; +} //end of the function EA_MoveDown +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_MoveForward(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_MOVEFORWARD; +} //end of the function EA_MoveForward +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_MoveBack(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_MOVEBACK; +} //end of the function EA_MoveBack +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_MoveLeft(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_MOVELEFT; +} //end of the function EA_MoveLeft +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_MoveRight(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_MOVERIGHT; +} //end of the function EA_MoveRight +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Move(int client, vec3_t dir, float speed) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + VectorCopy(dir, bi->dir); + //cap speed + if (speed > MAX_USERMOVE) speed = MAX_USERMOVE; + else if (speed < -MAX_USERMOVE) speed = -MAX_USERMOVE; + bi->speed = speed; +} //end of the function EA_Move +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_View(int client, vec3_t viewangles) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + VectorCopy(viewangles, bi->viewangles); +} //end of the function EA_View +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_EndRegular(int client, float thinktime) +{ +/* + bot_input_t *bi; + int jumped = qfalse; + + bi = &botinputs[client]; + + bi->actionflags &= ~ACTION_JUMPEDLASTFRAME; + + bi->thinktime = thinktime; + botimport.BotInput(client, bi); + + bi->thinktime = 0; + VectorClear(bi->dir); + bi->speed = 0; + jumped = bi->actionflags & ACTION_JUMP; + bi->actionflags = 0; + if (jumped) bi->actionflags |= ACTION_JUMPEDLASTFRAME; +*/ +} //end of the function EA_EndRegular +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_GetInput(int client, float thinktime, bot_input_t *input) +{ + bot_input_t *bi; +// int jumped = qfalse; + + bi = &botinputs[client]; + +// bi->actionflags &= ~ACTION_JUMPEDLASTFRAME; + + bi->thinktime = thinktime; + Com_Memcpy(input, bi, sizeof(bot_input_t)); + + /* + bi->thinktime = 0; + VectorClear(bi->dir); + bi->speed = 0; + jumped = bi->actionflags & ACTION_JUMP; + bi->actionflags = 0; + if (jumped) bi->actionflags |= ACTION_JUMPEDLASTFRAME; + */ +} //end of the function EA_GetInput +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_ResetInput(int client) +{ + bot_input_t *bi; + int jumped = qfalse; + + bi = &botinputs[client]; + bi->actionflags &= ~ACTION_JUMPEDLASTFRAME; + + bi->thinktime = 0; + VectorClear(bi->dir); + bi->speed = 0; + jumped = bi->actionflags & ACTION_JUMP; + bi->actionflags = 0; + if (jumped) bi->actionflags |= ACTION_JUMPEDLASTFRAME; +} //end of the function EA_ResetInput +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int EA_Setup(void) +{ + //initialize the bot inputs + botinputs = (bot_input_t *) GetClearedHunkMemory( + botlibglobals.maxclients * sizeof(bot_input_t)); + return BLERR_NOERROR; +} //end of the function EA_Setup +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Shutdown(void) +{ + FreeMemory(botinputs); + botinputs = NULL; +} //end of the function EA_Shutdown diff --git a/code/botlib/be_interface.c b/code/botlib/be_interface.c index b5a022b..b4f2d3c 100755 --- a/code/botlib/be_interface.c +++ b/code/botlib/be_interface.c @@ -1,881 +1,881 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: be_interface.c // bk010221 - FIXME - DEAD code elimination - * - * desc: bot library interface - * - * $Archive: /MissionPack/code/botlib/be_interface.c $ - * - *****************************************************************************/ - -#include "../game/q_shared.h" -#include "l_memory.h" -#include "l_log.h" -#include "l_libvar.h" -#include "l_script.h" -#include "l_precomp.h" -#include "l_struct.h" -#include "aasfile.h" -#include "../game/botlib.h" -#include "../game/be_aas.h" -#include "be_aas_funcs.h" -#include "be_aas_def.h" -#include "be_interface.h" - -#include "../game/be_ea.h" -#include "be_ai_weight.h" -#include "../game/be_ai_goal.h" -#include "../game/be_ai_move.h" -#include "../game/be_ai_weap.h" -#include "../game/be_ai_chat.h" -#include "../game/be_ai_char.h" -#include "../game/be_ai_gen.h" - -//library globals in a structure -botlib_globals_t botlibglobals; - -botlib_export_t be_botlib_export; -botlib_import_t botimport; -// -int bot_developer; -//qtrue if the library is setup -int botlibsetup = qfalse; - -//=========================================================================== -// -// several functions used by the exported functions -// -//=========================================================================== - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int Sys_MilliSeconds(void) -{ - return clock() * 1000 / CLOCKS_PER_SEC; -} //end of the function Sys_MilliSeconds -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean ValidClientNumber(int num, char *str) -{ - if (num < 0 || num > botlibglobals.maxclients) - { - //weird: the disabled stuff results in a crash - botimport.Print(PRT_ERROR, "%s: invalid client number %d, [0, %d]\n", - str, num, botlibglobals.maxclients); - return qfalse; - } //end if - return qtrue; -} //end of the function BotValidateClientNumber -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean ValidEntityNumber(int num, char *str) -{ - if (num < 0 || num > botlibglobals.maxentities) - { - botimport.Print(PRT_ERROR, "%s: invalid entity number %d, [0, %d]\n", - str, num, botlibglobals.maxentities); - return qfalse; - } //end if - return qtrue; -} //end of the function BotValidateClientNumber -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean BotLibSetup(char *str) -{ - if (!botlibglobals.botlibsetup) - { - botimport.Print(PRT_ERROR, "%s: bot library used before being setup\n", str); - return qfalse; - } //end if - return qtrue; -} //end of the function BotLibSetup - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int Export_BotLibSetup(void) -{ - int errnum; - - bot_developer = LibVarGetValue("bot_developer"); - memset( &botlibglobals, 0, sizeof(botlibglobals) ); // bk001207 - init - //initialize byte swapping (litte endian etc.) -// Swap_Init(); - Log_Open("botlib.log"); - // - botimport.Print(PRT_MESSAGE, "------- BotLib Initialization -------\n"); - // - botlibglobals.maxclients = (int) LibVarValue("maxclients", "128"); - botlibglobals.maxentities = (int) LibVarValue("maxentities", "1024"); - - errnum = AAS_Setup(); //be_aas_main.c - if (errnum != BLERR_NOERROR) return errnum; - errnum = EA_Setup(); //be_ea.c - if (errnum != BLERR_NOERROR) return errnum; - errnum = BotSetupWeaponAI(); //be_ai_weap.c - if (errnum != BLERR_NOERROR)return errnum; - errnum = BotSetupGoalAI(); //be_ai_goal.c - if (errnum != BLERR_NOERROR) return errnum; - errnum = BotSetupChatAI(); //be_ai_chat.c - if (errnum != BLERR_NOERROR) return errnum; - errnum = BotSetupMoveAI(); //be_ai_move.c - if (errnum != BLERR_NOERROR) return errnum; - - botlibsetup = qtrue; - botlibglobals.botlibsetup = qtrue; - - return BLERR_NOERROR; -} //end of the function Export_BotLibSetup -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int Export_BotLibShutdown(void) -{ - if (!BotLibSetup("BotLibShutdown")) return BLERR_LIBRARYNOTSETUP; -#ifndef DEMO - //DumpFileCRCs(); -#endif //DEMO - // - BotShutdownChatAI(); //be_ai_chat.c - BotShutdownMoveAI(); //be_ai_move.c - BotShutdownGoalAI(); //be_ai_goal.c - BotShutdownWeaponAI(); //be_ai_weap.c - BotShutdownWeights(); //be_ai_weight.c - BotShutdownCharacters(); //be_ai_char.c - //shud down aas - AAS_Shutdown(); - //shut down bot elemantary actions - EA_Shutdown(); - //free all libvars - LibVarDeAllocAll(); - //remove all global defines from the pre compiler - PC_RemoveAllGlobalDefines(); - - //dump all allocated memory -// DumpMemory(); -#ifdef DEBUG - PrintMemoryLabels(); -#endif - //shut down library log file - Log_Shutdown(); - // - botlibsetup = qfalse; - botlibglobals.botlibsetup = qfalse; - // print any files still open - PC_CheckOpenSourceHandles(); - // - return BLERR_NOERROR; -} //end of the function Export_BotLibShutdown -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int Export_BotLibVarSet(char *var_name, char *value) -{ - LibVarSet(var_name, value); - return BLERR_NOERROR; -} //end of the function Export_BotLibVarSet -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int Export_BotLibVarGet(char *var_name, char *value, int size) -{ - char *varvalue; - - varvalue = LibVarGetString(var_name); - strncpy(value, varvalue, size-1); - value[size-1] = '\0'; - return BLERR_NOERROR; -} //end of the function Export_BotLibVarGet -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int Export_BotLibStartFrame(float time) -{ - if (!BotLibSetup("BotStartFrame")) return BLERR_LIBRARYNOTSETUP; - return AAS_StartFrame(time); -} //end of the function Export_BotLibStartFrame -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int Export_BotLibLoadMap(const char *mapname) -{ -#ifdef DEBUG - int starttime = Sys_MilliSeconds(); -#endif - int errnum; - - if (!BotLibSetup("BotLoadMap")) return BLERR_LIBRARYNOTSETUP; - // - botimport.Print(PRT_MESSAGE, "------------ Map Loading ------------\n"); - //startup AAS for the current map, model and sound index - errnum = AAS_LoadMap(mapname); - if (errnum != BLERR_NOERROR) return errnum; - //initialize the items in the level - BotInitLevelItems(); //be_ai_goal.h - BotSetBrushModelTypes(); //be_ai_move.h - // - botimport.Print(PRT_MESSAGE, "-------------------------------------\n"); -#ifdef DEBUG - botimport.Print(PRT_MESSAGE, "map loaded in %d msec\n", Sys_MilliSeconds() - starttime); -#endif - // - return BLERR_NOERROR; -} //end of the function Export_BotLibLoadMap -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int Export_BotLibUpdateEntity(int ent, bot_entitystate_t *state) -{ - if (!BotLibSetup("BotUpdateEntity")) return BLERR_LIBRARYNOTSETUP; - if (!ValidEntityNumber(ent, "BotUpdateEntity")) return BLERR_INVALIDENTITYNUMBER; - - return AAS_UpdateEntity(ent, state); -} //end of the function Export_BotLibUpdateEntity -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_TestMovementPrediction(int entnum, vec3_t origin, vec3_t dir); -void ElevatorBottomCenter(aas_reachability_t *reach, vec3_t bottomcenter); -int BotGetReachabilityToGoal(vec3_t origin, int areanum, - int lastgoalareanum, int lastareanum, - int *avoidreach, float *avoidreachtimes, int *avoidreachtries, - bot_goal_t *goal, int travelflags, int movetravelflags, - struct bot_avoidspot_s *avoidspots, int numavoidspots, int *flags); - -int AAS_PointLight(vec3_t origin, int *red, int *green, int *blue); - -int AAS_TraceAreas(vec3_t start, vec3_t end, int *areas, vec3_t *points, int maxareas); - -int AAS_Reachability_WeaponJump(int area1num, int area2num); - -int BotFuzzyPointReachabilityArea(vec3_t origin); - -float BotGapDistance(vec3_t origin, vec3_t hordir, int entnum); - -void AAS_FloodAreas(vec3_t origin); - -int BotExportTest(int parm0, char *parm1, vec3_t parm2, vec3_t parm3) -{ - -// return AAS_PointLight(parm2, NULL, NULL, NULL); - -#ifdef DEBUG - static int area = -1; - static int line[2]; - int newarea, i, highlightarea, flood; -// int reachnum; - vec3_t eye, forward, right, end, origin; -// vec3_t bottomcenter; -// aas_trace_t trace; -// aas_face_t *face; -// aas_entity_t *ent; -// bsp_trace_t bsptrace; -// aas_reachability_t reach; -// bot_goal_t goal; - - // clock_t start_time, end_time; - vec3_t mins = {-16, -16, -24}; - vec3_t maxs = {16, 16, 32}; - -// int areas[10], numareas; - - - //return 0; - - if (!aasworld.loaded) return 0; - - /* - if (parm0 & 1) - { - AAS_ClearShownPolygons(); - AAS_FloodAreas(parm2); - } //end if - return 0; - */ - for (i = 0; i < 2; i++) if (!line[i]) line[i] = botimport.DebugLineCreate(); - -// AAS_ClearShownDebugLines(); - - //if (AAS_AgainstLadder(parm2)) botimport.Print(PRT_MESSAGE, "against ladder\n"); - //BotOnGround(parm2, PRESENCE_NORMAL, 1, &newarea, &newarea); - //botimport.Print(PRT_MESSAGE, "%f %f %f\n", parm2[0], parm2[1], parm2[2]); - //* - highlightarea = LibVarGetValue("bot_highlightarea"); - if (highlightarea > 0) - { - newarea = highlightarea; - } //end if - else - { - VectorCopy(parm2, origin); - origin[2] += 0.5; - //newarea = AAS_PointAreaNum(origin); - newarea = BotFuzzyPointReachabilityArea(origin); - } //end else - - botimport.Print(PRT_MESSAGE, "\rtravel time to goal (%d) = %d ", botlibglobals.goalareanum, - AAS_AreaTravelTimeToGoalArea(newarea, origin, botlibglobals.goalareanum, TFL_DEFAULT)); - //newarea = BotReachabilityArea(origin, qtrue); - if (newarea != area) - { - botimport.Print(PRT_MESSAGE, "origin = %f, %f, %f\n", origin[0], origin[1], origin[2]); - area = newarea; - botimport.Print(PRT_MESSAGE, "new area %d, cluster %d, presence type %d\n", - area, AAS_AreaCluster(area), AAS_PointPresenceType(origin)); - botimport.Print(PRT_MESSAGE, "area contents: "); - if (aasworld.areasettings[area].contents & AREACONTENTS_WATER) - { - botimport.Print(PRT_MESSAGE, "water &"); - } //end if - if (aasworld.areasettings[area].contents & AREACONTENTS_LAVA) - { - botimport.Print(PRT_MESSAGE, "lava &"); - } //end if - if (aasworld.areasettings[area].contents & AREACONTENTS_SLIME) - { - botimport.Print(PRT_MESSAGE, "slime &"); - } //end if - if (aasworld.areasettings[area].contents & AREACONTENTS_JUMPPAD) - { - botimport.Print(PRT_MESSAGE, "jump pad &"); - } //end if - if (aasworld.areasettings[area].contents & AREACONTENTS_CLUSTERPORTAL) - { - botimport.Print(PRT_MESSAGE, "cluster portal &"); - } //end if - if (aasworld.areasettings[area].contents & AREACONTENTS_VIEWPORTAL) - { - botimport.Print(PRT_MESSAGE, "view portal &"); - } //end if - if (aasworld.areasettings[area].contents & AREACONTENTS_DONOTENTER) - { - botimport.Print(PRT_MESSAGE, "do not enter &"); - } //end if - if (aasworld.areasettings[area].contents & AREACONTENTS_MOVER) - { - botimport.Print(PRT_MESSAGE, "mover &"); - } //end if - if (!aasworld.areasettings[area].contents) - { - botimport.Print(PRT_MESSAGE, "empty"); - } //end if - botimport.Print(PRT_MESSAGE, "\n"); - botimport.Print(PRT_MESSAGE, "travel time to goal (%d) = %d\n", botlibglobals.goalareanum, - AAS_AreaTravelTimeToGoalArea(newarea, origin, botlibglobals.goalareanum, TFL_DEFAULT|TFL_ROCKETJUMP)); - /* - VectorCopy(origin, end); - end[2] += 5; - numareas = AAS_TraceAreas(origin, end, areas, NULL, 10); - AAS_TraceClientBBox(origin, end, PRESENCE_CROUCH, -1); - botimport.Print(PRT_MESSAGE, "num areas = %d, area = %d\n", numareas, areas[0]); - */ - /* - botlibglobals.goalareanum = newarea; - VectorCopy(parm2, botlibglobals.goalorigin); - botimport.Print(PRT_MESSAGE, "new goal %2.1f %2.1f %2.1f area %d\n", - origin[0], origin[1], origin[2], newarea); - */ - } //end if - //* - flood = LibVarGetValue("bot_flood"); - if (parm0 & 1) - { - if (flood) - { - AAS_ClearShownPolygons(); - AAS_ClearShownDebugLines(); - AAS_FloodAreas(parm2); - } - else - { - botlibglobals.goalareanum = newarea; - VectorCopy(parm2, botlibglobals.goalorigin); - botimport.Print(PRT_MESSAGE, "new goal %2.1f %2.1f %2.1f area %d\n", - origin[0], origin[1], origin[2], newarea); - } - } //end if*/ - if (flood) - return 0; -// if (parm0 & BUTTON_USE) -// { -// botlibglobals.runai = !botlibglobals.runai; -// if (botlibglobals.runai) botimport.Print(PRT_MESSAGE, "started AI\n"); -// else botimport.Print(PRT_MESSAGE, "stopped AI\n"); - //* / - /* - goal.areanum = botlibglobals.goalareanum; - reachnum = BotGetReachabilityToGoal(parm2, newarea, 1, - ms.avoidreach, ms.avoidreachtimes, - &goal, TFL_DEFAULT); - if (!reachnum) - { - botimport.Print(PRT_MESSAGE, "goal not reachable\n"); - } //end if - else - { - AAS_ReachabilityFromNum(reachnum, &reach); - AAS_ClearShownDebugLines(); - AAS_ShowArea(area, qtrue); - AAS_ShowArea(reach.areanum, qtrue); - AAS_DrawCross(reach.start, 6, LINECOLOR_BLUE); - AAS_DrawCross(reach.end, 6, LINECOLOR_RED); - // - if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_ELEVATOR) - { - ElevatorBottomCenter(&reach, bottomcenter); - AAS_DrawCross(bottomcenter, 10, LINECOLOR_GREEN); - } //end if - } //end else*/ -// botimport.Print(PRT_MESSAGE, "travel time to goal = %d\n", -// AAS_AreaTravelTimeToGoalArea(area, origin, botlibglobals.goalareanum, TFL_DEFAULT)); -// botimport.Print(PRT_MESSAGE, "test rj from 703 to 716\n"); -// AAS_Reachability_WeaponJump(703, 716); -// } //end if*/ - -/* face = AAS_AreaGroundFace(newarea, parm2); - if (face) - { - AAS_ShowFace(face - aasworld.faces); - } //end if*/ - /* - AAS_ClearShownDebugLines(); - AAS_ShowArea(newarea, parm0 & BUTTON_USE); - AAS_ShowReachableAreas(area); - */ - AAS_ClearShownPolygons(); - AAS_ClearShownDebugLines(); - AAS_ShowAreaPolygons(newarea, 1, parm0 & 4); - if (parm0 & 2) AAS_ShowReachableAreas(area); - else - { - static int lastgoalareanum, lastareanum; - static int avoidreach[MAX_AVOIDREACH]; - static float avoidreachtimes[MAX_AVOIDREACH]; - static int avoidreachtries[MAX_AVOIDREACH]; - int reachnum, resultFlags; - bot_goal_t goal; - aas_reachability_t reach; - - /* - goal.areanum = botlibglobals.goalareanum; - VectorCopy(botlibglobals.goalorigin, goal.origin); - reachnum = BotGetReachabilityToGoal(origin, newarea, - lastgoalareanum, lastareanum, - avoidreach, avoidreachtimes, avoidreachtries, - &goal, TFL_DEFAULT|TFL_FUNCBOB|TFL_ROCKETJUMP, TFL_DEFAULT|TFL_FUNCBOB|TFL_ROCKETJUMP, - NULL, 0, &resultFlags); - AAS_ReachabilityFromNum(reachnum, &reach); - AAS_ShowReachability(&reach); - */ - int curarea; - vec3_t curorigin; - - goal.areanum = botlibglobals.goalareanum; - VectorCopy(botlibglobals.goalorigin, goal.origin); - VectorCopy(origin, curorigin); - curarea = newarea; - for ( i = 0; i < 100; i++ ) { - if ( curarea == goal.areanum ) { - break; - } - reachnum = BotGetReachabilityToGoal(curorigin, curarea, - lastgoalareanum, lastareanum, - avoidreach, avoidreachtimes, avoidreachtries, - &goal, TFL_DEFAULT|TFL_FUNCBOB|TFL_ROCKETJUMP, TFL_DEFAULT|TFL_FUNCBOB|TFL_ROCKETJUMP, - NULL, 0, &resultFlags); - AAS_ReachabilityFromNum(reachnum, &reach); - AAS_ShowReachability(&reach); - VectorCopy(reach.end, origin); - lastareanum = curarea; - curarea = reach.areanum; - } - } //end else - VectorClear(forward); - //BotGapDistance(origin, forward, 0); - /* - if (parm0 & BUTTON_USE) - { - botimport.Print(PRT_MESSAGE, "test rj from 703 to 716\n"); - AAS_Reachability_WeaponJump(703, 716); - } //end if*/ - - AngleVectors(parm3, forward, right, NULL); - //get the eye 16 units to the right of the origin - VectorMA(parm2, 8, right, eye); - //get the eye 24 units up - eye[2] += 24; - //get the end point for the line to be traced - VectorMA(eye, 800, forward, end); - -// AAS_TestMovementPrediction(1, parm2, forward); -/* - //trace the line to find the hit point - trace = AAS_TraceClientBBox(eye, end, PRESENCE_NORMAL, 1); - if (!line[0]) line[0] = botimport.DebugLineCreate(); - botimport.DebugLineShow(line[0], eye, trace.endpos, LINECOLOR_BLUE); - // - AAS_ClearShownDebugLines(); - if (trace.ent) - { - ent = &aasworld.entities[trace.ent]; - AAS_ShowBoundingBox(ent->origin, ent->mins, ent->maxs); - } //end if -*/ - -/* - start_time = clock(); - for (i = 0; i < 2000; i++) - { - AAS_Trace2(eye, mins, maxs, end, 1, MASK_PLAYERSOLID); -// AAS_TraceClientBBox(eye, end, PRESENCE_NORMAL, 1); - } //end for - end_time = clock(); - botimport.Print(PRT_MESSAGE, "me %lu clocks, %lu CLOCKS_PER_SEC\n", end_time - start_time, CLOCKS_PER_SEC); - start_time = clock(); - for (i = 0; i < 2000; i++) - { - AAS_Trace(eye, mins, maxs, end, 1, MASK_PLAYERSOLID); - } //end for - end_time = clock(); - botimport.Print(PRT_MESSAGE, "id %lu clocks, %lu CLOCKS_PER_SEC\n", end_time - start_time, CLOCKS_PER_SEC); -*/ - - // TTimo: nested comments are BAD for gcc -Werror, use #if 0 instead.. -#if 0 - AAS_ClearShownDebugLines(); - //bsptrace = AAS_Trace(eye, NULL, NULL, end, 1, MASK_PLAYERSOLID); - bsptrace = AAS_Trace(eye, mins, maxs, end, 1, MASK_PLAYERSOLID); - if (!line[0]) line[0] = botimport.DebugLineCreate(); - botimport.DebugLineShow(line[0], eye, bsptrace.endpos, LINECOLOR_YELLOW); - if (bsptrace.fraction < 1.0) - { - face = AAS_TraceEndFace(&trace); - if (face) - { - AAS_ShowFace(face - aasworld.faces); - } //end if - - AAS_DrawPlaneCross(bsptrace.endpos, - bsptrace.plane.normal, - bsptrace.plane.dist + bsptrace.exp_dist, - bsptrace.plane.type, LINECOLOR_GREEN); - if (trace.ent) - { - ent = &aasworld.entities[trace.ent]; - AAS_ShowBoundingBox(ent->origin, ent->mins, ent->maxs); - } //end if - } //end if - //bsptrace = AAS_Trace2(eye, NULL, NULL, end, 1, MASK_PLAYERSOLID); - bsptrace = AAS_Trace2(eye, mins, maxs, end, 1, MASK_PLAYERSOLID); - botimport.DebugLineShow(line[1], eye, bsptrace.endpos, LINECOLOR_BLUE); - if (bsptrace.fraction < 1.0) - { - AAS_DrawPlaneCross(bsptrace.endpos, - bsptrace.plane.normal, - bsptrace.plane.dist,// + bsptrace.exp_dist, - bsptrace.plane.type, LINECOLOR_RED); - if (bsptrace.ent) - { - ent = &aasworld.entities[bsptrace.ent]; - AAS_ShowBoundingBox(ent->origin, ent->mins, ent->maxs); - } //end if - } //end if -#endif -#endif - return 0; -} //end of the function BotExportTest - - -/* -============ -Init_AAS_Export -============ -*/ -static void Init_AAS_Export( aas_export_t *aas ) { - //-------------------------------------------- - // be_aas_entity.c - //-------------------------------------------- - aas->AAS_EntityInfo = AAS_EntityInfo; - //-------------------------------------------- - // be_aas_main.c - //-------------------------------------------- - aas->AAS_Initialized = AAS_Initialized; - aas->AAS_PresenceTypeBoundingBox = AAS_PresenceTypeBoundingBox; - aas->AAS_Time = AAS_Time; - //-------------------------------------------- - // be_aas_sample.c - //-------------------------------------------- - aas->AAS_PointAreaNum = AAS_PointAreaNum; - aas->AAS_PointReachabilityAreaIndex = AAS_PointReachabilityAreaIndex; - aas->AAS_TraceAreas = AAS_TraceAreas; - aas->AAS_BBoxAreas = AAS_BBoxAreas; - aas->AAS_AreaInfo = AAS_AreaInfo; - //-------------------------------------------- - // be_aas_bspq3.c - //-------------------------------------------- - aas->AAS_PointContents = AAS_PointContents; - aas->AAS_NextBSPEntity = AAS_NextBSPEntity; - aas->AAS_ValueForBSPEpairKey = AAS_ValueForBSPEpairKey; - aas->AAS_VectorForBSPEpairKey = AAS_VectorForBSPEpairKey; - aas->AAS_FloatForBSPEpairKey = AAS_FloatForBSPEpairKey; - aas->AAS_IntForBSPEpairKey = AAS_IntForBSPEpairKey; - //-------------------------------------------- - // be_aas_reach.c - //-------------------------------------------- - aas->AAS_AreaReachability = AAS_AreaReachability; - //-------------------------------------------- - // be_aas_route.c - //-------------------------------------------- - aas->AAS_AreaTravelTimeToGoalArea = AAS_AreaTravelTimeToGoalArea; - aas->AAS_EnableRoutingArea = AAS_EnableRoutingArea; - aas->AAS_PredictRoute = AAS_PredictRoute; - //-------------------------------------------- - // be_aas_altroute.c - //-------------------------------------------- - aas->AAS_AlternativeRouteGoals = AAS_AlternativeRouteGoals; - //-------------------------------------------- - // be_aas_move.c - //-------------------------------------------- - aas->AAS_Swimming = AAS_Swimming; - aas->AAS_PredictClientMovement = AAS_PredictClientMovement; -} - - -/* -============ -Init_EA_Export -============ -*/ -static void Init_EA_Export( ea_export_t *ea ) { - //ClientCommand elementary actions - ea->EA_Command = EA_Command; - ea->EA_Say = EA_Say; - ea->EA_SayTeam = EA_SayTeam; - - ea->EA_Action = EA_Action; - ea->EA_Gesture = EA_Gesture; - ea->EA_Talk = EA_Talk; - ea->EA_Attack = EA_Attack; - ea->EA_Use = EA_Use; - ea->EA_Respawn = EA_Respawn; - ea->EA_Crouch = EA_Crouch; - ea->EA_MoveUp = EA_MoveUp; - ea->EA_MoveDown = EA_MoveDown; - ea->EA_MoveForward = EA_MoveForward; - ea->EA_MoveBack = EA_MoveBack; - ea->EA_MoveLeft = EA_MoveLeft; - ea->EA_MoveRight = EA_MoveRight; - - ea->EA_SelectWeapon = EA_SelectWeapon; - ea->EA_Jump = EA_Jump; - ea->EA_DelayedJump = EA_DelayedJump; - ea->EA_Move = EA_Move; - ea->EA_View = EA_View; - ea->EA_GetInput = EA_GetInput; - ea->EA_EndRegular = EA_EndRegular; - ea->EA_ResetInput = EA_ResetInput; -} - - -/* -============ -Init_AI_Export -============ -*/ -static void Init_AI_Export( ai_export_t *ai ) { - //----------------------------------- - // be_ai_char.h - //----------------------------------- - ai->BotLoadCharacter = BotLoadCharacter; - ai->BotFreeCharacter = BotFreeCharacter; - ai->Characteristic_Float = Characteristic_Float; - ai->Characteristic_BFloat = Characteristic_BFloat; - ai->Characteristic_Integer = Characteristic_Integer; - ai->Characteristic_BInteger = Characteristic_BInteger; - ai->Characteristic_String = Characteristic_String; - //----------------------------------- - // be_ai_chat.h - //----------------------------------- - ai->BotAllocChatState = BotAllocChatState; - ai->BotFreeChatState = BotFreeChatState; - ai->BotQueueConsoleMessage = BotQueueConsoleMessage; - ai->BotRemoveConsoleMessage = BotRemoveConsoleMessage; - ai->BotNextConsoleMessage = BotNextConsoleMessage; - ai->BotNumConsoleMessages = BotNumConsoleMessages; - ai->BotInitialChat = BotInitialChat; - ai->BotNumInitialChats = BotNumInitialChats; - ai->BotReplyChat = BotReplyChat; - ai->BotChatLength = BotChatLength; - ai->BotEnterChat = BotEnterChat; - ai->BotGetChatMessage = BotGetChatMessage; - ai->StringContains = StringContains; - ai->BotFindMatch = BotFindMatch; - ai->BotMatchVariable = BotMatchVariable; - ai->UnifyWhiteSpaces = UnifyWhiteSpaces; - ai->BotReplaceSynonyms = BotReplaceSynonyms; - ai->BotLoadChatFile = BotLoadChatFile; - ai->BotSetChatGender = BotSetChatGender; - ai->BotSetChatName = BotSetChatName; - //----------------------------------- - // be_ai_goal.h - //----------------------------------- - ai->BotResetGoalState = BotResetGoalState; - ai->BotResetAvoidGoals = BotResetAvoidGoals; - ai->BotRemoveFromAvoidGoals = BotRemoveFromAvoidGoals; - ai->BotPushGoal = BotPushGoal; - ai->BotPopGoal = BotPopGoal; - ai->BotEmptyGoalStack = BotEmptyGoalStack; - ai->BotDumpAvoidGoals = BotDumpAvoidGoals; - ai->BotDumpGoalStack = BotDumpGoalStack; - ai->BotGoalName = BotGoalName; - ai->BotGetTopGoal = BotGetTopGoal; - ai->BotGetSecondGoal = BotGetSecondGoal; - ai->BotChooseLTGItem = BotChooseLTGItem; - ai->BotChooseNBGItem = BotChooseNBGItem; - ai->BotTouchingGoal = BotTouchingGoal; - ai->BotItemGoalInVisButNotVisible = BotItemGoalInVisButNotVisible; - ai->BotGetLevelItemGoal = BotGetLevelItemGoal; - ai->BotGetNextCampSpotGoal = BotGetNextCampSpotGoal; - ai->BotGetMapLocationGoal = BotGetMapLocationGoal; - ai->BotAvoidGoalTime = BotAvoidGoalTime; - ai->BotSetAvoidGoalTime = BotSetAvoidGoalTime; - ai->BotInitLevelItems = BotInitLevelItems; - ai->BotUpdateEntityItems = BotUpdateEntityItems; - ai->BotLoadItemWeights = BotLoadItemWeights; - ai->BotFreeItemWeights = BotFreeItemWeights; - ai->BotInterbreedGoalFuzzyLogic = BotInterbreedGoalFuzzyLogic; - ai->BotSaveGoalFuzzyLogic = BotSaveGoalFuzzyLogic; - ai->BotMutateGoalFuzzyLogic = BotMutateGoalFuzzyLogic; - ai->BotAllocGoalState = BotAllocGoalState; - ai->BotFreeGoalState = BotFreeGoalState; - //----------------------------------- - // be_ai_move.h - //----------------------------------- - ai->BotResetMoveState = BotResetMoveState; - ai->BotMoveToGoal = BotMoveToGoal; - ai->BotMoveInDirection = BotMoveInDirection; - ai->BotResetAvoidReach = BotResetAvoidReach; - ai->BotResetLastAvoidReach = BotResetLastAvoidReach; - ai->BotReachabilityArea = BotReachabilityArea; - ai->BotMovementViewTarget = BotMovementViewTarget; - ai->BotPredictVisiblePosition = BotPredictVisiblePosition; - ai->BotAllocMoveState = BotAllocMoveState; - ai->BotFreeMoveState = BotFreeMoveState; - ai->BotInitMoveState = BotInitMoveState; - ai->BotAddAvoidSpot = BotAddAvoidSpot; - //----------------------------------- - // be_ai_weap.h - //----------------------------------- - ai->BotChooseBestFightWeapon = BotChooseBestFightWeapon; - ai->BotGetWeaponInfo = BotGetWeaponInfo; - ai->BotLoadWeaponWeights = BotLoadWeaponWeights; - ai->BotAllocWeaponState = BotAllocWeaponState; - ai->BotFreeWeaponState = BotFreeWeaponState; - ai->BotResetWeaponState = BotResetWeaponState; - //----------------------------------- - // be_ai_gen.h - //----------------------------------- - ai->GeneticParentsAndChildSelection = GeneticParentsAndChildSelection; -} - - -/* -============ -GetBotLibAPI -============ -*/ -botlib_export_t *GetBotLibAPI(int apiVersion, botlib_import_t *import) { - assert(import); // bk001129 - this wasn't set for baseq3/ - botimport = *import; - assert(botimport.Print); // bk001129 - pars pro toto - - Com_Memset( &be_botlib_export, 0, sizeof( be_botlib_export ) ); - - if ( apiVersion != BOTLIB_API_VERSION ) { - botimport.Print( PRT_ERROR, "Mismatched BOTLIB_API_VERSION: expected %i, got %i\n", BOTLIB_API_VERSION, apiVersion ); - return NULL; - } - - Init_AAS_Export(&be_botlib_export.aas); - Init_EA_Export(&be_botlib_export.ea); - Init_AI_Export(&be_botlib_export.ai); - - be_botlib_export.BotLibSetup = Export_BotLibSetup; - be_botlib_export.BotLibShutdown = Export_BotLibShutdown; - be_botlib_export.BotLibVarSet = Export_BotLibVarSet; - be_botlib_export.BotLibVarGet = Export_BotLibVarGet; - - be_botlib_export.PC_AddGlobalDefine = PC_AddGlobalDefine; - be_botlib_export.PC_LoadSourceHandle = PC_LoadSourceHandle; - be_botlib_export.PC_FreeSourceHandle = PC_FreeSourceHandle; - be_botlib_export.PC_ReadTokenHandle = PC_ReadTokenHandle; - be_botlib_export.PC_SourceFileAndLine = PC_SourceFileAndLine; - - be_botlib_export.BotLibStartFrame = Export_BotLibStartFrame; - be_botlib_export.BotLibLoadMap = Export_BotLibLoadMap; - be_botlib_export.BotLibUpdateEntity = Export_BotLibUpdateEntity; - be_botlib_export.Test = BotExportTest; - - return &be_botlib_export; -} +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_interface.c // bk010221 - FIXME - DEAD code elimination + * + * desc: bot library interface + * + * $Archive: /MissionPack/code/botlib/be_interface.c $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_log.h" +#include "l_libvar.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_aas_def.h" +#include "be_interface.h" + +#include "../game/be_ea.h" +#include "be_ai_weight.h" +#include "../game/be_ai_goal.h" +#include "../game/be_ai_move.h" +#include "../game/be_ai_weap.h" +#include "../game/be_ai_chat.h" +#include "../game/be_ai_char.h" +#include "../game/be_ai_gen.h" + +//library globals in a structure +botlib_globals_t botlibglobals; + +botlib_export_t be_botlib_export; +botlib_import_t botimport; +// +int bot_developer; +//qtrue if the library is setup +int botlibsetup = qfalse; + +//=========================================================================== +// +// several functions used by the exported functions +// +//=========================================================================== + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Sys_MilliSeconds(void) +{ + return clock() * 1000 / CLOCKS_PER_SEC; +} //end of the function Sys_MilliSeconds +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean ValidClientNumber(int num, char *str) +{ + if (num < 0 || num > botlibglobals.maxclients) + { + //weird: the disabled stuff results in a crash + botimport.Print(PRT_ERROR, "%s: invalid client number %d, [0, %d]\n", + str, num, botlibglobals.maxclients); + return qfalse; + } //end if + return qtrue; +} //end of the function BotValidateClientNumber +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean ValidEntityNumber(int num, char *str) +{ + if (num < 0 || num > botlibglobals.maxentities) + { + botimport.Print(PRT_ERROR, "%s: invalid entity number %d, [0, %d]\n", + str, num, botlibglobals.maxentities); + return qfalse; + } //end if + return qtrue; +} //end of the function BotValidateClientNumber +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean BotLibSetup(char *str) +{ + if (!botlibglobals.botlibsetup) + { + botimport.Print(PRT_ERROR, "%s: bot library used before being setup\n", str); + return qfalse; + } //end if + return qtrue; +} //end of the function BotLibSetup + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Export_BotLibSetup(void) +{ + int errnum; + + bot_developer = LibVarGetValue("bot_developer"); + memset( &botlibglobals, 0, sizeof(botlibglobals) ); // bk001207 - init + //initialize byte swapping (litte endian etc.) +// Swap_Init(); + Log_Open("botlib.log"); + // + botimport.Print(PRT_MESSAGE, "------- BotLib Initialization -------\n"); + // + botlibglobals.maxclients = (int) LibVarValue("maxclients", "128"); + botlibglobals.maxentities = (int) LibVarValue("maxentities", "1024"); + + errnum = AAS_Setup(); //be_aas_main.c + if (errnum != BLERR_NOERROR) return errnum; + errnum = EA_Setup(); //be_ea.c + if (errnum != BLERR_NOERROR) return errnum; + errnum = BotSetupWeaponAI(); //be_ai_weap.c + if (errnum != BLERR_NOERROR)return errnum; + errnum = BotSetupGoalAI(); //be_ai_goal.c + if (errnum != BLERR_NOERROR) return errnum; + errnum = BotSetupChatAI(); //be_ai_chat.c + if (errnum != BLERR_NOERROR) return errnum; + errnum = BotSetupMoveAI(); //be_ai_move.c + if (errnum != BLERR_NOERROR) return errnum; + + botlibsetup = qtrue; + botlibglobals.botlibsetup = qtrue; + + return BLERR_NOERROR; +} //end of the function Export_BotLibSetup +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Export_BotLibShutdown(void) +{ + if (!BotLibSetup("BotLibShutdown")) return BLERR_LIBRARYNOTSETUP; +#ifndef DEMO + //DumpFileCRCs(); +#endif //DEMO + // + BotShutdownChatAI(); //be_ai_chat.c + BotShutdownMoveAI(); //be_ai_move.c + BotShutdownGoalAI(); //be_ai_goal.c + BotShutdownWeaponAI(); //be_ai_weap.c + BotShutdownWeights(); //be_ai_weight.c + BotShutdownCharacters(); //be_ai_char.c + //shud down aas + AAS_Shutdown(); + //shut down bot elemantary actions + EA_Shutdown(); + //free all libvars + LibVarDeAllocAll(); + //remove all global defines from the pre compiler + PC_RemoveAllGlobalDefines(); + + //dump all allocated memory +// DumpMemory(); +#ifdef DEBUG + PrintMemoryLabels(); +#endif + //shut down library log file + Log_Shutdown(); + // + botlibsetup = qfalse; + botlibglobals.botlibsetup = qfalse; + // print any files still open + PC_CheckOpenSourceHandles(); + // + return BLERR_NOERROR; +} //end of the function Export_BotLibShutdown +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Export_BotLibVarSet(char *var_name, char *value) +{ + LibVarSet(var_name, value); + return BLERR_NOERROR; +} //end of the function Export_BotLibVarSet +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Export_BotLibVarGet(char *var_name, char *value, int size) +{ + char *varvalue; + + varvalue = LibVarGetString(var_name); + strncpy(value, varvalue, size-1); + value[size-1] = '\0'; + return BLERR_NOERROR; +} //end of the function Export_BotLibVarGet +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Export_BotLibStartFrame(float time) +{ + if (!BotLibSetup("BotStartFrame")) return BLERR_LIBRARYNOTSETUP; + return AAS_StartFrame(time); +} //end of the function Export_BotLibStartFrame +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Export_BotLibLoadMap(const char *mapname) +{ +#ifdef DEBUG + int starttime = Sys_MilliSeconds(); +#endif + int errnum; + + if (!BotLibSetup("BotLoadMap")) return BLERR_LIBRARYNOTSETUP; + // + botimport.Print(PRT_MESSAGE, "------------ Map Loading ------------\n"); + //startup AAS for the current map, model and sound index + errnum = AAS_LoadMap(mapname); + if (errnum != BLERR_NOERROR) return errnum; + //initialize the items in the level + BotInitLevelItems(); //be_ai_goal.h + BotSetBrushModelTypes(); //be_ai_move.h + // + botimport.Print(PRT_MESSAGE, "-------------------------------------\n"); +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "map loaded in %d msec\n", Sys_MilliSeconds() - starttime); +#endif + // + return BLERR_NOERROR; +} //end of the function Export_BotLibLoadMap +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Export_BotLibUpdateEntity(int ent, bot_entitystate_t *state) +{ + if (!BotLibSetup("BotUpdateEntity")) return BLERR_LIBRARYNOTSETUP; + if (!ValidEntityNumber(ent, "BotUpdateEntity")) return BLERR_INVALIDENTITYNUMBER; + + return AAS_UpdateEntity(ent, state); +} //end of the function Export_BotLibUpdateEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_TestMovementPrediction(int entnum, vec3_t origin, vec3_t dir); +void ElevatorBottomCenter(aas_reachability_t *reach, vec3_t bottomcenter); +int BotGetReachabilityToGoal(vec3_t origin, int areanum, + int lastgoalareanum, int lastareanum, + int *avoidreach, float *avoidreachtimes, int *avoidreachtries, + bot_goal_t *goal, int travelflags, int movetravelflags, + struct bot_avoidspot_s *avoidspots, int numavoidspots, int *flags); + +int AAS_PointLight(vec3_t origin, int *red, int *green, int *blue); + +int AAS_TraceAreas(vec3_t start, vec3_t end, int *areas, vec3_t *points, int maxareas); + +int AAS_Reachability_WeaponJump(int area1num, int area2num); + +int BotFuzzyPointReachabilityArea(vec3_t origin); + +float BotGapDistance(vec3_t origin, vec3_t hordir, int entnum); + +void AAS_FloodAreas(vec3_t origin); + +int BotExportTest(int parm0, char *parm1, vec3_t parm2, vec3_t parm3) +{ + +// return AAS_PointLight(parm2, NULL, NULL, NULL); + +#ifdef DEBUG + static int area = -1; + static int line[2]; + int newarea, i, highlightarea, flood; +// int reachnum; + vec3_t eye, forward, right, end, origin; +// vec3_t bottomcenter; +// aas_trace_t trace; +// aas_face_t *face; +// aas_entity_t *ent; +// bsp_trace_t bsptrace; +// aas_reachability_t reach; +// bot_goal_t goal; + + // clock_t start_time, end_time; + vec3_t mins = {-16, -16, -24}; + vec3_t maxs = {16, 16, 32}; + +// int areas[10], numareas; + + + //return 0; + + if (!aasworld.loaded) return 0; + + /* + if (parm0 & 1) + { + AAS_ClearShownPolygons(); + AAS_FloodAreas(parm2); + } //end if + return 0; + */ + for (i = 0; i < 2; i++) if (!line[i]) line[i] = botimport.DebugLineCreate(); + +// AAS_ClearShownDebugLines(); + + //if (AAS_AgainstLadder(parm2)) botimport.Print(PRT_MESSAGE, "against ladder\n"); + //BotOnGround(parm2, PRESENCE_NORMAL, 1, &newarea, &newarea); + //botimport.Print(PRT_MESSAGE, "%f %f %f\n", parm2[0], parm2[1], parm2[2]); + //* + highlightarea = LibVarGetValue("bot_highlightarea"); + if (highlightarea > 0) + { + newarea = highlightarea; + } //end if + else + { + VectorCopy(parm2, origin); + origin[2] += 0.5; + //newarea = AAS_PointAreaNum(origin); + newarea = BotFuzzyPointReachabilityArea(origin); + } //end else + + botimport.Print(PRT_MESSAGE, "\rtravel time to goal (%d) = %d ", botlibglobals.goalareanum, + AAS_AreaTravelTimeToGoalArea(newarea, origin, botlibglobals.goalareanum, TFL_DEFAULT)); + //newarea = BotReachabilityArea(origin, qtrue); + if (newarea != area) + { + botimport.Print(PRT_MESSAGE, "origin = %f, %f, %f\n", origin[0], origin[1], origin[2]); + area = newarea; + botimport.Print(PRT_MESSAGE, "new area %d, cluster %d, presence type %d\n", + area, AAS_AreaCluster(area), AAS_PointPresenceType(origin)); + botimport.Print(PRT_MESSAGE, "area contents: "); + if (aasworld.areasettings[area].contents & AREACONTENTS_WATER) + { + botimport.Print(PRT_MESSAGE, "water &"); + } //end if + if (aasworld.areasettings[area].contents & AREACONTENTS_LAVA) + { + botimport.Print(PRT_MESSAGE, "lava &"); + } //end if + if (aasworld.areasettings[area].contents & AREACONTENTS_SLIME) + { + botimport.Print(PRT_MESSAGE, "slime &"); + } //end if + if (aasworld.areasettings[area].contents & AREACONTENTS_JUMPPAD) + { + botimport.Print(PRT_MESSAGE, "jump pad &"); + } //end if + if (aasworld.areasettings[area].contents & AREACONTENTS_CLUSTERPORTAL) + { + botimport.Print(PRT_MESSAGE, "cluster portal &"); + } //end if + if (aasworld.areasettings[area].contents & AREACONTENTS_VIEWPORTAL) + { + botimport.Print(PRT_MESSAGE, "view portal &"); + } //end if + if (aasworld.areasettings[area].contents & AREACONTENTS_DONOTENTER) + { + botimport.Print(PRT_MESSAGE, "do not enter &"); + } //end if + if (aasworld.areasettings[area].contents & AREACONTENTS_MOVER) + { + botimport.Print(PRT_MESSAGE, "mover &"); + } //end if + if (!aasworld.areasettings[area].contents) + { + botimport.Print(PRT_MESSAGE, "empty"); + } //end if + botimport.Print(PRT_MESSAGE, "\n"); + botimport.Print(PRT_MESSAGE, "travel time to goal (%d) = %d\n", botlibglobals.goalareanum, + AAS_AreaTravelTimeToGoalArea(newarea, origin, botlibglobals.goalareanum, TFL_DEFAULT|TFL_ROCKETJUMP)); + /* + VectorCopy(origin, end); + end[2] += 5; + numareas = AAS_TraceAreas(origin, end, areas, NULL, 10); + AAS_TraceClientBBox(origin, end, PRESENCE_CROUCH, -1); + botimport.Print(PRT_MESSAGE, "num areas = %d, area = %d\n", numareas, areas[0]); + */ + /* + botlibglobals.goalareanum = newarea; + VectorCopy(parm2, botlibglobals.goalorigin); + botimport.Print(PRT_MESSAGE, "new goal %2.1f %2.1f %2.1f area %d\n", + origin[0], origin[1], origin[2], newarea); + */ + } //end if + //* + flood = LibVarGetValue("bot_flood"); + if (parm0 & 1) + { + if (flood) + { + AAS_ClearShownPolygons(); + AAS_ClearShownDebugLines(); + AAS_FloodAreas(parm2); + } + else + { + botlibglobals.goalareanum = newarea; + VectorCopy(parm2, botlibglobals.goalorigin); + botimport.Print(PRT_MESSAGE, "new goal %2.1f %2.1f %2.1f area %d\n", + origin[0], origin[1], origin[2], newarea); + } + } //end if*/ + if (flood) + return 0; +// if (parm0 & BUTTON_USE) +// { +// botlibglobals.runai = !botlibglobals.runai; +// if (botlibglobals.runai) botimport.Print(PRT_MESSAGE, "started AI\n"); +// else botimport.Print(PRT_MESSAGE, "stopped AI\n"); + //* / + /* + goal.areanum = botlibglobals.goalareanum; + reachnum = BotGetReachabilityToGoal(parm2, newarea, 1, + ms.avoidreach, ms.avoidreachtimes, + &goal, TFL_DEFAULT); + if (!reachnum) + { + botimport.Print(PRT_MESSAGE, "goal not reachable\n"); + } //end if + else + { + AAS_ReachabilityFromNum(reachnum, &reach); + AAS_ClearShownDebugLines(); + AAS_ShowArea(area, qtrue); + AAS_ShowArea(reach.areanum, qtrue); + AAS_DrawCross(reach.start, 6, LINECOLOR_BLUE); + AAS_DrawCross(reach.end, 6, LINECOLOR_RED); + // + if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_ELEVATOR) + { + ElevatorBottomCenter(&reach, bottomcenter); + AAS_DrawCross(bottomcenter, 10, LINECOLOR_GREEN); + } //end if + } //end else*/ +// botimport.Print(PRT_MESSAGE, "travel time to goal = %d\n", +// AAS_AreaTravelTimeToGoalArea(area, origin, botlibglobals.goalareanum, TFL_DEFAULT)); +// botimport.Print(PRT_MESSAGE, "test rj from 703 to 716\n"); +// AAS_Reachability_WeaponJump(703, 716); +// } //end if*/ + +/* face = AAS_AreaGroundFace(newarea, parm2); + if (face) + { + AAS_ShowFace(face - aasworld.faces); + } //end if*/ + /* + AAS_ClearShownDebugLines(); + AAS_ShowArea(newarea, parm0 & BUTTON_USE); + AAS_ShowReachableAreas(area); + */ + AAS_ClearShownPolygons(); + AAS_ClearShownDebugLines(); + AAS_ShowAreaPolygons(newarea, 1, parm0 & 4); + if (parm0 & 2) AAS_ShowReachableAreas(area); + else + { + static int lastgoalareanum, lastareanum; + static int avoidreach[MAX_AVOIDREACH]; + static float avoidreachtimes[MAX_AVOIDREACH]; + static int avoidreachtries[MAX_AVOIDREACH]; + int reachnum, resultFlags; + bot_goal_t goal; + aas_reachability_t reach; + + /* + goal.areanum = botlibglobals.goalareanum; + VectorCopy(botlibglobals.goalorigin, goal.origin); + reachnum = BotGetReachabilityToGoal(origin, newarea, + lastgoalareanum, lastareanum, + avoidreach, avoidreachtimes, avoidreachtries, + &goal, TFL_DEFAULT|TFL_FUNCBOB|TFL_ROCKETJUMP, TFL_DEFAULT|TFL_FUNCBOB|TFL_ROCKETJUMP, + NULL, 0, &resultFlags); + AAS_ReachabilityFromNum(reachnum, &reach); + AAS_ShowReachability(&reach); + */ + int curarea; + vec3_t curorigin; + + goal.areanum = botlibglobals.goalareanum; + VectorCopy(botlibglobals.goalorigin, goal.origin); + VectorCopy(origin, curorigin); + curarea = newarea; + for ( i = 0; i < 100; i++ ) { + if ( curarea == goal.areanum ) { + break; + } + reachnum = BotGetReachabilityToGoal(curorigin, curarea, + lastgoalareanum, lastareanum, + avoidreach, avoidreachtimes, avoidreachtries, + &goal, TFL_DEFAULT|TFL_FUNCBOB|TFL_ROCKETJUMP, TFL_DEFAULT|TFL_FUNCBOB|TFL_ROCKETJUMP, + NULL, 0, &resultFlags); + AAS_ReachabilityFromNum(reachnum, &reach); + AAS_ShowReachability(&reach); + VectorCopy(reach.end, origin); + lastareanum = curarea; + curarea = reach.areanum; + } + } //end else + VectorClear(forward); + //BotGapDistance(origin, forward, 0); + /* + if (parm0 & BUTTON_USE) + { + botimport.Print(PRT_MESSAGE, "test rj from 703 to 716\n"); + AAS_Reachability_WeaponJump(703, 716); + } //end if*/ + + AngleVectors(parm3, forward, right, NULL); + //get the eye 16 units to the right of the origin + VectorMA(parm2, 8, right, eye); + //get the eye 24 units up + eye[2] += 24; + //get the end point for the line to be traced + VectorMA(eye, 800, forward, end); + +// AAS_TestMovementPrediction(1, parm2, forward); +/* + //trace the line to find the hit point + trace = AAS_TraceClientBBox(eye, end, PRESENCE_NORMAL, 1); + if (!line[0]) line[0] = botimport.DebugLineCreate(); + botimport.DebugLineShow(line[0], eye, trace.endpos, LINECOLOR_BLUE); + // + AAS_ClearShownDebugLines(); + if (trace.ent) + { + ent = &aasworld.entities[trace.ent]; + AAS_ShowBoundingBox(ent->origin, ent->mins, ent->maxs); + } //end if +*/ + +/* + start_time = clock(); + for (i = 0; i < 2000; i++) + { + AAS_Trace2(eye, mins, maxs, end, 1, MASK_PLAYERSOLID); +// AAS_TraceClientBBox(eye, end, PRESENCE_NORMAL, 1); + } //end for + end_time = clock(); + botimport.Print(PRT_MESSAGE, "me %lu clocks, %lu CLOCKS_PER_SEC\n", end_time - start_time, CLOCKS_PER_SEC); + start_time = clock(); + for (i = 0; i < 2000; i++) + { + AAS_Trace(eye, mins, maxs, end, 1, MASK_PLAYERSOLID); + } //end for + end_time = clock(); + botimport.Print(PRT_MESSAGE, "id %lu clocks, %lu CLOCKS_PER_SEC\n", end_time - start_time, CLOCKS_PER_SEC); +*/ + + // TTimo: nested comments are BAD for gcc -Werror, use #if 0 instead.. +#if 0 + AAS_ClearShownDebugLines(); + //bsptrace = AAS_Trace(eye, NULL, NULL, end, 1, MASK_PLAYERSOLID); + bsptrace = AAS_Trace(eye, mins, maxs, end, 1, MASK_PLAYERSOLID); + if (!line[0]) line[0] = botimport.DebugLineCreate(); + botimport.DebugLineShow(line[0], eye, bsptrace.endpos, LINECOLOR_YELLOW); + if (bsptrace.fraction < 1.0) + { + face = AAS_TraceEndFace(&trace); + if (face) + { + AAS_ShowFace(face - aasworld.faces); + } //end if + + AAS_DrawPlaneCross(bsptrace.endpos, + bsptrace.plane.normal, + bsptrace.plane.dist + bsptrace.exp_dist, + bsptrace.plane.type, LINECOLOR_GREEN); + if (trace.ent) + { + ent = &aasworld.entities[trace.ent]; + AAS_ShowBoundingBox(ent->origin, ent->mins, ent->maxs); + } //end if + } //end if + //bsptrace = AAS_Trace2(eye, NULL, NULL, end, 1, MASK_PLAYERSOLID); + bsptrace = AAS_Trace2(eye, mins, maxs, end, 1, MASK_PLAYERSOLID); + botimport.DebugLineShow(line[1], eye, bsptrace.endpos, LINECOLOR_BLUE); + if (bsptrace.fraction < 1.0) + { + AAS_DrawPlaneCross(bsptrace.endpos, + bsptrace.plane.normal, + bsptrace.plane.dist,// + bsptrace.exp_dist, + bsptrace.plane.type, LINECOLOR_RED); + if (bsptrace.ent) + { + ent = &aasworld.entities[bsptrace.ent]; + AAS_ShowBoundingBox(ent->origin, ent->mins, ent->maxs); + } //end if + } //end if +#endif +#endif + return 0; +} //end of the function BotExportTest + + +/* +============ +Init_AAS_Export +============ +*/ +static void Init_AAS_Export( aas_export_t *aas ) { + //-------------------------------------------- + // be_aas_entity.c + //-------------------------------------------- + aas->AAS_EntityInfo = AAS_EntityInfo; + //-------------------------------------------- + // be_aas_main.c + //-------------------------------------------- + aas->AAS_Initialized = AAS_Initialized; + aas->AAS_PresenceTypeBoundingBox = AAS_PresenceTypeBoundingBox; + aas->AAS_Time = AAS_Time; + //-------------------------------------------- + // be_aas_sample.c + //-------------------------------------------- + aas->AAS_PointAreaNum = AAS_PointAreaNum; + aas->AAS_PointReachabilityAreaIndex = AAS_PointReachabilityAreaIndex; + aas->AAS_TraceAreas = AAS_TraceAreas; + aas->AAS_BBoxAreas = AAS_BBoxAreas; + aas->AAS_AreaInfo = AAS_AreaInfo; + //-------------------------------------------- + // be_aas_bspq3.c + //-------------------------------------------- + aas->AAS_PointContents = AAS_PointContents; + aas->AAS_NextBSPEntity = AAS_NextBSPEntity; + aas->AAS_ValueForBSPEpairKey = AAS_ValueForBSPEpairKey; + aas->AAS_VectorForBSPEpairKey = AAS_VectorForBSPEpairKey; + aas->AAS_FloatForBSPEpairKey = AAS_FloatForBSPEpairKey; + aas->AAS_IntForBSPEpairKey = AAS_IntForBSPEpairKey; + //-------------------------------------------- + // be_aas_reach.c + //-------------------------------------------- + aas->AAS_AreaReachability = AAS_AreaReachability; + //-------------------------------------------- + // be_aas_route.c + //-------------------------------------------- + aas->AAS_AreaTravelTimeToGoalArea = AAS_AreaTravelTimeToGoalArea; + aas->AAS_EnableRoutingArea = AAS_EnableRoutingArea; + aas->AAS_PredictRoute = AAS_PredictRoute; + //-------------------------------------------- + // be_aas_altroute.c + //-------------------------------------------- + aas->AAS_AlternativeRouteGoals = AAS_AlternativeRouteGoals; + //-------------------------------------------- + // be_aas_move.c + //-------------------------------------------- + aas->AAS_Swimming = AAS_Swimming; + aas->AAS_PredictClientMovement = AAS_PredictClientMovement; +} + + +/* +============ +Init_EA_Export +============ +*/ +static void Init_EA_Export( ea_export_t *ea ) { + //ClientCommand elementary actions + ea->EA_Command = EA_Command; + ea->EA_Say = EA_Say; + ea->EA_SayTeam = EA_SayTeam; + + ea->EA_Action = EA_Action; + ea->EA_Gesture = EA_Gesture; + ea->EA_Talk = EA_Talk; + ea->EA_Attack = EA_Attack; + ea->EA_Use = EA_Use; + ea->EA_Respawn = EA_Respawn; + ea->EA_Crouch = EA_Crouch; + ea->EA_MoveUp = EA_MoveUp; + ea->EA_MoveDown = EA_MoveDown; + ea->EA_MoveForward = EA_MoveForward; + ea->EA_MoveBack = EA_MoveBack; + ea->EA_MoveLeft = EA_MoveLeft; + ea->EA_MoveRight = EA_MoveRight; + + ea->EA_SelectWeapon = EA_SelectWeapon; + ea->EA_Jump = EA_Jump; + ea->EA_DelayedJump = EA_DelayedJump; + ea->EA_Move = EA_Move; + ea->EA_View = EA_View; + ea->EA_GetInput = EA_GetInput; + ea->EA_EndRegular = EA_EndRegular; + ea->EA_ResetInput = EA_ResetInput; +} + + +/* +============ +Init_AI_Export +============ +*/ +static void Init_AI_Export( ai_export_t *ai ) { + //----------------------------------- + // be_ai_char.h + //----------------------------------- + ai->BotLoadCharacter = BotLoadCharacter; + ai->BotFreeCharacter = BotFreeCharacter; + ai->Characteristic_Float = Characteristic_Float; + ai->Characteristic_BFloat = Characteristic_BFloat; + ai->Characteristic_Integer = Characteristic_Integer; + ai->Characteristic_BInteger = Characteristic_BInteger; + ai->Characteristic_String = Characteristic_String; + //----------------------------------- + // be_ai_chat.h + //----------------------------------- + ai->BotAllocChatState = BotAllocChatState; + ai->BotFreeChatState = BotFreeChatState; + ai->BotQueueConsoleMessage = BotQueueConsoleMessage; + ai->BotRemoveConsoleMessage = BotRemoveConsoleMessage; + ai->BotNextConsoleMessage = BotNextConsoleMessage; + ai->BotNumConsoleMessages = BotNumConsoleMessages; + ai->BotInitialChat = BotInitialChat; + ai->BotNumInitialChats = BotNumInitialChats; + ai->BotReplyChat = BotReplyChat; + ai->BotChatLength = BotChatLength; + ai->BotEnterChat = BotEnterChat; + ai->BotGetChatMessage = BotGetChatMessage; + ai->StringContains = StringContains; + ai->BotFindMatch = BotFindMatch; + ai->BotMatchVariable = BotMatchVariable; + ai->UnifyWhiteSpaces = UnifyWhiteSpaces; + ai->BotReplaceSynonyms = BotReplaceSynonyms; + ai->BotLoadChatFile = BotLoadChatFile; + ai->BotSetChatGender = BotSetChatGender; + ai->BotSetChatName = BotSetChatName; + //----------------------------------- + // be_ai_goal.h + //----------------------------------- + ai->BotResetGoalState = BotResetGoalState; + ai->BotResetAvoidGoals = BotResetAvoidGoals; + ai->BotRemoveFromAvoidGoals = BotRemoveFromAvoidGoals; + ai->BotPushGoal = BotPushGoal; + ai->BotPopGoal = BotPopGoal; + ai->BotEmptyGoalStack = BotEmptyGoalStack; + ai->BotDumpAvoidGoals = BotDumpAvoidGoals; + ai->BotDumpGoalStack = BotDumpGoalStack; + ai->BotGoalName = BotGoalName; + ai->BotGetTopGoal = BotGetTopGoal; + ai->BotGetSecondGoal = BotGetSecondGoal; + ai->BotChooseLTGItem = BotChooseLTGItem; + ai->BotChooseNBGItem = BotChooseNBGItem; + ai->BotTouchingGoal = BotTouchingGoal; + ai->BotItemGoalInVisButNotVisible = BotItemGoalInVisButNotVisible; + ai->BotGetLevelItemGoal = BotGetLevelItemGoal; + ai->BotGetNextCampSpotGoal = BotGetNextCampSpotGoal; + ai->BotGetMapLocationGoal = BotGetMapLocationGoal; + ai->BotAvoidGoalTime = BotAvoidGoalTime; + ai->BotSetAvoidGoalTime = BotSetAvoidGoalTime; + ai->BotInitLevelItems = BotInitLevelItems; + ai->BotUpdateEntityItems = BotUpdateEntityItems; + ai->BotLoadItemWeights = BotLoadItemWeights; + ai->BotFreeItemWeights = BotFreeItemWeights; + ai->BotInterbreedGoalFuzzyLogic = BotInterbreedGoalFuzzyLogic; + ai->BotSaveGoalFuzzyLogic = BotSaveGoalFuzzyLogic; + ai->BotMutateGoalFuzzyLogic = BotMutateGoalFuzzyLogic; + ai->BotAllocGoalState = BotAllocGoalState; + ai->BotFreeGoalState = BotFreeGoalState; + //----------------------------------- + // be_ai_move.h + //----------------------------------- + ai->BotResetMoveState = BotResetMoveState; + ai->BotMoveToGoal = BotMoveToGoal; + ai->BotMoveInDirection = BotMoveInDirection; + ai->BotResetAvoidReach = BotResetAvoidReach; + ai->BotResetLastAvoidReach = BotResetLastAvoidReach; + ai->BotReachabilityArea = BotReachabilityArea; + ai->BotMovementViewTarget = BotMovementViewTarget; + ai->BotPredictVisiblePosition = BotPredictVisiblePosition; + ai->BotAllocMoveState = BotAllocMoveState; + ai->BotFreeMoveState = BotFreeMoveState; + ai->BotInitMoveState = BotInitMoveState; + ai->BotAddAvoidSpot = BotAddAvoidSpot; + //----------------------------------- + // be_ai_weap.h + //----------------------------------- + ai->BotChooseBestFightWeapon = BotChooseBestFightWeapon; + ai->BotGetWeaponInfo = BotGetWeaponInfo; + ai->BotLoadWeaponWeights = BotLoadWeaponWeights; + ai->BotAllocWeaponState = BotAllocWeaponState; + ai->BotFreeWeaponState = BotFreeWeaponState; + ai->BotResetWeaponState = BotResetWeaponState; + //----------------------------------- + // be_ai_gen.h + //----------------------------------- + ai->GeneticParentsAndChildSelection = GeneticParentsAndChildSelection; +} + + +/* +============ +GetBotLibAPI +============ +*/ +botlib_export_t *GetBotLibAPI(int apiVersion, botlib_import_t *import) { + assert(import); // bk001129 - this wasn't set for baseq3/ + botimport = *import; + assert(botimport.Print); // bk001129 - pars pro toto + + Com_Memset( &be_botlib_export, 0, sizeof( be_botlib_export ) ); + + if ( apiVersion != BOTLIB_API_VERSION ) { + botimport.Print( PRT_ERROR, "Mismatched BOTLIB_API_VERSION: expected %i, got %i\n", BOTLIB_API_VERSION, apiVersion ); + return NULL; + } + + Init_AAS_Export(&be_botlib_export.aas); + Init_EA_Export(&be_botlib_export.ea); + Init_AI_Export(&be_botlib_export.ai); + + be_botlib_export.BotLibSetup = Export_BotLibSetup; + be_botlib_export.BotLibShutdown = Export_BotLibShutdown; + be_botlib_export.BotLibVarSet = Export_BotLibVarSet; + be_botlib_export.BotLibVarGet = Export_BotLibVarGet; + + be_botlib_export.PC_AddGlobalDefine = PC_AddGlobalDefine; + be_botlib_export.PC_LoadSourceHandle = PC_LoadSourceHandle; + be_botlib_export.PC_FreeSourceHandle = PC_FreeSourceHandle; + be_botlib_export.PC_ReadTokenHandle = PC_ReadTokenHandle; + be_botlib_export.PC_SourceFileAndLine = PC_SourceFileAndLine; + + be_botlib_export.BotLibStartFrame = Export_BotLibStartFrame; + be_botlib_export.BotLibLoadMap = Export_BotLibLoadMap; + be_botlib_export.BotLibUpdateEntity = Export_BotLibUpdateEntity; + be_botlib_export.Test = BotExportTest; + + return &be_botlib_export; +} diff --git a/code/botlib/be_interface.h b/code/botlib/be_interface.h index a9f6347..bdeb512 100755 --- a/code/botlib/be_interface.h +++ b/code/botlib/be_interface.h @@ -1,57 +1,57 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: be_interface.h - * - * desc: botlib interface - * - * $Archive: /source/code/botlib/be_interface.h $ - * - *****************************************************************************/ - -//#define DEBUG //debug code -#define RANDOMIZE //randomize bot behaviour - -//FIXME: get rid of this global structure -typedef struct botlib_globals_s -{ - int botlibsetup; //true when the bot library has been setup - int maxentities; //maximum number of entities - int maxclients; //maximum number of clients - float time; //the global time -#ifdef DEBUG - qboolean debug; //true if debug is on - int goalareanum; - vec3_t goalorigin; - int runai; -#endif -} botlib_globals_t; - - -extern botlib_globals_t botlibglobals; -extern botlib_import_t botimport; -extern int bot_developer; //true if developer is on - -// -int Sys_MilliSeconds(void); - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_interface.h + * + * desc: botlib interface + * + * $Archive: /source/code/botlib/be_interface.h $ + * + *****************************************************************************/ + +//#define DEBUG //debug code +#define RANDOMIZE //randomize bot behaviour + +//FIXME: get rid of this global structure +typedef struct botlib_globals_s +{ + int botlibsetup; //true when the bot library has been setup + int maxentities; //maximum number of entities + int maxclients; //maximum number of clients + float time; //the global time +#ifdef DEBUG + qboolean debug; //true if debug is on + int goalareanum; + vec3_t goalorigin; + int runai; +#endif +} botlib_globals_t; + + +extern botlib_globals_t botlibglobals; +extern botlib_import_t botimport; +extern int bot_developer; //true if developer is on + +// +int Sys_MilliSeconds(void); + diff --git a/code/botlib/botlib.vcproj b/code/botlib/botlib.vcproj index e544a14..498c788 100755 --- a/code/botlib/botlib.vcproj +++ b/code/botlib/botlib.vcproj @@ -1,1559 +1,1559 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/botlib/l_crc.c b/code/botlib/l_crc.c index 9a945f5..8b1067b 100755 --- a/code/botlib/l_crc.c +++ b/code/botlib/l_crc.c @@ -1,151 +1,151 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: l_crc.c - * - * desc: CRC calculation - * - * $Archive: /MissionPack/CODE/botlib/l_crc.c $ - * - *****************************************************************************/ - -#include -#include -#include - -#include "../game/q_shared.h" -#include "../game/botlib.h" -#include "be_interface.h" //for botimport.Print - - -// FIXME: byte swap? - -// this is a 16 bit, non-reflected CRC using the polynomial 0x1021 -// and the initial and final xor values shown below... in other words, the -// CCITT standard CRC used by XMODEM - -#define CRC_INIT_VALUE 0xffff -#define CRC_XOR_VALUE 0x0000 - -unsigned short crctable[257] = -{ - 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, - 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, - 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, - 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, - 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, - 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, - 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, - 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, - 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, - 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, - 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, - 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, - 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, - 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, - 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, - 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, - 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, - 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, - 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, - 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, - 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, - 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, - 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, - 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, - 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, - 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, - 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, - 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, - 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, - 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, - 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, - 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 -}; - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void CRC_Init(unsigned short *crcvalue) -{ - *crcvalue = CRC_INIT_VALUE; -} //end of the function CRC_Init -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void CRC_ProcessByte(unsigned short *crcvalue, byte data) -{ - *crcvalue = (*crcvalue << 8) ^ crctable[(*crcvalue >> 8) ^ data]; -} //end of the function CRC_ProcessByte -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -unsigned short CRC_Value(unsigned short crcvalue) -{ - return crcvalue ^ CRC_XOR_VALUE; -} //end of the function CRC_Value -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -unsigned short CRC_ProcessString(unsigned char *data, int length) -{ - unsigned short crcvalue; - int i, ind; - - CRC_Init(&crcvalue); - - for (i = 0; i < length; i++) - { - ind = (crcvalue >> 8) ^ data[i]; - if (ind < 0 || ind > 256) ind = 0; - crcvalue = (crcvalue << 8) ^ crctable[ind]; - } //end for - return CRC_Value(crcvalue); -} //end of the function CRC_ProcessString -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void CRC_ContinueProcessString(unsigned short *crc, char *data, int length) -{ - int i; - - for (i = 0; i < length; i++) - { - *crc = (*crc << 8) ^ crctable[(*crc >> 8) ^ data[i]]; - } //end for -} //end of the function CRC_ProcessString +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: l_crc.c + * + * desc: CRC calculation + * + * $Archive: /MissionPack/CODE/botlib/l_crc.c $ + * + *****************************************************************************/ + +#include +#include +#include + +#include "../game/q_shared.h" +#include "../game/botlib.h" +#include "be_interface.h" //for botimport.Print + + +// FIXME: byte swap? + +// this is a 16 bit, non-reflected CRC using the polynomial 0x1021 +// and the initial and final xor values shown below... in other words, the +// CCITT standard CRC used by XMODEM + +#define CRC_INIT_VALUE 0xffff +#define CRC_XOR_VALUE 0x0000 + +unsigned short crctable[257] = +{ + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, + 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, + 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, + 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, + 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, + 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, + 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, + 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, + 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, + 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, + 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, + 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, + 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, + 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, + 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, + 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, + 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, + 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, + 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, + 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, + 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, + 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, + 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 +}; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void CRC_Init(unsigned short *crcvalue) +{ + *crcvalue = CRC_INIT_VALUE; +} //end of the function CRC_Init +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void CRC_ProcessByte(unsigned short *crcvalue, byte data) +{ + *crcvalue = (*crcvalue << 8) ^ crctable[(*crcvalue >> 8) ^ data]; +} //end of the function CRC_ProcessByte +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +unsigned short CRC_Value(unsigned short crcvalue) +{ + return crcvalue ^ CRC_XOR_VALUE; +} //end of the function CRC_Value +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +unsigned short CRC_ProcessString(unsigned char *data, int length) +{ + unsigned short crcvalue; + int i, ind; + + CRC_Init(&crcvalue); + + for (i = 0; i < length; i++) + { + ind = (crcvalue >> 8) ^ data[i]; + if (ind < 0 || ind > 256) ind = 0; + crcvalue = (crcvalue << 8) ^ crctable[ind]; + } //end for + return CRC_Value(crcvalue); +} //end of the function CRC_ProcessString +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void CRC_ContinueProcessString(unsigned short *crc, char *data, int length) +{ + int i; + + for (i = 0; i < length; i++) + { + *crc = (*crc << 8) ^ crctable[(*crc >> 8) ^ data[i]]; + } //end for +} //end of the function CRC_ProcessString diff --git a/code/botlib/l_crc.h b/code/botlib/l_crc.h index 3f37cd4..67baf56 100755 --- a/code/botlib/l_crc.h +++ b/code/botlib/l_crc.h @@ -1,29 +1,29 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -typedef unsigned short crc_t; - -void CRC_Init(unsigned short *crcvalue); -void CRC_ProcessByte(unsigned short *crcvalue, byte data); -unsigned short CRC_Value(unsigned short crcvalue); -unsigned short CRC_ProcessString(unsigned char *data, int length); -void CRC_ContinueProcessString(unsigned short *crc, char *data, int length); +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +typedef unsigned short crc_t; + +void CRC_Init(unsigned short *crcvalue); +void CRC_ProcessByte(unsigned short *crcvalue, byte data); +unsigned short CRC_Value(unsigned short crcvalue); +unsigned short CRC_ProcessString(unsigned char *data, int length); +void CRC_ContinueProcessString(unsigned short *crc, char *data, int length); diff --git a/code/botlib/l_libvar.c b/code/botlib/l_libvar.c index d7ac798..a8629c8 100755 --- a/code/botlib/l_libvar.c +++ b/code/botlib/l_libvar.c @@ -1,294 +1,294 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: l_libvar.c - * - * desc: bot library variables - * - * $Archive: /MissionPack/code/botlib/l_libvar.c $ - * - *****************************************************************************/ - -#include "../game/q_shared.h" -#include "l_memory.h" -#include "l_libvar.h" - -//list with library variables -libvar_t *libvarlist; - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -float LibVarStringValue(char *string) -{ - int dotfound = 0; - float value = 0; - - while(*string) - { - if (*string < '0' || *string > '9') - { - if (dotfound || *string != '.') - { - return 0; - } //end if - else - { - dotfound = 10; - string++; - } //end if - } //end if - if (dotfound) - { - value = value + (float) (*string - '0') / (float) dotfound; - dotfound *= 10; - } //end if - else - { - value = value * 10.0 + (float) (*string - '0'); - } //end else - string++; - } //end while - return value; -} //end of the function LibVarStringValue -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -libvar_t *LibVarAlloc(char *var_name) -{ - libvar_t *v; - - v = (libvar_t *) GetMemory(sizeof(libvar_t) + strlen(var_name) + 1); - Com_Memset(v, 0, sizeof(libvar_t)); - v->name = (char *) v + sizeof(libvar_t); - strcpy(v->name, var_name); - //add the variable in the list - v->next = libvarlist; - libvarlist = v; - return v; -} //end of the function LibVarAlloc -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void LibVarDeAlloc(libvar_t *v) -{ - if (v->string) FreeMemory(v->string); - FreeMemory(v); -} //end of the function LibVarDeAlloc -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void LibVarDeAllocAll(void) -{ - libvar_t *v; - - for (v = libvarlist; v; v = libvarlist) - { - libvarlist = libvarlist->next; - LibVarDeAlloc(v); - } //end for - libvarlist = NULL; -} //end of the function LibVarDeAllocAll -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -libvar_t *LibVarGet(char *var_name) -{ - libvar_t *v; - - for (v = libvarlist; v; v = v->next) - { - if (!Q_stricmp(v->name, var_name)) - { - return v; - } //end if - } //end for - return NULL; -} //end of the function LibVarGet -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -char *LibVarGetString(char *var_name) -{ - libvar_t *v; - - v = LibVarGet(var_name); - if (v) - { - return v->string; - } //end if - else - { - return ""; - } //end else -} //end of the function LibVarGetString -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -float LibVarGetValue(char *var_name) -{ - libvar_t *v; - - v = LibVarGet(var_name); - if (v) - { - return v->value; - } //end if - else - { - return 0; - } //end else -} //end of the function LibVarGetValue -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -libvar_t *LibVar(char *var_name, char *value) -{ - libvar_t *v; - v = LibVarGet(var_name); - if (v) return v; - //create new variable - v = LibVarAlloc(var_name); - //variable string - v->string = (char *) GetMemory(strlen(value) + 1); - strcpy(v->string, value); - //the value - v->value = LibVarStringValue(v->string); - //variable is modified - v->modified = qtrue; - // - return v; -} //end of the function LibVar -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -char *LibVarString(char *var_name, char *value) -{ - libvar_t *v; - - v = LibVar(var_name, value); - return v->string; -} //end of the function LibVarString -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -float LibVarValue(char *var_name, char *value) -{ - libvar_t *v; - - v = LibVar(var_name, value); - return v->value; -} //end of the function LibVarValue -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void LibVarSet(char *var_name, char *value) -{ - libvar_t *v; - - v = LibVarGet(var_name); - if (v) - { - FreeMemory(v->string); - } //end if - else - { - v = LibVarAlloc(var_name); - } //end else - //variable string - v->string = (char *) GetMemory(strlen(value) + 1); - strcpy(v->string, value); - //the value - v->value = LibVarStringValue(v->string); - //variable is modified - v->modified = qtrue; -} //end of the function LibVarSet -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean LibVarChanged(char *var_name) -{ - libvar_t *v; - - v = LibVarGet(var_name); - if (v) - { - return v->modified; - } //end if - else - { - return qfalse; - } //end else -} //end of the function LibVarChanged -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void LibVarSetNotModified(char *var_name) -{ - libvar_t *v; - - v = LibVarGet(var_name); - if (v) - { - v->modified = qfalse; - } //end if -} //end of the function LibVarSetNotModified +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: l_libvar.c + * + * desc: bot library variables + * + * $Archive: /MissionPack/code/botlib/l_libvar.c $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_libvar.h" + +//list with library variables +libvar_t *libvarlist; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float LibVarStringValue(char *string) +{ + int dotfound = 0; + float value = 0; + + while(*string) + { + if (*string < '0' || *string > '9') + { + if (dotfound || *string != '.') + { + return 0; + } //end if + else + { + dotfound = 10; + string++; + } //end if + } //end if + if (dotfound) + { + value = value + (float) (*string - '0') / (float) dotfound; + dotfound *= 10; + } //end if + else + { + value = value * 10.0 + (float) (*string - '0'); + } //end else + string++; + } //end while + return value; +} //end of the function LibVarStringValue +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +libvar_t *LibVarAlloc(char *var_name) +{ + libvar_t *v; + + v = (libvar_t *) GetMemory(sizeof(libvar_t) + strlen(var_name) + 1); + Com_Memset(v, 0, sizeof(libvar_t)); + v->name = (char *) v + sizeof(libvar_t); + strcpy(v->name, var_name); + //add the variable in the list + v->next = libvarlist; + libvarlist = v; + return v; +} //end of the function LibVarAlloc +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void LibVarDeAlloc(libvar_t *v) +{ + if (v->string) FreeMemory(v->string); + FreeMemory(v); +} //end of the function LibVarDeAlloc +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void LibVarDeAllocAll(void) +{ + libvar_t *v; + + for (v = libvarlist; v; v = libvarlist) + { + libvarlist = libvarlist->next; + LibVarDeAlloc(v); + } //end for + libvarlist = NULL; +} //end of the function LibVarDeAllocAll +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +libvar_t *LibVarGet(char *var_name) +{ + libvar_t *v; + + for (v = libvarlist; v; v = v->next) + { + if (!Q_stricmp(v->name, var_name)) + { + return v; + } //end if + } //end for + return NULL; +} //end of the function LibVarGet +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *LibVarGetString(char *var_name) +{ + libvar_t *v; + + v = LibVarGet(var_name); + if (v) + { + return v->string; + } //end if + else + { + return ""; + } //end else +} //end of the function LibVarGetString +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float LibVarGetValue(char *var_name) +{ + libvar_t *v; + + v = LibVarGet(var_name); + if (v) + { + return v->value; + } //end if + else + { + return 0; + } //end else +} //end of the function LibVarGetValue +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +libvar_t *LibVar(char *var_name, char *value) +{ + libvar_t *v; + v = LibVarGet(var_name); + if (v) return v; + //create new variable + v = LibVarAlloc(var_name); + //variable string + v->string = (char *) GetMemory(strlen(value) + 1); + strcpy(v->string, value); + //the value + v->value = LibVarStringValue(v->string); + //variable is modified + v->modified = qtrue; + // + return v; +} //end of the function LibVar +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *LibVarString(char *var_name, char *value) +{ + libvar_t *v; + + v = LibVar(var_name, value); + return v->string; +} //end of the function LibVarString +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float LibVarValue(char *var_name, char *value) +{ + libvar_t *v; + + v = LibVar(var_name, value); + return v->value; +} //end of the function LibVarValue +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void LibVarSet(char *var_name, char *value) +{ + libvar_t *v; + + v = LibVarGet(var_name); + if (v) + { + FreeMemory(v->string); + } //end if + else + { + v = LibVarAlloc(var_name); + } //end else + //variable string + v->string = (char *) GetMemory(strlen(value) + 1); + strcpy(v->string, value); + //the value + v->value = LibVarStringValue(v->string); + //variable is modified + v->modified = qtrue; +} //end of the function LibVarSet +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean LibVarChanged(char *var_name) +{ + libvar_t *v; + + v = LibVarGet(var_name); + if (v) + { + return v->modified; + } //end if + else + { + return qfalse; + } //end else +} //end of the function LibVarChanged +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void LibVarSetNotModified(char *var_name) +{ + libvar_t *v; + + v = LibVarGet(var_name); + if (v) + { + v->modified = qfalse; + } //end if +} //end of the function LibVarSetNotModified diff --git a/code/botlib/l_libvar.h b/code/botlib/l_libvar.h index 79fc65e..fef4303 100755 --- a/code/botlib/l_libvar.h +++ b/code/botlib/l_libvar.h @@ -1,63 +1,63 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: l_libvar.h - * - * desc: botlib vars - * - * $Archive: /source/code/botlib/l_libvar.h $ - * - *****************************************************************************/ - -//library variable -typedef struct libvar_s -{ - char *name; - char *string; - int flags; - qboolean modified; // set each time the cvar is changed - float value; - struct libvar_s *next; -} libvar_t; - -//removes all library variables -void LibVarDeAllocAll(void); -//gets the library variable with the given name -libvar_t *LibVarGet(char *var_name); -//gets the string of the library variable with the given name -char *LibVarGetString(char *var_name); -//gets the value of the library variable with the given name -float LibVarGetValue(char *var_name); -//creates the library variable if not existing already and returns it -libvar_t *LibVar(char *var_name, char *value); -//creates the library variable if not existing already and returns the value -float LibVarValue(char *var_name, char *value); -//creates the library variable if not existing already and returns the value string -char *LibVarString(char *var_name, char *value); -//sets the library variable -void LibVarSet(char *var_name, char *value); -//returns true if the library variable has been modified -qboolean LibVarChanged(char *var_name); -//sets the library variable to unmodified -void LibVarSetNotModified(char *var_name); - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: l_libvar.h + * + * desc: botlib vars + * + * $Archive: /source/code/botlib/l_libvar.h $ + * + *****************************************************************************/ + +//library variable +typedef struct libvar_s +{ + char *name; + char *string; + int flags; + qboolean modified; // set each time the cvar is changed + float value; + struct libvar_s *next; +} libvar_t; + +//removes all library variables +void LibVarDeAllocAll(void); +//gets the library variable with the given name +libvar_t *LibVarGet(char *var_name); +//gets the string of the library variable with the given name +char *LibVarGetString(char *var_name); +//gets the value of the library variable with the given name +float LibVarGetValue(char *var_name); +//creates the library variable if not existing already and returns it +libvar_t *LibVar(char *var_name, char *value); +//creates the library variable if not existing already and returns the value +float LibVarValue(char *var_name, char *value); +//creates the library variable if not existing already and returns the value string +char *LibVarString(char *var_name, char *value); +//sets the library variable +void LibVarSet(char *var_name, char *value); +//returns true if the library variable has been modified +qboolean LibVarChanged(char *var_name); +//sets the library variable to unmodified +void LibVarSetNotModified(char *var_name); + diff --git a/code/botlib/l_log.c b/code/botlib/l_log.c index d532c8c..7b1a346 100755 --- a/code/botlib/l_log.c +++ b/code/botlib/l_log.c @@ -1,169 +1,169 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: l_log.c - * - * desc: log file - * - * $Archive: /MissionPack/CODE/botlib/l_log.c $ - * - *****************************************************************************/ - -#include -#include -#include - -#include "../game/q_shared.h" -#include "../game/botlib.h" -#include "be_interface.h" //for botimport.Print -#include "l_libvar.h" - -#define MAX_LOGFILENAMESIZE 1024 - -typedef struct logfile_s -{ - char filename[MAX_LOGFILENAMESIZE]; - FILE *fp; - int numwrites; -} logfile_t; - -static logfile_t logfile; - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Log_Open(char *filename) -{ - if (!LibVarValue("log", "0")) return; - if (!filename || !strlen(filename)) - { - botimport.Print(PRT_MESSAGE, "openlog \n"); - return; - } //end if - if (logfile.fp) - { - botimport.Print(PRT_ERROR, "log file %s is already opened\n", logfile.filename); - return; - } //end if - logfile.fp = fopen(filename, "wb"); - if (!logfile.fp) - { - botimport.Print(PRT_ERROR, "can't open the log file %s\n", filename); - return; - } //end if - strncpy(logfile.filename, filename, MAX_LOGFILENAMESIZE); - botimport.Print(PRT_MESSAGE, "Opened log %s\n", logfile.filename); -} //end of the function Log_Create -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Log_Close(void) -{ - if (!logfile.fp) return; - if (fclose(logfile.fp)) - { - botimport.Print(PRT_ERROR, "can't close log file %s\n", logfile.filename); - return; - } //end if - logfile.fp = NULL; - botimport.Print(PRT_MESSAGE, "Closed log %s\n", logfile.filename); -} //end of the function Log_Close -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Log_Shutdown(void) -{ - if (logfile.fp) Log_Close(); -} //end of the function Log_Shutdown -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void QDECL Log_Write(char *fmt, ...) -{ - va_list ap; - - if (!logfile.fp) return; - va_start(ap, fmt); - vfprintf(logfile.fp, fmt, ap); - va_end(ap); - //fprintf(logfile.fp, "\r\n"); - fflush(logfile.fp); -} //end of the function Log_Write -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void QDECL Log_WriteTimeStamped(char *fmt, ...) -{ - va_list ap; - - if (!logfile.fp) return; - fprintf(logfile.fp, "%d %02d:%02d:%02d:%02d ", - logfile.numwrites, - (int) (botlibglobals.time / 60 / 60), - (int) (botlibglobals.time / 60), - (int) (botlibglobals.time), - (int) ((int) (botlibglobals.time * 100)) - - ((int) botlibglobals.time) * 100); - va_start(ap, fmt); - vfprintf(logfile.fp, fmt, ap); - va_end(ap); - fprintf(logfile.fp, "\r\n"); - logfile.numwrites++; - fflush(logfile.fp); -} //end of the function Log_Write -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -FILE *Log_FilePointer(void) -{ - return logfile.fp; -} //end of the function Log_FilePointer -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Log_Flush(void) -{ - if (logfile.fp) fflush(logfile.fp); -} //end of the function Log_Flush - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: l_log.c + * + * desc: log file + * + * $Archive: /MissionPack/CODE/botlib/l_log.c $ + * + *****************************************************************************/ + +#include +#include +#include + +#include "../game/q_shared.h" +#include "../game/botlib.h" +#include "be_interface.h" //for botimport.Print +#include "l_libvar.h" + +#define MAX_LOGFILENAMESIZE 1024 + +typedef struct logfile_s +{ + char filename[MAX_LOGFILENAMESIZE]; + FILE *fp; + int numwrites; +} logfile_t; + +static logfile_t logfile; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Log_Open(char *filename) +{ + if (!LibVarValue("log", "0")) return; + if (!filename || !strlen(filename)) + { + botimport.Print(PRT_MESSAGE, "openlog \n"); + return; + } //end if + if (logfile.fp) + { + botimport.Print(PRT_ERROR, "log file %s is already opened\n", logfile.filename); + return; + } //end if + logfile.fp = fopen(filename, "wb"); + if (!logfile.fp) + { + botimport.Print(PRT_ERROR, "can't open the log file %s\n", filename); + return; + } //end if + strncpy(logfile.filename, filename, MAX_LOGFILENAMESIZE); + botimport.Print(PRT_MESSAGE, "Opened log %s\n", logfile.filename); +} //end of the function Log_Create +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Log_Close(void) +{ + if (!logfile.fp) return; + if (fclose(logfile.fp)) + { + botimport.Print(PRT_ERROR, "can't close log file %s\n", logfile.filename); + return; + } //end if + logfile.fp = NULL; + botimport.Print(PRT_MESSAGE, "Closed log %s\n", logfile.filename); +} //end of the function Log_Close +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Log_Shutdown(void) +{ + if (logfile.fp) Log_Close(); +} //end of the function Log_Shutdown +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void QDECL Log_Write(char *fmt, ...) +{ + va_list ap; + + if (!logfile.fp) return; + va_start(ap, fmt); + vfprintf(logfile.fp, fmt, ap); + va_end(ap); + //fprintf(logfile.fp, "\r\n"); + fflush(logfile.fp); +} //end of the function Log_Write +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void QDECL Log_WriteTimeStamped(char *fmt, ...) +{ + va_list ap; + + if (!logfile.fp) return; + fprintf(logfile.fp, "%d %02d:%02d:%02d:%02d ", + logfile.numwrites, + (int) (botlibglobals.time / 60 / 60), + (int) (botlibglobals.time / 60), + (int) (botlibglobals.time), + (int) ((int) (botlibglobals.time * 100)) - + ((int) botlibglobals.time) * 100); + va_start(ap, fmt); + vfprintf(logfile.fp, fmt, ap); + va_end(ap); + fprintf(logfile.fp, "\r\n"); + logfile.numwrites++; + fflush(logfile.fp); +} //end of the function Log_Write +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +FILE *Log_FilePointer(void) +{ + return logfile.fp; +} //end of the function Log_FilePointer +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Log_Flush(void) +{ + if (logfile.fp) fflush(logfile.fp); +} //end of the function Log_Flush + diff --git a/code/botlib/l_log.h b/code/botlib/l_log.h index 2d07f9e..d7b143e 100755 --- a/code/botlib/l_log.h +++ b/code/botlib/l_log.h @@ -1,46 +1,46 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: l_log.h - * - * desc: log file - * - * $Archive: /source/code/botlib/l_log.h $ - * - *****************************************************************************/ - -//open a log file -void Log_Open(char *filename); -//close the current log file -void Log_Close(void); -//close log file if present -void Log_Shutdown(void); -//write to the current opened log file -void QDECL Log_Write(char *fmt, ...); -//write to the current opened log file with a time stamp -void QDECL Log_WriteTimeStamped(char *fmt, ...); -//returns a pointer to the log file -FILE *Log_FilePointer(void); -//flush log file -void Log_Flush(void); - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: l_log.h + * + * desc: log file + * + * $Archive: /source/code/botlib/l_log.h $ + * + *****************************************************************************/ + +//open a log file +void Log_Open(char *filename); +//close the current log file +void Log_Close(void); +//close log file if present +void Log_Shutdown(void); +//write to the current opened log file +void QDECL Log_Write(char *fmt, ...); +//write to the current opened log file with a time stamp +void QDECL Log_WriteTimeStamped(char *fmt, ...); +//returns a pointer to the log file +FILE *Log_FilePointer(void); +//flush log file +void Log_Flush(void); + diff --git a/code/botlib/l_memory.c b/code/botlib/l_memory.c index 68928ab..7bc12f6 100755 --- a/code/botlib/l_memory.c +++ b/code/botlib/l_memory.c @@ -1,463 +1,463 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: l_memory.c - * - * desc: memory allocation - * - * $Archive: /MissionPack/code/botlib/l_memory.c $ - * - *****************************************************************************/ - -#include "../game/q_shared.h" -#include "../game/botlib.h" -#include "l_log.h" -#include "be_interface.h" - -//#define MEMDEBUG -//#define MEMORYMANEGER - -#define MEM_ID 0x12345678l -#define HUNK_ID 0x87654321l - -int allocatedmemory; -int totalmemorysize; -int numblocks; - -#ifdef MEMORYMANEGER - -typedef struct memoryblock_s -{ - unsigned long int id; - void *ptr; - int size; -#ifdef MEMDEBUG - char *label; - char *file; - int line; -#endif //MEMDEBUG - struct memoryblock_s *prev, *next; -} memoryblock_t; - -memoryblock_t *memory; - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void LinkMemoryBlock(memoryblock_t *block) -{ - block->prev = NULL; - block->next = memory; - if (memory) memory->prev = block; - memory = block; -} //end of the function LinkMemoryBlock -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void UnlinkMemoryBlock(memoryblock_t *block) -{ - if (block->prev) block->prev->next = block->next; - else memory = block->next; - if (block->next) block->next->prev = block->prev; -} //end of the function UnlinkMemoryBlock -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -#ifdef MEMDEBUG -void *GetMemoryDebug(unsigned long size, char *label, char *file, int line) -#else -void *GetMemory(unsigned long size) -#endif //MEMDEBUG -{ - void *ptr; - memoryblock_t *block; - assert(botimport.GetMemory); // bk001129 - was NULL'ed - ptr = botimport.GetMemory(size + sizeof(memoryblock_t)); - block = (memoryblock_t *) ptr; - block->id = MEM_ID; - block->ptr = (char *) ptr + sizeof(memoryblock_t); - block->size = size + sizeof(memoryblock_t); -#ifdef MEMDEBUG - block->label = label; - block->file = file; - block->line = line; -#endif //MEMDEBUG - LinkMemoryBlock(block); - allocatedmemory += block->size; - totalmemorysize += block->size + sizeof(memoryblock_t); - numblocks++; - return block->ptr; -} //end of the function GetMemoryDebug -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -#ifdef MEMDEBUG -void *GetClearedMemoryDebug(unsigned long size, char *label, char *file, int line) -#else -void *GetClearedMemory(unsigned long size) -#endif //MEMDEBUG -{ - void *ptr; -#ifdef MEMDEBUG - ptr = GetMemoryDebug(size, label, file, line); -#else - ptr = GetMemory(size); -#endif //MEMDEBUG - Com_Memset(ptr, 0, size); - return ptr; -} //end of the function GetClearedMemory -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -#ifdef MEMDEBUG -void *GetHunkMemoryDebug(unsigned long size, char *label, char *file, int line) -#else -void *GetHunkMemory(unsigned long size) -#endif //MEMDEBUG -{ - void *ptr; - memoryblock_t *block; - - ptr = botimport.HunkAlloc(size + sizeof(memoryblock_t)); - block = (memoryblock_t *) ptr; - block->id = HUNK_ID; - block->ptr = (char *) ptr + sizeof(memoryblock_t); - block->size = size + sizeof(memoryblock_t); -#ifdef MEMDEBUG - block->label = label; - block->file = file; - block->line = line; -#endif //MEMDEBUG - LinkMemoryBlock(block); - allocatedmemory += block->size; - totalmemorysize += block->size + sizeof(memoryblock_t); - numblocks++; - return block->ptr; -} //end of the function GetHunkMemoryDebug -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -#ifdef MEMDEBUG -void *GetClearedHunkMemoryDebug(unsigned long size, char *label, char *file, int line) -#else -void *GetClearedHunkMemory(unsigned long size) -#endif //MEMDEBUG -{ - void *ptr; -#ifdef MEMDEBUG - ptr = GetHunkMemoryDebug(size, label, file, line); -#else - ptr = GetHunkMemory(size); -#endif //MEMDEBUG - Com_Memset(ptr, 0, size); - return ptr; -} //end of the function GetClearedHunkMemory -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -memoryblock_t *BlockFromPointer(void *ptr, char *str) -{ - memoryblock_t *block; - - if (!ptr) - { -#ifdef MEMDEBUG - //char *crash = (char *) NULL; - //crash[0] = 1; - botimport.Print(PRT_FATAL, "%s: NULL pointer\n", str); -#endif // MEMDEBUG - return NULL; - } //end if - block = (memoryblock_t *) ((char *) ptr - sizeof(memoryblock_t)); - if (block->id != MEM_ID && block->id != HUNK_ID) - { - botimport.Print(PRT_FATAL, "%s: invalid memory block\n", str); - return NULL; - } //end if - if (block->ptr != ptr) - { - botimport.Print(PRT_FATAL, "%s: memory block pointer invalid\n", str); - return NULL; - } //end if - return block; -} //end of the function BlockFromPointer -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void FreeMemory(void *ptr) -{ - memoryblock_t *block; - - block = BlockFromPointer(ptr, "FreeMemory"); - if (!block) return; - UnlinkMemoryBlock(block); - allocatedmemory -= block->size; - totalmemorysize -= block->size + sizeof(memoryblock_t); - numblocks--; - // - if (block->id == MEM_ID) - { - botimport.FreeMemory(block); - } //end if -} //end of the function FreeMemory -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AvailableMemory(void) -{ - return botimport.AvailableMemory(); -} //end of the function AvailableMemory -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int MemoryByteSize(void *ptr) -{ - memoryblock_t *block; - - block = BlockFromPointer(ptr, "MemoryByteSize"); - if (!block) return 0; - return block->size; -} //end of the function MemoryByteSize -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void PrintUsedMemorySize(void) -{ - botimport.Print(PRT_MESSAGE, "total allocated memory: %d KB\n", allocatedmemory >> 10); - botimport.Print(PRT_MESSAGE, "total botlib memory: %d KB\n", totalmemorysize >> 10); - botimport.Print(PRT_MESSAGE, "total memory blocks: %d\n", numblocks); -} //end of the function PrintUsedMemorySize -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void PrintMemoryLabels(void) -{ - memoryblock_t *block; - int i; - - PrintUsedMemorySize(); - i = 0; - Log_Write("============= Botlib memory log ==============\r\n"); - Log_Write("\r\n"); - for (block = memory; block; block = block->next) - { -#ifdef MEMDEBUG - if (block->id == HUNK_ID) - { - Log_Write("%6d, hunk %p, %8d: %24s line %6d: %s\r\n", i, block->ptr, block->size, block->file, block->line, block->label); - } //end if - else - { - Log_Write("%6d, %p, %8d: %24s line %6d: %s\r\n", i, block->ptr, block->size, block->file, block->line, block->label); - } //end else -#endif //MEMDEBUG - i++; - } //end for -} //end of the function PrintMemoryLabels -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void DumpMemory(void) -{ - memoryblock_t *block; - - for (block = memory; block; block = memory) - { - FreeMemory(block->ptr); - } //end for - totalmemorysize = 0; - allocatedmemory = 0; -} //end of the function DumpMemory - -#else - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -#ifdef MEMDEBUG -void *GetMemoryDebug(unsigned long size, char *label, char *file, int line) -#else -void *GetMemory(unsigned long size) -#endif //MEMDEBUG -{ - void *ptr; - unsigned long int *memid; - - ptr = botimport.GetMemory(size + sizeof(unsigned long int)); - if (!ptr) return NULL; - memid = (unsigned long int *) ptr; - *memid = MEM_ID; - return (unsigned long int *) ((char *) ptr + sizeof(unsigned long int)); -} //end of the function GetMemory -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -#ifdef MEMDEBUG -void *GetClearedMemoryDebug(unsigned long size, char *label, char *file, int line) -#else -void *GetClearedMemory(unsigned long size) -#endif //MEMDEBUG -{ - void *ptr; -#ifdef MEMDEBUG - ptr = GetMemoryDebug(size, label, file, line); -#else - ptr = GetMemory(size); -#endif //MEMDEBUG - Com_Memset(ptr, 0, size); - return ptr; -} //end of the function GetClearedMemory -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -#ifdef MEMDEBUG -void *GetHunkMemoryDebug(unsigned long size, char *label, char *file, int line) -#else -void *GetHunkMemory(unsigned long size) -#endif //MEMDEBUG -{ - void *ptr; - unsigned long int *memid; - - ptr = botimport.HunkAlloc(size + sizeof(unsigned long int)); - if (!ptr) return NULL; - memid = (unsigned long int *) ptr; - *memid = HUNK_ID; - return (unsigned long int *) ((char *) ptr + sizeof(unsigned long int)); -} //end of the function GetHunkMemory -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -#ifdef MEMDEBUG -void *GetClearedHunkMemoryDebug(unsigned long size, char *label, char *file, int line) -#else -void *GetClearedHunkMemory(unsigned long size) -#endif //MEMDEBUG -{ - void *ptr; -#ifdef MEMDEBUG - ptr = GetHunkMemoryDebug(size, label, file, line); -#else - ptr = GetHunkMemory(size); -#endif //MEMDEBUG - Com_Memset(ptr, 0, size); - return ptr; -} //end of the function GetClearedHunkMemory -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void FreeMemory(void *ptr) -{ - unsigned long int *memid; - - memid = (unsigned long int *) ((char *) ptr - sizeof(unsigned long int)); - - if (*memid == MEM_ID) - { - botimport.FreeMemory(memid); - } //end if -} //end of the function FreeMemory -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AvailableMemory(void) -{ - return botimport.AvailableMemory(); -} //end of the function AvailableMemory -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void PrintUsedMemorySize(void) -{ -} //end of the function PrintUsedMemorySize -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void PrintMemoryLabels(void) -{ -} //end of the function PrintMemoryLabels - -#endif +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: l_memory.c + * + * desc: memory allocation + * + * $Archive: /MissionPack/code/botlib/l_memory.c $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "../game/botlib.h" +#include "l_log.h" +#include "be_interface.h" + +//#define MEMDEBUG +//#define MEMORYMANEGER + +#define MEM_ID 0x12345678l +#define HUNK_ID 0x87654321l + +int allocatedmemory; +int totalmemorysize; +int numblocks; + +#ifdef MEMORYMANEGER + +typedef struct memoryblock_s +{ + unsigned long int id; + void *ptr; + int size; +#ifdef MEMDEBUG + char *label; + char *file; + int line; +#endif //MEMDEBUG + struct memoryblock_s *prev, *next; +} memoryblock_t; + +memoryblock_t *memory; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void LinkMemoryBlock(memoryblock_t *block) +{ + block->prev = NULL; + block->next = memory; + if (memory) memory->prev = block; + memory = block; +} //end of the function LinkMemoryBlock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void UnlinkMemoryBlock(memoryblock_t *block) +{ + if (block->prev) block->prev->next = block->next; + else memory = block->next; + if (block->next) block->next->prev = block->prev; +} //end of the function UnlinkMemoryBlock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetMemoryDebug(unsigned long size, char *label, char *file, int line) +#else +void *GetMemory(unsigned long size) +#endif //MEMDEBUG +{ + void *ptr; + memoryblock_t *block; + assert(botimport.GetMemory); // bk001129 - was NULL'ed + ptr = botimport.GetMemory(size + sizeof(memoryblock_t)); + block = (memoryblock_t *) ptr; + block->id = MEM_ID; + block->ptr = (char *) ptr + sizeof(memoryblock_t); + block->size = size + sizeof(memoryblock_t); +#ifdef MEMDEBUG + block->label = label; + block->file = file; + block->line = line; +#endif //MEMDEBUG + LinkMemoryBlock(block); + allocatedmemory += block->size; + totalmemorysize += block->size + sizeof(memoryblock_t); + numblocks++; + return block->ptr; +} //end of the function GetMemoryDebug +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetClearedMemoryDebug(unsigned long size, char *label, char *file, int line) +#else +void *GetClearedMemory(unsigned long size) +#endif //MEMDEBUG +{ + void *ptr; +#ifdef MEMDEBUG + ptr = GetMemoryDebug(size, label, file, line); +#else + ptr = GetMemory(size); +#endif //MEMDEBUG + Com_Memset(ptr, 0, size); + return ptr; +} //end of the function GetClearedMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetHunkMemoryDebug(unsigned long size, char *label, char *file, int line) +#else +void *GetHunkMemory(unsigned long size) +#endif //MEMDEBUG +{ + void *ptr; + memoryblock_t *block; + + ptr = botimport.HunkAlloc(size + sizeof(memoryblock_t)); + block = (memoryblock_t *) ptr; + block->id = HUNK_ID; + block->ptr = (char *) ptr + sizeof(memoryblock_t); + block->size = size + sizeof(memoryblock_t); +#ifdef MEMDEBUG + block->label = label; + block->file = file; + block->line = line; +#endif //MEMDEBUG + LinkMemoryBlock(block); + allocatedmemory += block->size; + totalmemorysize += block->size + sizeof(memoryblock_t); + numblocks++; + return block->ptr; +} //end of the function GetHunkMemoryDebug +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetClearedHunkMemoryDebug(unsigned long size, char *label, char *file, int line) +#else +void *GetClearedHunkMemory(unsigned long size) +#endif //MEMDEBUG +{ + void *ptr; +#ifdef MEMDEBUG + ptr = GetHunkMemoryDebug(size, label, file, line); +#else + ptr = GetHunkMemory(size); +#endif //MEMDEBUG + Com_Memset(ptr, 0, size); + return ptr; +} //end of the function GetClearedHunkMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +memoryblock_t *BlockFromPointer(void *ptr, char *str) +{ + memoryblock_t *block; + + if (!ptr) + { +#ifdef MEMDEBUG + //char *crash = (char *) NULL; + //crash[0] = 1; + botimport.Print(PRT_FATAL, "%s: NULL pointer\n", str); +#endif // MEMDEBUG + return NULL; + } //end if + block = (memoryblock_t *) ((char *) ptr - sizeof(memoryblock_t)); + if (block->id != MEM_ID && block->id != HUNK_ID) + { + botimport.Print(PRT_FATAL, "%s: invalid memory block\n", str); + return NULL; + } //end if + if (block->ptr != ptr) + { + botimport.Print(PRT_FATAL, "%s: memory block pointer invalid\n", str); + return NULL; + } //end if + return block; +} //end of the function BlockFromPointer +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeMemory(void *ptr) +{ + memoryblock_t *block; + + block = BlockFromPointer(ptr, "FreeMemory"); + if (!block) return; + UnlinkMemoryBlock(block); + allocatedmemory -= block->size; + totalmemorysize -= block->size + sizeof(memoryblock_t); + numblocks--; + // + if (block->id == MEM_ID) + { + botimport.FreeMemory(block); + } //end if +} //end of the function FreeMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AvailableMemory(void) +{ + return botimport.AvailableMemory(); +} //end of the function AvailableMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int MemoryByteSize(void *ptr) +{ + memoryblock_t *block; + + block = BlockFromPointer(ptr, "MemoryByteSize"); + if (!block) return 0; + return block->size; +} //end of the function MemoryByteSize +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void PrintUsedMemorySize(void) +{ + botimport.Print(PRT_MESSAGE, "total allocated memory: %d KB\n", allocatedmemory >> 10); + botimport.Print(PRT_MESSAGE, "total botlib memory: %d KB\n", totalmemorysize >> 10); + botimport.Print(PRT_MESSAGE, "total memory blocks: %d\n", numblocks); +} //end of the function PrintUsedMemorySize +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void PrintMemoryLabels(void) +{ + memoryblock_t *block; + int i; + + PrintUsedMemorySize(); + i = 0; + Log_Write("============= Botlib memory log ==============\r\n"); + Log_Write("\r\n"); + for (block = memory; block; block = block->next) + { +#ifdef MEMDEBUG + if (block->id == HUNK_ID) + { + Log_Write("%6d, hunk %p, %8d: %24s line %6d: %s\r\n", i, block->ptr, block->size, block->file, block->line, block->label); + } //end if + else + { + Log_Write("%6d, %p, %8d: %24s line %6d: %s\r\n", i, block->ptr, block->size, block->file, block->line, block->label); + } //end else +#endif //MEMDEBUG + i++; + } //end for +} //end of the function PrintMemoryLabels +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void DumpMemory(void) +{ + memoryblock_t *block; + + for (block = memory; block; block = memory) + { + FreeMemory(block->ptr); + } //end for + totalmemorysize = 0; + allocatedmemory = 0; +} //end of the function DumpMemory + +#else + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetMemoryDebug(unsigned long size, char *label, char *file, int line) +#else +void *GetMemory(unsigned long size) +#endif //MEMDEBUG +{ + void *ptr; + unsigned long int *memid; + + ptr = botimport.GetMemory(size + sizeof(unsigned long int)); + if (!ptr) return NULL; + memid = (unsigned long int *) ptr; + *memid = MEM_ID; + return (unsigned long int *) ((char *) ptr + sizeof(unsigned long int)); +} //end of the function GetMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetClearedMemoryDebug(unsigned long size, char *label, char *file, int line) +#else +void *GetClearedMemory(unsigned long size) +#endif //MEMDEBUG +{ + void *ptr; +#ifdef MEMDEBUG + ptr = GetMemoryDebug(size, label, file, line); +#else + ptr = GetMemory(size); +#endif //MEMDEBUG + Com_Memset(ptr, 0, size); + return ptr; +} //end of the function GetClearedMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetHunkMemoryDebug(unsigned long size, char *label, char *file, int line) +#else +void *GetHunkMemory(unsigned long size) +#endif //MEMDEBUG +{ + void *ptr; + unsigned long int *memid; + + ptr = botimport.HunkAlloc(size + sizeof(unsigned long int)); + if (!ptr) return NULL; + memid = (unsigned long int *) ptr; + *memid = HUNK_ID; + return (unsigned long int *) ((char *) ptr + sizeof(unsigned long int)); +} //end of the function GetHunkMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetClearedHunkMemoryDebug(unsigned long size, char *label, char *file, int line) +#else +void *GetClearedHunkMemory(unsigned long size) +#endif //MEMDEBUG +{ + void *ptr; +#ifdef MEMDEBUG + ptr = GetHunkMemoryDebug(size, label, file, line); +#else + ptr = GetHunkMemory(size); +#endif //MEMDEBUG + Com_Memset(ptr, 0, size); + return ptr; +} //end of the function GetClearedHunkMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeMemory(void *ptr) +{ + unsigned long int *memid; + + memid = (unsigned long int *) ((char *) ptr - sizeof(unsigned long int)); + + if (*memid == MEM_ID) + { + botimport.FreeMemory(memid); + } //end if +} //end of the function FreeMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AvailableMemory(void) +{ + return botimport.AvailableMemory(); +} //end of the function AvailableMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void PrintUsedMemorySize(void) +{ +} //end of the function PrintUsedMemorySize +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void PrintMemoryLabels(void) +{ +} //end of the function PrintMemoryLabels + +#endif diff --git a/code/botlib/l_memory.h b/code/botlib/l_memory.h index d0cfbb4..353f351 100755 --- a/code/botlib/l_memory.h +++ b/code/botlib/l_memory.h @@ -1,76 +1,76 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: l_memory.h - * - * desc: memory management - * - * $Archive: /source/code/botlib/l_memory.h $ - * - *****************************************************************************/ - -//#define MEMDEBUG - -#ifdef MEMDEBUG -#define GetMemory(size) GetMemoryDebug(size, #size, __FILE__, __LINE__); -#define GetClearedMemory(size) GetClearedMemoryDebug(size, #size, __FILE__, __LINE__); -//allocate a memory block of the given size -void *GetMemoryDebug(unsigned long size, char *label, char *file, int line); -//allocate a memory block of the given size and clear it -void *GetClearedMemoryDebug(unsigned long size, char *label, char *file, int line); -// -#define GetHunkMemory(size) GetHunkMemoryDebug(size, #size, __FILE__, __LINE__); -#define GetClearedHunkMemory(size) GetClearedHunkMemoryDebug(size, #size, __FILE__, __LINE__); -//allocate a memory block of the given size -void *GetHunkMemoryDebug(unsigned long size, char *label, char *file, int line); -//allocate a memory block of the given size and clear it -void *GetClearedHunkMemoryDebug(unsigned long size, char *label, char *file, int line); -#else -//allocate a memory block of the given size -void *GetMemory(unsigned long size); -//allocate a memory block of the given size and clear it -void *GetClearedMemory(unsigned long size); -// -#ifdef BSPC -#define GetHunkMemory GetMemory -#define GetClearedHunkMemory GetClearedMemory -#else -//allocate a memory block of the given size -void *GetHunkMemory(unsigned long size); -//allocate a memory block of the given size and clear it -void *GetClearedHunkMemory(unsigned long size); -#endif -#endif - -//free the given memory block -void FreeMemory(void *ptr); -//returns the amount available memory -int AvailableMemory(void); -//prints the total used memory size -void PrintUsedMemorySize(void); -//print all memory blocks with label -void PrintMemoryLabels(void); -//returns the size of the memory block in bytes -int MemoryByteSize(void *ptr); -//free all allocated memory -void DumpMemory(void); +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: l_memory.h + * + * desc: memory management + * + * $Archive: /source/code/botlib/l_memory.h $ + * + *****************************************************************************/ + +//#define MEMDEBUG + +#ifdef MEMDEBUG +#define GetMemory(size) GetMemoryDebug(size, #size, __FILE__, __LINE__); +#define GetClearedMemory(size) GetClearedMemoryDebug(size, #size, __FILE__, __LINE__); +//allocate a memory block of the given size +void *GetMemoryDebug(unsigned long size, char *label, char *file, int line); +//allocate a memory block of the given size and clear it +void *GetClearedMemoryDebug(unsigned long size, char *label, char *file, int line); +// +#define GetHunkMemory(size) GetHunkMemoryDebug(size, #size, __FILE__, __LINE__); +#define GetClearedHunkMemory(size) GetClearedHunkMemoryDebug(size, #size, __FILE__, __LINE__); +//allocate a memory block of the given size +void *GetHunkMemoryDebug(unsigned long size, char *label, char *file, int line); +//allocate a memory block of the given size and clear it +void *GetClearedHunkMemoryDebug(unsigned long size, char *label, char *file, int line); +#else +//allocate a memory block of the given size +void *GetMemory(unsigned long size); +//allocate a memory block of the given size and clear it +void *GetClearedMemory(unsigned long size); +// +#ifdef BSPC +#define GetHunkMemory GetMemory +#define GetClearedHunkMemory GetClearedMemory +#else +//allocate a memory block of the given size +void *GetHunkMemory(unsigned long size); +//allocate a memory block of the given size and clear it +void *GetClearedHunkMemory(unsigned long size); +#endif +#endif + +//free the given memory block +void FreeMemory(void *ptr); +//returns the amount available memory +int AvailableMemory(void); +//prints the total used memory size +void PrintUsedMemorySize(void); +//print all memory blocks with label +void PrintMemoryLabels(void); +//returns the size of the memory block in bytes +int MemoryByteSize(void *ptr); +//free all allocated memory +void DumpMemory(void); diff --git a/code/botlib/l_precomp.c b/code/botlib/l_precomp.c index 4fa708b..ae82981 100755 --- a/code/botlib/l_precomp.c +++ b/code/botlib/l_precomp.c @@ -1,3228 +1,3228 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -/***************************************************************************** - * name: l_precomp.c - * - * desc: pre compiler - * - * $Archive: /MissionPack/code/botlib/l_precomp.c $ - * - *****************************************************************************/ - -//Notes: fix: PC_StringizeTokens - -//#define SCREWUP -//#define BOTLIB -//#define QUAKE -//#define QUAKEC -//#define MEQCC - -#ifdef SCREWUP -#include -#include -#include -#include -#include -#include -#include "l_memory.h" -#include "l_script.h" -#include "l_precomp.h" - -typedef enum {qfalse, qtrue} qboolean; -#endif //SCREWUP - -#ifdef BOTLIB -#include "../game/q_shared.h" -#include "../game/botlib.h" -#include "be_interface.h" -#include "l_memory.h" -#include "l_script.h" -#include "l_precomp.h" -#include "l_log.h" -#endif //BOTLIB - -#ifdef MEQCC -#include "qcc.h" -#include "time.h" //time & ctime -#include "math.h" //fabs -#include "l_memory.h" -#include "l_script.h" -#include "l_precomp.h" -#include "l_log.h" - -#define qtrue true -#define qfalse false -#endif //MEQCC - -#ifdef BSPC -//include files for usage in the BSP Converter -#include "../bspc/qbsp.h" -#include "../bspc/l_log.h" -#include "../bspc/l_mem.h" -#include "l_precomp.h" - -#define qtrue true -#define qfalse false -#define Q_stricmp stricmp - -#endif //BSPC - -#if defined(QUAKE) && !defined(BSPC) -#include "l_utils.h" -#endif //QUAKE - -//#define DEBUG_EVAL - -#define MAX_DEFINEPARMS 128 - -#define DEFINEHASHING 1 - -//directive name with parse function -typedef struct directive_s -{ - char *name; - int (*func)(source_t *source); -} directive_t; - -#define DEFINEHASHSIZE 1024 - -#define TOKEN_HEAP_SIZE 4096 - -int numtokens; -/* -int tokenheapinitialized; //true when the token heap is initialized -token_t token_heap[TOKEN_HEAP_SIZE]; //heap with tokens -token_t *freetokens; //free tokens from the heap -*/ - -//list with global defines added to every source loaded -define_t *globaldefines; - -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -void QDECL SourceError(source_t *source, char *str, ...) -{ - char text[1024]; - va_list ap; - - va_start(ap, str); - vsprintf(text, str, ap); - va_end(ap); -#ifdef BOTLIB - botimport.Print(PRT_ERROR, "file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text); -#endif //BOTLIB -#ifdef MEQCC - printf("error: file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text); -#endif //MEQCC -#ifdef BSPC - Log_Print("error: file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text); -#endif //BSPC -} //end of the function SourceError -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void QDECL SourceWarning(source_t *source, char *str, ...) -{ - char text[1024]; - va_list ap; - - va_start(ap, str); - vsprintf(text, str, ap); - va_end(ap); -#ifdef BOTLIB - botimport.Print(PRT_WARNING, "file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text); -#endif //BOTLIB -#ifdef MEQCC - printf("warning: file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text); -#endif //MEQCC -#ifdef BSPC - Log_Print("warning: file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text); -#endif //BSPC -} //end of the function ScriptWarning -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -void PC_PushIndent(source_t *source, int type, int skip) -{ - indent_t *indent; - - indent = (indent_t *) GetMemory(sizeof(indent_t)); - indent->type = type; - indent->script = source->scriptstack; - indent->skip = (skip != 0); - source->skip += indent->skip; - indent->next = source->indentstack; - source->indentstack = indent; -} //end of the function PC_PushIndent -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -void PC_PopIndent(source_t *source, int *type, int *skip) -{ - indent_t *indent; - - *type = 0; - *skip = 0; - - indent = source->indentstack; - if (!indent) return; - - //must be an indent from the current script - if (source->indentstack->script != source->scriptstack) return; - - *type = indent->type; - *skip = indent->skip; - source->indentstack = source->indentstack->next; - source->skip -= indent->skip; - FreeMemory(indent); -} //end of the function PC_PopIndent -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -void PC_PushScript(source_t *source, script_t *script) -{ - script_t *s; - - for (s = source->scriptstack; s; s = s->next) - { - if (!Q_stricmp(s->filename, script->filename)) - { - SourceError(source, "%s recursively included", script->filename); - return; - } //end if - } //end for - //push the script on the script stack - script->next = source->scriptstack; - source->scriptstack = script; -} //end of the function PC_PushScript -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -void PC_InitTokenHeap(void) -{ - /* - int i; - - if (tokenheapinitialized) return; - freetokens = NULL; - for (i = 0; i < TOKEN_HEAP_SIZE; i++) - { - token_heap[i].next = freetokens; - freetokens = &token_heap[i]; - } //end for - tokenheapinitialized = qtrue; - */ -} //end of the function PC_InitTokenHeap -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -token_t *PC_CopyToken(token_t *token) -{ - token_t *t; - -// t = (token_t *) malloc(sizeof(token_t)); - t = (token_t *) GetMemory(sizeof(token_t)); -// t = freetokens; - if (!t) - { -#ifdef BSPC - Error("out of token space\n"); -#else - Com_Error(ERR_FATAL, "out of token space\n"); -#endif - return NULL; - } //end if -// freetokens = freetokens->next; - Com_Memcpy(t, token, sizeof(token_t)); - t->next = NULL; - numtokens++; - return t; -} //end of the function PC_CopyToken -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -void PC_FreeToken(token_t *token) -{ - //free(token); - FreeMemory(token); -// token->next = freetokens; -// freetokens = token; - numtokens--; -} //end of the function PC_FreeToken -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_ReadSourceToken(source_t *source, token_t *token) -{ - token_t *t; - script_t *script; - int type, skip; - - //if there's no token already available - while(!source->tokens) - { - //if there's a token to read from the script - if (PS_ReadToken(source->scriptstack, token)) return qtrue; - //if at the end of the script - if (EndOfScript(source->scriptstack)) - { - //remove all indents of the script - while(source->indentstack && - source->indentstack->script == source->scriptstack) - { - SourceWarning(source, "missing #endif"); - PC_PopIndent(source, &type, &skip); - } //end if - } //end if - //if this was the initial script - if (!source->scriptstack->next) return qfalse; - //remove the script and return to the last one - script = source->scriptstack; - source->scriptstack = source->scriptstack->next; - FreeScript(script); - } //end while - //copy the already available token - Com_Memcpy(token, source->tokens, sizeof(token_t)); - //free the read token - t = source->tokens; - source->tokens = source->tokens->next; - PC_FreeToken(t); - return qtrue; -} //end of the function PC_ReadSourceToken -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_UnreadSourceToken(source_t *source, token_t *token) -{ - token_t *t; - - t = PC_CopyToken(token); - t->next = source->tokens; - source->tokens = t; - return qtrue; -} //end of the function PC_UnreadSourceToken -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_ReadDefineParms(source_t *source, define_t *define, token_t **parms, int maxparms) -{ - token_t token, *t, *last; - int i, done, lastcomma, numparms, indent; - - if (!PC_ReadSourceToken(source, &token)) - { - SourceError(source, "define %s missing parms", define->name); - return qfalse; - } //end if - // - if (define->numparms > maxparms) - { - SourceError(source, "define with more than %d parameters", maxparms); - return qfalse; - } //end if - // - for (i = 0; i < define->numparms; i++) parms[i] = NULL; - //if no leading "(" - if (strcmp(token.string, "(")) - { - PC_UnreadSourceToken(source, &token); - SourceError(source, "define %s missing parms", define->name); - return qfalse; - } //end if - //read the define parameters - for (done = 0, numparms = 0, indent = 0; !done;) - { - if (numparms >= maxparms) - { - SourceError(source, "define %s with too many parms", define->name); - return qfalse; - } //end if - if (numparms >= define->numparms) - { - SourceWarning(source, "define %s has too many parms", define->name); - return qfalse; - } //end if - parms[numparms] = NULL; - lastcomma = 1; - last = NULL; - while(!done) - { - // - if (!PC_ReadSourceToken(source, &token)) - { - SourceError(source, "define %s incomplete", define->name); - return qfalse; - } //end if - // - if (!strcmp(token.string, ",")) - { - if (indent <= 0) - { - if (lastcomma) SourceWarning(source, "too many comma's"); - lastcomma = 1; - break; - } //end if - } //end if - lastcomma = 0; - // - if (!strcmp(token.string, "(")) - { - indent++; - continue; - } //end if - else if (!strcmp(token.string, ")")) - { - if (--indent <= 0) - { - if (!parms[define->numparms-1]) - { - SourceWarning(source, "too few define parms"); - } //end if - done = 1; - break; - } //end if - } //end if - // - if (numparms < define->numparms) - { - // - t = PC_CopyToken(&token); - t->next = NULL; - if (last) last->next = t; - else parms[numparms] = t; - last = t; - } //end if - } //end while - numparms++; - } //end for - return qtrue; -} //end of the function PC_ReadDefineParms -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_StringizeTokens(token_t *tokens, token_t *token) -{ - token_t *t; - - token->type = TT_STRING; - token->whitespace_p = NULL; - token->endwhitespace_p = NULL; - token->string[0] = '\0'; - strcat(token->string, "\""); - for (t = tokens; t; t = t->next) - { - strncat(token->string, t->string, MAX_TOKEN - strlen(token->string)); - } //end for - strncat(token->string, "\"", MAX_TOKEN - strlen(token->string)); - return qtrue; -} //end of the function PC_StringizeTokens -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_MergeTokens(token_t *t1, token_t *t2) -{ - //merging of a name with a name or number - if (t1->type == TT_NAME && (t2->type == TT_NAME || t2->type == TT_NUMBER)) - { - strcat(t1->string, t2->string); - return qtrue; - } //end if - //merging of two strings - if (t1->type == TT_STRING && t2->type == TT_STRING) - { - //remove trailing double quote - t1->string[strlen(t1->string)-1] = '\0'; - //concat without leading double quote - strcat(t1->string, &t2->string[1]); - return qtrue; - } //end if - //FIXME: merging of two number of the same sub type - return qfalse; -} //end of the function PC_MergeTokens -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -/* -void PC_PrintDefine(define_t *define) -{ - printf("define->name = %s\n", define->name); - printf("define->flags = %d\n", define->flags); - printf("define->builtin = %d\n", define->builtin); - printf("define->numparms = %d\n", define->numparms); -// token_t *parms; //define parameters -// token_t *tokens; //macro tokens (possibly containing parm tokens) -// struct define_s *next; //next defined macro in a list -} //end of the function PC_PrintDefine*/ -#if DEFINEHASHING -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -void PC_PrintDefineHashTable(define_t **definehash) -{ - int i; - define_t *d; - - for (i = 0; i < DEFINEHASHSIZE; i++) - { - Log_Write("%4d:", i); - for (d = definehash[i]; d; d = d->hashnext) - { - Log_Write(" %s", d->name); - } //end for - Log_Write("\n"); - } //end for -} //end of the function PC_PrintDefineHashTable -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -//char primes[16] = {1, 3, 5, 7, 11, 13, 17, 19, 23, 27, 29, 31, 37, 41, 43, 47}; - -int PC_NameHash(char *name) -{ - int register hash, i; - - hash = 0; - for (i = 0; name[i] != '\0'; i++) - { - hash += name[i] * (119 + i); - //hash += (name[i] << 7) + i; - //hash += (name[i] << (i&15)); - } //end while - hash = (hash ^ (hash >> 10) ^ (hash >> 20)) & (DEFINEHASHSIZE-1); - return hash; -} //end of the function PC_NameHash -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -void PC_AddDefineToHash(define_t *define, define_t **definehash) -{ - int hash; - - hash = PC_NameHash(define->name); - define->hashnext = definehash[hash]; - definehash[hash] = define; -} //end of the function PC_AddDefineToHash -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -define_t *PC_FindHashedDefine(define_t **definehash, char *name) -{ - define_t *d; - int hash; - - hash = PC_NameHash(name); - for (d = definehash[hash]; d; d = d->hashnext) - { - if (!strcmp(d->name, name)) return d; - } //end for - return NULL; -} //end of the function PC_FindHashedDefine -#endif //DEFINEHASHING -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -define_t *PC_FindDefine(define_t *defines, char *name) -{ - define_t *d; - - for (d = defines; d; d = d->next) - { - if (!strcmp(d->name, name)) return d; - } //end for - return NULL; -} //end of the function PC_FindDefine -//============================================================================ -// -// Parameter: - -// Returns: number of the parm -// if no parm found with the given name -1 is returned -// Changes Globals: - -//============================================================================ -int PC_FindDefineParm(define_t *define, char *name) -{ - token_t *p; - int i; - - i = 0; - for (p = define->parms; p; p = p->next) - { - if (!strcmp(p->string, name)) return i; - i++; - } //end for - return -1; -} //end of the function PC_FindDefineParm -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -void PC_FreeDefine(define_t *define) -{ - token_t *t, *next; - - //free the define parameters - for (t = define->parms; t; t = next) - { - next = t->next; - PC_FreeToken(t); - } //end for - //free the define tokens - for (t = define->tokens; t; t = next) - { - next = t->next; - PC_FreeToken(t); - } //end for - //free the define - FreeMemory(define); -} //end of the function PC_FreeDefine -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -void PC_AddBuiltinDefines(source_t *source) -{ - int i; - define_t *define; - struct builtin - { - char *string; - int builtin; - } builtin[] = { // bk001204 - brackets - { "__LINE__", BUILTIN_LINE }, - { "__FILE__", BUILTIN_FILE }, - { "__DATE__", BUILTIN_DATE }, - { "__TIME__", BUILTIN_TIME }, -// { "__STDC__", BUILTIN_STDC }, - { NULL, 0 } - }; - - for (i = 0; builtin[i].string; i++) - { - define = (define_t *) GetMemory(sizeof(define_t) + strlen(builtin[i].string) + 1); - Com_Memset(define, 0, sizeof(define_t)); - define->name = (char *) define + sizeof(define_t); - strcpy(define->name, builtin[i].string); - define->flags |= DEFINE_FIXED; - define->builtin = builtin[i].builtin; - //add the define to the source -#if DEFINEHASHING - PC_AddDefineToHash(define, source->definehash); -#else - define->next = source->defines; - source->defines = define; -#endif //DEFINEHASHING - } //end for -} //end of the function PC_AddBuiltinDefines -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_ExpandBuiltinDefine(source_t *source, token_t *deftoken, define_t *define, - token_t **firsttoken, token_t **lasttoken) -{ - token_t *token; - unsigned long t; // time_t t; //to prevent LCC warning - char *curtime; - - token = PC_CopyToken(deftoken); - switch(define->builtin) - { - case BUILTIN_LINE: - { - sprintf(token->string, "%d", deftoken->line); -#ifdef NUMBERVALUE - token->intvalue = deftoken->line; - token->floatvalue = deftoken->line; -#endif //NUMBERVALUE - token->type = TT_NUMBER; - token->subtype = TT_DECIMAL | TT_INTEGER; - *firsttoken = token; - *lasttoken = token; - break; - } //end case - case BUILTIN_FILE: - { - strcpy(token->string, source->scriptstack->filename); - token->type = TT_NAME; - token->subtype = strlen(token->string); - *firsttoken = token; - *lasttoken = token; - break; - } //end case - case BUILTIN_DATE: - { - t = time(NULL); - curtime = ctime(&t); - strcpy(token->string, "\""); - strncat(token->string, curtime+4, 7); - strncat(token->string+7, curtime+20, 4); - strcat(token->string, "\""); - free(curtime); - token->type = TT_NAME; - token->subtype = strlen(token->string); - *firsttoken = token; - *lasttoken = token; - break; - } //end case - case BUILTIN_TIME: - { - t = time(NULL); - curtime = ctime(&t); - strcpy(token->string, "\""); - strncat(token->string, curtime+11, 8); - strcat(token->string, "\""); - free(curtime); - token->type = TT_NAME; - token->subtype = strlen(token->string); - *firsttoken = token; - *lasttoken = token; - break; - } //end case - case BUILTIN_STDC: - default: - { - *firsttoken = NULL; - *lasttoken = NULL; - break; - } //end case - } //end switch - return qtrue; -} //end of the function PC_ExpandBuiltinDefine -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_ExpandDefine(source_t *source, token_t *deftoken, define_t *define, - token_t **firsttoken, token_t **lasttoken) -{ - token_t *parms[MAX_DEFINEPARMS], *dt, *pt, *t; - token_t *t1, *t2, *first, *last, *nextpt, token; - int parmnum, i; - - //if it is a builtin define - if (define->builtin) - { - return PC_ExpandBuiltinDefine(source, deftoken, define, firsttoken, lasttoken); - } //end if - //if the define has parameters - if (define->numparms) - { - if (!PC_ReadDefineParms(source, define, parms, MAX_DEFINEPARMS)) return qfalse; -#ifdef DEBUG_EVAL - for (i = 0; i < define->numparms; i++) - { - Log_Write("define parms %d:", i); - for (pt = parms[i]; pt; pt = pt->next) - { - Log_Write("%s", pt->string); - } //end for - } //end for -#endif //DEBUG_EVAL - } //end if - //empty list at first - first = NULL; - last = NULL; - //create a list with tokens of the expanded define - for (dt = define->tokens; dt; dt = dt->next) - { - parmnum = -1; - //if the token is a name, it could be a define parameter - if (dt->type == TT_NAME) - { - parmnum = PC_FindDefineParm(define, dt->string); - } //end if - //if it is a define parameter - if (parmnum >= 0) - { - for (pt = parms[parmnum]; pt; pt = pt->next) - { - t = PC_CopyToken(pt); - //add the token to the list - t->next = NULL; - if (last) last->next = t; - else first = t; - last = t; - } //end for - } //end if - else - { - //if stringizing operator - if (dt->string[0] == '#' && dt->string[1] == '\0') - { - //the stringizing operator must be followed by a define parameter - if (dt->next) parmnum = PC_FindDefineParm(define, dt->next->string); - else parmnum = -1; - // - if (parmnum >= 0) - { - //step over the stringizing operator - dt = dt->next; - //stringize the define parameter tokens - if (!PC_StringizeTokens(parms[parmnum], &token)) - { - SourceError(source, "can't stringize tokens"); - return qfalse; - } //end if - t = PC_CopyToken(&token); - } //end if - else - { - SourceWarning(source, "stringizing operator without define parameter"); - continue; - } //end if - } //end if - else - { - t = PC_CopyToken(dt); - } //end else - //add the token to the list - t->next = NULL; - if (last) last->next = t; - else first = t; - last = t; - } //end else - } //end for - //check for the merging operator - for (t = first; t; ) - { - if (t->next) - { - //if the merging operator - if (t->next->string[0] == '#' && t->next->string[1] == '#') - { - t1 = t; - t2 = t->next->next; - if (t2) - { - if (!PC_MergeTokens(t1, t2)) - { - SourceError(source, "can't merge %s with %s", t1->string, t2->string); - return qfalse; - } //end if - PC_FreeToken(t1->next); - t1->next = t2->next; - if (t2 == last) last = t1; - PC_FreeToken(t2); - continue; - } //end if - } //end if - } //end if - t = t->next; - } //end for - //store the first and last token of the list - *firsttoken = first; - *lasttoken = last; - //free all the parameter tokens - for (i = 0; i < define->numparms; i++) - { - for (pt = parms[i]; pt; pt = nextpt) - { - nextpt = pt->next; - PC_FreeToken(pt); - } //end for - } //end for - // - return qtrue; -} //end of the function PC_ExpandDefine -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_ExpandDefineIntoSource(source_t *source, token_t *deftoken, define_t *define) -{ - token_t *firsttoken, *lasttoken; - - if (!PC_ExpandDefine(source, deftoken, define, &firsttoken, &lasttoken)) return qfalse; - - if (firsttoken && lasttoken) - { - lasttoken->next = source->tokens; - source->tokens = firsttoken; - return qtrue; - } //end if - return qfalse; -} //end of the function PC_ExpandDefineIntoSource -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -void PC_ConvertPath(char *path) -{ - char *ptr; - - //remove double path seperators - for (ptr = path; *ptr;) - { - if ((*ptr == '\\' || *ptr == '/') && - (*(ptr+1) == '\\' || *(ptr+1) == '/')) - { - strcpy(ptr, ptr+1); - } //end if - else - { - ptr++; - } //end else - } //end while - //set OS dependent path seperators - for (ptr = path; *ptr;) - { - if (*ptr == '/' || *ptr == '\\') *ptr = PATHSEPERATOR_CHAR; - ptr++; - } //end while -} //end of the function PC_ConvertPath -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_Directive_include(source_t *source) -{ - script_t *script; - token_t token; - char path[MAX_PATH]; -#ifdef QUAKE - foundfile_t file; -#endif //QUAKE - - if (source->skip > 0) return qtrue; - // - if (!PC_ReadSourceToken(source, &token)) - { - SourceError(source, "#include without file name"); - return qfalse; - } //end if - if (token.linescrossed > 0) - { - SourceError(source, "#include without file name"); - return qfalse; - } //end if - if (token.type == TT_STRING) - { - StripDoubleQuotes(token.string); - PC_ConvertPath(token.string); - script = LoadScriptFile(token.string); - if (!script) - { - strcpy(path, source->includepath); - strcat(path, token.string); - script = LoadScriptFile(path); - } //end if - } //end if - else if (token.type == TT_PUNCTUATION && *token.string == '<') - { - strcpy(path, source->includepath); - while(PC_ReadSourceToken(source, &token)) - { - if (token.linescrossed > 0) - { - PC_UnreadSourceToken(source, &token); - break; - } //end if - if (token.type == TT_PUNCTUATION && *token.string == '>') break; - strncat(path, token.string, MAX_PATH); - } //end while - if (*token.string != '>') - { - SourceWarning(source, "#include missing trailing >"); - } //end if - if (!strlen(path)) - { - SourceError(source, "#include without file name between < >"); - return qfalse; - } //end if - PC_ConvertPath(path); - script = LoadScriptFile(path); - } //end if - else - { - SourceError(source, "#include without file name"); - return qfalse; - } //end else -#ifdef QUAKE - if (!script) - { - Com_Memset(&file, 0, sizeof(foundfile_t)); - script = LoadScriptFile(path); - if (script) strncpy(script->filename, path, MAX_PATH); - } //end if -#endif //QUAKE - if (!script) - { -#ifdef SCREWUP - SourceWarning(source, "file %s not found", path); - return qtrue; -#else - SourceError(source, "file %s not found", path); - return qfalse; -#endif //SCREWUP - } //end if - PC_PushScript(source, script); - return qtrue; -} //end of the function PC_Directive_include -//============================================================================ -// reads a token from the current line, continues reading on the next -// line only if a backslash '\' is encountered. -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_ReadLine(source_t *source, token_t *token) -{ - int crossline; - - crossline = 0; - do - { - if (!PC_ReadSourceToken(source, token)) return qfalse; - - if (token->linescrossed > crossline) - { - PC_UnreadSourceToken(source, token); - return qfalse; - } //end if - crossline = 1; - } while(!strcmp(token->string, "\\")); - return qtrue; -} //end of the function PC_ReadLine -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_WhiteSpaceBeforeToken(token_t *token) -{ - return token->endwhitespace_p - token->whitespace_p > 0; -} //end of the function PC_WhiteSpaceBeforeToken -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -void PC_ClearTokenWhiteSpace(token_t *token) -{ - token->whitespace_p = NULL; - token->endwhitespace_p = NULL; - token->linescrossed = 0; -} //end of the function PC_ClearTokenWhiteSpace -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_Directive_undef(source_t *source) -{ - token_t token; - define_t *define, *lastdefine; - int hash; - - if (source->skip > 0) return qtrue; - // - if (!PC_ReadLine(source, &token)) - { - SourceError(source, "undef without name"); - return qfalse; - } //end if - if (token.type != TT_NAME) - { - PC_UnreadSourceToken(source, &token); - SourceError(source, "expected name, found %s", token.string); - return qfalse; - } //end if -#if DEFINEHASHING - - hash = PC_NameHash(token.string); - for (lastdefine = NULL, define = source->definehash[hash]; define; define = define->hashnext) - { - if (!strcmp(define->name, token.string)) - { - if (define->flags & DEFINE_FIXED) - { - SourceWarning(source, "can't undef %s", token.string); - } //end if - else - { - if (lastdefine) lastdefine->hashnext = define->hashnext; - else source->definehash[hash] = define->hashnext; - PC_FreeDefine(define); - } //end else - break; - } //end if - lastdefine = define; - } //end for -#else //DEFINEHASHING - for (lastdefine = NULL, define = source->defines; define; define = define->next) - { - if (!strcmp(define->name, token.string)) - { - if (define->flags & DEFINE_FIXED) - { - SourceWarning(source, "can't undef %s", token.string); - } //end if - else - { - if (lastdefine) lastdefine->next = define->next; - else source->defines = define->next; - PC_FreeDefine(define); - } //end else - break; - } //end if - lastdefine = define; - } //end for -#endif //DEFINEHASHING - return qtrue; -} //end of the function PC_Directive_undef -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_Directive_define(source_t *source) -{ - token_t token, *t, *last; - define_t *define; - - if (source->skip > 0) return qtrue; - // - if (!PC_ReadLine(source, &token)) - { - SourceError(source, "#define without name"); - return qfalse; - } //end if - if (token.type != TT_NAME) - { - PC_UnreadSourceToken(source, &token); - SourceError(source, "expected name after #define, found %s", token.string); - return qfalse; - } //end if - //check if the define already exists -#if DEFINEHASHING - define = PC_FindHashedDefine(source->definehash, token.string); -#else - define = PC_FindDefine(source->defines, token.string); -#endif //DEFINEHASHING - if (define) - { - if (define->flags & DEFINE_FIXED) - { - SourceError(source, "can't redefine %s", token.string); - return qfalse; - } //end if - SourceWarning(source, "redefinition of %s", token.string); - //unread the define name before executing the #undef directive - PC_UnreadSourceToken(source, &token); - if (!PC_Directive_undef(source)) return qfalse; - //if the define was not removed (define->flags & DEFINE_FIXED) -#if DEFINEHASHING - define = PC_FindHashedDefine(source->definehash, token.string); -#else - define = PC_FindDefine(source->defines, token.string); -#endif //DEFINEHASHING - } //end if - //allocate define - define = (define_t *) GetMemory(sizeof(define_t) + strlen(token.string) + 1); - Com_Memset(define, 0, sizeof(define_t)); - define->name = (char *) define + sizeof(define_t); - strcpy(define->name, token.string); - //add the define to the source -#if DEFINEHASHING - PC_AddDefineToHash(define, source->definehash); -#else //DEFINEHASHING - define->next = source->defines; - source->defines = define; -#endif //DEFINEHASHING - //if nothing is defined, just return - if (!PC_ReadLine(source, &token)) return qtrue; - //if it is a define with parameters - if (!PC_WhiteSpaceBeforeToken(&token) && !strcmp(token.string, "(")) - { - //read the define parameters - last = NULL; - if (!PC_CheckTokenString(source, ")")) - { - while(1) - { - if (!PC_ReadLine(source, &token)) - { - SourceError(source, "expected define parameter"); - return qfalse; - } //end if - //if it isn't a name - if (token.type != TT_NAME) - { - SourceError(source, "invalid define parameter"); - return qfalse; - } //end if - // - if (PC_FindDefineParm(define, token.string) >= 0) - { - SourceError(source, "two the same define parameters"); - return qfalse; - } //end if - //add the define parm - t = PC_CopyToken(&token); - PC_ClearTokenWhiteSpace(t); - t->next = NULL; - if (last) last->next = t; - else define->parms = t; - last = t; - define->numparms++; - //read next token - if (!PC_ReadLine(source, &token)) - { - SourceError(source, "define parameters not terminated"); - return qfalse; - } //end if - // - if (!strcmp(token.string, ")")) break; - //then it must be a comma - if (strcmp(token.string, ",")) - { - SourceError(source, "define not terminated"); - return qfalse; - } //end if - } //end while - } //end if - if (!PC_ReadLine(source, &token)) return qtrue; - } //end if - //read the defined stuff - last = NULL; - do - { - t = PC_CopyToken(&token); - if (t->type == TT_NAME && !strcmp(t->string, define->name)) - { - SourceError(source, "recursive define (removed recursion)"); - continue; - } //end if - PC_ClearTokenWhiteSpace(t); - t->next = NULL; - if (last) last->next = t; - else define->tokens = t; - last = t; - } while(PC_ReadLine(source, &token)); - // - if (last) - { - //check for merge operators at the beginning or end - if (!strcmp(define->tokens->string, "##") || - !strcmp(last->string, "##")) - { - SourceError(source, "define with misplaced ##"); - return qfalse; - } //end if - } //end if - return qtrue; -} //end of the function PC_Directive_define -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -define_t *PC_DefineFromString(char *string) -{ - script_t *script; - source_t src; - token_t *t; - int res, i; - define_t *def; - - PC_InitTokenHeap(); - - script = LoadScriptMemory(string, strlen(string), "*extern"); - //create a new source - Com_Memset(&src, 0, sizeof(source_t)); - strncpy(src.filename, "*extern", MAX_PATH); - src.scriptstack = script; -#if DEFINEHASHING - src.definehash = GetClearedMemory(DEFINEHASHSIZE * sizeof(define_t *)); -#endif //DEFINEHASHING - //create a define from the source - res = PC_Directive_define(&src); - //free any tokens if left - for (t = src.tokens; t; t = src.tokens) - { - src.tokens = src.tokens->next; - PC_FreeToken(t); - } //end for -#ifdef DEFINEHASHING - def = NULL; - for (i = 0; i < DEFINEHASHSIZE; i++) - { - if (src.definehash[i]) - { - def = src.definehash[i]; - break; - } //end if - } //end for -#else - def = src.defines; -#endif //DEFINEHASHING - // -#if DEFINEHASHING - FreeMemory(src.definehash); -#endif //DEFINEHASHING - // - FreeScript(script); - //if the define was created succesfully - if (res > 0) return def; - //free the define is created - if (src.defines) PC_FreeDefine(def); - // - return NULL; -} //end of the function PC_DefineFromString -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_AddDefine(source_t *source, char *string) -{ - define_t *define; - - define = PC_DefineFromString(string); - if (!define) return qfalse; -#if DEFINEHASHING - PC_AddDefineToHash(define, source->definehash); -#else //DEFINEHASHING - define->next = source->defines; - source->defines = define; -#endif //DEFINEHASHING - return qtrue; -} //end of the function PC_AddDefine -//============================================================================ -// add a globals define that will be added to all opened sources -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_AddGlobalDefine(char *string) -{ - define_t *define; - - define = PC_DefineFromString(string); - if (!define) return qfalse; - define->next = globaldefines; - globaldefines = define; - return qtrue; -} //end of the function PC_AddGlobalDefine -//============================================================================ -// remove the given global define -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_RemoveGlobalDefine(char *name) -{ - define_t *define; - - define = PC_FindDefine(globaldefines, name); - if (define) - { - PC_FreeDefine(define); - return qtrue; - } //end if - return qfalse; -} //end of the function PC_RemoveGlobalDefine -//============================================================================ -// remove all globals defines -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -void PC_RemoveAllGlobalDefines(void) -{ - define_t *define; - - for (define = globaldefines; define; define = globaldefines) - { - globaldefines = globaldefines->next; - PC_FreeDefine(define); - } //end for -} //end of the function PC_RemoveAllGlobalDefines -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -define_t *PC_CopyDefine(source_t *source, define_t *define) -{ - define_t *newdefine; - token_t *token, *newtoken, *lasttoken; - - newdefine = (define_t *) GetMemory(sizeof(define_t) + strlen(define->name) + 1); - //copy the define name - newdefine->name = (char *) newdefine + sizeof(define_t); - strcpy(newdefine->name, define->name); - newdefine->flags = define->flags; - newdefine->builtin = define->builtin; - newdefine->numparms = define->numparms; - //the define is not linked - newdefine->next = NULL; - newdefine->hashnext = NULL; - //copy the define tokens - newdefine->tokens = NULL; - for (lasttoken = NULL, token = define->tokens; token; token = token->next) - { - newtoken = PC_CopyToken(token); - newtoken->next = NULL; - if (lasttoken) lasttoken->next = newtoken; - else newdefine->tokens = newtoken; - lasttoken = newtoken; - } //end for - //copy the define parameters - newdefine->parms = NULL; - for (lasttoken = NULL, token = define->parms; token; token = token->next) - { - newtoken = PC_CopyToken(token); - newtoken->next = NULL; - if (lasttoken) lasttoken->next = newtoken; - else newdefine->parms = newtoken; - lasttoken = newtoken; - } //end for - return newdefine; -} //end of the function PC_CopyDefine -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -void PC_AddGlobalDefinesToSource(source_t *source) -{ - define_t *define, *newdefine; - - for (define = globaldefines; define; define = define->next) - { - newdefine = PC_CopyDefine(source, define); -#if DEFINEHASHING - PC_AddDefineToHash(newdefine, source->definehash); -#else //DEFINEHASHING - newdefine->next = source->defines; - source->defines = newdefine; -#endif //DEFINEHASHING - } //end for -} //end of the function PC_AddGlobalDefinesToSource -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_Directive_if_def(source_t *source, int type) -{ - token_t token; - define_t *d; - int skip; - - if (!PC_ReadLine(source, &token)) - { - SourceError(source, "#ifdef without name"); - return qfalse; - } //end if - if (token.type != TT_NAME) - { - PC_UnreadSourceToken(source, &token); - SourceError(source, "expected name after #ifdef, found %s", token.string); - return qfalse; - } //end if -#if DEFINEHASHING - d = PC_FindHashedDefine(source->definehash, token.string); -#else - d = PC_FindDefine(source->defines, token.string); -#endif //DEFINEHASHING - skip = (type == INDENT_IFDEF) == (d == NULL); - PC_PushIndent(source, type, skip); - return qtrue; -} //end of the function PC_Directiveif_def -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_Directive_ifdef(source_t *source) -{ - return PC_Directive_if_def(source, INDENT_IFDEF); -} //end of the function PC_Directive_ifdef -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_Directive_ifndef(source_t *source) -{ - return PC_Directive_if_def(source, INDENT_IFNDEF); -} //end of the function PC_Directive_ifndef -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_Directive_else(source_t *source) -{ - int type, skip; - - PC_PopIndent(source, &type, &skip); - if (!type) - { - SourceError(source, "misplaced #else"); - return qfalse; - } //end if - if (type == INDENT_ELSE) - { - SourceError(source, "#else after #else"); - return qfalse; - } //end if - PC_PushIndent(source, INDENT_ELSE, !skip); - return qtrue; -} //end of the function PC_Directive_else -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_Directive_endif(source_t *source) -{ - int type, skip; - - PC_PopIndent(source, &type, &skip); - if (!type) - { - SourceError(source, "misplaced #endif"); - return qfalse; - } //end if - return qtrue; -} //end of the function PC_Directive_endif -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -typedef struct operator_s -{ - int operator; - int priority; - int parentheses; - struct operator_s *prev, *next; -} operator_t; - -typedef struct value_s -{ - signed long int intvalue; - double floatvalue; - int parentheses; - struct value_s *prev, *next; -} value_t; - -int PC_OperatorPriority(int op) -{ - switch(op) - { - case P_MUL: return 15; - case P_DIV: return 15; - case P_MOD: return 15; - case P_ADD: return 14; - case P_SUB: return 14; - - case P_LOGIC_AND: return 7; - case P_LOGIC_OR: return 6; - case P_LOGIC_GEQ: return 12; - case P_LOGIC_LEQ: return 12; - case P_LOGIC_EQ: return 11; - case P_LOGIC_UNEQ: return 11; - - case P_LOGIC_NOT: return 16; - case P_LOGIC_GREATER: return 12; - case P_LOGIC_LESS: return 12; - - case P_RSHIFT: return 13; - case P_LSHIFT: return 13; - - case P_BIN_AND: return 10; - case P_BIN_OR: return 8; - case P_BIN_XOR: return 9; - case P_BIN_NOT: return 16; - - case P_COLON: return 5; - case P_QUESTIONMARK: return 5; - } //end switch - return qfalse; -} //end of the function PC_OperatorPriority - -//#define AllocValue() GetClearedMemory(sizeof(value_t)); -//#define FreeValue(val) FreeMemory(val) -//#define AllocOperator(op) op = (operator_t *) GetClearedMemory(sizeof(operator_t)); -//#define FreeOperator(op) FreeMemory(op); - -#define MAX_VALUES 64 -#define MAX_OPERATORS 64 -#define AllocValue(val) \ - if (numvalues >= MAX_VALUES) { \ - SourceError(source, "out of value space\n"); \ - error = 1; \ - break; \ - } \ - else \ - val = &value_heap[numvalues++]; -#define FreeValue(val) -// -#define AllocOperator(op) \ - if (numoperators >= MAX_OPERATORS) { \ - SourceError(source, "out of operator space\n"); \ - error = 1; \ - break; \ - } \ - else \ - op = &operator_heap[numoperators++]; -#define FreeOperator(op) - -int PC_EvaluateTokens(source_t *source, token_t *tokens, signed long int *intvalue, - double *floatvalue, int integer) -{ - operator_t *o, *firstoperator, *lastoperator; - value_t *v, *firstvalue, *lastvalue, *v1, *v2; - token_t *t; - int brace = 0; - int parentheses = 0; - int error = 0; - int lastwasvalue = 0; - int negativevalue = 0; - int questmarkintvalue = 0; - double questmarkfloatvalue = 0; - int gotquestmarkvalue = qfalse; - int lastoperatortype = 0; - // - operator_t operator_heap[MAX_OPERATORS]; - int numoperators = 0; - value_t value_heap[MAX_VALUES]; - int numvalues = 0; - - firstoperator = lastoperator = NULL; - firstvalue = lastvalue = NULL; - if (intvalue) *intvalue = 0; - if (floatvalue) *floatvalue = 0; - for (t = tokens; t; t = t->next) - { - switch(t->type) - { - case TT_NAME: - { - if (lastwasvalue || negativevalue) - { - SourceError(source, "syntax error in #if/#elif"); - error = 1; - break; - } //end if - if (strcmp(t->string, "defined")) - { - SourceError(source, "undefined name %s in #if/#elif", t->string); - error = 1; - break; - } //end if - t = t->next; - if (!strcmp(t->string, "(")) - { - brace = qtrue; - t = t->next; - } //end if - if (!t || t->type != TT_NAME) - { - SourceError(source, "defined without name in #if/#elif"); - error = 1; - break; - } //end if - //v = (value_t *) GetClearedMemory(sizeof(value_t)); - AllocValue(v); -#if DEFINEHASHING - if (PC_FindHashedDefine(source->definehash, t->string)) -#else - if (PC_FindDefine(source->defines, t->string)) -#endif //DEFINEHASHING - { - v->intvalue = 1; - v->floatvalue = 1; - } //end if - else - { - v->intvalue = 0; - v->floatvalue = 0; - } //end else - v->parentheses = parentheses; - v->next = NULL; - v->prev = lastvalue; - if (lastvalue) lastvalue->next = v; - else firstvalue = v; - lastvalue = v; - if (brace) - { - t = t->next; - if (!t || strcmp(t->string, ")")) - { - SourceError(source, "defined without ) in #if/#elif"); - error = 1; - break; - } //end if - } //end if - brace = qfalse; - // defined() creates a value - lastwasvalue = 1; - break; - } //end case - case TT_NUMBER: - { - if (lastwasvalue) - { - SourceError(source, "syntax error in #if/#elif"); - error = 1; - break; - } //end if - //v = (value_t *) GetClearedMemory(sizeof(value_t)); - AllocValue(v); - if (negativevalue) - { - v->intvalue = - (signed int) t->intvalue; - v->floatvalue = - t->floatvalue; - } //end if - else - { - v->intvalue = t->intvalue; - v->floatvalue = t->floatvalue; - } //end else - v->parentheses = parentheses; - v->next = NULL; - v->prev = lastvalue; - if (lastvalue) lastvalue->next = v; - else firstvalue = v; - lastvalue = v; - //last token was a value - lastwasvalue = 1; - // - negativevalue = 0; - break; - } //end case - case TT_PUNCTUATION: - { - if (negativevalue) - { - SourceError(source, "misplaced minus sign in #if/#elif"); - error = 1; - break; - } //end if - if (t->subtype == P_PARENTHESESOPEN) - { - parentheses++; - break; - } //end if - else if (t->subtype == P_PARENTHESESCLOSE) - { - parentheses--; - if (parentheses < 0) - { - SourceError(source, "too many ) in #if/#elsif"); - error = 1; - } //end if - break; - } //end else if - //check for invalid operators on floating point values - if (!integer) - { - if (t->subtype == P_BIN_NOT || t->subtype == P_MOD || - t->subtype == P_RSHIFT || t->subtype == P_LSHIFT || - t->subtype == P_BIN_AND || t->subtype == P_BIN_OR || - t->subtype == P_BIN_XOR) - { - SourceError(source, "illigal operator %s on floating point operands\n", t->string); - error = 1; - break; - } //end if - } //end if - switch(t->subtype) - { - case P_LOGIC_NOT: - case P_BIN_NOT: - { - if (lastwasvalue) - { - SourceError(source, "! or ~ after value in #if/#elif"); - error = 1; - break; - } //end if - break; - } //end case - case P_INC: - case P_DEC: - { - SourceError(source, "++ or -- used in #if/#elif"); - break; - } //end case - case P_SUB: - { - if (!lastwasvalue) - { - negativevalue = 1; - break; - } //end if - } //end case - - case P_MUL: - case P_DIV: - case P_MOD: - case P_ADD: - - case P_LOGIC_AND: - case P_LOGIC_OR: - case P_LOGIC_GEQ: - case P_LOGIC_LEQ: - case P_LOGIC_EQ: - case P_LOGIC_UNEQ: - - case P_LOGIC_GREATER: - case P_LOGIC_LESS: - - case P_RSHIFT: - case P_LSHIFT: - - case P_BIN_AND: - case P_BIN_OR: - case P_BIN_XOR: - - case P_COLON: - case P_QUESTIONMARK: - { - if (!lastwasvalue) - { - SourceError(source, "operator %s after operator in #if/#elif", t->string); - error = 1; - break; - } //end if - break; - } //end case - default: - { - SourceError(source, "invalid operator %s in #if/#elif", t->string); - error = 1; - break; - } //end default - } //end switch - if (!error && !negativevalue) - { - //o = (operator_t *) GetClearedMemory(sizeof(operator_t)); - AllocOperator(o); - o->operator = t->subtype; - o->priority = PC_OperatorPriority(t->subtype); - o->parentheses = parentheses; - o->next = NULL; - o->prev = lastoperator; - if (lastoperator) lastoperator->next = o; - else firstoperator = o; - lastoperator = o; - lastwasvalue = 0; - } //end if - break; - } //end case - default: - { - SourceError(source, "unknown %s in #if/#elif", t->string); - error = 1; - break; - } //end default - } //end switch - if (error) break; - } //end for - if (!error) - { - if (!lastwasvalue) - { - SourceError(source, "trailing operator in #if/#elif"); - error = 1; - } //end if - else if (parentheses) - { - SourceError(source, "too many ( in #if/#elif"); - error = 1; - } //end else if - } //end if - // - gotquestmarkvalue = qfalse; - questmarkintvalue = 0; - questmarkfloatvalue = 0; - //while there are operators - while(!error && firstoperator) - { - v = firstvalue; - for (o = firstoperator; o->next; o = o->next) - { - //if the current operator is nested deeper in parentheses - //than the next operator - if (o->parentheses > o->next->parentheses) break; - //if the current and next operator are nested equally deep in parentheses - if (o->parentheses == o->next->parentheses) - { - //if the priority of the current operator is equal or higher - //than the priority of the next operator - if (o->priority >= o->next->priority) break; - } //end if - //if the arity of the operator isn't equal to 1 - if (o->operator != P_LOGIC_NOT - && o->operator != P_BIN_NOT) v = v->next; - //if there's no value or no next value - if (!v) - { - SourceError(source, "mising values in #if/#elif"); - error = 1; - break; - } //end if - } //end for - if (error) break; - v1 = v; - v2 = v->next; -#ifdef DEBUG_EVAL - if (integer) - { - Log_Write("operator %s, value1 = %d", PunctuationFromNum(source->scriptstack, o->operator), v1->intvalue); - if (v2) Log_Write("value2 = %d", v2->intvalue); - } //end if - else - { - Log_Write("operator %s, value1 = %f", PunctuationFromNum(source->scriptstack, o->operator), v1->floatvalue); - if (v2) Log_Write("value2 = %f", v2->floatvalue); - } //end else -#endif //DEBUG_EVAL - switch(o->operator) - { - case P_LOGIC_NOT: v1->intvalue = !v1->intvalue; - v1->floatvalue = !v1->floatvalue; break; - case P_BIN_NOT: v1->intvalue = ~v1->intvalue; - break; - case P_MUL: v1->intvalue *= v2->intvalue; - v1->floatvalue *= v2->floatvalue; break; - case P_DIV: if (!v2->intvalue || !v2->floatvalue) - { - SourceError(source, "divide by zero in #if/#elif\n"); - error = 1; - break; - } - v1->intvalue /= v2->intvalue; - v1->floatvalue /= v2->floatvalue; break; - case P_MOD: if (!v2->intvalue) - { - SourceError(source, "divide by zero in #if/#elif\n"); - error = 1; - break; - } - v1->intvalue %= v2->intvalue; break; - case P_ADD: v1->intvalue += v2->intvalue; - v1->floatvalue += v2->floatvalue; break; - case P_SUB: v1->intvalue -= v2->intvalue; - v1->floatvalue -= v2->floatvalue; break; - case P_LOGIC_AND: v1->intvalue = v1->intvalue && v2->intvalue; - v1->floatvalue = v1->floatvalue && v2->floatvalue; break; - case P_LOGIC_OR: v1->intvalue = v1->intvalue || v2->intvalue; - v1->floatvalue = v1->floatvalue || v2->floatvalue; break; - case P_LOGIC_GEQ: v1->intvalue = v1->intvalue >= v2->intvalue; - v1->floatvalue = v1->floatvalue >= v2->floatvalue; break; - case P_LOGIC_LEQ: v1->intvalue = v1->intvalue <= v2->intvalue; - v1->floatvalue = v1->floatvalue <= v2->floatvalue; break; - case P_LOGIC_EQ: v1->intvalue = v1->intvalue == v2->intvalue; - v1->floatvalue = v1->floatvalue == v2->floatvalue; break; - case P_LOGIC_UNEQ: v1->intvalue = v1->intvalue != v2->intvalue; - v1->floatvalue = v1->floatvalue != v2->floatvalue; break; - case P_LOGIC_GREATER: v1->intvalue = v1->intvalue > v2->intvalue; - v1->floatvalue = v1->floatvalue > v2->floatvalue; break; - case P_LOGIC_LESS: v1->intvalue = v1->intvalue < v2->intvalue; - v1->floatvalue = v1->floatvalue < v2->floatvalue; break; - case P_RSHIFT: v1->intvalue >>= v2->intvalue; - break; - case P_LSHIFT: v1->intvalue <<= v2->intvalue; - break; - case P_BIN_AND: v1->intvalue &= v2->intvalue; - break; - case P_BIN_OR: v1->intvalue |= v2->intvalue; - break; - case P_BIN_XOR: v1->intvalue ^= v2->intvalue; - break; - case P_COLON: - { - if (!gotquestmarkvalue) - { - SourceError(source, ": without ? in #if/#elif"); - error = 1; - break; - } //end if - if (integer) - { - if (!questmarkintvalue) v1->intvalue = v2->intvalue; - } //end if - else - { - if (!questmarkfloatvalue) v1->floatvalue = v2->floatvalue; - } //end else - gotquestmarkvalue = qfalse; - break; - } //end case - case P_QUESTIONMARK: - { - if (gotquestmarkvalue) - { - SourceError(source, "? after ? in #if/#elif"); - error = 1; - break; - } //end if - questmarkintvalue = v1->intvalue; - questmarkfloatvalue = v1->floatvalue; - gotquestmarkvalue = qtrue; - break; - } //end if - } //end switch -#ifdef DEBUG_EVAL - if (integer) Log_Write("result value = %d", v1->intvalue); - else Log_Write("result value = %f", v1->floatvalue); -#endif //DEBUG_EVAL - if (error) break; - lastoperatortype = o->operator; - //if not an operator with arity 1 - if (o->operator != P_LOGIC_NOT - && o->operator != P_BIN_NOT) - { - //remove the second value if not question mark operator - if (o->operator != P_QUESTIONMARK) v = v->next; - // - if (v->prev) v->prev->next = v->next; - else firstvalue = v->next; - if (v->next) v->next->prev = v->prev; - else lastvalue = v->prev; - //FreeMemory(v); - FreeValue(v); - } //end if - //remove the operator - if (o->prev) o->prev->next = o->next; - else firstoperator = o->next; - if (o->next) o->next->prev = o->prev; - else lastoperator = o->prev; - //FreeMemory(o); - FreeOperator(o); - } //end while - if (firstvalue) - { - if (intvalue) *intvalue = firstvalue->intvalue; - if (floatvalue) *floatvalue = firstvalue->floatvalue; - } //end if - for (o = firstoperator; o; o = lastoperator) - { - lastoperator = o->next; - //FreeMemory(o); - FreeOperator(o); - } //end for - for (v = firstvalue; v; v = lastvalue) - { - lastvalue = v->next; - //FreeMemory(v); - FreeValue(v); - } //end for - if (!error) return qtrue; - if (intvalue) *intvalue = 0; - if (floatvalue) *floatvalue = 0; - return qfalse; -} //end of the function PC_EvaluateTokens -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_Evaluate(source_t *source, signed long int *intvalue, - double *floatvalue, int integer) -{ - token_t token, *firsttoken, *lasttoken; - token_t *t, *nexttoken; - define_t *define; - int defined = qfalse; - - if (intvalue) *intvalue = 0; - if (floatvalue) *floatvalue = 0; - // - if (!PC_ReadLine(source, &token)) - { - SourceError(source, "no value after #if/#elif"); - return qfalse; - } //end if - firsttoken = NULL; - lasttoken = NULL; - do - { - //if the token is a name - if (token.type == TT_NAME) - { - if (defined) - { - defined = qfalse; - t = PC_CopyToken(&token); - t->next = NULL; - if (lasttoken) lasttoken->next = t; - else firsttoken = t; - lasttoken = t; - } //end if - else if (!strcmp(token.string, "defined")) - { - defined = qtrue; - t = PC_CopyToken(&token); - t->next = NULL; - if (lasttoken) lasttoken->next = t; - else firsttoken = t; - lasttoken = t; - } //end if - else - { - //then it must be a define -#if DEFINEHASHING - define = PC_FindHashedDefine(source->definehash, token.string); -#else - define = PC_FindDefine(source->defines, token.string); -#endif //DEFINEHASHING - if (!define) - { - SourceError(source, "can't evaluate %s, not defined", token.string); - return qfalse; - } //end if - if (!PC_ExpandDefineIntoSource(source, &token, define)) return qfalse; - } //end else - } //end if - //if the token is a number or a punctuation - else if (token.type == TT_NUMBER || token.type == TT_PUNCTUATION) - { - t = PC_CopyToken(&token); - t->next = NULL; - if (lasttoken) lasttoken->next = t; - else firsttoken = t; - lasttoken = t; - } //end else - else //can't evaluate the token - { - SourceError(source, "can't evaluate %s", token.string); - return qfalse; - } //end else - } while(PC_ReadLine(source, &token)); - // - if (!PC_EvaluateTokens(source, firsttoken, intvalue, floatvalue, integer)) return qfalse; - // -#ifdef DEBUG_EVAL - Log_Write("eval:"); -#endif //DEBUG_EVAL - for (t = firsttoken; t; t = nexttoken) - { -#ifdef DEBUG_EVAL - Log_Write(" %s", t->string); -#endif //DEBUG_EVAL - nexttoken = t->next; - PC_FreeToken(t); - } //end for -#ifdef DEBUG_EVAL - if (integer) Log_Write("eval result: %d", *intvalue); - else Log_Write("eval result: %f", *floatvalue); -#endif //DEBUG_EVAL - // - return qtrue; -} //end of the function PC_Evaluate -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_DollarEvaluate(source_t *source, signed long int *intvalue, - double *floatvalue, int integer) -{ - int indent, defined = qfalse; - token_t token, *firsttoken, *lasttoken; - token_t *t, *nexttoken; - define_t *define; - - if (intvalue) *intvalue = 0; - if (floatvalue) *floatvalue = 0; - // - if (!PC_ReadSourceToken(source, &token)) - { - SourceError(source, "no leading ( after $evalint/$evalfloat"); - return qfalse; - } //end if - if (!PC_ReadSourceToken(source, &token)) - { - SourceError(source, "nothing to evaluate"); - return qfalse; - } //end if - indent = 1; - firsttoken = NULL; - lasttoken = NULL; - do - { - //if the token is a name - if (token.type == TT_NAME) - { - if (defined) - { - defined = qfalse; - t = PC_CopyToken(&token); - t->next = NULL; - if (lasttoken) lasttoken->next = t; - else firsttoken = t; - lasttoken = t; - } //end if - else if (!strcmp(token.string, "defined")) - { - defined = qtrue; - t = PC_CopyToken(&token); - t->next = NULL; - if (lasttoken) lasttoken->next = t; - else firsttoken = t; - lasttoken = t; - } //end if - else - { - //then it must be a define -#if DEFINEHASHING - define = PC_FindHashedDefine(source->definehash, token.string); -#else - define = PC_FindDefine(source->defines, token.string); -#endif //DEFINEHASHING - if (!define) - { - SourceError(source, "can't evaluate %s, not defined", token.string); - return qfalse; - } //end if - if (!PC_ExpandDefineIntoSource(source, &token, define)) return qfalse; - } //end else - } //end if - //if the token is a number or a punctuation - else if (token.type == TT_NUMBER || token.type == TT_PUNCTUATION) - { - if (*token.string == '(') indent++; - else if (*token.string == ')') indent--; - if (indent <= 0) break; - t = PC_CopyToken(&token); - t->next = NULL; - if (lasttoken) lasttoken->next = t; - else firsttoken = t; - lasttoken = t; - } //end else - else //can't evaluate the token - { - SourceError(source, "can't evaluate %s", token.string); - return qfalse; - } //end else - } while(PC_ReadSourceToken(source, &token)); - // - if (!PC_EvaluateTokens(source, firsttoken, intvalue, floatvalue, integer)) return qfalse; - // -#ifdef DEBUG_EVAL - Log_Write("$eval:"); -#endif //DEBUG_EVAL - for (t = firsttoken; t; t = nexttoken) - { -#ifdef DEBUG_EVAL - Log_Write(" %s", t->string); -#endif //DEBUG_EVAL - nexttoken = t->next; - PC_FreeToken(t); - } //end for -#ifdef DEBUG_EVAL - if (integer) Log_Write("$eval result: %d", *intvalue); - else Log_Write("$eval result: %f", *floatvalue); -#endif //DEBUG_EVAL - // - return qtrue; -} //end of the function PC_DollarEvaluate -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_Directive_elif(source_t *source) -{ - signed long int value; - int type, skip; - - PC_PopIndent(source, &type, &skip); - if (!type || type == INDENT_ELSE) - { - SourceError(source, "misplaced #elif"); - return qfalse; - } //end if - if (!PC_Evaluate(source, &value, NULL, qtrue)) return qfalse; - skip = (value == 0); - PC_PushIndent(source, INDENT_ELIF, skip); - return qtrue; -} //end of the function PC_Directive_elif -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_Directive_if(source_t *source) -{ - signed long int value; - int skip; - - if (!PC_Evaluate(source, &value, NULL, qtrue)) return qfalse; - skip = (value == 0); - PC_PushIndent(source, INDENT_IF, skip); - return qtrue; -} //end of the function PC_Directive -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_Directive_line(source_t *source) -{ - SourceError(source, "#line directive not supported"); - return qfalse; -} //end of the function PC_Directive_line -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_Directive_error(source_t *source) -{ - token_t token; - - strcpy(token.string, ""); - PC_ReadSourceToken(source, &token); - SourceError(source, "#error directive: %s", token.string); - return qfalse; -} //end of the function PC_Directive_error -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_Directive_pragma(source_t *source) -{ - token_t token; - - SourceWarning(source, "#pragma directive not supported"); - while(PC_ReadLine(source, &token)) ; - return qtrue; -} //end of the function PC_Directive_pragma -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -void UnreadSignToken(source_t *source) -{ - token_t token; - - token.line = source->scriptstack->line; - token.whitespace_p = source->scriptstack->script_p; - token.endwhitespace_p = source->scriptstack->script_p; - token.linescrossed = 0; - strcpy(token.string, "-"); - token.type = TT_PUNCTUATION; - token.subtype = P_SUB; - PC_UnreadSourceToken(source, &token); -} //end of the function UnreadSignToken -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_Directive_eval(source_t *source) -{ - signed long int value; - token_t token; - - if (!PC_Evaluate(source, &value, NULL, qtrue)) return qfalse; - // - token.line = source->scriptstack->line; - token.whitespace_p = source->scriptstack->script_p; - token.endwhitespace_p = source->scriptstack->script_p; - token.linescrossed = 0; - sprintf(token.string, "%d", abs(value)); - token.type = TT_NUMBER; - token.subtype = TT_INTEGER|TT_LONG|TT_DECIMAL; - PC_UnreadSourceToken(source, &token); - if (value < 0) UnreadSignToken(source); - return qtrue; -} //end of the function PC_Directive_eval -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_Directive_evalfloat(source_t *source) -{ - double value; - token_t token; - - if (!PC_Evaluate(source, NULL, &value, qfalse)) return qfalse; - token.line = source->scriptstack->line; - token.whitespace_p = source->scriptstack->script_p; - token.endwhitespace_p = source->scriptstack->script_p; - token.linescrossed = 0; - sprintf(token.string, "%1.2f", fabs(value)); - token.type = TT_NUMBER; - token.subtype = TT_FLOAT|TT_LONG|TT_DECIMAL; - PC_UnreadSourceToken(source, &token); - if (value < 0) UnreadSignToken(source); - return qtrue; -} //end of the function PC_Directive_evalfloat -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -directive_t directives[20] = -{ - {"if", PC_Directive_if}, - {"ifdef", PC_Directive_ifdef}, - {"ifndef", PC_Directive_ifndef}, - {"elif", PC_Directive_elif}, - {"else", PC_Directive_else}, - {"endif", PC_Directive_endif}, - {"include", PC_Directive_include}, - {"define", PC_Directive_define}, - {"undef", PC_Directive_undef}, - {"line", PC_Directive_line}, - {"error", PC_Directive_error}, - {"pragma", PC_Directive_pragma}, - {"eval", PC_Directive_eval}, - {"evalfloat", PC_Directive_evalfloat}, - {NULL, NULL} -}; - -int PC_ReadDirective(source_t *source) -{ - token_t token; - int i; - - //read the directive name - if (!PC_ReadSourceToken(source, &token)) - { - SourceError(source, "found # without name"); - return qfalse; - } //end if - //directive name must be on the same line - if (token.linescrossed > 0) - { - PC_UnreadSourceToken(source, &token); - SourceError(source, "found # at end of line"); - return qfalse; - } //end if - //if if is a name - if (token.type == TT_NAME) - { - //find the precompiler directive - for (i = 0; directives[i].name; i++) - { - if (!strcmp(directives[i].name, token.string)) - { - return directives[i].func(source); - } //end if - } //end for - } //end if - SourceError(source, "unknown precompiler directive %s", token.string); - return qfalse; -} //end of the function PC_ReadDirective -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_DollarDirective_evalint(source_t *source) -{ - signed long int value; - token_t token; - - if (!PC_DollarEvaluate(source, &value, NULL, qtrue)) return qfalse; - // - token.line = source->scriptstack->line; - token.whitespace_p = source->scriptstack->script_p; - token.endwhitespace_p = source->scriptstack->script_p; - token.linescrossed = 0; - sprintf(token.string, "%d", abs(value)); - token.type = TT_NUMBER; - token.subtype = TT_INTEGER|TT_LONG|TT_DECIMAL; -#ifdef NUMBERVALUE - token.intvalue = value; - token.floatvalue = value; -#endif //NUMBERVALUE - PC_UnreadSourceToken(source, &token); - if (value < 0) UnreadSignToken(source); - return qtrue; -} //end of the function PC_DollarDirective_evalint -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_DollarDirective_evalfloat(source_t *source) -{ - double value; - token_t token; - - if (!PC_DollarEvaluate(source, NULL, &value, qfalse)) return qfalse; - token.line = source->scriptstack->line; - token.whitespace_p = source->scriptstack->script_p; - token.endwhitespace_p = source->scriptstack->script_p; - token.linescrossed = 0; - sprintf(token.string, "%1.2f", fabs(value)); - token.type = TT_NUMBER; - token.subtype = TT_FLOAT|TT_LONG|TT_DECIMAL; -#ifdef NUMBERVALUE - token.intvalue = (unsigned long) value; - token.floatvalue = value; -#endif //NUMBERVALUE - PC_UnreadSourceToken(source, &token); - if (value < 0) UnreadSignToken(source); - return qtrue; -} //end of the function PC_DollarDirective_evalfloat -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -directive_t dollardirectives[20] = -{ - {"evalint", PC_DollarDirective_evalint}, - {"evalfloat", PC_DollarDirective_evalfloat}, - {NULL, NULL} -}; - -int PC_ReadDollarDirective(source_t *source) -{ - token_t token; - int i; - - //read the directive name - if (!PC_ReadSourceToken(source, &token)) - { - SourceError(source, "found $ without name"); - return qfalse; - } //end if - //directive name must be on the same line - if (token.linescrossed > 0) - { - PC_UnreadSourceToken(source, &token); - SourceError(source, "found $ at end of line"); - return qfalse; - } //end if - //if if is a name - if (token.type == TT_NAME) - { - //find the precompiler directive - for (i = 0; dollardirectives[i].name; i++) - { - if (!strcmp(dollardirectives[i].name, token.string)) - { - return dollardirectives[i].func(source); - } //end if - } //end for - } //end if - PC_UnreadSourceToken(source, &token); - SourceError(source, "unknown precompiler directive %s", token.string); - return qfalse; -} //end of the function PC_ReadDirective - -#ifdef QUAKEC -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int BuiltinFunction(source_t *source) -{ - token_t token; - - if (!PC_ReadSourceToken(source, &token)) return qfalse; - if (token.type == TT_NUMBER) - { - PC_UnreadSourceToken(source, &token); - return qtrue; - } //end if - else - { - PC_UnreadSourceToken(source, &token); - return qfalse; - } //end else -} //end of the function BuiltinFunction -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int QuakeCMacro(source_t *source) -{ - int i; - token_t token; - - if (!PC_ReadSourceToken(source, &token)) return qtrue; - if (token.type != TT_NAME) - { - PC_UnreadSourceToken(source, &token); - return qtrue; - } //end if - //find the precompiler directive - for (i = 0; dollardirectives[i].name; i++) - { - if (!strcmp(dollardirectives[i].name, token.string)) - { - PC_UnreadSourceToken(source, &token); - return qfalse; - } //end if - } //end for - PC_UnreadSourceToken(source, &token); - return qtrue; -} //end of the function QuakeCMacro -#endif //QUAKEC -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_ReadToken(source_t *source, token_t *token) -{ - define_t *define; - - while(1) - { - if (!PC_ReadSourceToken(source, token)) return qfalse; - //check for precompiler directives - if (token->type == TT_PUNCTUATION && *token->string == '#') - { -#ifdef QUAKEC - if (!BuiltinFunction(source)) -#endif //QUAKC - { - //read the precompiler directive - if (!PC_ReadDirective(source)) return qfalse; - continue; - } //end if - } //end if - if (token->type == TT_PUNCTUATION && *token->string == '$') - { -#ifdef QUAKEC - if (!QuakeCMacro(source)) -#endif //QUAKEC - { - //read the precompiler directive - if (!PC_ReadDollarDirective(source)) return qfalse; - continue; - } //end if - } //end if - // recursively concatenate strings that are behind each other still resolving defines - if (token->type == TT_STRING) - { - token_t newtoken; - if (PC_ReadToken(source, &newtoken)) - { - if (newtoken.type == TT_STRING) - { - token->string[strlen(token->string)-1] = '\0'; - if (strlen(token->string) + strlen(newtoken.string+1) + 1 >= MAX_TOKEN) - { - SourceError(source, "string longer than MAX_TOKEN %d\n", MAX_TOKEN); - return qfalse; - } - strcat(token->string, newtoken.string+1); - } - else - { - PC_UnreadToken(source, &newtoken); - } - } - } //end if - //if skipping source because of conditional compilation - if (source->skip) continue; - //if the token is a name - if (token->type == TT_NAME) - { - //check if the name is a define macro -#if DEFINEHASHING - define = PC_FindHashedDefine(source->definehash, token->string); -#else - define = PC_FindDefine(source->defines, token->string); -#endif //DEFINEHASHING - //if it is a define macro - if (define) - { - //expand the defined macro - if (!PC_ExpandDefineIntoSource(source, token, define)) return qfalse; - continue; - } //end if - } //end if - //copy token for unreading - Com_Memcpy(&source->token, token, sizeof(token_t)); - //found a token - return qtrue; - } //end while -} //end of the function PC_ReadToken -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_ExpectTokenString(source_t *source, char *string) -{ - token_t token; - - if (!PC_ReadToken(source, &token)) - { - SourceError(source, "couldn't find expected %s", string); - return qfalse; - } //end if - - if (strcmp(token.string, string)) - { - SourceError(source, "expected %s, found %s", string, token.string); - return qfalse; - } //end if - return qtrue; -} //end of the function PC_ExpectTokenString -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_ExpectTokenType(source_t *source, int type, int subtype, token_t *token) -{ - char str[MAX_TOKEN]; - - if (!PC_ReadToken(source, token)) - { - SourceError(source, "couldn't read expected token"); - return qfalse; - } //end if - - if (token->type != type) - { - strcpy(str, ""); - if (type == TT_STRING) strcpy(str, "string"); - if (type == TT_LITERAL) strcpy(str, "literal"); - if (type == TT_NUMBER) strcpy(str, "number"); - if (type == TT_NAME) strcpy(str, "name"); - if (type == TT_PUNCTUATION) strcpy(str, "punctuation"); - SourceError(source, "expected a %s, found %s", str, token->string); - return qfalse; - } //end if - if (token->type == TT_NUMBER) - { - if ((token->subtype & subtype) != subtype) - { - if (subtype & TT_DECIMAL) strcpy(str, "decimal"); - if (subtype & TT_HEX) strcpy(str, "hex"); - if (subtype & TT_OCTAL) strcpy(str, "octal"); - if (subtype & TT_BINARY) strcpy(str, "binary"); - if (subtype & TT_LONG) strcat(str, " long"); - if (subtype & TT_UNSIGNED) strcat(str, " unsigned"); - if (subtype & TT_FLOAT) strcat(str, " float"); - if (subtype & TT_INTEGER) strcat(str, " integer"); - SourceError(source, "expected %s, found %s", str, token->string); - return qfalse; - } //end if - } //end if - else if (token->type == TT_PUNCTUATION) - { - if (token->subtype != subtype) - { - SourceError(source, "found %s", token->string); - return qfalse; - } //end if - } //end else if - return qtrue; -} //end of the function PC_ExpectTokenType -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_ExpectAnyToken(source_t *source, token_t *token) -{ - if (!PC_ReadToken(source, token)) - { - SourceError(source, "couldn't read expected token"); - return qfalse; - } //end if - else - { - return qtrue; - } //end else -} //end of the function PC_ExpectAnyToken -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_CheckTokenString(source_t *source, char *string) -{ - token_t tok; - - if (!PC_ReadToken(source, &tok)) return qfalse; - //if the token is available - if (!strcmp(tok.string, string)) return qtrue; - // - PC_UnreadSourceToken(source, &tok); - return qfalse; -} //end of the function PC_CheckTokenString -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_CheckTokenType(source_t *source, int type, int subtype, token_t *token) -{ - token_t tok; - - if (!PC_ReadToken(source, &tok)) return qfalse; - //if the type matches - if (tok.type == type && - (tok.subtype & subtype) == subtype) - { - Com_Memcpy(token, &tok, sizeof(token_t)); - return qtrue; - } //end if - // - PC_UnreadSourceToken(source, &tok); - return qfalse; -} //end of the function PC_CheckTokenType -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_SkipUntilString(source_t *source, char *string) -{ - token_t token; - - while(PC_ReadToken(source, &token)) - { - if (!strcmp(token.string, string)) return qtrue; - } //end while - return qfalse; -} //end of the function PC_SkipUntilString -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -void PC_UnreadLastToken(source_t *source) -{ - PC_UnreadSourceToken(source, &source->token); -} //end of the function PC_UnreadLastToken -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -void PC_UnreadToken(source_t *source, token_t *token) -{ - PC_UnreadSourceToken(source, token); -} //end of the function PC_UnreadToken -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -void PC_SetIncludePath(source_t *source, char *path) -{ - strncpy(source->includepath, path, MAX_PATH); - //add trailing path seperator - if (source->includepath[strlen(source->includepath)-1] != '\\' && - source->includepath[strlen(source->includepath)-1] != '/') - { - strcat(source->includepath, PATHSEPERATOR_STR); - } //end if -} //end of the function PC_SetIncludePath -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -void PC_SetPunctuations(source_t *source, punctuation_t *p) -{ - source->punctuations = p; -} //end of the function PC_SetPunctuations -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -source_t *LoadSourceFile(const char *filename) -{ - source_t *source; - script_t *script; - - PC_InitTokenHeap(); - - script = LoadScriptFile(filename); - if (!script) return NULL; - - script->next = NULL; - - source = (source_t *) GetMemory(sizeof(source_t)); - Com_Memset(source, 0, sizeof(source_t)); - - strncpy(source->filename, filename, MAX_PATH); - source->scriptstack = script; - source->tokens = NULL; - source->defines = NULL; - source->indentstack = NULL; - source->skip = 0; - -#if DEFINEHASHING - source->definehash = GetClearedMemory(DEFINEHASHSIZE * sizeof(define_t *)); -#endif //DEFINEHASHING - PC_AddGlobalDefinesToSource(source); - return source; -} //end of the function LoadSourceFile -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -source_t *LoadSourceMemory(char *ptr, int length, char *name) -{ - source_t *source; - script_t *script; - - PC_InitTokenHeap(); - - script = LoadScriptMemory(ptr, length, name); - if (!script) return NULL; - script->next = NULL; - - source = (source_t *) GetMemory(sizeof(source_t)); - Com_Memset(source, 0, sizeof(source_t)); - - strncpy(source->filename, name, MAX_PATH); - source->scriptstack = script; - source->tokens = NULL; - source->defines = NULL; - source->indentstack = NULL; - source->skip = 0; - -#if DEFINEHASHING - source->definehash = GetClearedMemory(DEFINEHASHSIZE * sizeof(define_t *)); -#endif //DEFINEHASHING - PC_AddGlobalDefinesToSource(source); - return source; -} //end of the function LoadSourceMemory -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -void FreeSource(source_t *source) -{ - script_t *script; - token_t *token; - define_t *define; - indent_t *indent; - int i; - - //PC_PrintDefineHashTable(source->definehash); - //free all the scripts - while(source->scriptstack) - { - script = source->scriptstack; - source->scriptstack = source->scriptstack->next; - FreeScript(script); - } //end for - //free all the tokens - while(source->tokens) - { - token = source->tokens; - source->tokens = source->tokens->next; - PC_FreeToken(token); - } //end for -#if DEFINEHASHING - for (i = 0; i < DEFINEHASHSIZE; i++) - { - while(source->definehash[i]) - { - define = source->definehash[i]; - source->definehash[i] = source->definehash[i]->hashnext; - PC_FreeDefine(define); - } //end while - } //end for -#else //DEFINEHASHING - //free all defines - while(source->defines) - { - define = source->defines; - source->defines = source->defines->next; - PC_FreeDefine(define); - } //end for -#endif //DEFINEHASHING - //free all indents - while(source->indentstack) - { - indent = source->indentstack; - source->indentstack = source->indentstack->next; - FreeMemory(indent); - } //end for -#if DEFINEHASHING - // - if (source->definehash) FreeMemory(source->definehash); -#endif //DEFINEHASHING - //free the source itself - FreeMemory(source); -} //end of the function FreeSource -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ - -#define MAX_SOURCEFILES 64 - -source_t *sourceFiles[MAX_SOURCEFILES]; - -int PC_LoadSourceHandle(const char *filename) -{ - source_t *source; - int i; - - for (i = 1; i < MAX_SOURCEFILES; i++) - { - if (!sourceFiles[i]) - break; - } //end for - if (i >= MAX_SOURCEFILES) - return 0; - PS_SetBaseFolder(""); - source = LoadSourceFile(filename); - if (!source) - return 0; - sourceFiles[i] = source; - return i; -} //end of the function PC_LoadSourceHandle -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_FreeSourceHandle(int handle) -{ - if (handle < 1 || handle >= MAX_SOURCEFILES) - return qfalse; - if (!sourceFiles[handle]) - return qfalse; - - FreeSource(sourceFiles[handle]); - sourceFiles[handle] = NULL; - return qtrue; -} //end of the function PC_FreeSourceHandle -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_ReadTokenHandle(int handle, pc_token_t *pc_token) -{ - token_t token; - int ret; - - if (handle < 1 || handle >= MAX_SOURCEFILES) - return 0; - if (!sourceFiles[handle]) - return 0; - - ret = PC_ReadToken(sourceFiles[handle], &token); - strcpy(pc_token->string, token.string); - pc_token->type = token.type; - pc_token->subtype = token.subtype; - pc_token->intvalue = token.intvalue; - pc_token->floatvalue = token.floatvalue; - if (pc_token->type == TT_STRING) - StripDoubleQuotes(pc_token->string); - return ret; -} //end of the function PC_ReadTokenHandle -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PC_SourceFileAndLine(int handle, char *filename, int *line) -{ - if (handle < 1 || handle >= MAX_SOURCEFILES) - return qfalse; - if (!sourceFiles[handle]) - return qfalse; - - strcpy(filename, sourceFiles[handle]->filename); - if (sourceFiles[handle]->scriptstack) - *line = sourceFiles[handle]->scriptstack->line; - else - *line = 0; - return qtrue; -} //end of the function PC_SourceFileAndLine -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -void PC_SetBaseFolder(char *path) -{ - PS_SetBaseFolder(path); -} //end of the function PC_SetBaseFolder -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -void PC_CheckOpenSourceHandles(void) -{ - int i; - - for (i = 1; i < MAX_SOURCEFILES; i++) - { - if (sourceFiles[i]) - { -#ifdef BOTLIB - botimport.Print(PRT_ERROR, "file %s still open in precompiler\n", sourceFiles[i]->scriptstack->filename); -#endif //BOTLIB - } //end if - } //end for -} //end of the function PC_CheckOpenSourceHandles - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: l_precomp.c + * + * desc: pre compiler + * + * $Archive: /MissionPack/code/botlib/l_precomp.c $ + * + *****************************************************************************/ + +//Notes: fix: PC_StringizeTokens + +//#define SCREWUP +//#define BOTLIB +//#define QUAKE +//#define QUAKEC +//#define MEQCC + +#ifdef SCREWUP +#include +#include +#include +#include +#include +#include +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" + +typedef enum {qfalse, qtrue} qboolean; +#endif //SCREWUP + +#ifdef BOTLIB +#include "../game/q_shared.h" +#include "../game/botlib.h" +#include "be_interface.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_log.h" +#endif //BOTLIB + +#ifdef MEQCC +#include "qcc.h" +#include "time.h" //time & ctime +#include "math.h" //fabs +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_log.h" + +#define qtrue true +#define qfalse false +#endif //MEQCC + +#ifdef BSPC +//include files for usage in the BSP Converter +#include "../bspc/qbsp.h" +#include "../bspc/l_log.h" +#include "../bspc/l_mem.h" +#include "l_precomp.h" + +#define qtrue true +#define qfalse false +#define Q_stricmp stricmp + +#endif //BSPC + +#if defined(QUAKE) && !defined(BSPC) +#include "l_utils.h" +#endif //QUAKE + +//#define DEBUG_EVAL + +#define MAX_DEFINEPARMS 128 + +#define DEFINEHASHING 1 + +//directive name with parse function +typedef struct directive_s +{ + char *name; + int (*func)(source_t *source); +} directive_t; + +#define DEFINEHASHSIZE 1024 + +#define TOKEN_HEAP_SIZE 4096 + +int numtokens; +/* +int tokenheapinitialized; //true when the token heap is initialized +token_t token_heap[TOKEN_HEAP_SIZE]; //heap with tokens +token_t *freetokens; //free tokens from the heap +*/ + +//list with global defines added to every source loaded +define_t *globaldefines; + +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void QDECL SourceError(source_t *source, char *str, ...) +{ + char text[1024]; + va_list ap; + + va_start(ap, str); + vsprintf(text, str, ap); + va_end(ap); +#ifdef BOTLIB + botimport.Print(PRT_ERROR, "file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text); +#endif //BOTLIB +#ifdef MEQCC + printf("error: file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text); +#endif //MEQCC +#ifdef BSPC + Log_Print("error: file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text); +#endif //BSPC +} //end of the function SourceError +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void QDECL SourceWarning(source_t *source, char *str, ...) +{ + char text[1024]; + va_list ap; + + va_start(ap, str); + vsprintf(text, str, ap); + va_end(ap); +#ifdef BOTLIB + botimport.Print(PRT_WARNING, "file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text); +#endif //BOTLIB +#ifdef MEQCC + printf("warning: file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text); +#endif //MEQCC +#ifdef BSPC + Log_Print("warning: file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text); +#endif //BSPC +} //end of the function ScriptWarning +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_PushIndent(source_t *source, int type, int skip) +{ + indent_t *indent; + + indent = (indent_t *) GetMemory(sizeof(indent_t)); + indent->type = type; + indent->script = source->scriptstack; + indent->skip = (skip != 0); + source->skip += indent->skip; + indent->next = source->indentstack; + source->indentstack = indent; +} //end of the function PC_PushIndent +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_PopIndent(source_t *source, int *type, int *skip) +{ + indent_t *indent; + + *type = 0; + *skip = 0; + + indent = source->indentstack; + if (!indent) return; + + //must be an indent from the current script + if (source->indentstack->script != source->scriptstack) return; + + *type = indent->type; + *skip = indent->skip; + source->indentstack = source->indentstack->next; + source->skip -= indent->skip; + FreeMemory(indent); +} //end of the function PC_PopIndent +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_PushScript(source_t *source, script_t *script) +{ + script_t *s; + + for (s = source->scriptstack; s; s = s->next) + { + if (!Q_stricmp(s->filename, script->filename)) + { + SourceError(source, "%s recursively included", script->filename); + return; + } //end if + } //end for + //push the script on the script stack + script->next = source->scriptstack; + source->scriptstack = script; +} //end of the function PC_PushScript +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_InitTokenHeap(void) +{ + /* + int i; + + if (tokenheapinitialized) return; + freetokens = NULL; + for (i = 0; i < TOKEN_HEAP_SIZE; i++) + { + token_heap[i].next = freetokens; + freetokens = &token_heap[i]; + } //end for + tokenheapinitialized = qtrue; + */ +} //end of the function PC_InitTokenHeap +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +token_t *PC_CopyToken(token_t *token) +{ + token_t *t; + +// t = (token_t *) malloc(sizeof(token_t)); + t = (token_t *) GetMemory(sizeof(token_t)); +// t = freetokens; + if (!t) + { +#ifdef BSPC + Error("out of token space\n"); +#else + Com_Error(ERR_FATAL, "out of token space\n"); +#endif + return NULL; + } //end if +// freetokens = freetokens->next; + Com_Memcpy(t, token, sizeof(token_t)); + t->next = NULL; + numtokens++; + return t; +} //end of the function PC_CopyToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_FreeToken(token_t *token) +{ + //free(token); + FreeMemory(token); +// token->next = freetokens; +// freetokens = token; + numtokens--; +} //end of the function PC_FreeToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ReadSourceToken(source_t *source, token_t *token) +{ + token_t *t; + script_t *script; + int type, skip; + + //if there's no token already available + while(!source->tokens) + { + //if there's a token to read from the script + if (PS_ReadToken(source->scriptstack, token)) return qtrue; + //if at the end of the script + if (EndOfScript(source->scriptstack)) + { + //remove all indents of the script + while(source->indentstack && + source->indentstack->script == source->scriptstack) + { + SourceWarning(source, "missing #endif"); + PC_PopIndent(source, &type, &skip); + } //end if + } //end if + //if this was the initial script + if (!source->scriptstack->next) return qfalse; + //remove the script and return to the last one + script = source->scriptstack; + source->scriptstack = source->scriptstack->next; + FreeScript(script); + } //end while + //copy the already available token + Com_Memcpy(token, source->tokens, sizeof(token_t)); + //free the read token + t = source->tokens; + source->tokens = source->tokens->next; + PC_FreeToken(t); + return qtrue; +} //end of the function PC_ReadSourceToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_UnreadSourceToken(source_t *source, token_t *token) +{ + token_t *t; + + t = PC_CopyToken(token); + t->next = source->tokens; + source->tokens = t; + return qtrue; +} //end of the function PC_UnreadSourceToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ReadDefineParms(source_t *source, define_t *define, token_t **parms, int maxparms) +{ + token_t token, *t, *last; + int i, done, lastcomma, numparms, indent; + + if (!PC_ReadSourceToken(source, &token)) + { + SourceError(source, "define %s missing parms", define->name); + return qfalse; + } //end if + // + if (define->numparms > maxparms) + { + SourceError(source, "define with more than %d parameters", maxparms); + return qfalse; + } //end if + // + for (i = 0; i < define->numparms; i++) parms[i] = NULL; + //if no leading "(" + if (strcmp(token.string, "(")) + { + PC_UnreadSourceToken(source, &token); + SourceError(source, "define %s missing parms", define->name); + return qfalse; + } //end if + //read the define parameters + for (done = 0, numparms = 0, indent = 0; !done;) + { + if (numparms >= maxparms) + { + SourceError(source, "define %s with too many parms", define->name); + return qfalse; + } //end if + if (numparms >= define->numparms) + { + SourceWarning(source, "define %s has too many parms", define->name); + return qfalse; + } //end if + parms[numparms] = NULL; + lastcomma = 1; + last = NULL; + while(!done) + { + // + if (!PC_ReadSourceToken(source, &token)) + { + SourceError(source, "define %s incomplete", define->name); + return qfalse; + } //end if + // + if (!strcmp(token.string, ",")) + { + if (indent <= 0) + { + if (lastcomma) SourceWarning(source, "too many comma's"); + lastcomma = 1; + break; + } //end if + } //end if + lastcomma = 0; + // + if (!strcmp(token.string, "(")) + { + indent++; + continue; + } //end if + else if (!strcmp(token.string, ")")) + { + if (--indent <= 0) + { + if (!parms[define->numparms-1]) + { + SourceWarning(source, "too few define parms"); + } //end if + done = 1; + break; + } //end if + } //end if + // + if (numparms < define->numparms) + { + // + t = PC_CopyToken(&token); + t->next = NULL; + if (last) last->next = t; + else parms[numparms] = t; + last = t; + } //end if + } //end while + numparms++; + } //end for + return qtrue; +} //end of the function PC_ReadDefineParms +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_StringizeTokens(token_t *tokens, token_t *token) +{ + token_t *t; + + token->type = TT_STRING; + token->whitespace_p = NULL; + token->endwhitespace_p = NULL; + token->string[0] = '\0'; + strcat(token->string, "\""); + for (t = tokens; t; t = t->next) + { + strncat(token->string, t->string, MAX_TOKEN - strlen(token->string)); + } //end for + strncat(token->string, "\"", MAX_TOKEN - strlen(token->string)); + return qtrue; +} //end of the function PC_StringizeTokens +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_MergeTokens(token_t *t1, token_t *t2) +{ + //merging of a name with a name or number + if (t1->type == TT_NAME && (t2->type == TT_NAME || t2->type == TT_NUMBER)) + { + strcat(t1->string, t2->string); + return qtrue; + } //end if + //merging of two strings + if (t1->type == TT_STRING && t2->type == TT_STRING) + { + //remove trailing double quote + t1->string[strlen(t1->string)-1] = '\0'; + //concat without leading double quote + strcat(t1->string, &t2->string[1]); + return qtrue; + } //end if + //FIXME: merging of two number of the same sub type + return qfalse; +} //end of the function PC_MergeTokens +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +/* +void PC_PrintDefine(define_t *define) +{ + printf("define->name = %s\n", define->name); + printf("define->flags = %d\n", define->flags); + printf("define->builtin = %d\n", define->builtin); + printf("define->numparms = %d\n", define->numparms); +// token_t *parms; //define parameters +// token_t *tokens; //macro tokens (possibly containing parm tokens) +// struct define_s *next; //next defined macro in a list +} //end of the function PC_PrintDefine*/ +#if DEFINEHASHING +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_PrintDefineHashTable(define_t **definehash) +{ + int i; + define_t *d; + + for (i = 0; i < DEFINEHASHSIZE; i++) + { + Log_Write("%4d:", i); + for (d = definehash[i]; d; d = d->hashnext) + { + Log_Write(" %s", d->name); + } //end for + Log_Write("\n"); + } //end for +} //end of the function PC_PrintDefineHashTable +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +//char primes[16] = {1, 3, 5, 7, 11, 13, 17, 19, 23, 27, 29, 31, 37, 41, 43, 47}; + +int PC_NameHash(char *name) +{ + int register hash, i; + + hash = 0; + for (i = 0; name[i] != '\0'; i++) + { + hash += name[i] * (119 + i); + //hash += (name[i] << 7) + i; + //hash += (name[i] << (i&15)); + } //end while + hash = (hash ^ (hash >> 10) ^ (hash >> 20)) & (DEFINEHASHSIZE-1); + return hash; +} //end of the function PC_NameHash +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_AddDefineToHash(define_t *define, define_t **definehash) +{ + int hash; + + hash = PC_NameHash(define->name); + define->hashnext = definehash[hash]; + definehash[hash] = define; +} //end of the function PC_AddDefineToHash +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +define_t *PC_FindHashedDefine(define_t **definehash, char *name) +{ + define_t *d; + int hash; + + hash = PC_NameHash(name); + for (d = definehash[hash]; d; d = d->hashnext) + { + if (!strcmp(d->name, name)) return d; + } //end for + return NULL; +} //end of the function PC_FindHashedDefine +#endif //DEFINEHASHING +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +define_t *PC_FindDefine(define_t *defines, char *name) +{ + define_t *d; + + for (d = defines; d; d = d->next) + { + if (!strcmp(d->name, name)) return d; + } //end for + return NULL; +} //end of the function PC_FindDefine +//============================================================================ +// +// Parameter: - +// Returns: number of the parm +// if no parm found with the given name -1 is returned +// Changes Globals: - +//============================================================================ +int PC_FindDefineParm(define_t *define, char *name) +{ + token_t *p; + int i; + + i = 0; + for (p = define->parms; p; p = p->next) + { + if (!strcmp(p->string, name)) return i; + i++; + } //end for + return -1; +} //end of the function PC_FindDefineParm +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_FreeDefine(define_t *define) +{ + token_t *t, *next; + + //free the define parameters + for (t = define->parms; t; t = next) + { + next = t->next; + PC_FreeToken(t); + } //end for + //free the define tokens + for (t = define->tokens; t; t = next) + { + next = t->next; + PC_FreeToken(t); + } //end for + //free the define + FreeMemory(define); +} //end of the function PC_FreeDefine +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_AddBuiltinDefines(source_t *source) +{ + int i; + define_t *define; + struct builtin + { + char *string; + int builtin; + } builtin[] = { // bk001204 - brackets + { "__LINE__", BUILTIN_LINE }, + { "__FILE__", BUILTIN_FILE }, + { "__DATE__", BUILTIN_DATE }, + { "__TIME__", BUILTIN_TIME }, +// { "__STDC__", BUILTIN_STDC }, + { NULL, 0 } + }; + + for (i = 0; builtin[i].string; i++) + { + define = (define_t *) GetMemory(sizeof(define_t) + strlen(builtin[i].string) + 1); + Com_Memset(define, 0, sizeof(define_t)); + define->name = (char *) define + sizeof(define_t); + strcpy(define->name, builtin[i].string); + define->flags |= DEFINE_FIXED; + define->builtin = builtin[i].builtin; + //add the define to the source +#if DEFINEHASHING + PC_AddDefineToHash(define, source->definehash); +#else + define->next = source->defines; + source->defines = define; +#endif //DEFINEHASHING + } //end for +} //end of the function PC_AddBuiltinDefines +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ExpandBuiltinDefine(source_t *source, token_t *deftoken, define_t *define, + token_t **firsttoken, token_t **lasttoken) +{ + token_t *token; + unsigned long t; // time_t t; //to prevent LCC warning + char *curtime; + + token = PC_CopyToken(deftoken); + switch(define->builtin) + { + case BUILTIN_LINE: + { + sprintf(token->string, "%d", deftoken->line); +#ifdef NUMBERVALUE + token->intvalue = deftoken->line; + token->floatvalue = deftoken->line; +#endif //NUMBERVALUE + token->type = TT_NUMBER; + token->subtype = TT_DECIMAL | TT_INTEGER; + *firsttoken = token; + *lasttoken = token; + break; + } //end case + case BUILTIN_FILE: + { + strcpy(token->string, source->scriptstack->filename); + token->type = TT_NAME; + token->subtype = strlen(token->string); + *firsttoken = token; + *lasttoken = token; + break; + } //end case + case BUILTIN_DATE: + { + t = time(NULL); + curtime = ctime(&t); + strcpy(token->string, "\""); + strncat(token->string, curtime+4, 7); + strncat(token->string+7, curtime+20, 4); + strcat(token->string, "\""); + free(curtime); + token->type = TT_NAME; + token->subtype = strlen(token->string); + *firsttoken = token; + *lasttoken = token; + break; + } //end case + case BUILTIN_TIME: + { + t = time(NULL); + curtime = ctime(&t); + strcpy(token->string, "\""); + strncat(token->string, curtime+11, 8); + strcat(token->string, "\""); + free(curtime); + token->type = TT_NAME; + token->subtype = strlen(token->string); + *firsttoken = token; + *lasttoken = token; + break; + } //end case + case BUILTIN_STDC: + default: + { + *firsttoken = NULL; + *lasttoken = NULL; + break; + } //end case + } //end switch + return qtrue; +} //end of the function PC_ExpandBuiltinDefine +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ExpandDefine(source_t *source, token_t *deftoken, define_t *define, + token_t **firsttoken, token_t **lasttoken) +{ + token_t *parms[MAX_DEFINEPARMS], *dt, *pt, *t; + token_t *t1, *t2, *first, *last, *nextpt, token; + int parmnum, i; + + //if it is a builtin define + if (define->builtin) + { + return PC_ExpandBuiltinDefine(source, deftoken, define, firsttoken, lasttoken); + } //end if + //if the define has parameters + if (define->numparms) + { + if (!PC_ReadDefineParms(source, define, parms, MAX_DEFINEPARMS)) return qfalse; +#ifdef DEBUG_EVAL + for (i = 0; i < define->numparms; i++) + { + Log_Write("define parms %d:", i); + for (pt = parms[i]; pt; pt = pt->next) + { + Log_Write("%s", pt->string); + } //end for + } //end for +#endif //DEBUG_EVAL + } //end if + //empty list at first + first = NULL; + last = NULL; + //create a list with tokens of the expanded define + for (dt = define->tokens; dt; dt = dt->next) + { + parmnum = -1; + //if the token is a name, it could be a define parameter + if (dt->type == TT_NAME) + { + parmnum = PC_FindDefineParm(define, dt->string); + } //end if + //if it is a define parameter + if (parmnum >= 0) + { + for (pt = parms[parmnum]; pt; pt = pt->next) + { + t = PC_CopyToken(pt); + //add the token to the list + t->next = NULL; + if (last) last->next = t; + else first = t; + last = t; + } //end for + } //end if + else + { + //if stringizing operator + if (dt->string[0] == '#' && dt->string[1] == '\0') + { + //the stringizing operator must be followed by a define parameter + if (dt->next) parmnum = PC_FindDefineParm(define, dt->next->string); + else parmnum = -1; + // + if (parmnum >= 0) + { + //step over the stringizing operator + dt = dt->next; + //stringize the define parameter tokens + if (!PC_StringizeTokens(parms[parmnum], &token)) + { + SourceError(source, "can't stringize tokens"); + return qfalse; + } //end if + t = PC_CopyToken(&token); + } //end if + else + { + SourceWarning(source, "stringizing operator without define parameter"); + continue; + } //end if + } //end if + else + { + t = PC_CopyToken(dt); + } //end else + //add the token to the list + t->next = NULL; + if (last) last->next = t; + else first = t; + last = t; + } //end else + } //end for + //check for the merging operator + for (t = first; t; ) + { + if (t->next) + { + //if the merging operator + if (t->next->string[0] == '#' && t->next->string[1] == '#') + { + t1 = t; + t2 = t->next->next; + if (t2) + { + if (!PC_MergeTokens(t1, t2)) + { + SourceError(source, "can't merge %s with %s", t1->string, t2->string); + return qfalse; + } //end if + PC_FreeToken(t1->next); + t1->next = t2->next; + if (t2 == last) last = t1; + PC_FreeToken(t2); + continue; + } //end if + } //end if + } //end if + t = t->next; + } //end for + //store the first and last token of the list + *firsttoken = first; + *lasttoken = last; + //free all the parameter tokens + for (i = 0; i < define->numparms; i++) + { + for (pt = parms[i]; pt; pt = nextpt) + { + nextpt = pt->next; + PC_FreeToken(pt); + } //end for + } //end for + // + return qtrue; +} //end of the function PC_ExpandDefine +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ExpandDefineIntoSource(source_t *source, token_t *deftoken, define_t *define) +{ + token_t *firsttoken, *lasttoken; + + if (!PC_ExpandDefine(source, deftoken, define, &firsttoken, &lasttoken)) return qfalse; + + if (firsttoken && lasttoken) + { + lasttoken->next = source->tokens; + source->tokens = firsttoken; + return qtrue; + } //end if + return qfalse; +} //end of the function PC_ExpandDefineIntoSource +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_ConvertPath(char *path) +{ + char *ptr; + + //remove double path seperators + for (ptr = path; *ptr;) + { + if ((*ptr == '\\' || *ptr == '/') && + (*(ptr+1) == '\\' || *(ptr+1) == '/')) + { + strcpy(ptr, ptr+1); + } //end if + else + { + ptr++; + } //end else + } //end while + //set OS dependent path seperators + for (ptr = path; *ptr;) + { + if (*ptr == '/' || *ptr == '\\') *ptr = PATHSEPERATOR_CHAR; + ptr++; + } //end while +} //end of the function PC_ConvertPath +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_include(source_t *source) +{ + script_t *script; + token_t token; + char path[MAX_PATH]; +#ifdef QUAKE + foundfile_t file; +#endif //QUAKE + + if (source->skip > 0) return qtrue; + // + if (!PC_ReadSourceToken(source, &token)) + { + SourceError(source, "#include without file name"); + return qfalse; + } //end if + if (token.linescrossed > 0) + { + SourceError(source, "#include without file name"); + return qfalse; + } //end if + if (token.type == TT_STRING) + { + StripDoubleQuotes(token.string); + PC_ConvertPath(token.string); + script = LoadScriptFile(token.string); + if (!script) + { + strcpy(path, source->includepath); + strcat(path, token.string); + script = LoadScriptFile(path); + } //end if + } //end if + else if (token.type == TT_PUNCTUATION && *token.string == '<') + { + strcpy(path, source->includepath); + while(PC_ReadSourceToken(source, &token)) + { + if (token.linescrossed > 0) + { + PC_UnreadSourceToken(source, &token); + break; + } //end if + if (token.type == TT_PUNCTUATION && *token.string == '>') break; + strncat(path, token.string, MAX_PATH); + } //end while + if (*token.string != '>') + { + SourceWarning(source, "#include missing trailing >"); + } //end if + if (!strlen(path)) + { + SourceError(source, "#include without file name between < >"); + return qfalse; + } //end if + PC_ConvertPath(path); + script = LoadScriptFile(path); + } //end if + else + { + SourceError(source, "#include without file name"); + return qfalse; + } //end else +#ifdef QUAKE + if (!script) + { + Com_Memset(&file, 0, sizeof(foundfile_t)); + script = LoadScriptFile(path); + if (script) strncpy(script->filename, path, MAX_PATH); + } //end if +#endif //QUAKE + if (!script) + { +#ifdef SCREWUP + SourceWarning(source, "file %s not found", path); + return qtrue; +#else + SourceError(source, "file %s not found", path); + return qfalse; +#endif //SCREWUP + } //end if + PC_PushScript(source, script); + return qtrue; +} //end of the function PC_Directive_include +//============================================================================ +// reads a token from the current line, continues reading on the next +// line only if a backslash '\' is encountered. +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ReadLine(source_t *source, token_t *token) +{ + int crossline; + + crossline = 0; + do + { + if (!PC_ReadSourceToken(source, token)) return qfalse; + + if (token->linescrossed > crossline) + { + PC_UnreadSourceToken(source, token); + return qfalse; + } //end if + crossline = 1; + } while(!strcmp(token->string, "\\")); + return qtrue; +} //end of the function PC_ReadLine +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_WhiteSpaceBeforeToken(token_t *token) +{ + return token->endwhitespace_p - token->whitespace_p > 0; +} //end of the function PC_WhiteSpaceBeforeToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_ClearTokenWhiteSpace(token_t *token) +{ + token->whitespace_p = NULL; + token->endwhitespace_p = NULL; + token->linescrossed = 0; +} //end of the function PC_ClearTokenWhiteSpace +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_undef(source_t *source) +{ + token_t token; + define_t *define, *lastdefine; + int hash; + + if (source->skip > 0) return qtrue; + // + if (!PC_ReadLine(source, &token)) + { + SourceError(source, "undef without name"); + return qfalse; + } //end if + if (token.type != TT_NAME) + { + PC_UnreadSourceToken(source, &token); + SourceError(source, "expected name, found %s", token.string); + return qfalse; + } //end if +#if DEFINEHASHING + + hash = PC_NameHash(token.string); + for (lastdefine = NULL, define = source->definehash[hash]; define; define = define->hashnext) + { + if (!strcmp(define->name, token.string)) + { + if (define->flags & DEFINE_FIXED) + { + SourceWarning(source, "can't undef %s", token.string); + } //end if + else + { + if (lastdefine) lastdefine->hashnext = define->hashnext; + else source->definehash[hash] = define->hashnext; + PC_FreeDefine(define); + } //end else + break; + } //end if + lastdefine = define; + } //end for +#else //DEFINEHASHING + for (lastdefine = NULL, define = source->defines; define; define = define->next) + { + if (!strcmp(define->name, token.string)) + { + if (define->flags & DEFINE_FIXED) + { + SourceWarning(source, "can't undef %s", token.string); + } //end if + else + { + if (lastdefine) lastdefine->next = define->next; + else source->defines = define->next; + PC_FreeDefine(define); + } //end else + break; + } //end if + lastdefine = define; + } //end for +#endif //DEFINEHASHING + return qtrue; +} //end of the function PC_Directive_undef +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_define(source_t *source) +{ + token_t token, *t, *last; + define_t *define; + + if (source->skip > 0) return qtrue; + // + if (!PC_ReadLine(source, &token)) + { + SourceError(source, "#define without name"); + return qfalse; + } //end if + if (token.type != TT_NAME) + { + PC_UnreadSourceToken(source, &token); + SourceError(source, "expected name after #define, found %s", token.string); + return qfalse; + } //end if + //check if the define already exists +#if DEFINEHASHING + define = PC_FindHashedDefine(source->definehash, token.string); +#else + define = PC_FindDefine(source->defines, token.string); +#endif //DEFINEHASHING + if (define) + { + if (define->flags & DEFINE_FIXED) + { + SourceError(source, "can't redefine %s", token.string); + return qfalse; + } //end if + SourceWarning(source, "redefinition of %s", token.string); + //unread the define name before executing the #undef directive + PC_UnreadSourceToken(source, &token); + if (!PC_Directive_undef(source)) return qfalse; + //if the define was not removed (define->flags & DEFINE_FIXED) +#if DEFINEHASHING + define = PC_FindHashedDefine(source->definehash, token.string); +#else + define = PC_FindDefine(source->defines, token.string); +#endif //DEFINEHASHING + } //end if + //allocate define + define = (define_t *) GetMemory(sizeof(define_t) + strlen(token.string) + 1); + Com_Memset(define, 0, sizeof(define_t)); + define->name = (char *) define + sizeof(define_t); + strcpy(define->name, token.string); + //add the define to the source +#if DEFINEHASHING + PC_AddDefineToHash(define, source->definehash); +#else //DEFINEHASHING + define->next = source->defines; + source->defines = define; +#endif //DEFINEHASHING + //if nothing is defined, just return + if (!PC_ReadLine(source, &token)) return qtrue; + //if it is a define with parameters + if (!PC_WhiteSpaceBeforeToken(&token) && !strcmp(token.string, "(")) + { + //read the define parameters + last = NULL; + if (!PC_CheckTokenString(source, ")")) + { + while(1) + { + if (!PC_ReadLine(source, &token)) + { + SourceError(source, "expected define parameter"); + return qfalse; + } //end if + //if it isn't a name + if (token.type != TT_NAME) + { + SourceError(source, "invalid define parameter"); + return qfalse; + } //end if + // + if (PC_FindDefineParm(define, token.string) >= 0) + { + SourceError(source, "two the same define parameters"); + return qfalse; + } //end if + //add the define parm + t = PC_CopyToken(&token); + PC_ClearTokenWhiteSpace(t); + t->next = NULL; + if (last) last->next = t; + else define->parms = t; + last = t; + define->numparms++; + //read next token + if (!PC_ReadLine(source, &token)) + { + SourceError(source, "define parameters not terminated"); + return qfalse; + } //end if + // + if (!strcmp(token.string, ")")) break; + //then it must be a comma + if (strcmp(token.string, ",")) + { + SourceError(source, "define not terminated"); + return qfalse; + } //end if + } //end while + } //end if + if (!PC_ReadLine(source, &token)) return qtrue; + } //end if + //read the defined stuff + last = NULL; + do + { + t = PC_CopyToken(&token); + if (t->type == TT_NAME && !strcmp(t->string, define->name)) + { + SourceError(source, "recursive define (removed recursion)"); + continue; + } //end if + PC_ClearTokenWhiteSpace(t); + t->next = NULL; + if (last) last->next = t; + else define->tokens = t; + last = t; + } while(PC_ReadLine(source, &token)); + // + if (last) + { + //check for merge operators at the beginning or end + if (!strcmp(define->tokens->string, "##") || + !strcmp(last->string, "##")) + { + SourceError(source, "define with misplaced ##"); + return qfalse; + } //end if + } //end if + return qtrue; +} //end of the function PC_Directive_define +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +define_t *PC_DefineFromString(char *string) +{ + script_t *script; + source_t src; + token_t *t; + int res, i; + define_t *def; + + PC_InitTokenHeap(); + + script = LoadScriptMemory(string, strlen(string), "*extern"); + //create a new source + Com_Memset(&src, 0, sizeof(source_t)); + strncpy(src.filename, "*extern", MAX_PATH); + src.scriptstack = script; +#if DEFINEHASHING + src.definehash = GetClearedMemory(DEFINEHASHSIZE * sizeof(define_t *)); +#endif //DEFINEHASHING + //create a define from the source + res = PC_Directive_define(&src); + //free any tokens if left + for (t = src.tokens; t; t = src.tokens) + { + src.tokens = src.tokens->next; + PC_FreeToken(t); + } //end for +#ifdef DEFINEHASHING + def = NULL; + for (i = 0; i < DEFINEHASHSIZE; i++) + { + if (src.definehash[i]) + { + def = src.definehash[i]; + break; + } //end if + } //end for +#else + def = src.defines; +#endif //DEFINEHASHING + // +#if DEFINEHASHING + FreeMemory(src.definehash); +#endif //DEFINEHASHING + // + FreeScript(script); + //if the define was created succesfully + if (res > 0) return def; + //free the define is created + if (src.defines) PC_FreeDefine(def); + // + return NULL; +} //end of the function PC_DefineFromString +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_AddDefine(source_t *source, char *string) +{ + define_t *define; + + define = PC_DefineFromString(string); + if (!define) return qfalse; +#if DEFINEHASHING + PC_AddDefineToHash(define, source->definehash); +#else //DEFINEHASHING + define->next = source->defines; + source->defines = define; +#endif //DEFINEHASHING + return qtrue; +} //end of the function PC_AddDefine +//============================================================================ +// add a globals define that will be added to all opened sources +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_AddGlobalDefine(char *string) +{ + define_t *define; + + define = PC_DefineFromString(string); + if (!define) return qfalse; + define->next = globaldefines; + globaldefines = define; + return qtrue; +} //end of the function PC_AddGlobalDefine +//============================================================================ +// remove the given global define +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_RemoveGlobalDefine(char *name) +{ + define_t *define; + + define = PC_FindDefine(globaldefines, name); + if (define) + { + PC_FreeDefine(define); + return qtrue; + } //end if + return qfalse; +} //end of the function PC_RemoveGlobalDefine +//============================================================================ +// remove all globals defines +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_RemoveAllGlobalDefines(void) +{ + define_t *define; + + for (define = globaldefines; define; define = globaldefines) + { + globaldefines = globaldefines->next; + PC_FreeDefine(define); + } //end for +} //end of the function PC_RemoveAllGlobalDefines +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +define_t *PC_CopyDefine(source_t *source, define_t *define) +{ + define_t *newdefine; + token_t *token, *newtoken, *lasttoken; + + newdefine = (define_t *) GetMemory(sizeof(define_t) + strlen(define->name) + 1); + //copy the define name + newdefine->name = (char *) newdefine + sizeof(define_t); + strcpy(newdefine->name, define->name); + newdefine->flags = define->flags; + newdefine->builtin = define->builtin; + newdefine->numparms = define->numparms; + //the define is not linked + newdefine->next = NULL; + newdefine->hashnext = NULL; + //copy the define tokens + newdefine->tokens = NULL; + for (lasttoken = NULL, token = define->tokens; token; token = token->next) + { + newtoken = PC_CopyToken(token); + newtoken->next = NULL; + if (lasttoken) lasttoken->next = newtoken; + else newdefine->tokens = newtoken; + lasttoken = newtoken; + } //end for + //copy the define parameters + newdefine->parms = NULL; + for (lasttoken = NULL, token = define->parms; token; token = token->next) + { + newtoken = PC_CopyToken(token); + newtoken->next = NULL; + if (lasttoken) lasttoken->next = newtoken; + else newdefine->parms = newtoken; + lasttoken = newtoken; + } //end for + return newdefine; +} //end of the function PC_CopyDefine +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_AddGlobalDefinesToSource(source_t *source) +{ + define_t *define, *newdefine; + + for (define = globaldefines; define; define = define->next) + { + newdefine = PC_CopyDefine(source, define); +#if DEFINEHASHING + PC_AddDefineToHash(newdefine, source->definehash); +#else //DEFINEHASHING + newdefine->next = source->defines; + source->defines = newdefine; +#endif //DEFINEHASHING + } //end for +} //end of the function PC_AddGlobalDefinesToSource +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_if_def(source_t *source, int type) +{ + token_t token; + define_t *d; + int skip; + + if (!PC_ReadLine(source, &token)) + { + SourceError(source, "#ifdef without name"); + return qfalse; + } //end if + if (token.type != TT_NAME) + { + PC_UnreadSourceToken(source, &token); + SourceError(source, "expected name after #ifdef, found %s", token.string); + return qfalse; + } //end if +#if DEFINEHASHING + d = PC_FindHashedDefine(source->definehash, token.string); +#else + d = PC_FindDefine(source->defines, token.string); +#endif //DEFINEHASHING + skip = (type == INDENT_IFDEF) == (d == NULL); + PC_PushIndent(source, type, skip); + return qtrue; +} //end of the function PC_Directiveif_def +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_ifdef(source_t *source) +{ + return PC_Directive_if_def(source, INDENT_IFDEF); +} //end of the function PC_Directive_ifdef +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_ifndef(source_t *source) +{ + return PC_Directive_if_def(source, INDENT_IFNDEF); +} //end of the function PC_Directive_ifndef +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_else(source_t *source) +{ + int type, skip; + + PC_PopIndent(source, &type, &skip); + if (!type) + { + SourceError(source, "misplaced #else"); + return qfalse; + } //end if + if (type == INDENT_ELSE) + { + SourceError(source, "#else after #else"); + return qfalse; + } //end if + PC_PushIndent(source, INDENT_ELSE, !skip); + return qtrue; +} //end of the function PC_Directive_else +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_endif(source_t *source) +{ + int type, skip; + + PC_PopIndent(source, &type, &skip); + if (!type) + { + SourceError(source, "misplaced #endif"); + return qfalse; + } //end if + return qtrue; +} //end of the function PC_Directive_endif +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +typedef struct operator_s +{ + int operator; + int priority; + int parentheses; + struct operator_s *prev, *next; +} operator_t; + +typedef struct value_s +{ + signed long int intvalue; + double floatvalue; + int parentheses; + struct value_s *prev, *next; +} value_t; + +int PC_OperatorPriority(int op) +{ + switch(op) + { + case P_MUL: return 15; + case P_DIV: return 15; + case P_MOD: return 15; + case P_ADD: return 14; + case P_SUB: return 14; + + case P_LOGIC_AND: return 7; + case P_LOGIC_OR: return 6; + case P_LOGIC_GEQ: return 12; + case P_LOGIC_LEQ: return 12; + case P_LOGIC_EQ: return 11; + case P_LOGIC_UNEQ: return 11; + + case P_LOGIC_NOT: return 16; + case P_LOGIC_GREATER: return 12; + case P_LOGIC_LESS: return 12; + + case P_RSHIFT: return 13; + case P_LSHIFT: return 13; + + case P_BIN_AND: return 10; + case P_BIN_OR: return 8; + case P_BIN_XOR: return 9; + case P_BIN_NOT: return 16; + + case P_COLON: return 5; + case P_QUESTIONMARK: return 5; + } //end switch + return qfalse; +} //end of the function PC_OperatorPriority + +//#define AllocValue() GetClearedMemory(sizeof(value_t)); +//#define FreeValue(val) FreeMemory(val) +//#define AllocOperator(op) op = (operator_t *) GetClearedMemory(sizeof(operator_t)); +//#define FreeOperator(op) FreeMemory(op); + +#define MAX_VALUES 64 +#define MAX_OPERATORS 64 +#define AllocValue(val) \ + if (numvalues >= MAX_VALUES) { \ + SourceError(source, "out of value space\n"); \ + error = 1; \ + break; \ + } \ + else \ + val = &value_heap[numvalues++]; +#define FreeValue(val) +// +#define AllocOperator(op) \ + if (numoperators >= MAX_OPERATORS) { \ + SourceError(source, "out of operator space\n"); \ + error = 1; \ + break; \ + } \ + else \ + op = &operator_heap[numoperators++]; +#define FreeOperator(op) + +int PC_EvaluateTokens(source_t *source, token_t *tokens, signed long int *intvalue, + double *floatvalue, int integer) +{ + operator_t *o, *firstoperator, *lastoperator; + value_t *v, *firstvalue, *lastvalue, *v1, *v2; + token_t *t; + int brace = 0; + int parentheses = 0; + int error = 0; + int lastwasvalue = 0; + int negativevalue = 0; + int questmarkintvalue = 0; + double questmarkfloatvalue = 0; + int gotquestmarkvalue = qfalse; + int lastoperatortype = 0; + // + operator_t operator_heap[MAX_OPERATORS]; + int numoperators = 0; + value_t value_heap[MAX_VALUES]; + int numvalues = 0; + + firstoperator = lastoperator = NULL; + firstvalue = lastvalue = NULL; + if (intvalue) *intvalue = 0; + if (floatvalue) *floatvalue = 0; + for (t = tokens; t; t = t->next) + { + switch(t->type) + { + case TT_NAME: + { + if (lastwasvalue || negativevalue) + { + SourceError(source, "syntax error in #if/#elif"); + error = 1; + break; + } //end if + if (strcmp(t->string, "defined")) + { + SourceError(source, "undefined name %s in #if/#elif", t->string); + error = 1; + break; + } //end if + t = t->next; + if (!strcmp(t->string, "(")) + { + brace = qtrue; + t = t->next; + } //end if + if (!t || t->type != TT_NAME) + { + SourceError(source, "defined without name in #if/#elif"); + error = 1; + break; + } //end if + //v = (value_t *) GetClearedMemory(sizeof(value_t)); + AllocValue(v); +#if DEFINEHASHING + if (PC_FindHashedDefine(source->definehash, t->string)) +#else + if (PC_FindDefine(source->defines, t->string)) +#endif //DEFINEHASHING + { + v->intvalue = 1; + v->floatvalue = 1; + } //end if + else + { + v->intvalue = 0; + v->floatvalue = 0; + } //end else + v->parentheses = parentheses; + v->next = NULL; + v->prev = lastvalue; + if (lastvalue) lastvalue->next = v; + else firstvalue = v; + lastvalue = v; + if (brace) + { + t = t->next; + if (!t || strcmp(t->string, ")")) + { + SourceError(source, "defined without ) in #if/#elif"); + error = 1; + break; + } //end if + } //end if + brace = qfalse; + // defined() creates a value + lastwasvalue = 1; + break; + } //end case + case TT_NUMBER: + { + if (lastwasvalue) + { + SourceError(source, "syntax error in #if/#elif"); + error = 1; + break; + } //end if + //v = (value_t *) GetClearedMemory(sizeof(value_t)); + AllocValue(v); + if (negativevalue) + { + v->intvalue = - (signed int) t->intvalue; + v->floatvalue = - t->floatvalue; + } //end if + else + { + v->intvalue = t->intvalue; + v->floatvalue = t->floatvalue; + } //end else + v->parentheses = parentheses; + v->next = NULL; + v->prev = lastvalue; + if (lastvalue) lastvalue->next = v; + else firstvalue = v; + lastvalue = v; + //last token was a value + lastwasvalue = 1; + // + negativevalue = 0; + break; + } //end case + case TT_PUNCTUATION: + { + if (negativevalue) + { + SourceError(source, "misplaced minus sign in #if/#elif"); + error = 1; + break; + } //end if + if (t->subtype == P_PARENTHESESOPEN) + { + parentheses++; + break; + } //end if + else if (t->subtype == P_PARENTHESESCLOSE) + { + parentheses--; + if (parentheses < 0) + { + SourceError(source, "too many ) in #if/#elsif"); + error = 1; + } //end if + break; + } //end else if + //check for invalid operators on floating point values + if (!integer) + { + if (t->subtype == P_BIN_NOT || t->subtype == P_MOD || + t->subtype == P_RSHIFT || t->subtype == P_LSHIFT || + t->subtype == P_BIN_AND || t->subtype == P_BIN_OR || + t->subtype == P_BIN_XOR) + { + SourceError(source, "illigal operator %s on floating point operands\n", t->string); + error = 1; + break; + } //end if + } //end if + switch(t->subtype) + { + case P_LOGIC_NOT: + case P_BIN_NOT: + { + if (lastwasvalue) + { + SourceError(source, "! or ~ after value in #if/#elif"); + error = 1; + break; + } //end if + break; + } //end case + case P_INC: + case P_DEC: + { + SourceError(source, "++ or -- used in #if/#elif"); + break; + } //end case + case P_SUB: + { + if (!lastwasvalue) + { + negativevalue = 1; + break; + } //end if + } //end case + + case P_MUL: + case P_DIV: + case P_MOD: + case P_ADD: + + case P_LOGIC_AND: + case P_LOGIC_OR: + case P_LOGIC_GEQ: + case P_LOGIC_LEQ: + case P_LOGIC_EQ: + case P_LOGIC_UNEQ: + + case P_LOGIC_GREATER: + case P_LOGIC_LESS: + + case P_RSHIFT: + case P_LSHIFT: + + case P_BIN_AND: + case P_BIN_OR: + case P_BIN_XOR: + + case P_COLON: + case P_QUESTIONMARK: + { + if (!lastwasvalue) + { + SourceError(source, "operator %s after operator in #if/#elif", t->string); + error = 1; + break; + } //end if + break; + } //end case + default: + { + SourceError(source, "invalid operator %s in #if/#elif", t->string); + error = 1; + break; + } //end default + } //end switch + if (!error && !negativevalue) + { + //o = (operator_t *) GetClearedMemory(sizeof(operator_t)); + AllocOperator(o); + o->operator = t->subtype; + o->priority = PC_OperatorPriority(t->subtype); + o->parentheses = parentheses; + o->next = NULL; + o->prev = lastoperator; + if (lastoperator) lastoperator->next = o; + else firstoperator = o; + lastoperator = o; + lastwasvalue = 0; + } //end if + break; + } //end case + default: + { + SourceError(source, "unknown %s in #if/#elif", t->string); + error = 1; + break; + } //end default + } //end switch + if (error) break; + } //end for + if (!error) + { + if (!lastwasvalue) + { + SourceError(source, "trailing operator in #if/#elif"); + error = 1; + } //end if + else if (parentheses) + { + SourceError(source, "too many ( in #if/#elif"); + error = 1; + } //end else if + } //end if + // + gotquestmarkvalue = qfalse; + questmarkintvalue = 0; + questmarkfloatvalue = 0; + //while there are operators + while(!error && firstoperator) + { + v = firstvalue; + for (o = firstoperator; o->next; o = o->next) + { + //if the current operator is nested deeper in parentheses + //than the next operator + if (o->parentheses > o->next->parentheses) break; + //if the current and next operator are nested equally deep in parentheses + if (o->parentheses == o->next->parentheses) + { + //if the priority of the current operator is equal or higher + //than the priority of the next operator + if (o->priority >= o->next->priority) break; + } //end if + //if the arity of the operator isn't equal to 1 + if (o->operator != P_LOGIC_NOT + && o->operator != P_BIN_NOT) v = v->next; + //if there's no value or no next value + if (!v) + { + SourceError(source, "mising values in #if/#elif"); + error = 1; + break; + } //end if + } //end for + if (error) break; + v1 = v; + v2 = v->next; +#ifdef DEBUG_EVAL + if (integer) + { + Log_Write("operator %s, value1 = %d", PunctuationFromNum(source->scriptstack, o->operator), v1->intvalue); + if (v2) Log_Write("value2 = %d", v2->intvalue); + } //end if + else + { + Log_Write("operator %s, value1 = %f", PunctuationFromNum(source->scriptstack, o->operator), v1->floatvalue); + if (v2) Log_Write("value2 = %f", v2->floatvalue); + } //end else +#endif //DEBUG_EVAL + switch(o->operator) + { + case P_LOGIC_NOT: v1->intvalue = !v1->intvalue; + v1->floatvalue = !v1->floatvalue; break; + case P_BIN_NOT: v1->intvalue = ~v1->intvalue; + break; + case P_MUL: v1->intvalue *= v2->intvalue; + v1->floatvalue *= v2->floatvalue; break; + case P_DIV: if (!v2->intvalue || !v2->floatvalue) + { + SourceError(source, "divide by zero in #if/#elif\n"); + error = 1; + break; + } + v1->intvalue /= v2->intvalue; + v1->floatvalue /= v2->floatvalue; break; + case P_MOD: if (!v2->intvalue) + { + SourceError(source, "divide by zero in #if/#elif\n"); + error = 1; + break; + } + v1->intvalue %= v2->intvalue; break; + case P_ADD: v1->intvalue += v2->intvalue; + v1->floatvalue += v2->floatvalue; break; + case P_SUB: v1->intvalue -= v2->intvalue; + v1->floatvalue -= v2->floatvalue; break; + case P_LOGIC_AND: v1->intvalue = v1->intvalue && v2->intvalue; + v1->floatvalue = v1->floatvalue && v2->floatvalue; break; + case P_LOGIC_OR: v1->intvalue = v1->intvalue || v2->intvalue; + v1->floatvalue = v1->floatvalue || v2->floatvalue; break; + case P_LOGIC_GEQ: v1->intvalue = v1->intvalue >= v2->intvalue; + v1->floatvalue = v1->floatvalue >= v2->floatvalue; break; + case P_LOGIC_LEQ: v1->intvalue = v1->intvalue <= v2->intvalue; + v1->floatvalue = v1->floatvalue <= v2->floatvalue; break; + case P_LOGIC_EQ: v1->intvalue = v1->intvalue == v2->intvalue; + v1->floatvalue = v1->floatvalue == v2->floatvalue; break; + case P_LOGIC_UNEQ: v1->intvalue = v1->intvalue != v2->intvalue; + v1->floatvalue = v1->floatvalue != v2->floatvalue; break; + case P_LOGIC_GREATER: v1->intvalue = v1->intvalue > v2->intvalue; + v1->floatvalue = v1->floatvalue > v2->floatvalue; break; + case P_LOGIC_LESS: v1->intvalue = v1->intvalue < v2->intvalue; + v1->floatvalue = v1->floatvalue < v2->floatvalue; break; + case P_RSHIFT: v1->intvalue >>= v2->intvalue; + break; + case P_LSHIFT: v1->intvalue <<= v2->intvalue; + break; + case P_BIN_AND: v1->intvalue &= v2->intvalue; + break; + case P_BIN_OR: v1->intvalue |= v2->intvalue; + break; + case P_BIN_XOR: v1->intvalue ^= v2->intvalue; + break; + case P_COLON: + { + if (!gotquestmarkvalue) + { + SourceError(source, ": without ? in #if/#elif"); + error = 1; + break; + } //end if + if (integer) + { + if (!questmarkintvalue) v1->intvalue = v2->intvalue; + } //end if + else + { + if (!questmarkfloatvalue) v1->floatvalue = v2->floatvalue; + } //end else + gotquestmarkvalue = qfalse; + break; + } //end case + case P_QUESTIONMARK: + { + if (gotquestmarkvalue) + { + SourceError(source, "? after ? in #if/#elif"); + error = 1; + break; + } //end if + questmarkintvalue = v1->intvalue; + questmarkfloatvalue = v1->floatvalue; + gotquestmarkvalue = qtrue; + break; + } //end if + } //end switch +#ifdef DEBUG_EVAL + if (integer) Log_Write("result value = %d", v1->intvalue); + else Log_Write("result value = %f", v1->floatvalue); +#endif //DEBUG_EVAL + if (error) break; + lastoperatortype = o->operator; + //if not an operator with arity 1 + if (o->operator != P_LOGIC_NOT + && o->operator != P_BIN_NOT) + { + //remove the second value if not question mark operator + if (o->operator != P_QUESTIONMARK) v = v->next; + // + if (v->prev) v->prev->next = v->next; + else firstvalue = v->next; + if (v->next) v->next->prev = v->prev; + else lastvalue = v->prev; + //FreeMemory(v); + FreeValue(v); + } //end if + //remove the operator + if (o->prev) o->prev->next = o->next; + else firstoperator = o->next; + if (o->next) o->next->prev = o->prev; + else lastoperator = o->prev; + //FreeMemory(o); + FreeOperator(o); + } //end while + if (firstvalue) + { + if (intvalue) *intvalue = firstvalue->intvalue; + if (floatvalue) *floatvalue = firstvalue->floatvalue; + } //end if + for (o = firstoperator; o; o = lastoperator) + { + lastoperator = o->next; + //FreeMemory(o); + FreeOperator(o); + } //end for + for (v = firstvalue; v; v = lastvalue) + { + lastvalue = v->next; + //FreeMemory(v); + FreeValue(v); + } //end for + if (!error) return qtrue; + if (intvalue) *intvalue = 0; + if (floatvalue) *floatvalue = 0; + return qfalse; +} //end of the function PC_EvaluateTokens +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Evaluate(source_t *source, signed long int *intvalue, + double *floatvalue, int integer) +{ + token_t token, *firsttoken, *lasttoken; + token_t *t, *nexttoken; + define_t *define; + int defined = qfalse; + + if (intvalue) *intvalue = 0; + if (floatvalue) *floatvalue = 0; + // + if (!PC_ReadLine(source, &token)) + { + SourceError(source, "no value after #if/#elif"); + return qfalse; + } //end if + firsttoken = NULL; + lasttoken = NULL; + do + { + //if the token is a name + if (token.type == TT_NAME) + { + if (defined) + { + defined = qfalse; + t = PC_CopyToken(&token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } //end if + else if (!strcmp(token.string, "defined")) + { + defined = qtrue; + t = PC_CopyToken(&token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } //end if + else + { + //then it must be a define +#if DEFINEHASHING + define = PC_FindHashedDefine(source->definehash, token.string); +#else + define = PC_FindDefine(source->defines, token.string); +#endif //DEFINEHASHING + if (!define) + { + SourceError(source, "can't evaluate %s, not defined", token.string); + return qfalse; + } //end if + if (!PC_ExpandDefineIntoSource(source, &token, define)) return qfalse; + } //end else + } //end if + //if the token is a number or a punctuation + else if (token.type == TT_NUMBER || token.type == TT_PUNCTUATION) + { + t = PC_CopyToken(&token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } //end else + else //can't evaluate the token + { + SourceError(source, "can't evaluate %s", token.string); + return qfalse; + } //end else + } while(PC_ReadLine(source, &token)); + // + if (!PC_EvaluateTokens(source, firsttoken, intvalue, floatvalue, integer)) return qfalse; + // +#ifdef DEBUG_EVAL + Log_Write("eval:"); +#endif //DEBUG_EVAL + for (t = firsttoken; t; t = nexttoken) + { +#ifdef DEBUG_EVAL + Log_Write(" %s", t->string); +#endif //DEBUG_EVAL + nexttoken = t->next; + PC_FreeToken(t); + } //end for +#ifdef DEBUG_EVAL + if (integer) Log_Write("eval result: %d", *intvalue); + else Log_Write("eval result: %f", *floatvalue); +#endif //DEBUG_EVAL + // + return qtrue; +} //end of the function PC_Evaluate +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_DollarEvaluate(source_t *source, signed long int *intvalue, + double *floatvalue, int integer) +{ + int indent, defined = qfalse; + token_t token, *firsttoken, *lasttoken; + token_t *t, *nexttoken; + define_t *define; + + if (intvalue) *intvalue = 0; + if (floatvalue) *floatvalue = 0; + // + if (!PC_ReadSourceToken(source, &token)) + { + SourceError(source, "no leading ( after $evalint/$evalfloat"); + return qfalse; + } //end if + if (!PC_ReadSourceToken(source, &token)) + { + SourceError(source, "nothing to evaluate"); + return qfalse; + } //end if + indent = 1; + firsttoken = NULL; + lasttoken = NULL; + do + { + //if the token is a name + if (token.type == TT_NAME) + { + if (defined) + { + defined = qfalse; + t = PC_CopyToken(&token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } //end if + else if (!strcmp(token.string, "defined")) + { + defined = qtrue; + t = PC_CopyToken(&token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } //end if + else + { + //then it must be a define +#if DEFINEHASHING + define = PC_FindHashedDefine(source->definehash, token.string); +#else + define = PC_FindDefine(source->defines, token.string); +#endif //DEFINEHASHING + if (!define) + { + SourceError(source, "can't evaluate %s, not defined", token.string); + return qfalse; + } //end if + if (!PC_ExpandDefineIntoSource(source, &token, define)) return qfalse; + } //end else + } //end if + //if the token is a number or a punctuation + else if (token.type == TT_NUMBER || token.type == TT_PUNCTUATION) + { + if (*token.string == '(') indent++; + else if (*token.string == ')') indent--; + if (indent <= 0) break; + t = PC_CopyToken(&token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } //end else + else //can't evaluate the token + { + SourceError(source, "can't evaluate %s", token.string); + return qfalse; + } //end else + } while(PC_ReadSourceToken(source, &token)); + // + if (!PC_EvaluateTokens(source, firsttoken, intvalue, floatvalue, integer)) return qfalse; + // +#ifdef DEBUG_EVAL + Log_Write("$eval:"); +#endif //DEBUG_EVAL + for (t = firsttoken; t; t = nexttoken) + { +#ifdef DEBUG_EVAL + Log_Write(" %s", t->string); +#endif //DEBUG_EVAL + nexttoken = t->next; + PC_FreeToken(t); + } //end for +#ifdef DEBUG_EVAL + if (integer) Log_Write("$eval result: %d", *intvalue); + else Log_Write("$eval result: %f", *floatvalue); +#endif //DEBUG_EVAL + // + return qtrue; +} //end of the function PC_DollarEvaluate +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_elif(source_t *source) +{ + signed long int value; + int type, skip; + + PC_PopIndent(source, &type, &skip); + if (!type || type == INDENT_ELSE) + { + SourceError(source, "misplaced #elif"); + return qfalse; + } //end if + if (!PC_Evaluate(source, &value, NULL, qtrue)) return qfalse; + skip = (value == 0); + PC_PushIndent(source, INDENT_ELIF, skip); + return qtrue; +} //end of the function PC_Directive_elif +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_if(source_t *source) +{ + signed long int value; + int skip; + + if (!PC_Evaluate(source, &value, NULL, qtrue)) return qfalse; + skip = (value == 0); + PC_PushIndent(source, INDENT_IF, skip); + return qtrue; +} //end of the function PC_Directive +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_line(source_t *source) +{ + SourceError(source, "#line directive not supported"); + return qfalse; +} //end of the function PC_Directive_line +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_error(source_t *source) +{ + token_t token; + + strcpy(token.string, ""); + PC_ReadSourceToken(source, &token); + SourceError(source, "#error directive: %s", token.string); + return qfalse; +} //end of the function PC_Directive_error +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_pragma(source_t *source) +{ + token_t token; + + SourceWarning(source, "#pragma directive not supported"); + while(PC_ReadLine(source, &token)) ; + return qtrue; +} //end of the function PC_Directive_pragma +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void UnreadSignToken(source_t *source) +{ + token_t token; + + token.line = source->scriptstack->line; + token.whitespace_p = source->scriptstack->script_p; + token.endwhitespace_p = source->scriptstack->script_p; + token.linescrossed = 0; + strcpy(token.string, "-"); + token.type = TT_PUNCTUATION; + token.subtype = P_SUB; + PC_UnreadSourceToken(source, &token); +} //end of the function UnreadSignToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_eval(source_t *source) +{ + signed long int value; + token_t token; + + if (!PC_Evaluate(source, &value, NULL, qtrue)) return qfalse; + // + token.line = source->scriptstack->line; + token.whitespace_p = source->scriptstack->script_p; + token.endwhitespace_p = source->scriptstack->script_p; + token.linescrossed = 0; + sprintf(token.string, "%d", abs(value)); + token.type = TT_NUMBER; + token.subtype = TT_INTEGER|TT_LONG|TT_DECIMAL; + PC_UnreadSourceToken(source, &token); + if (value < 0) UnreadSignToken(source); + return qtrue; +} //end of the function PC_Directive_eval +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_evalfloat(source_t *source) +{ + double value; + token_t token; + + if (!PC_Evaluate(source, NULL, &value, qfalse)) return qfalse; + token.line = source->scriptstack->line; + token.whitespace_p = source->scriptstack->script_p; + token.endwhitespace_p = source->scriptstack->script_p; + token.linescrossed = 0; + sprintf(token.string, "%1.2f", fabs(value)); + token.type = TT_NUMBER; + token.subtype = TT_FLOAT|TT_LONG|TT_DECIMAL; + PC_UnreadSourceToken(source, &token); + if (value < 0) UnreadSignToken(source); + return qtrue; +} //end of the function PC_Directive_evalfloat +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +directive_t directives[20] = +{ + {"if", PC_Directive_if}, + {"ifdef", PC_Directive_ifdef}, + {"ifndef", PC_Directive_ifndef}, + {"elif", PC_Directive_elif}, + {"else", PC_Directive_else}, + {"endif", PC_Directive_endif}, + {"include", PC_Directive_include}, + {"define", PC_Directive_define}, + {"undef", PC_Directive_undef}, + {"line", PC_Directive_line}, + {"error", PC_Directive_error}, + {"pragma", PC_Directive_pragma}, + {"eval", PC_Directive_eval}, + {"evalfloat", PC_Directive_evalfloat}, + {NULL, NULL} +}; + +int PC_ReadDirective(source_t *source) +{ + token_t token; + int i; + + //read the directive name + if (!PC_ReadSourceToken(source, &token)) + { + SourceError(source, "found # without name"); + return qfalse; + } //end if + //directive name must be on the same line + if (token.linescrossed > 0) + { + PC_UnreadSourceToken(source, &token); + SourceError(source, "found # at end of line"); + return qfalse; + } //end if + //if if is a name + if (token.type == TT_NAME) + { + //find the precompiler directive + for (i = 0; directives[i].name; i++) + { + if (!strcmp(directives[i].name, token.string)) + { + return directives[i].func(source); + } //end if + } //end for + } //end if + SourceError(source, "unknown precompiler directive %s", token.string); + return qfalse; +} //end of the function PC_ReadDirective +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_DollarDirective_evalint(source_t *source) +{ + signed long int value; + token_t token; + + if (!PC_DollarEvaluate(source, &value, NULL, qtrue)) return qfalse; + // + token.line = source->scriptstack->line; + token.whitespace_p = source->scriptstack->script_p; + token.endwhitespace_p = source->scriptstack->script_p; + token.linescrossed = 0; + sprintf(token.string, "%d", abs(value)); + token.type = TT_NUMBER; + token.subtype = TT_INTEGER|TT_LONG|TT_DECIMAL; +#ifdef NUMBERVALUE + token.intvalue = value; + token.floatvalue = value; +#endif //NUMBERVALUE + PC_UnreadSourceToken(source, &token); + if (value < 0) UnreadSignToken(source); + return qtrue; +} //end of the function PC_DollarDirective_evalint +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_DollarDirective_evalfloat(source_t *source) +{ + double value; + token_t token; + + if (!PC_DollarEvaluate(source, NULL, &value, qfalse)) return qfalse; + token.line = source->scriptstack->line; + token.whitespace_p = source->scriptstack->script_p; + token.endwhitespace_p = source->scriptstack->script_p; + token.linescrossed = 0; + sprintf(token.string, "%1.2f", fabs(value)); + token.type = TT_NUMBER; + token.subtype = TT_FLOAT|TT_LONG|TT_DECIMAL; +#ifdef NUMBERVALUE + token.intvalue = (unsigned long) value; + token.floatvalue = value; +#endif //NUMBERVALUE + PC_UnreadSourceToken(source, &token); + if (value < 0) UnreadSignToken(source); + return qtrue; +} //end of the function PC_DollarDirective_evalfloat +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +directive_t dollardirectives[20] = +{ + {"evalint", PC_DollarDirective_evalint}, + {"evalfloat", PC_DollarDirective_evalfloat}, + {NULL, NULL} +}; + +int PC_ReadDollarDirective(source_t *source) +{ + token_t token; + int i; + + //read the directive name + if (!PC_ReadSourceToken(source, &token)) + { + SourceError(source, "found $ without name"); + return qfalse; + } //end if + //directive name must be on the same line + if (token.linescrossed > 0) + { + PC_UnreadSourceToken(source, &token); + SourceError(source, "found $ at end of line"); + return qfalse; + } //end if + //if if is a name + if (token.type == TT_NAME) + { + //find the precompiler directive + for (i = 0; dollardirectives[i].name; i++) + { + if (!strcmp(dollardirectives[i].name, token.string)) + { + return dollardirectives[i].func(source); + } //end if + } //end for + } //end if + PC_UnreadSourceToken(source, &token); + SourceError(source, "unknown precompiler directive %s", token.string); + return qfalse; +} //end of the function PC_ReadDirective + +#ifdef QUAKEC +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int BuiltinFunction(source_t *source) +{ + token_t token; + + if (!PC_ReadSourceToken(source, &token)) return qfalse; + if (token.type == TT_NUMBER) + { + PC_UnreadSourceToken(source, &token); + return qtrue; + } //end if + else + { + PC_UnreadSourceToken(source, &token); + return qfalse; + } //end else +} //end of the function BuiltinFunction +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int QuakeCMacro(source_t *source) +{ + int i; + token_t token; + + if (!PC_ReadSourceToken(source, &token)) return qtrue; + if (token.type != TT_NAME) + { + PC_UnreadSourceToken(source, &token); + return qtrue; + } //end if + //find the precompiler directive + for (i = 0; dollardirectives[i].name; i++) + { + if (!strcmp(dollardirectives[i].name, token.string)) + { + PC_UnreadSourceToken(source, &token); + return qfalse; + } //end if + } //end for + PC_UnreadSourceToken(source, &token); + return qtrue; +} //end of the function QuakeCMacro +#endif //QUAKEC +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ReadToken(source_t *source, token_t *token) +{ + define_t *define; + + while(1) + { + if (!PC_ReadSourceToken(source, token)) return qfalse; + //check for precompiler directives + if (token->type == TT_PUNCTUATION && *token->string == '#') + { +#ifdef QUAKEC + if (!BuiltinFunction(source)) +#endif //QUAKC + { + //read the precompiler directive + if (!PC_ReadDirective(source)) return qfalse; + continue; + } //end if + } //end if + if (token->type == TT_PUNCTUATION && *token->string == '$') + { +#ifdef QUAKEC + if (!QuakeCMacro(source)) +#endif //QUAKEC + { + //read the precompiler directive + if (!PC_ReadDollarDirective(source)) return qfalse; + continue; + } //end if + } //end if + // recursively concatenate strings that are behind each other still resolving defines + if (token->type == TT_STRING) + { + token_t newtoken; + if (PC_ReadToken(source, &newtoken)) + { + if (newtoken.type == TT_STRING) + { + token->string[strlen(token->string)-1] = '\0'; + if (strlen(token->string) + strlen(newtoken.string+1) + 1 >= MAX_TOKEN) + { + SourceError(source, "string longer than MAX_TOKEN %d\n", MAX_TOKEN); + return qfalse; + } + strcat(token->string, newtoken.string+1); + } + else + { + PC_UnreadToken(source, &newtoken); + } + } + } //end if + //if skipping source because of conditional compilation + if (source->skip) continue; + //if the token is a name + if (token->type == TT_NAME) + { + //check if the name is a define macro +#if DEFINEHASHING + define = PC_FindHashedDefine(source->definehash, token->string); +#else + define = PC_FindDefine(source->defines, token->string); +#endif //DEFINEHASHING + //if it is a define macro + if (define) + { + //expand the defined macro + if (!PC_ExpandDefineIntoSource(source, token, define)) return qfalse; + continue; + } //end if + } //end if + //copy token for unreading + Com_Memcpy(&source->token, token, sizeof(token_t)); + //found a token + return qtrue; + } //end while +} //end of the function PC_ReadToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ExpectTokenString(source_t *source, char *string) +{ + token_t token; + + if (!PC_ReadToken(source, &token)) + { + SourceError(source, "couldn't find expected %s", string); + return qfalse; + } //end if + + if (strcmp(token.string, string)) + { + SourceError(source, "expected %s, found %s", string, token.string); + return qfalse; + } //end if + return qtrue; +} //end of the function PC_ExpectTokenString +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ExpectTokenType(source_t *source, int type, int subtype, token_t *token) +{ + char str[MAX_TOKEN]; + + if (!PC_ReadToken(source, token)) + { + SourceError(source, "couldn't read expected token"); + return qfalse; + } //end if + + if (token->type != type) + { + strcpy(str, ""); + if (type == TT_STRING) strcpy(str, "string"); + if (type == TT_LITERAL) strcpy(str, "literal"); + if (type == TT_NUMBER) strcpy(str, "number"); + if (type == TT_NAME) strcpy(str, "name"); + if (type == TT_PUNCTUATION) strcpy(str, "punctuation"); + SourceError(source, "expected a %s, found %s", str, token->string); + return qfalse; + } //end if + if (token->type == TT_NUMBER) + { + if ((token->subtype & subtype) != subtype) + { + if (subtype & TT_DECIMAL) strcpy(str, "decimal"); + if (subtype & TT_HEX) strcpy(str, "hex"); + if (subtype & TT_OCTAL) strcpy(str, "octal"); + if (subtype & TT_BINARY) strcpy(str, "binary"); + if (subtype & TT_LONG) strcat(str, " long"); + if (subtype & TT_UNSIGNED) strcat(str, " unsigned"); + if (subtype & TT_FLOAT) strcat(str, " float"); + if (subtype & TT_INTEGER) strcat(str, " integer"); + SourceError(source, "expected %s, found %s", str, token->string); + return qfalse; + } //end if + } //end if + else if (token->type == TT_PUNCTUATION) + { + if (token->subtype != subtype) + { + SourceError(source, "found %s", token->string); + return qfalse; + } //end if + } //end else if + return qtrue; +} //end of the function PC_ExpectTokenType +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ExpectAnyToken(source_t *source, token_t *token) +{ + if (!PC_ReadToken(source, token)) + { + SourceError(source, "couldn't read expected token"); + return qfalse; + } //end if + else + { + return qtrue; + } //end else +} //end of the function PC_ExpectAnyToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_CheckTokenString(source_t *source, char *string) +{ + token_t tok; + + if (!PC_ReadToken(source, &tok)) return qfalse; + //if the token is available + if (!strcmp(tok.string, string)) return qtrue; + // + PC_UnreadSourceToken(source, &tok); + return qfalse; +} //end of the function PC_CheckTokenString +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_CheckTokenType(source_t *source, int type, int subtype, token_t *token) +{ + token_t tok; + + if (!PC_ReadToken(source, &tok)) return qfalse; + //if the type matches + if (tok.type == type && + (tok.subtype & subtype) == subtype) + { + Com_Memcpy(token, &tok, sizeof(token_t)); + return qtrue; + } //end if + // + PC_UnreadSourceToken(source, &tok); + return qfalse; +} //end of the function PC_CheckTokenType +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_SkipUntilString(source_t *source, char *string) +{ + token_t token; + + while(PC_ReadToken(source, &token)) + { + if (!strcmp(token.string, string)) return qtrue; + } //end while + return qfalse; +} //end of the function PC_SkipUntilString +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_UnreadLastToken(source_t *source) +{ + PC_UnreadSourceToken(source, &source->token); +} //end of the function PC_UnreadLastToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_UnreadToken(source_t *source, token_t *token) +{ + PC_UnreadSourceToken(source, token); +} //end of the function PC_UnreadToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_SetIncludePath(source_t *source, char *path) +{ + strncpy(source->includepath, path, MAX_PATH); + //add trailing path seperator + if (source->includepath[strlen(source->includepath)-1] != '\\' && + source->includepath[strlen(source->includepath)-1] != '/') + { + strcat(source->includepath, PATHSEPERATOR_STR); + } //end if +} //end of the function PC_SetIncludePath +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_SetPunctuations(source_t *source, punctuation_t *p) +{ + source->punctuations = p; +} //end of the function PC_SetPunctuations +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +source_t *LoadSourceFile(const char *filename) +{ + source_t *source; + script_t *script; + + PC_InitTokenHeap(); + + script = LoadScriptFile(filename); + if (!script) return NULL; + + script->next = NULL; + + source = (source_t *) GetMemory(sizeof(source_t)); + Com_Memset(source, 0, sizeof(source_t)); + + strncpy(source->filename, filename, MAX_PATH); + source->scriptstack = script; + source->tokens = NULL; + source->defines = NULL; + source->indentstack = NULL; + source->skip = 0; + +#if DEFINEHASHING + source->definehash = GetClearedMemory(DEFINEHASHSIZE * sizeof(define_t *)); +#endif //DEFINEHASHING + PC_AddGlobalDefinesToSource(source); + return source; +} //end of the function LoadSourceFile +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +source_t *LoadSourceMemory(char *ptr, int length, char *name) +{ + source_t *source; + script_t *script; + + PC_InitTokenHeap(); + + script = LoadScriptMemory(ptr, length, name); + if (!script) return NULL; + script->next = NULL; + + source = (source_t *) GetMemory(sizeof(source_t)); + Com_Memset(source, 0, sizeof(source_t)); + + strncpy(source->filename, name, MAX_PATH); + source->scriptstack = script; + source->tokens = NULL; + source->defines = NULL; + source->indentstack = NULL; + source->skip = 0; + +#if DEFINEHASHING + source->definehash = GetClearedMemory(DEFINEHASHSIZE * sizeof(define_t *)); +#endif //DEFINEHASHING + PC_AddGlobalDefinesToSource(source); + return source; +} //end of the function LoadSourceMemory +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void FreeSource(source_t *source) +{ + script_t *script; + token_t *token; + define_t *define; + indent_t *indent; + int i; + + //PC_PrintDefineHashTable(source->definehash); + //free all the scripts + while(source->scriptstack) + { + script = source->scriptstack; + source->scriptstack = source->scriptstack->next; + FreeScript(script); + } //end for + //free all the tokens + while(source->tokens) + { + token = source->tokens; + source->tokens = source->tokens->next; + PC_FreeToken(token); + } //end for +#if DEFINEHASHING + for (i = 0; i < DEFINEHASHSIZE; i++) + { + while(source->definehash[i]) + { + define = source->definehash[i]; + source->definehash[i] = source->definehash[i]->hashnext; + PC_FreeDefine(define); + } //end while + } //end for +#else //DEFINEHASHING + //free all defines + while(source->defines) + { + define = source->defines; + source->defines = source->defines->next; + PC_FreeDefine(define); + } //end for +#endif //DEFINEHASHING + //free all indents + while(source->indentstack) + { + indent = source->indentstack; + source->indentstack = source->indentstack->next; + FreeMemory(indent); + } //end for +#if DEFINEHASHING + // + if (source->definehash) FreeMemory(source->definehash); +#endif //DEFINEHASHING + //free the source itself + FreeMemory(source); +} //end of the function FreeSource +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ + +#define MAX_SOURCEFILES 64 + +source_t *sourceFiles[MAX_SOURCEFILES]; + +int PC_LoadSourceHandle(const char *filename) +{ + source_t *source; + int i; + + for (i = 1; i < MAX_SOURCEFILES; i++) + { + if (!sourceFiles[i]) + break; + } //end for + if (i >= MAX_SOURCEFILES) + return 0; + PS_SetBaseFolder(""); + source = LoadSourceFile(filename); + if (!source) + return 0; + sourceFiles[i] = source; + return i; +} //end of the function PC_LoadSourceHandle +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_FreeSourceHandle(int handle) +{ + if (handle < 1 || handle >= MAX_SOURCEFILES) + return qfalse; + if (!sourceFiles[handle]) + return qfalse; + + FreeSource(sourceFiles[handle]); + sourceFiles[handle] = NULL; + return qtrue; +} //end of the function PC_FreeSourceHandle +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ReadTokenHandle(int handle, pc_token_t *pc_token) +{ + token_t token; + int ret; + + if (handle < 1 || handle >= MAX_SOURCEFILES) + return 0; + if (!sourceFiles[handle]) + return 0; + + ret = PC_ReadToken(sourceFiles[handle], &token); + strcpy(pc_token->string, token.string); + pc_token->type = token.type; + pc_token->subtype = token.subtype; + pc_token->intvalue = token.intvalue; + pc_token->floatvalue = token.floatvalue; + if (pc_token->type == TT_STRING) + StripDoubleQuotes(pc_token->string); + return ret; +} //end of the function PC_ReadTokenHandle +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_SourceFileAndLine(int handle, char *filename, int *line) +{ + if (handle < 1 || handle >= MAX_SOURCEFILES) + return qfalse; + if (!sourceFiles[handle]) + return qfalse; + + strcpy(filename, sourceFiles[handle]->filename); + if (sourceFiles[handle]->scriptstack) + *line = sourceFiles[handle]->scriptstack->line; + else + *line = 0; + return qtrue; +} //end of the function PC_SourceFileAndLine +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_SetBaseFolder(char *path) +{ + PS_SetBaseFolder(path); +} //end of the function PC_SetBaseFolder +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_CheckOpenSourceHandles(void) +{ + int i; + + for (i = 1; i < MAX_SOURCEFILES; i++) + { + if (sourceFiles[i]) + { +#ifdef BOTLIB + botimport.Print(PRT_ERROR, "file %s still open in precompiler\n", sourceFiles[i]->scriptstack->filename); +#endif //BOTLIB + } //end if + } //end for +} //end of the function PC_CheckOpenSourceHandles + diff --git a/code/botlib/l_precomp.h b/code/botlib/l_precomp.h index b2da94f..c789527 100755 --- a/code/botlib/l_precomp.h +++ b/code/botlib/l_precomp.h @@ -1,180 +1,180 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: l_precomp.h - * - * desc: pre compiler - * - * $Archive: /source/code/botlib/l_precomp.h $ - * - *****************************************************************************/ - -#ifndef MAX_PATH - #define MAX_PATH MAX_QPATH -#endif - -#ifndef PATH_SEPERATORSTR - #if defined(WIN32)|defined(_WIN32)|defined(__NT__)|defined(__WINDOWS__)|defined(__WINDOWS_386__) - #define PATHSEPERATOR_STR "\\" - #else - #define PATHSEPERATOR_STR "/" - #endif -#endif -#ifndef PATH_SEPERATORCHAR - #if defined(WIN32)|defined(_WIN32)|defined(__NT__)|defined(__WINDOWS__)|defined(__WINDOWS_386__) - #define PATHSEPERATOR_CHAR '\\' - #else - #define PATHSEPERATOR_CHAR '/' - #endif -#endif - -#if defined(BSPC) && !defined(QDECL) -#define QDECL -#endif - - -#define DEFINE_FIXED 0x0001 - -#define BUILTIN_LINE 1 -#define BUILTIN_FILE 2 -#define BUILTIN_DATE 3 -#define BUILTIN_TIME 4 -#define BUILTIN_STDC 5 - -#define INDENT_IF 0x0001 -#define INDENT_ELSE 0x0002 -#define INDENT_ELIF 0x0004 -#define INDENT_IFDEF 0x0008 -#define INDENT_IFNDEF 0x0010 - -//macro definitions -typedef struct define_s -{ - char *name; //define name - int flags; //define flags - int builtin; // > 0 if builtin define - int numparms; //number of define parameters - token_t *parms; //define parameters - token_t *tokens; //macro tokens (possibly containing parm tokens) - struct define_s *next; //next defined macro in a list - struct define_s *hashnext; //next define in the hash chain -} define_t; - -//indents -//used for conditional compilation directives: -//#if, #else, #elif, #ifdef, #ifndef -typedef struct indent_s -{ - int type; //indent type - int skip; //true if skipping current indent - script_t *script; //script the indent was in - struct indent_s *next; //next indent on the indent stack -} indent_t; - -//source file -typedef struct source_s -{ - char filename[1024]; //file name of the script - char includepath[1024]; //path to include files - punctuation_t *punctuations; //punctuations to use - script_t *scriptstack; //stack with scripts of the source - token_t *tokens; //tokens to read first - define_t *defines; //list with macro definitions - define_t **definehash; //hash chain with defines - indent_t *indentstack; //stack with indents - int skip; // > 0 if skipping conditional code - token_t token; //last read token -} source_t; - - -//read a token from the source -int PC_ReadToken(source_t *source, token_t *token); -//expect a certain token -int PC_ExpectTokenString(source_t *source, char *string); -//expect a certain token type -int PC_ExpectTokenType(source_t *source, int type, int subtype, token_t *token); -//expect a token -int PC_ExpectAnyToken(source_t *source, token_t *token); -//returns true when the token is available -int PC_CheckTokenString(source_t *source, char *string); -//returns true an reads the token when a token with the given type is available -int PC_CheckTokenType(source_t *source, int type, int subtype, token_t *token); -//skip tokens until the given token string is read -int PC_SkipUntilString(source_t *source, char *string); -//unread the last token read from the script -void PC_UnreadLastToken(source_t *source); -//unread the given token -void PC_UnreadToken(source_t *source, token_t *token); -//read a token only if on the same line, lines are concatenated with a slash -int PC_ReadLine(source_t *source, token_t *token); -//returns true if there was a white space in front of the token -int PC_WhiteSpaceBeforeToken(token_t *token); -//add a define to the source -int PC_AddDefine(source_t *source, char *string); -//add a globals define that will be added to all opened sources -int PC_AddGlobalDefine(char *string); -//remove the given global define -int PC_RemoveGlobalDefine(char *name); -//remove all globals defines -void PC_RemoveAllGlobalDefines(void); -//add builtin defines -void PC_AddBuiltinDefines(source_t *source); -//set the source include path -void PC_SetIncludePath(source_t *source, char *path); -//set the punction set -void PC_SetPunctuations(source_t *source, punctuation_t *p); -//set the base folder to load files from -void PC_SetBaseFolder(char *path); -//load a source file -source_t *LoadSourceFile(const char *filename); -//load a source from memory -source_t *LoadSourceMemory(char *ptr, int length, char *name); -//free the given source -void FreeSource(source_t *source); -//print a source error -void QDECL SourceError(source_t *source, char *str, ...); -//print a source warning -void QDECL SourceWarning(source_t *source, char *str, ...); - -#ifdef BSPC -// some of BSPC source does include game/q_shared.h and some does not -// we define pc_token_s pc_token_t if needed (yes, it's ugly) -#ifndef __Q_SHARED_H -#define MAX_TOKENLENGTH 1024 -typedef struct pc_token_s -{ - int type; - int subtype; - int intvalue; - float floatvalue; - char string[MAX_TOKENLENGTH]; -} pc_token_t; -#endif //!_Q_SHARED_H -#endif //BSPC - -// -int PC_LoadSourceHandle(const char *filename); -int PC_FreeSourceHandle(int handle); -int PC_ReadTokenHandle(int handle, pc_token_t *pc_token); -int PC_SourceFileAndLine(int handle, char *filename, int *line); -void PC_CheckOpenSourceHandles(void); +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: l_precomp.h + * + * desc: pre compiler + * + * $Archive: /source/code/botlib/l_precomp.h $ + * + *****************************************************************************/ + +#ifndef MAX_PATH + #define MAX_PATH MAX_QPATH +#endif + +#ifndef PATH_SEPERATORSTR + #if defined(WIN32)|defined(_WIN32)|defined(__NT__)|defined(__WINDOWS__)|defined(__WINDOWS_386__) + #define PATHSEPERATOR_STR "\\" + #else + #define PATHSEPERATOR_STR "/" + #endif +#endif +#ifndef PATH_SEPERATORCHAR + #if defined(WIN32)|defined(_WIN32)|defined(__NT__)|defined(__WINDOWS__)|defined(__WINDOWS_386__) + #define PATHSEPERATOR_CHAR '\\' + #else + #define PATHSEPERATOR_CHAR '/' + #endif +#endif + +#if defined(BSPC) && !defined(QDECL) +#define QDECL +#endif + + +#define DEFINE_FIXED 0x0001 + +#define BUILTIN_LINE 1 +#define BUILTIN_FILE 2 +#define BUILTIN_DATE 3 +#define BUILTIN_TIME 4 +#define BUILTIN_STDC 5 + +#define INDENT_IF 0x0001 +#define INDENT_ELSE 0x0002 +#define INDENT_ELIF 0x0004 +#define INDENT_IFDEF 0x0008 +#define INDENT_IFNDEF 0x0010 + +//macro definitions +typedef struct define_s +{ + char *name; //define name + int flags; //define flags + int builtin; // > 0 if builtin define + int numparms; //number of define parameters + token_t *parms; //define parameters + token_t *tokens; //macro tokens (possibly containing parm tokens) + struct define_s *next; //next defined macro in a list + struct define_s *hashnext; //next define in the hash chain +} define_t; + +//indents +//used for conditional compilation directives: +//#if, #else, #elif, #ifdef, #ifndef +typedef struct indent_s +{ + int type; //indent type + int skip; //true if skipping current indent + script_t *script; //script the indent was in + struct indent_s *next; //next indent on the indent stack +} indent_t; + +//source file +typedef struct source_s +{ + char filename[1024]; //file name of the script + char includepath[1024]; //path to include files + punctuation_t *punctuations; //punctuations to use + script_t *scriptstack; //stack with scripts of the source + token_t *tokens; //tokens to read first + define_t *defines; //list with macro definitions + define_t **definehash; //hash chain with defines + indent_t *indentstack; //stack with indents + int skip; // > 0 if skipping conditional code + token_t token; //last read token +} source_t; + + +//read a token from the source +int PC_ReadToken(source_t *source, token_t *token); +//expect a certain token +int PC_ExpectTokenString(source_t *source, char *string); +//expect a certain token type +int PC_ExpectTokenType(source_t *source, int type, int subtype, token_t *token); +//expect a token +int PC_ExpectAnyToken(source_t *source, token_t *token); +//returns true when the token is available +int PC_CheckTokenString(source_t *source, char *string); +//returns true an reads the token when a token with the given type is available +int PC_CheckTokenType(source_t *source, int type, int subtype, token_t *token); +//skip tokens until the given token string is read +int PC_SkipUntilString(source_t *source, char *string); +//unread the last token read from the script +void PC_UnreadLastToken(source_t *source); +//unread the given token +void PC_UnreadToken(source_t *source, token_t *token); +//read a token only if on the same line, lines are concatenated with a slash +int PC_ReadLine(source_t *source, token_t *token); +//returns true if there was a white space in front of the token +int PC_WhiteSpaceBeforeToken(token_t *token); +//add a define to the source +int PC_AddDefine(source_t *source, char *string); +//add a globals define that will be added to all opened sources +int PC_AddGlobalDefine(char *string); +//remove the given global define +int PC_RemoveGlobalDefine(char *name); +//remove all globals defines +void PC_RemoveAllGlobalDefines(void); +//add builtin defines +void PC_AddBuiltinDefines(source_t *source); +//set the source include path +void PC_SetIncludePath(source_t *source, char *path); +//set the punction set +void PC_SetPunctuations(source_t *source, punctuation_t *p); +//set the base folder to load files from +void PC_SetBaseFolder(char *path); +//load a source file +source_t *LoadSourceFile(const char *filename); +//load a source from memory +source_t *LoadSourceMemory(char *ptr, int length, char *name); +//free the given source +void FreeSource(source_t *source); +//print a source error +void QDECL SourceError(source_t *source, char *str, ...); +//print a source warning +void QDECL SourceWarning(source_t *source, char *str, ...); + +#ifdef BSPC +// some of BSPC source does include game/q_shared.h and some does not +// we define pc_token_s pc_token_t if needed (yes, it's ugly) +#ifndef __Q_SHARED_H +#define MAX_TOKENLENGTH 1024 +typedef struct pc_token_s +{ + int type; + int subtype; + int intvalue; + float floatvalue; + char string[MAX_TOKENLENGTH]; +} pc_token_t; +#endif //!_Q_SHARED_H +#endif //BSPC + +// +int PC_LoadSourceHandle(const char *filename); +int PC_FreeSourceHandle(int handle); +int PC_ReadTokenHandle(int handle, pc_token_t *pc_token); +int PC_SourceFileAndLine(int handle, char *filename, int *line); +void PC_CheckOpenSourceHandles(void); diff --git a/code/botlib/l_script.c b/code/botlib/l_script.c index 2f1e4fa..e782e7f 100755 --- a/code/botlib/l_script.c +++ b/code/botlib/l_script.c @@ -1,1433 +1,1433 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: l_script.c - * - * desc: lexicographical parser - * - * $Archive: /MissionPack/code/botlib/l_script.c $ - * - *****************************************************************************/ - -//#define SCREWUP -//#define BOTLIB -//#define MEQCC -//#define BSPC - -#ifdef SCREWUP -#include -#include -#include -#include -#include -#include "l_memory.h" -#include "l_script.h" - -typedef enum {qfalse, qtrue} qboolean; - -#endif //SCREWUP - -#ifdef BOTLIB -//include files for usage in the bot library -#include "../game/q_shared.h" -#include "../game/botlib.h" -#include "be_interface.h" -#include "l_script.h" -#include "l_memory.h" -#include "l_log.h" -#include "l_libvar.h" -#endif //BOTLIB - -#ifdef MEQCC -//include files for usage in MrElusive's QuakeC Compiler -#include "qcc.h" -#include "l_script.h" -#include "l_memory.h" -#include "l_log.h" - -#define qtrue true -#define qfalse false -#endif //MEQCC - -#ifdef BSPC -//include files for usage in the BSP Converter -#include "../bspc/qbsp.h" -#include "../bspc/l_log.h" -#include "../bspc/l_mem.h" - -#define qtrue true -#define qfalse false -#endif //BSPC - - -#define PUNCTABLE - -//longer punctuations first -punctuation_t default_punctuations[] = -{ - //binary operators - {">>=",P_RSHIFT_ASSIGN, NULL}, - {"<<=",P_LSHIFT_ASSIGN, NULL}, - // - {"...",P_PARMS, NULL}, - //define merge operator - {"##",P_PRECOMPMERGE, NULL}, - //logic operators - {"&&",P_LOGIC_AND, NULL}, - {"||",P_LOGIC_OR, NULL}, - {">=",P_LOGIC_GEQ, NULL}, - {"<=",P_LOGIC_LEQ, NULL}, - {"==",P_LOGIC_EQ, NULL}, - {"!=",P_LOGIC_UNEQ, NULL}, - //arithmatic operators - {"*=",P_MUL_ASSIGN, NULL}, - {"/=",P_DIV_ASSIGN, NULL}, - {"%=",P_MOD_ASSIGN, NULL}, - {"+=",P_ADD_ASSIGN, NULL}, - {"-=",P_SUB_ASSIGN, NULL}, - {"++",P_INC, NULL}, - {"--",P_DEC, NULL}, - //binary operators - {"&=",P_BIN_AND_ASSIGN, NULL}, - {"|=",P_BIN_OR_ASSIGN, NULL}, - {"^=",P_BIN_XOR_ASSIGN, NULL}, - {">>",P_RSHIFT, NULL}, - {"<<",P_LSHIFT, NULL}, - //reference operators - {"->",P_POINTERREF, NULL}, - //C++ - {"::",P_CPP1, NULL}, - {".*",P_CPP2, NULL}, - //arithmatic operators - {"*",P_MUL, NULL}, - {"/",P_DIV, NULL}, - {"%",P_MOD, NULL}, - {"+",P_ADD, NULL}, - {"-",P_SUB, NULL}, - {"=",P_ASSIGN, NULL}, - //binary operators - {"&",P_BIN_AND, NULL}, - {"|",P_BIN_OR, NULL}, - {"^",P_BIN_XOR, NULL}, - {"~",P_BIN_NOT, NULL}, - //logic operators - {"!",P_LOGIC_NOT, NULL}, - {">",P_LOGIC_GREATER, NULL}, - {"<",P_LOGIC_LESS, NULL}, - //reference operator - {".",P_REF, NULL}, - //seperators - {",",P_COMMA, NULL}, - {";",P_SEMICOLON, NULL}, - //label indication - {":",P_COLON, NULL}, - //if statement - {"?",P_QUESTIONMARK, NULL}, - //embracements - {"(",P_PARENTHESESOPEN, NULL}, - {")",P_PARENTHESESCLOSE, NULL}, - {"{",P_BRACEOPEN, NULL}, - {"}",P_BRACECLOSE, NULL}, - {"[",P_SQBRACKETOPEN, NULL}, - {"]",P_SQBRACKETCLOSE, NULL}, - // - {"\\",P_BACKSLASH, NULL}, - //precompiler operator - {"#",P_PRECOMP, NULL}, -#ifdef DOLLAR - {"$",P_DOLLAR, NULL}, -#endif //DOLLAR - {NULL, 0} -}; - -#ifdef BSPC -char basefolder[MAX_PATH]; -#else -char basefolder[MAX_QPATH]; -#endif - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void PS_CreatePunctuationTable(script_t *script, punctuation_t *punctuations) -{ - int i; - punctuation_t *p, *lastp, *newp; - - //get memory for the table - if (!script->punctuationtable) script->punctuationtable = (punctuation_t **) - GetMemory(256 * sizeof(punctuation_t *)); - Com_Memset(script->punctuationtable, 0, 256 * sizeof(punctuation_t *)); - //add the punctuations in the list to the punctuation table - for (i = 0; punctuations[i].p; i++) - { - newp = &punctuations[i]; - lastp = NULL; - //sort the punctuations in this table entry on length (longer punctuations first) - for (p = script->punctuationtable[(unsigned int) newp->p[0]]; p; p = p->next) - { - if (strlen(p->p) < strlen(newp->p)) - { - newp->next = p; - if (lastp) lastp->next = newp; - else script->punctuationtable[(unsigned int) newp->p[0]] = newp; - break; - } //end if - lastp = p; - } //end for - if (!p) - { - newp->next = NULL; - if (lastp) lastp->next = newp; - else script->punctuationtable[(unsigned int) newp->p[0]] = newp; - } //end if - } //end for -} //end of the function PS_CreatePunctuationTable -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -char *PunctuationFromNum(script_t *script, int num) -{ - int i; - - for (i = 0; script->punctuations[i].p; i++) - { - if (script->punctuations[i].n == num) return script->punctuations[i].p; - } //end for - return "unkown punctuation"; -} //end of the function PunctuationFromNum -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void QDECL ScriptError(script_t *script, char *str, ...) -{ - char text[1024]; - va_list ap; - - if (script->flags & SCFL_NOERRORS) return; - - va_start(ap, str); - vsprintf(text, str, ap); - va_end(ap); -#ifdef BOTLIB - botimport.Print(PRT_ERROR, "file %s, line %d: %s\n", script->filename, script->line, text); -#endif //BOTLIB -#ifdef MEQCC - printf("error: file %s, line %d: %s\n", script->filename, script->line, text); -#endif //MEQCC -#ifdef BSPC - Log_Print("error: file %s, line %d: %s\n", script->filename, script->line, text); -#endif //BSPC -} //end of the function ScriptError -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void QDECL ScriptWarning(script_t *script, char *str, ...) -{ - char text[1024]; - va_list ap; - - if (script->flags & SCFL_NOWARNINGS) return; - - va_start(ap, str); - vsprintf(text, str, ap); - va_end(ap); -#ifdef BOTLIB - botimport.Print(PRT_WARNING, "file %s, line %d: %s\n", script->filename, script->line, text); -#endif //BOTLIB -#ifdef MEQCC - printf("warning: file %s, line %d: %s\n", script->filename, script->line, text); -#endif //MEQCC -#ifdef BSPC - Log_Print("warning: file %s, line %d: %s\n", script->filename, script->line, text); -#endif //BSPC -} //end of the function ScriptWarning -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void SetScriptPunctuations(script_t *script, punctuation_t *p) -{ -#ifdef PUNCTABLE - if (p) PS_CreatePunctuationTable(script, p); - else PS_CreatePunctuationTable(script, default_punctuations); -#endif //PUNCTABLE - if (p) script->punctuations = p; - else script->punctuations = default_punctuations; -} //end of the function SetScriptPunctuations -//============================================================================ -// Reads spaces, tabs, C-like comments etc. -// When a newline character is found the scripts line counter is increased. -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PS_ReadWhiteSpace(script_t *script) -{ - while(1) - { - //skip white space - while(*script->script_p <= ' ') - { - if (!*script->script_p) return 0; - if (*script->script_p == '\n') script->line++; - script->script_p++; - } //end while - //skip comments - if (*script->script_p == '/') - { - //comments // - if (*(script->script_p+1) == '/') - { - script->script_p++; - do - { - script->script_p++; - if (!*script->script_p) return 0; - } //end do - while(*script->script_p != '\n'); - script->line++; - script->script_p++; - if (!*script->script_p) return 0; - continue; - } //end if - //comments /* */ - else if (*(script->script_p+1) == '*') - { - script->script_p++; - do - { - script->script_p++; - if (!*script->script_p) return 0; - if (*script->script_p == '\n') script->line++; - } //end do - while(!(*script->script_p == '*' && *(script->script_p+1) == '/')); - script->script_p++; - if (!*script->script_p) return 0; - script->script_p++; - if (!*script->script_p) return 0; - continue; - } //end if - } //end if - break; - } //end while - return 1; -} //end of the function PS_ReadWhiteSpace -//============================================================================ -// Reads an escape character. -// -// Parameter: script : script to read from -// ch : place to store the read escape character -// Returns: - -// Changes Globals: - -//============================================================================ -int PS_ReadEscapeCharacter(script_t *script, char *ch) -{ - int c, val, i; - - //step over the leading '\\' - script->script_p++; - //determine the escape character - switch(*script->script_p) - { - case '\\': c = '\\'; break; - case 'n': c = '\n'; break; - case 'r': c = '\r'; break; - case 't': c = '\t'; break; - case 'v': c = '\v'; break; - case 'b': c = '\b'; break; - case 'f': c = '\f'; break; - case 'a': c = '\a'; break; - case '\'': c = '\''; break; - case '\"': c = '\"'; break; - case '\?': c = '\?'; break; - case 'x': - { - script->script_p++; - for (i = 0, val = 0; ; i++, script->script_p++) - { - c = *script->script_p; - if (c >= '0' && c <= '9') c = c - '0'; - else if (c >= 'A' && c <= 'Z') c = c - 'A' + 10; - else if (c >= 'a' && c <= 'z') c = c - 'a' + 10; - else break; - val = (val << 4) + c; - } //end for - script->script_p--; - if (val > 0xFF) - { - ScriptWarning(script, "too large value in escape character"); - val = 0xFF; - } //end if - c = val; - break; - } //end case - default: //NOTE: decimal ASCII code, NOT octal - { - if (*script->script_p < '0' || *script->script_p > '9') ScriptError(script, "unknown escape char"); - for (i = 0, val = 0; ; i++, script->script_p++) - { - c = *script->script_p; - if (c >= '0' && c <= '9') c = c - '0'; - else break; - val = val * 10 + c; - } //end for - script->script_p--; - if (val > 0xFF) - { - ScriptWarning(script, "too large value in escape character"); - val = 0xFF; - } //end if - c = val; - break; - } //end default - } //end switch - //step over the escape character or the last digit of the number - script->script_p++; - //store the escape character - *ch = c; - //succesfully read escape character - return 1; -} //end of the function PS_ReadEscapeCharacter -//============================================================================ -// Reads C-like string. Escape characters are interpretted. -// Quotes are included with the string. -// Reads two strings with a white space between them as one string. -// -// Parameter: script : script to read from -// token : buffer to store the string -// Returns: qtrue when a string was read succesfully -// Changes Globals: - -//============================================================================ -int PS_ReadString(script_t *script, token_t *token, int quote) -{ - int len, tmpline; - char *tmpscript_p; - - if (quote == '\"') token->type = TT_STRING; - else token->type = TT_LITERAL; - - len = 0; - //leading quote - token->string[len++] = *script->script_p++; - // - while(1) - { - //minus 2 because trailing double quote and zero have to be appended - if (len >= MAX_TOKEN - 2) - { - ScriptError(script, "string longer than MAX_TOKEN = %d", MAX_TOKEN); - return 0; - } //end if - //if there is an escape character and - //if escape characters inside a string are allowed - if (*script->script_p == '\\' && !(script->flags & SCFL_NOSTRINGESCAPECHARS)) - { - if (!PS_ReadEscapeCharacter(script, &token->string[len])) - { - token->string[len] = 0; - return 0; - } //end if - len++; - } //end if - //if a trailing quote - else if (*script->script_p == quote) - { - //step over the double quote - script->script_p++; - //if white spaces in a string are not allowed - if (script->flags & SCFL_NOSTRINGWHITESPACES) break; - // - tmpscript_p = script->script_p; - tmpline = script->line; - //read unusefull stuff between possible two following strings - if (!PS_ReadWhiteSpace(script)) - { - script->script_p = tmpscript_p; - script->line = tmpline; - break; - } //end if - //if there's no leading double qoute - if (*script->script_p != quote) - { - script->script_p = tmpscript_p; - script->line = tmpline; - break; - } //end if - //step over the new leading double quote - script->script_p++; - } //end if - else - { - if (*script->script_p == '\0') - { - token->string[len] = 0; - ScriptError(script, "missing trailing quote"); - return 0; - } //end if - if (*script->script_p == '\n') - { - token->string[len] = 0; - ScriptError(script, "newline inside string %s", token->string); - return 0; - } //end if - token->string[len++] = *script->script_p++; - } //end else - } //end while - //trailing quote - token->string[len++] = quote; - //end string with a zero - token->string[len] = '\0'; - //the sub type is the length of the string - token->subtype = len; - return 1; -} //end of the function PS_ReadString -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PS_ReadName(script_t *script, token_t *token) -{ - int len = 0; - char c; - - token->type = TT_NAME; - do - { - token->string[len++] = *script->script_p++; - if (len >= MAX_TOKEN) - { - ScriptError(script, "name longer than MAX_TOKEN = %d", MAX_TOKEN); - return 0; - } //end if - c = *script->script_p; - } while ((c >= 'a' && c <= 'z') || - (c >= 'A' && c <= 'Z') || - (c >= '0' && c <= '9') || - c == '_'); - token->string[len] = '\0'; - //the sub type is the length of the name - token->subtype = len; - return 1; -} //end of the function PS_ReadName -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -void NumberValue(char *string, int subtype, unsigned long int *intvalue, - long double *floatvalue) -{ - unsigned long int dotfound = 0; - - *intvalue = 0; - *floatvalue = 0; - //floating point number - if (subtype & TT_FLOAT) - { - while(*string) - { - if (*string == '.') - { - if (dotfound) return; - dotfound = 10; - string++; - } //end if - if (dotfound) - { - *floatvalue = *floatvalue + (long double) (*string - '0') / - (long double) dotfound; - dotfound *= 10; - } //end if - else - { - *floatvalue = *floatvalue * 10.0 + (long double) (*string - '0'); - } //end else - string++; - } //end while - *intvalue = (unsigned long) *floatvalue; - } //end if - else if (subtype & TT_DECIMAL) - { - while(*string) *intvalue = *intvalue * 10 + (*string++ - '0'); - *floatvalue = *intvalue; - } //end else if - else if (subtype & TT_HEX) - { - //step over the leading 0x or 0X - string += 2; - while(*string) - { - *intvalue <<= 4; - if (*string >= 'a' && *string <= 'f') *intvalue += *string - 'a' + 10; - else if (*string >= 'A' && *string <= 'F') *intvalue += *string - 'A' + 10; - else *intvalue += *string - '0'; - string++; - } //end while - *floatvalue = *intvalue; - } //end else if - else if (subtype & TT_OCTAL) - { - //step over the first zero - string += 1; - while(*string) *intvalue = (*intvalue << 3) + (*string++ - '0'); - *floatvalue = *intvalue; - } //end else if - else if (subtype & TT_BINARY) - { - //step over the leading 0b or 0B - string += 2; - while(*string) *intvalue = (*intvalue << 1) + (*string++ - '0'); - *floatvalue = *intvalue; - } //end else if -} //end of the function NumberValue -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PS_ReadNumber(script_t *script, token_t *token) -{ - int len = 0, i; - int octal, dot; - char c; -// unsigned long int intvalue = 0; -// long double floatvalue = 0; - - token->type = TT_NUMBER; - //check for a hexadecimal number - if (*script->script_p == '0' && - (*(script->script_p + 1) == 'x' || - *(script->script_p + 1) == 'X')) - { - token->string[len++] = *script->script_p++; - token->string[len++] = *script->script_p++; - c = *script->script_p; - //hexadecimal - while((c >= '0' && c <= '9') || - (c >= 'a' && c <= 'f') || - (c >= 'A' && c <= 'A')) - { - token->string[len++] = *script->script_p++; - if (len >= MAX_TOKEN) - { - ScriptError(script, "hexadecimal number longer than MAX_TOKEN = %d", MAX_TOKEN); - return 0; - } //end if - c = *script->script_p; - } //end while - token->subtype |= TT_HEX; - } //end if -#ifdef BINARYNUMBERS - //check for a binary number - else if (*script->script_p == '0' && - (*(script->script_p + 1) == 'b' || - *(script->script_p + 1) == 'B')) - { - token->string[len++] = *script->script_p++; - token->string[len++] = *script->script_p++; - c = *script->script_p; - //binary - while(c == '0' || c == '1') - { - token->string[len++] = *script->script_p++; - if (len >= MAX_TOKEN) - { - ScriptError(script, "binary number longer than MAX_TOKEN = %d", MAX_TOKEN); - return 0; - } //end if - c = *script->script_p; - } //end while - token->subtype |= TT_BINARY; - } //end if -#endif //BINARYNUMBERS - else //decimal or octal integer or floating point number - { - octal = qfalse; - dot = qfalse; - if (*script->script_p == '0') octal = qtrue; - while(1) - { - c = *script->script_p; - if (c == '.') dot = qtrue; - else if (c == '8' || c == '9') octal = qfalse; - else if (c < '0' || c > '9') break; - token->string[len++] = *script->script_p++; - if (len >= MAX_TOKEN - 1) - { - ScriptError(script, "number longer than MAX_TOKEN = %d", MAX_TOKEN); - return 0; - } //end if - } //end while - if (octal) token->subtype |= TT_OCTAL; - else token->subtype |= TT_DECIMAL; - if (dot) token->subtype |= TT_FLOAT; - } //end else - for (i = 0; i < 2; i++) - { - c = *script->script_p; - //check for a LONG number - if ( (c == 'l' || c == 'L') // bk001204 - brackets - && !(token->subtype & TT_LONG)) - { - script->script_p++; - token->subtype |= TT_LONG; - } //end if - //check for an UNSIGNED number - else if ( (c == 'u' || c == 'U') // bk001204 - brackets - && !(token->subtype & (TT_UNSIGNED | TT_FLOAT))) - { - script->script_p++; - token->subtype |= TT_UNSIGNED; - } //end if - } //end for - token->string[len] = '\0'; -#ifdef NUMBERVALUE - NumberValue(token->string, token->subtype, &token->intvalue, &token->floatvalue); -#endif //NUMBERVALUE - if (!(token->subtype & TT_FLOAT)) token->subtype |= TT_INTEGER; - return 1; -} //end of the function PS_ReadNumber -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PS_ReadLiteral(script_t *script, token_t *token) -{ - token->type = TT_LITERAL; - //first quote - token->string[0] = *script->script_p++; - //check for end of file - if (!*script->script_p) - { - ScriptError(script, "end of file before trailing \'"); - return 0; - } //end if - //if it is an escape character - if (*script->script_p == '\\') - { - if (!PS_ReadEscapeCharacter(script, &token->string[1])) return 0; - } //end if - else - { - token->string[1] = *script->script_p++; - } //end else - //check for trailing quote - if (*script->script_p != '\'') - { - ScriptWarning(script, "too many characters in literal, ignored"); - while(*script->script_p && - *script->script_p != '\'' && - *script->script_p != '\n') - { - script->script_p++; - } //end while - if (*script->script_p == '\'') script->script_p++; - } //end if - //store the trailing quote - token->string[2] = *script->script_p++; - //store trailing zero to end the string - token->string[3] = '\0'; - //the sub type is the integer literal value - token->subtype = token->string[1]; - // - return 1; -} //end of the function PS_ReadLiteral -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PS_ReadPunctuation(script_t *script, token_t *token) -{ - int len; - char *p; - punctuation_t *punc; - -#ifdef PUNCTABLE - for (punc = script->punctuationtable[(unsigned int)*script->script_p]; punc; punc = punc->next) - { -#else - int i; - - for (i = 0; script->punctuations[i].p; i++) - { - punc = &script->punctuations[i]; -#endif //PUNCTABLE - p = punc->p; - len = strlen(p); - //if the script contains at least as much characters as the punctuation - if (script->script_p + len <= script->end_p) - { - //if the script contains the punctuation - if (!strncmp(script->script_p, p, len)) - { - strncpy(token->string, p, MAX_TOKEN); - script->script_p += len; - token->type = TT_PUNCTUATION; - //sub type is the number of the punctuation - token->subtype = punc->n; - return 1; - } //end if - } //end if - } //end for - return 0; -} //end of the function PS_ReadPunctuation -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PS_ReadPrimitive(script_t *script, token_t *token) -{ - int len; - - len = 0; - while(*script->script_p > ' ' && *script->script_p != ';') - { - if (len >= MAX_TOKEN) - { - ScriptError(script, "primitive token longer than MAX_TOKEN = %d", MAX_TOKEN); - return 0; - } //end if - token->string[len++] = *script->script_p++; - } //end while - token->string[len] = 0; - //copy the token into the script structure - Com_Memcpy(&script->token, token, sizeof(token_t)); - //primitive reading successfull - return 1; -} //end of the function PS_ReadPrimitive -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PS_ReadToken(script_t *script, token_t *token) -{ - //if there is a token available (from UnreadToken) - if (script->tokenavailable) - { - script->tokenavailable = 0; - Com_Memcpy(token, &script->token, sizeof(token_t)); - return 1; - } //end if - //save script pointer - script->lastscript_p = script->script_p; - //save line counter - script->lastline = script->line; - //clear the token stuff - Com_Memset(token, 0, sizeof(token_t)); - //start of the white space - script->whitespace_p = script->script_p; - token->whitespace_p = script->script_p; - //read unusefull stuff - if (!PS_ReadWhiteSpace(script)) return 0; - //end of the white space - script->endwhitespace_p = script->script_p; - token->endwhitespace_p = script->script_p; - //line the token is on - token->line = script->line; - //number of lines crossed before token - token->linescrossed = script->line - script->lastline; - //if there is a leading double quote - if (*script->script_p == '\"') - { - if (!PS_ReadString(script, token, '\"')) return 0; - } //end if - //if an literal - else if (*script->script_p == '\'') - { - //if (!PS_ReadLiteral(script, token)) return 0; - if (!PS_ReadString(script, token, '\'')) return 0; - } //end if - //if there is a number - else if ((*script->script_p >= '0' && *script->script_p <= '9') || - (*script->script_p == '.' && - (*(script->script_p + 1) >= '0' && *(script->script_p + 1) <= '9'))) - { - if (!PS_ReadNumber(script, token)) return 0; - } //end if - //if this is a primitive script - else if (script->flags & SCFL_PRIMITIVE) - { - return PS_ReadPrimitive(script, token); - } //end else if - //if there is a name - else if ((*script->script_p >= 'a' && *script->script_p <= 'z') || - (*script->script_p >= 'A' && *script->script_p <= 'Z') || - *script->script_p == '_') - { - if (!PS_ReadName(script, token)) return 0; - } //end if - //check for punctuations - else if (!PS_ReadPunctuation(script, token)) - { - ScriptError(script, "can't read token"); - return 0; - } //end if - //copy the token into the script structure - Com_Memcpy(&script->token, token, sizeof(token_t)); - //succesfully read a token - return 1; -} //end of the function PS_ReadToken -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PS_ExpectTokenString(script_t *script, char *string) -{ - token_t token; - - if (!PS_ReadToken(script, &token)) - { - ScriptError(script, "couldn't find expected %s", string); - return 0; - } //end if - - if (strcmp(token.string, string)) - { - ScriptError(script, "expected %s, found %s", string, token.string); - return 0; - } //end if - return 1; -} //end of the function PS_ExpectToken -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PS_ExpectTokenType(script_t *script, int type, int subtype, token_t *token) -{ - char str[MAX_TOKEN]; - - if (!PS_ReadToken(script, token)) - { - ScriptError(script, "couldn't read expected token"); - return 0; - } //end if - - if (token->type != type) - { - if (type == TT_STRING) strcpy(str, "string"); - if (type == TT_LITERAL) strcpy(str, "literal"); - if (type == TT_NUMBER) strcpy(str, "number"); - if (type == TT_NAME) strcpy(str, "name"); - if (type == TT_PUNCTUATION) strcpy(str, "punctuation"); - ScriptError(script, "expected a %s, found %s", str, token->string); - return 0; - } //end if - if (token->type == TT_NUMBER) - { - if ((token->subtype & subtype) != subtype) - { - if (subtype & TT_DECIMAL) strcpy(str, "decimal"); - if (subtype & TT_HEX) strcpy(str, "hex"); - if (subtype & TT_OCTAL) strcpy(str, "octal"); - if (subtype & TT_BINARY) strcpy(str, "binary"); - if (subtype & TT_LONG) strcat(str, " long"); - if (subtype & TT_UNSIGNED) strcat(str, " unsigned"); - if (subtype & TT_FLOAT) strcat(str, " float"); - if (subtype & TT_INTEGER) strcat(str, " integer"); - ScriptError(script, "expected %s, found %s", str, token->string); - return 0; - } //end if - } //end if - else if (token->type == TT_PUNCTUATION) - { - if (subtype < 0) - { - ScriptError(script, "BUG: wrong punctuation subtype"); - return 0; - } //end if - if (token->subtype != subtype) - { - ScriptError(script, "expected %s, found %s", - script->punctuations[subtype], token->string); - return 0; - } //end if - } //end else if - return 1; -} //end of the function PS_ExpectTokenType -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PS_ExpectAnyToken(script_t *script, token_t *token) -{ - if (!PS_ReadToken(script, token)) - { - ScriptError(script, "couldn't read expected token"); - return 0; - } //end if - else - { - return 1; - } //end else -} //end of the function PS_ExpectAnyToken -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PS_CheckTokenString(script_t *script, char *string) -{ - token_t tok; - - if (!PS_ReadToken(script, &tok)) return 0; - //if the token is available - if (!strcmp(tok.string, string)) return 1; - //token not available - script->script_p = script->lastscript_p; - return 0; -} //end of the function PS_CheckTokenString -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PS_CheckTokenType(script_t *script, int type, int subtype, token_t *token) -{ - token_t tok; - - if (!PS_ReadToken(script, &tok)) return 0; - //if the type matches - if (tok.type == type && - (tok.subtype & subtype) == subtype) - { - Com_Memcpy(token, &tok, sizeof(token_t)); - return 1; - } //end if - //token is not available - script->script_p = script->lastscript_p; - return 0; -} //end of the function PS_CheckTokenType -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int PS_SkipUntilString(script_t *script, char *string) -{ - token_t token; - - while(PS_ReadToken(script, &token)) - { - if (!strcmp(token.string, string)) return 1; - } //end while - return 0; -} //end of the function PS_SkipUntilString -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -void PS_UnreadLastToken(script_t *script) -{ - script->tokenavailable = 1; -} //end of the function UnreadLastToken -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -void PS_UnreadToken(script_t *script, token_t *token) -{ - Com_Memcpy(&script->token, token, sizeof(token_t)); - script->tokenavailable = 1; -} //end of the function UnreadToken -//============================================================================ -// returns the next character of the read white space, returns NULL if none -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -char PS_NextWhiteSpaceChar(script_t *script) -{ - if (script->whitespace_p != script->endwhitespace_p) - { - return *script->whitespace_p++; - } //end if - else - { - return 0; - } //end else -} //end of the function PS_NextWhiteSpaceChar -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -void StripDoubleQuotes(char *string) -{ - if (*string == '\"') - { - strcpy(string, string+1); - } //end if - if (string[strlen(string)-1] == '\"') - { - string[strlen(string)-1] = '\0'; - } //end if -} //end of the function StripDoubleQuotes -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -void StripSingleQuotes(char *string) -{ - if (*string == '\'') - { - strcpy(string, string+1); - } //end if - if (string[strlen(string)-1] == '\'') - { - string[strlen(string)-1] = '\0'; - } //end if -} //end of the function StripSingleQuotes -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -long double ReadSignedFloat(script_t *script) -{ - token_t token; - long double sign = 1; - - PS_ExpectAnyToken(script, &token); - if (!strcmp(token.string, "-")) - { - sign = -1; - PS_ExpectTokenType(script, TT_NUMBER, 0, &token); - } //end if - else if (token.type != TT_NUMBER) - { - ScriptError(script, "expected float value, found %s\n", token.string); - } //end else if - return sign * token.floatvalue; -} //end of the function ReadSignedFloat -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -signed long int ReadSignedInt(script_t *script) -{ - token_t token; - signed long int sign = 1; - - PS_ExpectAnyToken(script, &token); - if (!strcmp(token.string, "-")) - { - sign = -1; - PS_ExpectTokenType(script, TT_NUMBER, TT_INTEGER, &token); - } //end if - else if (token.type != TT_NUMBER || token.subtype == TT_FLOAT) - { - ScriptError(script, "expected integer value, found %s\n", token.string); - } //end else if - return sign * token.intvalue; -} //end of the function ReadSignedInt -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -void SetScriptFlags(script_t *script, int flags) -{ - script->flags = flags; -} //end of the function SetScriptFlags -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int GetScriptFlags(script_t *script) -{ - return script->flags; -} //end of the function GetScriptFlags -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -void ResetScript(script_t *script) -{ - //pointer in script buffer - script->script_p = script->buffer; - //pointer in script buffer before reading token - script->lastscript_p = script->buffer; - //begin of white space - script->whitespace_p = NULL; - //end of white space - script->endwhitespace_p = NULL; - //set if there's a token available in script->token - script->tokenavailable = 0; - // - script->line = 1; - script->lastline = 1; - //clear the saved token - Com_Memset(&script->token, 0, sizeof(token_t)); -} //end of the function ResetScript -//============================================================================ -// returns true if at the end of the script -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int EndOfScript(script_t *script) -{ - return script->script_p >= script->end_p; -} //end of the function EndOfScript -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int NumLinesCrossed(script_t *script) -{ - return script->line - script->lastline; -} //end of the function NumLinesCrossed -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int ScriptSkipTo(script_t *script, char *value) -{ - int len; - char firstchar; - - firstchar = *value; - len = strlen(value); - do - { - if (!PS_ReadWhiteSpace(script)) return 0; - if (*script->script_p == firstchar) - { - if (!strncmp(script->script_p, value, len)) - { - return 1; - } //end if - } //end if - script->script_p++; - } while(1); -} //end of the function ScriptSkipTo -#ifndef BOTLIB -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -int FileLength(FILE *fp) -{ - int pos; - int end; - - pos = ftell(fp); - fseek(fp, 0, SEEK_END); - end = ftell(fp); - fseek(fp, pos, SEEK_SET); - - return end; -} //end of the function FileLength -#endif -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -script_t *LoadScriptFile(const char *filename) -{ -#ifdef BOTLIB - fileHandle_t fp; - char pathname[MAX_QPATH]; -#else - FILE *fp; -#endif - int length; - void *buffer; - script_t *script; - -#ifdef BOTLIB - if (strlen(basefolder)) - Com_sprintf(pathname, sizeof(pathname), "%s/%s", basefolder, filename); - else - Com_sprintf(pathname, sizeof(pathname), "%s", filename); - length = botimport.FS_FOpenFile( pathname, &fp, FS_READ ); - if (!fp) return NULL; -#else - fp = fopen(filename, "rb"); - if (!fp) return NULL; - - length = FileLength(fp); -#endif - - buffer = GetClearedMemory(sizeof(script_t) + length + 1); - script = (script_t *) buffer; - Com_Memset(script, 0, sizeof(script_t)); - strcpy(script->filename, filename); - script->buffer = (char *) buffer + sizeof(script_t); - script->buffer[length] = 0; - script->length = length; - //pointer in script buffer - script->script_p = script->buffer; - //pointer in script buffer before reading token - script->lastscript_p = script->buffer; - //pointer to end of script buffer - script->end_p = &script->buffer[length]; - //set if there's a token available in script->token - script->tokenavailable = 0; - // - script->line = 1; - script->lastline = 1; - // - SetScriptPunctuations(script, NULL); - // -#ifdef BOTLIB - botimport.FS_Read(script->buffer, length, fp); - botimport.FS_FCloseFile(fp); -#else - if (fread(script->buffer, length, 1, fp) != 1) - { - FreeMemory(buffer); - script = NULL; - } //end if - fclose(fp); -#endif - // - script->length = COM_Compress(script->buffer); - - return script; -} //end of the function LoadScriptFile -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -script_t *LoadScriptMemory(char *ptr, int length, char *name) -{ - void *buffer; - script_t *script; - - buffer = GetClearedMemory(sizeof(script_t) + length + 1); - script = (script_t *) buffer; - Com_Memset(script, 0, sizeof(script_t)); - strcpy(script->filename, name); - script->buffer = (char *) buffer + sizeof(script_t); - script->buffer[length] = 0; - script->length = length; - //pointer in script buffer - script->script_p = script->buffer; - //pointer in script buffer before reading token - script->lastscript_p = script->buffer; - //pointer to end of script buffer - script->end_p = &script->buffer[length]; - //set if there's a token available in script->token - script->tokenavailable = 0; - // - script->line = 1; - script->lastline = 1; - // - SetScriptPunctuations(script, NULL); - // - Com_Memcpy(script->buffer, ptr, length); - // - return script; -} //end of the function LoadScriptMemory -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -void FreeScript(script_t *script) -{ -#ifdef PUNCTABLE - if (script->punctuationtable) FreeMemory(script->punctuationtable); -#endif //PUNCTABLE - FreeMemory(script); -} //end of the function FreeScript -//============================================================================ -// -// Parameter: - -// Returns: - -// Changes Globals: - -//============================================================================ -void PS_SetBaseFolder(char *path) -{ -#ifdef BSPC - sprintf(basefolder, path); -#else - Com_sprintf(basefolder, sizeof(basefolder), path); -#endif -} //end of the function PS_SetBaseFolder +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: l_script.c + * + * desc: lexicographical parser + * + * $Archive: /MissionPack/code/botlib/l_script.c $ + * + *****************************************************************************/ + +//#define SCREWUP +//#define BOTLIB +//#define MEQCC +//#define BSPC + +#ifdef SCREWUP +#include +#include +#include +#include +#include +#include "l_memory.h" +#include "l_script.h" + +typedef enum {qfalse, qtrue} qboolean; + +#endif //SCREWUP + +#ifdef BOTLIB +//include files for usage in the bot library +#include "../game/q_shared.h" +#include "../game/botlib.h" +#include "be_interface.h" +#include "l_script.h" +#include "l_memory.h" +#include "l_log.h" +#include "l_libvar.h" +#endif //BOTLIB + +#ifdef MEQCC +//include files for usage in MrElusive's QuakeC Compiler +#include "qcc.h" +#include "l_script.h" +#include "l_memory.h" +#include "l_log.h" + +#define qtrue true +#define qfalse false +#endif //MEQCC + +#ifdef BSPC +//include files for usage in the BSP Converter +#include "../bspc/qbsp.h" +#include "../bspc/l_log.h" +#include "../bspc/l_mem.h" + +#define qtrue true +#define qfalse false +#endif //BSPC + + +#define PUNCTABLE + +//longer punctuations first +punctuation_t default_punctuations[] = +{ + //binary operators + {">>=",P_RSHIFT_ASSIGN, NULL}, + {"<<=",P_LSHIFT_ASSIGN, NULL}, + // + {"...",P_PARMS, NULL}, + //define merge operator + {"##",P_PRECOMPMERGE, NULL}, + //logic operators + {"&&",P_LOGIC_AND, NULL}, + {"||",P_LOGIC_OR, NULL}, + {">=",P_LOGIC_GEQ, NULL}, + {"<=",P_LOGIC_LEQ, NULL}, + {"==",P_LOGIC_EQ, NULL}, + {"!=",P_LOGIC_UNEQ, NULL}, + //arithmatic operators + {"*=",P_MUL_ASSIGN, NULL}, + {"/=",P_DIV_ASSIGN, NULL}, + {"%=",P_MOD_ASSIGN, NULL}, + {"+=",P_ADD_ASSIGN, NULL}, + {"-=",P_SUB_ASSIGN, NULL}, + {"++",P_INC, NULL}, + {"--",P_DEC, NULL}, + //binary operators + {"&=",P_BIN_AND_ASSIGN, NULL}, + {"|=",P_BIN_OR_ASSIGN, NULL}, + {"^=",P_BIN_XOR_ASSIGN, NULL}, + {">>",P_RSHIFT, NULL}, + {"<<",P_LSHIFT, NULL}, + //reference operators + {"->",P_POINTERREF, NULL}, + //C++ + {"::",P_CPP1, NULL}, + {".*",P_CPP2, NULL}, + //arithmatic operators + {"*",P_MUL, NULL}, + {"/",P_DIV, NULL}, + {"%",P_MOD, NULL}, + {"+",P_ADD, NULL}, + {"-",P_SUB, NULL}, + {"=",P_ASSIGN, NULL}, + //binary operators + {"&",P_BIN_AND, NULL}, + {"|",P_BIN_OR, NULL}, + {"^",P_BIN_XOR, NULL}, + {"~",P_BIN_NOT, NULL}, + //logic operators + {"!",P_LOGIC_NOT, NULL}, + {">",P_LOGIC_GREATER, NULL}, + {"<",P_LOGIC_LESS, NULL}, + //reference operator + {".",P_REF, NULL}, + //seperators + {",",P_COMMA, NULL}, + {";",P_SEMICOLON, NULL}, + //label indication + {":",P_COLON, NULL}, + //if statement + {"?",P_QUESTIONMARK, NULL}, + //embracements + {"(",P_PARENTHESESOPEN, NULL}, + {")",P_PARENTHESESCLOSE, NULL}, + {"{",P_BRACEOPEN, NULL}, + {"}",P_BRACECLOSE, NULL}, + {"[",P_SQBRACKETOPEN, NULL}, + {"]",P_SQBRACKETCLOSE, NULL}, + // + {"\\",P_BACKSLASH, NULL}, + //precompiler operator + {"#",P_PRECOMP, NULL}, +#ifdef DOLLAR + {"$",P_DOLLAR, NULL}, +#endif //DOLLAR + {NULL, 0} +}; + +#ifdef BSPC +char basefolder[MAX_PATH]; +#else +char basefolder[MAX_QPATH]; +#endif + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void PS_CreatePunctuationTable(script_t *script, punctuation_t *punctuations) +{ + int i; + punctuation_t *p, *lastp, *newp; + + //get memory for the table + if (!script->punctuationtable) script->punctuationtable = (punctuation_t **) + GetMemory(256 * sizeof(punctuation_t *)); + Com_Memset(script->punctuationtable, 0, 256 * sizeof(punctuation_t *)); + //add the punctuations in the list to the punctuation table + for (i = 0; punctuations[i].p; i++) + { + newp = &punctuations[i]; + lastp = NULL; + //sort the punctuations in this table entry on length (longer punctuations first) + for (p = script->punctuationtable[(unsigned int) newp->p[0]]; p; p = p->next) + { + if (strlen(p->p) < strlen(newp->p)) + { + newp->next = p; + if (lastp) lastp->next = newp; + else script->punctuationtable[(unsigned int) newp->p[0]] = newp; + break; + } //end if + lastp = p; + } //end for + if (!p) + { + newp->next = NULL; + if (lastp) lastp->next = newp; + else script->punctuationtable[(unsigned int) newp->p[0]] = newp; + } //end if + } //end for +} //end of the function PS_CreatePunctuationTable +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *PunctuationFromNum(script_t *script, int num) +{ + int i; + + for (i = 0; script->punctuations[i].p; i++) + { + if (script->punctuations[i].n == num) return script->punctuations[i].p; + } //end for + return "unkown punctuation"; +} //end of the function PunctuationFromNum +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void QDECL ScriptError(script_t *script, char *str, ...) +{ + char text[1024]; + va_list ap; + + if (script->flags & SCFL_NOERRORS) return; + + va_start(ap, str); + vsprintf(text, str, ap); + va_end(ap); +#ifdef BOTLIB + botimport.Print(PRT_ERROR, "file %s, line %d: %s\n", script->filename, script->line, text); +#endif //BOTLIB +#ifdef MEQCC + printf("error: file %s, line %d: %s\n", script->filename, script->line, text); +#endif //MEQCC +#ifdef BSPC + Log_Print("error: file %s, line %d: %s\n", script->filename, script->line, text); +#endif //BSPC +} //end of the function ScriptError +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void QDECL ScriptWarning(script_t *script, char *str, ...) +{ + char text[1024]; + va_list ap; + + if (script->flags & SCFL_NOWARNINGS) return; + + va_start(ap, str); + vsprintf(text, str, ap); + va_end(ap); +#ifdef BOTLIB + botimport.Print(PRT_WARNING, "file %s, line %d: %s\n", script->filename, script->line, text); +#endif //BOTLIB +#ifdef MEQCC + printf("warning: file %s, line %d: %s\n", script->filename, script->line, text); +#endif //MEQCC +#ifdef BSPC + Log_Print("warning: file %s, line %d: %s\n", script->filename, script->line, text); +#endif //BSPC +} //end of the function ScriptWarning +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void SetScriptPunctuations(script_t *script, punctuation_t *p) +{ +#ifdef PUNCTABLE + if (p) PS_CreatePunctuationTable(script, p); + else PS_CreatePunctuationTable(script, default_punctuations); +#endif //PUNCTABLE + if (p) script->punctuations = p; + else script->punctuations = default_punctuations; +} //end of the function SetScriptPunctuations +//============================================================================ +// Reads spaces, tabs, C-like comments etc. +// When a newline character is found the scripts line counter is increased. +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ReadWhiteSpace(script_t *script) +{ + while(1) + { + //skip white space + while(*script->script_p <= ' ') + { + if (!*script->script_p) return 0; + if (*script->script_p == '\n') script->line++; + script->script_p++; + } //end while + //skip comments + if (*script->script_p == '/') + { + //comments // + if (*(script->script_p+1) == '/') + { + script->script_p++; + do + { + script->script_p++; + if (!*script->script_p) return 0; + } //end do + while(*script->script_p != '\n'); + script->line++; + script->script_p++; + if (!*script->script_p) return 0; + continue; + } //end if + //comments /* */ + else if (*(script->script_p+1) == '*') + { + script->script_p++; + do + { + script->script_p++; + if (!*script->script_p) return 0; + if (*script->script_p == '\n') script->line++; + } //end do + while(!(*script->script_p == '*' && *(script->script_p+1) == '/')); + script->script_p++; + if (!*script->script_p) return 0; + script->script_p++; + if (!*script->script_p) return 0; + continue; + } //end if + } //end if + break; + } //end while + return 1; +} //end of the function PS_ReadWhiteSpace +//============================================================================ +// Reads an escape character. +// +// Parameter: script : script to read from +// ch : place to store the read escape character +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ReadEscapeCharacter(script_t *script, char *ch) +{ + int c, val, i; + + //step over the leading '\\' + script->script_p++; + //determine the escape character + switch(*script->script_p) + { + case '\\': c = '\\'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + case 'v': c = '\v'; break; + case 'b': c = '\b'; break; + case 'f': c = '\f'; break; + case 'a': c = '\a'; break; + case '\'': c = '\''; break; + case '\"': c = '\"'; break; + case '\?': c = '\?'; break; + case 'x': + { + script->script_p++; + for (i = 0, val = 0; ; i++, script->script_p++) + { + c = *script->script_p; + if (c >= '0' && c <= '9') c = c - '0'; + else if (c >= 'A' && c <= 'Z') c = c - 'A' + 10; + else if (c >= 'a' && c <= 'z') c = c - 'a' + 10; + else break; + val = (val << 4) + c; + } //end for + script->script_p--; + if (val > 0xFF) + { + ScriptWarning(script, "too large value in escape character"); + val = 0xFF; + } //end if + c = val; + break; + } //end case + default: //NOTE: decimal ASCII code, NOT octal + { + if (*script->script_p < '0' || *script->script_p > '9') ScriptError(script, "unknown escape char"); + for (i = 0, val = 0; ; i++, script->script_p++) + { + c = *script->script_p; + if (c >= '0' && c <= '9') c = c - '0'; + else break; + val = val * 10 + c; + } //end for + script->script_p--; + if (val > 0xFF) + { + ScriptWarning(script, "too large value in escape character"); + val = 0xFF; + } //end if + c = val; + break; + } //end default + } //end switch + //step over the escape character or the last digit of the number + script->script_p++; + //store the escape character + *ch = c; + //succesfully read escape character + return 1; +} //end of the function PS_ReadEscapeCharacter +//============================================================================ +// Reads C-like string. Escape characters are interpretted. +// Quotes are included with the string. +// Reads two strings with a white space between them as one string. +// +// Parameter: script : script to read from +// token : buffer to store the string +// Returns: qtrue when a string was read succesfully +// Changes Globals: - +//============================================================================ +int PS_ReadString(script_t *script, token_t *token, int quote) +{ + int len, tmpline; + char *tmpscript_p; + + if (quote == '\"') token->type = TT_STRING; + else token->type = TT_LITERAL; + + len = 0; + //leading quote + token->string[len++] = *script->script_p++; + // + while(1) + { + //minus 2 because trailing double quote and zero have to be appended + if (len >= MAX_TOKEN - 2) + { + ScriptError(script, "string longer than MAX_TOKEN = %d", MAX_TOKEN); + return 0; + } //end if + //if there is an escape character and + //if escape characters inside a string are allowed + if (*script->script_p == '\\' && !(script->flags & SCFL_NOSTRINGESCAPECHARS)) + { + if (!PS_ReadEscapeCharacter(script, &token->string[len])) + { + token->string[len] = 0; + return 0; + } //end if + len++; + } //end if + //if a trailing quote + else if (*script->script_p == quote) + { + //step over the double quote + script->script_p++; + //if white spaces in a string are not allowed + if (script->flags & SCFL_NOSTRINGWHITESPACES) break; + // + tmpscript_p = script->script_p; + tmpline = script->line; + //read unusefull stuff between possible two following strings + if (!PS_ReadWhiteSpace(script)) + { + script->script_p = tmpscript_p; + script->line = tmpline; + break; + } //end if + //if there's no leading double qoute + if (*script->script_p != quote) + { + script->script_p = tmpscript_p; + script->line = tmpline; + break; + } //end if + //step over the new leading double quote + script->script_p++; + } //end if + else + { + if (*script->script_p == '\0') + { + token->string[len] = 0; + ScriptError(script, "missing trailing quote"); + return 0; + } //end if + if (*script->script_p == '\n') + { + token->string[len] = 0; + ScriptError(script, "newline inside string %s", token->string); + return 0; + } //end if + token->string[len++] = *script->script_p++; + } //end else + } //end while + //trailing quote + token->string[len++] = quote; + //end string with a zero + token->string[len] = '\0'; + //the sub type is the length of the string + token->subtype = len; + return 1; +} //end of the function PS_ReadString +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ReadName(script_t *script, token_t *token) +{ + int len = 0; + char c; + + token->type = TT_NAME; + do + { + token->string[len++] = *script->script_p++; + if (len >= MAX_TOKEN) + { + ScriptError(script, "name longer than MAX_TOKEN = %d", MAX_TOKEN); + return 0; + } //end if + c = *script->script_p; + } while ((c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || + c == '_'); + token->string[len] = '\0'; + //the sub type is the length of the name + token->subtype = len; + return 1; +} //end of the function PS_ReadName +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void NumberValue(char *string, int subtype, unsigned long int *intvalue, + long double *floatvalue) +{ + unsigned long int dotfound = 0; + + *intvalue = 0; + *floatvalue = 0; + //floating point number + if (subtype & TT_FLOAT) + { + while(*string) + { + if (*string == '.') + { + if (dotfound) return; + dotfound = 10; + string++; + } //end if + if (dotfound) + { + *floatvalue = *floatvalue + (long double) (*string - '0') / + (long double) dotfound; + dotfound *= 10; + } //end if + else + { + *floatvalue = *floatvalue * 10.0 + (long double) (*string - '0'); + } //end else + string++; + } //end while + *intvalue = (unsigned long) *floatvalue; + } //end if + else if (subtype & TT_DECIMAL) + { + while(*string) *intvalue = *intvalue * 10 + (*string++ - '0'); + *floatvalue = *intvalue; + } //end else if + else if (subtype & TT_HEX) + { + //step over the leading 0x or 0X + string += 2; + while(*string) + { + *intvalue <<= 4; + if (*string >= 'a' && *string <= 'f') *intvalue += *string - 'a' + 10; + else if (*string >= 'A' && *string <= 'F') *intvalue += *string - 'A' + 10; + else *intvalue += *string - '0'; + string++; + } //end while + *floatvalue = *intvalue; + } //end else if + else if (subtype & TT_OCTAL) + { + //step over the first zero + string += 1; + while(*string) *intvalue = (*intvalue << 3) + (*string++ - '0'); + *floatvalue = *intvalue; + } //end else if + else if (subtype & TT_BINARY) + { + //step over the leading 0b or 0B + string += 2; + while(*string) *intvalue = (*intvalue << 1) + (*string++ - '0'); + *floatvalue = *intvalue; + } //end else if +} //end of the function NumberValue +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ReadNumber(script_t *script, token_t *token) +{ + int len = 0, i; + int octal, dot; + char c; +// unsigned long int intvalue = 0; +// long double floatvalue = 0; + + token->type = TT_NUMBER; + //check for a hexadecimal number + if (*script->script_p == '0' && + (*(script->script_p + 1) == 'x' || + *(script->script_p + 1) == 'X')) + { + token->string[len++] = *script->script_p++; + token->string[len++] = *script->script_p++; + c = *script->script_p; + //hexadecimal + while((c >= '0' && c <= '9') || + (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'A')) + { + token->string[len++] = *script->script_p++; + if (len >= MAX_TOKEN) + { + ScriptError(script, "hexadecimal number longer than MAX_TOKEN = %d", MAX_TOKEN); + return 0; + } //end if + c = *script->script_p; + } //end while + token->subtype |= TT_HEX; + } //end if +#ifdef BINARYNUMBERS + //check for a binary number + else if (*script->script_p == '0' && + (*(script->script_p + 1) == 'b' || + *(script->script_p + 1) == 'B')) + { + token->string[len++] = *script->script_p++; + token->string[len++] = *script->script_p++; + c = *script->script_p; + //binary + while(c == '0' || c == '1') + { + token->string[len++] = *script->script_p++; + if (len >= MAX_TOKEN) + { + ScriptError(script, "binary number longer than MAX_TOKEN = %d", MAX_TOKEN); + return 0; + } //end if + c = *script->script_p; + } //end while + token->subtype |= TT_BINARY; + } //end if +#endif //BINARYNUMBERS + else //decimal or octal integer or floating point number + { + octal = qfalse; + dot = qfalse; + if (*script->script_p == '0') octal = qtrue; + while(1) + { + c = *script->script_p; + if (c == '.') dot = qtrue; + else if (c == '8' || c == '9') octal = qfalse; + else if (c < '0' || c > '9') break; + token->string[len++] = *script->script_p++; + if (len >= MAX_TOKEN - 1) + { + ScriptError(script, "number longer than MAX_TOKEN = %d", MAX_TOKEN); + return 0; + } //end if + } //end while + if (octal) token->subtype |= TT_OCTAL; + else token->subtype |= TT_DECIMAL; + if (dot) token->subtype |= TT_FLOAT; + } //end else + for (i = 0; i < 2; i++) + { + c = *script->script_p; + //check for a LONG number + if ( (c == 'l' || c == 'L') // bk001204 - brackets + && !(token->subtype & TT_LONG)) + { + script->script_p++; + token->subtype |= TT_LONG; + } //end if + //check for an UNSIGNED number + else if ( (c == 'u' || c == 'U') // bk001204 - brackets + && !(token->subtype & (TT_UNSIGNED | TT_FLOAT))) + { + script->script_p++; + token->subtype |= TT_UNSIGNED; + } //end if + } //end for + token->string[len] = '\0'; +#ifdef NUMBERVALUE + NumberValue(token->string, token->subtype, &token->intvalue, &token->floatvalue); +#endif //NUMBERVALUE + if (!(token->subtype & TT_FLOAT)) token->subtype |= TT_INTEGER; + return 1; +} //end of the function PS_ReadNumber +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ReadLiteral(script_t *script, token_t *token) +{ + token->type = TT_LITERAL; + //first quote + token->string[0] = *script->script_p++; + //check for end of file + if (!*script->script_p) + { + ScriptError(script, "end of file before trailing \'"); + return 0; + } //end if + //if it is an escape character + if (*script->script_p == '\\') + { + if (!PS_ReadEscapeCharacter(script, &token->string[1])) return 0; + } //end if + else + { + token->string[1] = *script->script_p++; + } //end else + //check for trailing quote + if (*script->script_p != '\'') + { + ScriptWarning(script, "too many characters in literal, ignored"); + while(*script->script_p && + *script->script_p != '\'' && + *script->script_p != '\n') + { + script->script_p++; + } //end while + if (*script->script_p == '\'') script->script_p++; + } //end if + //store the trailing quote + token->string[2] = *script->script_p++; + //store trailing zero to end the string + token->string[3] = '\0'; + //the sub type is the integer literal value + token->subtype = token->string[1]; + // + return 1; +} //end of the function PS_ReadLiteral +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ReadPunctuation(script_t *script, token_t *token) +{ + int len; + char *p; + punctuation_t *punc; + +#ifdef PUNCTABLE + for (punc = script->punctuationtable[(unsigned int)*script->script_p]; punc; punc = punc->next) + { +#else + int i; + + for (i = 0; script->punctuations[i].p; i++) + { + punc = &script->punctuations[i]; +#endif //PUNCTABLE + p = punc->p; + len = strlen(p); + //if the script contains at least as much characters as the punctuation + if (script->script_p + len <= script->end_p) + { + //if the script contains the punctuation + if (!strncmp(script->script_p, p, len)) + { + strncpy(token->string, p, MAX_TOKEN); + script->script_p += len; + token->type = TT_PUNCTUATION; + //sub type is the number of the punctuation + token->subtype = punc->n; + return 1; + } //end if + } //end if + } //end for + return 0; +} //end of the function PS_ReadPunctuation +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ReadPrimitive(script_t *script, token_t *token) +{ + int len; + + len = 0; + while(*script->script_p > ' ' && *script->script_p != ';') + { + if (len >= MAX_TOKEN) + { + ScriptError(script, "primitive token longer than MAX_TOKEN = %d", MAX_TOKEN); + return 0; + } //end if + token->string[len++] = *script->script_p++; + } //end while + token->string[len] = 0; + //copy the token into the script structure + Com_Memcpy(&script->token, token, sizeof(token_t)); + //primitive reading successfull + return 1; +} //end of the function PS_ReadPrimitive +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ReadToken(script_t *script, token_t *token) +{ + //if there is a token available (from UnreadToken) + if (script->tokenavailable) + { + script->tokenavailable = 0; + Com_Memcpy(token, &script->token, sizeof(token_t)); + return 1; + } //end if + //save script pointer + script->lastscript_p = script->script_p; + //save line counter + script->lastline = script->line; + //clear the token stuff + Com_Memset(token, 0, sizeof(token_t)); + //start of the white space + script->whitespace_p = script->script_p; + token->whitespace_p = script->script_p; + //read unusefull stuff + if (!PS_ReadWhiteSpace(script)) return 0; + //end of the white space + script->endwhitespace_p = script->script_p; + token->endwhitespace_p = script->script_p; + //line the token is on + token->line = script->line; + //number of lines crossed before token + token->linescrossed = script->line - script->lastline; + //if there is a leading double quote + if (*script->script_p == '\"') + { + if (!PS_ReadString(script, token, '\"')) return 0; + } //end if + //if an literal + else if (*script->script_p == '\'') + { + //if (!PS_ReadLiteral(script, token)) return 0; + if (!PS_ReadString(script, token, '\'')) return 0; + } //end if + //if there is a number + else if ((*script->script_p >= '0' && *script->script_p <= '9') || + (*script->script_p == '.' && + (*(script->script_p + 1) >= '0' && *(script->script_p + 1) <= '9'))) + { + if (!PS_ReadNumber(script, token)) return 0; + } //end if + //if this is a primitive script + else if (script->flags & SCFL_PRIMITIVE) + { + return PS_ReadPrimitive(script, token); + } //end else if + //if there is a name + else if ((*script->script_p >= 'a' && *script->script_p <= 'z') || + (*script->script_p >= 'A' && *script->script_p <= 'Z') || + *script->script_p == '_') + { + if (!PS_ReadName(script, token)) return 0; + } //end if + //check for punctuations + else if (!PS_ReadPunctuation(script, token)) + { + ScriptError(script, "can't read token"); + return 0; + } //end if + //copy the token into the script structure + Com_Memcpy(&script->token, token, sizeof(token_t)); + //succesfully read a token + return 1; +} //end of the function PS_ReadToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ExpectTokenString(script_t *script, char *string) +{ + token_t token; + + if (!PS_ReadToken(script, &token)) + { + ScriptError(script, "couldn't find expected %s", string); + return 0; + } //end if + + if (strcmp(token.string, string)) + { + ScriptError(script, "expected %s, found %s", string, token.string); + return 0; + } //end if + return 1; +} //end of the function PS_ExpectToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ExpectTokenType(script_t *script, int type, int subtype, token_t *token) +{ + char str[MAX_TOKEN]; + + if (!PS_ReadToken(script, token)) + { + ScriptError(script, "couldn't read expected token"); + return 0; + } //end if + + if (token->type != type) + { + if (type == TT_STRING) strcpy(str, "string"); + if (type == TT_LITERAL) strcpy(str, "literal"); + if (type == TT_NUMBER) strcpy(str, "number"); + if (type == TT_NAME) strcpy(str, "name"); + if (type == TT_PUNCTUATION) strcpy(str, "punctuation"); + ScriptError(script, "expected a %s, found %s", str, token->string); + return 0; + } //end if + if (token->type == TT_NUMBER) + { + if ((token->subtype & subtype) != subtype) + { + if (subtype & TT_DECIMAL) strcpy(str, "decimal"); + if (subtype & TT_HEX) strcpy(str, "hex"); + if (subtype & TT_OCTAL) strcpy(str, "octal"); + if (subtype & TT_BINARY) strcpy(str, "binary"); + if (subtype & TT_LONG) strcat(str, " long"); + if (subtype & TT_UNSIGNED) strcat(str, " unsigned"); + if (subtype & TT_FLOAT) strcat(str, " float"); + if (subtype & TT_INTEGER) strcat(str, " integer"); + ScriptError(script, "expected %s, found %s", str, token->string); + return 0; + } //end if + } //end if + else if (token->type == TT_PUNCTUATION) + { + if (subtype < 0) + { + ScriptError(script, "BUG: wrong punctuation subtype"); + return 0; + } //end if + if (token->subtype != subtype) + { + ScriptError(script, "expected %s, found %s", + script->punctuations[subtype], token->string); + return 0; + } //end if + } //end else if + return 1; +} //end of the function PS_ExpectTokenType +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ExpectAnyToken(script_t *script, token_t *token) +{ + if (!PS_ReadToken(script, token)) + { + ScriptError(script, "couldn't read expected token"); + return 0; + } //end if + else + { + return 1; + } //end else +} //end of the function PS_ExpectAnyToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_CheckTokenString(script_t *script, char *string) +{ + token_t tok; + + if (!PS_ReadToken(script, &tok)) return 0; + //if the token is available + if (!strcmp(tok.string, string)) return 1; + //token not available + script->script_p = script->lastscript_p; + return 0; +} //end of the function PS_CheckTokenString +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_CheckTokenType(script_t *script, int type, int subtype, token_t *token) +{ + token_t tok; + + if (!PS_ReadToken(script, &tok)) return 0; + //if the type matches + if (tok.type == type && + (tok.subtype & subtype) == subtype) + { + Com_Memcpy(token, &tok, sizeof(token_t)); + return 1; + } //end if + //token is not available + script->script_p = script->lastscript_p; + return 0; +} //end of the function PS_CheckTokenType +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_SkipUntilString(script_t *script, char *string) +{ + token_t token; + + while(PS_ReadToken(script, &token)) + { + if (!strcmp(token.string, string)) return 1; + } //end while + return 0; +} //end of the function PS_SkipUntilString +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PS_UnreadLastToken(script_t *script) +{ + script->tokenavailable = 1; +} //end of the function UnreadLastToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PS_UnreadToken(script_t *script, token_t *token) +{ + Com_Memcpy(&script->token, token, sizeof(token_t)); + script->tokenavailable = 1; +} //end of the function UnreadToken +//============================================================================ +// returns the next character of the read white space, returns NULL if none +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +char PS_NextWhiteSpaceChar(script_t *script) +{ + if (script->whitespace_p != script->endwhitespace_p) + { + return *script->whitespace_p++; + } //end if + else + { + return 0; + } //end else +} //end of the function PS_NextWhiteSpaceChar +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void StripDoubleQuotes(char *string) +{ + if (*string == '\"') + { + strcpy(string, string+1); + } //end if + if (string[strlen(string)-1] == '\"') + { + string[strlen(string)-1] = '\0'; + } //end if +} //end of the function StripDoubleQuotes +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void StripSingleQuotes(char *string) +{ + if (*string == '\'') + { + strcpy(string, string+1); + } //end if + if (string[strlen(string)-1] == '\'') + { + string[strlen(string)-1] = '\0'; + } //end if +} //end of the function StripSingleQuotes +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +long double ReadSignedFloat(script_t *script) +{ + token_t token; + long double sign = 1; + + PS_ExpectAnyToken(script, &token); + if (!strcmp(token.string, "-")) + { + sign = -1; + PS_ExpectTokenType(script, TT_NUMBER, 0, &token); + } //end if + else if (token.type != TT_NUMBER) + { + ScriptError(script, "expected float value, found %s\n", token.string); + } //end else if + return sign * token.floatvalue; +} //end of the function ReadSignedFloat +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +signed long int ReadSignedInt(script_t *script) +{ + token_t token; + signed long int sign = 1; + + PS_ExpectAnyToken(script, &token); + if (!strcmp(token.string, "-")) + { + sign = -1; + PS_ExpectTokenType(script, TT_NUMBER, TT_INTEGER, &token); + } //end if + else if (token.type != TT_NUMBER || token.subtype == TT_FLOAT) + { + ScriptError(script, "expected integer value, found %s\n", token.string); + } //end else if + return sign * token.intvalue; +} //end of the function ReadSignedInt +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void SetScriptFlags(script_t *script, int flags) +{ + script->flags = flags; +} //end of the function SetScriptFlags +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int GetScriptFlags(script_t *script) +{ + return script->flags; +} //end of the function GetScriptFlags +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void ResetScript(script_t *script) +{ + //pointer in script buffer + script->script_p = script->buffer; + //pointer in script buffer before reading token + script->lastscript_p = script->buffer; + //begin of white space + script->whitespace_p = NULL; + //end of white space + script->endwhitespace_p = NULL; + //set if there's a token available in script->token + script->tokenavailable = 0; + // + script->line = 1; + script->lastline = 1; + //clear the saved token + Com_Memset(&script->token, 0, sizeof(token_t)); +} //end of the function ResetScript +//============================================================================ +// returns true if at the end of the script +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int EndOfScript(script_t *script) +{ + return script->script_p >= script->end_p; +} //end of the function EndOfScript +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int NumLinesCrossed(script_t *script) +{ + return script->line - script->lastline; +} //end of the function NumLinesCrossed +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int ScriptSkipTo(script_t *script, char *value) +{ + int len; + char firstchar; + + firstchar = *value; + len = strlen(value); + do + { + if (!PS_ReadWhiteSpace(script)) return 0; + if (*script->script_p == firstchar) + { + if (!strncmp(script->script_p, value, len)) + { + return 1; + } //end if + } //end if + script->script_p++; + } while(1); +} //end of the function ScriptSkipTo +#ifndef BOTLIB +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int FileLength(FILE *fp) +{ + int pos; + int end; + + pos = ftell(fp); + fseek(fp, 0, SEEK_END); + end = ftell(fp); + fseek(fp, pos, SEEK_SET); + + return end; +} //end of the function FileLength +#endif +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +script_t *LoadScriptFile(const char *filename) +{ +#ifdef BOTLIB + fileHandle_t fp; + char pathname[MAX_QPATH]; +#else + FILE *fp; +#endif + int length; + void *buffer; + script_t *script; + +#ifdef BOTLIB + if (strlen(basefolder)) + Com_sprintf(pathname, sizeof(pathname), "%s/%s", basefolder, filename); + else + Com_sprintf(pathname, sizeof(pathname), "%s", filename); + length = botimport.FS_FOpenFile( pathname, &fp, FS_READ ); + if (!fp) return NULL; +#else + fp = fopen(filename, "rb"); + if (!fp) return NULL; + + length = FileLength(fp); +#endif + + buffer = GetClearedMemory(sizeof(script_t) + length + 1); + script = (script_t *) buffer; + Com_Memset(script, 0, sizeof(script_t)); + strcpy(script->filename, filename); + script->buffer = (char *) buffer + sizeof(script_t); + script->buffer[length] = 0; + script->length = length; + //pointer in script buffer + script->script_p = script->buffer; + //pointer in script buffer before reading token + script->lastscript_p = script->buffer; + //pointer to end of script buffer + script->end_p = &script->buffer[length]; + //set if there's a token available in script->token + script->tokenavailable = 0; + // + script->line = 1; + script->lastline = 1; + // + SetScriptPunctuations(script, NULL); + // +#ifdef BOTLIB + botimport.FS_Read(script->buffer, length, fp); + botimport.FS_FCloseFile(fp); +#else + if (fread(script->buffer, length, 1, fp) != 1) + { + FreeMemory(buffer); + script = NULL; + } //end if + fclose(fp); +#endif + // + script->length = COM_Compress(script->buffer); + + return script; +} //end of the function LoadScriptFile +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +script_t *LoadScriptMemory(char *ptr, int length, char *name) +{ + void *buffer; + script_t *script; + + buffer = GetClearedMemory(sizeof(script_t) + length + 1); + script = (script_t *) buffer; + Com_Memset(script, 0, sizeof(script_t)); + strcpy(script->filename, name); + script->buffer = (char *) buffer + sizeof(script_t); + script->buffer[length] = 0; + script->length = length; + //pointer in script buffer + script->script_p = script->buffer; + //pointer in script buffer before reading token + script->lastscript_p = script->buffer; + //pointer to end of script buffer + script->end_p = &script->buffer[length]; + //set if there's a token available in script->token + script->tokenavailable = 0; + // + script->line = 1; + script->lastline = 1; + // + SetScriptPunctuations(script, NULL); + // + Com_Memcpy(script->buffer, ptr, length); + // + return script; +} //end of the function LoadScriptMemory +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void FreeScript(script_t *script) +{ +#ifdef PUNCTABLE + if (script->punctuationtable) FreeMemory(script->punctuationtable); +#endif //PUNCTABLE + FreeMemory(script); +} //end of the function FreeScript +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PS_SetBaseFolder(char *path) +{ +#ifdef BSPC + sprintf(basefolder, path); +#else + Com_sprintf(basefolder, sizeof(basefolder), path); +#endif +} //end of the function PS_SetBaseFolder diff --git a/code/botlib/l_script.h b/code/botlib/l_script.h index 2317907..88696bf 100755 --- a/code/botlib/l_script.h +++ b/code/botlib/l_script.h @@ -1,247 +1,247 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: l_script.h - * - * desc: lexicographical parser - * - * $Archive: /source/code/botlib/l_script.h $ - * - *****************************************************************************/ - -//undef if binary numbers of the form 0b... or 0B... are not allowed -#define BINARYNUMBERS -//undef if not using the token.intvalue and token.floatvalue -#define NUMBERVALUE -//use dollar sign also as punctuation -#define DOLLAR - -//maximum token length -#define MAX_TOKEN 1024 - -#if defined(BSPC) && !defined(QDECL) -#define QDECL -#endif - - -//script flags -#define SCFL_NOERRORS 0x0001 -#define SCFL_NOWARNINGS 0x0002 -#define SCFL_NOSTRINGWHITESPACES 0x0004 -#define SCFL_NOSTRINGESCAPECHARS 0x0008 -#define SCFL_PRIMITIVE 0x0010 -#define SCFL_NOBINARYNUMBERS 0x0020 -#define SCFL_NONUMBERVALUES 0x0040 - -//token types -#define TT_STRING 1 // string -#define TT_LITERAL 2 // literal -#define TT_NUMBER 3 // number -#define TT_NAME 4 // name -#define TT_PUNCTUATION 5 // punctuation - -//string sub type -//--------------- -// the length of the string -//literal sub type -//---------------- -// the ASCII code of the literal -//number sub type -//--------------- -#define TT_DECIMAL 0x0008 // decimal number -#define TT_HEX 0x0100 // hexadecimal number -#define TT_OCTAL 0x0200 // octal number -#ifdef BINARYNUMBERS -#define TT_BINARY 0x0400 // binary number -#endif //BINARYNUMBERS -#define TT_FLOAT 0x0800 // floating point number -#define TT_INTEGER 0x1000 // integer number -#define TT_LONG 0x2000 // long number -#define TT_UNSIGNED 0x4000 // unsigned number -//punctuation sub type -//-------------------- -#define P_RSHIFT_ASSIGN 1 -#define P_LSHIFT_ASSIGN 2 -#define P_PARMS 3 -#define P_PRECOMPMERGE 4 - -#define P_LOGIC_AND 5 -#define P_LOGIC_OR 6 -#define P_LOGIC_GEQ 7 -#define P_LOGIC_LEQ 8 -#define P_LOGIC_EQ 9 -#define P_LOGIC_UNEQ 10 - -#define P_MUL_ASSIGN 11 -#define P_DIV_ASSIGN 12 -#define P_MOD_ASSIGN 13 -#define P_ADD_ASSIGN 14 -#define P_SUB_ASSIGN 15 -#define P_INC 16 -#define P_DEC 17 - -#define P_BIN_AND_ASSIGN 18 -#define P_BIN_OR_ASSIGN 19 -#define P_BIN_XOR_ASSIGN 20 -#define P_RSHIFT 21 -#define P_LSHIFT 22 - -#define P_POINTERREF 23 -#define P_CPP1 24 -#define P_CPP2 25 -#define P_MUL 26 -#define P_DIV 27 -#define P_MOD 28 -#define P_ADD 29 -#define P_SUB 30 -#define P_ASSIGN 31 - -#define P_BIN_AND 32 -#define P_BIN_OR 33 -#define P_BIN_XOR 34 -#define P_BIN_NOT 35 - -#define P_LOGIC_NOT 36 -#define P_LOGIC_GREATER 37 -#define P_LOGIC_LESS 38 - -#define P_REF 39 -#define P_COMMA 40 -#define P_SEMICOLON 41 -#define P_COLON 42 -#define P_QUESTIONMARK 43 - -#define P_PARENTHESESOPEN 44 -#define P_PARENTHESESCLOSE 45 -#define P_BRACEOPEN 46 -#define P_BRACECLOSE 47 -#define P_SQBRACKETOPEN 48 -#define P_SQBRACKETCLOSE 49 -#define P_BACKSLASH 50 - -#define P_PRECOMP 51 -#define P_DOLLAR 52 -//name sub type -//------------- -// the length of the name - -//punctuation -typedef struct punctuation_s -{ - char *p; //punctuation character(s) - int n; //punctuation indication - struct punctuation_s *next; //next punctuation -} punctuation_t; - -//token -typedef struct token_s -{ - char string[MAX_TOKEN]; //available token - int type; //last read token type - int subtype; //last read token sub type -#ifdef NUMBERVALUE - unsigned long int intvalue; //integer value - long double floatvalue; //floating point value -#endif //NUMBERVALUE - char *whitespace_p; //start of white space before token - char *endwhitespace_p; //start of white space before token - int line; //line the token was on - int linescrossed; //lines crossed in white space - struct token_s *next; //next token in chain -} token_t; - -//script file -typedef struct script_s -{ - char filename[1024]; //file name of the script - char *buffer; //buffer containing the script - char *script_p; //current pointer in the script - char *end_p; //pointer to the end of the script - char *lastscript_p; //script pointer before reading token - char *whitespace_p; //begin of the white space - char *endwhitespace_p; //end of the white space - int length; //length of the script in bytes - int line; //current line in script - int lastline; //line before reading token - int tokenavailable; //set by UnreadLastToken - int flags; //several script flags - punctuation_t *punctuations; //the punctuations used in the script - punctuation_t **punctuationtable; - token_t token; //available token - struct script_s *next; //next script in a chain -} script_t; - -//read a token from the script -int PS_ReadToken(script_t *script, token_t *token); -//expect a certain token -int PS_ExpectTokenString(script_t *script, char *string); -//expect a certain token type -int PS_ExpectTokenType(script_t *script, int type, int subtype, token_t *token); -//expect a token -int PS_ExpectAnyToken(script_t *script, token_t *token); -//returns true when the token is available -int PS_CheckTokenString(script_t *script, char *string); -//returns true an reads the token when a token with the given type is available -int PS_CheckTokenType(script_t *script, int type, int subtype, token_t *token); -//skip tokens until the given token string is read -int PS_SkipUntilString(script_t *script, char *string); -//unread the last token read from the script -void PS_UnreadLastToken(script_t *script); -//unread the given token -void PS_UnreadToken(script_t *script, token_t *token); -//returns the next character of the read white space, returns NULL if none -char PS_NextWhiteSpaceChar(script_t *script); -//remove any leading and trailing double quotes from the token -void StripDoubleQuotes(char *string); -//remove any leading and trailing single quotes from the token -void StripSingleQuotes(char *string); -//read a possible signed integer -signed long int ReadSignedInt(script_t *script); -//read a possible signed floating point number -long double ReadSignedFloat(script_t *script); -//set an array with punctuations, NULL restores default C/C++ set -void SetScriptPunctuations(script_t *script, punctuation_t *p); -//set script flags -void SetScriptFlags(script_t *script, int flags); -//get script flags -int GetScriptFlags(script_t *script); -//reset a script -void ResetScript(script_t *script); -//returns true if at the end of the script -int EndOfScript(script_t *script); -//returns a pointer to the punctuation with the given number -char *PunctuationFromNum(script_t *script, int num); -//load a script from the given file at the given offset with the given length -script_t *LoadScriptFile(const char *filename); -//load a script from the given memory with the given length -script_t *LoadScriptMemory(char *ptr, int length, char *name); -//free a script -void FreeScript(script_t *script); -//set the base folder to load files from -void PS_SetBaseFolder(char *path); -//print a script error with filename and line number -void QDECL ScriptError(script_t *script, char *str, ...); -//print a script warning with filename and line number -void QDECL ScriptWarning(script_t *script, char *str, ...); - - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: l_script.h + * + * desc: lexicographical parser + * + * $Archive: /source/code/botlib/l_script.h $ + * + *****************************************************************************/ + +//undef if binary numbers of the form 0b... or 0B... are not allowed +#define BINARYNUMBERS +//undef if not using the token.intvalue and token.floatvalue +#define NUMBERVALUE +//use dollar sign also as punctuation +#define DOLLAR + +//maximum token length +#define MAX_TOKEN 1024 + +#if defined(BSPC) && !defined(QDECL) +#define QDECL +#endif + + +//script flags +#define SCFL_NOERRORS 0x0001 +#define SCFL_NOWARNINGS 0x0002 +#define SCFL_NOSTRINGWHITESPACES 0x0004 +#define SCFL_NOSTRINGESCAPECHARS 0x0008 +#define SCFL_PRIMITIVE 0x0010 +#define SCFL_NOBINARYNUMBERS 0x0020 +#define SCFL_NONUMBERVALUES 0x0040 + +//token types +#define TT_STRING 1 // string +#define TT_LITERAL 2 // literal +#define TT_NUMBER 3 // number +#define TT_NAME 4 // name +#define TT_PUNCTUATION 5 // punctuation + +//string sub type +//--------------- +// the length of the string +//literal sub type +//---------------- +// the ASCII code of the literal +//number sub type +//--------------- +#define TT_DECIMAL 0x0008 // decimal number +#define TT_HEX 0x0100 // hexadecimal number +#define TT_OCTAL 0x0200 // octal number +#ifdef BINARYNUMBERS +#define TT_BINARY 0x0400 // binary number +#endif //BINARYNUMBERS +#define TT_FLOAT 0x0800 // floating point number +#define TT_INTEGER 0x1000 // integer number +#define TT_LONG 0x2000 // long number +#define TT_UNSIGNED 0x4000 // unsigned number +//punctuation sub type +//-------------------- +#define P_RSHIFT_ASSIGN 1 +#define P_LSHIFT_ASSIGN 2 +#define P_PARMS 3 +#define P_PRECOMPMERGE 4 + +#define P_LOGIC_AND 5 +#define P_LOGIC_OR 6 +#define P_LOGIC_GEQ 7 +#define P_LOGIC_LEQ 8 +#define P_LOGIC_EQ 9 +#define P_LOGIC_UNEQ 10 + +#define P_MUL_ASSIGN 11 +#define P_DIV_ASSIGN 12 +#define P_MOD_ASSIGN 13 +#define P_ADD_ASSIGN 14 +#define P_SUB_ASSIGN 15 +#define P_INC 16 +#define P_DEC 17 + +#define P_BIN_AND_ASSIGN 18 +#define P_BIN_OR_ASSIGN 19 +#define P_BIN_XOR_ASSIGN 20 +#define P_RSHIFT 21 +#define P_LSHIFT 22 + +#define P_POINTERREF 23 +#define P_CPP1 24 +#define P_CPP2 25 +#define P_MUL 26 +#define P_DIV 27 +#define P_MOD 28 +#define P_ADD 29 +#define P_SUB 30 +#define P_ASSIGN 31 + +#define P_BIN_AND 32 +#define P_BIN_OR 33 +#define P_BIN_XOR 34 +#define P_BIN_NOT 35 + +#define P_LOGIC_NOT 36 +#define P_LOGIC_GREATER 37 +#define P_LOGIC_LESS 38 + +#define P_REF 39 +#define P_COMMA 40 +#define P_SEMICOLON 41 +#define P_COLON 42 +#define P_QUESTIONMARK 43 + +#define P_PARENTHESESOPEN 44 +#define P_PARENTHESESCLOSE 45 +#define P_BRACEOPEN 46 +#define P_BRACECLOSE 47 +#define P_SQBRACKETOPEN 48 +#define P_SQBRACKETCLOSE 49 +#define P_BACKSLASH 50 + +#define P_PRECOMP 51 +#define P_DOLLAR 52 +//name sub type +//------------- +// the length of the name + +//punctuation +typedef struct punctuation_s +{ + char *p; //punctuation character(s) + int n; //punctuation indication + struct punctuation_s *next; //next punctuation +} punctuation_t; + +//token +typedef struct token_s +{ + char string[MAX_TOKEN]; //available token + int type; //last read token type + int subtype; //last read token sub type +#ifdef NUMBERVALUE + unsigned long int intvalue; //integer value + long double floatvalue; //floating point value +#endif //NUMBERVALUE + char *whitespace_p; //start of white space before token + char *endwhitespace_p; //start of white space before token + int line; //line the token was on + int linescrossed; //lines crossed in white space + struct token_s *next; //next token in chain +} token_t; + +//script file +typedef struct script_s +{ + char filename[1024]; //file name of the script + char *buffer; //buffer containing the script + char *script_p; //current pointer in the script + char *end_p; //pointer to the end of the script + char *lastscript_p; //script pointer before reading token + char *whitespace_p; //begin of the white space + char *endwhitespace_p; //end of the white space + int length; //length of the script in bytes + int line; //current line in script + int lastline; //line before reading token + int tokenavailable; //set by UnreadLastToken + int flags; //several script flags + punctuation_t *punctuations; //the punctuations used in the script + punctuation_t **punctuationtable; + token_t token; //available token + struct script_s *next; //next script in a chain +} script_t; + +//read a token from the script +int PS_ReadToken(script_t *script, token_t *token); +//expect a certain token +int PS_ExpectTokenString(script_t *script, char *string); +//expect a certain token type +int PS_ExpectTokenType(script_t *script, int type, int subtype, token_t *token); +//expect a token +int PS_ExpectAnyToken(script_t *script, token_t *token); +//returns true when the token is available +int PS_CheckTokenString(script_t *script, char *string); +//returns true an reads the token when a token with the given type is available +int PS_CheckTokenType(script_t *script, int type, int subtype, token_t *token); +//skip tokens until the given token string is read +int PS_SkipUntilString(script_t *script, char *string); +//unread the last token read from the script +void PS_UnreadLastToken(script_t *script); +//unread the given token +void PS_UnreadToken(script_t *script, token_t *token); +//returns the next character of the read white space, returns NULL if none +char PS_NextWhiteSpaceChar(script_t *script); +//remove any leading and trailing double quotes from the token +void StripDoubleQuotes(char *string); +//remove any leading and trailing single quotes from the token +void StripSingleQuotes(char *string); +//read a possible signed integer +signed long int ReadSignedInt(script_t *script); +//read a possible signed floating point number +long double ReadSignedFloat(script_t *script); +//set an array with punctuations, NULL restores default C/C++ set +void SetScriptPunctuations(script_t *script, punctuation_t *p); +//set script flags +void SetScriptFlags(script_t *script, int flags); +//get script flags +int GetScriptFlags(script_t *script); +//reset a script +void ResetScript(script_t *script); +//returns true if at the end of the script +int EndOfScript(script_t *script); +//returns a pointer to the punctuation with the given number +char *PunctuationFromNum(script_t *script, int num); +//load a script from the given file at the given offset with the given length +script_t *LoadScriptFile(const char *filename); +//load a script from the given memory with the given length +script_t *LoadScriptMemory(char *ptr, int length, char *name); +//free a script +void FreeScript(script_t *script); +//set the base folder to load files from +void PS_SetBaseFolder(char *path); +//print a script error with filename and line number +void QDECL ScriptError(script_t *script, char *str, ...); +//print a script warning with filename and line number +void QDECL ScriptWarning(script_t *script, char *str, ...); + + diff --git a/code/botlib/l_struct.c b/code/botlib/l_struct.c index dea3b20..c628821 100755 --- a/code/botlib/l_struct.c +++ b/code/botlib/l_struct.c @@ -1,462 +1,462 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: l_struct.c - * - * desc: structure reading / writing - * - * $Archive: /MissionPack/CODE/botlib/l_struct.c $ - * - *****************************************************************************/ - -#ifdef BOTLIB -#include "../game/q_shared.h" -#include "../game/botlib.h" //for the include of be_interface.h -#include "l_script.h" -#include "l_precomp.h" -#include "l_struct.h" -#include "l_utils.h" -#include "be_interface.h" -#endif //BOTLIB - -#ifdef BSPC -//include files for usage in the BSP Converter -#include "../bspc/qbsp.h" -#include "../bspc/l_log.h" -#include "../bspc/l_mem.h" -#include "l_precomp.h" -#include "l_struct.h" - -#define qtrue true -#define qfalse false -#endif //BSPC - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -fielddef_t *FindField(fielddef_t *defs, char *name) -{ - int i; - - for (i = 0; defs[i].name; i++) - { - if (!strcmp(defs[i].name, name)) return &defs[i]; - } //end for - return NULL; -} //end of the function FindField -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean ReadNumber(source_t *source, fielddef_t *fd, void *p) -{ - token_t token; - int negative = qfalse; - long int intval, intmin = 0, intmax = 0; - double floatval; - - if (!PC_ExpectAnyToken(source, &token)) return 0; - - //check for minus sign - if (token.type == TT_PUNCTUATION) - { - if (fd->type & FT_UNSIGNED) - { - SourceError(source, "expected unsigned value, found %s", token.string); - return 0; - } //end if - //if not a minus sign - if (strcmp(token.string, "-")) - { - SourceError(source, "unexpected punctuation %s", token.string); - return 0; - } //end if - negative = qtrue; - //read the number - if (!PC_ExpectAnyToken(source, &token)) return 0; - } //end if - //check if it is a number - if (token.type != TT_NUMBER) - { - SourceError(source, "expected number, found %s", token.string); - return 0; - } //end if - //check for a float value - if (token.subtype & TT_FLOAT) - { - if ((fd->type & FT_TYPE) != FT_FLOAT) - { - SourceError(source, "unexpected float"); - return 0; - } //end if - floatval = token.floatvalue; - if (negative) floatval = -floatval; - if (fd->type & FT_BOUNDED) - { - if (floatval < fd->floatmin || floatval > fd->floatmax) - { - SourceError(source, "float out of range [%f, %f]", fd->floatmin, fd->floatmax); - return 0; - } //end if - } //end if - *(float *) p = (float) floatval; - return 1; - } //end if - // - intval = token.intvalue; - if (negative) intval = -intval; - //check bounds - if ((fd->type & FT_TYPE) == FT_CHAR) - { - if (fd->type & FT_UNSIGNED) {intmin = 0; intmax = 255;} - else {intmin = -128; intmax = 127;} - } //end if - if ((fd->type & FT_TYPE) == FT_INT) - { - if (fd->type & FT_UNSIGNED) {intmin = 0; intmax = 65535;} - else {intmin = -32768; intmax = 32767;} - } //end else if - if ((fd->type & FT_TYPE) == FT_CHAR || (fd->type & FT_TYPE) == FT_INT) - { - if (fd->type & FT_BOUNDED) - { - intmin = Maximum(intmin, fd->floatmin); - intmax = Minimum(intmax, fd->floatmax); - } //end if - if (intval < intmin || intval > intmax) - { - SourceError(source, "value %d out of range [%d, %d]", intval, intmin, intmax); - return 0; - } //end if - } //end if - else if ((fd->type & FT_TYPE) == FT_FLOAT) - { - if (fd->type & FT_BOUNDED) - { - if (intval < fd->floatmin || intval > fd->floatmax) - { - SourceError(source, "value %d out of range [%f, %f]", intval, fd->floatmin, fd->floatmax); - return 0; - } //end if - } //end if - } //end else if - //store the value - if ((fd->type & FT_TYPE) == FT_CHAR) - { - if (fd->type & FT_UNSIGNED) *(unsigned char *) p = (unsigned char) intval; - else *(char *) p = (char) intval; - } //end if - else if ((fd->type & FT_TYPE) == FT_INT) - { - if (fd->type & FT_UNSIGNED) *(unsigned int *) p = (unsigned int) intval; - else *(int *) p = (int) intval; - } //end else - else if ((fd->type & FT_TYPE) == FT_FLOAT) - { - *(float *) p = (float) intval; - } //end else - return 1; -} //end of the function ReadNumber -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean ReadChar(source_t *source, fielddef_t *fd, void *p) -{ - token_t token; - - if (!PC_ExpectAnyToken(source, &token)) return 0; - - //take literals into account - if (token.type == TT_LITERAL) - { - StripSingleQuotes(token.string); - *(char *) p = token.string[0]; - } //end if - else - { - PC_UnreadLastToken(source); - if (!ReadNumber(source, fd, p)) return 0; - } //end if - return 1; -} //end of the function ReadChar -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int ReadString(source_t *source, fielddef_t *fd, void *p) -{ - token_t token; - - if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) return 0; - //remove the double quotes - StripDoubleQuotes(token.string); - //copy the string - strncpy((char *) p, token.string, MAX_STRINGFIELD); - //make sure the string is closed with a zero - ((char *)p)[MAX_STRINGFIELD-1] = '\0'; - // - return 1; -} //end of the function ReadString -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int ReadStructure(source_t *source, structdef_t *def, char *structure) -{ - token_t token; - fielddef_t *fd; - void *p; - int num; - - if (!PC_ExpectTokenString(source, "{")) return 0; - while(1) - { - if (!PC_ExpectAnyToken(source, &token)) return qfalse; - //if end of structure - if (!strcmp(token.string, "}")) break; - //find the field with the name - fd = FindField(def->fields, token.string); - if (!fd) - { - SourceError(source, "unknown structure field %s", token.string); - return qfalse; - } //end if - if (fd->type & FT_ARRAY) - { - num = fd->maxarray; - if (!PC_ExpectTokenString(source, "{")) return qfalse; - } //end if - else - { - num = 1; - } //end else - p = (void *)(structure + fd->offset); - while (num-- > 0) - { - if (fd->type & FT_ARRAY) - { - if (PC_CheckTokenString(source, "}")) break; - } //end if - switch(fd->type & FT_TYPE) - { - case FT_CHAR: - { - if (!ReadChar(source, fd, p)) return qfalse; - p = (char *) p + sizeof(char); - break; - } //end case - case FT_INT: - { - if (!ReadNumber(source, fd, p)) return qfalse; - p = (char *) p + sizeof(int); - break; - } //end case - case FT_FLOAT: - { - if (!ReadNumber(source, fd, p)) return qfalse; - p = (char *) p + sizeof(float); - break; - } //end case - case FT_STRING: - { - if (!ReadString(source, fd, p)) return qfalse; - p = (char *) p + MAX_STRINGFIELD; - break; - } //end case - case FT_STRUCT: - { - if (!fd->substruct) - { - SourceError(source, "BUG: no sub structure defined"); - return qfalse; - } //end if - ReadStructure(source, fd->substruct, (char *) p); - p = (char *) p + fd->substruct->size; - break; - } //end case - } //end switch - if (fd->type & FT_ARRAY) - { - if (!PC_ExpectAnyToken(source, &token)) return qfalse; - if (!strcmp(token.string, "}")) break; - if (strcmp(token.string, ",")) - { - SourceError(source, "expected a comma, found %s", token.string); - return qfalse; - } //end if - } //end if - } //end while - } //end while - return qtrue; -} //end of the function ReadStructure -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int WriteIndent(FILE *fp, int indent) -{ - while(indent-- > 0) - { - if (fprintf(fp, "\t") < 0) return qfalse; - } //end while - return qtrue; -} //end of the function WriteIndent -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int WriteFloat(FILE *fp, float value) -{ - char buf[128]; - int l; - - sprintf(buf, "%f", value); - l = strlen(buf); - //strip any trailing zeros - while(l-- > 1) - { - if (buf[l] != '0' && buf[l] != '.') break; - if (buf[l] == '.') - { - buf[l] = 0; - break; - } //end if - buf[l] = 0; - } //end while - //write the float to file - if (fprintf(fp, "%s", buf) < 0) return 0; - return 1; -} //end of the function WriteFloat -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int WriteStructWithIndent(FILE *fp, structdef_t *def, char *structure, int indent) -{ - int i, num; - void *p; - fielddef_t *fd; - - if (!WriteIndent(fp, indent)) return qfalse; - if (fprintf(fp, "{\r\n") < 0) return qfalse; - - indent++; - for (i = 0; def->fields[i].name; i++) - { - fd = &def->fields[i]; - if (!WriteIndent(fp, indent)) return qfalse; - if (fprintf(fp, "%s\t", fd->name) < 0) return qfalse; - p = (void *)(structure + fd->offset); - if (fd->type & FT_ARRAY) - { - num = fd->maxarray; - if (fprintf(fp, "{") < 0) return qfalse; - } //end if - else - { - num = 1; - } //end else - while(num-- > 0) - { - switch(fd->type & FT_TYPE) - { - case FT_CHAR: - { - if (fprintf(fp, "%d", *(char *) p) < 0) return qfalse; - p = (char *) p + sizeof(char); - break; - } //end case - case FT_INT: - { - if (fprintf(fp, "%d", *(int *) p) < 0) return qfalse; - p = (char *) p + sizeof(int); - break; - } //end case - case FT_FLOAT: - { - if (!WriteFloat(fp, *(float *)p)) return qfalse; - p = (char *) p + sizeof(float); - break; - } //end case - case FT_STRING: - { - if (fprintf(fp, "\"%s\"", (char *) p) < 0) return qfalse; - p = (char *) p + MAX_STRINGFIELD; - break; - } //end case - case FT_STRUCT: - { - if (!WriteStructWithIndent(fp, fd->substruct, structure, indent)) return qfalse; - p = (char *) p + fd->substruct->size; - break; - } //end case - } //end switch - if (fd->type & FT_ARRAY) - { - if (num > 0) - { - if (fprintf(fp, ",") < 0) return qfalse; - } //end if - else - { - if (fprintf(fp, "}") < 0) return qfalse; - } //end else - } //end if - } //end while - if (fprintf(fp, "\r\n") < 0) return qfalse; - } //end for - indent--; - - if (!WriteIndent(fp, indent)) return qfalse; - if (fprintf(fp, "}\r\n") < 0) return qfalse; - return qtrue; -} //end of the function WriteStructWithIndent -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int WriteStructure(FILE *fp, structdef_t *def, char *structure) -{ - return WriteStructWithIndent(fp, def, structure, 0); -} //end of the function WriteStructure - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: l_struct.c + * + * desc: structure reading / writing + * + * $Archive: /MissionPack/CODE/botlib/l_struct.c $ + * + *****************************************************************************/ + +#ifdef BOTLIB +#include "../game/q_shared.h" +#include "../game/botlib.h" //for the include of be_interface.h +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_utils.h" +#include "be_interface.h" +#endif //BOTLIB + +#ifdef BSPC +//include files for usage in the BSP Converter +#include "../bspc/qbsp.h" +#include "../bspc/l_log.h" +#include "../bspc/l_mem.h" +#include "l_precomp.h" +#include "l_struct.h" + +#define qtrue true +#define qfalse false +#endif //BSPC + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +fielddef_t *FindField(fielddef_t *defs, char *name) +{ + int i; + + for (i = 0; defs[i].name; i++) + { + if (!strcmp(defs[i].name, name)) return &defs[i]; + } //end for + return NULL; +} //end of the function FindField +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean ReadNumber(source_t *source, fielddef_t *fd, void *p) +{ + token_t token; + int negative = qfalse; + long int intval, intmin = 0, intmax = 0; + double floatval; + + if (!PC_ExpectAnyToken(source, &token)) return 0; + + //check for minus sign + if (token.type == TT_PUNCTUATION) + { + if (fd->type & FT_UNSIGNED) + { + SourceError(source, "expected unsigned value, found %s", token.string); + return 0; + } //end if + //if not a minus sign + if (strcmp(token.string, "-")) + { + SourceError(source, "unexpected punctuation %s", token.string); + return 0; + } //end if + negative = qtrue; + //read the number + if (!PC_ExpectAnyToken(source, &token)) return 0; + } //end if + //check if it is a number + if (token.type != TT_NUMBER) + { + SourceError(source, "expected number, found %s", token.string); + return 0; + } //end if + //check for a float value + if (token.subtype & TT_FLOAT) + { + if ((fd->type & FT_TYPE) != FT_FLOAT) + { + SourceError(source, "unexpected float"); + return 0; + } //end if + floatval = token.floatvalue; + if (negative) floatval = -floatval; + if (fd->type & FT_BOUNDED) + { + if (floatval < fd->floatmin || floatval > fd->floatmax) + { + SourceError(source, "float out of range [%f, %f]", fd->floatmin, fd->floatmax); + return 0; + } //end if + } //end if + *(float *) p = (float) floatval; + return 1; + } //end if + // + intval = token.intvalue; + if (negative) intval = -intval; + //check bounds + if ((fd->type & FT_TYPE) == FT_CHAR) + { + if (fd->type & FT_UNSIGNED) {intmin = 0; intmax = 255;} + else {intmin = -128; intmax = 127;} + } //end if + if ((fd->type & FT_TYPE) == FT_INT) + { + if (fd->type & FT_UNSIGNED) {intmin = 0; intmax = 65535;} + else {intmin = -32768; intmax = 32767;} + } //end else if + if ((fd->type & FT_TYPE) == FT_CHAR || (fd->type & FT_TYPE) == FT_INT) + { + if (fd->type & FT_BOUNDED) + { + intmin = Maximum(intmin, fd->floatmin); + intmax = Minimum(intmax, fd->floatmax); + } //end if + if (intval < intmin || intval > intmax) + { + SourceError(source, "value %d out of range [%d, %d]", intval, intmin, intmax); + return 0; + } //end if + } //end if + else if ((fd->type & FT_TYPE) == FT_FLOAT) + { + if (fd->type & FT_BOUNDED) + { + if (intval < fd->floatmin || intval > fd->floatmax) + { + SourceError(source, "value %d out of range [%f, %f]", intval, fd->floatmin, fd->floatmax); + return 0; + } //end if + } //end if + } //end else if + //store the value + if ((fd->type & FT_TYPE) == FT_CHAR) + { + if (fd->type & FT_UNSIGNED) *(unsigned char *) p = (unsigned char) intval; + else *(char *) p = (char) intval; + } //end if + else if ((fd->type & FT_TYPE) == FT_INT) + { + if (fd->type & FT_UNSIGNED) *(unsigned int *) p = (unsigned int) intval; + else *(int *) p = (int) intval; + } //end else + else if ((fd->type & FT_TYPE) == FT_FLOAT) + { + *(float *) p = (float) intval; + } //end else + return 1; +} //end of the function ReadNumber +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean ReadChar(source_t *source, fielddef_t *fd, void *p) +{ + token_t token; + + if (!PC_ExpectAnyToken(source, &token)) return 0; + + //take literals into account + if (token.type == TT_LITERAL) + { + StripSingleQuotes(token.string); + *(char *) p = token.string[0]; + } //end if + else + { + PC_UnreadLastToken(source); + if (!ReadNumber(source, fd, p)) return 0; + } //end if + return 1; +} //end of the function ReadChar +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int ReadString(source_t *source, fielddef_t *fd, void *p) +{ + token_t token; + + if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) return 0; + //remove the double quotes + StripDoubleQuotes(token.string); + //copy the string + strncpy((char *) p, token.string, MAX_STRINGFIELD); + //make sure the string is closed with a zero + ((char *)p)[MAX_STRINGFIELD-1] = '\0'; + // + return 1; +} //end of the function ReadString +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int ReadStructure(source_t *source, structdef_t *def, char *structure) +{ + token_t token; + fielddef_t *fd; + void *p; + int num; + + if (!PC_ExpectTokenString(source, "{")) return 0; + while(1) + { + if (!PC_ExpectAnyToken(source, &token)) return qfalse; + //if end of structure + if (!strcmp(token.string, "}")) break; + //find the field with the name + fd = FindField(def->fields, token.string); + if (!fd) + { + SourceError(source, "unknown structure field %s", token.string); + return qfalse; + } //end if + if (fd->type & FT_ARRAY) + { + num = fd->maxarray; + if (!PC_ExpectTokenString(source, "{")) return qfalse; + } //end if + else + { + num = 1; + } //end else + p = (void *)(structure + fd->offset); + while (num-- > 0) + { + if (fd->type & FT_ARRAY) + { + if (PC_CheckTokenString(source, "}")) break; + } //end if + switch(fd->type & FT_TYPE) + { + case FT_CHAR: + { + if (!ReadChar(source, fd, p)) return qfalse; + p = (char *) p + sizeof(char); + break; + } //end case + case FT_INT: + { + if (!ReadNumber(source, fd, p)) return qfalse; + p = (char *) p + sizeof(int); + break; + } //end case + case FT_FLOAT: + { + if (!ReadNumber(source, fd, p)) return qfalse; + p = (char *) p + sizeof(float); + break; + } //end case + case FT_STRING: + { + if (!ReadString(source, fd, p)) return qfalse; + p = (char *) p + MAX_STRINGFIELD; + break; + } //end case + case FT_STRUCT: + { + if (!fd->substruct) + { + SourceError(source, "BUG: no sub structure defined"); + return qfalse; + } //end if + ReadStructure(source, fd->substruct, (char *) p); + p = (char *) p + fd->substruct->size; + break; + } //end case + } //end switch + if (fd->type & FT_ARRAY) + { + if (!PC_ExpectAnyToken(source, &token)) return qfalse; + if (!strcmp(token.string, "}")) break; + if (strcmp(token.string, ",")) + { + SourceError(source, "expected a comma, found %s", token.string); + return qfalse; + } //end if + } //end if + } //end while + } //end while + return qtrue; +} //end of the function ReadStructure +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WriteIndent(FILE *fp, int indent) +{ + while(indent-- > 0) + { + if (fprintf(fp, "\t") < 0) return qfalse; + } //end while + return qtrue; +} //end of the function WriteIndent +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WriteFloat(FILE *fp, float value) +{ + char buf[128]; + int l; + + sprintf(buf, "%f", value); + l = strlen(buf); + //strip any trailing zeros + while(l-- > 1) + { + if (buf[l] != '0' && buf[l] != '.') break; + if (buf[l] == '.') + { + buf[l] = 0; + break; + } //end if + buf[l] = 0; + } //end while + //write the float to file + if (fprintf(fp, "%s", buf) < 0) return 0; + return 1; +} //end of the function WriteFloat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WriteStructWithIndent(FILE *fp, structdef_t *def, char *structure, int indent) +{ + int i, num; + void *p; + fielddef_t *fd; + + if (!WriteIndent(fp, indent)) return qfalse; + if (fprintf(fp, "{\r\n") < 0) return qfalse; + + indent++; + for (i = 0; def->fields[i].name; i++) + { + fd = &def->fields[i]; + if (!WriteIndent(fp, indent)) return qfalse; + if (fprintf(fp, "%s\t", fd->name) < 0) return qfalse; + p = (void *)(structure + fd->offset); + if (fd->type & FT_ARRAY) + { + num = fd->maxarray; + if (fprintf(fp, "{") < 0) return qfalse; + } //end if + else + { + num = 1; + } //end else + while(num-- > 0) + { + switch(fd->type & FT_TYPE) + { + case FT_CHAR: + { + if (fprintf(fp, "%d", *(char *) p) < 0) return qfalse; + p = (char *) p + sizeof(char); + break; + } //end case + case FT_INT: + { + if (fprintf(fp, "%d", *(int *) p) < 0) return qfalse; + p = (char *) p + sizeof(int); + break; + } //end case + case FT_FLOAT: + { + if (!WriteFloat(fp, *(float *)p)) return qfalse; + p = (char *) p + sizeof(float); + break; + } //end case + case FT_STRING: + { + if (fprintf(fp, "\"%s\"", (char *) p) < 0) return qfalse; + p = (char *) p + MAX_STRINGFIELD; + break; + } //end case + case FT_STRUCT: + { + if (!WriteStructWithIndent(fp, fd->substruct, structure, indent)) return qfalse; + p = (char *) p + fd->substruct->size; + break; + } //end case + } //end switch + if (fd->type & FT_ARRAY) + { + if (num > 0) + { + if (fprintf(fp, ",") < 0) return qfalse; + } //end if + else + { + if (fprintf(fp, "}") < 0) return qfalse; + } //end else + } //end if + } //end while + if (fprintf(fp, "\r\n") < 0) return qfalse; + } //end for + indent--; + + if (!WriteIndent(fp, indent)) return qfalse; + if (fprintf(fp, "}\r\n") < 0) return qfalse; + return qtrue; +} //end of the function WriteStructWithIndent +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WriteStructure(FILE *fp, structdef_t *def, char *structure) +{ + return WriteStructWithIndent(fp, def, structure, 0); +} //end of the function WriteStructure + diff --git a/code/botlib/l_struct.h b/code/botlib/l_struct.h index 66ac035..b8ef719 100755 --- a/code/botlib/l_struct.h +++ b/code/botlib/l_struct.h @@ -1,75 +1,75 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: l_struct.h - * - * desc: structure reading/writing - * - * $Archive: /source/code/botlib/l_struct.h $ - * - *****************************************************************************/ - - -#define MAX_STRINGFIELD 80 -//field types -#define FT_CHAR 1 // char -#define FT_INT 2 // int -#define FT_FLOAT 3 // float -#define FT_STRING 4 // char [MAX_STRINGFIELD] -#define FT_STRUCT 6 // struct (sub structure) -//type only mask -#define FT_TYPE 0x00FF // only type, clear subtype -//sub types -#define FT_ARRAY 0x0100 // array of type -#define FT_BOUNDED 0x0200 // bounded value -#define FT_UNSIGNED 0x0400 - -//structure field definition -typedef struct fielddef_s -{ - char *name; //name of the field - int offset; //offset in the structure - int type; //type of the field - //type specific fields - int maxarray; //maximum array size - float floatmin, floatmax; //float min and max - struct structdef_s *substruct; //sub structure -} fielddef_t; - -//structure definition -typedef struct structdef_s -{ - int size; - fielddef_t *fields; -} structdef_t; - -//read a structure from a script -int ReadStructure(source_t *source, structdef_t *def, char *structure); -//write a structure to a file -int WriteStructure(FILE *fp, structdef_t *def, char *structure); -//writes indents -int WriteIndent(FILE *fp, int indent); -//writes a float without traling zeros -int WriteFloat(FILE *fp, float value); - - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: l_struct.h + * + * desc: structure reading/writing + * + * $Archive: /source/code/botlib/l_struct.h $ + * + *****************************************************************************/ + + +#define MAX_STRINGFIELD 80 +//field types +#define FT_CHAR 1 // char +#define FT_INT 2 // int +#define FT_FLOAT 3 // float +#define FT_STRING 4 // char [MAX_STRINGFIELD] +#define FT_STRUCT 6 // struct (sub structure) +//type only mask +#define FT_TYPE 0x00FF // only type, clear subtype +//sub types +#define FT_ARRAY 0x0100 // array of type +#define FT_BOUNDED 0x0200 // bounded value +#define FT_UNSIGNED 0x0400 + +//structure field definition +typedef struct fielddef_s +{ + char *name; //name of the field + int offset; //offset in the structure + int type; //type of the field + //type specific fields + int maxarray; //maximum array size + float floatmin, floatmax; //float min and max + struct structdef_s *substruct; //sub structure +} fielddef_t; + +//structure definition +typedef struct structdef_s +{ + int size; + fielddef_t *fields; +} structdef_t; + +//read a structure from a script +int ReadStructure(source_t *source, structdef_t *def, char *structure); +//write a structure to a file +int WriteStructure(FILE *fp, structdef_t *def, char *structure); +//writes indents +int WriteIndent(FILE *fp, int indent); +//writes a float without traling zeros +int WriteFloat(FILE *fp, float value); + + diff --git a/code/botlib/l_utils.h b/code/botlib/l_utils.h index 04bcc6a..a784d7a 100755 --- a/code/botlib/l_utils.h +++ b/code/botlib/l_utils.h @@ -1,35 +1,35 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: l_util.h - * - * desc: utils - * - * $Archive: /source/code/botlib/l_util.h $ - * - *****************************************************************************/ - -#define Vector2Angles(v,a) vectoangles(v,a) -#define MAX_PATH MAX_QPATH -#define Maximum(x,y) (x > y ? x : y) -#define Minimum(x,y) (x < y ? x : y) +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: l_util.h + * + * desc: utils + * + * $Archive: /source/code/botlib/l_util.h $ + * + *****************************************************************************/ + +#define Vector2Angles(v,a) vectoangles(v,a) +#define MAX_PATH MAX_QPATH +#define Maximum(x,y) (x > y ? x : y) +#define Minimum(x,y) (x < y ? x : y) diff --git a/code/botlib/lcc.mak b/code/botlib/lcc.mak index 6de559e..f5567c3 100755 --- a/code/botlib/lcc.mak +++ b/code/botlib/lcc.mak @@ -1,55 +1,55 @@ -# -# Makefile for Gladiator Bot library: gladiator.dll -# Intended for LCC-Win32 -# - -CC=lcc -CFLAGS=-DC_ONLY -o -OBJS= be_aas_bspq2.obj \ - be_aas_bsphl.obj \ - be_aas_cluster.obj \ - be_aas_debug.obj \ - be_aas_entity.obj \ - be_aas_file.obj \ - be_aas_light.obj \ - be_aas_main.obj \ - be_aas_move.obj \ - be_aas_optimize.obj \ - be_aas_reach.obj \ - be_aas_route.obj \ - be_aas_routealt.obj \ - be_aas_sample.obj \ - be_aas_sound.obj \ - be_ai2_dm.obj \ - be_ai2_dmnet.obj \ - be_ai2_main.obj \ - be_ai_char.obj \ - be_ai_chat.obj \ - be_ai_goal.obj \ - be_ai_load.obj \ - be_ai_move.obj \ - be_ai_weap.obj \ - be_ai_weight.obj \ - be_ea.obj \ - be_interface.obj \ - l_crc.obj \ - l_libvar.obj \ - l_log.obj \ - l_memory.obj \ - l_precomp.obj \ - l_script.obj \ - l_struct.obj \ - l_utils.obj \ - q_shared.obj - -all: gladiator.dll - -gladiator.dll: $(OBJS) - lcclnk -dll -entry GetBotAPI *.obj botlib.def -o gladiator.dll - -clean: - del *.obj gladiator.dll - -%.obj: %.c - $(CC) $(CFLAGS) $< - +# +# Makefile for Gladiator Bot library: gladiator.dll +# Intended for LCC-Win32 +# + +CC=lcc +CFLAGS=-DC_ONLY -o +OBJS= be_aas_bspq2.obj \ + be_aas_bsphl.obj \ + be_aas_cluster.obj \ + be_aas_debug.obj \ + be_aas_entity.obj \ + be_aas_file.obj \ + be_aas_light.obj \ + be_aas_main.obj \ + be_aas_move.obj \ + be_aas_optimize.obj \ + be_aas_reach.obj \ + be_aas_route.obj \ + be_aas_routealt.obj \ + be_aas_sample.obj \ + be_aas_sound.obj \ + be_ai2_dm.obj \ + be_ai2_dmnet.obj \ + be_ai2_main.obj \ + be_ai_char.obj \ + be_ai_chat.obj \ + be_ai_goal.obj \ + be_ai_load.obj \ + be_ai_move.obj \ + be_ai_weap.obj \ + be_ai_weight.obj \ + be_ea.obj \ + be_interface.obj \ + l_crc.obj \ + l_libvar.obj \ + l_log.obj \ + l_memory.obj \ + l_precomp.obj \ + l_script.obj \ + l_struct.obj \ + l_utils.obj \ + q_shared.obj + +all: gladiator.dll + +gladiator.dll: $(OBJS) + lcclnk -dll -entry GetBotAPI *.obj botlib.def -o gladiator.dll + +clean: + del *.obj gladiator.dll + +%.obj: %.c + $(CC) $(CFLAGS) $< + diff --git a/code/botlib/linux-i386.mak b/code/botlib/linux-i386.mak index db6ee20..c9607a7 100755 --- a/code/botlib/linux-i386.mak +++ b/code/botlib/linux-i386.mak @@ -1,92 +1,92 @@ -# -# Makefile for Gladiator Bot library: gladiator.so -# Intended for gcc/Linux -# - -ARCH=i386 -CC=gcc -BASE_CFLAGS=-Dstricmp=strcasecmp - -#use these cflags to optimize it -CFLAGS=$(BASE_CFLAGS) -m486 -O6 -ffast-math -funroll-loops \ - -fomit-frame-pointer -fexpensive-optimizations -malign-loops=2 \ - -malign-jumps=2 -malign-functions=2 -#use these when debugging -#CFLAGS=$(BASE_CFLAGS) -g - -LDFLAGS=-ldl -lm -SHLIBEXT=so -SHLIBCFLAGS=-fPIC -SHLIBLDFLAGS=-shared - -DO_CC=$(CC) $(CFLAGS) $(SHLIBCFLAGS) -o $@ -c $< - -############################################################################# -# SETUP AND BUILD -# GLADIATOR BOT -############################################################################# - -.c.o: - $(DO_CC) - -GAME_OBJS = \ - be_aas_bsphl.o\ - be_aas_bspq2.o\ - be_aas_cluster.o\ - be_aas_debug.o\ - be_aas_entity.o\ - be_aas_file.o\ - be_aas_light.o\ - be_aas_main.o\ - be_aas_move.o\ - be_aas_optimize.o\ - be_aas_reach.o\ - be_aas_route.o\ - be_aas_routealt.o\ - be_aas_sample.o\ - be_aas_sound.o\ - be_ai2_dmq2.o\ - be_ai2_dmhl.o\ - be_ai2_dmnet.o\ - be_ai2_main.o\ - be_ai_char.o\ - be_ai_chat.o\ - be_ai_goal.o\ - be_ai_load.o\ - be_ai_move.o\ - be_ai_weap.o\ - be_ai_weight.o\ - be_ea.o\ - be_interface.o\ - l_crc.o\ - l_libvar.o\ - l_log.o\ - l_memory.o\ - l_precomp.o\ - l_script.o\ - l_struct.o\ - l_utils.o\ - q_shared.o - -glad$(ARCH).$(SHLIBEXT) : $(GAME_OBJS) - $(CC) $(CFLAGS) $(SHLIBLDFLAGS) -o $@ $(GAME_OBJS) - - -############################################################################# -# MISC -############################################################################# - -clean: - -rm -f $(GAME_OBJS) - -depend: - gcc -MM $(GAME_OBJS:.o=.c) - - -install: - cp gladiator.so .. - -# -# From "make depend" -# - +# +# Makefile for Gladiator Bot library: gladiator.so +# Intended for gcc/Linux +# + +ARCH=i386 +CC=gcc +BASE_CFLAGS=-Dstricmp=strcasecmp + +#use these cflags to optimize it +CFLAGS=$(BASE_CFLAGS) -m486 -O6 -ffast-math -funroll-loops \ + -fomit-frame-pointer -fexpensive-optimizations -malign-loops=2 \ + -malign-jumps=2 -malign-functions=2 +#use these when debugging +#CFLAGS=$(BASE_CFLAGS) -g + +LDFLAGS=-ldl -lm +SHLIBEXT=so +SHLIBCFLAGS=-fPIC +SHLIBLDFLAGS=-shared + +DO_CC=$(CC) $(CFLAGS) $(SHLIBCFLAGS) -o $@ -c $< + +############################################################################# +# SETUP AND BUILD +# GLADIATOR BOT +############################################################################# + +.c.o: + $(DO_CC) + +GAME_OBJS = \ + be_aas_bsphl.o\ + be_aas_bspq2.o\ + be_aas_cluster.o\ + be_aas_debug.o\ + be_aas_entity.o\ + be_aas_file.o\ + be_aas_light.o\ + be_aas_main.o\ + be_aas_move.o\ + be_aas_optimize.o\ + be_aas_reach.o\ + be_aas_route.o\ + be_aas_routealt.o\ + be_aas_sample.o\ + be_aas_sound.o\ + be_ai2_dmq2.o\ + be_ai2_dmhl.o\ + be_ai2_dmnet.o\ + be_ai2_main.o\ + be_ai_char.o\ + be_ai_chat.o\ + be_ai_goal.o\ + be_ai_load.o\ + be_ai_move.o\ + be_ai_weap.o\ + be_ai_weight.o\ + be_ea.o\ + be_interface.o\ + l_crc.o\ + l_libvar.o\ + l_log.o\ + l_memory.o\ + l_precomp.o\ + l_script.o\ + l_struct.o\ + l_utils.o\ + q_shared.o + +glad$(ARCH).$(SHLIBEXT) : $(GAME_OBJS) + $(CC) $(CFLAGS) $(SHLIBLDFLAGS) -o $@ $(GAME_OBJS) + + +############################################################################# +# MISC +############################################################################# + +clean: + -rm -f $(GAME_OBJS) + +depend: + gcc -MM $(GAME_OBJS:.o=.c) + + +install: + cp gladiator.so .. + +# +# From "make depend" +# + diff --git a/code/bspc/Conscript b/code/bspc/Conscript index 08487ab..264e7c7 100755 --- a/code/bspc/Conscript +++ b/code/bspc/Conscript @@ -1,75 +1,75 @@ -# bspc compile - -Import qw( BSPC_BASE_CFLAGS BUILD_DIR INSTALL_DIR CC CXX LINK ); - -@BSPC_FILES = qw( - aas_areamerging.c - aas_cfg.c - aas_create.c - aas_edgemelting.c - aas_facemerging.c - aas_file.c - aas_gsubdiv.c - aas_map.c - aas_prunenodes.c - aas_store.c - be_aas_bspc.c - ../botlib/be_aas_bspq3.c - ../botlib/be_aas_cluster.c - ../botlib/be_aas_move.c - ../botlib/be_aas_optimize.c - ../botlib/be_aas_reach.c - ../botlib/be_aas_sample.c - brushbsp.c - bspc.c - ../qcommon/cm_load.c - ../qcommon/cm_patch.c - ../qcommon/cm_test.c - ../qcommon/cm_trace.c - csg.c - glfile.c - l_bsp_ent.c - l_bsp_hl.c - l_bsp_q1.c - l_bsp_q2.c - l_bsp_q3.c - l_bsp_sin.c - l_cmd.c - ../botlib/l_libvar.c - l_log.c - l_math.c - l_mem.c - l_poly.c - ../botlib/l_precomp.c - l_qfiles.c - ../botlib/l_script.c - ../botlib/l_struct.c - l_threads.c - l_utils.c - leakfile.c - map.c - map_hl.c - map_q1.c - map_q2.c - map_q3.c - map_sin.c - ../qcommon/md4.c - nodraw.c - portals.c - textures.c - tree.c - ../qcommon/unzip.c - ); -$BSPC_REF = \@BSPC_FILES; - -$env = new cons( - CC => $CC, - CXX => $CXX, - LINK => $LINK, - CFLAGS => $BSPC_BASE_CFLAGS, - LIBS => '-ldl -lm -lpthread' -); - -Program $env 'bspc', @$BSPC_REF; -# this should install to Q3 or something? -Install $env $INSTALL_DIR, 'bspc'; +# bspc compile + +Import qw( BSPC_BASE_CFLAGS BUILD_DIR INSTALL_DIR CC CXX LINK ); + +@BSPC_FILES = qw( + aas_areamerging.c + aas_cfg.c + aas_create.c + aas_edgemelting.c + aas_facemerging.c + aas_file.c + aas_gsubdiv.c + aas_map.c + aas_prunenodes.c + aas_store.c + be_aas_bspc.c + ../botlib/be_aas_bspq3.c + ../botlib/be_aas_cluster.c + ../botlib/be_aas_move.c + ../botlib/be_aas_optimize.c + ../botlib/be_aas_reach.c + ../botlib/be_aas_sample.c + brushbsp.c + bspc.c + ../qcommon/cm_load.c + ../qcommon/cm_patch.c + ../qcommon/cm_test.c + ../qcommon/cm_trace.c + csg.c + glfile.c + l_bsp_ent.c + l_bsp_hl.c + l_bsp_q1.c + l_bsp_q2.c + l_bsp_q3.c + l_bsp_sin.c + l_cmd.c + ../botlib/l_libvar.c + l_log.c + l_math.c + l_mem.c + l_poly.c + ../botlib/l_precomp.c + l_qfiles.c + ../botlib/l_script.c + ../botlib/l_struct.c + l_threads.c + l_utils.c + leakfile.c + map.c + map_hl.c + map_q1.c + map_q2.c + map_q3.c + map_sin.c + ../qcommon/md4.c + nodraw.c + portals.c + textures.c + tree.c + ../qcommon/unzip.c + ); +$BSPC_REF = \@BSPC_FILES; + +$env = new cons( + CC => $CC, + CXX => $CXX, + LINK => $LINK, + CFLAGS => $BSPC_BASE_CFLAGS, + LIBS => '-ldl -lm -lpthread' +); + +Program $env 'bspc', @$BSPC_REF; +# this should install to Q3 or something? +Install $env $INSTALL_DIR, 'bspc'; diff --git a/code/bspc/Makefile b/code/bspc/Makefile index 2f16169..b6d3b94 100755 --- a/code/bspc/Makefile +++ b/code/bspc/Makefile @@ -1,114 +1,114 @@ -# -# Makefile for the BSPC tool for the Gladiator Bot -# Intended for gcc/Linux -# -# TTimo 5/15/2001 -# some cleanup .. only used on i386 for GtkRadiant setups AFAIK .. removing the i386 tag -# TODO: the intermediate object files should go into their own directory -# specially for ../botlib and ../qcommon, the compilation flags on those might not be what you expect - -#ARCH=i386 -CC=gcc -BASE_CFLAGS=-Dstricmp=strcasecmp - -#use these cflags to optimize it -CFLAGS=$(BASE_CFLAGS) -m486 -O6 -ffast-math -funroll-loops \ - -fomit-frame-pointer -fexpensive-optimizations -malign-loops=2 \ - -malign-jumps=2 -malign-functions=2 -DLINUX -DBSPC -#use these when debugging -#CFLAGS=$(BASE_CFLAGS) -g - -LDFLAGS=-ldl -lm -lpthread - -DO_CC=$(CC) $(CFLAGS) -o $@ -c $< - -############################################################################# -# SETUP AND BUILD BSPC -############################################################################# - -.c.o: - $(DO_CC) - -GAME_OBJS = \ - _files.o\ - aas_areamerging.o\ - aas_cfg.o\ - aas_create.o\ - aas_edgemelting.o\ - aas_facemerging.o\ - aas_file.o\ - aas_gsubdiv.o\ - aas_map.o\ - aas_prunenodes.o\ - aas_store.o\ - be_aas_bspc.o\ - ../botlib/be_aas_bspq3.o\ - ../botlib/be_aas_cluster.o\ - ../botlib/be_aas_move.o\ - ../botlib/be_aas_optimize.o\ - ../botlib/be_aas_reach.o\ - ../botlib/be_aas_sample.o\ - brushbsp.o\ - bspc.o\ - ../qcommon/cm_load.o\ - ../qcommon/cm_patch.o\ - ../qcommon/cm_test.o\ - ../qcommon/cm_trace.o\ - csg.o\ - glfile.o\ - l_bsp_ent.o\ - l_bsp_hl.o\ - l_bsp_q1.o\ - l_bsp_q2.o\ - l_bsp_q3.o\ - l_bsp_sin.o\ - l_cmd.o\ - ../botlib/l_libvar.o\ - l_log.o\ - l_math.o\ - l_mem.o\ - l_poly.o\ - ../botlib/l_precomp.o\ - l_qfiles.o\ - ../botlib/l_script.o\ - ../botlib/l_struct.o\ - l_threads.o\ - l_utils.o\ - leakfile.o\ - map.o\ - map_hl.o\ - map_q1.o\ - map_q2.o\ - map_q3.o\ - map_sin.o\ - ../qcommon/md4.o\ - nodraw.o\ - portals.o\ - textures.o\ - tree.o\ - ../qcommon/unzip.o - - #tetrahedron.o - -bspc : $(GAME_OBJS) - $(CC) $(CFLAGS) -o $@ $(GAME_OBJS) $(LDFLAGS) - strip $@ - - -############################################################################# -# MISC -############################################################################# - -clean: - -rm -f $(GAME_OBJS) - -depend: - gcc -MM $(GAME_OBJS:.o=.c) - -#install: -# cp bspci386 .. - -# -# From "make depend" -# - +# +# Makefile for the BSPC tool for the Gladiator Bot +# Intended for gcc/Linux +# +# TTimo 5/15/2001 +# some cleanup .. only used on i386 for GtkRadiant setups AFAIK .. removing the i386 tag +# TODO: the intermediate object files should go into their own directory +# specially for ../botlib and ../qcommon, the compilation flags on those might not be what you expect + +#ARCH=i386 +CC=gcc +BASE_CFLAGS=-Dstricmp=strcasecmp + +#use these cflags to optimize it +CFLAGS=$(BASE_CFLAGS) -m486 -O6 -ffast-math -funroll-loops \ + -fomit-frame-pointer -fexpensive-optimizations -malign-loops=2 \ + -malign-jumps=2 -malign-functions=2 -DLINUX -DBSPC +#use these when debugging +#CFLAGS=$(BASE_CFLAGS) -g + +LDFLAGS=-ldl -lm -lpthread + +DO_CC=$(CC) $(CFLAGS) -o $@ -c $< + +############################################################################# +# SETUP AND BUILD BSPC +############################################################################# + +.c.o: + $(DO_CC) + +GAME_OBJS = \ + _files.o\ + aas_areamerging.o\ + aas_cfg.o\ + aas_create.o\ + aas_edgemelting.o\ + aas_facemerging.o\ + aas_file.o\ + aas_gsubdiv.o\ + aas_map.o\ + aas_prunenodes.o\ + aas_store.o\ + be_aas_bspc.o\ + ../botlib/be_aas_bspq3.o\ + ../botlib/be_aas_cluster.o\ + ../botlib/be_aas_move.o\ + ../botlib/be_aas_optimize.o\ + ../botlib/be_aas_reach.o\ + ../botlib/be_aas_sample.o\ + brushbsp.o\ + bspc.o\ + ../qcommon/cm_load.o\ + ../qcommon/cm_patch.o\ + ../qcommon/cm_test.o\ + ../qcommon/cm_trace.o\ + csg.o\ + glfile.o\ + l_bsp_ent.o\ + l_bsp_hl.o\ + l_bsp_q1.o\ + l_bsp_q2.o\ + l_bsp_q3.o\ + l_bsp_sin.o\ + l_cmd.o\ + ../botlib/l_libvar.o\ + l_log.o\ + l_math.o\ + l_mem.o\ + l_poly.o\ + ../botlib/l_precomp.o\ + l_qfiles.o\ + ../botlib/l_script.o\ + ../botlib/l_struct.o\ + l_threads.o\ + l_utils.o\ + leakfile.o\ + map.o\ + map_hl.o\ + map_q1.o\ + map_q2.o\ + map_q3.o\ + map_sin.o\ + ../qcommon/md4.o\ + nodraw.o\ + portals.o\ + textures.o\ + tree.o\ + ../qcommon/unzip.o + + #tetrahedron.o + +bspc : $(GAME_OBJS) + $(CC) $(CFLAGS) -o $@ $(GAME_OBJS) $(LDFLAGS) + strip $@ + + +############################################################################# +# MISC +############################################################################# + +clean: + -rm -f $(GAME_OBJS) + +depend: + gcc -MM $(GAME_OBJS:.o=.c) + +#install: +# cp bspci386 .. + +# +# From "make depend" +# + diff --git a/code/bspc/aas_areamerging.c b/code/bspc/aas_areamerging.c index c5f82d2..f9de65f 100755 --- a/code/bspc/aas_areamerging.c +++ b/code/bspc/aas_areamerging.c @@ -1,390 +1,390 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "qbsp.h" -#include "../botlib/aasfile.h" -#include "aas_create.h" -#include "aas_store.h" - -#define CONVEX_EPSILON 0.3 - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -tmp_node_t *AAS_RefreshMergedTree_r(tmp_node_t *tmpnode) -{ - tmp_area_t *tmparea; - - //if this is a solid leaf - if (!tmpnode) return NULL; - //if this is an area leaf - if (tmpnode->tmparea) - { - tmparea = tmpnode->tmparea; - while(tmparea->mergedarea) tmparea = tmparea->mergedarea; - tmpnode->tmparea = tmparea; - return tmpnode; - } //end if - //do the children recursively - tmpnode->children[0] = AAS_RefreshMergedTree_r(tmpnode->children[0]); - tmpnode->children[1] = AAS_RefreshMergedTree_r(tmpnode->children[1]); - return tmpnode; -} //end of the function AAS_RefreshMergedTree_r -//=========================================================================== -// returns true if the two given faces would create a non-convex area at -// the given sides, otherwise false is returned -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int NonConvex(tmp_face_t *face1, tmp_face_t *face2, int side1, int side2) -{ - int i; - winding_t *w1, *w2; - plane_t *plane1, *plane2; - - w1 = face1->winding; - w2 = face2->winding; - - plane1 = &mapplanes[face1->planenum ^ side1]; - plane2 = &mapplanes[face2->planenum ^ side2]; - - //check if one of the points of face1 is at the back of the plane of face2 - for (i = 0; i < w1->numpoints; i++) - { - if (DotProduct(plane2->normal, w1->p[i]) - plane2->dist < -CONVEX_EPSILON) return true; - } //end for - //check if one of the points of face2 is at the back of the plane of face1 - for (i = 0; i < w2->numpoints; i++) - { - if (DotProduct(plane1->normal, w2->p[i]) - plane1->dist < -CONVEX_EPSILON) return true; - } //end for - - return false; -} //end of the function NonConvex -//=========================================================================== -// try to merge the areas at both sides of the given face -// -// Parameter: seperatingface : face that seperates two areas -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_TryMergeFaceAreas(tmp_face_t *seperatingface) -{ - int side1, side2, area1faceflags, area2faceflags; - tmp_area_t *tmparea1, *tmparea2, *newarea; - tmp_face_t *face1, *face2, *nextface1, *nextface2; - - tmparea1 = seperatingface->frontarea; - tmparea2 = seperatingface->backarea; - - //areas must have the same presence type - if (tmparea1->presencetype != tmparea2->presencetype) return false; - //areas must have the same area contents - if (tmparea1->contents != tmparea2->contents) return false; - //areas must have the same bsp model inside (or both none) - if (tmparea1->modelnum != tmparea2->modelnum) return false; - - area1faceflags = 0; - area2faceflags = 0; - for (face1 = tmparea1->tmpfaces; face1; face1 = face1->next[side1]) - { - side1 = (face1->frontarea != tmparea1); - //debug: check if the area belongs to the area - if (face1->frontarea != tmparea1 && - face1->backarea != tmparea1) Error("face does not belong to area1"); - //just continue if the face is seperating the two areas - //NOTE: a result of this is that ground and gap areas can - // be merged if the seperating face is the gap - if ((face1->frontarea == tmparea1 && - face1->backarea == tmparea2) || - (face1->frontarea == tmparea2 && - face1->backarea == tmparea1)) continue; - //get area1 face flags - area1faceflags |= face1->faceflags; - if (AAS_GapFace(face1, side1)) area1faceflags |= FACE_GAP; - // - for (face2 = tmparea2->tmpfaces; face2; face2 = face2->next[side2]) - { - side2 = (face2->frontarea != tmparea2); - //debug: check if the area belongs to the area - if (face2->frontarea != tmparea2 && - face2->backarea != tmparea2) Error("face does not belong to area2"); - //just continue if the face is seperating the two areas - //NOTE: a result of this is that ground and gap areas can - // be merged if the seperating face is the gap - if ((face2->frontarea == tmparea1 && - face2->backarea == tmparea2) || - (face2->frontarea == tmparea2 && - face2->backarea == tmparea1)) continue; - //get area2 face flags - area2faceflags |= face2->faceflags; - if (AAS_GapFace(face2, side2)) area2faceflags |= FACE_GAP; - //if the two faces would create a non-convex area - if (NonConvex(face1, face2, side1, side2)) return false; - } //end for - } //end for - //if one area has gap faces (that aren't seperating the two areas) - //and the other has ground faces (that aren't seperating the two areas), - //the areas can't be merged - if (((area1faceflags & FACE_GROUND) && (area2faceflags & FACE_GAP)) || - ((area2faceflags & FACE_GROUND) && (area1faceflags & FACE_GAP))) - { -// Log_Print(" can't merge: ground/gap\n"); - return false; - } //end if - -// Log_Print("merged area %d & %d to %d with %d faces\n", tmparea1->areanum, tmparea2->areanum, newarea->areanum, numfaces); -// return false; - // - //AAS_CheckArea(tmparea1); - //AAS_CheckArea(tmparea2); - //create the new area - newarea = AAS_AllocTmpArea(); - newarea->presencetype = tmparea1->presencetype; - newarea->contents = tmparea1->contents; - newarea->modelnum = tmparea1->modelnum; - newarea->tmpfaces = NULL; - - //add all the faces (except the seperating ones) from the first area - //to the new area - for (face1 = tmparea1->tmpfaces; face1; face1 = nextface1) - { - side1 = (face1->frontarea != tmparea1); - nextface1 = face1->next[side1]; - //don't add seperating faces - if ((face1->frontarea == tmparea1 && - face1->backarea == tmparea2) || - (face1->frontarea == tmparea2 && - face1->backarea == tmparea1)) - { - continue; - } //end if - // - AAS_RemoveFaceFromArea(face1, tmparea1); - AAS_AddFaceSideToArea(face1, side1, newarea); - } //end for - //add all the faces (except the seperating ones) from the second area - //to the new area - for (face2 = tmparea2->tmpfaces; face2; face2 = nextface2) - { - side2 = (face2->frontarea != tmparea2); - nextface2 = face2->next[side2]; - //don't add seperating faces - if ((face2->frontarea == tmparea1 && - face2->backarea == tmparea2) || - (face2->frontarea == tmparea2 && - face2->backarea == tmparea1)) - { - continue; - } //end if - // - AAS_RemoveFaceFromArea(face2, tmparea2); - AAS_AddFaceSideToArea(face2, side2, newarea); - } //end for - //free all shared faces - for (face1 = tmparea1->tmpfaces; face1; face1 = nextface1) - { - side1 = (face1->frontarea != tmparea1); - nextface1 = face1->next[side1]; - // - AAS_RemoveFaceFromArea(face1, face1->frontarea); - AAS_RemoveFaceFromArea(face1, face1->backarea); - AAS_FreeTmpFace(face1); - } //end for - // - tmparea1->mergedarea = newarea; - tmparea1->invalid = true; - tmparea2->mergedarea = newarea; - tmparea2->invalid = true; - // - AAS_CheckArea(newarea); - AAS_FlipAreaFaces(newarea); -// Log_Print("merged area %d & %d to %d with %d faces\n", tmparea1->areanum, tmparea2->areanum, newarea->areanum); - return true; -} //end of the function AAS_TryMergeFaceAreas -//=========================================================================== -// try to merge areas -// merged areas are added to the end of the convex area list so merging -// will be tried for those areas as well -// -// Parameter: - -// Returns: - -// Changes Globals: tmpaasworld -//=========================================================================== -/* -void AAS_MergeAreas(void) -{ - int side, nummerges; - tmp_area_t *tmparea, *othertmparea; - tmp_face_t *face; - - nummerges = 0; - Log_Write("AAS_MergeAreas\r\n"); - qprintf("%6d areas merged", 1); - //first merge grounded areas only - //NOTE: this is useless because the area settings aren't available yet - for (tmparea = tmpaasworld.areas; tmparea; tmparea = tmparea->l_next) - { -// Log_Print("checking area %d\n", i); - //if the area is invalid - if (tmparea->invalid) - { -// Log_Print(" area invalid\n"); - continue; - } //end if - // -// if (!(tmparea->settings->areaflags & AREA_GROUNDED)) continue; - // - for (face = tmparea->tmpfaces; face; face = face->next[side]) - { - side = (face->frontarea != tmparea); - //if the face has both a front and back area - if (face->frontarea && face->backarea) - { - // - if (face->frontarea == tmparea) othertmparea = face->backarea; - else othertmparea = face->frontarea; -// if (!(othertmparea->settings->areaflags & AREA_GROUNDED)) continue; -// Log_Print(" checking area %d with %d\n", face->frontarea, face->backarea); - if (AAS_TryMergeFaceAreas(face)) - { - qprintf("\r%6d", ++nummerges); - break; - } //end if - } //end if - } //end for - } //end for - //merge all areas - for (tmparea = tmpaasworld.areas; tmparea; tmparea = tmparea->l_next) - { -// Log_Print("checking area %d\n", i); - //if the area is invalid - if (tmparea->invalid) - { -// Log_Print(" area invalid\n"); - continue; - } //end if - // - for (face = tmparea->tmpfaces; face; face = face->next[side]) - { - side = (face->frontarea != tmparea); - //if the face has both a front and back area - if (face->frontarea && face->backarea) - { -// Log_Print(" checking area %d with %d\n", face->frontarea, face->backarea); - if (AAS_TryMergeFaceAreas(face)) - { - qprintf("\r%6d", ++nummerges); - break; - } //end if - } //end if - } //end for - } //end for - Log_Print("\r%6d areas merged\n", nummerges); - //refresh the merged tree - AAS_RefreshMergedTree_r(tmpaasworld.nodes); -} //end of the function AAS_MergeAreas*/ - -int AAS_GroundArea(tmp_area_t *tmparea) -{ - tmp_face_t *face; - int side; - - for (face = tmparea->tmpfaces; face; face = face->next[side]) - { - side = (face->frontarea != tmparea); - if (face->faceflags & FACE_GROUND) return true; - } //end for - return false; -} //end of the function AAS_GroundArea - -void AAS_MergeAreas(void) -{ - int side, nummerges, merges, groundfirst; - tmp_area_t *tmparea, *othertmparea; - tmp_face_t *face; - - nummerges = 0; - Log_Write("AAS_MergeAreas\r\n"); - qprintf("%6d areas merged", 1); - // - groundfirst = true; - //for (i = 0; i < 4 || merges; i++) - while(1) - { - //if (i < 2) groundfirst = true; - //else groundfirst = false; - // - merges = 0; - //first merge grounded areas only - for (tmparea = tmpaasworld.areas; tmparea; tmparea = tmparea->l_next) - { - //if the area is invalid - if (tmparea->invalid) - { - continue; - } //end if - // - if (groundfirst) - { - if (!AAS_GroundArea(tmparea)) continue; - } //end if - // - for (face = tmparea->tmpfaces; face; face = face->next[side]) - { - side = (face->frontarea != tmparea); - //if the face has both a front and back area - if (face->frontarea && face->backarea) - { - // - if (face->frontarea == tmparea) othertmparea = face->backarea; - else othertmparea = face->frontarea; - // - if (groundfirst) - { - if (!AAS_GroundArea(othertmparea)) continue; - } //end if - if (AAS_TryMergeFaceAreas(face)) - { - qprintf("\r%6d", ++nummerges); - merges++; - break; - } //end if - } //end if - } //end for - } //end for - if (!merges) - { - if (groundfirst) groundfirst = false; - else break; - } //end if - } //end for - qprintf("\n"); - Log_Write("%6d areas merged\r\n", nummerges); - //refresh the merged tree - AAS_RefreshMergedTree_r(tmpaasworld.nodes); -} //end of the function AAS_MergeAreas +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "qbsp.h" +#include "../botlib/aasfile.h" +#include "aas_create.h" +#include "aas_store.h" + +#define CONVEX_EPSILON 0.3 + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +tmp_node_t *AAS_RefreshMergedTree_r(tmp_node_t *tmpnode) +{ + tmp_area_t *tmparea; + + //if this is a solid leaf + if (!tmpnode) return NULL; + //if this is an area leaf + if (tmpnode->tmparea) + { + tmparea = tmpnode->tmparea; + while(tmparea->mergedarea) tmparea = tmparea->mergedarea; + tmpnode->tmparea = tmparea; + return tmpnode; + } //end if + //do the children recursively + tmpnode->children[0] = AAS_RefreshMergedTree_r(tmpnode->children[0]); + tmpnode->children[1] = AAS_RefreshMergedTree_r(tmpnode->children[1]); + return tmpnode; +} //end of the function AAS_RefreshMergedTree_r +//=========================================================================== +// returns true if the two given faces would create a non-convex area at +// the given sides, otherwise false is returned +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int NonConvex(tmp_face_t *face1, tmp_face_t *face2, int side1, int side2) +{ + int i; + winding_t *w1, *w2; + plane_t *plane1, *plane2; + + w1 = face1->winding; + w2 = face2->winding; + + plane1 = &mapplanes[face1->planenum ^ side1]; + plane2 = &mapplanes[face2->planenum ^ side2]; + + //check if one of the points of face1 is at the back of the plane of face2 + for (i = 0; i < w1->numpoints; i++) + { + if (DotProduct(plane2->normal, w1->p[i]) - plane2->dist < -CONVEX_EPSILON) return true; + } //end for + //check if one of the points of face2 is at the back of the plane of face1 + for (i = 0; i < w2->numpoints; i++) + { + if (DotProduct(plane1->normal, w2->p[i]) - plane1->dist < -CONVEX_EPSILON) return true; + } //end for + + return false; +} //end of the function NonConvex +//=========================================================================== +// try to merge the areas at both sides of the given face +// +// Parameter: seperatingface : face that seperates two areas +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_TryMergeFaceAreas(tmp_face_t *seperatingface) +{ + int side1, side2, area1faceflags, area2faceflags; + tmp_area_t *tmparea1, *tmparea2, *newarea; + tmp_face_t *face1, *face2, *nextface1, *nextface2; + + tmparea1 = seperatingface->frontarea; + tmparea2 = seperatingface->backarea; + + //areas must have the same presence type + if (tmparea1->presencetype != tmparea2->presencetype) return false; + //areas must have the same area contents + if (tmparea1->contents != tmparea2->contents) return false; + //areas must have the same bsp model inside (or both none) + if (tmparea1->modelnum != tmparea2->modelnum) return false; + + area1faceflags = 0; + area2faceflags = 0; + for (face1 = tmparea1->tmpfaces; face1; face1 = face1->next[side1]) + { + side1 = (face1->frontarea != tmparea1); + //debug: check if the area belongs to the area + if (face1->frontarea != tmparea1 && + face1->backarea != tmparea1) Error("face does not belong to area1"); + //just continue if the face is seperating the two areas + //NOTE: a result of this is that ground and gap areas can + // be merged if the seperating face is the gap + if ((face1->frontarea == tmparea1 && + face1->backarea == tmparea2) || + (face1->frontarea == tmparea2 && + face1->backarea == tmparea1)) continue; + //get area1 face flags + area1faceflags |= face1->faceflags; + if (AAS_GapFace(face1, side1)) area1faceflags |= FACE_GAP; + // + for (face2 = tmparea2->tmpfaces; face2; face2 = face2->next[side2]) + { + side2 = (face2->frontarea != tmparea2); + //debug: check if the area belongs to the area + if (face2->frontarea != tmparea2 && + face2->backarea != tmparea2) Error("face does not belong to area2"); + //just continue if the face is seperating the two areas + //NOTE: a result of this is that ground and gap areas can + // be merged if the seperating face is the gap + if ((face2->frontarea == tmparea1 && + face2->backarea == tmparea2) || + (face2->frontarea == tmparea2 && + face2->backarea == tmparea1)) continue; + //get area2 face flags + area2faceflags |= face2->faceflags; + if (AAS_GapFace(face2, side2)) area2faceflags |= FACE_GAP; + //if the two faces would create a non-convex area + if (NonConvex(face1, face2, side1, side2)) return false; + } //end for + } //end for + //if one area has gap faces (that aren't seperating the two areas) + //and the other has ground faces (that aren't seperating the two areas), + //the areas can't be merged + if (((area1faceflags & FACE_GROUND) && (area2faceflags & FACE_GAP)) || + ((area2faceflags & FACE_GROUND) && (area1faceflags & FACE_GAP))) + { +// Log_Print(" can't merge: ground/gap\n"); + return false; + } //end if + +// Log_Print("merged area %d & %d to %d with %d faces\n", tmparea1->areanum, tmparea2->areanum, newarea->areanum, numfaces); +// return false; + // + //AAS_CheckArea(tmparea1); + //AAS_CheckArea(tmparea2); + //create the new area + newarea = AAS_AllocTmpArea(); + newarea->presencetype = tmparea1->presencetype; + newarea->contents = tmparea1->contents; + newarea->modelnum = tmparea1->modelnum; + newarea->tmpfaces = NULL; + + //add all the faces (except the seperating ones) from the first area + //to the new area + for (face1 = tmparea1->tmpfaces; face1; face1 = nextface1) + { + side1 = (face1->frontarea != tmparea1); + nextface1 = face1->next[side1]; + //don't add seperating faces + if ((face1->frontarea == tmparea1 && + face1->backarea == tmparea2) || + (face1->frontarea == tmparea2 && + face1->backarea == tmparea1)) + { + continue; + } //end if + // + AAS_RemoveFaceFromArea(face1, tmparea1); + AAS_AddFaceSideToArea(face1, side1, newarea); + } //end for + //add all the faces (except the seperating ones) from the second area + //to the new area + for (face2 = tmparea2->tmpfaces; face2; face2 = nextface2) + { + side2 = (face2->frontarea != tmparea2); + nextface2 = face2->next[side2]; + //don't add seperating faces + if ((face2->frontarea == tmparea1 && + face2->backarea == tmparea2) || + (face2->frontarea == tmparea2 && + face2->backarea == tmparea1)) + { + continue; + } //end if + // + AAS_RemoveFaceFromArea(face2, tmparea2); + AAS_AddFaceSideToArea(face2, side2, newarea); + } //end for + //free all shared faces + for (face1 = tmparea1->tmpfaces; face1; face1 = nextface1) + { + side1 = (face1->frontarea != tmparea1); + nextface1 = face1->next[side1]; + // + AAS_RemoveFaceFromArea(face1, face1->frontarea); + AAS_RemoveFaceFromArea(face1, face1->backarea); + AAS_FreeTmpFace(face1); + } //end for + // + tmparea1->mergedarea = newarea; + tmparea1->invalid = true; + tmparea2->mergedarea = newarea; + tmparea2->invalid = true; + // + AAS_CheckArea(newarea); + AAS_FlipAreaFaces(newarea); +// Log_Print("merged area %d & %d to %d with %d faces\n", tmparea1->areanum, tmparea2->areanum, newarea->areanum); + return true; +} //end of the function AAS_TryMergeFaceAreas +//=========================================================================== +// try to merge areas +// merged areas are added to the end of the convex area list so merging +// will be tried for those areas as well +// +// Parameter: - +// Returns: - +// Changes Globals: tmpaasworld +//=========================================================================== +/* +void AAS_MergeAreas(void) +{ + int side, nummerges; + tmp_area_t *tmparea, *othertmparea; + tmp_face_t *face; + + nummerges = 0; + Log_Write("AAS_MergeAreas\r\n"); + qprintf("%6d areas merged", 1); + //first merge grounded areas only + //NOTE: this is useless because the area settings aren't available yet + for (tmparea = tmpaasworld.areas; tmparea; tmparea = tmparea->l_next) + { +// Log_Print("checking area %d\n", i); + //if the area is invalid + if (tmparea->invalid) + { +// Log_Print(" area invalid\n"); + continue; + } //end if + // +// if (!(tmparea->settings->areaflags & AREA_GROUNDED)) continue; + // + for (face = tmparea->tmpfaces; face; face = face->next[side]) + { + side = (face->frontarea != tmparea); + //if the face has both a front and back area + if (face->frontarea && face->backarea) + { + // + if (face->frontarea == tmparea) othertmparea = face->backarea; + else othertmparea = face->frontarea; +// if (!(othertmparea->settings->areaflags & AREA_GROUNDED)) continue; +// Log_Print(" checking area %d with %d\n", face->frontarea, face->backarea); + if (AAS_TryMergeFaceAreas(face)) + { + qprintf("\r%6d", ++nummerges); + break; + } //end if + } //end if + } //end for + } //end for + //merge all areas + for (tmparea = tmpaasworld.areas; tmparea; tmparea = tmparea->l_next) + { +// Log_Print("checking area %d\n", i); + //if the area is invalid + if (tmparea->invalid) + { +// Log_Print(" area invalid\n"); + continue; + } //end if + // + for (face = tmparea->tmpfaces; face; face = face->next[side]) + { + side = (face->frontarea != tmparea); + //if the face has both a front and back area + if (face->frontarea && face->backarea) + { +// Log_Print(" checking area %d with %d\n", face->frontarea, face->backarea); + if (AAS_TryMergeFaceAreas(face)) + { + qprintf("\r%6d", ++nummerges); + break; + } //end if + } //end if + } //end for + } //end for + Log_Print("\r%6d areas merged\n", nummerges); + //refresh the merged tree + AAS_RefreshMergedTree_r(tmpaasworld.nodes); +} //end of the function AAS_MergeAreas*/ + +int AAS_GroundArea(tmp_area_t *tmparea) +{ + tmp_face_t *face; + int side; + + for (face = tmparea->tmpfaces; face; face = face->next[side]) + { + side = (face->frontarea != tmparea); + if (face->faceflags & FACE_GROUND) return true; + } //end for + return false; +} //end of the function AAS_GroundArea + +void AAS_MergeAreas(void) +{ + int side, nummerges, merges, groundfirst; + tmp_area_t *tmparea, *othertmparea; + tmp_face_t *face; + + nummerges = 0; + Log_Write("AAS_MergeAreas\r\n"); + qprintf("%6d areas merged", 1); + // + groundfirst = true; + //for (i = 0; i < 4 || merges; i++) + while(1) + { + //if (i < 2) groundfirst = true; + //else groundfirst = false; + // + merges = 0; + //first merge grounded areas only + for (tmparea = tmpaasworld.areas; tmparea; tmparea = tmparea->l_next) + { + //if the area is invalid + if (tmparea->invalid) + { + continue; + } //end if + // + if (groundfirst) + { + if (!AAS_GroundArea(tmparea)) continue; + } //end if + // + for (face = tmparea->tmpfaces; face; face = face->next[side]) + { + side = (face->frontarea != tmparea); + //if the face has both a front and back area + if (face->frontarea && face->backarea) + { + // + if (face->frontarea == tmparea) othertmparea = face->backarea; + else othertmparea = face->frontarea; + // + if (groundfirst) + { + if (!AAS_GroundArea(othertmparea)) continue; + } //end if + if (AAS_TryMergeFaceAreas(face)) + { + qprintf("\r%6d", ++nummerges); + merges++; + break; + } //end if + } //end if + } //end for + } //end for + if (!merges) + { + if (groundfirst) groundfirst = false; + else break; + } //end if + } //end for + qprintf("\n"); + Log_Write("%6d areas merged\r\n", nummerges); + //refresh the merged tree + AAS_RefreshMergedTree_r(tmpaasworld.nodes); +} //end of the function AAS_MergeAreas diff --git a/code/bspc/aas_areamerging.h b/code/bspc/aas_areamerging.h index c0b39a4..14cb8ed 100755 --- a/code/bspc/aas_areamerging.h +++ b/code/bspc/aas_areamerging.h @@ -1,24 +1,24 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -void AAS_MergeAreas(void); - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +void AAS_MergeAreas(void); + diff --git a/code/bspc/aas_cfg.c b/code/bspc/aas_cfg.c index 3aeb569..b805176 100755 --- a/code/bspc/aas_cfg.c +++ b/code/bspc/aas_cfg.c @@ -1,252 +1,252 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "qbsp.h" -#include "float.h" -#include "../botlib/aasfile.h" -#include "aas_store.h" -#include "aas_cfg.h" -#include "../botlib/l_precomp.h" -#include "../botlib/l_struct.h" -#include "../botlib/l_libvar.h" - -//structure field offsets -#define BBOX_OFS(x) (int)&(((aas_bbox_t *)0)->x) -#define CFG_OFS(x) (int)&(((cfg_t *)0)->x) - -//bounding box definition -fielddef_t bbox_fields[] = -{ - {"presencetype", BBOX_OFS(presencetype), FT_INT}, - {"flags", BBOX_OFS(flags), FT_INT}, - {"mins", BBOX_OFS(mins), FT_FLOAT|FT_ARRAY, 3}, - {"maxs", BBOX_OFS(maxs), FT_FLOAT|FT_ARRAY, 3}, - {NULL, 0, 0, 0} -}; - -fielddef_t cfg_fields[] = -{ - {"phys_gravitydirection", CFG_OFS(phys_gravitydirection), FT_FLOAT|FT_ARRAY, 3}, - {"phys_friction", CFG_OFS(phys_friction), FT_FLOAT}, - {"phys_stopspeed", CFG_OFS(phys_stopspeed), FT_FLOAT}, - {"phys_gravity", CFG_OFS(phys_gravity), FT_FLOAT}, - {"phys_waterfriction", CFG_OFS(phys_waterfriction), FT_FLOAT}, - {"phys_watergravity", CFG_OFS(phys_watergravity), FT_FLOAT}, - {"phys_maxvelocity", CFG_OFS(phys_maxvelocity), FT_FLOAT}, - {"phys_maxwalkvelocity", CFG_OFS(phys_maxwalkvelocity), FT_FLOAT}, - {"phys_maxcrouchvelocity", CFG_OFS(phys_maxcrouchvelocity), FT_FLOAT}, - {"phys_maxswimvelocity", CFG_OFS(phys_maxswimvelocity), FT_FLOAT}, - {"phys_walkaccelerate", CFG_OFS(phys_walkaccelerate), FT_FLOAT}, - {"phys_airaccelerate", CFG_OFS(phys_airaccelerate), FT_FLOAT}, - {"phys_swimaccelerate", CFG_OFS(phys_swimaccelerate), FT_FLOAT}, - {"phys_maxstep", CFG_OFS(phys_maxstep), FT_FLOAT}, - {"phys_maxsteepness", CFG_OFS(phys_maxsteepness), FT_FLOAT}, - {"phys_maxwaterjump", CFG_OFS(phys_maxwaterjump), FT_FLOAT}, - {"phys_maxbarrier", CFG_OFS(phys_maxbarrier), FT_FLOAT}, - {"phys_jumpvel", CFG_OFS(phys_jumpvel), FT_FLOAT}, - {"phys_falldelta5", CFG_OFS(phys_falldelta5), FT_FLOAT}, - {"phys_falldelta10", CFG_OFS(phys_falldelta10), FT_FLOAT}, - {"rs_waterjump", CFG_OFS(rs_waterjump), FT_FLOAT}, - {"rs_teleport", CFG_OFS(rs_teleport), FT_FLOAT}, - {"rs_barrierjump", CFG_OFS(rs_barrierjump), FT_FLOAT}, - {"rs_startcrouch", CFG_OFS(rs_startcrouch), FT_FLOAT}, - {"rs_startgrapple", CFG_OFS(rs_startgrapple), FT_FLOAT}, - {"rs_startwalkoffledge", CFG_OFS(rs_startwalkoffledge), FT_FLOAT}, - {"rs_startjump", CFG_OFS(rs_startjump), FT_FLOAT}, - {"rs_rocketjump", CFG_OFS(rs_rocketjump), FT_FLOAT}, - {"rs_bfgjump", CFG_OFS(rs_bfgjump), FT_FLOAT}, - {"rs_jumppad", CFG_OFS(rs_jumppad), FT_FLOAT}, - {"rs_aircontrolledjumppad", CFG_OFS(rs_aircontrolledjumppad), FT_FLOAT}, - {"rs_funcbob", CFG_OFS(rs_funcbob), FT_FLOAT}, - {"rs_startelevator", CFG_OFS(rs_startelevator), FT_FLOAT}, - {"rs_falldamage5", CFG_OFS(rs_falldamage5), FT_FLOAT}, - {"rs_falldamage10", CFG_OFS(rs_falldamage10), FT_FLOAT}, - {"rs_maxjumpfallheight", CFG_OFS(rs_maxjumpfallheight), FT_FLOAT}, - {NULL, 0, 0, 0} -}; - -structdef_t bbox_struct = -{ - sizeof(aas_bbox_t), bbox_fields -}; -structdef_t cfg_struct = -{ - sizeof(cfg_t), cfg_fields -}; - -//global cfg -cfg_t cfg; - -//=========================================================================== -// the default Q3A configuration -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void DefaultCfg(void) -{ - int i; - - // default all float values to infinite - for (i = 0; cfg_fields[i].name; i++) - { - if ((cfg_fields[i].type & FT_TYPE) == FT_FLOAT) - *(float *)( ((char*)&cfg) + cfg_fields[i].offset ) = FLT_MAX; - } //end for - // - cfg.numbboxes = 2; - //bbox 0 - cfg.bboxes[0].presencetype = PRESENCE_NORMAL; - cfg.bboxes[0].flags = 0; - cfg.bboxes[0].mins[0] = -15; - cfg.bboxes[0].mins[1] = -15; - cfg.bboxes[0].mins[2] = -24; - cfg.bboxes[0].maxs[0] = 15; - cfg.bboxes[0].maxs[1] = 15; - cfg.bboxes[0].maxs[2] = 32; - //bbox 1 - cfg.bboxes[1].presencetype = PRESENCE_CROUCH; - cfg.bboxes[1].flags = 1; - cfg.bboxes[1].mins[0] = -15; - cfg.bboxes[1].mins[1] = -15; - cfg.bboxes[1].mins[2] = -24; - cfg.bboxes[1].maxs[0] = 15; - cfg.bboxes[1].maxs[1] = 15; - cfg.bboxes[1].maxs[2] = 16; - // - cfg.allpresencetypes = PRESENCE_NORMAL|PRESENCE_CROUCH; - cfg.phys_gravitydirection[0] = 0; - cfg.phys_gravitydirection[1] = 0; - cfg.phys_gravitydirection[2] = -1; - cfg.phys_maxsteepness = 0.7; -} //end of the function DefaultCfg -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -char * QDECL va( char *format, ... ) -{ - va_list argptr; - static char string[2][32000]; // in case va is called by nested functions - static int index = 0; - char *buf; - - buf = string[index & 1]; - index++; - - va_start (argptr, format); - vsprintf (buf, format,argptr); - va_end (argptr); - - return buf; -} //end of the function va -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void SetCfgLibVars(void) -{ - int i; - float value; - - for (i = 0; cfg_fields[i].name; i++) - { - if ((cfg_fields[i].type & FT_TYPE) == FT_FLOAT) - { - value = *(float *)(((char*)&cfg) + cfg_fields[i].offset); - if (value != FLT_MAX) - { - LibVarSet(cfg_fields[i].name, va("%f", value)); - } //end if - } //end if - } //end for -} //end of the function SetCfgLibVars -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int LoadCfgFile(char *filename) -{ - source_t *source; - token_t token; - int settingsdefined; - - source = LoadSourceFile(filename); - if (!source) - { - Log_Print("couldn't open cfg file %s\n", filename); - return false; - } //end if - - settingsdefined = false; - memset(&cfg, 0, sizeof(cfg_t)); - - while(PC_ReadToken(source, &token)) - { - if (!stricmp(token.string, "bbox")) - { - if (cfg.numbboxes >= AAS_MAX_BBOXES) - { - SourceError(source, "too many bounding box volumes defined"); - } //end if - if (!ReadStructure(source, &bbox_struct, (char *) &cfg.bboxes[cfg.numbboxes])) - { - FreeSource(source); - return false; - } //end if - cfg.allpresencetypes |= cfg.bboxes[cfg.numbboxes].presencetype; - cfg.numbboxes++; - } //end if - else if (!stricmp(token.string, "settings")) - { - if (settingsdefined) - { - SourceWarning(source, "settings already defined\n"); - } //end if - settingsdefined = true; - if (!ReadStructure(source, &cfg_struct, (char *) &cfg)) - { - FreeSource(source); - return false; - } //end if - } //end else if - } //end while - if (VectorLength(cfg.phys_gravitydirection) < 0.9 || VectorLength(cfg.phys_gravitydirection) > 1.1) - { - SourceError(source, "invalid gravity direction specified"); - } //end if - if (cfg.numbboxes <= 0) - { - SourceError(source, "no bounding volumes specified"); - } //end if - FreeSource(source); - SetCfgLibVars(); - Log_Print("using cfg file %s\n", filename); - return true; -} //end of the function LoadCfgFile +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "qbsp.h" +#include "float.h" +#include "../botlib/aasfile.h" +#include "aas_store.h" +#include "aas_cfg.h" +#include "../botlib/l_precomp.h" +#include "../botlib/l_struct.h" +#include "../botlib/l_libvar.h" + +//structure field offsets +#define BBOX_OFS(x) (int)&(((aas_bbox_t *)0)->x) +#define CFG_OFS(x) (int)&(((cfg_t *)0)->x) + +//bounding box definition +fielddef_t bbox_fields[] = +{ + {"presencetype", BBOX_OFS(presencetype), FT_INT}, + {"flags", BBOX_OFS(flags), FT_INT}, + {"mins", BBOX_OFS(mins), FT_FLOAT|FT_ARRAY, 3}, + {"maxs", BBOX_OFS(maxs), FT_FLOAT|FT_ARRAY, 3}, + {NULL, 0, 0, 0} +}; + +fielddef_t cfg_fields[] = +{ + {"phys_gravitydirection", CFG_OFS(phys_gravitydirection), FT_FLOAT|FT_ARRAY, 3}, + {"phys_friction", CFG_OFS(phys_friction), FT_FLOAT}, + {"phys_stopspeed", CFG_OFS(phys_stopspeed), FT_FLOAT}, + {"phys_gravity", CFG_OFS(phys_gravity), FT_FLOAT}, + {"phys_waterfriction", CFG_OFS(phys_waterfriction), FT_FLOAT}, + {"phys_watergravity", CFG_OFS(phys_watergravity), FT_FLOAT}, + {"phys_maxvelocity", CFG_OFS(phys_maxvelocity), FT_FLOAT}, + {"phys_maxwalkvelocity", CFG_OFS(phys_maxwalkvelocity), FT_FLOAT}, + {"phys_maxcrouchvelocity", CFG_OFS(phys_maxcrouchvelocity), FT_FLOAT}, + {"phys_maxswimvelocity", CFG_OFS(phys_maxswimvelocity), FT_FLOAT}, + {"phys_walkaccelerate", CFG_OFS(phys_walkaccelerate), FT_FLOAT}, + {"phys_airaccelerate", CFG_OFS(phys_airaccelerate), FT_FLOAT}, + {"phys_swimaccelerate", CFG_OFS(phys_swimaccelerate), FT_FLOAT}, + {"phys_maxstep", CFG_OFS(phys_maxstep), FT_FLOAT}, + {"phys_maxsteepness", CFG_OFS(phys_maxsteepness), FT_FLOAT}, + {"phys_maxwaterjump", CFG_OFS(phys_maxwaterjump), FT_FLOAT}, + {"phys_maxbarrier", CFG_OFS(phys_maxbarrier), FT_FLOAT}, + {"phys_jumpvel", CFG_OFS(phys_jumpvel), FT_FLOAT}, + {"phys_falldelta5", CFG_OFS(phys_falldelta5), FT_FLOAT}, + {"phys_falldelta10", CFG_OFS(phys_falldelta10), FT_FLOAT}, + {"rs_waterjump", CFG_OFS(rs_waterjump), FT_FLOAT}, + {"rs_teleport", CFG_OFS(rs_teleport), FT_FLOAT}, + {"rs_barrierjump", CFG_OFS(rs_barrierjump), FT_FLOAT}, + {"rs_startcrouch", CFG_OFS(rs_startcrouch), FT_FLOAT}, + {"rs_startgrapple", CFG_OFS(rs_startgrapple), FT_FLOAT}, + {"rs_startwalkoffledge", CFG_OFS(rs_startwalkoffledge), FT_FLOAT}, + {"rs_startjump", CFG_OFS(rs_startjump), FT_FLOAT}, + {"rs_rocketjump", CFG_OFS(rs_rocketjump), FT_FLOAT}, + {"rs_bfgjump", CFG_OFS(rs_bfgjump), FT_FLOAT}, + {"rs_jumppad", CFG_OFS(rs_jumppad), FT_FLOAT}, + {"rs_aircontrolledjumppad", CFG_OFS(rs_aircontrolledjumppad), FT_FLOAT}, + {"rs_funcbob", CFG_OFS(rs_funcbob), FT_FLOAT}, + {"rs_startelevator", CFG_OFS(rs_startelevator), FT_FLOAT}, + {"rs_falldamage5", CFG_OFS(rs_falldamage5), FT_FLOAT}, + {"rs_falldamage10", CFG_OFS(rs_falldamage10), FT_FLOAT}, + {"rs_maxjumpfallheight", CFG_OFS(rs_maxjumpfallheight), FT_FLOAT}, + {NULL, 0, 0, 0} +}; + +structdef_t bbox_struct = +{ + sizeof(aas_bbox_t), bbox_fields +}; +structdef_t cfg_struct = +{ + sizeof(cfg_t), cfg_fields +}; + +//global cfg +cfg_t cfg; + +//=========================================================================== +// the default Q3A configuration +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void DefaultCfg(void) +{ + int i; + + // default all float values to infinite + for (i = 0; cfg_fields[i].name; i++) + { + if ((cfg_fields[i].type & FT_TYPE) == FT_FLOAT) + *(float *)( ((char*)&cfg) + cfg_fields[i].offset ) = FLT_MAX; + } //end for + // + cfg.numbboxes = 2; + //bbox 0 + cfg.bboxes[0].presencetype = PRESENCE_NORMAL; + cfg.bboxes[0].flags = 0; + cfg.bboxes[0].mins[0] = -15; + cfg.bboxes[0].mins[1] = -15; + cfg.bboxes[0].mins[2] = -24; + cfg.bboxes[0].maxs[0] = 15; + cfg.bboxes[0].maxs[1] = 15; + cfg.bboxes[0].maxs[2] = 32; + //bbox 1 + cfg.bboxes[1].presencetype = PRESENCE_CROUCH; + cfg.bboxes[1].flags = 1; + cfg.bboxes[1].mins[0] = -15; + cfg.bboxes[1].mins[1] = -15; + cfg.bboxes[1].mins[2] = -24; + cfg.bboxes[1].maxs[0] = 15; + cfg.bboxes[1].maxs[1] = 15; + cfg.bboxes[1].maxs[2] = 16; + // + cfg.allpresencetypes = PRESENCE_NORMAL|PRESENCE_CROUCH; + cfg.phys_gravitydirection[0] = 0; + cfg.phys_gravitydirection[1] = 0; + cfg.phys_gravitydirection[2] = -1; + cfg.phys_maxsteepness = 0.7; +} //end of the function DefaultCfg +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char * QDECL va( char *format, ... ) +{ + va_list argptr; + static char string[2][32000]; // in case va is called by nested functions + static int index = 0; + char *buf; + + buf = string[index & 1]; + index++; + + va_start (argptr, format); + vsprintf (buf, format,argptr); + va_end (argptr); + + return buf; +} //end of the function va +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void SetCfgLibVars(void) +{ + int i; + float value; + + for (i = 0; cfg_fields[i].name; i++) + { + if ((cfg_fields[i].type & FT_TYPE) == FT_FLOAT) + { + value = *(float *)(((char*)&cfg) + cfg_fields[i].offset); + if (value != FLT_MAX) + { + LibVarSet(cfg_fields[i].name, va("%f", value)); + } //end if + } //end if + } //end for +} //end of the function SetCfgLibVars +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int LoadCfgFile(char *filename) +{ + source_t *source; + token_t token; + int settingsdefined; + + source = LoadSourceFile(filename); + if (!source) + { + Log_Print("couldn't open cfg file %s\n", filename); + return false; + } //end if + + settingsdefined = false; + memset(&cfg, 0, sizeof(cfg_t)); + + while(PC_ReadToken(source, &token)) + { + if (!stricmp(token.string, "bbox")) + { + if (cfg.numbboxes >= AAS_MAX_BBOXES) + { + SourceError(source, "too many bounding box volumes defined"); + } //end if + if (!ReadStructure(source, &bbox_struct, (char *) &cfg.bboxes[cfg.numbboxes])) + { + FreeSource(source); + return false; + } //end if + cfg.allpresencetypes |= cfg.bboxes[cfg.numbboxes].presencetype; + cfg.numbboxes++; + } //end if + else if (!stricmp(token.string, "settings")) + { + if (settingsdefined) + { + SourceWarning(source, "settings already defined\n"); + } //end if + settingsdefined = true; + if (!ReadStructure(source, &cfg_struct, (char *) &cfg)) + { + FreeSource(source); + return false; + } //end if + } //end else if + } //end while + if (VectorLength(cfg.phys_gravitydirection) < 0.9 || VectorLength(cfg.phys_gravitydirection) > 1.1) + { + SourceError(source, "invalid gravity direction specified"); + } //end if + if (cfg.numbboxes <= 0) + { + SourceError(source, "no bounding volumes specified"); + } //end if + FreeSource(source); + SetCfgLibVars(); + Log_Print("using cfg file %s\n", filename); + return true; +} //end of the function LoadCfgFile diff --git a/code/bspc/aas_cfg.h b/code/bspc/aas_cfg.h index 25537c6..a5db5a8 100755 --- a/code/bspc/aas_cfg.h +++ b/code/bspc/aas_cfg.h @@ -1,73 +1,73 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#define BBOXFL_GROUNDED 1 //bounding box only valid when on ground -#define BBOXFL_NOTGROUNDED 2 //bounding box only valid when NOT on ground - -typedef struct cfg_s -{ - int numbboxes; //number of bounding boxes - aas_bbox_t bboxes[AAS_MAX_BBOXES]; //all the bounding boxes - int allpresencetypes; //or of all presence types - // aas settings - vec3_t phys_gravitydirection; - float phys_friction; - float phys_stopspeed; - float phys_gravity; - float phys_waterfriction; - float phys_watergravity; - float phys_maxvelocity; - float phys_maxwalkvelocity; - float phys_maxcrouchvelocity; - float phys_maxswimvelocity; - float phys_walkaccelerate; - float phys_airaccelerate; - float phys_swimaccelerate; - float phys_maxstep; - float phys_maxsteepness; - float phys_maxwaterjump; - float phys_maxbarrier; - float phys_jumpvel; - float phys_falldelta5; - float phys_falldelta10; - float rs_waterjump; - float rs_teleport; - float rs_barrierjump; - float rs_startcrouch; - float rs_startgrapple; - float rs_startwalkoffledge; - float rs_startjump; - float rs_rocketjump; - float rs_bfgjump; - float rs_jumppad; - float rs_aircontrolledjumppad; - float rs_funcbob; - float rs_startelevator; - float rs_falldamage5; - float rs_falldamage10; - float rs_maxjumpfallheight; -} cfg_t; - -extern cfg_t cfg; - -void DefaultCfg(void); -int LoadCfgFile(char *filename); +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#define BBOXFL_GROUNDED 1 //bounding box only valid when on ground +#define BBOXFL_NOTGROUNDED 2 //bounding box only valid when NOT on ground + +typedef struct cfg_s +{ + int numbboxes; //number of bounding boxes + aas_bbox_t bboxes[AAS_MAX_BBOXES]; //all the bounding boxes + int allpresencetypes; //or of all presence types + // aas settings + vec3_t phys_gravitydirection; + float phys_friction; + float phys_stopspeed; + float phys_gravity; + float phys_waterfriction; + float phys_watergravity; + float phys_maxvelocity; + float phys_maxwalkvelocity; + float phys_maxcrouchvelocity; + float phys_maxswimvelocity; + float phys_walkaccelerate; + float phys_airaccelerate; + float phys_swimaccelerate; + float phys_maxstep; + float phys_maxsteepness; + float phys_maxwaterjump; + float phys_maxbarrier; + float phys_jumpvel; + float phys_falldelta5; + float phys_falldelta10; + float rs_waterjump; + float rs_teleport; + float rs_barrierjump; + float rs_startcrouch; + float rs_startgrapple; + float rs_startwalkoffledge; + float rs_startjump; + float rs_rocketjump; + float rs_bfgjump; + float rs_jumppad; + float rs_aircontrolledjumppad; + float rs_funcbob; + float rs_startelevator; + float rs_falldamage5; + float rs_falldamage10; + float rs_maxjumpfallheight; +} cfg_t; + +extern cfg_t cfg; + +void DefaultCfg(void); +int LoadCfgFile(char *filename); diff --git a/code/bspc/aas_create.c b/code/bspc/aas_create.c index 914b25b..1705362 100755 --- a/code/bspc/aas_create.c +++ b/code/bspc/aas_create.c @@ -1,1142 +1,1142 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "qbsp.h" -#include "../botlib/aasfile.h" -#include "aas_create.h" -#include "aas_store.h" -#include "aas_gsubdiv.h" -#include "aas_facemerging.h" -#include "aas_areamerging.h" -#include "aas_edgemelting.h" -#include "aas_prunenodes.h" -#include "aas_cfg.h" -#include "../game/surfaceflags.h" - -//#define AW_DEBUG -//#define L_DEBUG - -#define AREAONFACESIDE(face, area) (face->frontarea != area) - -tmp_aas_t tmpaasworld; - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_InitTmpAAS(void) -{ - //tmp faces - tmpaasworld.numfaces = 0; - tmpaasworld.facenum = 0; - tmpaasworld.faces = NULL; - //tmp convex areas - tmpaasworld.numareas = 0; - tmpaasworld.areanum = 0; - tmpaasworld.areas = NULL; - //tmp nodes - tmpaasworld.numnodes = 0; - tmpaasworld.nodes = NULL; - // - tmpaasworld.nodebuffer = NULL; -} //end of the function AAS_InitTmpAAS -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_FreeTmpAAS(void) -{ - tmp_face_t *f, *nextf; - tmp_area_t *a, *nexta; - tmp_nodebuf_t *nb, *nextnb; - - //free all the faces - for (f = tmpaasworld.faces; f; f = nextf) - { - nextf = f->l_next; - if (f->winding) FreeWinding(f->winding); - FreeMemory(f); - } //end if - //free all tmp areas - for (a = tmpaasworld.areas; a; a = nexta) - { - nexta = a->l_next; - if (a->settings) FreeMemory(a->settings); - FreeMemory(a); - } //end for - //free all the tmp nodes - for (nb = tmpaasworld.nodebuffer; nb; nb = nextnb) - { - nextnb = nb->next; - FreeMemory(nb); - } //end for -} //end of the function AAS_FreeTmpAAS -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -tmp_face_t *AAS_AllocTmpFace(void) -{ - tmp_face_t *tmpface; - - tmpface = (tmp_face_t *) GetClearedMemory(sizeof(tmp_face_t)); - tmpface->num = tmpaasworld.facenum++; - tmpface->l_prev = NULL; - tmpface->l_next = tmpaasworld.faces; - if (tmpaasworld.faces) tmpaasworld.faces->l_prev = tmpface; - tmpaasworld.faces = tmpface; - tmpaasworld.numfaces++; - return tmpface; -} //end of the function AAS_AllocTmpFace -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_FreeTmpFace(tmp_face_t *tmpface) -{ - if (tmpface->l_next) tmpface->l_next->l_prev = tmpface->l_prev; - if (tmpface->l_prev) tmpface->l_prev->l_next = tmpface->l_next; - else tmpaasworld.faces = tmpface->l_next; - //free the winding - if (tmpface->winding) FreeWinding(tmpface->winding); - //free the face - FreeMemory(tmpface); - tmpaasworld.numfaces--; -} //end of the function AAS_FreeTmpFace -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -tmp_area_t *AAS_AllocTmpArea(void) -{ - tmp_area_t *tmparea; - - tmparea = (tmp_area_t *) GetClearedMemory(sizeof(tmp_area_t)); - tmparea->areanum = tmpaasworld.areanum++; - tmparea->l_prev = NULL; - tmparea->l_next = tmpaasworld.areas; - if (tmpaasworld.areas) tmpaasworld.areas->l_prev = tmparea; - tmpaasworld.areas = tmparea; - tmpaasworld.numareas++; - return tmparea; -} //end of the function AAS_AllocTmpArea -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_FreeTmpArea(tmp_area_t *tmparea) -{ - if (tmparea->l_next) tmparea->l_next->l_prev = tmparea->l_prev; - if (tmparea->l_prev) tmparea->l_prev->l_next = tmparea->l_next; - else tmpaasworld.areas = tmparea->l_next; - if (tmparea->settings) FreeMemory(tmparea->settings); - FreeMemory(tmparea); - tmpaasworld.numareas--; -} //end of the function AAS_FreeTmpArea -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -tmp_node_t *AAS_AllocTmpNode(void) -{ - tmp_nodebuf_t *nodebuf; - - if (!tmpaasworld.nodebuffer || - tmpaasworld.nodebuffer->numnodes >= NODEBUF_SIZE) - { - nodebuf = (tmp_nodebuf_t *) GetClearedMemory(sizeof(tmp_nodebuf_t)); - nodebuf->next = tmpaasworld.nodebuffer; - nodebuf->numnodes = 0; - tmpaasworld.nodebuffer = nodebuf; - } //end if - tmpaasworld.numnodes++; - return &tmpaasworld.nodebuffer->nodes[tmpaasworld.nodebuffer->numnodes++]; -} //end of the function AAS_AllocTmpNode -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_FreeTmpNode(tmp_node_t *tmpnode) -{ - tmpaasworld.numnodes--; -} //end of the function AAS_FreeTmpNode -//=========================================================================== -// returns true if the face is a gap from the given side -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_GapFace(tmp_face_t *tmpface, int side) -{ - vec3_t invgravity; - - //if the face is a solid or ground face it can't be a gap - if (tmpface->faceflags & (FACE_GROUND | FACE_SOLID)) return 0; - - VectorCopy(cfg.phys_gravitydirection, invgravity); - VectorInverse(invgravity); - - return (DotProduct(invgravity, mapplanes[tmpface->planenum ^ side].normal) > cfg.phys_maxsteepness); -} //end of the function AAS_GapFace -//=========================================================================== -// returns true if the face is a ground face -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_GroundFace(tmp_face_t *tmpface) -{ - vec3_t invgravity; - - //must be a solid face - if (!(tmpface->faceflags & FACE_SOLID)) return 0; - - VectorCopy(cfg.phys_gravitydirection, invgravity); - VectorInverse(invgravity); - - return (DotProduct(invgravity, mapplanes[tmpface->planenum].normal) > cfg.phys_maxsteepness); -} //end of the function AAS_GroundFace -//=========================================================================== -// adds the side of a face to an area -// -// side : 0 = front side -// 1 = back side -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_AddFaceSideToArea(tmp_face_t *tmpface, int side, tmp_area_t *tmparea) -{ - int tmpfaceside; - - if (side) - { - if (tmpface->backarea) Error("AAS_AddFaceSideToArea: already a back area\n"); - } //end if - else - { - if (tmpface->frontarea) Error("AAS_AddFaceSideToArea: already a front area\n"); - } //end else - - if (side) tmpface->backarea = tmparea; - else tmpface->frontarea = tmparea; - - if (tmparea->tmpfaces) - { - tmpfaceside = tmparea->tmpfaces->frontarea != tmparea; - tmparea->tmpfaces->prev[tmpfaceside] = tmpface; - } //end if - tmpface->next[side] = tmparea->tmpfaces; - tmpface->prev[side] = NULL; - tmparea->tmpfaces = tmpface; -} //end of the function AAS_AddFaceSideToArea -//=========================================================================== -// remove (a side of) a face from an area -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_RemoveFaceFromArea(tmp_face_t *tmpface, tmp_area_t *tmparea) -{ - int side, prevside, nextside; - - if (tmpface->frontarea != tmparea && - tmpface->backarea != tmparea) - { - Error("AAS_RemoveFaceFromArea: face not part of the area"); - } //end if - side = tmpface->frontarea != tmparea; - if (tmpface->prev[side]) - { - prevside = tmpface->prev[side]->frontarea != tmparea; - tmpface->prev[side]->next[prevside] = tmpface->next[side]; - } //end if - else - { - tmparea->tmpfaces = tmpface->next[side]; - } //end else - if (tmpface->next[side]) - { - nextside = tmpface->next[side]->frontarea != tmparea; - tmpface->next[side]->prev[nextside] = tmpface->prev[side]; - } //end if - //remove the area number from the face depending on the side - if (side) tmpface->backarea = NULL; - else tmpface->frontarea = NULL; - tmpface->prev[side] = NULL; - tmpface->next[side] = NULL; -} //end of the function AAS_RemoveFaceFromArea -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_CheckArea(tmp_area_t *tmparea) -{ - int side; - tmp_face_t *face; - plane_t *plane; - vec3_t wcenter, acenter = {0, 0, 0}; - vec3_t normal; - float n, dist; - - if (tmparea->invalid) Log_Print("AAS_CheckArea: invalid area\n"); - for (n = 0, face = tmparea->tmpfaces; face; face = face->next[side]) - { - //side of the face the area is on - side = face->frontarea != tmparea; - WindingCenter(face->winding, wcenter); - VectorAdd(acenter, wcenter, acenter); - n++; - } //end for - n = 1 / n; - VectorScale(acenter, n, acenter); - for (face = tmparea->tmpfaces; face; face = face->next[side]) - { - //side of the face the area is on - side = face->frontarea != tmparea; - -#ifdef L_DEBUG - if (WindingError(face->winding)) - { - Log_Write("AAS_CheckArea: area %d face %d: %s\r\n", tmparea->areanum, - face->num, WindingErrorString()); - } //end if -#endif L_DEBUG - - plane = &mapplanes[face->planenum ^ side]; - - if (DotProduct(plane->normal, acenter) - plane->dist < 0) - { - Log_Print("AAS_CheckArea: area %d face %d is flipped\n", tmparea->areanum, face->num); - Log_Print("AAS_CheckArea: area %d center is %f %f %f\n", tmparea->areanum, acenter[0], acenter[1], acenter[2]); - } //end if - //check if the winding plane is the same as the face plane - WindingPlane(face->winding, normal, &dist); - plane = &mapplanes[face->planenum]; -#ifdef L_DEBUG - if (fabs(dist - plane->dist) > 0.4 || - fabs(normal[0] - plane->normal[0]) > 0.0001 || - fabs(normal[1] - plane->normal[1]) > 0.0001 || - fabs(normal[2] - plane->normal[2]) > 0.0001) - { - Log_Write("AAS_CheckArea: area %d face %d winding plane unequal to face plane\r\n", - tmparea->areanum, face->num); - } //end if -#endif L_DEBUG - } //end for -} //end of the function AAS_CheckArea -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_CheckFaceWindingPlane(tmp_face_t *face) -{ - float dist, sign1, sign2; - vec3_t normal; - plane_t *plane; - winding_t *w; - - //check if the winding plane is the same as the face plane - WindingPlane(face->winding, normal, &dist); - plane = &mapplanes[face->planenum]; - // - sign1 = DotProduct(plane->normal, normal); - // - if (fabs(dist - plane->dist) > 0.4 || - fabs(normal[0] - plane->normal[0]) > 0.0001 || - fabs(normal[1] - plane->normal[1]) > 0.0001 || - fabs(normal[2] - plane->normal[2]) > 0.0001) - { - VectorInverse(normal); - dist = -dist; - if (fabs(dist - plane->dist) > 0.4 || - fabs(normal[0] - plane->normal[0]) > 0.0001 || - fabs(normal[1] - plane->normal[1]) > 0.0001 || - fabs(normal[2] - plane->normal[2]) > 0.0001) - { - Log_Write("AAS_CheckFaceWindingPlane: face %d winding plane unequal to face plane\r\n", - face->num); - // - sign2 = DotProduct(plane->normal, normal); - if ((sign1 < 0 && sign2 > 0) || - (sign1 > 0 && sign2 < 0)) - { - Log_Write("AAS_CheckFaceWindingPlane: face %d winding reversed\r\n", - face->num); - w = face->winding; - face->winding = ReverseWinding(w); - FreeWinding(w); - } //end if - } //end if - else - { - Log_Write("AAS_CheckFaceWindingPlane: face %d winding reversed\r\n", - face->num); - w = face->winding; - face->winding = ReverseWinding(w); - FreeWinding(w); - } //end else - } //end if -} //end of the function AAS_CheckFaceWindingPlane -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_CheckAreaWindingPlanes(void) -{ - int side; - tmp_area_t *tmparea; - tmp_face_t *face; - - Log_Write("AAS_CheckAreaWindingPlanes:\r\n"); - for (tmparea = tmpaasworld.areas; tmparea; tmparea = tmparea->l_next) - { - if (tmparea->invalid) continue; - for (face = tmparea->tmpfaces; face; face = face->next[side]) - { - side = face->frontarea != tmparea; - AAS_CheckFaceWindingPlane(face); - } //end for - } //end for -} //end of the function AAS_CheckAreaWindingPlanes -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_FlipAreaFaces(tmp_area_t *tmparea) -{ - int side; - tmp_face_t *face; - plane_t *plane; - vec3_t wcenter, acenter = {0, 0, 0}; - //winding_t *w; - float n; - - for (n = 0, face = tmparea->tmpfaces; face; face = face->next[side]) - { - if (!face->frontarea) Error("face %d has no front area\n", face->num); - //side of the face the area is on - side = face->frontarea != tmparea; - WindingCenter(face->winding, wcenter); - VectorAdd(acenter, wcenter, acenter); - n++; - } //end for - n = 1 / n; - VectorScale(acenter, n, acenter); - for (face = tmparea->tmpfaces; face; face = face->next[side]) - { - //side of the face the area is on - side = face->frontarea != tmparea; - - plane = &mapplanes[face->planenum ^ side]; - - if (DotProduct(plane->normal, acenter) - plane->dist < 0) - { - Log_Print("area %d face %d flipped: front area %d, back area %d\n", tmparea->areanum, face->num, - face->frontarea ? face->frontarea->areanum : 0, - face->backarea ? face->backarea->areanum : 0); - /* - face->planenum = face->planenum ^ 1; - w = face->winding; - face->winding = ReverseWinding(w); - FreeWinding(w); - */ - } //end if -#ifdef L_DEBUG - { - float dist; - vec3_t normal; - - //check if the winding plane is the same as the face plane - WindingPlane(face->winding, normal, &dist); - plane = &mapplanes[face->planenum]; - if (fabs(dist - plane->dist) > 0.4 || - fabs(normal[0] - plane->normal[0]) > 0.0001 || - fabs(normal[1] - plane->normal[1]) > 0.0001 || - fabs(normal[2] - plane->normal[2]) > 0.0001) - { - Log_Write("area %d face %d winding plane unequal to face plane\r\n", - tmparea->areanum, face->num); - } //end if - } -#endif - } //end for -} //end of the function AAS_FlipAreaFaces -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_RemoveAreaFaceColinearPoints(void) -{ - int side; - tmp_face_t *face; - tmp_area_t *tmparea; - - //FIXME: loop over the faces instead of area->faces - for (tmparea = tmpaasworld.areas; tmparea; tmparea = tmparea->l_next) - { - for (face = tmparea->tmpfaces; face; face = face->next[side]) - { - side = face->frontarea != tmparea; - RemoveColinearPoints(face->winding); -// RemoveEqualPoints(face->winding, 0.1); - } //end for - } //end for -} //end of the function AAS_RemoveAreaFaceColinearPoints -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_RemoveTinyFaces(void) -{ - int side, num; - tmp_face_t *face, *nextface; - tmp_area_t *tmparea; - - //FIXME: loop over the faces instead of area->faces - Log_Write("AAS_RemoveTinyFaces\r\n"); - num = 0; - for (tmparea = tmpaasworld.areas; tmparea; tmparea = tmparea->l_next) - { - for (face = tmparea->tmpfaces; face; face = nextface) - { - side = face->frontarea != tmparea; - nextface = face->next[side]; - // - if (WindingArea(face->winding) < 1) - { - if (face->frontarea) AAS_RemoveFaceFromArea(face, face->frontarea); - if (face->backarea) AAS_RemoveFaceFromArea(face, face->backarea); - AAS_FreeTmpFace(face); - //Log_Write("area %d face %d is tiny\r\n", tmparea->areanum, face->num); - num++; - } //end if - } //end for - } //end for - Log_Write("%d tiny faces removed\r\n", num); -} //end of the function AAS_RemoveTinyFaces -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_CreateAreaSettings(void) -{ - int i, flags, side, numgrounded, numladderareas, numliquidareas; - tmp_face_t *face; - tmp_area_t *tmparea; - - numgrounded = 0; - numladderareas = 0; - numliquidareas = 0; - Log_Write("AAS_CreateAreaSettings\r\n"); - i = 0; - qprintf("%6d areas provided with settings", i); - for (tmparea = tmpaasworld.areas; tmparea; tmparea = tmparea->l_next) - { - //if the area is invalid there no need to create settings for it - if (tmparea->invalid) continue; - - tmparea->settings = (tmp_areasettings_t *) GetClearedMemory(sizeof(tmp_areasettings_t)); - tmparea->settings->contents = tmparea->contents; - tmparea->settings->modelnum = tmparea->modelnum; - flags = 0; - for (face = tmparea->tmpfaces; face; face = face->next[side]) - { - side = face->frontarea != tmparea; - flags |= face->faceflags; - } //end for - tmparea->settings->areaflags = 0; - if (flags & FACE_GROUND) - { - tmparea->settings->areaflags |= AREA_GROUNDED; - numgrounded++; - } //end if - if (flags & FACE_LADDER) - { - tmparea->settings->areaflags |= AREA_LADDER; - numladderareas++; - } //end if - if (tmparea->contents & (AREACONTENTS_WATER | - AREACONTENTS_SLIME | - AREACONTENTS_LAVA)) - { - tmparea->settings->areaflags |= AREA_LIQUID; - numliquidareas++; - } //end if - //presence type of the area - tmparea->settings->presencetype = tmparea->presencetype; - // - qprintf("\r%6d", ++i); - } //end for - qprintf("\n"); -#ifdef AASINFO - Log_Print("%6d grounded areas\n", numgrounded); - Log_Print("%6d ladder areas\n", numladderareas); - Log_Print("%6d liquid areas\n", numliquidareas); -#endif //AASINFO -} //end of the function AAS_CreateAreaSettings -//=========================================================================== -// create a tmp AAS area from a leaf node -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -tmp_node_t *AAS_CreateArea(node_t *node) -{ - int pside; - int areafaceflags; - portal_t *p; - tmp_face_t *tmpface; - tmp_area_t *tmparea; - tmp_node_t *tmpnode; - vec3_t up = {0, 0, 1}; - - //create an area from this leaf - tmparea = AAS_AllocTmpArea(); - tmparea->tmpfaces = NULL; - //clear the area face flags - areafaceflags = 0; - //make aas faces from the portals - for (p = node->portals; p; p = p->next[pside]) - { - pside = (p->nodes[1] == node); - //don't create faces from very small portals -// if (WindingArea(p->winding) < 1) continue; - //if there's already a face created for this portal - if (p->tmpface) - { - //add the back side of the face to the area - AAS_AddFaceSideToArea(p->tmpface, 1, tmparea); - } //end if - else - { - tmpface = AAS_AllocTmpFace(); - //set the face pointer at the portal so we can see from - //the portal there's a face created for it - p->tmpface = tmpface; - //FIXME: test this change - //tmpface->planenum = (p->planenum & ~1) | pside; - tmpface->planenum = p->planenum ^ pside; - if (pside) tmpface->winding = ReverseWinding(p->winding); - else tmpface->winding = CopyWinding(p->winding); -#ifdef L_DEBUG - // - AAS_CheckFaceWindingPlane(tmpface); -#endif //L_DEBUG - //if there's solid at the other side of the portal - if (p->nodes[!pside]->contents & (CONTENTS_SOLID | CONTENTS_PLAYERCLIP)) - { - tmpface->faceflags |= FACE_SOLID; - } //end if - //else there is no solid at the other side and if there - //is a liquid at this side - else if (node->contents & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA)) - { - tmpface->faceflags |= FACE_LIQUID; - //if there's no liquid at the other side - if (!(p->nodes[!pside]->contents & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA))) - { - tmpface->faceflags |= FACE_LIQUIDSURFACE; - } //end if - } //end else - //if there's ladder contents at other side of the portal - if ((p->nodes[pside]->contents & CONTENTS_LADDER) || - (p->nodes[!pside]->contents & CONTENTS_LADDER)) - { - - //NOTE: doesn't have to be solid at the other side because - // when standing one can use a crouch area (which is not solid) - // as a ladder - // imagine a ladder one can walk underthrough, - // under the ladder against the ladder is a crouch area - // the (vertical) sides of this crouch area area also used as - // ladder sides when standing (not crouched) - tmpface->faceflags |= FACE_LADDER; - } //end if - //if it is possible to stand on the face - if (AAS_GroundFace(tmpface)) - { - tmpface->faceflags |= FACE_GROUND; - } //end if - // - areafaceflags |= tmpface->faceflags; - //no aas face number yet (zero is a dummy in the aasworld faces) - tmpface->aasfacenum = 0; - //add the front side of the face to the area - AAS_AddFaceSideToArea(tmpface, 0, tmparea); - } //end else - } //end for - qprintf("\r%6d", tmparea->areanum); - //presence type in the area - tmparea->presencetype = ~node->expansionbboxes & cfg.allpresencetypes; - // - tmparea->contents = 0; - if (node->contents & CONTENTS_CLUSTERPORTAL) tmparea->contents |= AREACONTENTS_CLUSTERPORTAL; - if (node->contents & CONTENTS_MOVER) tmparea->contents |= AREACONTENTS_MOVER; - if (node->contents & CONTENTS_TELEPORTER) tmparea->contents |= AREACONTENTS_TELEPORTER; - if (node->contents & CONTENTS_JUMPPAD) tmparea->contents |= AREACONTENTS_JUMPPAD; - if (node->contents & CONTENTS_DONOTENTER) tmparea->contents |= AREACONTENTS_DONOTENTER; - if (node->contents & CONTENTS_WATER) tmparea->contents |= AREACONTENTS_WATER; - if (node->contents & CONTENTS_LAVA) tmparea->contents |= AREACONTENTS_LAVA; - if (node->contents & CONTENTS_SLIME) tmparea->contents |= AREACONTENTS_SLIME; - if (node->contents & CONTENTS_NOTTEAM1) tmparea->contents |= AREACONTENTS_NOTTEAM1; - if (node->contents & CONTENTS_NOTTEAM2) tmparea->contents |= AREACONTENTS_NOTTEAM2; - - //store the bsp model that's inside this node - tmparea->modelnum = node->modelnum; - //sorta check for flipped area faces (remove??) - AAS_FlipAreaFaces(tmparea); - //check if the area is ok (remove??) - AAS_CheckArea(tmparea); - // - tmpnode = AAS_AllocTmpNode(); - tmpnode->planenum = 0; - tmpnode->children[0] = 0; - tmpnode->children[1] = 0; - tmpnode->tmparea = tmparea; - // - return tmpnode; -} //end of the function AAS_CreateArea -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -tmp_node_t *AAS_CreateAreas_r(node_t *node) -{ - tmp_node_t *tmpnode; - - //recurse down to leafs - if (node->planenum != PLANENUM_LEAF) - { - //the first tmp node is a dummy - tmpnode = AAS_AllocTmpNode(); - tmpnode->planenum = node->planenum; - tmpnode->children[0] = AAS_CreateAreas_r(node->children[0]); - tmpnode->children[1] = AAS_CreateAreas_r(node->children[1]); - return tmpnode; - } //end if - //areas won't be created for solid leafs - if (node->contents & CONTENTS_SOLID) - { - //just return zero for a solid leaf (in tmp AAS NULL is a solid leaf) - return NULL; - } //end if - - return AAS_CreateArea(node); -} //end of the function AAS_CreateAreas_r -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_CreateAreas(node_t *node) -{ - Log_Write("AAS_CreateAreas\r\n"); - qprintf("%6d areas created", 0); - tmpaasworld.nodes = AAS_CreateAreas_r(node); - qprintf("\n"); - Log_Write("%6d areas created\r\n", tmpaasworld.numareas); -} //end of the function AAS_CreateAreas -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_PrintNumGroundFaces(void) -{ - tmp_face_t *tmpface; - int numgroundfaces = 0; - - for (tmpface = tmpaasworld.faces; tmpface; tmpface = tmpface->l_next) - { - if (tmpface->faceflags & FACE_GROUND) - { - numgroundfaces++; - } //end if - } //end for - qprintf("%6d ground faces\n", numgroundfaces); -} //end of the function AAS_PrintNumGroundFaces -//=========================================================================== -// checks the number of shared faces between the given two areas -// since areas are convex they should only have ONE shared face -// however due to crappy face merging there are sometimes several -// shared faces -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_CheckAreaSharedFaces(tmp_area_t *tmparea1, tmp_area_t *tmparea2) -{ - int numsharedfaces, side; - tmp_face_t *face1, *sharedface; - - if (tmparea1->invalid || tmparea2->invalid) return; - - sharedface = NULL; - numsharedfaces = 0; - for (face1 = tmparea1->tmpfaces; face1; face1 = face1->next[side]) - { - side = face1->frontarea != tmparea1; - if (face1->backarea == tmparea2 || face1->frontarea == tmparea2) - { - sharedface = face1; - numsharedfaces++; - } //end if - } //end if - if (!sharedface) return; - //the areas should only have one shared face - if (numsharedfaces > 1) - { - Log_Write("---- tmp area %d and %d have %d shared faces\r\n", - tmparea1->areanum, tmparea2->areanum, numsharedfaces); - for (face1 = tmparea1->tmpfaces; face1; face1 = face1->next[side]) - { - side = face1->frontarea != tmparea1; - if (face1->backarea == tmparea2 || face1->frontarea == tmparea2) - { - Log_Write("face %d, planenum = %d, face->frontarea = %d face->backarea = %d\r\n", - face1->num, face1->planenum, face1->frontarea->areanum, face1->backarea->areanum); - } //end if - } //end if - } //end if -} //end of the function AAS_CheckAreaSharedFaces -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_CheckSharedFaces(void) -{ - tmp_area_t *tmparea1, *tmparea2; - - for (tmparea1 = tmpaasworld.areas; tmparea1; tmparea1 = tmparea1->l_next) - { - for (tmparea2 = tmpaasworld.areas; tmparea2; tmparea2 = tmparea2->l_next) - { - if (tmparea1 == tmparea2) continue; - AAS_CheckAreaSharedFaces(tmparea1, tmparea2); - } //end for - } //end for -} //end of the function AAS_CheckSharedFaces -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_FlipFace(tmp_face_t *face) -{ - tmp_area_t *frontarea, *backarea; - winding_t *w; - - frontarea = face->frontarea; - backarea = face->backarea; - //must have an area at both sides before flipping is allowed - if (!frontarea || !backarea) return; - //flip the face winding - w = face->winding; - face->winding = ReverseWinding(w); - FreeWinding(w); - //flip the face plane - face->planenum ^= 1; - //flip the face areas - AAS_RemoveFaceFromArea(face, frontarea); - AAS_RemoveFaceFromArea(face, backarea); - AAS_AddFaceSideToArea(face, 1, frontarea); - AAS_AddFaceSideToArea(face, 0, backarea); -} //end of the function AAS_FlipFace -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -/* -void AAS_FlipAreaSharedFaces(tmp_area_t *tmparea1, tmp_area_t *tmparea2) -{ - int numsharedfaces, side, area1facing, area2facing; - tmp_face_t *face1, *sharedface; - - if (tmparea1->invalid || tmparea2->invalid) return; - - sharedface = NULL; - numsharedfaces = 0; - area1facing = 0; //number of shared faces facing towards area 1 - area2facing = 0; //number of shared faces facing towards area 2 - for (face1 = tmparea1->tmpfaces; face1; face1 = face1->next[side]) - { - side = face1->frontarea != tmparea1; - if (face1->backarea == tmparea2 || face1->frontarea == tmparea2) - { - sharedface = face1; - numsharedfaces++; - if (face1->frontarea == tmparea1) area1facing++; - else area2facing++; - } //end if - } //end if - if (!sharedface) return; - //if there's only one shared face - if (numsharedfaces <= 1) return; - //if all the shared faces are facing to the same area - if (numsharedfaces == area1facing || numsharedfaces == area2facing) return; - // - do - { - for (face1 = tmparea1->tmpfaces; face1; face1 = face1->next[side]) - { - side = face1->frontarea != tmparea1; - if (face1->backarea == tmparea2 || face1->frontarea == tmparea2) - { - if (face1->frontarea != tmparea1) - { - AAS_FlipFace(face1); - break; - } //end if - } //end if - } //end for - } while(face1); -} //end of the function AAS_FlipAreaSharedFaces -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_FlipSharedFaces(void) -{ - int i; - tmp_area_t *tmparea1, *tmparea2; - - i = 0; - qprintf("%6d areas checked for shared face flipping", i); - for (tmparea1 = tmpaasworld.areas; tmparea1; tmparea1 = tmparea1->l_next) - { - if (tmparea1->invalid) continue; - for (tmparea2 = tmpaasworld.areas; tmparea2; tmparea2 = tmparea2->l_next) - { - if (tmparea2->invalid) continue; - if (tmparea1 == tmparea2) continue; - AAS_FlipAreaSharedFaces(tmparea1, tmparea2); - } //end for - qprintf("\r%6d", ++i); - } //end for - Log_Print("\r%6d areas checked for shared face flipping\n", i); -} //end of the function AAS_FlipSharedFaces -*/ -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_FlipSharedFaces(void) -{ - int i, side1, side2; - tmp_area_t *tmparea1; - tmp_face_t *face1, *face2; - - i = 0; - qprintf("%6d areas checked for shared face flipping", i); - for (tmparea1 = tmpaasworld.areas; tmparea1; tmparea1 = tmparea1->l_next) - { - if (tmparea1->invalid) continue; - for (face1 = tmparea1->tmpfaces; face1; face1 = face1->next[side1]) - { - side1 = face1->frontarea != tmparea1; - if (!face1->frontarea || !face1->backarea) continue; - // - for (face2 = face1->next[side1]; face2; face2 = face2->next[side2]) - { - side2 = face2->frontarea != tmparea1; - if (!face2->frontarea || !face2->backarea) continue; - // - if (face1->frontarea == face2->backarea && - face1->backarea == face2->frontarea) - { - AAS_FlipFace(face2); - } //end if - //recheck side - side2 = face2->frontarea != tmparea1; - } //end for - } //end for - qprintf("\r%6d", ++i); - } //end for - qprintf("\n"); - Log_Write("%6d areas checked for shared face flipping\r\n", i); -} //end of the function AAS_FlipSharedFaces -//=========================================================================== -// creates an .AAS file with the given name -// a MAP should be loaded before calling this -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_Create(char *aasfile) -{ - entity_t *e; - tree_t *tree; - double start_time; - - //for a possible leak file - strcpy(source, aasfile); - StripExtension(source); - //the time started - start_time = I_FloatTime(); - //set the default number of threads (depends on number of processors) - ThreadSetDefault(); - //set the global entity number to the world model - entity_num = 0; - //the world entity - e = &entities[entity_num]; - //process the whole world - tree = ProcessWorldBrushes(e->firstbrush, e->firstbrush + e->numbrushes); - //if the conversion is cancelled - if (cancelconversion) - { - Tree_Free(tree); - return; - } //end if - //display BSP tree creation time - Log_Print("BSP tree created in %5.0f seconds\n", I_FloatTime() - start_time); - //prune the bsp tree - Tree_PruneNodes(tree->headnode); - //if the conversion is cancelled - if (cancelconversion) - { - Tree_Free(tree); - return; - } //end if - //create the tree portals - MakeTreePortals(tree); - //if the conversion is cancelled - if (cancelconversion) - { - Tree_Free(tree); - return; - } //end if - //Marks all nodes that can be reached by entites - if (FloodEntities(tree)) - { - //fill out nodes that can't be reached - FillOutside(tree->headnode); - } //end if - else - { - LeakFile(tree); - Error("**** leaked ****\n"); - return; - } //end else - //create AAS from the BSP tree - //========================================== - //initialize tmp aas - AAS_InitTmpAAS(); - //create the convex areas from the leaves - AAS_CreateAreas(tree->headnode); - //free the BSP tree because it isn't used anymore - if (freetree) Tree_Free(tree); - //try to merge area faces - AAS_MergeAreaFaces(); - //do gravitational subdivision - AAS_GravitationalSubdivision(); - //merge faces if possible - AAS_MergeAreaFaces(); - AAS_RemoveAreaFaceColinearPoints(); - //merge areas if possible - AAS_MergeAreas(); - //NOTE: prune nodes directly after area merging - AAS_PruneNodes(); - //flip shared faces so they are all facing to the same area - AAS_FlipSharedFaces(); - AAS_RemoveAreaFaceColinearPoints(); - //merge faces if possible - AAS_MergeAreaFaces(); - //merge area faces in the same plane - AAS_MergeAreaPlaneFaces(); - //do ladder subdivision - AAS_LadderSubdivision(); - //FIXME: melting is buggy - AAS_MeltAreaFaceWindings(); - //remove tiny faces - AAS_RemoveTinyFaces(); - //create area settings - AAS_CreateAreaSettings(); - //check if the winding plane is equal to the face plane - //AAS_CheckAreaWindingPlanes(); - // - //AAS_CheckSharedFaces(); - //========================================== - //if the conversion is cancelled - if (cancelconversion) - { - Tree_Free(tree); - AAS_FreeTmpAAS(); - return; - } //end if - //store the created AAS stuff in the AAS file format and write the file - AAS_StoreFile(aasfile); - //free the temporary AAS memory - AAS_FreeTmpAAS(); - //display creation time - Log_Print("\nAAS created in %5.0f seconds\n", I_FloatTime() - start_time); -} //end of the function AAS_Create +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "qbsp.h" +#include "../botlib/aasfile.h" +#include "aas_create.h" +#include "aas_store.h" +#include "aas_gsubdiv.h" +#include "aas_facemerging.h" +#include "aas_areamerging.h" +#include "aas_edgemelting.h" +#include "aas_prunenodes.h" +#include "aas_cfg.h" +#include "../game/surfaceflags.h" + +//#define AW_DEBUG +//#define L_DEBUG + +#define AREAONFACESIDE(face, area) (face->frontarea != area) + +tmp_aas_t tmpaasworld; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitTmpAAS(void) +{ + //tmp faces + tmpaasworld.numfaces = 0; + tmpaasworld.facenum = 0; + tmpaasworld.faces = NULL; + //tmp convex areas + tmpaasworld.numareas = 0; + tmpaasworld.areanum = 0; + tmpaasworld.areas = NULL; + //tmp nodes + tmpaasworld.numnodes = 0; + tmpaasworld.nodes = NULL; + // + tmpaasworld.nodebuffer = NULL; +} //end of the function AAS_InitTmpAAS +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeTmpAAS(void) +{ + tmp_face_t *f, *nextf; + tmp_area_t *a, *nexta; + tmp_nodebuf_t *nb, *nextnb; + + //free all the faces + for (f = tmpaasworld.faces; f; f = nextf) + { + nextf = f->l_next; + if (f->winding) FreeWinding(f->winding); + FreeMemory(f); + } //end if + //free all tmp areas + for (a = tmpaasworld.areas; a; a = nexta) + { + nexta = a->l_next; + if (a->settings) FreeMemory(a->settings); + FreeMemory(a); + } //end for + //free all the tmp nodes + for (nb = tmpaasworld.nodebuffer; nb; nb = nextnb) + { + nextnb = nb->next; + FreeMemory(nb); + } //end for +} //end of the function AAS_FreeTmpAAS +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +tmp_face_t *AAS_AllocTmpFace(void) +{ + tmp_face_t *tmpface; + + tmpface = (tmp_face_t *) GetClearedMemory(sizeof(tmp_face_t)); + tmpface->num = tmpaasworld.facenum++; + tmpface->l_prev = NULL; + tmpface->l_next = tmpaasworld.faces; + if (tmpaasworld.faces) tmpaasworld.faces->l_prev = tmpface; + tmpaasworld.faces = tmpface; + tmpaasworld.numfaces++; + return tmpface; +} //end of the function AAS_AllocTmpFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeTmpFace(tmp_face_t *tmpface) +{ + if (tmpface->l_next) tmpface->l_next->l_prev = tmpface->l_prev; + if (tmpface->l_prev) tmpface->l_prev->l_next = tmpface->l_next; + else tmpaasworld.faces = tmpface->l_next; + //free the winding + if (tmpface->winding) FreeWinding(tmpface->winding); + //free the face + FreeMemory(tmpface); + tmpaasworld.numfaces--; +} //end of the function AAS_FreeTmpFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +tmp_area_t *AAS_AllocTmpArea(void) +{ + tmp_area_t *tmparea; + + tmparea = (tmp_area_t *) GetClearedMemory(sizeof(tmp_area_t)); + tmparea->areanum = tmpaasworld.areanum++; + tmparea->l_prev = NULL; + tmparea->l_next = tmpaasworld.areas; + if (tmpaasworld.areas) tmpaasworld.areas->l_prev = tmparea; + tmpaasworld.areas = tmparea; + tmpaasworld.numareas++; + return tmparea; +} //end of the function AAS_AllocTmpArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeTmpArea(tmp_area_t *tmparea) +{ + if (tmparea->l_next) tmparea->l_next->l_prev = tmparea->l_prev; + if (tmparea->l_prev) tmparea->l_prev->l_next = tmparea->l_next; + else tmpaasworld.areas = tmparea->l_next; + if (tmparea->settings) FreeMemory(tmparea->settings); + FreeMemory(tmparea); + tmpaasworld.numareas--; +} //end of the function AAS_FreeTmpArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +tmp_node_t *AAS_AllocTmpNode(void) +{ + tmp_nodebuf_t *nodebuf; + + if (!tmpaasworld.nodebuffer || + tmpaasworld.nodebuffer->numnodes >= NODEBUF_SIZE) + { + nodebuf = (tmp_nodebuf_t *) GetClearedMemory(sizeof(tmp_nodebuf_t)); + nodebuf->next = tmpaasworld.nodebuffer; + nodebuf->numnodes = 0; + tmpaasworld.nodebuffer = nodebuf; + } //end if + tmpaasworld.numnodes++; + return &tmpaasworld.nodebuffer->nodes[tmpaasworld.nodebuffer->numnodes++]; +} //end of the function AAS_AllocTmpNode +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeTmpNode(tmp_node_t *tmpnode) +{ + tmpaasworld.numnodes--; +} //end of the function AAS_FreeTmpNode +//=========================================================================== +// returns true if the face is a gap from the given side +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_GapFace(tmp_face_t *tmpface, int side) +{ + vec3_t invgravity; + + //if the face is a solid or ground face it can't be a gap + if (tmpface->faceflags & (FACE_GROUND | FACE_SOLID)) return 0; + + VectorCopy(cfg.phys_gravitydirection, invgravity); + VectorInverse(invgravity); + + return (DotProduct(invgravity, mapplanes[tmpface->planenum ^ side].normal) > cfg.phys_maxsteepness); +} //end of the function AAS_GapFace +//=========================================================================== +// returns true if the face is a ground face +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_GroundFace(tmp_face_t *tmpface) +{ + vec3_t invgravity; + + //must be a solid face + if (!(tmpface->faceflags & FACE_SOLID)) return 0; + + VectorCopy(cfg.phys_gravitydirection, invgravity); + VectorInverse(invgravity); + + return (DotProduct(invgravity, mapplanes[tmpface->planenum].normal) > cfg.phys_maxsteepness); +} //end of the function AAS_GroundFace +//=========================================================================== +// adds the side of a face to an area +// +// side : 0 = front side +// 1 = back side +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_AddFaceSideToArea(tmp_face_t *tmpface, int side, tmp_area_t *tmparea) +{ + int tmpfaceside; + + if (side) + { + if (tmpface->backarea) Error("AAS_AddFaceSideToArea: already a back area\n"); + } //end if + else + { + if (tmpface->frontarea) Error("AAS_AddFaceSideToArea: already a front area\n"); + } //end else + + if (side) tmpface->backarea = tmparea; + else tmpface->frontarea = tmparea; + + if (tmparea->tmpfaces) + { + tmpfaceside = tmparea->tmpfaces->frontarea != tmparea; + tmparea->tmpfaces->prev[tmpfaceside] = tmpface; + } //end if + tmpface->next[side] = tmparea->tmpfaces; + tmpface->prev[side] = NULL; + tmparea->tmpfaces = tmpface; +} //end of the function AAS_AddFaceSideToArea +//=========================================================================== +// remove (a side of) a face from an area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RemoveFaceFromArea(tmp_face_t *tmpface, tmp_area_t *tmparea) +{ + int side, prevside, nextside; + + if (tmpface->frontarea != tmparea && + tmpface->backarea != tmparea) + { + Error("AAS_RemoveFaceFromArea: face not part of the area"); + } //end if + side = tmpface->frontarea != tmparea; + if (tmpface->prev[side]) + { + prevside = tmpface->prev[side]->frontarea != tmparea; + tmpface->prev[side]->next[prevside] = tmpface->next[side]; + } //end if + else + { + tmparea->tmpfaces = tmpface->next[side]; + } //end else + if (tmpface->next[side]) + { + nextside = tmpface->next[side]->frontarea != tmparea; + tmpface->next[side]->prev[nextside] = tmpface->prev[side]; + } //end if + //remove the area number from the face depending on the side + if (side) tmpface->backarea = NULL; + else tmpface->frontarea = NULL; + tmpface->prev[side] = NULL; + tmpface->next[side] = NULL; +} //end of the function AAS_RemoveFaceFromArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CheckArea(tmp_area_t *tmparea) +{ + int side; + tmp_face_t *face; + plane_t *plane; + vec3_t wcenter, acenter = {0, 0, 0}; + vec3_t normal; + float n, dist; + + if (tmparea->invalid) Log_Print("AAS_CheckArea: invalid area\n"); + for (n = 0, face = tmparea->tmpfaces; face; face = face->next[side]) + { + //side of the face the area is on + side = face->frontarea != tmparea; + WindingCenter(face->winding, wcenter); + VectorAdd(acenter, wcenter, acenter); + n++; + } //end for + n = 1 / n; + VectorScale(acenter, n, acenter); + for (face = tmparea->tmpfaces; face; face = face->next[side]) + { + //side of the face the area is on + side = face->frontarea != tmparea; + +#ifdef L_DEBUG + if (WindingError(face->winding)) + { + Log_Write("AAS_CheckArea: area %d face %d: %s\r\n", tmparea->areanum, + face->num, WindingErrorString()); + } //end if +#endif L_DEBUG + + plane = &mapplanes[face->planenum ^ side]; + + if (DotProduct(plane->normal, acenter) - plane->dist < 0) + { + Log_Print("AAS_CheckArea: area %d face %d is flipped\n", tmparea->areanum, face->num); + Log_Print("AAS_CheckArea: area %d center is %f %f %f\n", tmparea->areanum, acenter[0], acenter[1], acenter[2]); + } //end if + //check if the winding plane is the same as the face plane + WindingPlane(face->winding, normal, &dist); + plane = &mapplanes[face->planenum]; +#ifdef L_DEBUG + if (fabs(dist - plane->dist) > 0.4 || + fabs(normal[0] - plane->normal[0]) > 0.0001 || + fabs(normal[1] - plane->normal[1]) > 0.0001 || + fabs(normal[2] - plane->normal[2]) > 0.0001) + { + Log_Write("AAS_CheckArea: area %d face %d winding plane unequal to face plane\r\n", + tmparea->areanum, face->num); + } //end if +#endif L_DEBUG + } //end for +} //end of the function AAS_CheckArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CheckFaceWindingPlane(tmp_face_t *face) +{ + float dist, sign1, sign2; + vec3_t normal; + plane_t *plane; + winding_t *w; + + //check if the winding plane is the same as the face plane + WindingPlane(face->winding, normal, &dist); + plane = &mapplanes[face->planenum]; + // + sign1 = DotProduct(plane->normal, normal); + // + if (fabs(dist - plane->dist) > 0.4 || + fabs(normal[0] - plane->normal[0]) > 0.0001 || + fabs(normal[1] - plane->normal[1]) > 0.0001 || + fabs(normal[2] - plane->normal[2]) > 0.0001) + { + VectorInverse(normal); + dist = -dist; + if (fabs(dist - plane->dist) > 0.4 || + fabs(normal[0] - plane->normal[0]) > 0.0001 || + fabs(normal[1] - plane->normal[1]) > 0.0001 || + fabs(normal[2] - plane->normal[2]) > 0.0001) + { + Log_Write("AAS_CheckFaceWindingPlane: face %d winding plane unequal to face plane\r\n", + face->num); + // + sign2 = DotProduct(plane->normal, normal); + if ((sign1 < 0 && sign2 > 0) || + (sign1 > 0 && sign2 < 0)) + { + Log_Write("AAS_CheckFaceWindingPlane: face %d winding reversed\r\n", + face->num); + w = face->winding; + face->winding = ReverseWinding(w); + FreeWinding(w); + } //end if + } //end if + else + { + Log_Write("AAS_CheckFaceWindingPlane: face %d winding reversed\r\n", + face->num); + w = face->winding; + face->winding = ReverseWinding(w); + FreeWinding(w); + } //end else + } //end if +} //end of the function AAS_CheckFaceWindingPlane +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CheckAreaWindingPlanes(void) +{ + int side; + tmp_area_t *tmparea; + tmp_face_t *face; + + Log_Write("AAS_CheckAreaWindingPlanes:\r\n"); + for (tmparea = tmpaasworld.areas; tmparea; tmparea = tmparea->l_next) + { + if (tmparea->invalid) continue; + for (face = tmparea->tmpfaces; face; face = face->next[side]) + { + side = face->frontarea != tmparea; + AAS_CheckFaceWindingPlane(face); + } //end for + } //end for +} //end of the function AAS_CheckAreaWindingPlanes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FlipAreaFaces(tmp_area_t *tmparea) +{ + int side; + tmp_face_t *face; + plane_t *plane; + vec3_t wcenter, acenter = {0, 0, 0}; + //winding_t *w; + float n; + + for (n = 0, face = tmparea->tmpfaces; face; face = face->next[side]) + { + if (!face->frontarea) Error("face %d has no front area\n", face->num); + //side of the face the area is on + side = face->frontarea != tmparea; + WindingCenter(face->winding, wcenter); + VectorAdd(acenter, wcenter, acenter); + n++; + } //end for + n = 1 / n; + VectorScale(acenter, n, acenter); + for (face = tmparea->tmpfaces; face; face = face->next[side]) + { + //side of the face the area is on + side = face->frontarea != tmparea; + + plane = &mapplanes[face->planenum ^ side]; + + if (DotProduct(plane->normal, acenter) - plane->dist < 0) + { + Log_Print("area %d face %d flipped: front area %d, back area %d\n", tmparea->areanum, face->num, + face->frontarea ? face->frontarea->areanum : 0, + face->backarea ? face->backarea->areanum : 0); + /* + face->planenum = face->planenum ^ 1; + w = face->winding; + face->winding = ReverseWinding(w); + FreeWinding(w); + */ + } //end if +#ifdef L_DEBUG + { + float dist; + vec3_t normal; + + //check if the winding plane is the same as the face plane + WindingPlane(face->winding, normal, &dist); + plane = &mapplanes[face->planenum]; + if (fabs(dist - plane->dist) > 0.4 || + fabs(normal[0] - plane->normal[0]) > 0.0001 || + fabs(normal[1] - plane->normal[1]) > 0.0001 || + fabs(normal[2] - plane->normal[2]) > 0.0001) + { + Log_Write("area %d face %d winding plane unequal to face plane\r\n", + tmparea->areanum, face->num); + } //end if + } +#endif + } //end for +} //end of the function AAS_FlipAreaFaces +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RemoveAreaFaceColinearPoints(void) +{ + int side; + tmp_face_t *face; + tmp_area_t *tmparea; + + //FIXME: loop over the faces instead of area->faces + for (tmparea = tmpaasworld.areas; tmparea; tmparea = tmparea->l_next) + { + for (face = tmparea->tmpfaces; face; face = face->next[side]) + { + side = face->frontarea != tmparea; + RemoveColinearPoints(face->winding); +// RemoveEqualPoints(face->winding, 0.1); + } //end for + } //end for +} //end of the function AAS_RemoveAreaFaceColinearPoints +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RemoveTinyFaces(void) +{ + int side, num; + tmp_face_t *face, *nextface; + tmp_area_t *tmparea; + + //FIXME: loop over the faces instead of area->faces + Log_Write("AAS_RemoveTinyFaces\r\n"); + num = 0; + for (tmparea = tmpaasworld.areas; tmparea; tmparea = tmparea->l_next) + { + for (face = tmparea->tmpfaces; face; face = nextface) + { + side = face->frontarea != tmparea; + nextface = face->next[side]; + // + if (WindingArea(face->winding) < 1) + { + if (face->frontarea) AAS_RemoveFaceFromArea(face, face->frontarea); + if (face->backarea) AAS_RemoveFaceFromArea(face, face->backarea); + AAS_FreeTmpFace(face); + //Log_Write("area %d face %d is tiny\r\n", tmparea->areanum, face->num); + num++; + } //end if + } //end for + } //end for + Log_Write("%d tiny faces removed\r\n", num); +} //end of the function AAS_RemoveTinyFaces +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CreateAreaSettings(void) +{ + int i, flags, side, numgrounded, numladderareas, numliquidareas; + tmp_face_t *face; + tmp_area_t *tmparea; + + numgrounded = 0; + numladderareas = 0; + numliquidareas = 0; + Log_Write("AAS_CreateAreaSettings\r\n"); + i = 0; + qprintf("%6d areas provided with settings", i); + for (tmparea = tmpaasworld.areas; tmparea; tmparea = tmparea->l_next) + { + //if the area is invalid there no need to create settings for it + if (tmparea->invalid) continue; + + tmparea->settings = (tmp_areasettings_t *) GetClearedMemory(sizeof(tmp_areasettings_t)); + tmparea->settings->contents = tmparea->contents; + tmparea->settings->modelnum = tmparea->modelnum; + flags = 0; + for (face = tmparea->tmpfaces; face; face = face->next[side]) + { + side = face->frontarea != tmparea; + flags |= face->faceflags; + } //end for + tmparea->settings->areaflags = 0; + if (flags & FACE_GROUND) + { + tmparea->settings->areaflags |= AREA_GROUNDED; + numgrounded++; + } //end if + if (flags & FACE_LADDER) + { + tmparea->settings->areaflags |= AREA_LADDER; + numladderareas++; + } //end if + if (tmparea->contents & (AREACONTENTS_WATER | + AREACONTENTS_SLIME | + AREACONTENTS_LAVA)) + { + tmparea->settings->areaflags |= AREA_LIQUID; + numliquidareas++; + } //end if + //presence type of the area + tmparea->settings->presencetype = tmparea->presencetype; + // + qprintf("\r%6d", ++i); + } //end for + qprintf("\n"); +#ifdef AASINFO + Log_Print("%6d grounded areas\n", numgrounded); + Log_Print("%6d ladder areas\n", numladderareas); + Log_Print("%6d liquid areas\n", numliquidareas); +#endif //AASINFO +} //end of the function AAS_CreateAreaSettings +//=========================================================================== +// create a tmp AAS area from a leaf node +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +tmp_node_t *AAS_CreateArea(node_t *node) +{ + int pside; + int areafaceflags; + portal_t *p; + tmp_face_t *tmpface; + tmp_area_t *tmparea; + tmp_node_t *tmpnode; + vec3_t up = {0, 0, 1}; + + //create an area from this leaf + tmparea = AAS_AllocTmpArea(); + tmparea->tmpfaces = NULL; + //clear the area face flags + areafaceflags = 0; + //make aas faces from the portals + for (p = node->portals; p; p = p->next[pside]) + { + pside = (p->nodes[1] == node); + //don't create faces from very small portals +// if (WindingArea(p->winding) < 1) continue; + //if there's already a face created for this portal + if (p->tmpface) + { + //add the back side of the face to the area + AAS_AddFaceSideToArea(p->tmpface, 1, tmparea); + } //end if + else + { + tmpface = AAS_AllocTmpFace(); + //set the face pointer at the portal so we can see from + //the portal there's a face created for it + p->tmpface = tmpface; + //FIXME: test this change + //tmpface->planenum = (p->planenum & ~1) | pside; + tmpface->planenum = p->planenum ^ pside; + if (pside) tmpface->winding = ReverseWinding(p->winding); + else tmpface->winding = CopyWinding(p->winding); +#ifdef L_DEBUG + // + AAS_CheckFaceWindingPlane(tmpface); +#endif //L_DEBUG + //if there's solid at the other side of the portal + if (p->nodes[!pside]->contents & (CONTENTS_SOLID | CONTENTS_PLAYERCLIP)) + { + tmpface->faceflags |= FACE_SOLID; + } //end if + //else there is no solid at the other side and if there + //is a liquid at this side + else if (node->contents & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA)) + { + tmpface->faceflags |= FACE_LIQUID; + //if there's no liquid at the other side + if (!(p->nodes[!pside]->contents & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA))) + { + tmpface->faceflags |= FACE_LIQUIDSURFACE; + } //end if + } //end else + //if there's ladder contents at other side of the portal + if ((p->nodes[pside]->contents & CONTENTS_LADDER) || + (p->nodes[!pside]->contents & CONTENTS_LADDER)) + { + + //NOTE: doesn't have to be solid at the other side because + // when standing one can use a crouch area (which is not solid) + // as a ladder + // imagine a ladder one can walk underthrough, + // under the ladder against the ladder is a crouch area + // the (vertical) sides of this crouch area area also used as + // ladder sides when standing (not crouched) + tmpface->faceflags |= FACE_LADDER; + } //end if + //if it is possible to stand on the face + if (AAS_GroundFace(tmpface)) + { + tmpface->faceflags |= FACE_GROUND; + } //end if + // + areafaceflags |= tmpface->faceflags; + //no aas face number yet (zero is a dummy in the aasworld faces) + tmpface->aasfacenum = 0; + //add the front side of the face to the area + AAS_AddFaceSideToArea(tmpface, 0, tmparea); + } //end else + } //end for + qprintf("\r%6d", tmparea->areanum); + //presence type in the area + tmparea->presencetype = ~node->expansionbboxes & cfg.allpresencetypes; + // + tmparea->contents = 0; + if (node->contents & CONTENTS_CLUSTERPORTAL) tmparea->contents |= AREACONTENTS_CLUSTERPORTAL; + if (node->contents & CONTENTS_MOVER) tmparea->contents |= AREACONTENTS_MOVER; + if (node->contents & CONTENTS_TELEPORTER) tmparea->contents |= AREACONTENTS_TELEPORTER; + if (node->contents & CONTENTS_JUMPPAD) tmparea->contents |= AREACONTENTS_JUMPPAD; + if (node->contents & CONTENTS_DONOTENTER) tmparea->contents |= AREACONTENTS_DONOTENTER; + if (node->contents & CONTENTS_WATER) tmparea->contents |= AREACONTENTS_WATER; + if (node->contents & CONTENTS_LAVA) tmparea->contents |= AREACONTENTS_LAVA; + if (node->contents & CONTENTS_SLIME) tmparea->contents |= AREACONTENTS_SLIME; + if (node->contents & CONTENTS_NOTTEAM1) tmparea->contents |= AREACONTENTS_NOTTEAM1; + if (node->contents & CONTENTS_NOTTEAM2) tmparea->contents |= AREACONTENTS_NOTTEAM2; + + //store the bsp model that's inside this node + tmparea->modelnum = node->modelnum; + //sorta check for flipped area faces (remove??) + AAS_FlipAreaFaces(tmparea); + //check if the area is ok (remove??) + AAS_CheckArea(tmparea); + // + tmpnode = AAS_AllocTmpNode(); + tmpnode->planenum = 0; + tmpnode->children[0] = 0; + tmpnode->children[1] = 0; + tmpnode->tmparea = tmparea; + // + return tmpnode; +} //end of the function AAS_CreateArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +tmp_node_t *AAS_CreateAreas_r(node_t *node) +{ + tmp_node_t *tmpnode; + + //recurse down to leafs + if (node->planenum != PLANENUM_LEAF) + { + //the first tmp node is a dummy + tmpnode = AAS_AllocTmpNode(); + tmpnode->planenum = node->planenum; + tmpnode->children[0] = AAS_CreateAreas_r(node->children[0]); + tmpnode->children[1] = AAS_CreateAreas_r(node->children[1]); + return tmpnode; + } //end if + //areas won't be created for solid leafs + if (node->contents & CONTENTS_SOLID) + { + //just return zero for a solid leaf (in tmp AAS NULL is a solid leaf) + return NULL; + } //end if + + return AAS_CreateArea(node); +} //end of the function AAS_CreateAreas_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CreateAreas(node_t *node) +{ + Log_Write("AAS_CreateAreas\r\n"); + qprintf("%6d areas created", 0); + tmpaasworld.nodes = AAS_CreateAreas_r(node); + qprintf("\n"); + Log_Write("%6d areas created\r\n", tmpaasworld.numareas); +} //end of the function AAS_CreateAreas +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_PrintNumGroundFaces(void) +{ + tmp_face_t *tmpface; + int numgroundfaces = 0; + + for (tmpface = tmpaasworld.faces; tmpface; tmpface = tmpface->l_next) + { + if (tmpface->faceflags & FACE_GROUND) + { + numgroundfaces++; + } //end if + } //end for + qprintf("%6d ground faces\n", numgroundfaces); +} //end of the function AAS_PrintNumGroundFaces +//=========================================================================== +// checks the number of shared faces between the given two areas +// since areas are convex they should only have ONE shared face +// however due to crappy face merging there are sometimes several +// shared faces +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CheckAreaSharedFaces(tmp_area_t *tmparea1, tmp_area_t *tmparea2) +{ + int numsharedfaces, side; + tmp_face_t *face1, *sharedface; + + if (tmparea1->invalid || tmparea2->invalid) return; + + sharedface = NULL; + numsharedfaces = 0; + for (face1 = tmparea1->tmpfaces; face1; face1 = face1->next[side]) + { + side = face1->frontarea != tmparea1; + if (face1->backarea == tmparea2 || face1->frontarea == tmparea2) + { + sharedface = face1; + numsharedfaces++; + } //end if + } //end if + if (!sharedface) return; + //the areas should only have one shared face + if (numsharedfaces > 1) + { + Log_Write("---- tmp area %d and %d have %d shared faces\r\n", + tmparea1->areanum, tmparea2->areanum, numsharedfaces); + for (face1 = tmparea1->tmpfaces; face1; face1 = face1->next[side]) + { + side = face1->frontarea != tmparea1; + if (face1->backarea == tmparea2 || face1->frontarea == tmparea2) + { + Log_Write("face %d, planenum = %d, face->frontarea = %d face->backarea = %d\r\n", + face1->num, face1->planenum, face1->frontarea->areanum, face1->backarea->areanum); + } //end if + } //end if + } //end if +} //end of the function AAS_CheckAreaSharedFaces +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CheckSharedFaces(void) +{ + tmp_area_t *tmparea1, *tmparea2; + + for (tmparea1 = tmpaasworld.areas; tmparea1; tmparea1 = tmparea1->l_next) + { + for (tmparea2 = tmpaasworld.areas; tmparea2; tmparea2 = tmparea2->l_next) + { + if (tmparea1 == tmparea2) continue; + AAS_CheckAreaSharedFaces(tmparea1, tmparea2); + } //end for + } //end for +} //end of the function AAS_CheckSharedFaces +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FlipFace(tmp_face_t *face) +{ + tmp_area_t *frontarea, *backarea; + winding_t *w; + + frontarea = face->frontarea; + backarea = face->backarea; + //must have an area at both sides before flipping is allowed + if (!frontarea || !backarea) return; + //flip the face winding + w = face->winding; + face->winding = ReverseWinding(w); + FreeWinding(w); + //flip the face plane + face->planenum ^= 1; + //flip the face areas + AAS_RemoveFaceFromArea(face, frontarea); + AAS_RemoveFaceFromArea(face, backarea); + AAS_AddFaceSideToArea(face, 1, frontarea); + AAS_AddFaceSideToArea(face, 0, backarea); +} //end of the function AAS_FlipFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +/* +void AAS_FlipAreaSharedFaces(tmp_area_t *tmparea1, tmp_area_t *tmparea2) +{ + int numsharedfaces, side, area1facing, area2facing; + tmp_face_t *face1, *sharedface; + + if (tmparea1->invalid || tmparea2->invalid) return; + + sharedface = NULL; + numsharedfaces = 0; + area1facing = 0; //number of shared faces facing towards area 1 + area2facing = 0; //number of shared faces facing towards area 2 + for (face1 = tmparea1->tmpfaces; face1; face1 = face1->next[side]) + { + side = face1->frontarea != tmparea1; + if (face1->backarea == tmparea2 || face1->frontarea == tmparea2) + { + sharedface = face1; + numsharedfaces++; + if (face1->frontarea == tmparea1) area1facing++; + else area2facing++; + } //end if + } //end if + if (!sharedface) return; + //if there's only one shared face + if (numsharedfaces <= 1) return; + //if all the shared faces are facing to the same area + if (numsharedfaces == area1facing || numsharedfaces == area2facing) return; + // + do + { + for (face1 = tmparea1->tmpfaces; face1; face1 = face1->next[side]) + { + side = face1->frontarea != tmparea1; + if (face1->backarea == tmparea2 || face1->frontarea == tmparea2) + { + if (face1->frontarea != tmparea1) + { + AAS_FlipFace(face1); + break; + } //end if + } //end if + } //end for + } while(face1); +} //end of the function AAS_FlipAreaSharedFaces +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FlipSharedFaces(void) +{ + int i; + tmp_area_t *tmparea1, *tmparea2; + + i = 0; + qprintf("%6d areas checked for shared face flipping", i); + for (tmparea1 = tmpaasworld.areas; tmparea1; tmparea1 = tmparea1->l_next) + { + if (tmparea1->invalid) continue; + for (tmparea2 = tmpaasworld.areas; tmparea2; tmparea2 = tmparea2->l_next) + { + if (tmparea2->invalid) continue; + if (tmparea1 == tmparea2) continue; + AAS_FlipAreaSharedFaces(tmparea1, tmparea2); + } //end for + qprintf("\r%6d", ++i); + } //end for + Log_Print("\r%6d areas checked for shared face flipping\n", i); +} //end of the function AAS_FlipSharedFaces +*/ +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FlipSharedFaces(void) +{ + int i, side1, side2; + tmp_area_t *tmparea1; + tmp_face_t *face1, *face2; + + i = 0; + qprintf("%6d areas checked for shared face flipping", i); + for (tmparea1 = tmpaasworld.areas; tmparea1; tmparea1 = tmparea1->l_next) + { + if (tmparea1->invalid) continue; + for (face1 = tmparea1->tmpfaces; face1; face1 = face1->next[side1]) + { + side1 = face1->frontarea != tmparea1; + if (!face1->frontarea || !face1->backarea) continue; + // + for (face2 = face1->next[side1]; face2; face2 = face2->next[side2]) + { + side2 = face2->frontarea != tmparea1; + if (!face2->frontarea || !face2->backarea) continue; + // + if (face1->frontarea == face2->backarea && + face1->backarea == face2->frontarea) + { + AAS_FlipFace(face2); + } //end if + //recheck side + side2 = face2->frontarea != tmparea1; + } //end for + } //end for + qprintf("\r%6d", ++i); + } //end for + qprintf("\n"); + Log_Write("%6d areas checked for shared face flipping\r\n", i); +} //end of the function AAS_FlipSharedFaces +//=========================================================================== +// creates an .AAS file with the given name +// a MAP should be loaded before calling this +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Create(char *aasfile) +{ + entity_t *e; + tree_t *tree; + double start_time; + + //for a possible leak file + strcpy(source, aasfile); + StripExtension(source); + //the time started + start_time = I_FloatTime(); + //set the default number of threads (depends on number of processors) + ThreadSetDefault(); + //set the global entity number to the world model + entity_num = 0; + //the world entity + e = &entities[entity_num]; + //process the whole world + tree = ProcessWorldBrushes(e->firstbrush, e->firstbrush + e->numbrushes); + //if the conversion is cancelled + if (cancelconversion) + { + Tree_Free(tree); + return; + } //end if + //display BSP tree creation time + Log_Print("BSP tree created in %5.0f seconds\n", I_FloatTime() - start_time); + //prune the bsp tree + Tree_PruneNodes(tree->headnode); + //if the conversion is cancelled + if (cancelconversion) + { + Tree_Free(tree); + return; + } //end if + //create the tree portals + MakeTreePortals(tree); + //if the conversion is cancelled + if (cancelconversion) + { + Tree_Free(tree); + return; + } //end if + //Marks all nodes that can be reached by entites + if (FloodEntities(tree)) + { + //fill out nodes that can't be reached + FillOutside(tree->headnode); + } //end if + else + { + LeakFile(tree); + Error("**** leaked ****\n"); + return; + } //end else + //create AAS from the BSP tree + //========================================== + //initialize tmp aas + AAS_InitTmpAAS(); + //create the convex areas from the leaves + AAS_CreateAreas(tree->headnode); + //free the BSP tree because it isn't used anymore + if (freetree) Tree_Free(tree); + //try to merge area faces + AAS_MergeAreaFaces(); + //do gravitational subdivision + AAS_GravitationalSubdivision(); + //merge faces if possible + AAS_MergeAreaFaces(); + AAS_RemoveAreaFaceColinearPoints(); + //merge areas if possible + AAS_MergeAreas(); + //NOTE: prune nodes directly after area merging + AAS_PruneNodes(); + //flip shared faces so they are all facing to the same area + AAS_FlipSharedFaces(); + AAS_RemoveAreaFaceColinearPoints(); + //merge faces if possible + AAS_MergeAreaFaces(); + //merge area faces in the same plane + AAS_MergeAreaPlaneFaces(); + //do ladder subdivision + AAS_LadderSubdivision(); + //FIXME: melting is buggy + AAS_MeltAreaFaceWindings(); + //remove tiny faces + AAS_RemoveTinyFaces(); + //create area settings + AAS_CreateAreaSettings(); + //check if the winding plane is equal to the face plane + //AAS_CheckAreaWindingPlanes(); + // + //AAS_CheckSharedFaces(); + //========================================== + //if the conversion is cancelled + if (cancelconversion) + { + Tree_Free(tree); + AAS_FreeTmpAAS(); + return; + } //end if + //store the created AAS stuff in the AAS file format and write the file + AAS_StoreFile(aasfile); + //free the temporary AAS memory + AAS_FreeTmpAAS(); + //display creation time + Log_Print("\nAAS created in %5.0f seconds\n", I_FloatTime() - start_time); +} //end of the function AAS_Create diff --git a/code/bspc/aas_create.h b/code/bspc/aas_create.h index cc5693c..7f41f02 100755 --- a/code/bspc/aas_create.h +++ b/code/bspc/aas_create.h @@ -1,136 +1,136 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#define AREA_PORTAL 1 - -//temporary AAS face -typedef struct tmp_face_s -{ - int num; //face number - int planenum; //number of the plane the face is in - winding_t *winding; //winding of the face - struct tmp_area_s *frontarea; //area at the front of the face - struct tmp_area_s *backarea; //area at the back of the face - int faceflags; //flags of this face - int aasfacenum; //the number of the aas face used for this face - //double link list pointers for front and back area - struct tmp_face_s *prev[2], *next[2]; - //links in the list with faces - struct tmp_face_s *l_prev, *l_next; -} tmp_face_t; - -//temporary AAS area settings -typedef struct tmp_areasettings_s -{ - //could also add all kind of statistic fields - int contents; //contents of the area - int modelnum; //bsp model inside this area - int areaflags; //area flags - int presencetype; //how a bot can be present in this area - int numreachableareas; //number of reachable areas from this one - int firstreachablearea; //first reachable area in the reachable area index -} tmp_areasettings_t; - -//temporary AAS area -typedef struct tmp_area_s -{ - int areanum; //number of the area - struct tmp_face_s *tmpfaces; //the faces of the area - int presencetype; //presence type of the area - int contents; //area contents - int modelnum; //bsp model inside this area - int invalid; //true if the area is invalid - tmp_areasettings_t *settings; //area settings - struct tmp_area_s *mergedarea; //points to the new area after merging - //when mergedarea != 0 the area has only the - //seperating face of the merged areas - int aasareanum; //number of the aas area created for this tmp area - //links in the list with areas - struct tmp_area_s *l_prev, *l_next; -} tmp_area_t; - -//temporary AAS node -typedef struct tmp_node_s -{ - int planenum; //node plane number - struct tmp_area_s *tmparea; //points to an area if this node is an area - struct tmp_node_s *children[2]; //child nodes of this node -} tmp_node_t; - -#define NODEBUF_SIZE 128 -//node buffer -typedef struct tmp_nodebuf_s -{ - int numnodes; - struct tmp_nodebuf_s *next; - tmp_node_t nodes[NODEBUF_SIZE]; -} tmp_nodebuf_t; - -//the whole temorary AAS -typedef struct tmp_aas_s -{ - //faces - int numfaces; - int facenum; - tmp_face_t *faces; - //areas - int numareas; - int areanum; - tmp_area_t *areas; - //area settings - int numareasettings; - tmp_areasettings_t *areasettings; - //nodes - int numnodes; - tmp_node_t *nodes; - //node buffer - tmp_nodebuf_t *nodebuffer; -} tmp_aas_t; - -extern tmp_aas_t tmpaasworld; - -//creates a .AAS file with the given name from an already loaded map -void AAS_Create(char *aasfile); -//adds a face side to an area -void AAS_AddFaceSideToArea(tmp_face_t *tmpface, int side, tmp_area_t *tmparea); -//remvoes a face from an area -void AAS_RemoveFaceFromArea(tmp_face_t *tmpface, tmp_area_t *tmparea); -//allocate a tmp face -tmp_face_t *AAS_AllocTmpFace(void); -//free the tmp face -void AAS_FreeTmpFace(tmp_face_t *tmpface); -//allocate a tmp area -tmp_area_t *AAS_AllocTmpArea(void); -//free a tmp area -void AAS_FreeTmpArea(tmp_area_t *tmparea); -//allocate a tmp node -tmp_node_t *AAS_AllocTmpNode(void); -//free a tmp node -void AAS_FreeTmpNode(tmp_node_t *node); -//checks if an area is ok -void AAS_CheckArea(tmp_area_t *tmparea); -//flips the area faces where needed -void AAS_FlipAreaFaces(tmp_area_t *tmparea); -//returns true if the face is a gap seen from the given side -int AAS_GapFace(tmp_face_t *tmpface, int side); -//returns true if the face is a ground face -int AAS_GroundFace(tmp_face_t *tmpface); +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#define AREA_PORTAL 1 + +//temporary AAS face +typedef struct tmp_face_s +{ + int num; //face number + int planenum; //number of the plane the face is in + winding_t *winding; //winding of the face + struct tmp_area_s *frontarea; //area at the front of the face + struct tmp_area_s *backarea; //area at the back of the face + int faceflags; //flags of this face + int aasfacenum; //the number of the aas face used for this face + //double link list pointers for front and back area + struct tmp_face_s *prev[2], *next[2]; + //links in the list with faces + struct tmp_face_s *l_prev, *l_next; +} tmp_face_t; + +//temporary AAS area settings +typedef struct tmp_areasettings_s +{ + //could also add all kind of statistic fields + int contents; //contents of the area + int modelnum; //bsp model inside this area + int areaflags; //area flags + int presencetype; //how a bot can be present in this area + int numreachableareas; //number of reachable areas from this one + int firstreachablearea; //first reachable area in the reachable area index +} tmp_areasettings_t; + +//temporary AAS area +typedef struct tmp_area_s +{ + int areanum; //number of the area + struct tmp_face_s *tmpfaces; //the faces of the area + int presencetype; //presence type of the area + int contents; //area contents + int modelnum; //bsp model inside this area + int invalid; //true if the area is invalid + tmp_areasettings_t *settings; //area settings + struct tmp_area_s *mergedarea; //points to the new area after merging + //when mergedarea != 0 the area has only the + //seperating face of the merged areas + int aasareanum; //number of the aas area created for this tmp area + //links in the list with areas + struct tmp_area_s *l_prev, *l_next; +} tmp_area_t; + +//temporary AAS node +typedef struct tmp_node_s +{ + int planenum; //node plane number + struct tmp_area_s *tmparea; //points to an area if this node is an area + struct tmp_node_s *children[2]; //child nodes of this node +} tmp_node_t; + +#define NODEBUF_SIZE 128 +//node buffer +typedef struct tmp_nodebuf_s +{ + int numnodes; + struct tmp_nodebuf_s *next; + tmp_node_t nodes[NODEBUF_SIZE]; +} tmp_nodebuf_t; + +//the whole temorary AAS +typedef struct tmp_aas_s +{ + //faces + int numfaces; + int facenum; + tmp_face_t *faces; + //areas + int numareas; + int areanum; + tmp_area_t *areas; + //area settings + int numareasettings; + tmp_areasettings_t *areasettings; + //nodes + int numnodes; + tmp_node_t *nodes; + //node buffer + tmp_nodebuf_t *nodebuffer; +} tmp_aas_t; + +extern tmp_aas_t tmpaasworld; + +//creates a .AAS file with the given name from an already loaded map +void AAS_Create(char *aasfile); +//adds a face side to an area +void AAS_AddFaceSideToArea(tmp_face_t *tmpface, int side, tmp_area_t *tmparea); +//remvoes a face from an area +void AAS_RemoveFaceFromArea(tmp_face_t *tmpface, tmp_area_t *tmparea); +//allocate a tmp face +tmp_face_t *AAS_AllocTmpFace(void); +//free the tmp face +void AAS_FreeTmpFace(tmp_face_t *tmpface); +//allocate a tmp area +tmp_area_t *AAS_AllocTmpArea(void); +//free a tmp area +void AAS_FreeTmpArea(tmp_area_t *tmparea); +//allocate a tmp node +tmp_node_t *AAS_AllocTmpNode(void); +//free a tmp node +void AAS_FreeTmpNode(tmp_node_t *node); +//checks if an area is ok +void AAS_CheckArea(tmp_area_t *tmparea); +//flips the area faces where needed +void AAS_FlipAreaFaces(tmp_area_t *tmparea); +//returns true if the face is a gap seen from the given side +int AAS_GapFace(tmp_face_t *tmpface, int side); +//returns true if the face is a ground face +int AAS_GroundFace(tmp_face_t *tmpface); diff --git a/code/bspc/aas_edgemelting.c b/code/bspc/aas_edgemelting.c index e84f48b..e59f4f9 100755 --- a/code/bspc/aas_edgemelting.c +++ b/code/bspc/aas_edgemelting.c @@ -1,108 +1,108 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "qbsp.h" -#include "../botlib/aasfile.h" -#include "aas_create.h" - -//=========================================================================== -// try to melt the windings of the two faces -// FIXME: this is buggy -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_MeltFaceWinding(tmp_face_t *face1, tmp_face_t *face2) -{ - int i, n; - int splits = 0; - winding_t *w2, *neww; - plane_t *plane1; - -#ifdef DEBUG - if (!face1->winding) Error("face1 %d without winding", face1->num); - if (!face2->winding) Error("face2 %d without winding", face2->num); -#endif //DEBUG - w2 = face2->winding; - plane1 = &mapplanes[face1->planenum]; - for (i = 0; i < w2->numpoints; i++) - { - if (PointOnWinding(face1->winding, plane1->normal, plane1->dist, w2->p[i], &n)) - { - neww = AddWindingPoint(face1->winding, w2->p[i], n); - FreeWinding(face1->winding); - face1->winding = neww; - - splits++; - } //end if - } //end for - return splits; -} //end of the function AAS_MeltFaceWinding -//=========================================================================== -// melt the windings of the area faces -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_MeltFaceWindingsOfArea(tmp_area_t *tmparea) -{ - int side1, side2, num_windingsplits = 0; - tmp_face_t *face1, *face2; - - for (face1 = tmparea->tmpfaces; face1; face1 = face1->next[side1]) - { - side1 = face1->frontarea != tmparea; - for (face2 = tmparea->tmpfaces; face2; face2 = face2->next[side2]) - { - side2 = face2->frontarea != tmparea; - if (face1 == face2) continue; - num_windingsplits += AAS_MeltFaceWinding(face1, face2); - } //end for - } //end for - return num_windingsplits; -} //end of the function AAS_MeltFaceWindingsOfArea -//=========================================================================== -// melt the windings of the faces of all areas -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_MeltAreaFaceWindings(void) -{ - tmp_area_t *tmparea; - int num_windingsplits = 0; - - Log_Write("AAS_MeltAreaFaceWindings\r\n"); - qprintf("%6d edges melted", num_windingsplits); - //NOTE: first convex area (zero) is a dummy - for (tmparea = tmpaasworld.areas; tmparea; tmparea = tmparea->l_next) - { - num_windingsplits += AAS_MeltFaceWindingsOfArea(tmparea); - qprintf("\r%6d", num_windingsplits); - } //end for - qprintf("\n"); - Log_Write("%6d edges melted\r\n", num_windingsplits); -} //end of the function AAS_MeltAreaFaceWindings - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "qbsp.h" +#include "../botlib/aasfile.h" +#include "aas_create.h" + +//=========================================================================== +// try to melt the windings of the two faces +// FIXME: this is buggy +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_MeltFaceWinding(tmp_face_t *face1, tmp_face_t *face2) +{ + int i, n; + int splits = 0; + winding_t *w2, *neww; + plane_t *plane1; + +#ifdef DEBUG + if (!face1->winding) Error("face1 %d without winding", face1->num); + if (!face2->winding) Error("face2 %d without winding", face2->num); +#endif //DEBUG + w2 = face2->winding; + plane1 = &mapplanes[face1->planenum]; + for (i = 0; i < w2->numpoints; i++) + { + if (PointOnWinding(face1->winding, plane1->normal, plane1->dist, w2->p[i], &n)) + { + neww = AddWindingPoint(face1->winding, w2->p[i], n); + FreeWinding(face1->winding); + face1->winding = neww; + + splits++; + } //end if + } //end for + return splits; +} //end of the function AAS_MeltFaceWinding +//=========================================================================== +// melt the windings of the area faces +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_MeltFaceWindingsOfArea(tmp_area_t *tmparea) +{ + int side1, side2, num_windingsplits = 0; + tmp_face_t *face1, *face2; + + for (face1 = tmparea->tmpfaces; face1; face1 = face1->next[side1]) + { + side1 = face1->frontarea != tmparea; + for (face2 = tmparea->tmpfaces; face2; face2 = face2->next[side2]) + { + side2 = face2->frontarea != tmparea; + if (face1 == face2) continue; + num_windingsplits += AAS_MeltFaceWinding(face1, face2); + } //end for + } //end for + return num_windingsplits; +} //end of the function AAS_MeltFaceWindingsOfArea +//=========================================================================== +// melt the windings of the faces of all areas +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_MeltAreaFaceWindings(void) +{ + tmp_area_t *tmparea; + int num_windingsplits = 0; + + Log_Write("AAS_MeltAreaFaceWindings\r\n"); + qprintf("%6d edges melted", num_windingsplits); + //NOTE: first convex area (zero) is a dummy + for (tmparea = tmpaasworld.areas; tmparea; tmparea = tmparea->l_next) + { + num_windingsplits += AAS_MeltFaceWindingsOfArea(tmparea); + qprintf("\r%6d", num_windingsplits); + } //end for + qprintf("\n"); + Log_Write("%6d edges melted\r\n", num_windingsplits); +} //end of the function AAS_MeltAreaFaceWindings + diff --git a/code/bspc/aas_edgemelting.h b/code/bspc/aas_edgemelting.h index 4c03e97..a296cb6 100755 --- a/code/bspc/aas_edgemelting.h +++ b/code/bspc/aas_edgemelting.h @@ -1,24 +1,24 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -void AAS_MeltAreaFaceWindings(void); - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +void AAS_MeltAreaFaceWindings(void); + diff --git a/code/bspc/aas_facemerging.c b/code/bspc/aas_facemerging.c index bf170de..2fb2903 100755 --- a/code/bspc/aas_facemerging.c +++ b/code/bspc/aas_facemerging.c @@ -1,282 +1,282 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "qbsp.h" -#include "../botlib/aasfile.h" -#include "aas_create.h" - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_TryMergeFaces(tmp_face_t *face1, tmp_face_t *face2) -{ - winding_t *neww; - -#ifdef DEBUG - if (!face1->winding) Error("face1 %d without winding", face1->num); - if (!face2->winding) Error("face2 %d without winding", face2->num); -#endif //DEBUG - // - if (face1->faceflags != face2->faceflags) return false; - //NOTE: if the front or back area is zero this doesn't mean there's - //a real area. It means there's solid at that side of the face - //if both faces have the same front area - if (face1->frontarea == face2->frontarea) - { - //if both faces have the same back area - if (face1->backarea == face2->backarea) - { - //if the faces are in the same plane - if (face1->planenum == face2->planenum) - { - //if they have both a front and a back area (no solid on either side) - if (face1->frontarea && face1->backarea) - { - neww = MergeWindings(face1->winding, face2->winding, - mapplanes[face1->planenum].normal); - } //end if - else - { - //this function is to be found in l_poly.c - neww = TryMergeWinding(face1->winding, face2->winding, - mapplanes[face1->planenum].normal); - } //end else - if (neww) - { - FreeWinding(face1->winding); - face1->winding = neww; - if (face2->frontarea) AAS_RemoveFaceFromArea(face2, face2->frontarea); - if (face2->backarea) AAS_RemoveFaceFromArea(face2, face2->backarea); - AAS_FreeTmpFace(face2); - return true; - } //end if - } //end if - else if ((face1->planenum & ~1) == (face2->planenum & ~1)) - { - Log_Write("face %d and %d, same front and back area but flipped planes\r\n", - face1->num, face2->num); - } //end if - } //end if - } //end if - return false; -} //end of the function AAS_TryMergeFaces -/* -int AAS_TryMergeFaces(tmp_face_t *face1, tmp_face_t *face2) -{ - winding_t *neww; - -#ifdef DEBUG - if (!face1->winding) Error("face1 %d without winding", face1->num); - if (!face2->winding) Error("face2 %d without winding", face2->num); -#endif //DEBUG - //if the faces are in the same plane - if ((face1->planenum & ~1) != (face2->planenum & ~1)) return false; -// if (face1->planenum != face2->planenum) return false; - //NOTE: if the front or back area is zero this doesn't mean there's - //a real area. It means there's solid at that side of the face - //if both faces have the same front area - if (face1->frontarea != face2->frontarea || - face1->backarea != face2->backarea) - { - if (!face1->frontarea || !face1->backarea || - !face2->frontarea || !face2->backarea) return false; - else if (face1->frontarea != face2->backarea || - face1->backarea != face2->frontarea) return false; -// return false; - } //end if - //this function is to be found in l_poly.c - neww = TryMergeWinding(face1->winding, face2->winding, - mapplanes[face1->planenum].normal); - if (!neww) return false; - // - FreeWinding(face1->winding); - face1->winding = neww; - //remove face2 - if (face2->frontarea) - AAS_RemoveFaceFromArea(face2, &tmpaasworld.areas[face2->frontarea]); - if (face2->backarea) - AAS_RemoveFaceFromArea(face2, &tmpaasworld.areas[face2->backarea]); - return true; -} //end of the function AAS_TryMergeFaces*/ -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_MergeAreaFaces(void) -{ - int num_facemerges = 0; - int side1, side2, restart; - tmp_area_t *tmparea, *lasttmparea; - tmp_face_t *face1, *face2; - - Log_Write("AAS_MergeAreaFaces\r\n"); - qprintf("%6d face merges", num_facemerges); - //NOTE: first convex area is a dummy - lasttmparea = tmpaasworld.areas; - for (tmparea = tmpaasworld.areas; tmparea; tmparea = tmparea->l_next) - { - restart = false; - // - if (tmparea->invalid) continue; - // - for (face1 = tmparea->tmpfaces; face1; face1 = face1->next[side1]) - { - side1 = face1->frontarea != tmparea; - for (face2 = face1->next[side1]; face2; face2 = face2->next[side2]) - { - side2 = face2->frontarea != tmparea; - //if succesfully merged - if (AAS_TryMergeFaces(face1, face2)) - { - //start over again after merging two faces - restart = true; - num_facemerges++; - qprintf("\r%6d", num_facemerges); - AAS_CheckArea(tmparea); - break; - } //end if - } //end for - if (restart) - { - tmparea = lasttmparea; - break; - } //end if - } //end for - lasttmparea = tmparea; - } //end for - qprintf("\n"); - Log_Write("%6d face merges\r\n", num_facemerges); -} //end of the function AAS_MergeAreaFaces -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_MergePlaneFaces(tmp_area_t *tmparea, int planenum) -{ - tmp_face_t *face1, *face2, *nextface2; - winding_t *neww; - int side1, side2; - - for (face1 = tmparea->tmpfaces; face1; face1 = face1->next[side1]) - { - side1 = face1->frontarea != tmparea; - if (face1->planenum != planenum) continue; - // - for (face2 = face1->next[side1]; face2; face2 = nextface2) - { - side2 = face2->frontarea != tmparea; - nextface2 = face2->next[side2]; - // - if ((face2->planenum & ~1) != (planenum & ~1)) continue; - // - neww = MergeWindings(face1->winding, face2->winding, - mapplanes[face1->planenum].normal); - FreeWinding(face1->winding); - face1->winding = neww; - if (face2->frontarea) AAS_RemoveFaceFromArea(face2, face2->frontarea); - if (face2->backarea) AAS_RemoveFaceFromArea(face2, face2->backarea); - AAS_FreeTmpFace(face2); - // - nextface2 = face1->next[side1]; - } //end for - } //end for -} //end of the function AAS_MergePlaneFaces -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_CanMergePlaneFaces(tmp_area_t *tmparea, int planenum) -{ - tmp_area_t *frontarea, *backarea; - tmp_face_t *face1; - int side1, merge, faceflags; - - frontarea = backarea = NULL; - merge = false; - for (face1 = tmparea->tmpfaces; face1; face1 = face1->next[side1]) - { - side1 = face1->frontarea != tmparea; - if ((face1->planenum & ~1) != (planenum & ~1)) continue; - if (!frontarea && !backarea) - { - frontarea = face1->frontarea; - backarea = face1->backarea; - faceflags = face1->faceflags; - } //end if - else - { - if (frontarea != face1->frontarea) return false; - if (backarea != face1->backarea) return false; - if (faceflags != face1->faceflags) return false; - merge = true; - } //end else - } //end for - return merge; -} //end of the function AAS_CanMergePlaneFaces -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_MergeAreaPlaneFaces(void) -{ - int num_facemerges = 0; - int side1; - tmp_area_t *tmparea, *nexttmparea; - tmp_face_t *face1; - - Log_Write("AAS_MergePlaneFaces\r\n"); - qprintf("%6d plane face merges", num_facemerges); - //NOTE: first convex area is a dummy - for (tmparea = tmpaasworld.areas; tmparea; tmparea = nexttmparea) - { - nexttmparea = tmparea->l_next; - // - if (tmparea->invalid) continue; - // - for (face1 = tmparea->tmpfaces; face1; face1 = face1->next[side1]) - { - side1 = face1->frontarea != tmparea; - // - if (AAS_CanMergePlaneFaces(tmparea, face1->planenum)) - { - AAS_MergePlaneFaces(tmparea, face1->planenum); - nexttmparea = tmparea; - num_facemerges++; - qprintf("\r%6d", num_facemerges); - break; - } //end if - } //end for - } //end for - qprintf("\n"); - Log_Write("%6d plane face merges\r\n", num_facemerges); -} //end of the function AAS_MergeAreaPlaneFaces +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "qbsp.h" +#include "../botlib/aasfile.h" +#include "aas_create.h" + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_TryMergeFaces(tmp_face_t *face1, tmp_face_t *face2) +{ + winding_t *neww; + +#ifdef DEBUG + if (!face1->winding) Error("face1 %d without winding", face1->num); + if (!face2->winding) Error("face2 %d without winding", face2->num); +#endif //DEBUG + // + if (face1->faceflags != face2->faceflags) return false; + //NOTE: if the front or back area is zero this doesn't mean there's + //a real area. It means there's solid at that side of the face + //if both faces have the same front area + if (face1->frontarea == face2->frontarea) + { + //if both faces have the same back area + if (face1->backarea == face2->backarea) + { + //if the faces are in the same plane + if (face1->planenum == face2->planenum) + { + //if they have both a front and a back area (no solid on either side) + if (face1->frontarea && face1->backarea) + { + neww = MergeWindings(face1->winding, face2->winding, + mapplanes[face1->planenum].normal); + } //end if + else + { + //this function is to be found in l_poly.c + neww = TryMergeWinding(face1->winding, face2->winding, + mapplanes[face1->planenum].normal); + } //end else + if (neww) + { + FreeWinding(face1->winding); + face1->winding = neww; + if (face2->frontarea) AAS_RemoveFaceFromArea(face2, face2->frontarea); + if (face2->backarea) AAS_RemoveFaceFromArea(face2, face2->backarea); + AAS_FreeTmpFace(face2); + return true; + } //end if + } //end if + else if ((face1->planenum & ~1) == (face2->planenum & ~1)) + { + Log_Write("face %d and %d, same front and back area but flipped planes\r\n", + face1->num, face2->num); + } //end if + } //end if + } //end if + return false; +} //end of the function AAS_TryMergeFaces +/* +int AAS_TryMergeFaces(tmp_face_t *face1, tmp_face_t *face2) +{ + winding_t *neww; + +#ifdef DEBUG + if (!face1->winding) Error("face1 %d without winding", face1->num); + if (!face2->winding) Error("face2 %d without winding", face2->num); +#endif //DEBUG + //if the faces are in the same plane + if ((face1->planenum & ~1) != (face2->planenum & ~1)) return false; +// if (face1->planenum != face2->planenum) return false; + //NOTE: if the front or back area is zero this doesn't mean there's + //a real area. It means there's solid at that side of the face + //if both faces have the same front area + if (face1->frontarea != face2->frontarea || + face1->backarea != face2->backarea) + { + if (!face1->frontarea || !face1->backarea || + !face2->frontarea || !face2->backarea) return false; + else if (face1->frontarea != face2->backarea || + face1->backarea != face2->frontarea) return false; +// return false; + } //end if + //this function is to be found in l_poly.c + neww = TryMergeWinding(face1->winding, face2->winding, + mapplanes[face1->planenum].normal); + if (!neww) return false; + // + FreeWinding(face1->winding); + face1->winding = neww; + //remove face2 + if (face2->frontarea) + AAS_RemoveFaceFromArea(face2, &tmpaasworld.areas[face2->frontarea]); + if (face2->backarea) + AAS_RemoveFaceFromArea(face2, &tmpaasworld.areas[face2->backarea]); + return true; +} //end of the function AAS_TryMergeFaces*/ +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_MergeAreaFaces(void) +{ + int num_facemerges = 0; + int side1, side2, restart; + tmp_area_t *tmparea, *lasttmparea; + tmp_face_t *face1, *face2; + + Log_Write("AAS_MergeAreaFaces\r\n"); + qprintf("%6d face merges", num_facemerges); + //NOTE: first convex area is a dummy + lasttmparea = tmpaasworld.areas; + for (tmparea = tmpaasworld.areas; tmparea; tmparea = tmparea->l_next) + { + restart = false; + // + if (tmparea->invalid) continue; + // + for (face1 = tmparea->tmpfaces; face1; face1 = face1->next[side1]) + { + side1 = face1->frontarea != tmparea; + for (face2 = face1->next[side1]; face2; face2 = face2->next[side2]) + { + side2 = face2->frontarea != tmparea; + //if succesfully merged + if (AAS_TryMergeFaces(face1, face2)) + { + //start over again after merging two faces + restart = true; + num_facemerges++; + qprintf("\r%6d", num_facemerges); + AAS_CheckArea(tmparea); + break; + } //end if + } //end for + if (restart) + { + tmparea = lasttmparea; + break; + } //end if + } //end for + lasttmparea = tmparea; + } //end for + qprintf("\n"); + Log_Write("%6d face merges\r\n", num_facemerges); +} //end of the function AAS_MergeAreaFaces +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_MergePlaneFaces(tmp_area_t *tmparea, int planenum) +{ + tmp_face_t *face1, *face2, *nextface2; + winding_t *neww; + int side1, side2; + + for (face1 = tmparea->tmpfaces; face1; face1 = face1->next[side1]) + { + side1 = face1->frontarea != tmparea; + if (face1->planenum != planenum) continue; + // + for (face2 = face1->next[side1]; face2; face2 = nextface2) + { + side2 = face2->frontarea != tmparea; + nextface2 = face2->next[side2]; + // + if ((face2->planenum & ~1) != (planenum & ~1)) continue; + // + neww = MergeWindings(face1->winding, face2->winding, + mapplanes[face1->planenum].normal); + FreeWinding(face1->winding); + face1->winding = neww; + if (face2->frontarea) AAS_RemoveFaceFromArea(face2, face2->frontarea); + if (face2->backarea) AAS_RemoveFaceFromArea(face2, face2->backarea); + AAS_FreeTmpFace(face2); + // + nextface2 = face1->next[side1]; + } //end for + } //end for +} //end of the function AAS_MergePlaneFaces +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_CanMergePlaneFaces(tmp_area_t *tmparea, int planenum) +{ + tmp_area_t *frontarea, *backarea; + tmp_face_t *face1; + int side1, merge, faceflags; + + frontarea = backarea = NULL; + merge = false; + for (face1 = tmparea->tmpfaces; face1; face1 = face1->next[side1]) + { + side1 = face1->frontarea != tmparea; + if ((face1->planenum & ~1) != (planenum & ~1)) continue; + if (!frontarea && !backarea) + { + frontarea = face1->frontarea; + backarea = face1->backarea; + faceflags = face1->faceflags; + } //end if + else + { + if (frontarea != face1->frontarea) return false; + if (backarea != face1->backarea) return false; + if (faceflags != face1->faceflags) return false; + merge = true; + } //end else + } //end for + return merge; +} //end of the function AAS_CanMergePlaneFaces +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_MergeAreaPlaneFaces(void) +{ + int num_facemerges = 0; + int side1; + tmp_area_t *tmparea, *nexttmparea; + tmp_face_t *face1; + + Log_Write("AAS_MergePlaneFaces\r\n"); + qprintf("%6d plane face merges", num_facemerges); + //NOTE: first convex area is a dummy + for (tmparea = tmpaasworld.areas; tmparea; tmparea = nexttmparea) + { + nexttmparea = tmparea->l_next; + // + if (tmparea->invalid) continue; + // + for (face1 = tmparea->tmpfaces; face1; face1 = face1->next[side1]) + { + side1 = face1->frontarea != tmparea; + // + if (AAS_CanMergePlaneFaces(tmparea, face1->planenum)) + { + AAS_MergePlaneFaces(tmparea, face1->planenum); + nexttmparea = tmparea; + num_facemerges++; + qprintf("\r%6d", num_facemerges); + break; + } //end if + } //end for + } //end for + qprintf("\n"); + Log_Write("%6d plane face merges\r\n", num_facemerges); +} //end of the function AAS_MergeAreaPlaneFaces diff --git a/code/bspc/aas_facemerging.h b/code/bspc/aas_facemerging.h index 5a81735..b2e02e5 100755 --- a/code/bspc/aas_facemerging.h +++ b/code/bspc/aas_facemerging.h @@ -1,24 +1,24 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -void AAS_MergeAreaFaces(void); -void AAS_MergeAreaPlaneFaces(void); +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +void AAS_MergeAreaFaces(void); +void AAS_MergeAreaPlaneFaces(void); diff --git a/code/bspc/aas_file.c b/code/bspc/aas_file.c index 9f41639..9dc6b12 100755 --- a/code/bspc/aas_file.c +++ b/code/bspc/aas_file.c @@ -1,549 +1,549 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "qbsp.h" -#include "../botlib/aasfile.h" -#include "aas_file.h" -#include "aas_store.h" -#include "aas_create.h" - -#define AAS_Error Error - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_SwapAASData(void) -{ - int i, j; - //bounding boxes - for (i = 0; i < aasworld.numbboxes; i++) - { - aasworld.bboxes[i].presencetype = LittleLong(aasworld.bboxes[i].presencetype); - aasworld.bboxes[i].flags = LittleLong(aasworld.bboxes[i].flags); - for (j = 0; j < 3; j++) - { - aasworld.bboxes[i].mins[j] = LittleLong(aasworld.bboxes[i].mins[j]); - aasworld.bboxes[i].maxs[j] = LittleLong(aasworld.bboxes[i].maxs[j]); - } //end for - } //end for - //vertexes - for (i = 0; i < aasworld.numvertexes; i++) - { - for (j = 0; j < 3; j++) - aasworld.vertexes[i][j] = LittleFloat(aasworld.vertexes[i][j]); - } //end for - //planes - for (i = 0; i < aasworld.numplanes; i++) - { - for (j = 0; j < 3; j++) - aasworld.planes[i].normal[j] = LittleFloat(aasworld.planes[i].normal[j]); - aasworld.planes[i].dist = LittleFloat(aasworld.planes[i].dist); - aasworld.planes[i].type = LittleLong(aasworld.planes[i].type); - } //end for - //edges - for (i = 0; i < aasworld.numedges; i++) - { - aasworld.edges[i].v[0] = LittleLong(aasworld.edges[i].v[0]); - aasworld.edges[i].v[1] = LittleLong(aasworld.edges[i].v[1]); - } //end for - //edgeindex - for (i = 0; i < aasworld.edgeindexsize; i++) - { - aasworld.edgeindex[i] = LittleLong(aasworld.edgeindex[i]); - } //end for - //faces - for (i = 0; i < aasworld.numfaces; i++) - { - aasworld.faces[i].planenum = LittleLong(aasworld.faces[i].planenum); - aasworld.faces[i].faceflags = LittleLong(aasworld.faces[i].faceflags); - aasworld.faces[i].numedges = LittleLong(aasworld.faces[i].numedges); - aasworld.faces[i].firstedge = LittleLong(aasworld.faces[i].firstedge); - aasworld.faces[i].frontarea = LittleLong(aasworld.faces[i].frontarea); - aasworld.faces[i].backarea = LittleLong(aasworld.faces[i].backarea); - } //end for - //face index - for (i = 0; i < aasworld.faceindexsize; i++) - { - aasworld.faceindex[i] = LittleLong(aasworld.faceindex[i]); - } //end for - //convex areas - for (i = 0; i < aasworld.numareas; i++) - { - aasworld.areas[i].areanum = LittleLong(aasworld.areas[i].areanum); - aasworld.areas[i].numfaces = LittleLong(aasworld.areas[i].numfaces); - aasworld.areas[i].firstface = LittleLong(aasworld.areas[i].firstface); - for (j = 0; j < 3; j++) - { - aasworld.areas[i].mins[j] = LittleFloat(aasworld.areas[i].mins[j]); - aasworld.areas[i].maxs[j] = LittleFloat(aasworld.areas[i].maxs[j]); - aasworld.areas[i].center[j] = LittleFloat(aasworld.areas[i].center[j]); - } //end for - } //end for - //area settings - for (i = 0; i < aasworld.numareasettings; i++) - { - aasworld.areasettings[i].contents = LittleLong(aasworld.areasettings[i].contents); - aasworld.areasettings[i].areaflags = LittleLong(aasworld.areasettings[i].areaflags); - aasworld.areasettings[i].presencetype = LittleLong(aasworld.areasettings[i].presencetype); - aasworld.areasettings[i].cluster = LittleLong(aasworld.areasettings[i].cluster); - aasworld.areasettings[i].clusterareanum = LittleLong(aasworld.areasettings[i].clusterareanum); - aasworld.areasettings[i].numreachableareas = LittleLong(aasworld.areasettings[i].numreachableareas); - aasworld.areasettings[i].firstreachablearea = LittleLong(aasworld.areasettings[i].firstreachablearea); - } //end for - //area reachability - for (i = 0; i < aasworld.reachabilitysize; i++) - { - aasworld.reachability[i].areanum = LittleLong(aasworld.reachability[i].areanum); - aasworld.reachability[i].facenum = LittleLong(aasworld.reachability[i].facenum); - aasworld.reachability[i].edgenum = LittleLong(aasworld.reachability[i].edgenum); - for (j = 0; j < 3; j++) - { - aasworld.reachability[i].start[j] = LittleFloat(aasworld.reachability[i].start[j]); - aasworld.reachability[i].end[j] = LittleFloat(aasworld.reachability[i].end[j]); - } //end for - aasworld.reachability[i].traveltype = LittleLong(aasworld.reachability[i].traveltype); - aasworld.reachability[i].traveltime = LittleShort(aasworld.reachability[i].traveltime); - } //end for - //nodes - for (i = 0; i < aasworld.numnodes; i++) - { - aasworld.nodes[i].planenum = LittleLong(aasworld.nodes[i].planenum); - aasworld.nodes[i].children[0] = LittleLong(aasworld.nodes[i].children[0]); - aasworld.nodes[i].children[1] = LittleLong(aasworld.nodes[i].children[1]); - } //end for - //cluster portals - for (i = 0; i < aasworld.numportals; i++) - { - aasworld.portals[i].areanum = LittleLong(aasworld.portals[i].areanum); - aasworld.portals[i].frontcluster = LittleLong(aasworld.portals[i].frontcluster); - aasworld.portals[i].backcluster = LittleLong(aasworld.portals[i].backcluster); - aasworld.portals[i].clusterareanum[0] = LittleLong(aasworld.portals[i].clusterareanum[0]); - aasworld.portals[i].clusterareanum[1] = LittleLong(aasworld.portals[i].clusterareanum[1]); - } //end for - //cluster portal index - for (i = 0; i < aasworld.portalindexsize; i++) - { - aasworld.portalindex[i] = LittleLong(aasworld.portalindex[i]); - } //end for - //cluster - for (i = 0; i < aasworld.numclusters; i++) - { - aasworld.clusters[i].numareas = LittleLong(aasworld.clusters[i].numareas); - aasworld.clusters[i].numportals = LittleLong(aasworld.clusters[i].numportals); - aasworld.clusters[i].firstportal = LittleLong(aasworld.clusters[i].firstportal); - } //end for -} //end of the function AAS_SwapAASData -//=========================================================================== -// dump the current loaded aas file -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_DumpAASData(void) -{ - /* - if (aasworld.vertexes) FreeMemory(aasworld.vertexes); - aasworld.vertexes = NULL; - if (aasworld.planes) FreeMemory(aasworld.planes); - aasworld.planes = NULL; - if (aasworld.edges) FreeMemory(aasworld.edges); - aasworld.edges = NULL; - if (aasworld.edgeindex) FreeMemory(aasworld.edgeindex); - aasworld.edgeindex = NULL; - if (aasworld.faces) FreeMemory(aasworld.faces); - aasworld.faces = NULL; - if (aasworld.faceindex) FreeMemory(aasworld.faceindex); - aasworld.faceindex = NULL; - if (aasworld.areas) FreeMemory(aasworld.areas); - aasworld.areas = NULL; - if (aasworld.areasettings) FreeMemory(aasworld.areasettings); - aasworld.areasettings = NULL; - if (aasworld.reachability) FreeMemory(aasworld.reachability); - aasworld.reachability = NULL; - */ - aasworld.loaded = false; -} //end of the function AAS_DumpAASData -//=========================================================================== -// allocate memory and read a lump of a AAS file -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -char *AAS_LoadAASLump(FILE *fp, int offset, int length, void *buf) -{ - if (!length) - { - printf("lump size 0\n"); - return buf; - } //end if - //seek to the data - if (fseek(fp, offset, SEEK_SET)) - { - AAS_Error("can't seek to lump\n"); - AAS_DumpAASData(); - fclose(fp); - return 0; - } //end if - //allocate memory - if (!buf) buf = (void *) GetClearedMemory(length); - //read the data - if (fread((char *) buf, 1, length, fp) != length) - { - AAS_Error("can't read lump\n"); - FreeMemory(buf); - AAS_DumpAASData(); - fclose(fp); - return NULL; - } //end if - return buf; -} //end of the function AAS_LoadAASLump -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_DData(unsigned char *data, int size) -{ - int i; - - for (i = 0; i < size; i++) - { - data[i] ^= (unsigned char) i * 119; - } //end for -} //end of the function AAS_DData -//=========================================================================== -// load an aas file -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean AAS_LoadAASFile(char *filename, int fpoffset, int fplength) -{ - FILE *fp; - aas_header_t header; - int offset, length; - - //dump current loaded aas file - AAS_DumpAASData(); - //open the file - fp = fopen(filename, "rb"); - if (!fp) - { - AAS_Error("can't open %s\n", filename); - return false; - } //end if - //seek to the correct position (in the pak file) - if (fseek(fp, fpoffset, SEEK_SET)) - { - AAS_Error("can't seek to file %s\n"); - fclose(fp); - return false; - } //end if - //read the header - if (fread(&header, sizeof(aas_header_t), 1, fp) != 1) - { - AAS_Error("can't read header of file %s\n", filename); - fclose(fp); - return false; - } //end if - //check header identification - header.ident = LittleLong(header.ident); - if (header.ident != AASID) - { - AAS_Error("%s is not an AAS file\n", filename); - fclose(fp); - return false; - } //end if - //check the version - header.version = LittleLong(header.version); - if (header.version != AASVERSION_OLD && header.version != AASVERSION) - { - AAS_Error("%s is version %i, not %i\n", filename, header.version, AASVERSION); - fclose(fp); - return false; - } //end if - // - if (header.version == AASVERSION) - { - AAS_DData((unsigned char *) &header + 8, sizeof(aas_header_t) - 8); - } //end if - aasworld.bspchecksum = LittleLong(header.bspchecksum); - //load the lumps: - //bounding boxes - offset = fpoffset + LittleLong(header.lumps[AASLUMP_BBOXES].fileofs); - length = LittleLong(header.lumps[AASLUMP_BBOXES].filelen); - aasworld.bboxes = (aas_bbox_t *) AAS_LoadAASLump(fp, offset, length, aasworld.bboxes); - if (!aasworld.bboxes) return false; - aasworld.numbboxes = length / sizeof(aas_bbox_t); - //vertexes - offset = fpoffset + LittleLong(header.lumps[AASLUMP_VERTEXES].fileofs); - length = LittleLong(header.lumps[AASLUMP_VERTEXES].filelen); - aasworld.vertexes = (aas_vertex_t *) AAS_LoadAASLump(fp, offset, length, aasworld.vertexes); - if (!aasworld.vertexes) return false; - aasworld.numvertexes = length / sizeof(aas_vertex_t); - //planes - offset = fpoffset + LittleLong(header.lumps[AASLUMP_PLANES].fileofs); - length = LittleLong(header.lumps[AASLUMP_PLANES].filelen); - aasworld.planes = (aas_plane_t *) AAS_LoadAASLump(fp, offset, length, aasworld.planes); - if (!aasworld.planes) return false; - aasworld.numplanes = length / sizeof(aas_plane_t); - //edges - offset = fpoffset + LittleLong(header.lumps[AASLUMP_EDGES].fileofs); - length = LittleLong(header.lumps[AASLUMP_EDGES].filelen); - aasworld.edges = (aas_edge_t *) AAS_LoadAASLump(fp, offset, length, aasworld.edges); - if (!aasworld.edges) return false; - aasworld.numedges = length / sizeof(aas_edge_t); - //edgeindex - offset = fpoffset + LittleLong(header.lumps[AASLUMP_EDGEINDEX].fileofs); - length = LittleLong(header.lumps[AASLUMP_EDGEINDEX].filelen); - aasworld.edgeindex = (aas_edgeindex_t *) AAS_LoadAASLump(fp, offset, length, aasworld.edgeindex); - if (!aasworld.edgeindex) return false; - aasworld.edgeindexsize = length / sizeof(aas_edgeindex_t); - //faces - offset = fpoffset + LittleLong(header.lumps[AASLUMP_FACES].fileofs); - length = LittleLong(header.lumps[AASLUMP_FACES].filelen); - aasworld.faces = (aas_face_t *) AAS_LoadAASLump(fp, offset, length, aasworld.faces); - if (!aasworld.faces) return false; - aasworld.numfaces = length / sizeof(aas_face_t); - //faceindex - offset = fpoffset + LittleLong(header.lumps[AASLUMP_FACEINDEX].fileofs); - length = LittleLong(header.lumps[AASLUMP_FACEINDEX].filelen); - aasworld.faceindex = (aas_faceindex_t *) AAS_LoadAASLump(fp, offset, length, aasworld.faceindex); - if (!aasworld.faceindex) return false; - aasworld.faceindexsize = length / sizeof(int); - //convex areas - offset = fpoffset + LittleLong(header.lumps[AASLUMP_AREAS].fileofs); - length = LittleLong(header.lumps[AASLUMP_AREAS].filelen); - aasworld.areas = (aas_area_t *) AAS_LoadAASLump(fp, offset, length, aasworld.areas); - if (!aasworld.areas) return false; - aasworld.numareas = length / sizeof(aas_area_t); - //area settings - offset = fpoffset + LittleLong(header.lumps[AASLUMP_AREASETTINGS].fileofs); - length = LittleLong(header.lumps[AASLUMP_AREASETTINGS].filelen); - aasworld.areasettings = (aas_areasettings_t *) AAS_LoadAASLump(fp, offset, length, aasworld.areasettings); - if (!aasworld.areasettings) return false; - aasworld.numareasettings = length / sizeof(aas_areasettings_t); - //reachability list - offset = fpoffset + LittleLong(header.lumps[AASLUMP_REACHABILITY].fileofs); - length = LittleLong(header.lumps[AASLUMP_REACHABILITY].filelen); - aasworld.reachability = (aas_reachability_t *) AAS_LoadAASLump(fp, offset, length, aasworld.reachability); - if (length && !aasworld.reachability) return false; - aasworld.reachabilitysize = length / sizeof(aas_reachability_t); - //nodes - offset = fpoffset + LittleLong(header.lumps[AASLUMP_NODES].fileofs); - length = LittleLong(header.lumps[AASLUMP_NODES].filelen); - aasworld.nodes = (aas_node_t *) AAS_LoadAASLump(fp, offset, length, aasworld.nodes); - if (!aasworld.nodes) return false; - aasworld.numnodes = length / sizeof(aas_node_t); - //cluster portals - offset = fpoffset + LittleLong(header.lumps[AASLUMP_PORTALS].fileofs); - length = LittleLong(header.lumps[AASLUMP_PORTALS].filelen); - aasworld.portals = (aas_portal_t *) AAS_LoadAASLump(fp, offset, length, aasworld.portals); - if (length && !aasworld.portals) return false; - aasworld.numportals = length / sizeof(aas_portal_t); - //cluster portal index - offset = fpoffset + LittleLong(header.lumps[AASLUMP_PORTALINDEX].fileofs); - length = LittleLong(header.lumps[AASLUMP_PORTALINDEX].filelen); - aasworld.portalindex = (aas_portalindex_t *) AAS_LoadAASLump(fp, offset, length, aasworld.portalindex); - if (length && !aasworld.portalindex) return false; - aasworld.portalindexsize = length / sizeof(aas_portalindex_t); - //clusters - offset = fpoffset + LittleLong(header.lumps[AASLUMP_CLUSTERS].fileofs); - length = LittleLong(header.lumps[AASLUMP_CLUSTERS].filelen); - aasworld.clusters = (aas_cluster_t *) AAS_LoadAASLump(fp, offset, length, aasworld.clusters); - if (length && !aasworld.clusters) return false; - aasworld.numclusters = length / sizeof(aas_cluster_t); - //swap everything - AAS_SwapAASData(); - //aas file is loaded - aasworld.loaded = true; - //close the file - fclose(fp); - return true; -} //end of the function AAS_LoadAASFile -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_WriteAASLump(FILE *fp, aas_header_t *h, int lumpnum, void *data, int length) -{ - aas_lump_t *lump; - - lump = &h->lumps[lumpnum]; - - lump->fileofs = LittleLong(ftell(fp)); - lump->filelen = LittleLong(length); - - if (length > 0) - { - if (fwrite(data, length, 1, fp) < 1) - { - Log_Print("error writing lump %s\n", lumpnum); - fclose(fp); - return false; - } //end if - } //end if - return true; -} //end of the function AAS_WriteAASLump -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_ShowNumReachabilities(int tt, char *name) -{ - int i, num; - - num = 0; - for (i = 0; i < aasworld.reachabilitysize; i++) - { - if ((aasworld.reachability[i].traveltype & TRAVELTYPE_MASK) == tt) - num++; - } //end for - Log_Print("%6d %s\n", num, name); -} //end of the function AAS_ShowNumReachabilities -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_ShowTotals(void) -{ - Log_Print("numvertexes = %d\r\n", aasworld.numvertexes); - Log_Print("numplanes = %d\r\n", aasworld.numplanes); - Log_Print("numedges = %d\r\n", aasworld.numedges); - Log_Print("edgeindexsize = %d\r\n", aasworld.edgeindexsize); - Log_Print("numfaces = %d\r\n", aasworld.numfaces); - Log_Print("faceindexsize = %d\r\n", aasworld.faceindexsize); - Log_Print("numareas = %d\r\n", aasworld.numareas); - Log_Print("numareasettings = %d\r\n", aasworld.numareasettings); - Log_Print("reachabilitysize = %d\r\n", aasworld.reachabilitysize); - Log_Print("numnodes = %d\r\n", aasworld.numnodes); - Log_Print("numportals = %d\r\n", aasworld.numportals); - Log_Print("portalindexsize = %d\r\n", aasworld.portalindexsize); - Log_Print("numclusters = %d\r\n", aasworld.numclusters); - AAS_ShowNumReachabilities(TRAVEL_WALK, "walk"); - AAS_ShowNumReachabilities(TRAVEL_CROUCH, "crouch"); - AAS_ShowNumReachabilities(TRAVEL_BARRIERJUMP, "barrier jump"); - AAS_ShowNumReachabilities(TRAVEL_JUMP, "jump"); - AAS_ShowNumReachabilities(TRAVEL_LADDER, "ladder"); - AAS_ShowNumReachabilities(TRAVEL_WALKOFFLEDGE, "walk off ledge"); - AAS_ShowNumReachabilities(TRAVEL_SWIM, "swim"); - AAS_ShowNumReachabilities(TRAVEL_WATERJUMP, "water jump"); - AAS_ShowNumReachabilities(TRAVEL_TELEPORT, "teleport"); - AAS_ShowNumReachabilities(TRAVEL_ELEVATOR, "elevator"); - AAS_ShowNumReachabilities(TRAVEL_ROCKETJUMP, "rocket jump"); - AAS_ShowNumReachabilities(TRAVEL_BFGJUMP, "bfg jump"); - AAS_ShowNumReachabilities(TRAVEL_GRAPPLEHOOK, "grapple hook"); - AAS_ShowNumReachabilities(TRAVEL_DOUBLEJUMP, "double jump"); - AAS_ShowNumReachabilities(TRAVEL_RAMPJUMP, "ramp jump"); - AAS_ShowNumReachabilities(TRAVEL_STRAFEJUMP, "strafe jump"); - AAS_ShowNumReachabilities(TRAVEL_JUMPPAD, "jump pad"); - AAS_ShowNumReachabilities(TRAVEL_FUNCBOB, "func bob"); -} //end of the function AAS_ShowTotals -//=========================================================================== -// aas data is useless after writing to file because it is byte swapped -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean AAS_WriteAASFile(char *filename) -{ - aas_header_t header; - FILE *fp; - - Log_Print("writing %s\n", filename); - AAS_ShowTotals(); - //swap the aas data - AAS_SwapAASData(); - //initialize the file header - memset(&header, 0, sizeof(aas_header_t)); - header.ident = LittleLong(AASID); - header.version = LittleLong(AASVERSION); - header.bspchecksum = LittleLong(aasworld.bspchecksum); - //open a new file - fp = fopen(filename, "wb"); - if (!fp) - { - Log_Print("error opening %s\n", filename); - return false; - } //end if - //write the header - if (fwrite(&header, sizeof(aas_header_t), 1, fp) < 1) - { - fclose(fp); - return false; - } //end if - //add the data lumps to the file - if (!AAS_WriteAASLump(fp, &header, AASLUMP_BBOXES, aasworld.bboxes, - aasworld.numbboxes * sizeof(aas_bbox_t))) return false; - if (!AAS_WriteAASLump(fp, &header, AASLUMP_VERTEXES, aasworld.vertexes, - aasworld.numvertexes * sizeof(aas_vertex_t))) return false; - if (!AAS_WriteAASLump(fp, &header, AASLUMP_PLANES, aasworld.planes, - aasworld.numplanes * sizeof(aas_plane_t))) return false; - if (!AAS_WriteAASLump(fp, &header, AASLUMP_EDGES, aasworld.edges, - aasworld.numedges * sizeof(aas_edge_t))) return false; - if (!AAS_WriteAASLump(fp, &header, AASLUMP_EDGEINDEX, aasworld.edgeindex, - aasworld.edgeindexsize * sizeof(aas_edgeindex_t))) return false; - if (!AAS_WriteAASLump(fp, &header, AASLUMP_FACES, aasworld.faces, - aasworld.numfaces * sizeof(aas_face_t))) return false; - if (!AAS_WriteAASLump(fp, &header, AASLUMP_FACEINDEX, aasworld.faceindex, - aasworld.faceindexsize * sizeof(aas_faceindex_t))) return false; - if (!AAS_WriteAASLump(fp, &header, AASLUMP_AREAS, aasworld.areas, - aasworld.numareas * sizeof(aas_area_t))) return false; - if (!AAS_WriteAASLump(fp, &header, AASLUMP_AREASETTINGS, aasworld.areasettings, - aasworld.numareasettings * sizeof(aas_areasettings_t))) return false; - if (!AAS_WriteAASLump(fp, &header, AASLUMP_REACHABILITY, aasworld.reachability, - aasworld.reachabilitysize * sizeof(aas_reachability_t))) return false; - if (!AAS_WriteAASLump(fp, &header, AASLUMP_NODES, aasworld.nodes, - aasworld.numnodes * sizeof(aas_node_t))) return false; - if (!AAS_WriteAASLump(fp, &header, AASLUMP_PORTALS, aasworld.portals, - aasworld.numportals * sizeof(aas_portal_t))) return false; - if (!AAS_WriteAASLump(fp, &header, AASLUMP_PORTALINDEX, aasworld.portalindex, - aasworld.portalindexsize * sizeof(aas_portalindex_t))) return false; - if (!AAS_WriteAASLump(fp, &header, AASLUMP_CLUSTERS, aasworld.clusters, - aasworld.numclusters * sizeof(aas_cluster_t))) return false; - //rewrite the header with the added lumps - fseek(fp, 0, SEEK_SET); - AAS_DData((unsigned char *) &header + 8, sizeof(aas_header_t) - 8); - if (fwrite(&header, sizeof(aas_header_t), 1, fp) < 1) - { - fclose(fp); - return false; - } //end if - //close the file - fclose(fp); - return true; -} //end of the function AAS_WriteAASFile - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "qbsp.h" +#include "../botlib/aasfile.h" +#include "aas_file.h" +#include "aas_store.h" +#include "aas_create.h" + +#define AAS_Error Error + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_SwapAASData(void) +{ + int i, j; + //bounding boxes + for (i = 0; i < aasworld.numbboxes; i++) + { + aasworld.bboxes[i].presencetype = LittleLong(aasworld.bboxes[i].presencetype); + aasworld.bboxes[i].flags = LittleLong(aasworld.bboxes[i].flags); + for (j = 0; j < 3; j++) + { + aasworld.bboxes[i].mins[j] = LittleLong(aasworld.bboxes[i].mins[j]); + aasworld.bboxes[i].maxs[j] = LittleLong(aasworld.bboxes[i].maxs[j]); + } //end for + } //end for + //vertexes + for (i = 0; i < aasworld.numvertexes; i++) + { + for (j = 0; j < 3; j++) + aasworld.vertexes[i][j] = LittleFloat(aasworld.vertexes[i][j]); + } //end for + //planes + for (i = 0; i < aasworld.numplanes; i++) + { + for (j = 0; j < 3; j++) + aasworld.planes[i].normal[j] = LittleFloat(aasworld.planes[i].normal[j]); + aasworld.planes[i].dist = LittleFloat(aasworld.planes[i].dist); + aasworld.planes[i].type = LittleLong(aasworld.planes[i].type); + } //end for + //edges + for (i = 0; i < aasworld.numedges; i++) + { + aasworld.edges[i].v[0] = LittleLong(aasworld.edges[i].v[0]); + aasworld.edges[i].v[1] = LittleLong(aasworld.edges[i].v[1]); + } //end for + //edgeindex + for (i = 0; i < aasworld.edgeindexsize; i++) + { + aasworld.edgeindex[i] = LittleLong(aasworld.edgeindex[i]); + } //end for + //faces + for (i = 0; i < aasworld.numfaces; i++) + { + aasworld.faces[i].planenum = LittleLong(aasworld.faces[i].planenum); + aasworld.faces[i].faceflags = LittleLong(aasworld.faces[i].faceflags); + aasworld.faces[i].numedges = LittleLong(aasworld.faces[i].numedges); + aasworld.faces[i].firstedge = LittleLong(aasworld.faces[i].firstedge); + aasworld.faces[i].frontarea = LittleLong(aasworld.faces[i].frontarea); + aasworld.faces[i].backarea = LittleLong(aasworld.faces[i].backarea); + } //end for + //face index + for (i = 0; i < aasworld.faceindexsize; i++) + { + aasworld.faceindex[i] = LittleLong(aasworld.faceindex[i]); + } //end for + //convex areas + for (i = 0; i < aasworld.numareas; i++) + { + aasworld.areas[i].areanum = LittleLong(aasworld.areas[i].areanum); + aasworld.areas[i].numfaces = LittleLong(aasworld.areas[i].numfaces); + aasworld.areas[i].firstface = LittleLong(aasworld.areas[i].firstface); + for (j = 0; j < 3; j++) + { + aasworld.areas[i].mins[j] = LittleFloat(aasworld.areas[i].mins[j]); + aasworld.areas[i].maxs[j] = LittleFloat(aasworld.areas[i].maxs[j]); + aasworld.areas[i].center[j] = LittleFloat(aasworld.areas[i].center[j]); + } //end for + } //end for + //area settings + for (i = 0; i < aasworld.numareasettings; i++) + { + aasworld.areasettings[i].contents = LittleLong(aasworld.areasettings[i].contents); + aasworld.areasettings[i].areaflags = LittleLong(aasworld.areasettings[i].areaflags); + aasworld.areasettings[i].presencetype = LittleLong(aasworld.areasettings[i].presencetype); + aasworld.areasettings[i].cluster = LittleLong(aasworld.areasettings[i].cluster); + aasworld.areasettings[i].clusterareanum = LittleLong(aasworld.areasettings[i].clusterareanum); + aasworld.areasettings[i].numreachableareas = LittleLong(aasworld.areasettings[i].numreachableareas); + aasworld.areasettings[i].firstreachablearea = LittleLong(aasworld.areasettings[i].firstreachablearea); + } //end for + //area reachability + for (i = 0; i < aasworld.reachabilitysize; i++) + { + aasworld.reachability[i].areanum = LittleLong(aasworld.reachability[i].areanum); + aasworld.reachability[i].facenum = LittleLong(aasworld.reachability[i].facenum); + aasworld.reachability[i].edgenum = LittleLong(aasworld.reachability[i].edgenum); + for (j = 0; j < 3; j++) + { + aasworld.reachability[i].start[j] = LittleFloat(aasworld.reachability[i].start[j]); + aasworld.reachability[i].end[j] = LittleFloat(aasworld.reachability[i].end[j]); + } //end for + aasworld.reachability[i].traveltype = LittleLong(aasworld.reachability[i].traveltype); + aasworld.reachability[i].traveltime = LittleShort(aasworld.reachability[i].traveltime); + } //end for + //nodes + for (i = 0; i < aasworld.numnodes; i++) + { + aasworld.nodes[i].planenum = LittleLong(aasworld.nodes[i].planenum); + aasworld.nodes[i].children[0] = LittleLong(aasworld.nodes[i].children[0]); + aasworld.nodes[i].children[1] = LittleLong(aasworld.nodes[i].children[1]); + } //end for + //cluster portals + for (i = 0; i < aasworld.numportals; i++) + { + aasworld.portals[i].areanum = LittleLong(aasworld.portals[i].areanum); + aasworld.portals[i].frontcluster = LittleLong(aasworld.portals[i].frontcluster); + aasworld.portals[i].backcluster = LittleLong(aasworld.portals[i].backcluster); + aasworld.portals[i].clusterareanum[0] = LittleLong(aasworld.portals[i].clusterareanum[0]); + aasworld.portals[i].clusterareanum[1] = LittleLong(aasworld.portals[i].clusterareanum[1]); + } //end for + //cluster portal index + for (i = 0; i < aasworld.portalindexsize; i++) + { + aasworld.portalindex[i] = LittleLong(aasworld.portalindex[i]); + } //end for + //cluster + for (i = 0; i < aasworld.numclusters; i++) + { + aasworld.clusters[i].numareas = LittleLong(aasworld.clusters[i].numareas); + aasworld.clusters[i].numportals = LittleLong(aasworld.clusters[i].numportals); + aasworld.clusters[i].firstportal = LittleLong(aasworld.clusters[i].firstportal); + } //end for +} //end of the function AAS_SwapAASData +//=========================================================================== +// dump the current loaded aas file +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DumpAASData(void) +{ + /* + if (aasworld.vertexes) FreeMemory(aasworld.vertexes); + aasworld.vertexes = NULL; + if (aasworld.planes) FreeMemory(aasworld.planes); + aasworld.planes = NULL; + if (aasworld.edges) FreeMemory(aasworld.edges); + aasworld.edges = NULL; + if (aasworld.edgeindex) FreeMemory(aasworld.edgeindex); + aasworld.edgeindex = NULL; + if (aasworld.faces) FreeMemory(aasworld.faces); + aasworld.faces = NULL; + if (aasworld.faceindex) FreeMemory(aasworld.faceindex); + aasworld.faceindex = NULL; + if (aasworld.areas) FreeMemory(aasworld.areas); + aasworld.areas = NULL; + if (aasworld.areasettings) FreeMemory(aasworld.areasettings); + aasworld.areasettings = NULL; + if (aasworld.reachability) FreeMemory(aasworld.reachability); + aasworld.reachability = NULL; + */ + aasworld.loaded = false; +} //end of the function AAS_DumpAASData +//=========================================================================== +// allocate memory and read a lump of a AAS file +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *AAS_LoadAASLump(FILE *fp, int offset, int length, void *buf) +{ + if (!length) + { + printf("lump size 0\n"); + return buf; + } //end if + //seek to the data + if (fseek(fp, offset, SEEK_SET)) + { + AAS_Error("can't seek to lump\n"); + AAS_DumpAASData(); + fclose(fp); + return 0; + } //end if + //allocate memory + if (!buf) buf = (void *) GetClearedMemory(length); + //read the data + if (fread((char *) buf, 1, length, fp) != length) + { + AAS_Error("can't read lump\n"); + FreeMemory(buf); + AAS_DumpAASData(); + fclose(fp); + return NULL; + } //end if + return buf; +} //end of the function AAS_LoadAASLump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DData(unsigned char *data, int size) +{ + int i; + + for (i = 0; i < size; i++) + { + data[i] ^= (unsigned char) i * 119; + } //end for +} //end of the function AAS_DData +//=========================================================================== +// load an aas file +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_LoadAASFile(char *filename, int fpoffset, int fplength) +{ + FILE *fp; + aas_header_t header; + int offset, length; + + //dump current loaded aas file + AAS_DumpAASData(); + //open the file + fp = fopen(filename, "rb"); + if (!fp) + { + AAS_Error("can't open %s\n", filename); + return false; + } //end if + //seek to the correct position (in the pak file) + if (fseek(fp, fpoffset, SEEK_SET)) + { + AAS_Error("can't seek to file %s\n"); + fclose(fp); + return false; + } //end if + //read the header + if (fread(&header, sizeof(aas_header_t), 1, fp) != 1) + { + AAS_Error("can't read header of file %s\n", filename); + fclose(fp); + return false; + } //end if + //check header identification + header.ident = LittleLong(header.ident); + if (header.ident != AASID) + { + AAS_Error("%s is not an AAS file\n", filename); + fclose(fp); + return false; + } //end if + //check the version + header.version = LittleLong(header.version); + if (header.version != AASVERSION_OLD && header.version != AASVERSION) + { + AAS_Error("%s is version %i, not %i\n", filename, header.version, AASVERSION); + fclose(fp); + return false; + } //end if + // + if (header.version == AASVERSION) + { + AAS_DData((unsigned char *) &header + 8, sizeof(aas_header_t) - 8); + } //end if + aasworld.bspchecksum = LittleLong(header.bspchecksum); + //load the lumps: + //bounding boxes + offset = fpoffset + LittleLong(header.lumps[AASLUMP_BBOXES].fileofs); + length = LittleLong(header.lumps[AASLUMP_BBOXES].filelen); + aasworld.bboxes = (aas_bbox_t *) AAS_LoadAASLump(fp, offset, length, aasworld.bboxes); + if (!aasworld.bboxes) return false; + aasworld.numbboxes = length / sizeof(aas_bbox_t); + //vertexes + offset = fpoffset + LittleLong(header.lumps[AASLUMP_VERTEXES].fileofs); + length = LittleLong(header.lumps[AASLUMP_VERTEXES].filelen); + aasworld.vertexes = (aas_vertex_t *) AAS_LoadAASLump(fp, offset, length, aasworld.vertexes); + if (!aasworld.vertexes) return false; + aasworld.numvertexes = length / sizeof(aas_vertex_t); + //planes + offset = fpoffset + LittleLong(header.lumps[AASLUMP_PLANES].fileofs); + length = LittleLong(header.lumps[AASLUMP_PLANES].filelen); + aasworld.planes = (aas_plane_t *) AAS_LoadAASLump(fp, offset, length, aasworld.planes); + if (!aasworld.planes) return false; + aasworld.numplanes = length / sizeof(aas_plane_t); + //edges + offset = fpoffset + LittleLong(header.lumps[AASLUMP_EDGES].fileofs); + length = LittleLong(header.lumps[AASLUMP_EDGES].filelen); + aasworld.edges = (aas_edge_t *) AAS_LoadAASLump(fp, offset, length, aasworld.edges); + if (!aasworld.edges) return false; + aasworld.numedges = length / sizeof(aas_edge_t); + //edgeindex + offset = fpoffset + LittleLong(header.lumps[AASLUMP_EDGEINDEX].fileofs); + length = LittleLong(header.lumps[AASLUMP_EDGEINDEX].filelen); + aasworld.edgeindex = (aas_edgeindex_t *) AAS_LoadAASLump(fp, offset, length, aasworld.edgeindex); + if (!aasworld.edgeindex) return false; + aasworld.edgeindexsize = length / sizeof(aas_edgeindex_t); + //faces + offset = fpoffset + LittleLong(header.lumps[AASLUMP_FACES].fileofs); + length = LittleLong(header.lumps[AASLUMP_FACES].filelen); + aasworld.faces = (aas_face_t *) AAS_LoadAASLump(fp, offset, length, aasworld.faces); + if (!aasworld.faces) return false; + aasworld.numfaces = length / sizeof(aas_face_t); + //faceindex + offset = fpoffset + LittleLong(header.lumps[AASLUMP_FACEINDEX].fileofs); + length = LittleLong(header.lumps[AASLUMP_FACEINDEX].filelen); + aasworld.faceindex = (aas_faceindex_t *) AAS_LoadAASLump(fp, offset, length, aasworld.faceindex); + if (!aasworld.faceindex) return false; + aasworld.faceindexsize = length / sizeof(int); + //convex areas + offset = fpoffset + LittleLong(header.lumps[AASLUMP_AREAS].fileofs); + length = LittleLong(header.lumps[AASLUMP_AREAS].filelen); + aasworld.areas = (aas_area_t *) AAS_LoadAASLump(fp, offset, length, aasworld.areas); + if (!aasworld.areas) return false; + aasworld.numareas = length / sizeof(aas_area_t); + //area settings + offset = fpoffset + LittleLong(header.lumps[AASLUMP_AREASETTINGS].fileofs); + length = LittleLong(header.lumps[AASLUMP_AREASETTINGS].filelen); + aasworld.areasettings = (aas_areasettings_t *) AAS_LoadAASLump(fp, offset, length, aasworld.areasettings); + if (!aasworld.areasettings) return false; + aasworld.numareasettings = length / sizeof(aas_areasettings_t); + //reachability list + offset = fpoffset + LittleLong(header.lumps[AASLUMP_REACHABILITY].fileofs); + length = LittleLong(header.lumps[AASLUMP_REACHABILITY].filelen); + aasworld.reachability = (aas_reachability_t *) AAS_LoadAASLump(fp, offset, length, aasworld.reachability); + if (length && !aasworld.reachability) return false; + aasworld.reachabilitysize = length / sizeof(aas_reachability_t); + //nodes + offset = fpoffset + LittleLong(header.lumps[AASLUMP_NODES].fileofs); + length = LittleLong(header.lumps[AASLUMP_NODES].filelen); + aasworld.nodes = (aas_node_t *) AAS_LoadAASLump(fp, offset, length, aasworld.nodes); + if (!aasworld.nodes) return false; + aasworld.numnodes = length / sizeof(aas_node_t); + //cluster portals + offset = fpoffset + LittleLong(header.lumps[AASLUMP_PORTALS].fileofs); + length = LittleLong(header.lumps[AASLUMP_PORTALS].filelen); + aasworld.portals = (aas_portal_t *) AAS_LoadAASLump(fp, offset, length, aasworld.portals); + if (length && !aasworld.portals) return false; + aasworld.numportals = length / sizeof(aas_portal_t); + //cluster portal index + offset = fpoffset + LittleLong(header.lumps[AASLUMP_PORTALINDEX].fileofs); + length = LittleLong(header.lumps[AASLUMP_PORTALINDEX].filelen); + aasworld.portalindex = (aas_portalindex_t *) AAS_LoadAASLump(fp, offset, length, aasworld.portalindex); + if (length && !aasworld.portalindex) return false; + aasworld.portalindexsize = length / sizeof(aas_portalindex_t); + //clusters + offset = fpoffset + LittleLong(header.lumps[AASLUMP_CLUSTERS].fileofs); + length = LittleLong(header.lumps[AASLUMP_CLUSTERS].filelen); + aasworld.clusters = (aas_cluster_t *) AAS_LoadAASLump(fp, offset, length, aasworld.clusters); + if (length && !aasworld.clusters) return false; + aasworld.numclusters = length / sizeof(aas_cluster_t); + //swap everything + AAS_SwapAASData(); + //aas file is loaded + aasworld.loaded = true; + //close the file + fclose(fp); + return true; +} //end of the function AAS_LoadAASFile +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_WriteAASLump(FILE *fp, aas_header_t *h, int lumpnum, void *data, int length) +{ + aas_lump_t *lump; + + lump = &h->lumps[lumpnum]; + + lump->fileofs = LittleLong(ftell(fp)); + lump->filelen = LittleLong(length); + + if (length > 0) + { + if (fwrite(data, length, 1, fp) < 1) + { + Log_Print("error writing lump %s\n", lumpnum); + fclose(fp); + return false; + } //end if + } //end if + return true; +} //end of the function AAS_WriteAASLump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowNumReachabilities(int tt, char *name) +{ + int i, num; + + num = 0; + for (i = 0; i < aasworld.reachabilitysize; i++) + { + if ((aasworld.reachability[i].traveltype & TRAVELTYPE_MASK) == tt) + num++; + } //end for + Log_Print("%6d %s\n", num, name); +} //end of the function AAS_ShowNumReachabilities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowTotals(void) +{ + Log_Print("numvertexes = %d\r\n", aasworld.numvertexes); + Log_Print("numplanes = %d\r\n", aasworld.numplanes); + Log_Print("numedges = %d\r\n", aasworld.numedges); + Log_Print("edgeindexsize = %d\r\n", aasworld.edgeindexsize); + Log_Print("numfaces = %d\r\n", aasworld.numfaces); + Log_Print("faceindexsize = %d\r\n", aasworld.faceindexsize); + Log_Print("numareas = %d\r\n", aasworld.numareas); + Log_Print("numareasettings = %d\r\n", aasworld.numareasettings); + Log_Print("reachabilitysize = %d\r\n", aasworld.reachabilitysize); + Log_Print("numnodes = %d\r\n", aasworld.numnodes); + Log_Print("numportals = %d\r\n", aasworld.numportals); + Log_Print("portalindexsize = %d\r\n", aasworld.portalindexsize); + Log_Print("numclusters = %d\r\n", aasworld.numclusters); + AAS_ShowNumReachabilities(TRAVEL_WALK, "walk"); + AAS_ShowNumReachabilities(TRAVEL_CROUCH, "crouch"); + AAS_ShowNumReachabilities(TRAVEL_BARRIERJUMP, "barrier jump"); + AAS_ShowNumReachabilities(TRAVEL_JUMP, "jump"); + AAS_ShowNumReachabilities(TRAVEL_LADDER, "ladder"); + AAS_ShowNumReachabilities(TRAVEL_WALKOFFLEDGE, "walk off ledge"); + AAS_ShowNumReachabilities(TRAVEL_SWIM, "swim"); + AAS_ShowNumReachabilities(TRAVEL_WATERJUMP, "water jump"); + AAS_ShowNumReachabilities(TRAVEL_TELEPORT, "teleport"); + AAS_ShowNumReachabilities(TRAVEL_ELEVATOR, "elevator"); + AAS_ShowNumReachabilities(TRAVEL_ROCKETJUMP, "rocket jump"); + AAS_ShowNumReachabilities(TRAVEL_BFGJUMP, "bfg jump"); + AAS_ShowNumReachabilities(TRAVEL_GRAPPLEHOOK, "grapple hook"); + AAS_ShowNumReachabilities(TRAVEL_DOUBLEJUMP, "double jump"); + AAS_ShowNumReachabilities(TRAVEL_RAMPJUMP, "ramp jump"); + AAS_ShowNumReachabilities(TRAVEL_STRAFEJUMP, "strafe jump"); + AAS_ShowNumReachabilities(TRAVEL_JUMPPAD, "jump pad"); + AAS_ShowNumReachabilities(TRAVEL_FUNCBOB, "func bob"); +} //end of the function AAS_ShowTotals +//=========================================================================== +// aas data is useless after writing to file because it is byte swapped +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_WriteAASFile(char *filename) +{ + aas_header_t header; + FILE *fp; + + Log_Print("writing %s\n", filename); + AAS_ShowTotals(); + //swap the aas data + AAS_SwapAASData(); + //initialize the file header + memset(&header, 0, sizeof(aas_header_t)); + header.ident = LittleLong(AASID); + header.version = LittleLong(AASVERSION); + header.bspchecksum = LittleLong(aasworld.bspchecksum); + //open a new file + fp = fopen(filename, "wb"); + if (!fp) + { + Log_Print("error opening %s\n", filename); + return false; + } //end if + //write the header + if (fwrite(&header, sizeof(aas_header_t), 1, fp) < 1) + { + fclose(fp); + return false; + } //end if + //add the data lumps to the file + if (!AAS_WriteAASLump(fp, &header, AASLUMP_BBOXES, aasworld.bboxes, + aasworld.numbboxes * sizeof(aas_bbox_t))) return false; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_VERTEXES, aasworld.vertexes, + aasworld.numvertexes * sizeof(aas_vertex_t))) return false; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_PLANES, aasworld.planes, + aasworld.numplanes * sizeof(aas_plane_t))) return false; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_EDGES, aasworld.edges, + aasworld.numedges * sizeof(aas_edge_t))) return false; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_EDGEINDEX, aasworld.edgeindex, + aasworld.edgeindexsize * sizeof(aas_edgeindex_t))) return false; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_FACES, aasworld.faces, + aasworld.numfaces * sizeof(aas_face_t))) return false; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_FACEINDEX, aasworld.faceindex, + aasworld.faceindexsize * sizeof(aas_faceindex_t))) return false; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_AREAS, aasworld.areas, + aasworld.numareas * sizeof(aas_area_t))) return false; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_AREASETTINGS, aasworld.areasettings, + aasworld.numareasettings * sizeof(aas_areasettings_t))) return false; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_REACHABILITY, aasworld.reachability, + aasworld.reachabilitysize * sizeof(aas_reachability_t))) return false; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_NODES, aasworld.nodes, + aasworld.numnodes * sizeof(aas_node_t))) return false; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_PORTALS, aasworld.portals, + aasworld.numportals * sizeof(aas_portal_t))) return false; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_PORTALINDEX, aasworld.portalindex, + aasworld.portalindexsize * sizeof(aas_portalindex_t))) return false; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_CLUSTERS, aasworld.clusters, + aasworld.numclusters * sizeof(aas_cluster_t))) return false; + //rewrite the header with the added lumps + fseek(fp, 0, SEEK_SET); + AAS_DData((unsigned char *) &header + 8, sizeof(aas_header_t) - 8); + if (fwrite(&header, sizeof(aas_header_t), 1, fp) < 1) + { + fclose(fp); + return false; + } //end if + //close the file + fclose(fp); + return true; +} //end of the function AAS_WriteAASFile + diff --git a/code/bspc/aas_file.h b/code/bspc/aas_file.h index 5176461..d3e15c2 100755 --- a/code/bspc/aas_file.h +++ b/code/bspc/aas_file.h @@ -1,25 +1,25 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -qboolean AAS_WriteAASFile(char *filename); -qboolean AAS_LoadAASFile(char *filename, int fpoffset, int fplength); - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +qboolean AAS_WriteAASFile(char *filename); +qboolean AAS_LoadAASFile(char *filename, int fpoffset, int fplength); + diff --git a/code/bspc/aas_gsubdiv.c b/code/bspc/aas_gsubdiv.c index d9ba597..4cfb194 100755 --- a/code/bspc/aas_gsubdiv.c +++ b/code/bspc/aas_gsubdiv.c @@ -1,656 +1,656 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "qbsp.h" -#include "../botlib/aasfile.h" -#include "aas_create.h" -#include "aas_store.h" -#include "aas_cfg.h" - -#define FACECLIP_EPSILON 0.2 -#define FACE_EPSILON 1.0 - -int numgravitationalsubdivisions = 0; -int numladdersubdivisions = 0; - -//NOTE: only do gravitational subdivision BEFORE area merging!!!!!!! -// because the bsp tree isn't refreshes like with ladder subdivision - -//=========================================================================== -// NOTE: the original face is invalid after splitting -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_SplitFace(tmp_face_t *face, vec3_t normal, float dist, - tmp_face_t **frontface, tmp_face_t **backface) -{ - winding_t *frontw, *backw; - - // - *frontface = *backface = NULL; - - ClipWindingEpsilon(face->winding, normal, dist, FACECLIP_EPSILON, &frontw, &backw); - -#ifdef DEBUG - // - if (frontw) - { - if (WindingIsTiny(frontw)) - { - Log_Write("AAS_SplitFace: tiny back face\r\n"); - FreeWinding(frontw); - frontw = NULL; - } //end if - } //end if - if (backw) - { - if (WindingIsTiny(backw)) - { - Log_Write("AAS_SplitFace: tiny back face\r\n"); - FreeWinding(backw); - backw = NULL; - } //end if - } //end if -#endif //DEBUG - //if the winding was split - if (frontw) - { - //check bounds - (*frontface) = AAS_AllocTmpFace(); - (*frontface)->planenum = face->planenum; - (*frontface)->winding = frontw; - (*frontface)->faceflags = face->faceflags; - } //end if - if (backw) - { - //check bounds - (*backface) = AAS_AllocTmpFace(); - (*backface)->planenum = face->planenum; - (*backface)->winding = backw; - (*backface)->faceflags = face->faceflags; - } //end if -} //end of the function AAS_SplitFace -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -winding_t *AAS_SplitWinding(tmp_area_t *tmparea, int planenum) -{ - tmp_face_t *face; - plane_t *plane; - int side; - winding_t *splitwinding; - - // - plane = &mapplanes[planenum]; - //create a split winding, first base winding for plane - splitwinding = BaseWindingForPlane(plane->normal, plane->dist); - //chop with all the faces of the area - for (face = tmparea->tmpfaces; face && splitwinding; face = face->next[side]) - { - //side of the face the original area was on - side = face->frontarea != tmparea; - plane = &mapplanes[face->planenum ^ side]; - ChopWindingInPlace(&splitwinding, plane->normal, plane->dist, 0); // PLANESIDE_EPSILON); - } //end for - return splitwinding; -} //end of the function AAS_SplitWinding -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_TestSplitPlane(tmp_area_t *tmparea, vec3_t normal, float dist, - int *facesplits, int *groundsplits, int *epsilonfaces) -{ - int j, side, front, back, planenum; - float d, d_front, d_back; - tmp_face_t *face; - winding_t *w; - - *facesplits = *groundsplits = *epsilonfaces = 0; - - planenum = FindFloatPlane(normal, dist); - - w = AAS_SplitWinding(tmparea, planenum); - if (!w) return false; - FreeWinding(w); - // - for (face = tmparea->tmpfaces; face; face = face->next[side]) - { - //side of the face the area is on - side = face->frontarea != tmparea; - - if ((face->planenum & ~1) == (planenum & ~1)) - { - Log_Print("AAS_TestSplitPlane: tried face plane as splitter\n"); - return false; - } //end if - w = face->winding; - //reset distance at front and back side of plane - d_front = d_back = 0; - //reset front and back flags - front = back = 0; - for (j = 0; j < w->numpoints; j++) - { - d = DotProduct(w->p[j], normal) - dist; - if (d > d_front) d_front = d; - if (d < d_back) d_back = d; - - if (d > 0.4) // PLANESIDE_EPSILON) - front = 1; - if (d < -0.4) // PLANESIDE_EPSILON) - back = 1; - } //end for - //check for an epsilon face - if ( (d_front > FACECLIP_EPSILON && d_front < FACE_EPSILON) - || (d_back < -FACECLIP_EPSILON && d_back > -FACE_EPSILON) ) - { - (*epsilonfaces)++; - } //end if - //if the face has points at both sides of the plane - if (front && back) - { - (*facesplits)++; - if (face->faceflags & FACE_GROUND) - { - (*groundsplits)++; - } //end if - } //end if - } //end for - return true; -} //end of the function AAS_TestSplitPlane -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_SplitArea(tmp_area_t *tmparea, int planenum, tmp_area_t **frontarea, tmp_area_t **backarea) -{ - int side; - tmp_area_t *facefrontarea, *facebackarea, *faceotherarea; - tmp_face_t *face, *frontface, *backface, *splitface, *nextface; - winding_t *splitwinding; - plane_t *splitplane; - -/* -#ifdef AW_DEBUG - int facesplits, groundsplits, epsilonface; - Log_Print("\n----------------------\n"); - Log_Print("splitting area %d\n", areanum); - Log_Print("with normal = \'%f %f %f\', dist = %f\n", normal[0], normal[1], normal[2], dist); - AAS_TestSplitPlane(areanum, normal, dist, - &facesplits, &groundsplits, &epsilonface); - Log_Print("face splits = %d\nground splits = %d\n", facesplits, groundsplits); - if (epsilonface) Log_Print("aaahh epsilon face\n"); -#endif //AW_DEBUG*/ - //the original area - - AAS_FlipAreaFaces(tmparea); - AAS_CheckArea(tmparea); - // - splitplane = &mapplanes[planenum]; -/* //create a split winding, first base winding for plane - splitwinding = BaseWindingForPlane(splitplane->normal, splitplane->dist); - //chop with all the faces of the area - for (face = tmparea->tmpfaces; face && splitwinding; face = face->next[side]) - { - //side of the face the original area was on - side = face->frontarea != tmparea->areanum; - plane = &mapplanes[face->planenum ^ side]; - ChopWindingInPlace(&splitwinding, plane->normal, plane->dist, 0); // PLANESIDE_EPSILON); - } //end for*/ - splitwinding = AAS_SplitWinding(tmparea, planenum); - if (!splitwinding) - { -/* -#ifdef DEBUG - AAS_TestSplitPlane(areanum, normal, dist, - &facesplits, &groundsplits, &epsilonface); - Log_Print("\nface splits = %d\nground splits = %d\n", facesplits, groundsplits); - if (epsilonface) Log_Print("aaahh epsilon face\n"); -#endif //DEBUG*/ - Error("AAS_SplitArea: no split winding when splitting area %d\n", tmparea->areanum); - } //end if - //create a split face - splitface = AAS_AllocTmpFace(); - //get the map plane - splitface->planenum = planenum; - //store the split winding - splitface->winding = splitwinding; - //the new front area - (*frontarea) = AAS_AllocTmpArea(); - (*frontarea)->presencetype = tmparea->presencetype; - (*frontarea)->contents = tmparea->contents; - (*frontarea)->modelnum = tmparea->modelnum; - (*frontarea)->tmpfaces = NULL; - //the new back area - (*backarea) = AAS_AllocTmpArea(); - (*backarea)->presencetype = tmparea->presencetype; - (*backarea)->contents = tmparea->contents; - (*backarea)->modelnum = tmparea->modelnum; - (*backarea)->tmpfaces = NULL; - //add the split face to the new areas - AAS_AddFaceSideToArea(splitface, 0, (*frontarea)); - AAS_AddFaceSideToArea(splitface, 1, (*backarea)); - - //split all the faces of the original area - for (face = tmparea->tmpfaces; face; face = nextface) - { - //side of the face the original area was on - side = face->frontarea != tmparea; - //next face of the original area - nextface = face->next[side]; - //front area of the face - facefrontarea = face->frontarea; - //back area of the face - facebackarea = face->backarea; - //remove the face from both the front and back areas - if (facefrontarea) AAS_RemoveFaceFromArea(face, facefrontarea); - if (facebackarea) AAS_RemoveFaceFromArea(face, facebackarea); - //split the face - AAS_SplitFace(face, splitplane->normal, splitplane->dist, &frontface, &backface); - //free the original face - AAS_FreeTmpFace(face); - //get the number of the area at the other side of the face - if (side) faceotherarea = facefrontarea; - else faceotherarea = facebackarea; - //if there is an area at the other side of the original face - if (faceotherarea) - { - if (frontface) AAS_AddFaceSideToArea(frontface, !side, faceotherarea); - if (backface) AAS_AddFaceSideToArea(backface, !side, faceotherarea); - } //end if - //add the front and back part left after splitting the original face to the new areas - if (frontface) AAS_AddFaceSideToArea(frontface, side, (*frontarea)); - if (backface) AAS_AddFaceSideToArea(backface, side, (*backarea)); - } //end for - - if (!(*frontarea)->tmpfaces) Log_Print("AAS_SplitArea: front area without faces\n"); - if (!(*backarea)->tmpfaces) Log_Print("AAS_SplitArea: back area without faces\n"); - - tmparea->invalid = true; -/* -#ifdef AW_DEBUG - for (i = 0, face = frontarea->tmpfaces; face; face = face->next[side]) - { - side = face->frontarea != frontarea->areanum; - i++; - } //end for - Log_Print("created front area %d with %d faces\n", frontarea->areanum, i); - - for (i = 0, face = backarea->tmpfaces; face; face = face->next[side]) - { - side = face->frontarea != backarea->areanum; - i++; - } //end for - Log_Print("created back area %d with %d faces\n", backarea->areanum, i); -#endif //AW_DEBUG*/ - - AAS_FlipAreaFaces((*frontarea)); - AAS_FlipAreaFaces((*backarea)); - // - AAS_CheckArea((*frontarea)); - AAS_CheckArea((*backarea)); -} //end of the function AAS_SplitArea -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_FindBestAreaSplitPlane(tmp_area_t *tmparea, vec3_t normal, float *dist) -{ - int side1, side2; - int foundsplitter, facesplits, groundsplits, epsilonfaces, bestepsilonfaces; - float bestvalue, value; - tmp_face_t *face1, *face2; - vec3_t tmpnormal, invgravity; - float tmpdist; - - //get inverse of gravity direction - VectorCopy(cfg.phys_gravitydirection, invgravity); - VectorInverse(invgravity); - - foundsplitter = false; - bestvalue = -999999; - bestepsilonfaces = 0; - // -#ifdef AW_DEBUG - Log_Print("finding split plane for area %d\n", tmparea->areanum); -#endif //AW_DEBUG - for (face1 = tmparea->tmpfaces; face1; face1 = face1->next[side1]) - { - //side of the face the area is on - side1 = face1->frontarea != tmparea; - // - if (WindingIsTiny(face1->winding)) - { - Log_Write("gsubdiv: area %d has a tiny winding\r\n", tmparea->areanum); - continue; - } //end if - //if the face isn't a gap or ground there's no split edge - if (!(face1->faceflags & FACE_GROUND) && !AAS_GapFace(face1, side1)) continue; - // - for (face2 = face1->next[side1]; face2; face2 = face2->next[side2]) - { - //side of the face the area is on - side2 = face2->frontarea != tmparea; - // - if (WindingIsTiny(face1->winding)) - { - Log_Write("gsubdiv: area %d has a tiny winding\r\n", tmparea->areanum); - continue; - } //end if - //if the face isn't a gap or ground there's no split edge - if (!(face2->faceflags & FACE_GROUND) && !AAS_GapFace(face2, side2)) continue; - //only split between gaps and ground - if (!(((face1->faceflags & FACE_GROUND) && AAS_GapFace(face2, side2)) || - ((face2->faceflags & FACE_GROUND) && AAS_GapFace(face1, side1)))) continue; - //find a plane seperating the windings of the faces - if (!FindPlaneSeperatingWindings(face1->winding, face2->winding, invgravity, - tmpnormal, &tmpdist)) continue; -#ifdef AW_DEBUG - Log_Print("normal = \'%f %f %f\', dist = %f\n", - tmpnormal[0], tmpnormal[1], tmpnormal[2], tmpdist); -#endif //AW_DEBUG - //get metrics for this vertical plane - if (!AAS_TestSplitPlane(tmparea, tmpnormal, tmpdist, - &facesplits, &groundsplits, &epsilonfaces)) - { - continue; - } //end if -#ifdef AW_DEBUG - Log_Print("face splits = %d\nground splits = %d\n", - facesplits, groundsplits); -#endif //AW_DEBUG - value = 100 - facesplits - 2 * groundsplits; - //avoid epsilon faces - value += epsilonfaces * -1000; - if (value > bestvalue) - { - VectorCopy(tmpnormal, normal); - *dist = tmpdist; - bestvalue = value; - bestepsilonfaces = epsilonfaces; - foundsplitter = true; - } //end if - } //end for - } //end for - if (bestepsilonfaces) - { - Log_Write("found %d epsilon faces trying to split area %d\r\n", - epsilonfaces, tmparea->areanum); - } //end else - return foundsplitter; -} //end of the function AAS_FindBestAreaSplitPlane -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -tmp_node_t *AAS_SubdivideArea_r(tmp_node_t *tmpnode) -{ - int planenum; - tmp_area_t *frontarea, *backarea; - tmp_node_t *tmpnode1, *tmpnode2; - vec3_t normal; - float dist; - - if (AAS_FindBestAreaSplitPlane(tmpnode->tmparea, normal, &dist)) - { - qprintf("\r%6d", ++numgravitationalsubdivisions); - // - planenum = FindFloatPlane(normal, dist); - //split the area - AAS_SplitArea(tmpnode->tmparea, planenum, &frontarea, &backarea); - // - tmpnode->tmparea = NULL; - tmpnode->planenum = FindFloatPlane(normal, dist); - // - tmpnode1 = AAS_AllocTmpNode(); - tmpnode1->planenum = 0; - tmpnode1->tmparea = frontarea; - // - tmpnode2 = AAS_AllocTmpNode(); - tmpnode2->planenum = 0; - tmpnode2->tmparea = backarea; - //subdivide the areas created by splitting recursively - tmpnode->children[0] = AAS_SubdivideArea_r(tmpnode1); - tmpnode->children[1] = AAS_SubdivideArea_r(tmpnode2); - } //end if - return tmpnode; -} //end of the function AAS_SubdivideArea_r -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -tmp_node_t *AAS_GravitationalSubdivision_r(tmp_node_t *tmpnode) -{ - //if this is a solid leaf - if (!tmpnode) return NULL; - //negative so it's an area - if (tmpnode->tmparea) return AAS_SubdivideArea_r(tmpnode); - //do the children recursively - tmpnode->children[0] = AAS_GravitationalSubdivision_r(tmpnode->children[0]); - tmpnode->children[1] = AAS_GravitationalSubdivision_r(tmpnode->children[1]); - return tmpnode; -} //end of the function AAS_GravitationalSubdivision_r -//=========================================================================== -// NOTE: merge faces and melt edges first -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_GravitationalSubdivision(void) -{ - Log_Write("AAS_GravitationalSubdivision\r\n"); - numgravitationalsubdivisions = 0; - qprintf("%6i gravitational subdivisions", numgravitationalsubdivisions); - //start with the head node - AAS_GravitationalSubdivision_r(tmpaasworld.nodes); - qprintf("\n"); - Log_Write("%6i gravitational subdivisions\r\n", numgravitationalsubdivisions); -} //end of the function AAS_GravitationalSubdivision -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -tmp_node_t *AAS_RefreshLadderSubdividedTree_r(tmp_node_t *tmpnode, tmp_area_t *tmparea, - tmp_node_t *tmpnode1, tmp_node_t *tmpnode2, int planenum) -{ - //if this is a solid leaf - if (!tmpnode) return NULL; - //negative so it's an area - if (tmpnode->tmparea) - { - if (tmpnode->tmparea == tmparea) - { - tmpnode->tmparea = NULL; - tmpnode->planenum = planenum; - tmpnode->children[0] = tmpnode1; - tmpnode->children[1] = tmpnode2; - } //end if - return tmpnode; - } //end if - //do the children recursively - tmpnode->children[0] = AAS_RefreshLadderSubdividedTree_r(tmpnode->children[0], - tmparea, tmpnode1, tmpnode2, planenum); - tmpnode->children[1] = AAS_RefreshLadderSubdividedTree_r(tmpnode->children[1], - tmparea, tmpnode1, tmpnode2, planenum); - return tmpnode; -} //end of the function AAS_RefreshLadderSubdividedTree_r -//=========================================================================== -// find an area with ladder faces and ground faces that are not connected -// split the area with a horizontal plane at the lowest vertex of all -// ladder faces in the area -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -tmp_node_t *AAS_LadderSubdivideArea_r(tmp_node_t *tmpnode) -{ - int side1, i, planenum; - int foundladderface, foundgroundface; - float dist; - tmp_area_t *tmparea, *frontarea, *backarea; - tmp_face_t *face1; - tmp_node_t *tmpnode1, *tmpnode2; - vec3_t lowestpoint, normal = {0, 0, 1}; - plane_t *plane; - winding_t *w; - - tmparea = tmpnode->tmparea; - //skip areas with a liquid - if (tmparea->contents & (AREACONTENTS_WATER - | AREACONTENTS_LAVA - | AREACONTENTS_SLIME)) return tmpnode; - //must be possible to stand in the area - if (!(tmparea->presencetype & PRESENCE_NORMAL)) return tmpnode; - // - foundladderface = false; - foundgroundface = false; - lowestpoint[2] = 99999; - // - for (face1 = tmparea->tmpfaces; face1; face1 = face1->next[side1]) - { - //side of the face the area is on - side1 = face1->frontarea != tmparea; - //if the face is a ladder face - if (face1->faceflags & FACE_LADDER) - { - plane = &mapplanes[face1->planenum]; - //the ladder face plane should be pretty much vertical - if (DotProduct(plane->normal, normal) > -0.1) - { - foundladderface = true; - //find lowest point - for (i = 0; i < face1->winding->numpoints; i++) - { - if (face1->winding->p[i][2] < lowestpoint[2]) - { - VectorCopy(face1->winding->p[i], lowestpoint); - } //end if - } //end for - } //end if - } //end if - else if (face1->faceflags & FACE_GROUND) - { - foundgroundface = true; - } //end else if - } //end for - // - if ((!foundladderface) || (!foundgroundface)) return tmpnode; - // - for (face1 = tmparea->tmpfaces; face1; face1 = face1->next[side1]) - { - //side of the face the area is on - side1 = face1->frontarea != tmparea; - //if the face isn't a ground face - if (!(face1->faceflags & FACE_GROUND)) continue; - //the ground plane - plane = &mapplanes[face1->planenum]; - //get the difference between the ground plane and the lowest point - dist = DotProduct(plane->normal, lowestpoint) - plane->dist; - //if the lowest point is very near one of the ground planes - if (dist > -1 && dist < 1) - { - return tmpnode; - } //end if - } //end for - // - dist = DotProduct(normal, lowestpoint); - planenum = FindFloatPlane(normal, dist); - // - w = AAS_SplitWinding(tmparea, planenum); - if (!w) return tmpnode; - FreeWinding(w); - //split the area with a horizontal plane through the lowest point - qprintf("\r%6d", ++numladdersubdivisions); - // - AAS_SplitArea(tmparea, planenum, &frontarea, &backarea); - // - tmpnode->tmparea = NULL; - tmpnode->planenum = planenum; - // - tmpnode1 = AAS_AllocTmpNode(); - tmpnode1->planenum = 0; - tmpnode1->tmparea = frontarea; - // - tmpnode2 = AAS_AllocTmpNode(); - tmpnode2->planenum = 0; - tmpnode2->tmparea = backarea; - //subdivide the areas created by splitting recursively - tmpnode->children[0] = AAS_LadderSubdivideArea_r(tmpnode1); - tmpnode->children[1] = AAS_LadderSubdivideArea_r(tmpnode2); - //refresh the tree - AAS_RefreshLadderSubdividedTree_r(tmpaasworld.nodes, tmparea, tmpnode1, tmpnode2, planenum); - // - return tmpnode; -} //end of the function AAS_LadderSubdivideArea_r -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -tmp_node_t *AAS_LadderSubdivision_r(tmp_node_t *tmpnode) -{ - //if this is a solid leaf - if (!tmpnode) return 0; - //negative so it's an area - if (tmpnode->tmparea) return AAS_LadderSubdivideArea_r(tmpnode); - //do the children recursively - tmpnode->children[0] = AAS_LadderSubdivision_r(tmpnode->children[0]); - tmpnode->children[1] = AAS_LadderSubdivision_r(tmpnode->children[1]); - return tmpnode; -} //end of the function AAS_LadderSubdivision_r -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_LadderSubdivision(void) -{ - Log_Write("AAS_LadderSubdivision\r\n"); - numladdersubdivisions = 0; - qprintf("%6i ladder subdivisions", numladdersubdivisions); - //start with the head node - AAS_LadderSubdivision_r(tmpaasworld.nodes); - // - qprintf("\n"); - Log_Write("%6i ladder subdivisions\r\n", numladdersubdivisions); -} //end of the function AAS_LadderSubdivision +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "qbsp.h" +#include "../botlib/aasfile.h" +#include "aas_create.h" +#include "aas_store.h" +#include "aas_cfg.h" + +#define FACECLIP_EPSILON 0.2 +#define FACE_EPSILON 1.0 + +int numgravitationalsubdivisions = 0; +int numladdersubdivisions = 0; + +//NOTE: only do gravitational subdivision BEFORE area merging!!!!!!! +// because the bsp tree isn't refreshes like with ladder subdivision + +//=========================================================================== +// NOTE: the original face is invalid after splitting +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_SplitFace(tmp_face_t *face, vec3_t normal, float dist, + tmp_face_t **frontface, tmp_face_t **backface) +{ + winding_t *frontw, *backw; + + // + *frontface = *backface = NULL; + + ClipWindingEpsilon(face->winding, normal, dist, FACECLIP_EPSILON, &frontw, &backw); + +#ifdef DEBUG + // + if (frontw) + { + if (WindingIsTiny(frontw)) + { + Log_Write("AAS_SplitFace: tiny back face\r\n"); + FreeWinding(frontw); + frontw = NULL; + } //end if + } //end if + if (backw) + { + if (WindingIsTiny(backw)) + { + Log_Write("AAS_SplitFace: tiny back face\r\n"); + FreeWinding(backw); + backw = NULL; + } //end if + } //end if +#endif //DEBUG + //if the winding was split + if (frontw) + { + //check bounds + (*frontface) = AAS_AllocTmpFace(); + (*frontface)->planenum = face->planenum; + (*frontface)->winding = frontw; + (*frontface)->faceflags = face->faceflags; + } //end if + if (backw) + { + //check bounds + (*backface) = AAS_AllocTmpFace(); + (*backface)->planenum = face->planenum; + (*backface)->winding = backw; + (*backface)->faceflags = face->faceflags; + } //end if +} //end of the function AAS_SplitFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +winding_t *AAS_SplitWinding(tmp_area_t *tmparea, int planenum) +{ + tmp_face_t *face; + plane_t *plane; + int side; + winding_t *splitwinding; + + // + plane = &mapplanes[planenum]; + //create a split winding, first base winding for plane + splitwinding = BaseWindingForPlane(plane->normal, plane->dist); + //chop with all the faces of the area + for (face = tmparea->tmpfaces; face && splitwinding; face = face->next[side]) + { + //side of the face the original area was on + side = face->frontarea != tmparea; + plane = &mapplanes[face->planenum ^ side]; + ChopWindingInPlace(&splitwinding, plane->normal, plane->dist, 0); // PLANESIDE_EPSILON); + } //end for + return splitwinding; +} //end of the function AAS_SplitWinding +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_TestSplitPlane(tmp_area_t *tmparea, vec3_t normal, float dist, + int *facesplits, int *groundsplits, int *epsilonfaces) +{ + int j, side, front, back, planenum; + float d, d_front, d_back; + tmp_face_t *face; + winding_t *w; + + *facesplits = *groundsplits = *epsilonfaces = 0; + + planenum = FindFloatPlane(normal, dist); + + w = AAS_SplitWinding(tmparea, planenum); + if (!w) return false; + FreeWinding(w); + // + for (face = tmparea->tmpfaces; face; face = face->next[side]) + { + //side of the face the area is on + side = face->frontarea != tmparea; + + if ((face->planenum & ~1) == (planenum & ~1)) + { + Log_Print("AAS_TestSplitPlane: tried face plane as splitter\n"); + return false; + } //end if + w = face->winding; + //reset distance at front and back side of plane + d_front = d_back = 0; + //reset front and back flags + front = back = 0; + for (j = 0; j < w->numpoints; j++) + { + d = DotProduct(w->p[j], normal) - dist; + if (d > d_front) d_front = d; + if (d < d_back) d_back = d; + + if (d > 0.4) // PLANESIDE_EPSILON) + front = 1; + if (d < -0.4) // PLANESIDE_EPSILON) + back = 1; + } //end for + //check for an epsilon face + if ( (d_front > FACECLIP_EPSILON && d_front < FACE_EPSILON) + || (d_back < -FACECLIP_EPSILON && d_back > -FACE_EPSILON) ) + { + (*epsilonfaces)++; + } //end if + //if the face has points at both sides of the plane + if (front && back) + { + (*facesplits)++; + if (face->faceflags & FACE_GROUND) + { + (*groundsplits)++; + } //end if + } //end if + } //end for + return true; +} //end of the function AAS_TestSplitPlane +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_SplitArea(tmp_area_t *tmparea, int planenum, tmp_area_t **frontarea, tmp_area_t **backarea) +{ + int side; + tmp_area_t *facefrontarea, *facebackarea, *faceotherarea; + tmp_face_t *face, *frontface, *backface, *splitface, *nextface; + winding_t *splitwinding; + plane_t *splitplane; + +/* +#ifdef AW_DEBUG + int facesplits, groundsplits, epsilonface; + Log_Print("\n----------------------\n"); + Log_Print("splitting area %d\n", areanum); + Log_Print("with normal = \'%f %f %f\', dist = %f\n", normal[0], normal[1], normal[2], dist); + AAS_TestSplitPlane(areanum, normal, dist, + &facesplits, &groundsplits, &epsilonface); + Log_Print("face splits = %d\nground splits = %d\n", facesplits, groundsplits); + if (epsilonface) Log_Print("aaahh epsilon face\n"); +#endif //AW_DEBUG*/ + //the original area + + AAS_FlipAreaFaces(tmparea); + AAS_CheckArea(tmparea); + // + splitplane = &mapplanes[planenum]; +/* //create a split winding, first base winding for plane + splitwinding = BaseWindingForPlane(splitplane->normal, splitplane->dist); + //chop with all the faces of the area + for (face = tmparea->tmpfaces; face && splitwinding; face = face->next[side]) + { + //side of the face the original area was on + side = face->frontarea != tmparea->areanum; + plane = &mapplanes[face->planenum ^ side]; + ChopWindingInPlace(&splitwinding, plane->normal, plane->dist, 0); // PLANESIDE_EPSILON); + } //end for*/ + splitwinding = AAS_SplitWinding(tmparea, planenum); + if (!splitwinding) + { +/* +#ifdef DEBUG + AAS_TestSplitPlane(areanum, normal, dist, + &facesplits, &groundsplits, &epsilonface); + Log_Print("\nface splits = %d\nground splits = %d\n", facesplits, groundsplits); + if (epsilonface) Log_Print("aaahh epsilon face\n"); +#endif //DEBUG*/ + Error("AAS_SplitArea: no split winding when splitting area %d\n", tmparea->areanum); + } //end if + //create a split face + splitface = AAS_AllocTmpFace(); + //get the map plane + splitface->planenum = planenum; + //store the split winding + splitface->winding = splitwinding; + //the new front area + (*frontarea) = AAS_AllocTmpArea(); + (*frontarea)->presencetype = tmparea->presencetype; + (*frontarea)->contents = tmparea->contents; + (*frontarea)->modelnum = tmparea->modelnum; + (*frontarea)->tmpfaces = NULL; + //the new back area + (*backarea) = AAS_AllocTmpArea(); + (*backarea)->presencetype = tmparea->presencetype; + (*backarea)->contents = tmparea->contents; + (*backarea)->modelnum = tmparea->modelnum; + (*backarea)->tmpfaces = NULL; + //add the split face to the new areas + AAS_AddFaceSideToArea(splitface, 0, (*frontarea)); + AAS_AddFaceSideToArea(splitface, 1, (*backarea)); + + //split all the faces of the original area + for (face = tmparea->tmpfaces; face; face = nextface) + { + //side of the face the original area was on + side = face->frontarea != tmparea; + //next face of the original area + nextface = face->next[side]; + //front area of the face + facefrontarea = face->frontarea; + //back area of the face + facebackarea = face->backarea; + //remove the face from both the front and back areas + if (facefrontarea) AAS_RemoveFaceFromArea(face, facefrontarea); + if (facebackarea) AAS_RemoveFaceFromArea(face, facebackarea); + //split the face + AAS_SplitFace(face, splitplane->normal, splitplane->dist, &frontface, &backface); + //free the original face + AAS_FreeTmpFace(face); + //get the number of the area at the other side of the face + if (side) faceotherarea = facefrontarea; + else faceotherarea = facebackarea; + //if there is an area at the other side of the original face + if (faceotherarea) + { + if (frontface) AAS_AddFaceSideToArea(frontface, !side, faceotherarea); + if (backface) AAS_AddFaceSideToArea(backface, !side, faceotherarea); + } //end if + //add the front and back part left after splitting the original face to the new areas + if (frontface) AAS_AddFaceSideToArea(frontface, side, (*frontarea)); + if (backface) AAS_AddFaceSideToArea(backface, side, (*backarea)); + } //end for + + if (!(*frontarea)->tmpfaces) Log_Print("AAS_SplitArea: front area without faces\n"); + if (!(*backarea)->tmpfaces) Log_Print("AAS_SplitArea: back area without faces\n"); + + tmparea->invalid = true; +/* +#ifdef AW_DEBUG + for (i = 0, face = frontarea->tmpfaces; face; face = face->next[side]) + { + side = face->frontarea != frontarea->areanum; + i++; + } //end for + Log_Print("created front area %d with %d faces\n", frontarea->areanum, i); + + for (i = 0, face = backarea->tmpfaces; face; face = face->next[side]) + { + side = face->frontarea != backarea->areanum; + i++; + } //end for + Log_Print("created back area %d with %d faces\n", backarea->areanum, i); +#endif //AW_DEBUG*/ + + AAS_FlipAreaFaces((*frontarea)); + AAS_FlipAreaFaces((*backarea)); + // + AAS_CheckArea((*frontarea)); + AAS_CheckArea((*backarea)); +} //end of the function AAS_SplitArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_FindBestAreaSplitPlane(tmp_area_t *tmparea, vec3_t normal, float *dist) +{ + int side1, side2; + int foundsplitter, facesplits, groundsplits, epsilonfaces, bestepsilonfaces; + float bestvalue, value; + tmp_face_t *face1, *face2; + vec3_t tmpnormal, invgravity; + float tmpdist; + + //get inverse of gravity direction + VectorCopy(cfg.phys_gravitydirection, invgravity); + VectorInverse(invgravity); + + foundsplitter = false; + bestvalue = -999999; + bestepsilonfaces = 0; + // +#ifdef AW_DEBUG + Log_Print("finding split plane for area %d\n", tmparea->areanum); +#endif //AW_DEBUG + for (face1 = tmparea->tmpfaces; face1; face1 = face1->next[side1]) + { + //side of the face the area is on + side1 = face1->frontarea != tmparea; + // + if (WindingIsTiny(face1->winding)) + { + Log_Write("gsubdiv: area %d has a tiny winding\r\n", tmparea->areanum); + continue; + } //end if + //if the face isn't a gap or ground there's no split edge + if (!(face1->faceflags & FACE_GROUND) && !AAS_GapFace(face1, side1)) continue; + // + for (face2 = face1->next[side1]; face2; face2 = face2->next[side2]) + { + //side of the face the area is on + side2 = face2->frontarea != tmparea; + // + if (WindingIsTiny(face1->winding)) + { + Log_Write("gsubdiv: area %d has a tiny winding\r\n", tmparea->areanum); + continue; + } //end if + //if the face isn't a gap or ground there's no split edge + if (!(face2->faceflags & FACE_GROUND) && !AAS_GapFace(face2, side2)) continue; + //only split between gaps and ground + if (!(((face1->faceflags & FACE_GROUND) && AAS_GapFace(face2, side2)) || + ((face2->faceflags & FACE_GROUND) && AAS_GapFace(face1, side1)))) continue; + //find a plane seperating the windings of the faces + if (!FindPlaneSeperatingWindings(face1->winding, face2->winding, invgravity, + tmpnormal, &tmpdist)) continue; +#ifdef AW_DEBUG + Log_Print("normal = \'%f %f %f\', dist = %f\n", + tmpnormal[0], tmpnormal[1], tmpnormal[2], tmpdist); +#endif //AW_DEBUG + //get metrics for this vertical plane + if (!AAS_TestSplitPlane(tmparea, tmpnormal, tmpdist, + &facesplits, &groundsplits, &epsilonfaces)) + { + continue; + } //end if +#ifdef AW_DEBUG + Log_Print("face splits = %d\nground splits = %d\n", + facesplits, groundsplits); +#endif //AW_DEBUG + value = 100 - facesplits - 2 * groundsplits; + //avoid epsilon faces + value += epsilonfaces * -1000; + if (value > bestvalue) + { + VectorCopy(tmpnormal, normal); + *dist = tmpdist; + bestvalue = value; + bestepsilonfaces = epsilonfaces; + foundsplitter = true; + } //end if + } //end for + } //end for + if (bestepsilonfaces) + { + Log_Write("found %d epsilon faces trying to split area %d\r\n", + epsilonfaces, tmparea->areanum); + } //end else + return foundsplitter; +} //end of the function AAS_FindBestAreaSplitPlane +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +tmp_node_t *AAS_SubdivideArea_r(tmp_node_t *tmpnode) +{ + int planenum; + tmp_area_t *frontarea, *backarea; + tmp_node_t *tmpnode1, *tmpnode2; + vec3_t normal; + float dist; + + if (AAS_FindBestAreaSplitPlane(tmpnode->tmparea, normal, &dist)) + { + qprintf("\r%6d", ++numgravitationalsubdivisions); + // + planenum = FindFloatPlane(normal, dist); + //split the area + AAS_SplitArea(tmpnode->tmparea, planenum, &frontarea, &backarea); + // + tmpnode->tmparea = NULL; + tmpnode->planenum = FindFloatPlane(normal, dist); + // + tmpnode1 = AAS_AllocTmpNode(); + tmpnode1->planenum = 0; + tmpnode1->tmparea = frontarea; + // + tmpnode2 = AAS_AllocTmpNode(); + tmpnode2->planenum = 0; + tmpnode2->tmparea = backarea; + //subdivide the areas created by splitting recursively + tmpnode->children[0] = AAS_SubdivideArea_r(tmpnode1); + tmpnode->children[1] = AAS_SubdivideArea_r(tmpnode2); + } //end if + return tmpnode; +} //end of the function AAS_SubdivideArea_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +tmp_node_t *AAS_GravitationalSubdivision_r(tmp_node_t *tmpnode) +{ + //if this is a solid leaf + if (!tmpnode) return NULL; + //negative so it's an area + if (tmpnode->tmparea) return AAS_SubdivideArea_r(tmpnode); + //do the children recursively + tmpnode->children[0] = AAS_GravitationalSubdivision_r(tmpnode->children[0]); + tmpnode->children[1] = AAS_GravitationalSubdivision_r(tmpnode->children[1]); + return tmpnode; +} //end of the function AAS_GravitationalSubdivision_r +//=========================================================================== +// NOTE: merge faces and melt edges first +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_GravitationalSubdivision(void) +{ + Log_Write("AAS_GravitationalSubdivision\r\n"); + numgravitationalsubdivisions = 0; + qprintf("%6i gravitational subdivisions", numgravitationalsubdivisions); + //start with the head node + AAS_GravitationalSubdivision_r(tmpaasworld.nodes); + qprintf("\n"); + Log_Write("%6i gravitational subdivisions\r\n", numgravitationalsubdivisions); +} //end of the function AAS_GravitationalSubdivision +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +tmp_node_t *AAS_RefreshLadderSubdividedTree_r(tmp_node_t *tmpnode, tmp_area_t *tmparea, + tmp_node_t *tmpnode1, tmp_node_t *tmpnode2, int planenum) +{ + //if this is a solid leaf + if (!tmpnode) return NULL; + //negative so it's an area + if (tmpnode->tmparea) + { + if (tmpnode->tmparea == tmparea) + { + tmpnode->tmparea = NULL; + tmpnode->planenum = planenum; + tmpnode->children[0] = tmpnode1; + tmpnode->children[1] = tmpnode2; + } //end if + return tmpnode; + } //end if + //do the children recursively + tmpnode->children[0] = AAS_RefreshLadderSubdividedTree_r(tmpnode->children[0], + tmparea, tmpnode1, tmpnode2, planenum); + tmpnode->children[1] = AAS_RefreshLadderSubdividedTree_r(tmpnode->children[1], + tmparea, tmpnode1, tmpnode2, planenum); + return tmpnode; +} //end of the function AAS_RefreshLadderSubdividedTree_r +//=========================================================================== +// find an area with ladder faces and ground faces that are not connected +// split the area with a horizontal plane at the lowest vertex of all +// ladder faces in the area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +tmp_node_t *AAS_LadderSubdivideArea_r(tmp_node_t *tmpnode) +{ + int side1, i, planenum; + int foundladderface, foundgroundface; + float dist; + tmp_area_t *tmparea, *frontarea, *backarea; + tmp_face_t *face1; + tmp_node_t *tmpnode1, *tmpnode2; + vec3_t lowestpoint, normal = {0, 0, 1}; + plane_t *plane; + winding_t *w; + + tmparea = tmpnode->tmparea; + //skip areas with a liquid + if (tmparea->contents & (AREACONTENTS_WATER + | AREACONTENTS_LAVA + | AREACONTENTS_SLIME)) return tmpnode; + //must be possible to stand in the area + if (!(tmparea->presencetype & PRESENCE_NORMAL)) return tmpnode; + // + foundladderface = false; + foundgroundface = false; + lowestpoint[2] = 99999; + // + for (face1 = tmparea->tmpfaces; face1; face1 = face1->next[side1]) + { + //side of the face the area is on + side1 = face1->frontarea != tmparea; + //if the face is a ladder face + if (face1->faceflags & FACE_LADDER) + { + plane = &mapplanes[face1->planenum]; + //the ladder face plane should be pretty much vertical + if (DotProduct(plane->normal, normal) > -0.1) + { + foundladderface = true; + //find lowest point + for (i = 0; i < face1->winding->numpoints; i++) + { + if (face1->winding->p[i][2] < lowestpoint[2]) + { + VectorCopy(face1->winding->p[i], lowestpoint); + } //end if + } //end for + } //end if + } //end if + else if (face1->faceflags & FACE_GROUND) + { + foundgroundface = true; + } //end else if + } //end for + // + if ((!foundladderface) || (!foundgroundface)) return tmpnode; + // + for (face1 = tmparea->tmpfaces; face1; face1 = face1->next[side1]) + { + //side of the face the area is on + side1 = face1->frontarea != tmparea; + //if the face isn't a ground face + if (!(face1->faceflags & FACE_GROUND)) continue; + //the ground plane + plane = &mapplanes[face1->planenum]; + //get the difference between the ground plane and the lowest point + dist = DotProduct(plane->normal, lowestpoint) - plane->dist; + //if the lowest point is very near one of the ground planes + if (dist > -1 && dist < 1) + { + return tmpnode; + } //end if + } //end for + // + dist = DotProduct(normal, lowestpoint); + planenum = FindFloatPlane(normal, dist); + // + w = AAS_SplitWinding(tmparea, planenum); + if (!w) return tmpnode; + FreeWinding(w); + //split the area with a horizontal plane through the lowest point + qprintf("\r%6d", ++numladdersubdivisions); + // + AAS_SplitArea(tmparea, planenum, &frontarea, &backarea); + // + tmpnode->tmparea = NULL; + tmpnode->planenum = planenum; + // + tmpnode1 = AAS_AllocTmpNode(); + tmpnode1->planenum = 0; + tmpnode1->tmparea = frontarea; + // + tmpnode2 = AAS_AllocTmpNode(); + tmpnode2->planenum = 0; + tmpnode2->tmparea = backarea; + //subdivide the areas created by splitting recursively + tmpnode->children[0] = AAS_LadderSubdivideArea_r(tmpnode1); + tmpnode->children[1] = AAS_LadderSubdivideArea_r(tmpnode2); + //refresh the tree + AAS_RefreshLadderSubdividedTree_r(tmpaasworld.nodes, tmparea, tmpnode1, tmpnode2, planenum); + // + return tmpnode; +} //end of the function AAS_LadderSubdivideArea_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +tmp_node_t *AAS_LadderSubdivision_r(tmp_node_t *tmpnode) +{ + //if this is a solid leaf + if (!tmpnode) return 0; + //negative so it's an area + if (tmpnode->tmparea) return AAS_LadderSubdivideArea_r(tmpnode); + //do the children recursively + tmpnode->children[0] = AAS_LadderSubdivision_r(tmpnode->children[0]); + tmpnode->children[1] = AAS_LadderSubdivision_r(tmpnode->children[1]); + return tmpnode; +} //end of the function AAS_LadderSubdivision_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_LadderSubdivision(void) +{ + Log_Write("AAS_LadderSubdivision\r\n"); + numladdersubdivisions = 0; + qprintf("%6i ladder subdivisions", numladdersubdivisions); + //start with the head node + AAS_LadderSubdivision_r(tmpaasworld.nodes); + // + qprintf("\n"); + Log_Write("%6i ladder subdivisions\r\n", numladdersubdivisions); +} //end of the function AAS_LadderSubdivision diff --git a/code/bspc/aas_gsubdiv.h b/code/bspc/aas_gsubdiv.h index 0156a9a..bb2b095 100755 --- a/code/bspc/aas_gsubdiv.h +++ b/code/bspc/aas_gsubdiv.h @@ -1,25 +1,25 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -//works with the global tmpaasworld -void AAS_GravitationalSubdivision(void); -void AAS_LadderSubdivision(void); +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +//works with the global tmpaasworld +void AAS_GravitationalSubdivision(void); +void AAS_LadderSubdivision(void); diff --git a/code/bspc/aas_map.c b/code/bspc/aas_map.c index da16d1b..10c616a 100755 --- a/code/bspc/aas_map.c +++ b/code/bspc/aas_map.c @@ -1,849 +1,849 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "qbsp.h" -#include "l_mem.h" -#include "../botlib/aasfile.h" //aas_bbox_t -#include "aas_store.h" //AAS_MAX_BBOXES -#include "aas_cfg.h" -#include "../game/surfaceflags.h" - -#define SPAWNFLAG_NOT_EASY 0x00000100 -#define SPAWNFLAG_NOT_MEDIUM 0x00000200 -#define SPAWNFLAG_NOT_HARD 0x00000400 -#define SPAWNFLAG_NOT_DEATHMATCH 0x00000800 -#define SPAWNFLAG_NOT_COOP 0x00001000 - -#define STATE_TOP 0 -#define STATE_BOTTOM 1 -#define STATE_UP 2 -#define STATE_DOWN 3 - -#define DOOR_START_OPEN 1 -#define DOOR_REVERSE 2 -#define DOOR_CRUSHER 4 -#define DOOR_NOMONSTER 8 -#define DOOR_TOGGLE 32 -#define DOOR_X_AXIS 64 -#define DOOR_Y_AXIS 128 - -#define BBOX_NORMAL_EPSILON 0.0001 - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -vec_t BoxOriginDistanceFromPlane(vec3_t normal, vec3_t mins, vec3_t maxs, int side) -{ - vec3_t v1, v2; - int i; - - if (side) - { - for (i = 0; i < 3; i++) - { - if (normal[i] > BBOX_NORMAL_EPSILON) v1[i] = maxs[i]; - else if (normal[i] < -BBOX_NORMAL_EPSILON) v1[i] = mins[i]; - else v1[i] = 0; - } //end for - } //end if - else - { - for (i = 0; i < 3; i++) - { - if (normal[i] > BBOX_NORMAL_EPSILON) v1[i] = mins[i]; - else if (normal[i] < -BBOX_NORMAL_EPSILON) v1[i] = maxs[i]; - else v1[i] = 0; - } //end for - } //end else - VectorCopy(normal, v2); - VectorInverse(v2); - return DotProduct(v1, v2); -} //end of the function BoxOriginDistanceFromPlane -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -vec_t CapsuleOriginDistanceFromPlane(vec3_t normal, vec3_t mins, vec3_t maxs) -{ - float offset_up, offset_down, width, radius; - - width = maxs[0] - mins[0]; - // if the box is less high then it is wide - if (maxs[2] - mins[2] < width) { - width = maxs[2] - mins[2]; - } - radius = width * 0.5; - // offset to upper and lower sphere - offset_up = maxs[2] - radius; - offset_down = -mins[2] - radius; - - // if normal points upward - if ( normal[2] > 0 ) { - // touches lower sphere first - return normal[2] * offset_down + radius; - } - else { - // touched upper sphere first - return -normal[2] * offset_up + radius; - } -} -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_ExpandMapBrush(mapbrush_t *brush, vec3_t mins, vec3_t maxs) -{ - int sn; - float dist; - side_t *s; - plane_t *plane; - - for (sn = 0; sn < brush->numsides; sn++) - { - s = brush->original_sides + sn; - plane = &mapplanes[s->planenum]; - dist = plane->dist; - if (capsule_collision) { - dist += CapsuleOriginDistanceFromPlane(plane->normal, mins, maxs); - } - else { - dist += BoxOriginDistanceFromPlane(plane->normal, mins, maxs, 0); - } - s->planenum = FindFloatPlane(plane->normal, dist); - //the side isn't a bevel after expanding - s->flags &= ~SFL_BEVEL; - //don't skip the surface - s->surf &= ~SURF_SKIP; - //make sure the texinfo is not TEXINFO_NODE - //when player clip contents brushes are read from the bsp tree - //they have the texinfo field set to TEXINFO_NODE - //s->texinfo = 0; - } //end for -} //end of the function AAS_ExpandMapBrush -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_SetTexinfo(mapbrush_t *brush) -{ - int n; - side_t *side; - - if (brush->contents & (CONTENTS_LADDER - | CONTENTS_AREAPORTAL - | CONTENTS_CLUSTERPORTAL - | CONTENTS_TELEPORTER - | CONTENTS_JUMPPAD - | CONTENTS_DONOTENTER - | CONTENTS_WATER - | CONTENTS_LAVA - | CONTENTS_SLIME - | CONTENTS_WINDOW - | CONTENTS_PLAYERCLIP)) - { - //we just set texinfo to 0 because these brush sides MUST be used as - //bsp splitters textured or not textured - for (n = 0; n < brush->numsides; n++) - { - side = brush->original_sides + n; - //side->flags |= SFL_TEXTURED|SFL_VISIBLE; - side->texinfo = 0; - } //end for - } //end if - else - { - //only use brush sides as splitters if they are textured - //texinfo of non-textured sides will be set to TEXINFO_NODE - for (n = 0; n < brush->numsides; n++) - { - side = brush->original_sides + n; - //don't use side as splitter (set texinfo to TEXINFO_NODE) if not textured - if (side->flags & (SFL_TEXTURED|SFL_BEVEL)) side->texinfo = 0; - else side->texinfo = TEXINFO_NODE; - } //end for - } //end else -} //end of the function AAS_SetTexinfo -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void FreeBrushWindings(mapbrush_t *brush) -{ - int n; - side_t *side; - // - for (n = 0; n < brush->numsides; n++) - { - side = brush->original_sides + n; - // - if (side->winding) FreeWinding(side->winding); - } //end for -} //end of the function FreeBrushWindings -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_AddMapBrushSide(mapbrush_t *brush, int planenum) -{ - side_t *side; - // - if (nummapbrushsides >= MAX_MAPFILE_BRUSHSIDES) - Error ("MAX_MAPFILE_BRUSHSIDES"); - // - side = brush->original_sides + brush->numsides; - side->original = NULL; - side->winding = NULL; - side->contents = brush->contents; - side->flags &= ~(SFL_BEVEL|SFL_VISIBLE); - side->surf = 0; - side->planenum = planenum; - side->texinfo = 0; - // - nummapbrushsides++; - brush->numsides++; -} //end of the function AAS_AddMapBrushSide -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_FixMapBrush(mapbrush_t *brush) -{ - int i, j, planenum; - float dist; - winding_t *w; - plane_t *plane, *plane1, *plane2; - side_t *side; - vec3_t normal; - - //calculate the brush bounds - ClearBounds(brush->mins, brush->maxs); - for (i = 0; i < brush->numsides; i++) - { - plane = &mapplanes[brush->original_sides[i].planenum]; - w = BaseWindingForPlane(plane->normal, plane->dist); - for (j = 0; j < brush->numsides && w; j++) - { - if (i == j) continue; - //there are no brush bevels marked but who cares :) - if (brush->original_sides[j].flags & SFL_BEVEL) continue; - plane = &mapplanes[brush->original_sides[j].planenum^1]; - ChopWindingInPlace(&w, plane->normal, plane->dist, 0); //CLIP_EPSILON); - } //end for - - side = &brush->original_sides[i]; - side->winding = w; - if (w) - { - for (j = 0; j < w->numpoints; j++) - { - AddPointToBounds(w->p[j], brush->mins, brush->maxs); - } //end for - } //end if - } //end for - // - for (i = 0; i < brush->numsides; i++) - { - for (j = 0; j < brush->numsides; j++) - { - if (i == j) continue; - plane1 = &mapplanes[brush->original_sides[i].planenum]; - plane2 = &mapplanes[brush->original_sides[j].planenum]; - if (WindingsNonConvex(brush->original_sides[i].winding, - brush->original_sides[j].winding, - plane1->normal, plane2->normal, - plane1->dist, plane2->dist)) - { - Log_Print("non convex brush"); - } //end if - } //end for - } //end for - - //NOW close the fucking brush!! - for (i = 0; i < 3; i++) - { - if (brush->mins[i] < -MAX_MAP_BOUNDS) - { - VectorClear(normal); - normal[i] = -1; - dist = MAX_MAP_BOUNDS - 10; - planenum = FindFloatPlane(normal, dist); - // - Log_Print("mins out of range: added extra brush side\n"); - AAS_AddMapBrushSide(brush, planenum); - } //end if - if (brush->maxs[i] > MAX_MAP_BOUNDS) - { - VectorClear(normal); - normal[i] = 1; - dist = MAX_MAP_BOUNDS - 10; - planenum = FindFloatPlane(normal, dist); - // - Log_Print("maxs out of range: added extra brush side\n"); - AAS_AddMapBrushSide(brush, planenum); - } //end if - if (brush->mins[i] > MAX_MAP_BOUNDS || brush->maxs[i] < -MAX_MAP_BOUNDS) - { - Log_Print("entity %i, brush %i: no visible sides on brush\n", brush->entitynum, brush->brushnum); - } //end if - } //end for - //free all the windings - FreeBrushWindings(brush); -} //end of the function AAS_FixMapBrush -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean AAS_MakeBrushWindings(mapbrush_t *ob) -{ - int i, j; - winding_t *w; - side_t *side; - plane_t *plane, *plane1, *plane2; - - ClearBounds (ob->mins, ob->maxs); - - for (i = 0; i < ob->numsides; i++) - { - plane = &mapplanes[ob->original_sides[i].planenum]; - w = BaseWindingForPlane(plane->normal, plane->dist); - for (j = 0; j numsides && w; j++) - { - if (i == j) continue; - if (ob->original_sides[j].flags & SFL_BEVEL) continue; - plane = &mapplanes[ob->original_sides[j].planenum^1]; - ChopWindingInPlace(&w, plane->normal, plane->dist, 0); //CLIP_EPSILON); - } - - side = &ob->original_sides[i]; - side->winding = w; - if (w) - { - side->flags |= SFL_VISIBLE; - for (j = 0; j < w->numpoints; j++) - AddPointToBounds (w->p[j], ob->mins, ob->maxs); - } - } - //check if the brush is convex - for (i = 0; i < ob->numsides; i++) - { - for (j = 0; j < ob->numsides; j++) - { - if (i == j) continue; - plane1 = &mapplanes[ob->original_sides[i].planenum]; - plane2 = &mapplanes[ob->original_sides[j].planenum]; - if (WindingsNonConvex(ob->original_sides[i].winding, - ob->original_sides[j].winding, - plane1->normal, plane2->normal, - plane1->dist, plane2->dist)) - { - Log_Print("non convex brush"); - } //end if - } //end for - } //end for - //check for out of bound brushes - for (i = 0; i < 3; i++) - { - //IDBUG: all the indexes into the mins and maxs were zero (not using i) - if (ob->mins[i] < -MAX_MAP_BOUNDS || ob->maxs[i] > MAX_MAP_BOUNDS) - { - Log_Print("entity %i, brush %i: bounds out of range\n", ob->entitynum, ob->brushnum); - Log_Print("ob->mins[%d] = %f, ob->maxs[%d] = %f\n", i, ob->mins[i], i, ob->maxs[i]); - ob->numsides = 0; //remove the brush - break; - } //end if - if (ob->mins[i] > MAX_MAP_BOUNDS || ob->maxs[i] < -MAX_MAP_BOUNDS) - { - Log_Print("entity %i, brush %i: no visible sides on brush\n", ob->entitynum, ob->brushnum); - Log_Print("ob->mins[%d] = %f, ob->maxs[%d] = %f\n", i, ob->mins[i], i, ob->maxs[i]); - ob->numsides = 0; //remove the brush - break; - } //end if - } //end for - return true; -} //end of the function AAS_MakeBrushWindings -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -mapbrush_t *AAS_CopyMapBrush(mapbrush_t *brush, entity_t *mapent) -{ - int n; - mapbrush_t *newbrush; - side_t *side, *newside; - - if (nummapbrushes >= MAX_MAPFILE_BRUSHES) - Error ("MAX_MAPFILE_BRUSHES"); - - newbrush = &mapbrushes[nummapbrushes]; - newbrush->original_sides = &brushsides[nummapbrushsides]; - newbrush->entitynum = brush->entitynum; - newbrush->brushnum = nummapbrushes - mapent->firstbrush; - newbrush->numsides = brush->numsides; - newbrush->contents = brush->contents; - - //copy the sides - for (n = 0; n < brush->numsides; n++) - { - if (nummapbrushsides >= MAX_MAPFILE_BRUSHSIDES) - Error ("MAX_MAPFILE_BRUSHSIDES"); - side = brush->original_sides + n; - - newside = newbrush->original_sides + n; - newside->original = NULL; - newside->winding = NULL; - newside->contents = side->contents; - newside->flags = side->flags; - newside->surf = side->surf; - newside->planenum = side->planenum; - newside->texinfo = side->texinfo; - nummapbrushsides++; - } //end for - // - nummapbrushes++; - mapent->numbrushes++; - return newbrush; -} //end of the function AAS_CopyMapBrush -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int mark_entities[MAX_MAP_ENTITIES]; - -int AAS_AlwaysTriggered_r(char *targetname) -{ - int i; - - if (!strlen(targetname)) { - return false; - } - // - for (i = 0; i < num_entities; i++) { - // if the entity will activate the given targetname - if ( !strcmp(targetname, ValueForKey(&entities[i], "target")) ) { - // if this activator is present in deathmatch - if (!(atoi(ValueForKey(&entities[i], "spawnflags")) & SPAWNFLAG_NOT_DEATHMATCH)) { - // if it is a trigger_always entity - if (!strcmp("trigger_always", ValueForKey(&entities[i], "classname"))) { - return true; - } - // check for possible trigger_always entities activating this entity - if ( mark_entities[i] ) { - Warning( "entity %d, classname %s has recursive targetname %s\n", i, - ValueForKey(&entities[i], "classname"), targetname ); - return false; - } - mark_entities[i] = true; - if ( AAS_AlwaysTriggered_r(ValueForKey(&entities[i], "targetname")) ) { - return true; - } - } - } - } - return false; -} - -int AAS_AlwaysTriggered(char *targetname) { - memset( mark_entities, 0, sizeof(mark_entities) ); - return AAS_AlwaysTriggered_r( targetname ); -} -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_ValidEntity(entity_t *mapent) -{ - int i; - char target[1024]; - - //all world brushes are used for AAS - if (mapent == &entities[0]) - { - return true; - } //end if - //some of the func_wall brushes are also used for AAS - else if (!strcmp("func_wall", ValueForKey(mapent, "classname"))) - { - //Log_Print("found func_wall entity %d\n", mapent - entities); - //if the func wall is used in deathmatch - if (!(atoi(ValueForKey(mapent, "spawnflags")) & SPAWNFLAG_NOT_DEATHMATCH)) - { - //Log_Print("func_wall USED in deathmatch mode %d\n", atoi(ValueForKey(mapent, "spawnflags"))); - return true; - } //end if - } //end else if - else if (!strcmp("func_door_rotating", ValueForKey(mapent, "classname"))) - { - //if the func_door_rotating is present in deathmatch - if (!(atoi(ValueForKey(mapent, "spawnflags")) & SPAWNFLAG_NOT_DEATHMATCH)) - { - //if the func_door_rotating is always activated in deathmatch - if (AAS_AlwaysTriggered(ValueForKey(mapent, "targetname"))) - { - //Log_Print("found func_door_rotating in deathmatch\ntargetname %s\n", ValueForKey(mapent, "targetname")); - return true; - } //end if - } //end if - } //end else if - else if (!strcmp("trigger_hurt", ValueForKey(mapent, "classname"))) - { - //"dmg" is the damage, for instance: "dmg" "666" - return true; - } //end else if - else if (!strcmp("trigger_push", ValueForKey(mapent, "classname"))) - { - return true; - } //end else if - else if (!strcmp("trigger_multiple", ValueForKey(mapent, "classname"))) - { - //find out if the trigger_multiple is pointing to a target_teleporter - strcpy(target, ValueForKey(mapent, "target")); - for (i = 0; i < num_entities; i++) - { - //if the entity will activate the given targetname - if (!strcmp(target, ValueForKey(&entities[i], "targetname"))) - { - if (!strcmp("target_teleporter", ValueForKey(&entities[i], "classname"))) - { - return true; - } //end if - } //end if - } //end for - } //end else if - else if (!strcmp("trigger_teleport", ValueForKey(mapent, "classname"))) - { - return true; - } //end else if - else if (!strcmp("func_static", ValueForKey(mapent, "classname"))) - { - //FIXME: easy/medium/hard/deathmatch specific? - return true; - } //end else if - else if (!strcmp("func_door", ValueForKey(mapent, "classname"))) - { - return true; - } //end else if - return false; -} //end of the function AAS_ValidEntity -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_TransformPlane(int planenum, vec3_t origin, vec3_t angles) -{ - float newdist, matrix[3][3]; - vec3_t normal; - - //rotate the node plane - VectorCopy(mapplanes[planenum].normal, normal); - CreateRotationMatrix(angles, matrix); - RotatePoint(normal, matrix); - newdist = mapplanes[planenum].dist + DotProduct(normal, origin); - return FindFloatPlane(normal, newdist); -} //end of the function AAS_TransformPlane -//=========================================================================== -// this function sets the func_rotating_door in it's final position -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_PositionFuncRotatingBrush(entity_t *mapent, mapbrush_t *brush) -{ - int spawnflags, i; - float distance; - vec3_t movedir, angles, pos1, pos2; - side_t *s; - - spawnflags = FloatForKey(mapent, "spawnflags"); - VectorClear(movedir); - if (spawnflags & DOOR_X_AXIS) - movedir[2] = 1.0; //roll - else if (spawnflags & DOOR_Y_AXIS) - movedir[0] = 1.0; //pitch - else // Z_AXIS - movedir[1] = 1.0; //yaw - - // check for reverse rotation - if (spawnflags & DOOR_REVERSE) - VectorInverse(movedir); - - distance = FloatForKey(mapent, "distance"); - if (!distance) distance = 90; - - GetVectorForKey(mapent, "angles", angles); - VectorCopy(angles, pos1); - VectorMA(angles, -distance, movedir, pos2); - // if it starts open, switch the positions - if (spawnflags & DOOR_START_OPEN) - { - VectorCopy(pos2, angles); - VectorCopy(pos1, pos2); - VectorCopy(angles, pos1); - VectorInverse(movedir); - } //end if - // - for (i = 0; i < brush->numsides; i++) - { - s = &brush->original_sides[i]; - s->planenum = AAS_TransformPlane(s->planenum, mapent->origin, pos2); - } //end for - // - FreeBrushWindings(brush); - AAS_MakeBrushWindings(brush); - AddBrushBevels(brush); - FreeBrushWindings(brush); -} //end of the function AAS_PositionFuncRotatingBrush -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_PositionBrush(entity_t *mapent, mapbrush_t *brush) -{ - side_t *s; - float newdist; - int i, notteam; - char *model; - - if (!strcmp(ValueForKey(mapent, "classname"), "func_door_rotating")) - { - AAS_PositionFuncRotatingBrush(mapent, brush); - } //end if - else - { - if (mapent->origin[0] || mapent->origin[1] || mapent->origin[2]) - { - for (i = 0; i < brush->numsides; i++) - { - s = &brush->original_sides[i]; - newdist = mapplanes[s->planenum].dist + - DotProduct(mapplanes[s->planenum].normal, mapent->origin); - s->planenum = FindFloatPlane(mapplanes[s->planenum].normal, newdist); - } //end for - } //end if - //if it's a trigger hurt - if (!strcmp("trigger_hurt", ValueForKey(mapent, "classname"))) - { - notteam = FloatForKey(mapent, "bot_notteam"); - if ( notteam == 1 ) { - brush->contents |= CONTENTS_NOTTEAM1; - } - else if ( notteam == 2 ) { - brush->contents |= CONTENTS_NOTTEAM2; - } - else { - // always avoid so set lava contents - brush->contents |= CONTENTS_LAVA; - } - } //end if - // - else if (!strcmp("trigger_push", ValueForKey(mapent, "classname"))) - { - //set the jumppad contents - brush->contents = CONTENTS_JUMPPAD; - //Log_Print("found trigger_push brush\n"); - } //end if - // - else if (!strcmp("trigger_multiple", ValueForKey(mapent, "classname"))) - { - //set teleporter contents - brush->contents = CONTENTS_TELEPORTER; - //Log_Print("found trigger_multiple teleporter brush\n"); - } //end if - // - else if (!strcmp("trigger_teleport", ValueForKey(mapent, "classname"))) - { - //set teleporter contents - brush->contents = CONTENTS_TELEPORTER; - //Log_Print("found trigger_teleport teleporter brush\n"); - } //end if - else if (!strcmp("func_door", ValueForKey(mapent, "classname"))) - { - //set mover contents - brush->contents = CONTENTS_MOVER; - //get the model number - model = ValueForKey(mapent, "model"); - brush->modelnum = atoi(model+1); - } //end if - } //end else -} //end of the function AAS_PositionBrush -//=========================================================================== -// uses the global cfg_t cfg -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_CreateMapBrushes(mapbrush_t *brush, entity_t *mapent, int addbevels) -{ - int i; - //side_t *s; - mapbrush_t *bboxbrushes[16]; - - //if the brushes are not from an entity used for AAS - if (!AAS_ValidEntity(mapent)) - { - nummapbrushsides -= brush->numsides; - brush->numsides = 0; - return; - } //end if - // - AAS_PositionBrush(mapent, brush); - //from all normal solid brushes only the textured brush sides will - //be used as bsp splitters, so set the right texinfo reference here - AAS_SetTexinfo(brush); - //remove contents detail flag, otherwise player clip contents won't be - //bsped correctly for AAS! - brush->contents &= ~CONTENTS_DETAIL; - //if the brush has contents area portal it should be the only contents - if (brush->contents & (CONTENTS_AREAPORTAL|CONTENTS_CLUSTERPORTAL)) - { - brush->contents = CONTENTS_CLUSTERPORTAL; - brush->leafnum = -1; - } //end if - //window and playerclip are used for player clipping, make them solid - if (brush->contents & (CONTENTS_WINDOW | CONTENTS_PLAYERCLIP)) - { - // - brush->contents &= ~(CONTENTS_WINDOW | CONTENTS_PLAYERCLIP); - brush->contents |= CONTENTS_SOLID; - brush->leafnum = -1; - } //end if - // - if (brush->contents & CONTENTS_BOTCLIP) - { - brush->contents = CONTENTS_SOLID; - brush->leafnum = -1; - } //end if - // - //Log_Write("brush %d contents = ", brush->brushnum); - //PrintContents(brush->contents); - //Log_Write("\r\n"); - //if not one of the following brushes then the brush is NOT used for AAS - if (!(brush->contents & (CONTENTS_SOLID - | CONTENTS_LADDER - | CONTENTS_CLUSTERPORTAL - | CONTENTS_DONOTENTER - | CONTENTS_TELEPORTER - | CONTENTS_JUMPPAD - | CONTENTS_WATER - | CONTENTS_LAVA - | CONTENTS_SLIME - | CONTENTS_MOVER - ))) - { - nummapbrushsides -= brush->numsides; - brush->numsides = 0; - return; - } //end if - //fix the map brush - //AAS_FixMapBrush(brush); - //if brush bevels should be added (for real map brushes, not bsp map brushes) - if (addbevels) - { - //NOTE: we first have to get the mins and maxs of the brush before - // creating the brush bevels... the mins and maxs are used to - // create them. so we call MakeBrushWindings to get the mins - // and maxs and then after creating the bevels we free the - // windings because they are created for all sides (including - // bevels) a little later - AAS_MakeBrushWindings(brush); - AddBrushBevels(brush); - FreeBrushWindings(brush); - } //end if - //NOTE: add the brush to the WORLD entity!!! - mapent = &entities[0]; - //there's at least one new brush for now - nummapbrushes++; - mapent->numbrushes++; - //liquid brushes are expanded for the maximum possible bounding box - if (brush->contents & (CONTENTS_WATER - | CONTENTS_LAVA - | CONTENTS_SLIME - | CONTENTS_TELEPORTER - | CONTENTS_JUMPPAD - | CONTENTS_DONOTENTER - | CONTENTS_MOVER - )) - { - brush->expansionbbox = 0; - //NOTE: the first bounding box is the max - //FIXME: use max bounding box created from all bboxes - AAS_ExpandMapBrush(brush, cfg.bboxes[0].mins, cfg.bboxes[0].maxs); - AAS_MakeBrushWindings(brush); - } //end if - //area portal brushes are NOT expanded - else if (brush->contents & CONTENTS_CLUSTERPORTAL) - { - brush->expansionbbox = 0; - //NOTE: the first bounding box is the max - //FIXME: use max bounding box created from all bboxes - AAS_ExpandMapBrush(brush, cfg.bboxes[0].mins, cfg.bboxes[0].maxs); - AAS_MakeBrushWindings(brush); - } //end if - //all solid brushes are expanded for all bounding boxes - else if (brush->contents & (CONTENTS_SOLID - | CONTENTS_LADDER - )) - { - //brush for the first bounding box - bboxbrushes[0] = brush; - //make a copy for the other bounding boxes - for (i = 1; i < cfg.numbboxes; i++) - { - bboxbrushes[i] = AAS_CopyMapBrush(brush, mapent); - } //end for - //expand every brush for it's bounding box and create windings - for (i = 0; i < cfg.numbboxes; i++) - { - AAS_ExpandMapBrush(bboxbrushes[i], cfg.bboxes[i].mins, cfg.bboxes[i].maxs); - bboxbrushes[i]->expansionbbox = cfg.bboxes[i].presencetype; - AAS_MakeBrushWindings(bboxbrushes[i]); - } //end for - } //end else -} //end of the function AAS_CreateMapBrushes +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "qbsp.h" +#include "l_mem.h" +#include "../botlib/aasfile.h" //aas_bbox_t +#include "aas_store.h" //AAS_MAX_BBOXES +#include "aas_cfg.h" +#include "../game/surfaceflags.h" + +#define SPAWNFLAG_NOT_EASY 0x00000100 +#define SPAWNFLAG_NOT_MEDIUM 0x00000200 +#define SPAWNFLAG_NOT_HARD 0x00000400 +#define SPAWNFLAG_NOT_DEATHMATCH 0x00000800 +#define SPAWNFLAG_NOT_COOP 0x00001000 + +#define STATE_TOP 0 +#define STATE_BOTTOM 1 +#define STATE_UP 2 +#define STATE_DOWN 3 + +#define DOOR_START_OPEN 1 +#define DOOR_REVERSE 2 +#define DOOR_CRUSHER 4 +#define DOOR_NOMONSTER 8 +#define DOOR_TOGGLE 32 +#define DOOR_X_AXIS 64 +#define DOOR_Y_AXIS 128 + +#define BBOX_NORMAL_EPSILON 0.0001 + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +vec_t BoxOriginDistanceFromPlane(vec3_t normal, vec3_t mins, vec3_t maxs, int side) +{ + vec3_t v1, v2; + int i; + + if (side) + { + for (i = 0; i < 3; i++) + { + if (normal[i] > BBOX_NORMAL_EPSILON) v1[i] = maxs[i]; + else if (normal[i] < -BBOX_NORMAL_EPSILON) v1[i] = mins[i]; + else v1[i] = 0; + } //end for + } //end if + else + { + for (i = 0; i < 3; i++) + { + if (normal[i] > BBOX_NORMAL_EPSILON) v1[i] = mins[i]; + else if (normal[i] < -BBOX_NORMAL_EPSILON) v1[i] = maxs[i]; + else v1[i] = 0; + } //end for + } //end else + VectorCopy(normal, v2); + VectorInverse(v2); + return DotProduct(v1, v2); +} //end of the function BoxOriginDistanceFromPlane +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +vec_t CapsuleOriginDistanceFromPlane(vec3_t normal, vec3_t mins, vec3_t maxs) +{ + float offset_up, offset_down, width, radius; + + width = maxs[0] - mins[0]; + // if the box is less high then it is wide + if (maxs[2] - mins[2] < width) { + width = maxs[2] - mins[2]; + } + radius = width * 0.5; + // offset to upper and lower sphere + offset_up = maxs[2] - radius; + offset_down = -mins[2] - radius; + + // if normal points upward + if ( normal[2] > 0 ) { + // touches lower sphere first + return normal[2] * offset_down + radius; + } + else { + // touched upper sphere first + return -normal[2] * offset_up + radius; + } +} +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ExpandMapBrush(mapbrush_t *brush, vec3_t mins, vec3_t maxs) +{ + int sn; + float dist; + side_t *s; + plane_t *plane; + + for (sn = 0; sn < brush->numsides; sn++) + { + s = brush->original_sides + sn; + plane = &mapplanes[s->planenum]; + dist = plane->dist; + if (capsule_collision) { + dist += CapsuleOriginDistanceFromPlane(plane->normal, mins, maxs); + } + else { + dist += BoxOriginDistanceFromPlane(plane->normal, mins, maxs, 0); + } + s->planenum = FindFloatPlane(plane->normal, dist); + //the side isn't a bevel after expanding + s->flags &= ~SFL_BEVEL; + //don't skip the surface + s->surf &= ~SURF_SKIP; + //make sure the texinfo is not TEXINFO_NODE + //when player clip contents brushes are read from the bsp tree + //they have the texinfo field set to TEXINFO_NODE + //s->texinfo = 0; + } //end for +} //end of the function AAS_ExpandMapBrush +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_SetTexinfo(mapbrush_t *brush) +{ + int n; + side_t *side; + + if (brush->contents & (CONTENTS_LADDER + | CONTENTS_AREAPORTAL + | CONTENTS_CLUSTERPORTAL + | CONTENTS_TELEPORTER + | CONTENTS_JUMPPAD + | CONTENTS_DONOTENTER + | CONTENTS_WATER + | CONTENTS_LAVA + | CONTENTS_SLIME + | CONTENTS_WINDOW + | CONTENTS_PLAYERCLIP)) + { + //we just set texinfo to 0 because these brush sides MUST be used as + //bsp splitters textured or not textured + for (n = 0; n < brush->numsides; n++) + { + side = brush->original_sides + n; + //side->flags |= SFL_TEXTURED|SFL_VISIBLE; + side->texinfo = 0; + } //end for + } //end if + else + { + //only use brush sides as splitters if they are textured + //texinfo of non-textured sides will be set to TEXINFO_NODE + for (n = 0; n < brush->numsides; n++) + { + side = brush->original_sides + n; + //don't use side as splitter (set texinfo to TEXINFO_NODE) if not textured + if (side->flags & (SFL_TEXTURED|SFL_BEVEL)) side->texinfo = 0; + else side->texinfo = TEXINFO_NODE; + } //end for + } //end else +} //end of the function AAS_SetTexinfo +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeBrushWindings(mapbrush_t *brush) +{ + int n; + side_t *side; + // + for (n = 0; n < brush->numsides; n++) + { + side = brush->original_sides + n; + // + if (side->winding) FreeWinding(side->winding); + } //end for +} //end of the function FreeBrushWindings +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_AddMapBrushSide(mapbrush_t *brush, int planenum) +{ + side_t *side; + // + if (nummapbrushsides >= MAX_MAPFILE_BRUSHSIDES) + Error ("MAX_MAPFILE_BRUSHSIDES"); + // + side = brush->original_sides + brush->numsides; + side->original = NULL; + side->winding = NULL; + side->contents = brush->contents; + side->flags &= ~(SFL_BEVEL|SFL_VISIBLE); + side->surf = 0; + side->planenum = planenum; + side->texinfo = 0; + // + nummapbrushsides++; + brush->numsides++; +} //end of the function AAS_AddMapBrushSide +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FixMapBrush(mapbrush_t *brush) +{ + int i, j, planenum; + float dist; + winding_t *w; + plane_t *plane, *plane1, *plane2; + side_t *side; + vec3_t normal; + + //calculate the brush bounds + ClearBounds(brush->mins, brush->maxs); + for (i = 0; i < brush->numsides; i++) + { + plane = &mapplanes[brush->original_sides[i].planenum]; + w = BaseWindingForPlane(plane->normal, plane->dist); + for (j = 0; j < brush->numsides && w; j++) + { + if (i == j) continue; + //there are no brush bevels marked but who cares :) + if (brush->original_sides[j].flags & SFL_BEVEL) continue; + plane = &mapplanes[brush->original_sides[j].planenum^1]; + ChopWindingInPlace(&w, plane->normal, plane->dist, 0); //CLIP_EPSILON); + } //end for + + side = &brush->original_sides[i]; + side->winding = w; + if (w) + { + for (j = 0; j < w->numpoints; j++) + { + AddPointToBounds(w->p[j], brush->mins, brush->maxs); + } //end for + } //end if + } //end for + // + for (i = 0; i < brush->numsides; i++) + { + for (j = 0; j < brush->numsides; j++) + { + if (i == j) continue; + plane1 = &mapplanes[brush->original_sides[i].planenum]; + plane2 = &mapplanes[brush->original_sides[j].planenum]; + if (WindingsNonConvex(brush->original_sides[i].winding, + brush->original_sides[j].winding, + plane1->normal, plane2->normal, + plane1->dist, plane2->dist)) + { + Log_Print("non convex brush"); + } //end if + } //end for + } //end for + + //NOW close the fucking brush!! + for (i = 0; i < 3; i++) + { + if (brush->mins[i] < -MAX_MAP_BOUNDS) + { + VectorClear(normal); + normal[i] = -1; + dist = MAX_MAP_BOUNDS - 10; + planenum = FindFloatPlane(normal, dist); + // + Log_Print("mins out of range: added extra brush side\n"); + AAS_AddMapBrushSide(brush, planenum); + } //end if + if (brush->maxs[i] > MAX_MAP_BOUNDS) + { + VectorClear(normal); + normal[i] = 1; + dist = MAX_MAP_BOUNDS - 10; + planenum = FindFloatPlane(normal, dist); + // + Log_Print("maxs out of range: added extra brush side\n"); + AAS_AddMapBrushSide(brush, planenum); + } //end if + if (brush->mins[i] > MAX_MAP_BOUNDS || brush->maxs[i] < -MAX_MAP_BOUNDS) + { + Log_Print("entity %i, brush %i: no visible sides on brush\n", brush->entitynum, brush->brushnum); + } //end if + } //end for + //free all the windings + FreeBrushWindings(brush); +} //end of the function AAS_FixMapBrush +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_MakeBrushWindings(mapbrush_t *ob) +{ + int i, j; + winding_t *w; + side_t *side; + plane_t *plane, *plane1, *plane2; + + ClearBounds (ob->mins, ob->maxs); + + for (i = 0; i < ob->numsides; i++) + { + plane = &mapplanes[ob->original_sides[i].planenum]; + w = BaseWindingForPlane(plane->normal, plane->dist); + for (j = 0; j numsides && w; j++) + { + if (i == j) continue; + if (ob->original_sides[j].flags & SFL_BEVEL) continue; + plane = &mapplanes[ob->original_sides[j].planenum^1]; + ChopWindingInPlace(&w, plane->normal, plane->dist, 0); //CLIP_EPSILON); + } + + side = &ob->original_sides[i]; + side->winding = w; + if (w) + { + side->flags |= SFL_VISIBLE; + for (j = 0; j < w->numpoints; j++) + AddPointToBounds (w->p[j], ob->mins, ob->maxs); + } + } + //check if the brush is convex + for (i = 0; i < ob->numsides; i++) + { + for (j = 0; j < ob->numsides; j++) + { + if (i == j) continue; + plane1 = &mapplanes[ob->original_sides[i].planenum]; + plane2 = &mapplanes[ob->original_sides[j].planenum]; + if (WindingsNonConvex(ob->original_sides[i].winding, + ob->original_sides[j].winding, + plane1->normal, plane2->normal, + plane1->dist, plane2->dist)) + { + Log_Print("non convex brush"); + } //end if + } //end for + } //end for + //check for out of bound brushes + for (i = 0; i < 3; i++) + { + //IDBUG: all the indexes into the mins and maxs were zero (not using i) + if (ob->mins[i] < -MAX_MAP_BOUNDS || ob->maxs[i] > MAX_MAP_BOUNDS) + { + Log_Print("entity %i, brush %i: bounds out of range\n", ob->entitynum, ob->brushnum); + Log_Print("ob->mins[%d] = %f, ob->maxs[%d] = %f\n", i, ob->mins[i], i, ob->maxs[i]); + ob->numsides = 0; //remove the brush + break; + } //end if + if (ob->mins[i] > MAX_MAP_BOUNDS || ob->maxs[i] < -MAX_MAP_BOUNDS) + { + Log_Print("entity %i, brush %i: no visible sides on brush\n", ob->entitynum, ob->brushnum); + Log_Print("ob->mins[%d] = %f, ob->maxs[%d] = %f\n", i, ob->mins[i], i, ob->maxs[i]); + ob->numsides = 0; //remove the brush + break; + } //end if + } //end for + return true; +} //end of the function AAS_MakeBrushWindings +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +mapbrush_t *AAS_CopyMapBrush(mapbrush_t *brush, entity_t *mapent) +{ + int n; + mapbrush_t *newbrush; + side_t *side, *newside; + + if (nummapbrushes >= MAX_MAPFILE_BRUSHES) + Error ("MAX_MAPFILE_BRUSHES"); + + newbrush = &mapbrushes[nummapbrushes]; + newbrush->original_sides = &brushsides[nummapbrushsides]; + newbrush->entitynum = brush->entitynum; + newbrush->brushnum = nummapbrushes - mapent->firstbrush; + newbrush->numsides = brush->numsides; + newbrush->contents = brush->contents; + + //copy the sides + for (n = 0; n < brush->numsides; n++) + { + if (nummapbrushsides >= MAX_MAPFILE_BRUSHSIDES) + Error ("MAX_MAPFILE_BRUSHSIDES"); + side = brush->original_sides + n; + + newside = newbrush->original_sides + n; + newside->original = NULL; + newside->winding = NULL; + newside->contents = side->contents; + newside->flags = side->flags; + newside->surf = side->surf; + newside->planenum = side->planenum; + newside->texinfo = side->texinfo; + nummapbrushsides++; + } //end for + // + nummapbrushes++; + mapent->numbrushes++; + return newbrush; +} //end of the function AAS_CopyMapBrush +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int mark_entities[MAX_MAP_ENTITIES]; + +int AAS_AlwaysTriggered_r(char *targetname) +{ + int i; + + if (!strlen(targetname)) { + return false; + } + // + for (i = 0; i < num_entities; i++) { + // if the entity will activate the given targetname + if ( !strcmp(targetname, ValueForKey(&entities[i], "target")) ) { + // if this activator is present in deathmatch + if (!(atoi(ValueForKey(&entities[i], "spawnflags")) & SPAWNFLAG_NOT_DEATHMATCH)) { + // if it is a trigger_always entity + if (!strcmp("trigger_always", ValueForKey(&entities[i], "classname"))) { + return true; + } + // check for possible trigger_always entities activating this entity + if ( mark_entities[i] ) { + Warning( "entity %d, classname %s has recursive targetname %s\n", i, + ValueForKey(&entities[i], "classname"), targetname ); + return false; + } + mark_entities[i] = true; + if ( AAS_AlwaysTriggered_r(ValueForKey(&entities[i], "targetname")) ) { + return true; + } + } + } + } + return false; +} + +int AAS_AlwaysTriggered(char *targetname) { + memset( mark_entities, 0, sizeof(mark_entities) ); + return AAS_AlwaysTriggered_r( targetname ); +} +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_ValidEntity(entity_t *mapent) +{ + int i; + char target[1024]; + + //all world brushes are used for AAS + if (mapent == &entities[0]) + { + return true; + } //end if + //some of the func_wall brushes are also used for AAS + else if (!strcmp("func_wall", ValueForKey(mapent, "classname"))) + { + //Log_Print("found func_wall entity %d\n", mapent - entities); + //if the func wall is used in deathmatch + if (!(atoi(ValueForKey(mapent, "spawnflags")) & SPAWNFLAG_NOT_DEATHMATCH)) + { + //Log_Print("func_wall USED in deathmatch mode %d\n", atoi(ValueForKey(mapent, "spawnflags"))); + return true; + } //end if + } //end else if + else if (!strcmp("func_door_rotating", ValueForKey(mapent, "classname"))) + { + //if the func_door_rotating is present in deathmatch + if (!(atoi(ValueForKey(mapent, "spawnflags")) & SPAWNFLAG_NOT_DEATHMATCH)) + { + //if the func_door_rotating is always activated in deathmatch + if (AAS_AlwaysTriggered(ValueForKey(mapent, "targetname"))) + { + //Log_Print("found func_door_rotating in deathmatch\ntargetname %s\n", ValueForKey(mapent, "targetname")); + return true; + } //end if + } //end if + } //end else if + else if (!strcmp("trigger_hurt", ValueForKey(mapent, "classname"))) + { + //"dmg" is the damage, for instance: "dmg" "666" + return true; + } //end else if + else if (!strcmp("trigger_push", ValueForKey(mapent, "classname"))) + { + return true; + } //end else if + else if (!strcmp("trigger_multiple", ValueForKey(mapent, "classname"))) + { + //find out if the trigger_multiple is pointing to a target_teleporter + strcpy(target, ValueForKey(mapent, "target")); + for (i = 0; i < num_entities; i++) + { + //if the entity will activate the given targetname + if (!strcmp(target, ValueForKey(&entities[i], "targetname"))) + { + if (!strcmp("target_teleporter", ValueForKey(&entities[i], "classname"))) + { + return true; + } //end if + } //end if + } //end for + } //end else if + else if (!strcmp("trigger_teleport", ValueForKey(mapent, "classname"))) + { + return true; + } //end else if + else if (!strcmp("func_static", ValueForKey(mapent, "classname"))) + { + //FIXME: easy/medium/hard/deathmatch specific? + return true; + } //end else if + else if (!strcmp("func_door", ValueForKey(mapent, "classname"))) + { + return true; + } //end else if + return false; +} //end of the function AAS_ValidEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_TransformPlane(int planenum, vec3_t origin, vec3_t angles) +{ + float newdist, matrix[3][3]; + vec3_t normal; + + //rotate the node plane + VectorCopy(mapplanes[planenum].normal, normal); + CreateRotationMatrix(angles, matrix); + RotatePoint(normal, matrix); + newdist = mapplanes[planenum].dist + DotProduct(normal, origin); + return FindFloatPlane(normal, newdist); +} //end of the function AAS_TransformPlane +//=========================================================================== +// this function sets the func_rotating_door in it's final position +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_PositionFuncRotatingBrush(entity_t *mapent, mapbrush_t *brush) +{ + int spawnflags, i; + float distance; + vec3_t movedir, angles, pos1, pos2; + side_t *s; + + spawnflags = FloatForKey(mapent, "spawnflags"); + VectorClear(movedir); + if (spawnflags & DOOR_X_AXIS) + movedir[2] = 1.0; //roll + else if (spawnflags & DOOR_Y_AXIS) + movedir[0] = 1.0; //pitch + else // Z_AXIS + movedir[1] = 1.0; //yaw + + // check for reverse rotation + if (spawnflags & DOOR_REVERSE) + VectorInverse(movedir); + + distance = FloatForKey(mapent, "distance"); + if (!distance) distance = 90; + + GetVectorForKey(mapent, "angles", angles); + VectorCopy(angles, pos1); + VectorMA(angles, -distance, movedir, pos2); + // if it starts open, switch the positions + if (spawnflags & DOOR_START_OPEN) + { + VectorCopy(pos2, angles); + VectorCopy(pos1, pos2); + VectorCopy(angles, pos1); + VectorInverse(movedir); + } //end if + // + for (i = 0; i < brush->numsides; i++) + { + s = &brush->original_sides[i]; + s->planenum = AAS_TransformPlane(s->planenum, mapent->origin, pos2); + } //end for + // + FreeBrushWindings(brush); + AAS_MakeBrushWindings(brush); + AddBrushBevels(brush); + FreeBrushWindings(brush); +} //end of the function AAS_PositionFuncRotatingBrush +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_PositionBrush(entity_t *mapent, mapbrush_t *brush) +{ + side_t *s; + float newdist; + int i, notteam; + char *model; + + if (!strcmp(ValueForKey(mapent, "classname"), "func_door_rotating")) + { + AAS_PositionFuncRotatingBrush(mapent, brush); + } //end if + else + { + if (mapent->origin[0] || mapent->origin[1] || mapent->origin[2]) + { + for (i = 0; i < brush->numsides; i++) + { + s = &brush->original_sides[i]; + newdist = mapplanes[s->planenum].dist + + DotProduct(mapplanes[s->planenum].normal, mapent->origin); + s->planenum = FindFloatPlane(mapplanes[s->planenum].normal, newdist); + } //end for + } //end if + //if it's a trigger hurt + if (!strcmp("trigger_hurt", ValueForKey(mapent, "classname"))) + { + notteam = FloatForKey(mapent, "bot_notteam"); + if ( notteam == 1 ) { + brush->contents |= CONTENTS_NOTTEAM1; + } + else if ( notteam == 2 ) { + brush->contents |= CONTENTS_NOTTEAM2; + } + else { + // always avoid so set lava contents + brush->contents |= CONTENTS_LAVA; + } + } //end if + // + else if (!strcmp("trigger_push", ValueForKey(mapent, "classname"))) + { + //set the jumppad contents + brush->contents = CONTENTS_JUMPPAD; + //Log_Print("found trigger_push brush\n"); + } //end if + // + else if (!strcmp("trigger_multiple", ValueForKey(mapent, "classname"))) + { + //set teleporter contents + brush->contents = CONTENTS_TELEPORTER; + //Log_Print("found trigger_multiple teleporter brush\n"); + } //end if + // + else if (!strcmp("trigger_teleport", ValueForKey(mapent, "classname"))) + { + //set teleporter contents + brush->contents = CONTENTS_TELEPORTER; + //Log_Print("found trigger_teleport teleporter brush\n"); + } //end if + else if (!strcmp("func_door", ValueForKey(mapent, "classname"))) + { + //set mover contents + brush->contents = CONTENTS_MOVER; + //get the model number + model = ValueForKey(mapent, "model"); + brush->modelnum = atoi(model+1); + } //end if + } //end else +} //end of the function AAS_PositionBrush +//=========================================================================== +// uses the global cfg_t cfg +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CreateMapBrushes(mapbrush_t *brush, entity_t *mapent, int addbevels) +{ + int i; + //side_t *s; + mapbrush_t *bboxbrushes[16]; + + //if the brushes are not from an entity used for AAS + if (!AAS_ValidEntity(mapent)) + { + nummapbrushsides -= brush->numsides; + brush->numsides = 0; + return; + } //end if + // + AAS_PositionBrush(mapent, brush); + //from all normal solid brushes only the textured brush sides will + //be used as bsp splitters, so set the right texinfo reference here + AAS_SetTexinfo(brush); + //remove contents detail flag, otherwise player clip contents won't be + //bsped correctly for AAS! + brush->contents &= ~CONTENTS_DETAIL; + //if the brush has contents area portal it should be the only contents + if (brush->contents & (CONTENTS_AREAPORTAL|CONTENTS_CLUSTERPORTAL)) + { + brush->contents = CONTENTS_CLUSTERPORTAL; + brush->leafnum = -1; + } //end if + //window and playerclip are used for player clipping, make them solid + if (brush->contents & (CONTENTS_WINDOW | CONTENTS_PLAYERCLIP)) + { + // + brush->contents &= ~(CONTENTS_WINDOW | CONTENTS_PLAYERCLIP); + brush->contents |= CONTENTS_SOLID; + brush->leafnum = -1; + } //end if + // + if (brush->contents & CONTENTS_BOTCLIP) + { + brush->contents = CONTENTS_SOLID; + brush->leafnum = -1; + } //end if + // + //Log_Write("brush %d contents = ", brush->brushnum); + //PrintContents(brush->contents); + //Log_Write("\r\n"); + //if not one of the following brushes then the brush is NOT used for AAS + if (!(brush->contents & (CONTENTS_SOLID + | CONTENTS_LADDER + | CONTENTS_CLUSTERPORTAL + | CONTENTS_DONOTENTER + | CONTENTS_TELEPORTER + | CONTENTS_JUMPPAD + | CONTENTS_WATER + | CONTENTS_LAVA + | CONTENTS_SLIME + | CONTENTS_MOVER + ))) + { + nummapbrushsides -= brush->numsides; + brush->numsides = 0; + return; + } //end if + //fix the map brush + //AAS_FixMapBrush(brush); + //if brush bevels should be added (for real map brushes, not bsp map brushes) + if (addbevels) + { + //NOTE: we first have to get the mins and maxs of the brush before + // creating the brush bevels... the mins and maxs are used to + // create them. so we call MakeBrushWindings to get the mins + // and maxs and then after creating the bevels we free the + // windings because they are created for all sides (including + // bevels) a little later + AAS_MakeBrushWindings(brush); + AddBrushBevels(brush); + FreeBrushWindings(brush); + } //end if + //NOTE: add the brush to the WORLD entity!!! + mapent = &entities[0]; + //there's at least one new brush for now + nummapbrushes++; + mapent->numbrushes++; + //liquid brushes are expanded for the maximum possible bounding box + if (brush->contents & (CONTENTS_WATER + | CONTENTS_LAVA + | CONTENTS_SLIME + | CONTENTS_TELEPORTER + | CONTENTS_JUMPPAD + | CONTENTS_DONOTENTER + | CONTENTS_MOVER + )) + { + brush->expansionbbox = 0; + //NOTE: the first bounding box is the max + //FIXME: use max bounding box created from all bboxes + AAS_ExpandMapBrush(brush, cfg.bboxes[0].mins, cfg.bboxes[0].maxs); + AAS_MakeBrushWindings(brush); + } //end if + //area portal brushes are NOT expanded + else if (brush->contents & CONTENTS_CLUSTERPORTAL) + { + brush->expansionbbox = 0; + //NOTE: the first bounding box is the max + //FIXME: use max bounding box created from all bboxes + AAS_ExpandMapBrush(brush, cfg.bboxes[0].mins, cfg.bboxes[0].maxs); + AAS_MakeBrushWindings(brush); + } //end if + //all solid brushes are expanded for all bounding boxes + else if (brush->contents & (CONTENTS_SOLID + | CONTENTS_LADDER + )) + { + //brush for the first bounding box + bboxbrushes[0] = brush; + //make a copy for the other bounding boxes + for (i = 1; i < cfg.numbboxes; i++) + { + bboxbrushes[i] = AAS_CopyMapBrush(brush, mapent); + } //end for + //expand every brush for it's bounding box and create windings + for (i = 0; i < cfg.numbboxes; i++) + { + AAS_ExpandMapBrush(bboxbrushes[i], cfg.bboxes[i].mins, cfg.bboxes[i].maxs); + bboxbrushes[i]->expansionbbox = cfg.bboxes[i].presencetype; + AAS_MakeBrushWindings(bboxbrushes[i]); + } //end for + } //end else +} //end of the function AAS_CreateMapBrushes diff --git a/code/bspc/aas_map.h b/code/bspc/aas_map.h index 601cdfb..181f711 100755 --- a/code/bspc/aas_map.h +++ b/code/bspc/aas_map.h @@ -1,23 +1,23 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -void AAS_CreateMapBrushes(mapbrush_t *brush, entity_t *mapent, int addbevels); +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +void AAS_CreateMapBrushes(mapbrush_t *brush, entity_t *mapent, int addbevels); diff --git a/code/bspc/aas_prunenodes.c b/code/bspc/aas_prunenodes.c index a03e9d2..1ba292a 100755 --- a/code/bspc/aas_prunenodes.c +++ b/code/bspc/aas_prunenodes.c @@ -1,89 +1,89 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "qbsp.h" -#include "../botlib/aasfile.h" -#include "aas_create.h" - -int c_numprunes; - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -tmp_node_t *AAS_PruneNodes_r(tmp_node_t *tmpnode) -{ - tmp_area_t *tmparea1, *tmparea2; - - //if it is a solid leaf - if (!tmpnode) return NULL; - // - if (tmpnode->tmparea) return tmpnode; - //process the children first - tmpnode->children[0] = AAS_PruneNodes_r(tmpnode->children[0]); - tmpnode->children[1] = AAS_PruneNodes_r(tmpnode->children[1]); - //if both children are areas - if (tmpnode->children[0] && tmpnode->children[1] && - tmpnode->children[0]->tmparea && tmpnode->children[1]->tmparea) - { - tmparea1 = tmpnode->children[0]->tmparea; - while(tmparea1->mergedarea) tmparea1 = tmparea1->mergedarea; - - tmparea2 = tmpnode->children[1]->tmparea; - while(tmparea2->mergedarea) tmparea2 = tmparea2->mergedarea; - - if (tmparea1 == tmparea2) - { - c_numprunes++; - tmpnode->tmparea = tmparea1; - tmpnode->planenum = 0; - AAS_FreeTmpNode(tmpnode->children[0]); - AAS_FreeTmpNode(tmpnode->children[1]); - tmpnode->children[0] = NULL; - tmpnode->children[1] = NULL; - return tmpnode; - } //end if - } //end if - //if both solid leafs - if (!tmpnode->children[0] && !tmpnode->children[1]) - { - c_numprunes++; - AAS_FreeTmpNode(tmpnode); - return NULL; - } //end if - // - return tmpnode; -} //end of the function AAS_PruneNodes_r -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_PruneNodes(void) -{ - Log_Write("AAS_PruneNodes\r\n"); - AAS_PruneNodes_r(tmpaasworld.nodes); - Log_Print("%6d nodes pruned\r\n", c_numprunes); -} //end of the function AAS_PruneNodes +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "qbsp.h" +#include "../botlib/aasfile.h" +#include "aas_create.h" + +int c_numprunes; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +tmp_node_t *AAS_PruneNodes_r(tmp_node_t *tmpnode) +{ + tmp_area_t *tmparea1, *tmparea2; + + //if it is a solid leaf + if (!tmpnode) return NULL; + // + if (tmpnode->tmparea) return tmpnode; + //process the children first + tmpnode->children[0] = AAS_PruneNodes_r(tmpnode->children[0]); + tmpnode->children[1] = AAS_PruneNodes_r(tmpnode->children[1]); + //if both children are areas + if (tmpnode->children[0] && tmpnode->children[1] && + tmpnode->children[0]->tmparea && tmpnode->children[1]->tmparea) + { + tmparea1 = tmpnode->children[0]->tmparea; + while(tmparea1->mergedarea) tmparea1 = tmparea1->mergedarea; + + tmparea2 = tmpnode->children[1]->tmparea; + while(tmparea2->mergedarea) tmparea2 = tmparea2->mergedarea; + + if (tmparea1 == tmparea2) + { + c_numprunes++; + tmpnode->tmparea = tmparea1; + tmpnode->planenum = 0; + AAS_FreeTmpNode(tmpnode->children[0]); + AAS_FreeTmpNode(tmpnode->children[1]); + tmpnode->children[0] = NULL; + tmpnode->children[1] = NULL; + return tmpnode; + } //end if + } //end if + //if both solid leafs + if (!tmpnode->children[0] && !tmpnode->children[1]) + { + c_numprunes++; + AAS_FreeTmpNode(tmpnode); + return NULL; + } //end if + // + return tmpnode; +} //end of the function AAS_PruneNodes_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_PruneNodes(void) +{ + Log_Write("AAS_PruneNodes\r\n"); + AAS_PruneNodes_r(tmpaasworld.nodes); + Log_Print("%6d nodes pruned\r\n", c_numprunes); +} //end of the function AAS_PruneNodes diff --git a/code/bspc/aas_prunenodes.h b/code/bspc/aas_prunenodes.h index 36989ad..9433864 100755 --- a/code/bspc/aas_prunenodes.h +++ b/code/bspc/aas_prunenodes.h @@ -1,24 +1,24 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -void AAS_PruneNodes(void); - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +void AAS_PruneNodes(void); + diff --git a/code/bspc/aas_store.c b/code/bspc/aas_store.c index 4836d51..d549ab7 100755 --- a/code/bspc/aas_store.c +++ b/code/bspc/aas_store.c @@ -1,1082 +1,1082 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "qbsp.h" -#include "../botlib/aasfile.h" -#include "aas_file.h" -#include "aas_store.h" -#include "aas_create.h" -#include "aas_cfg.h" - - -//#define NOTHREEVERTEXFACES - -#define STOREPLANESDOUBLE - -#define VERTEX_EPSILON 0.1 //NOTE: changed from 0.5 -#define DIST_EPSILON 0.05 //NOTE: changed from 0.9 -#define NORMAL_EPSILON 0.0001 //NOTE: changed from 0.005 -#define INTEGRAL_EPSILON 0.01 - -#define VERTEX_HASHING -#define VERTEX_HASH_SHIFT 7 -#define VERTEX_HASH_SIZE ((MAX_MAP_BOUNDS>>(VERTEX_HASH_SHIFT-1))+1) //was 64 -// -#define PLANE_HASHING -#define PLANE_HASH_SIZE 1024 //must be power of 2 -// -#define EDGE_HASHING -#define EDGE_HASH_SIZE 1024 //must be power of 2 - -aas_t aasworld; - -//vertex hash -int *aas_vertexchain; // the next vertex in a hash chain -int aas_hashverts[VERTEX_HASH_SIZE*VERTEX_HASH_SIZE]; // a vertex number, or 0 for no verts -//plane hash -int *aas_planechain; -int aas_hashplanes[PLANE_HASH_SIZE]; -//edge hash -int *aas_edgechain; -int aas_hashedges[EDGE_HASH_SIZE]; - -int allocatedaasmem = 0; - -int groundfacesonly = false;//true; -// -typedef struct max_aas_s -{ - int max_bboxes; - int max_vertexes; - int max_planes; - int max_edges; - int max_edgeindexsize; - int max_faces; - int max_faceindexsize; - int max_areas; - int max_areasettings; - int max_reachabilitysize; - int max_nodes; - int max_portals; - int max_portalindexsize; - int max_clusters; -} max_aas_t; -//maximums of everything -max_aas_t max_aas; - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_CountTmpNodes(tmp_node_t *tmpnode) -{ - if (!tmpnode) return 0; - return AAS_CountTmpNodes(tmpnode->children[0]) + - AAS_CountTmpNodes(tmpnode->children[1]) + 1; -} //end of the function AAS_CountTmpNodes -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_InitMaxAAS(void) -{ - int numfaces, numpoints, numareas; - tmp_face_t *f; - tmp_area_t *a; - - numpoints = 0; - numfaces = 0; - for (f = tmpaasworld.faces; f; f = f->l_next) - { - numfaces++; - if (f->winding) numpoints += f->winding->numpoints; - } //end for - // - numareas = 0; - for (a = tmpaasworld.areas; a; a = a->l_next) - { - numareas++; - } //end for - max_aas.max_bboxes = AAS_MAX_BBOXES; - max_aas.max_vertexes = numpoints + 1; - max_aas.max_planes = nummapplanes; - max_aas.max_edges = numpoints + 1; - max_aas.max_edgeindexsize = (numpoints + 1) * 3; - max_aas.max_faces = numfaces + 10; - max_aas.max_faceindexsize = (numfaces + 10) * 2; - max_aas.max_areas = numareas + 10; - max_aas.max_areasettings = numareas + 10; - max_aas.max_reachabilitysize = 0; - max_aas.max_nodes = AAS_CountTmpNodes(tmpaasworld.nodes) + 10; - max_aas.max_portals = 0; - max_aas.max_portalindexsize = 0; - max_aas.max_clusters = 0; -} //end of the function AAS_InitMaxAAS -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_AllocMaxAAS(void) -{ - int i; - - AAS_InitMaxAAS(); - //bounding boxes - aasworld.numbboxes = 0; - aasworld.bboxes = (aas_bbox_t *) GetClearedMemory(max_aas.max_bboxes * sizeof(aas_bbox_t)); - allocatedaasmem += max_aas.max_bboxes * sizeof(aas_bbox_t); - //vertexes - aasworld.numvertexes = 0; - aasworld.vertexes = (aas_vertex_t *) GetClearedMemory(max_aas.max_vertexes * sizeof(aas_vertex_t)); - allocatedaasmem += max_aas.max_vertexes * sizeof(aas_vertex_t); - //planes - aasworld.numplanes = 0; - aasworld.planes = (aas_plane_t *) GetClearedMemory(max_aas.max_planes * sizeof(aas_plane_t)); - allocatedaasmem += max_aas.max_planes * sizeof(aas_plane_t); - //edges - aasworld.numedges = 0; - aasworld.edges = (aas_edge_t *) GetClearedMemory(max_aas.max_edges * sizeof(aas_edge_t)); - allocatedaasmem += max_aas.max_edges * sizeof(aas_edge_t); - //edge index - aasworld.edgeindexsize = 0; - aasworld.edgeindex = (aas_edgeindex_t *) GetClearedMemory(max_aas.max_edgeindexsize * sizeof(aas_edgeindex_t)); - allocatedaasmem += max_aas.max_edgeindexsize * sizeof(aas_edgeindex_t); - //faces - aasworld.numfaces = 0; - aasworld.faces = (aas_face_t *) GetClearedMemory(max_aas.max_faces * sizeof(aas_face_t)); - allocatedaasmem += max_aas.max_faces * sizeof(aas_face_t); - //face index - aasworld.faceindexsize = 0; - aasworld.faceindex = (aas_faceindex_t *) GetClearedMemory(max_aas.max_faceindexsize * sizeof(aas_faceindex_t)); - allocatedaasmem += max_aas.max_faceindexsize * sizeof(aas_faceindex_t); - //convex areas - aasworld.numareas = 0; - aasworld.areas = (aas_area_t *) GetClearedMemory(max_aas.max_areas * sizeof(aas_area_t)); - allocatedaasmem += max_aas.max_areas * sizeof(aas_area_t); - //convex area settings - aasworld.numareasettings = 0; - aasworld.areasettings = (aas_areasettings_t *) GetClearedMemory(max_aas.max_areasettings * sizeof(aas_areasettings_t)); - allocatedaasmem += max_aas.max_areasettings * sizeof(aas_areasettings_t); - //reachablity list - aasworld.reachabilitysize = 0; - aasworld.reachability = (aas_reachability_t *) GetClearedMemory(max_aas.max_reachabilitysize * sizeof(aas_reachability_t)); - allocatedaasmem += max_aas.max_reachabilitysize * sizeof(aas_reachability_t); - //nodes of the bsp tree - aasworld.numnodes = 0; - aasworld.nodes = (aas_node_t *) GetClearedMemory(max_aas.max_nodes * sizeof(aas_node_t)); - allocatedaasmem += max_aas.max_nodes * sizeof(aas_node_t); - //cluster portals - aasworld.numportals = 0; - aasworld.portals = (aas_portal_t *) GetClearedMemory(max_aas.max_portals * sizeof(aas_portal_t)); - allocatedaasmem += max_aas.max_portals * sizeof(aas_portal_t); - //cluster portal index - aasworld.portalindexsize = 0; - aasworld.portalindex = (aas_portalindex_t *) GetClearedMemory(max_aas.max_portalindexsize * sizeof(aas_portalindex_t)); - allocatedaasmem += max_aas.max_portalindexsize * sizeof(aas_portalindex_t); - //cluster - aasworld.numclusters = 0; - aasworld.clusters = (aas_cluster_t *) GetClearedMemory(max_aas.max_clusters * sizeof(aas_cluster_t)); - allocatedaasmem += max_aas.max_clusters * sizeof(aas_cluster_t); - // - Log_Print("allocated "); - PrintMemorySize(allocatedaasmem); - Log_Print(" of AAS memory\n"); - //reset the has stuff - aas_vertexchain = (int *) GetClearedMemory(max_aas.max_vertexes * sizeof(int)); - aas_planechain = (int *) GetClearedMemory(max_aas.max_planes * sizeof(int)); - aas_edgechain = (int *) GetClearedMemory(max_aas.max_edges * sizeof(int)); - // - for (i = 0; i < max_aas.max_vertexes; i++) aas_vertexchain[i] = -1; - for (i = 0; i < VERTEX_HASH_SIZE * VERTEX_HASH_SIZE; i++) aas_hashverts[i] = -1; - // - for (i = 0; i < max_aas.max_planes; i++) aas_planechain[i] = -1; - for (i = 0; i < PLANE_HASH_SIZE; i++) aas_hashplanes[i] = -1; - // - for (i = 0; i < max_aas.max_edges; i++) aas_edgechain[i] = -1; - for (i = 0; i < EDGE_HASH_SIZE; i++) aas_hashedges[i] = -1; -} //end of the function AAS_AllocMaxAAS -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_FreeMaxAAS(void) -{ - //bounding boxes - if (aasworld.bboxes) FreeMemory(aasworld.bboxes); - aasworld.bboxes = NULL; - aasworld.numbboxes = 0; - //vertexes - if (aasworld.vertexes) FreeMemory(aasworld.vertexes); - aasworld.vertexes = NULL; - aasworld.numvertexes = 0; - //planes - if (aasworld.planes) FreeMemory(aasworld.planes); - aasworld.planes = NULL; - aasworld.numplanes = 0; - //edges - if (aasworld.edges) FreeMemory(aasworld.edges); - aasworld.edges = NULL; - aasworld.numedges = 0; - //edge index - if (aasworld.edgeindex) FreeMemory(aasworld.edgeindex); - aasworld.edgeindex = NULL; - aasworld.edgeindexsize = 0; - //faces - if (aasworld.faces) FreeMemory(aasworld.faces); - aasworld.faces = NULL; - aasworld.numfaces = 0; - //face index - if (aasworld.faceindex) FreeMemory(aasworld.faceindex); - aasworld.faceindex = NULL; - aasworld.faceindexsize = 0; - //convex areas - if (aasworld.areas) FreeMemory(aasworld.areas); - aasworld.areas = NULL; - aasworld.numareas = 0; - //convex area settings - if (aasworld.areasettings) FreeMemory(aasworld.areasettings); - aasworld.areasettings = NULL; - aasworld.numareasettings = 0; - //reachablity list - if (aasworld.reachability) FreeMemory(aasworld.reachability); - aasworld.reachability = NULL; - aasworld.reachabilitysize = 0; - //nodes of the bsp tree - if (aasworld.nodes) FreeMemory(aasworld.nodes); - aasworld.nodes = NULL; - aasworld.numnodes = 0; - //cluster portals - if (aasworld.portals) FreeMemory(aasworld.portals); - aasworld.portals = NULL; - aasworld.numportals = 0; - //cluster portal index - if (aasworld.portalindex) FreeMemory(aasworld.portalindex); - aasworld.portalindex = NULL; - aasworld.portalindexsize = 0; - //clusters - if (aasworld.clusters) FreeMemory(aasworld.clusters); - aasworld.clusters = NULL; - aasworld.numclusters = 0; - - Log_Print("freed "); - PrintMemorySize(allocatedaasmem); - Log_Print(" of AAS memory\n"); - allocatedaasmem = 0; - // - if (aas_vertexchain) FreeMemory(aas_vertexchain); - aas_vertexchain = NULL; - if (aas_planechain) FreeMemory(aas_planechain); - aas_planechain = NULL; - if (aas_edgechain) FreeMemory(aas_edgechain); - aas_edgechain = NULL; -} //end of the function AAS_FreeMaxAAS -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -unsigned AAS_HashVec(vec3_t vec) -{ - int x, y; - - x = (MAX_MAP_BOUNDS + (int)(vec[0]+0.5)) >> VERTEX_HASH_SHIFT; - y = (MAX_MAP_BOUNDS + (int)(vec[1]+0.5)) >> VERTEX_HASH_SHIFT; - - if (x < 0 || x >= VERTEX_HASH_SIZE || y < 0 || y >= VERTEX_HASH_SIZE) - { - Log_Print("WARNING! HashVec: point %f %f %f outside valid range\n", vec[0], vec[1], vec[2]); - Log_Print("This should never happen!\n"); - return -1; - } //end if - - return y*VERTEX_HASH_SIZE + x; -} //end of the function AAS_HashVec -//=========================================================================== -// returns true if the vertex was found in the list -// stores the vertex number in *vnum -// stores a new vertex if not stored already -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean AAS_GetVertex(vec3_t v, int *vnum) -{ - int i; -#ifndef VERTEX_HASHING - float diff; -#endif //VERTEX_HASHING - -#ifdef VERTEX_HASHING - int h, vn; - vec3_t vert; - - for (i = 0; i < 3; i++) - { - if ( fabs(v[i] - Q_rint(v[i])) < INTEGRAL_EPSILON) - vert[i] = Q_rint(v[i]); - else - vert[i] = v[i]; - } //end for - - h = AAS_HashVec(vert); - //if the vertex was outside the valid range - if (h == -1) - { - *vnum = -1; - return true; - } //end if - - for (vn = aas_hashverts[h]; vn >= 0; vn = aas_vertexchain[vn]) - { - if (fabs(aasworld.vertexes[vn][0] - vert[0]) < VERTEX_EPSILON - && fabs(aasworld.vertexes[vn][1] - vert[1]) < VERTEX_EPSILON - && fabs(aasworld.vertexes[vn][2] - vert[2]) < VERTEX_EPSILON) - { - *vnum = vn; - return true; - } //end if - } //end for -#else //VERTEX_HASHING - //check if the vertex is already stored - //stupid linear search - for (i = 0; i < aasworld.numvertexes; i++) - { - diff = vert[0] - aasworld.vertexes[i][0]; - if (diff < VERTEX_EPSILON && diff > -VERTEX_EPSILON) - { - diff = vert[1] - aasworld.vertexes[i][1]; - if (diff < VERTEX_EPSILON && diff > -VERTEX_EPSILON) - { - diff = vert[2] - aasworld.vertexes[i][2]; - if (diff < VERTEX_EPSILON && diff > -VERTEX_EPSILON) - { - *vnum = i; - return true; - } //end if - } //end if - } //end if - } //end for -#endif //VERTEX_HASHING - - if (aasworld.numvertexes >= max_aas.max_vertexes) - { - Error("AAS_MAX_VERTEXES = %d", max_aas.max_vertexes); - } //end if - VectorCopy(vert, aasworld.vertexes[aasworld.numvertexes]); - *vnum = aasworld.numvertexes; - -#ifdef VERTEX_HASHING - aas_vertexchain[aasworld.numvertexes] = aas_hashverts[h]; - aas_hashverts[h] = aasworld.numvertexes; -#endif //VERTEX_HASHING - - aasworld.numvertexes++; - return false; -} //end of the function AAS_GetVertex -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -unsigned AAS_HashEdge(int v1, int v2) -{ - int vnum1, vnum2; - // - if (v1 < v2) - { - vnum1 = v1; - vnum2 = v2; - } //end if - else - { - vnum1 = v2; - vnum2 = v1; - } //end else - return (vnum1 + vnum2) & (EDGE_HASH_SIZE-1); -} //end of the function AAS_HashVec -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_AddEdgeToHash(int edgenum) -{ - int hash; - aas_edge_t *edge; - - edge = &aasworld.edges[edgenum]; - - hash = AAS_HashEdge(edge->v[0], edge->v[1]); - - aas_edgechain[edgenum] = aas_hashedges[hash]; - aas_hashedges[hash] = edgenum; -} //end of the function AAS_AddEdgeToHash -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean AAS_FindHashedEdge(int v1num, int v2num, int *edgenum) -{ - int e, hash; - aas_edge_t *edge; - - hash = AAS_HashEdge(v1num, v2num); - for (e = aas_hashedges[hash]; e >= 0; e = aas_edgechain[e]) - { - edge = &aasworld.edges[e]; - if (edge->v[0] == v1num) - { - if (edge->v[1] == v2num) - { - *edgenum = e; - return true; - } //end if - } //end if - else if (edge->v[1] == v1num) - { - if (edge->v[0] == v2num) - { - //negative for a reversed edge - *edgenum = -e; - return true; - } //end if - } //end else - } //end for - return false; -} //end of the function AAS_FindHashedPlane -//=========================================================================== -// returns true if the edge was found -// stores the edge number in *edgenum (negative if reversed edge) -// stores new edge if not stored already -// returns zero when the edge is degenerate -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean AAS_GetEdge(vec3_t v1, vec3_t v2, int *edgenum) -{ - int v1num, v2num; - qboolean found; - - //the first edge is a dummy - if (aasworld.numedges == 0) aasworld.numedges = 1; - - found = AAS_GetVertex(v1, &v1num); - found &= AAS_GetVertex(v2, &v2num); - //if one of the vertexes was outside the valid range - if (v1num == -1 || v2num == -1) - { - *edgenum = 0; - return true; - } //end if - //if both vertexes are the same or snapped onto each other - if (v1num == v2num) - { - *edgenum = 0; - return true; - } //end if - //if both vertexes where already stored - if (found) - { -#ifdef EDGE_HASHING - if (AAS_FindHashedEdge(v1num, v2num, edgenum)) return true; -#else - int i; - for (i = 1; i < aasworld.numedges; i++) - { - if (aasworld.edges[i].v[0] == v1num) - { - if (aasworld.edges[i].v[1] == v2num) - { - *edgenum = i; - return true; - } //end if - } //end if - else if (aasworld.edges[i].v[1] == v1num) - { - if (aasworld.edges[i].v[0] == v2num) - { - //negative for a reversed edge - *edgenum = -i; - return true; - } //end if - } //end else - } //end for -#endif //EDGE_HASHING - } //end if - if (aasworld.numedges >= max_aas.max_edges) - { - Error("AAS_MAX_EDGES = %d", max_aas.max_edges); - } //end if - aasworld.edges[aasworld.numedges].v[0] = v1num; - aasworld.edges[aasworld.numedges].v[1] = v2num; - *edgenum = aasworld.numedges; -#ifdef EDGE_HASHING - AAS_AddEdgeToHash(*edgenum); -#endif //EDGE_HASHING - aasworld.numedges++; - return false; -} //end of the function AAS_GetEdge -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_PlaneTypeForNormal(vec3_t normal) -{ - vec_t ax, ay, az; - - //NOTE: epsilon used - if ( (normal[0] >= 1.0 -NORMAL_EPSILON) || - (normal[0] <= -1.0 + NORMAL_EPSILON)) return PLANE_X; - if ( (normal[1] >= 1.0 -NORMAL_EPSILON) || - (normal[1] <= -1.0 + NORMAL_EPSILON)) return PLANE_Y; - if ( (normal[2] >= 1.0 -NORMAL_EPSILON) || - (normal[2] <= -1.0 + NORMAL_EPSILON)) return PLANE_Z; - - ax = fabs(normal[0]); - ay = fabs(normal[1]); - az = fabs(normal[2]); - - if (ax >= ay && ax >= az) return PLANE_ANYX; - if (ay >= ax && ay >= az) return PLANE_ANYY; - return PLANE_ANYZ; -} //end of the function AAS_PlaneTypeForNormal -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_AddPlaneToHash(int planenum) -{ - int hash; - aas_plane_t *plane; - - plane = &aasworld.planes[planenum]; - - hash = (int)fabs(plane->dist) / 8; - hash &= (PLANE_HASH_SIZE-1); - - aas_planechain[planenum] = aas_hashplanes[hash]; - aas_hashplanes[hash] = planenum; -} //end of the function AAS_AddPlaneToHash -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_PlaneEqual(vec3_t normal, float dist, int planenum) -{ - float diff; - - diff = dist - aasworld.planes[planenum].dist; - if (diff > -DIST_EPSILON && diff < DIST_EPSILON) - { - diff = normal[0] - aasworld.planes[planenum].normal[0]; - if (diff > -NORMAL_EPSILON && diff < NORMAL_EPSILON) - { - diff = normal[1] - aasworld.planes[planenum].normal[1]; - if (diff > -NORMAL_EPSILON && diff < NORMAL_EPSILON) - { - diff = normal[2] - aasworld.planes[planenum].normal[2]; - if (diff > -NORMAL_EPSILON && diff < NORMAL_EPSILON) - { - return true; - } //end if - } //end if - } //end if - } //end if - return false; -} //end of the function AAS_PlaneEqual -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean AAS_FindPlane(vec3_t normal, float dist, int *planenum) -{ - int i; - - for (i = 0; i < aasworld.numplanes; i++) - { - if (AAS_PlaneEqual(normal, dist, i)) - { - *planenum = i; - return true; - } //end if - } //end for - return false; -} //end of the function AAS_FindPlane -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean AAS_FindHashedPlane(vec3_t normal, float dist, int *planenum) -{ - int i, p; - aas_plane_t *plane; - int hash, h; - - hash = (int)fabs(dist) / 8; - hash &= (PLANE_HASH_SIZE-1); - - //search the border bins as well - for (i = -1; i <= 1; i++) - { - h = (hash+i)&(PLANE_HASH_SIZE-1); - for (p = aas_hashplanes[h]; p >= 0; p = aas_planechain[p]) - { - plane = &aasworld.planes[p]; - if (AAS_PlaneEqual(normal, dist, p)) - { - *planenum = p; - return true; - } //end if - } //end for - } //end for - return false; -} //end of the function AAS_FindHashedPlane -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean AAS_GetPlane(vec3_t normal, vec_t dist, int *planenum) -{ - aas_plane_t *plane, temp; - - //if (AAS_FindPlane(normal, dist, planenum)) return true; - if (AAS_FindHashedPlane(normal, dist, planenum)) return true; - - if (aasworld.numplanes >= max_aas.max_planes-1) - { - Error("AAS_MAX_PLANES = %d", max_aas.max_planes); - } //end if - -#ifdef STOREPLANESDOUBLE - plane = &aasworld.planes[aasworld.numplanes]; - VectorCopy(normal, plane->normal); - plane->dist = dist; - plane->type = (plane+1)->type = PlaneTypeForNormal(plane->normal); - - VectorCopy(normal, (plane+1)->normal); - VectorNegate((plane+1)->normal, (plane+1)->normal); - (plane+1)->dist = -dist; - - aasworld.numplanes += 2; - - //allways put axial planes facing positive first - if (plane->type < 3) - { - if (plane->normal[0] < 0 || plane->normal[1] < 0 || plane->normal[2] < 0) - { - // flip order - temp = *plane; - *plane = *(plane+1); - *(plane+1) = temp; - *planenum = aasworld.numplanes - 1; - return false; - } //end if - } //end if - *planenum = aasworld.numplanes - 2; - //add the planes to the hash - AAS_AddPlaneToHash(aasworld.numplanes - 1); - AAS_AddPlaneToHash(aasworld.numplanes - 2); - return false; -#else - plane = &aasworld.planes[aasworld.numplanes]; - VectorCopy(normal, plane->normal); - plane->dist = dist; - plane->type = AAS_PlaneTypeForNormal(normal); - - *planenum = aasworld.numplanes; - aasworld.numplanes++; - //add the plane to the hash - AAS_AddPlaneToHash(aasworld.numplanes - 1); - return false; -#endif //STOREPLANESDOUBLE -} //end of the function AAS_GetPlane -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean AAS_GetFace(winding_t *w, plane_t *p, int side, int *facenum) -{ - int edgenum, i, j; - aas_face_t *face; - - //face zero is a dummy, because of the face index with negative numbers - if (aasworld.numfaces == 0) aasworld.numfaces = 1; - - if (aasworld.numfaces >= max_aas.max_faces) - { - Error("AAS_MAX_FACES = %d", max_aas.max_faces); - } //end if - face = &aasworld.faces[aasworld.numfaces]; - AAS_GetPlane(p->normal, p->dist, &face->planenum); - face->faceflags = 0; - face->firstedge = aasworld.edgeindexsize; - face->frontarea = 0; - face->backarea = 0; - face->numedges = 0; - for (i = 0; i < w->numpoints; i++) - { - if (aasworld.edgeindexsize >= max_aas.max_edgeindexsize) - { - Error("AAS_MAX_EDGEINDEXSIZE = %d", max_aas.max_edgeindexsize); - } //end if - j = (i+1) % w->numpoints; - AAS_GetEdge(w->p[i], w->p[j], &edgenum); - //if the edge wasn't degenerate - if (edgenum) - { - aasworld.edgeindex[aasworld.edgeindexsize++] = edgenum; - face->numedges++; - } //end if - else if (verbose) - { - Log_Write("AAS_GetFace: face %d had degenerate edge %d-%d\r\n", - aasworld.numfaces, i, j); - } //end else - } //end for - if (face->numedges < 1 -#ifdef NOTHREEVERTEXFACES - || face->numedges < 3 -#endif //NOTHREEVERTEXFACES - ) - { - memset(&aasworld.faces[aasworld.numfaces], 0, sizeof(aas_face_t)); - Log_Write("AAS_GetFace: face %d was tiny\r\n", aasworld.numfaces); - return false; - } //end if - *facenum = aasworld.numfaces; - aasworld.numfaces++; - return true; -} //end of the function AAS_GetFace -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -/* -qboolean AAS_GetFace(winding_t *w, plane_t *p, int side, int *facenum) -{ - aas_edgeindex_t edges[1024]; - int planenum, numedges, i; - int j, edgenum; - qboolean foundplane, foundedges; - aas_face_t *face; - - //face zero is a dummy, because of the face index with negative numbers - if (aasworld.numfaces == 0) aasworld.numfaces = 1; - - foundplane = AAS_GetPlane(p->normal, p->dist, &planenum); - - foundedges = true; - numedges = w->numpoints; - for (i = 0; i < w->numpoints; i++) - { - if (i >= 1024) Error("AAS_GetFace: more than %d edges\n", 1024); - foundedges &= AAS_GetEdge(w->p[i], w->p[(i+1 >= w->numpoints ? 0 : i+1)], &edges[i]); - } //end for - - //FIXME: use portal number instead of a search - //if the plane and all edges already existed - if (foundplane && foundedges) - { - for (i = 0; i < aasworld.numfaces; i++) - { - face = &aasworld.faces[i]; - if (planenum == face->planenum) - { - if (numedges == face->numedges) - { - for (j = 0; j < numedges; j++) - { - edgenum = abs(aasworld.edgeindex[face->firstedge + j]); - if (abs(edges[i]) != edgenum) break; - } //end for - if (j == numedges) - { - //jippy found the face - *facenum = -i; - return true; - } //end if - } //end if - } //end if - } //end for - } //end if - if (aasworld.numfaces >= max_aas.max_faces) - { - Error("AAS_MAX_FACES = %d", max_aas.max_faces); - } //end if - face = &aasworld.faces[aasworld.numfaces]; - face->planenum = planenum; - face->faceflags = 0; - face->numedges = numedges; - face->firstedge = aasworld.edgeindexsize; - face->frontarea = 0; - face->backarea = 0; - for (i = 0; i < numedges; i++) - { - if (aasworld.edgeindexsize >= max_aas.max_edgeindexsize) - { - Error("AAS_MAX_EDGEINDEXSIZE = %d", max_aas.max_edgeindexsize); - } //end if - aasworld.edgeindex[aasworld.edgeindexsize++] = edges[i]; - } //end for - *facenum = aasworld.numfaces; - aasworld.numfaces++; - return false; -} //end of the function AAS_GetFace*/ -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_StoreAreaSettings(tmp_areasettings_t *tmpareasettings) -{ - aas_areasettings_t *areasettings; - - if (aasworld.numareasettings == 0) aasworld.numareasettings = 1; - areasettings = &aasworld.areasettings[aasworld.numareasettings++]; - areasettings->areaflags = tmpareasettings->areaflags; - areasettings->presencetype = tmpareasettings->presencetype; - areasettings->contents = tmpareasettings->contents; - if (tmpareasettings->modelnum > AREACONTENTS_MAXMODELNUM) - Log_Print("WARNING: more than %d mover models\n", AREACONTENTS_MAXMODELNUM); - areasettings->contents |= (tmpareasettings->modelnum & AREACONTENTS_MAXMODELNUM) << AREACONTENTS_MODELNUMSHIFT; -} //end of the function AAS_StoreAreaSettings -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_StoreArea(tmp_area_t *tmparea) -{ - int side, edgenum, i; - plane_t *plane; - tmp_face_t *tmpface; - aas_area_t *aasarea; - aas_edge_t *edge; - aas_face_t *aasface; - aas_faceindex_t aasfacenum; - vec3_t facecenter; - winding_t *w; - - //when the area is merged go to the merged area - //FIXME: this isn't necessary anymore because the tree - // is refreshed after area merging - while(tmparea->mergedarea) tmparea = tmparea->mergedarea; - // - if (tmparea->invalid) Error("AAS_StoreArea: tried to store invalid area"); - //if there is an aas area already stored for this tmp area - if (tmparea->aasareanum) return -tmparea->aasareanum; - // - if (aasworld.numareas >= max_aas.max_areas) - { - Error("AAS_MAX_AREAS = %d", max_aas.max_areas); - } //end if - //area zero is a dummy - if (aasworld.numareas == 0) aasworld.numareas = 1; - //create an area from this leaf - aasarea = &aasworld.areas[aasworld.numareas]; - aasarea->areanum = aasworld.numareas; - aasarea->numfaces = 0; - aasarea->firstface = aasworld.faceindexsize; - ClearBounds(aasarea->mins, aasarea->maxs); - VectorClear(aasarea->center); - // -// Log_Write("tmparea %d became aasarea %d\r\n", tmparea->areanum, aasarea->areanum); - //store the aas area number at the tmp area - tmparea->aasareanum = aasarea->areanum; - // - for (tmpface = tmparea->tmpfaces; tmpface; tmpface = tmpface->next[side]) - { - side = tmpface->frontarea != tmparea; - //if there's an aas face created for the tmp face already - if (tmpface->aasfacenum) - { - //we're at the back of the face so use a negative index - aasfacenum = -tmpface->aasfacenum; -#ifdef DEBUG - if (tmpface->aasfacenum < 0 || tmpface->aasfacenum > max_aas.max_faces) - { - Error("AAS_CreateTree_r: face number out of range"); - } //end if -#endif //DEBUG - aasface = &aasworld.faces[tmpface->aasfacenum]; - aasface->backarea = aasarea->areanum; - } //end if - else - { - plane = &mapplanes[tmpface->planenum ^ side]; - if (side) - { - w = tmpface->winding; - tmpface->winding = ReverseWinding(tmpface->winding); - } //end if - if (!AAS_GetFace(tmpface->winding, plane, 0, &aasfacenum)) continue; - if (side) - { - FreeWinding(tmpface->winding); - tmpface->winding = w; - } //end if - aasface = &aasworld.faces[aasfacenum]; - aasface->frontarea = aasarea->areanum; - aasface->backarea = 0; - aasface->faceflags = tmpface->faceflags; - //set the face number at the tmp face - tmpface->aasfacenum = aasfacenum; - } //end else - //add face points to the area bounds and - //calculate the face 'center' - VectorClear(facecenter); - for (edgenum = 0; edgenum < aasface->numedges; edgenum++) - { - edge = &aasworld.edges[abs(aasworld.edgeindex[aasface->firstedge + edgenum])]; - for (i = 0; i < 2; i++) - { - AddPointToBounds(aasworld.vertexes[edge->v[i]], aasarea->mins, aasarea->maxs); - VectorAdd(aasworld.vertexes[edge->v[i]], facecenter, facecenter); - } //end for - } //end for - VectorScale(facecenter, 1.0 / (aasface->numedges * 2.0), facecenter); - //add the face 'center' to the area 'center' - VectorAdd(aasarea->center, facecenter, aasarea->center); - // - if (aasworld.faceindexsize >= max_aas.max_faceindexsize) - { - Error("AAS_MAX_FACEINDEXSIZE = %d", max_aas.max_faceindexsize); - } //end if - aasworld.faceindex[aasworld.faceindexsize++] = aasfacenum; - aasarea->numfaces++; - } //end for - //if the area has no faces at all (return 0, = solid leaf) - if (!aasarea->numfaces) return 0; - // - VectorScale(aasarea->center, 1.0 / aasarea->numfaces, aasarea->center); - //Log_Write("area %d center %f %f %f\r\n", aasworld.numareas, - // aasarea->center[0], aasarea->center[1], aasarea->center[2]); - //store the area settings - AAS_StoreAreaSettings(tmparea->settings); - // - //Log_Write("tmp area %d became aas area %d\r\n", tmpareanum, aasarea->areanum); - qprintf("\r%6d", aasarea->areanum); - // - aasworld.numareas++; - return -(aasworld.numareas - 1); -} //end of the function AAS_StoreArea -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int AAS_StoreTree_r(tmp_node_t *tmpnode) -{ - int aasnodenum; - plane_t *plane; - aas_node_t *aasnode; - - //if it is a solid leaf - if (!tmpnode) return 0; - //negative so it's an area - if (tmpnode->tmparea) return AAS_StoreArea(tmpnode->tmparea); - //it's another node - //the first node is a dummy - if (aasworld.numnodes == 0) aasworld.numnodes = 1; - if (aasworld.numnodes >= max_aas.max_nodes) - { - Error("AAS_MAX_NODES = %d", max_aas.max_nodes); - } //end if - aasnodenum = aasworld.numnodes; - aasnode = &aasworld.nodes[aasworld.numnodes++]; - plane = &mapplanes[tmpnode->planenum]; - AAS_GetPlane(plane->normal, plane->dist, &aasnode->planenum); - aasnode->children[0] = AAS_StoreTree_r(tmpnode->children[0]); - aasnode->children[1] = AAS_StoreTree_r(tmpnode->children[1]); - return aasnodenum; -} //end of the function AAS_StoreTree_r -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_StoreBoundingBoxes(void) -{ - if (cfg.numbboxes > max_aas.max_bboxes) - { - Error("more than %d bounding boxes", max_aas.max_bboxes); - } //end if - aasworld.numbboxes = cfg.numbboxes; - memcpy(aasworld.bboxes, cfg.bboxes, cfg.numbboxes * sizeof(aas_bbox_t)); -} //end of the function AAS_StoreBoundingBoxes -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_StoreFile(char *filename) -{ - AAS_AllocMaxAAS(); - - Log_Write("AAS_StoreFile\r\n"); - // - AAS_StoreBoundingBoxes(); - // - qprintf("%6d areas stored", 0); - //start with node 1 because node zero is a dummy - AAS_StoreTree_r(tmpaasworld.nodes); - qprintf("\n"); - Log_Write("%6d areas stored\r\n", aasworld.numareas); - aasworld.loaded = true; -} //end of the function AAS_StoreFile +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "qbsp.h" +#include "../botlib/aasfile.h" +#include "aas_file.h" +#include "aas_store.h" +#include "aas_create.h" +#include "aas_cfg.h" + + +//#define NOTHREEVERTEXFACES + +#define STOREPLANESDOUBLE + +#define VERTEX_EPSILON 0.1 //NOTE: changed from 0.5 +#define DIST_EPSILON 0.05 //NOTE: changed from 0.9 +#define NORMAL_EPSILON 0.0001 //NOTE: changed from 0.005 +#define INTEGRAL_EPSILON 0.01 + +#define VERTEX_HASHING +#define VERTEX_HASH_SHIFT 7 +#define VERTEX_HASH_SIZE ((MAX_MAP_BOUNDS>>(VERTEX_HASH_SHIFT-1))+1) //was 64 +// +#define PLANE_HASHING +#define PLANE_HASH_SIZE 1024 //must be power of 2 +// +#define EDGE_HASHING +#define EDGE_HASH_SIZE 1024 //must be power of 2 + +aas_t aasworld; + +//vertex hash +int *aas_vertexchain; // the next vertex in a hash chain +int aas_hashverts[VERTEX_HASH_SIZE*VERTEX_HASH_SIZE]; // a vertex number, or 0 for no verts +//plane hash +int *aas_planechain; +int aas_hashplanes[PLANE_HASH_SIZE]; +//edge hash +int *aas_edgechain; +int aas_hashedges[EDGE_HASH_SIZE]; + +int allocatedaasmem = 0; + +int groundfacesonly = false;//true; +// +typedef struct max_aas_s +{ + int max_bboxes; + int max_vertexes; + int max_planes; + int max_edges; + int max_edgeindexsize; + int max_faces; + int max_faceindexsize; + int max_areas; + int max_areasettings; + int max_reachabilitysize; + int max_nodes; + int max_portals; + int max_portalindexsize; + int max_clusters; +} max_aas_t; +//maximums of everything +max_aas_t max_aas; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_CountTmpNodes(tmp_node_t *tmpnode) +{ + if (!tmpnode) return 0; + return AAS_CountTmpNodes(tmpnode->children[0]) + + AAS_CountTmpNodes(tmpnode->children[1]) + 1; +} //end of the function AAS_CountTmpNodes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitMaxAAS(void) +{ + int numfaces, numpoints, numareas; + tmp_face_t *f; + tmp_area_t *a; + + numpoints = 0; + numfaces = 0; + for (f = tmpaasworld.faces; f; f = f->l_next) + { + numfaces++; + if (f->winding) numpoints += f->winding->numpoints; + } //end for + // + numareas = 0; + for (a = tmpaasworld.areas; a; a = a->l_next) + { + numareas++; + } //end for + max_aas.max_bboxes = AAS_MAX_BBOXES; + max_aas.max_vertexes = numpoints + 1; + max_aas.max_planes = nummapplanes; + max_aas.max_edges = numpoints + 1; + max_aas.max_edgeindexsize = (numpoints + 1) * 3; + max_aas.max_faces = numfaces + 10; + max_aas.max_faceindexsize = (numfaces + 10) * 2; + max_aas.max_areas = numareas + 10; + max_aas.max_areasettings = numareas + 10; + max_aas.max_reachabilitysize = 0; + max_aas.max_nodes = AAS_CountTmpNodes(tmpaasworld.nodes) + 10; + max_aas.max_portals = 0; + max_aas.max_portalindexsize = 0; + max_aas.max_clusters = 0; +} //end of the function AAS_InitMaxAAS +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_AllocMaxAAS(void) +{ + int i; + + AAS_InitMaxAAS(); + //bounding boxes + aasworld.numbboxes = 0; + aasworld.bboxes = (aas_bbox_t *) GetClearedMemory(max_aas.max_bboxes * sizeof(aas_bbox_t)); + allocatedaasmem += max_aas.max_bboxes * sizeof(aas_bbox_t); + //vertexes + aasworld.numvertexes = 0; + aasworld.vertexes = (aas_vertex_t *) GetClearedMemory(max_aas.max_vertexes * sizeof(aas_vertex_t)); + allocatedaasmem += max_aas.max_vertexes * sizeof(aas_vertex_t); + //planes + aasworld.numplanes = 0; + aasworld.planes = (aas_plane_t *) GetClearedMemory(max_aas.max_planes * sizeof(aas_plane_t)); + allocatedaasmem += max_aas.max_planes * sizeof(aas_plane_t); + //edges + aasworld.numedges = 0; + aasworld.edges = (aas_edge_t *) GetClearedMemory(max_aas.max_edges * sizeof(aas_edge_t)); + allocatedaasmem += max_aas.max_edges * sizeof(aas_edge_t); + //edge index + aasworld.edgeindexsize = 0; + aasworld.edgeindex = (aas_edgeindex_t *) GetClearedMemory(max_aas.max_edgeindexsize * sizeof(aas_edgeindex_t)); + allocatedaasmem += max_aas.max_edgeindexsize * sizeof(aas_edgeindex_t); + //faces + aasworld.numfaces = 0; + aasworld.faces = (aas_face_t *) GetClearedMemory(max_aas.max_faces * sizeof(aas_face_t)); + allocatedaasmem += max_aas.max_faces * sizeof(aas_face_t); + //face index + aasworld.faceindexsize = 0; + aasworld.faceindex = (aas_faceindex_t *) GetClearedMemory(max_aas.max_faceindexsize * sizeof(aas_faceindex_t)); + allocatedaasmem += max_aas.max_faceindexsize * sizeof(aas_faceindex_t); + //convex areas + aasworld.numareas = 0; + aasworld.areas = (aas_area_t *) GetClearedMemory(max_aas.max_areas * sizeof(aas_area_t)); + allocatedaasmem += max_aas.max_areas * sizeof(aas_area_t); + //convex area settings + aasworld.numareasettings = 0; + aasworld.areasettings = (aas_areasettings_t *) GetClearedMemory(max_aas.max_areasettings * sizeof(aas_areasettings_t)); + allocatedaasmem += max_aas.max_areasettings * sizeof(aas_areasettings_t); + //reachablity list + aasworld.reachabilitysize = 0; + aasworld.reachability = (aas_reachability_t *) GetClearedMemory(max_aas.max_reachabilitysize * sizeof(aas_reachability_t)); + allocatedaasmem += max_aas.max_reachabilitysize * sizeof(aas_reachability_t); + //nodes of the bsp tree + aasworld.numnodes = 0; + aasworld.nodes = (aas_node_t *) GetClearedMemory(max_aas.max_nodes * sizeof(aas_node_t)); + allocatedaasmem += max_aas.max_nodes * sizeof(aas_node_t); + //cluster portals + aasworld.numportals = 0; + aasworld.portals = (aas_portal_t *) GetClearedMemory(max_aas.max_portals * sizeof(aas_portal_t)); + allocatedaasmem += max_aas.max_portals * sizeof(aas_portal_t); + //cluster portal index + aasworld.portalindexsize = 0; + aasworld.portalindex = (aas_portalindex_t *) GetClearedMemory(max_aas.max_portalindexsize * sizeof(aas_portalindex_t)); + allocatedaasmem += max_aas.max_portalindexsize * sizeof(aas_portalindex_t); + //cluster + aasworld.numclusters = 0; + aasworld.clusters = (aas_cluster_t *) GetClearedMemory(max_aas.max_clusters * sizeof(aas_cluster_t)); + allocatedaasmem += max_aas.max_clusters * sizeof(aas_cluster_t); + // + Log_Print("allocated "); + PrintMemorySize(allocatedaasmem); + Log_Print(" of AAS memory\n"); + //reset the has stuff + aas_vertexchain = (int *) GetClearedMemory(max_aas.max_vertexes * sizeof(int)); + aas_planechain = (int *) GetClearedMemory(max_aas.max_planes * sizeof(int)); + aas_edgechain = (int *) GetClearedMemory(max_aas.max_edges * sizeof(int)); + // + for (i = 0; i < max_aas.max_vertexes; i++) aas_vertexchain[i] = -1; + for (i = 0; i < VERTEX_HASH_SIZE * VERTEX_HASH_SIZE; i++) aas_hashverts[i] = -1; + // + for (i = 0; i < max_aas.max_planes; i++) aas_planechain[i] = -1; + for (i = 0; i < PLANE_HASH_SIZE; i++) aas_hashplanes[i] = -1; + // + for (i = 0; i < max_aas.max_edges; i++) aas_edgechain[i] = -1; + for (i = 0; i < EDGE_HASH_SIZE; i++) aas_hashedges[i] = -1; +} //end of the function AAS_AllocMaxAAS +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeMaxAAS(void) +{ + //bounding boxes + if (aasworld.bboxes) FreeMemory(aasworld.bboxes); + aasworld.bboxes = NULL; + aasworld.numbboxes = 0; + //vertexes + if (aasworld.vertexes) FreeMemory(aasworld.vertexes); + aasworld.vertexes = NULL; + aasworld.numvertexes = 0; + //planes + if (aasworld.planes) FreeMemory(aasworld.planes); + aasworld.planes = NULL; + aasworld.numplanes = 0; + //edges + if (aasworld.edges) FreeMemory(aasworld.edges); + aasworld.edges = NULL; + aasworld.numedges = 0; + //edge index + if (aasworld.edgeindex) FreeMemory(aasworld.edgeindex); + aasworld.edgeindex = NULL; + aasworld.edgeindexsize = 0; + //faces + if (aasworld.faces) FreeMemory(aasworld.faces); + aasworld.faces = NULL; + aasworld.numfaces = 0; + //face index + if (aasworld.faceindex) FreeMemory(aasworld.faceindex); + aasworld.faceindex = NULL; + aasworld.faceindexsize = 0; + //convex areas + if (aasworld.areas) FreeMemory(aasworld.areas); + aasworld.areas = NULL; + aasworld.numareas = 0; + //convex area settings + if (aasworld.areasettings) FreeMemory(aasworld.areasettings); + aasworld.areasettings = NULL; + aasworld.numareasettings = 0; + //reachablity list + if (aasworld.reachability) FreeMemory(aasworld.reachability); + aasworld.reachability = NULL; + aasworld.reachabilitysize = 0; + //nodes of the bsp tree + if (aasworld.nodes) FreeMemory(aasworld.nodes); + aasworld.nodes = NULL; + aasworld.numnodes = 0; + //cluster portals + if (aasworld.portals) FreeMemory(aasworld.portals); + aasworld.portals = NULL; + aasworld.numportals = 0; + //cluster portal index + if (aasworld.portalindex) FreeMemory(aasworld.portalindex); + aasworld.portalindex = NULL; + aasworld.portalindexsize = 0; + //clusters + if (aasworld.clusters) FreeMemory(aasworld.clusters); + aasworld.clusters = NULL; + aasworld.numclusters = 0; + + Log_Print("freed "); + PrintMemorySize(allocatedaasmem); + Log_Print(" of AAS memory\n"); + allocatedaasmem = 0; + // + if (aas_vertexchain) FreeMemory(aas_vertexchain); + aas_vertexchain = NULL; + if (aas_planechain) FreeMemory(aas_planechain); + aas_planechain = NULL; + if (aas_edgechain) FreeMemory(aas_edgechain); + aas_edgechain = NULL; +} //end of the function AAS_FreeMaxAAS +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +unsigned AAS_HashVec(vec3_t vec) +{ + int x, y; + + x = (MAX_MAP_BOUNDS + (int)(vec[0]+0.5)) >> VERTEX_HASH_SHIFT; + y = (MAX_MAP_BOUNDS + (int)(vec[1]+0.5)) >> VERTEX_HASH_SHIFT; + + if (x < 0 || x >= VERTEX_HASH_SIZE || y < 0 || y >= VERTEX_HASH_SIZE) + { + Log_Print("WARNING! HashVec: point %f %f %f outside valid range\n", vec[0], vec[1], vec[2]); + Log_Print("This should never happen!\n"); + return -1; + } //end if + + return y*VERTEX_HASH_SIZE + x; +} //end of the function AAS_HashVec +//=========================================================================== +// returns true if the vertex was found in the list +// stores the vertex number in *vnum +// stores a new vertex if not stored already +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_GetVertex(vec3_t v, int *vnum) +{ + int i; +#ifndef VERTEX_HASHING + float diff; +#endif //VERTEX_HASHING + +#ifdef VERTEX_HASHING + int h, vn; + vec3_t vert; + + for (i = 0; i < 3; i++) + { + if ( fabs(v[i] - Q_rint(v[i])) < INTEGRAL_EPSILON) + vert[i] = Q_rint(v[i]); + else + vert[i] = v[i]; + } //end for + + h = AAS_HashVec(vert); + //if the vertex was outside the valid range + if (h == -1) + { + *vnum = -1; + return true; + } //end if + + for (vn = aas_hashverts[h]; vn >= 0; vn = aas_vertexchain[vn]) + { + if (fabs(aasworld.vertexes[vn][0] - vert[0]) < VERTEX_EPSILON + && fabs(aasworld.vertexes[vn][1] - vert[1]) < VERTEX_EPSILON + && fabs(aasworld.vertexes[vn][2] - vert[2]) < VERTEX_EPSILON) + { + *vnum = vn; + return true; + } //end if + } //end for +#else //VERTEX_HASHING + //check if the vertex is already stored + //stupid linear search + for (i = 0; i < aasworld.numvertexes; i++) + { + diff = vert[0] - aasworld.vertexes[i][0]; + if (diff < VERTEX_EPSILON && diff > -VERTEX_EPSILON) + { + diff = vert[1] - aasworld.vertexes[i][1]; + if (diff < VERTEX_EPSILON && diff > -VERTEX_EPSILON) + { + diff = vert[2] - aasworld.vertexes[i][2]; + if (diff < VERTEX_EPSILON && diff > -VERTEX_EPSILON) + { + *vnum = i; + return true; + } //end if + } //end if + } //end if + } //end for +#endif //VERTEX_HASHING + + if (aasworld.numvertexes >= max_aas.max_vertexes) + { + Error("AAS_MAX_VERTEXES = %d", max_aas.max_vertexes); + } //end if + VectorCopy(vert, aasworld.vertexes[aasworld.numvertexes]); + *vnum = aasworld.numvertexes; + +#ifdef VERTEX_HASHING + aas_vertexchain[aasworld.numvertexes] = aas_hashverts[h]; + aas_hashverts[h] = aasworld.numvertexes; +#endif //VERTEX_HASHING + + aasworld.numvertexes++; + return false; +} //end of the function AAS_GetVertex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +unsigned AAS_HashEdge(int v1, int v2) +{ + int vnum1, vnum2; + // + if (v1 < v2) + { + vnum1 = v1; + vnum2 = v2; + } //end if + else + { + vnum1 = v2; + vnum2 = v1; + } //end else + return (vnum1 + vnum2) & (EDGE_HASH_SIZE-1); +} //end of the function AAS_HashVec +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_AddEdgeToHash(int edgenum) +{ + int hash; + aas_edge_t *edge; + + edge = &aasworld.edges[edgenum]; + + hash = AAS_HashEdge(edge->v[0], edge->v[1]); + + aas_edgechain[edgenum] = aas_hashedges[hash]; + aas_hashedges[hash] = edgenum; +} //end of the function AAS_AddEdgeToHash +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_FindHashedEdge(int v1num, int v2num, int *edgenum) +{ + int e, hash; + aas_edge_t *edge; + + hash = AAS_HashEdge(v1num, v2num); + for (e = aas_hashedges[hash]; e >= 0; e = aas_edgechain[e]) + { + edge = &aasworld.edges[e]; + if (edge->v[0] == v1num) + { + if (edge->v[1] == v2num) + { + *edgenum = e; + return true; + } //end if + } //end if + else if (edge->v[1] == v1num) + { + if (edge->v[0] == v2num) + { + //negative for a reversed edge + *edgenum = -e; + return true; + } //end if + } //end else + } //end for + return false; +} //end of the function AAS_FindHashedPlane +//=========================================================================== +// returns true if the edge was found +// stores the edge number in *edgenum (negative if reversed edge) +// stores new edge if not stored already +// returns zero when the edge is degenerate +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_GetEdge(vec3_t v1, vec3_t v2, int *edgenum) +{ + int v1num, v2num; + qboolean found; + + //the first edge is a dummy + if (aasworld.numedges == 0) aasworld.numedges = 1; + + found = AAS_GetVertex(v1, &v1num); + found &= AAS_GetVertex(v2, &v2num); + //if one of the vertexes was outside the valid range + if (v1num == -1 || v2num == -1) + { + *edgenum = 0; + return true; + } //end if + //if both vertexes are the same or snapped onto each other + if (v1num == v2num) + { + *edgenum = 0; + return true; + } //end if + //if both vertexes where already stored + if (found) + { +#ifdef EDGE_HASHING + if (AAS_FindHashedEdge(v1num, v2num, edgenum)) return true; +#else + int i; + for (i = 1; i < aasworld.numedges; i++) + { + if (aasworld.edges[i].v[0] == v1num) + { + if (aasworld.edges[i].v[1] == v2num) + { + *edgenum = i; + return true; + } //end if + } //end if + else if (aasworld.edges[i].v[1] == v1num) + { + if (aasworld.edges[i].v[0] == v2num) + { + //negative for a reversed edge + *edgenum = -i; + return true; + } //end if + } //end else + } //end for +#endif //EDGE_HASHING + } //end if + if (aasworld.numedges >= max_aas.max_edges) + { + Error("AAS_MAX_EDGES = %d", max_aas.max_edges); + } //end if + aasworld.edges[aasworld.numedges].v[0] = v1num; + aasworld.edges[aasworld.numedges].v[1] = v2num; + *edgenum = aasworld.numedges; +#ifdef EDGE_HASHING + AAS_AddEdgeToHash(*edgenum); +#endif //EDGE_HASHING + aasworld.numedges++; + return false; +} //end of the function AAS_GetEdge +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_PlaneTypeForNormal(vec3_t normal) +{ + vec_t ax, ay, az; + + //NOTE: epsilon used + if ( (normal[0] >= 1.0 -NORMAL_EPSILON) || + (normal[0] <= -1.0 + NORMAL_EPSILON)) return PLANE_X; + if ( (normal[1] >= 1.0 -NORMAL_EPSILON) || + (normal[1] <= -1.0 + NORMAL_EPSILON)) return PLANE_Y; + if ( (normal[2] >= 1.0 -NORMAL_EPSILON) || + (normal[2] <= -1.0 + NORMAL_EPSILON)) return PLANE_Z; + + ax = fabs(normal[0]); + ay = fabs(normal[1]); + az = fabs(normal[2]); + + if (ax >= ay && ax >= az) return PLANE_ANYX; + if (ay >= ax && ay >= az) return PLANE_ANYY; + return PLANE_ANYZ; +} //end of the function AAS_PlaneTypeForNormal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_AddPlaneToHash(int planenum) +{ + int hash; + aas_plane_t *plane; + + plane = &aasworld.planes[planenum]; + + hash = (int)fabs(plane->dist) / 8; + hash &= (PLANE_HASH_SIZE-1); + + aas_planechain[planenum] = aas_hashplanes[hash]; + aas_hashplanes[hash] = planenum; +} //end of the function AAS_AddPlaneToHash +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_PlaneEqual(vec3_t normal, float dist, int planenum) +{ + float diff; + + diff = dist - aasworld.planes[planenum].dist; + if (diff > -DIST_EPSILON && diff < DIST_EPSILON) + { + diff = normal[0] - aasworld.planes[planenum].normal[0]; + if (diff > -NORMAL_EPSILON && diff < NORMAL_EPSILON) + { + diff = normal[1] - aasworld.planes[planenum].normal[1]; + if (diff > -NORMAL_EPSILON && diff < NORMAL_EPSILON) + { + diff = normal[2] - aasworld.planes[planenum].normal[2]; + if (diff > -NORMAL_EPSILON && diff < NORMAL_EPSILON) + { + return true; + } //end if + } //end if + } //end if + } //end if + return false; +} //end of the function AAS_PlaneEqual +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_FindPlane(vec3_t normal, float dist, int *planenum) +{ + int i; + + for (i = 0; i < aasworld.numplanes; i++) + { + if (AAS_PlaneEqual(normal, dist, i)) + { + *planenum = i; + return true; + } //end if + } //end for + return false; +} //end of the function AAS_FindPlane +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_FindHashedPlane(vec3_t normal, float dist, int *planenum) +{ + int i, p; + aas_plane_t *plane; + int hash, h; + + hash = (int)fabs(dist) / 8; + hash &= (PLANE_HASH_SIZE-1); + + //search the border bins as well + for (i = -1; i <= 1; i++) + { + h = (hash+i)&(PLANE_HASH_SIZE-1); + for (p = aas_hashplanes[h]; p >= 0; p = aas_planechain[p]) + { + plane = &aasworld.planes[p]; + if (AAS_PlaneEqual(normal, dist, p)) + { + *planenum = p; + return true; + } //end if + } //end for + } //end for + return false; +} //end of the function AAS_FindHashedPlane +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_GetPlane(vec3_t normal, vec_t dist, int *planenum) +{ + aas_plane_t *plane, temp; + + //if (AAS_FindPlane(normal, dist, planenum)) return true; + if (AAS_FindHashedPlane(normal, dist, planenum)) return true; + + if (aasworld.numplanes >= max_aas.max_planes-1) + { + Error("AAS_MAX_PLANES = %d", max_aas.max_planes); + } //end if + +#ifdef STOREPLANESDOUBLE + plane = &aasworld.planes[aasworld.numplanes]; + VectorCopy(normal, plane->normal); + plane->dist = dist; + plane->type = (plane+1)->type = PlaneTypeForNormal(plane->normal); + + VectorCopy(normal, (plane+1)->normal); + VectorNegate((plane+1)->normal, (plane+1)->normal); + (plane+1)->dist = -dist; + + aasworld.numplanes += 2; + + //allways put axial planes facing positive first + if (plane->type < 3) + { + if (plane->normal[0] < 0 || plane->normal[1] < 0 || plane->normal[2] < 0) + { + // flip order + temp = *plane; + *plane = *(plane+1); + *(plane+1) = temp; + *planenum = aasworld.numplanes - 1; + return false; + } //end if + } //end if + *planenum = aasworld.numplanes - 2; + //add the planes to the hash + AAS_AddPlaneToHash(aasworld.numplanes - 1); + AAS_AddPlaneToHash(aasworld.numplanes - 2); + return false; +#else + plane = &aasworld.planes[aasworld.numplanes]; + VectorCopy(normal, plane->normal); + plane->dist = dist; + plane->type = AAS_PlaneTypeForNormal(normal); + + *planenum = aasworld.numplanes; + aasworld.numplanes++; + //add the plane to the hash + AAS_AddPlaneToHash(aasworld.numplanes - 1); + return false; +#endif //STOREPLANESDOUBLE +} //end of the function AAS_GetPlane +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_GetFace(winding_t *w, plane_t *p, int side, int *facenum) +{ + int edgenum, i, j; + aas_face_t *face; + + //face zero is a dummy, because of the face index with negative numbers + if (aasworld.numfaces == 0) aasworld.numfaces = 1; + + if (aasworld.numfaces >= max_aas.max_faces) + { + Error("AAS_MAX_FACES = %d", max_aas.max_faces); + } //end if + face = &aasworld.faces[aasworld.numfaces]; + AAS_GetPlane(p->normal, p->dist, &face->planenum); + face->faceflags = 0; + face->firstedge = aasworld.edgeindexsize; + face->frontarea = 0; + face->backarea = 0; + face->numedges = 0; + for (i = 0; i < w->numpoints; i++) + { + if (aasworld.edgeindexsize >= max_aas.max_edgeindexsize) + { + Error("AAS_MAX_EDGEINDEXSIZE = %d", max_aas.max_edgeindexsize); + } //end if + j = (i+1) % w->numpoints; + AAS_GetEdge(w->p[i], w->p[j], &edgenum); + //if the edge wasn't degenerate + if (edgenum) + { + aasworld.edgeindex[aasworld.edgeindexsize++] = edgenum; + face->numedges++; + } //end if + else if (verbose) + { + Log_Write("AAS_GetFace: face %d had degenerate edge %d-%d\r\n", + aasworld.numfaces, i, j); + } //end else + } //end for + if (face->numedges < 1 +#ifdef NOTHREEVERTEXFACES + || face->numedges < 3 +#endif //NOTHREEVERTEXFACES + ) + { + memset(&aasworld.faces[aasworld.numfaces], 0, sizeof(aas_face_t)); + Log_Write("AAS_GetFace: face %d was tiny\r\n", aasworld.numfaces); + return false; + } //end if + *facenum = aasworld.numfaces; + aasworld.numfaces++; + return true; +} //end of the function AAS_GetFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +/* +qboolean AAS_GetFace(winding_t *w, plane_t *p, int side, int *facenum) +{ + aas_edgeindex_t edges[1024]; + int planenum, numedges, i; + int j, edgenum; + qboolean foundplane, foundedges; + aas_face_t *face; + + //face zero is a dummy, because of the face index with negative numbers + if (aasworld.numfaces == 0) aasworld.numfaces = 1; + + foundplane = AAS_GetPlane(p->normal, p->dist, &planenum); + + foundedges = true; + numedges = w->numpoints; + for (i = 0; i < w->numpoints; i++) + { + if (i >= 1024) Error("AAS_GetFace: more than %d edges\n", 1024); + foundedges &= AAS_GetEdge(w->p[i], w->p[(i+1 >= w->numpoints ? 0 : i+1)], &edges[i]); + } //end for + + //FIXME: use portal number instead of a search + //if the plane and all edges already existed + if (foundplane && foundedges) + { + for (i = 0; i < aasworld.numfaces; i++) + { + face = &aasworld.faces[i]; + if (planenum == face->planenum) + { + if (numedges == face->numedges) + { + for (j = 0; j < numedges; j++) + { + edgenum = abs(aasworld.edgeindex[face->firstedge + j]); + if (abs(edges[i]) != edgenum) break; + } //end for + if (j == numedges) + { + //jippy found the face + *facenum = -i; + return true; + } //end if + } //end if + } //end if + } //end for + } //end if + if (aasworld.numfaces >= max_aas.max_faces) + { + Error("AAS_MAX_FACES = %d", max_aas.max_faces); + } //end if + face = &aasworld.faces[aasworld.numfaces]; + face->planenum = planenum; + face->faceflags = 0; + face->numedges = numedges; + face->firstedge = aasworld.edgeindexsize; + face->frontarea = 0; + face->backarea = 0; + for (i = 0; i < numedges; i++) + { + if (aasworld.edgeindexsize >= max_aas.max_edgeindexsize) + { + Error("AAS_MAX_EDGEINDEXSIZE = %d", max_aas.max_edgeindexsize); + } //end if + aasworld.edgeindex[aasworld.edgeindexsize++] = edges[i]; + } //end for + *facenum = aasworld.numfaces; + aasworld.numfaces++; + return false; +} //end of the function AAS_GetFace*/ +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_StoreAreaSettings(tmp_areasettings_t *tmpareasettings) +{ + aas_areasettings_t *areasettings; + + if (aasworld.numareasettings == 0) aasworld.numareasettings = 1; + areasettings = &aasworld.areasettings[aasworld.numareasettings++]; + areasettings->areaflags = tmpareasettings->areaflags; + areasettings->presencetype = tmpareasettings->presencetype; + areasettings->contents = tmpareasettings->contents; + if (tmpareasettings->modelnum > AREACONTENTS_MAXMODELNUM) + Log_Print("WARNING: more than %d mover models\n", AREACONTENTS_MAXMODELNUM); + areasettings->contents |= (tmpareasettings->modelnum & AREACONTENTS_MAXMODELNUM) << AREACONTENTS_MODELNUMSHIFT; +} //end of the function AAS_StoreAreaSettings +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_StoreArea(tmp_area_t *tmparea) +{ + int side, edgenum, i; + plane_t *plane; + tmp_face_t *tmpface; + aas_area_t *aasarea; + aas_edge_t *edge; + aas_face_t *aasface; + aas_faceindex_t aasfacenum; + vec3_t facecenter; + winding_t *w; + + //when the area is merged go to the merged area + //FIXME: this isn't necessary anymore because the tree + // is refreshed after area merging + while(tmparea->mergedarea) tmparea = tmparea->mergedarea; + // + if (tmparea->invalid) Error("AAS_StoreArea: tried to store invalid area"); + //if there is an aas area already stored for this tmp area + if (tmparea->aasareanum) return -tmparea->aasareanum; + // + if (aasworld.numareas >= max_aas.max_areas) + { + Error("AAS_MAX_AREAS = %d", max_aas.max_areas); + } //end if + //area zero is a dummy + if (aasworld.numareas == 0) aasworld.numareas = 1; + //create an area from this leaf + aasarea = &aasworld.areas[aasworld.numareas]; + aasarea->areanum = aasworld.numareas; + aasarea->numfaces = 0; + aasarea->firstface = aasworld.faceindexsize; + ClearBounds(aasarea->mins, aasarea->maxs); + VectorClear(aasarea->center); + // +// Log_Write("tmparea %d became aasarea %d\r\n", tmparea->areanum, aasarea->areanum); + //store the aas area number at the tmp area + tmparea->aasareanum = aasarea->areanum; + // + for (tmpface = tmparea->tmpfaces; tmpface; tmpface = tmpface->next[side]) + { + side = tmpface->frontarea != tmparea; + //if there's an aas face created for the tmp face already + if (tmpface->aasfacenum) + { + //we're at the back of the face so use a negative index + aasfacenum = -tmpface->aasfacenum; +#ifdef DEBUG + if (tmpface->aasfacenum < 0 || tmpface->aasfacenum > max_aas.max_faces) + { + Error("AAS_CreateTree_r: face number out of range"); + } //end if +#endif //DEBUG + aasface = &aasworld.faces[tmpface->aasfacenum]; + aasface->backarea = aasarea->areanum; + } //end if + else + { + plane = &mapplanes[tmpface->planenum ^ side]; + if (side) + { + w = tmpface->winding; + tmpface->winding = ReverseWinding(tmpface->winding); + } //end if + if (!AAS_GetFace(tmpface->winding, plane, 0, &aasfacenum)) continue; + if (side) + { + FreeWinding(tmpface->winding); + tmpface->winding = w; + } //end if + aasface = &aasworld.faces[aasfacenum]; + aasface->frontarea = aasarea->areanum; + aasface->backarea = 0; + aasface->faceflags = tmpface->faceflags; + //set the face number at the tmp face + tmpface->aasfacenum = aasfacenum; + } //end else + //add face points to the area bounds and + //calculate the face 'center' + VectorClear(facecenter); + for (edgenum = 0; edgenum < aasface->numedges; edgenum++) + { + edge = &aasworld.edges[abs(aasworld.edgeindex[aasface->firstedge + edgenum])]; + for (i = 0; i < 2; i++) + { + AddPointToBounds(aasworld.vertexes[edge->v[i]], aasarea->mins, aasarea->maxs); + VectorAdd(aasworld.vertexes[edge->v[i]], facecenter, facecenter); + } //end for + } //end for + VectorScale(facecenter, 1.0 / (aasface->numedges * 2.0), facecenter); + //add the face 'center' to the area 'center' + VectorAdd(aasarea->center, facecenter, aasarea->center); + // + if (aasworld.faceindexsize >= max_aas.max_faceindexsize) + { + Error("AAS_MAX_FACEINDEXSIZE = %d", max_aas.max_faceindexsize); + } //end if + aasworld.faceindex[aasworld.faceindexsize++] = aasfacenum; + aasarea->numfaces++; + } //end for + //if the area has no faces at all (return 0, = solid leaf) + if (!aasarea->numfaces) return 0; + // + VectorScale(aasarea->center, 1.0 / aasarea->numfaces, aasarea->center); + //Log_Write("area %d center %f %f %f\r\n", aasworld.numareas, + // aasarea->center[0], aasarea->center[1], aasarea->center[2]); + //store the area settings + AAS_StoreAreaSettings(tmparea->settings); + // + //Log_Write("tmp area %d became aas area %d\r\n", tmpareanum, aasarea->areanum); + qprintf("\r%6d", aasarea->areanum); + // + aasworld.numareas++; + return -(aasworld.numareas - 1); +} //end of the function AAS_StoreArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_StoreTree_r(tmp_node_t *tmpnode) +{ + int aasnodenum; + plane_t *plane; + aas_node_t *aasnode; + + //if it is a solid leaf + if (!tmpnode) return 0; + //negative so it's an area + if (tmpnode->tmparea) return AAS_StoreArea(tmpnode->tmparea); + //it's another node + //the first node is a dummy + if (aasworld.numnodes == 0) aasworld.numnodes = 1; + if (aasworld.numnodes >= max_aas.max_nodes) + { + Error("AAS_MAX_NODES = %d", max_aas.max_nodes); + } //end if + aasnodenum = aasworld.numnodes; + aasnode = &aasworld.nodes[aasworld.numnodes++]; + plane = &mapplanes[tmpnode->planenum]; + AAS_GetPlane(plane->normal, plane->dist, &aasnode->planenum); + aasnode->children[0] = AAS_StoreTree_r(tmpnode->children[0]); + aasnode->children[1] = AAS_StoreTree_r(tmpnode->children[1]); + return aasnodenum; +} //end of the function AAS_StoreTree_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_StoreBoundingBoxes(void) +{ + if (cfg.numbboxes > max_aas.max_bboxes) + { + Error("more than %d bounding boxes", max_aas.max_bboxes); + } //end if + aasworld.numbboxes = cfg.numbboxes; + memcpy(aasworld.bboxes, cfg.bboxes, cfg.numbboxes * sizeof(aas_bbox_t)); +} //end of the function AAS_StoreBoundingBoxes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_StoreFile(char *filename) +{ + AAS_AllocMaxAAS(); + + Log_Write("AAS_StoreFile\r\n"); + // + AAS_StoreBoundingBoxes(); + // + qprintf("%6d areas stored", 0); + //start with node 1 because node zero is a dummy + AAS_StoreTree_r(tmpaasworld.nodes); + qprintf("\n"); + Log_Write("%6d areas stored\r\n", aasworld.numareas); + aasworld.loaded = true; +} //end of the function AAS_StoreFile diff --git a/code/bspc/aas_store.h b/code/bspc/aas_store.h index 26957e4..063adbe 100755 --- a/code/bspc/aas_store.h +++ b/code/bspc/aas_store.h @@ -1,107 +1,107 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#define AAS_MAX_BBOXES 5 -#define AAS_MAX_VERTEXES 512000 -#define AAS_MAX_PLANES 65536 -#define AAS_MAX_EDGES 512000 -#define AAS_MAX_EDGEINDEXSIZE 512000 -#define AAS_MAX_FACES 512000 -#define AAS_MAX_FACEINDEXSIZE 512000 -#define AAS_MAX_AREAS 65536 -#define AAS_MAX_AREASETTINGS 65536 -#define AAS_MAX_REACHABILITYSIZE 65536 -#define AAS_MAX_NODES 256000 -#define AAS_MAX_PORTALS 65536 -#define AAS_MAX_PORTALINDEXSIZE 65536 -#define AAS_MAX_CLUSTERS 65536 - -#define BSPCINCLUDE -#include "../game/be_aas.h" -#include "../botlib/be_aas_def.h" - -/* -typedef struct bspc_aas_s -{ - int loaded; - int initialized; //true when AAS has been initialized - int savefile; //set true when file should be saved - //bounding boxes - int numbboxes; - aas_bbox_t *bboxes; - //vertexes - int numvertexes; - aas_vertex_t *vertexes; - //planes - int numplanes; - aas_plane_t *planes; - //edges - int numedges; - aas_edge_t *edges; - //edge index - int edgeindexsize; - aas_edgeindex_t *edgeindex; - //faces - int numfaces; - aas_face_t *faces; - //face index - int faceindexsize; - aas_faceindex_t *faceindex; - //convex areas - int numareas; - aas_area_t *areas; - //convex area settings - int numareasettings; - aas_areasettings_t *areasettings; - //reachablity list - int reachabilitysize; - aas_reachability_t *reachability; - //nodes of the bsp tree - int numnodes; - aas_node_t *nodes; - //cluster portals - int numportals; - aas_portal_t *portals; - //cluster portal index - int portalindexsize; - aas_portalindex_t *portalindex; - //clusters - int numclusters; - aas_cluster_t *clusters; - // - int numreachabilityareas; - float reachabilitytime; -} bspc_aas_t; - -extern bspc_aas_t aasworld; -//*/ - -extern aas_t aasworld; - -//stores the AAS file from the temporary AAS -void AAS_StoreFile(char *filename); -//returns a number of the given plane -qboolean AAS_FindPlane(vec3_t normal, float dist, int *planenum); -//allocates the maximum AAS memory for storage -void AAS_AllocMaxAAS(void); -//frees the maximum AAS memory for storage -void AAS_FreeMaxAAS(void); +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#define AAS_MAX_BBOXES 5 +#define AAS_MAX_VERTEXES 512000 +#define AAS_MAX_PLANES 65536 +#define AAS_MAX_EDGES 512000 +#define AAS_MAX_EDGEINDEXSIZE 512000 +#define AAS_MAX_FACES 512000 +#define AAS_MAX_FACEINDEXSIZE 512000 +#define AAS_MAX_AREAS 65536 +#define AAS_MAX_AREASETTINGS 65536 +#define AAS_MAX_REACHABILITYSIZE 65536 +#define AAS_MAX_NODES 256000 +#define AAS_MAX_PORTALS 65536 +#define AAS_MAX_PORTALINDEXSIZE 65536 +#define AAS_MAX_CLUSTERS 65536 + +#define BSPCINCLUDE +#include "../game/be_aas.h" +#include "../botlib/be_aas_def.h" + +/* +typedef struct bspc_aas_s +{ + int loaded; + int initialized; //true when AAS has been initialized + int savefile; //set true when file should be saved + //bounding boxes + int numbboxes; + aas_bbox_t *bboxes; + //vertexes + int numvertexes; + aas_vertex_t *vertexes; + //planes + int numplanes; + aas_plane_t *planes; + //edges + int numedges; + aas_edge_t *edges; + //edge index + int edgeindexsize; + aas_edgeindex_t *edgeindex; + //faces + int numfaces; + aas_face_t *faces; + //face index + int faceindexsize; + aas_faceindex_t *faceindex; + //convex areas + int numareas; + aas_area_t *areas; + //convex area settings + int numareasettings; + aas_areasettings_t *areasettings; + //reachablity list + int reachabilitysize; + aas_reachability_t *reachability; + //nodes of the bsp tree + int numnodes; + aas_node_t *nodes; + //cluster portals + int numportals; + aas_portal_t *portals; + //cluster portal index + int portalindexsize; + aas_portalindex_t *portalindex; + //clusters + int numclusters; + aas_cluster_t *clusters; + // + int numreachabilityareas; + float reachabilitytime; +} bspc_aas_t; + +extern bspc_aas_t aasworld; +//*/ + +extern aas_t aasworld; + +//stores the AAS file from the temporary AAS +void AAS_StoreFile(char *filename); +//returns a number of the given plane +qboolean AAS_FindPlane(vec3_t normal, float dist, int *planenum); +//allocates the maximum AAS memory for storage +void AAS_AllocMaxAAS(void); +//frees the maximum AAS memory for storage +void AAS_FreeMaxAAS(void); diff --git a/code/bspc/aasfile.h b/code/bspc/aasfile.h index 7ed8677..fc3dc77 100755 --- a/code/bspc/aasfile.h +++ b/code/bspc/aasfile.h @@ -1,252 +1,252 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - - -//NOTE: int = default signed -// default long - -#define AASID (('S'<<24)+('A'<<16)+('A'<<8)+'E') -#define AASVERSION_OLD 4 -#define AASVERSION 5 - -//presence types -#define PRESENCE_NONE 1 -#define PRESENCE_NORMAL 2 -#define PRESENCE_CROUCH 4 - -//travel types -#define MAX_TRAVELTYPES 32 -#define TRAVEL_INVALID 1 //temporary not possible -#define TRAVEL_WALK 2 //walking -#define TRAVEL_CROUCH 3 //crouching -#define TRAVEL_BARRIERJUMP 4 //jumping onto a barrier -#define TRAVEL_JUMP 5 //jumping -#define TRAVEL_LADDER 6 //climbing a ladder -#define TRAVEL_WALKOFFLEDGE 7 //walking of a ledge -#define TRAVEL_SWIM 8 //swimming -#define TRAVEL_WATERJUMP 9 //jump out of the water -#define TRAVEL_TELEPORT 10 //teleportation -#define TRAVEL_ELEVATOR 11 //travel by elevator -#define TRAVEL_ROCKETJUMP 12 //rocket jumping required for travel -#define TRAVEL_BFGJUMP 13 //bfg jumping required for travel -#define TRAVEL_GRAPPLEHOOK 14 //grappling hook required for travel -#define TRAVEL_DOUBLEJUMP 15 //double jump -#define TRAVEL_RAMPJUMP 16 //ramp jump -#define TRAVEL_STRAFEJUMP 17 //strafe jump -#define TRAVEL_JUMPPAD 18 //jump pad -#define TRAVEL_FUNCBOB 19 //func bob - -//face flags -#define FACE_SOLID 1 //just solid at the other side -#define FACE_LADDER 2 //ladder -#define FACE_GROUND 4 //standing on ground when in this face -#define FACE_GAP 8 //gap in the ground -#define FACE_LIQUID 16 -#define FACE_LIQUIDSURFACE 32 - -//area contents -#define AREACONTENTS_WATER 1 -#define AREACONTENTS_LAVA 2 -#define AREACONTENTS_SLIME 4 -#define AREACONTENTS_CLUSTERPORTAL 8 -#define AREACONTENTS_TELEPORTAL 16 -#define AREACONTENTS_ROUTEPORTAL 32 -#define AREACONTENTS_TELEPORTER 64 -#define AREACONTENTS_JUMPPAD 128 -#define AREACONTENTS_DONOTENTER 256 -#define AREACONTENTS_VIEWPORTAL 512 - -//area flags -#define AREA_GROUNDED 1 //bot can stand on the ground -#define AREA_LADDER 2 //area contains one or more ladder faces -#define AREA_LIQUID 4 //area contains a liquid - -//aas file header lumps -#define AAS_LUMPS 14 -#define AASLUMP_BBOXES 0 -#define AASLUMP_VERTEXES 1 -#define AASLUMP_PLANES 2 -#define AASLUMP_EDGES 3 -#define AASLUMP_EDGEINDEX 4 -#define AASLUMP_FACES 5 -#define AASLUMP_FACEINDEX 6 -#define AASLUMP_AREAS 7 -#define AASLUMP_AREASETTINGS 8 -#define AASLUMP_REACHABILITY 9 -#define AASLUMP_NODES 10 -#define AASLUMP_PORTALS 11 -#define AASLUMP_PORTALINDEX 12 -#define AASLUMP_CLUSTERS 13 - -//========== bounding box ========= - -//bounding box -typedef struct aas_bbox_s -{ - int presencetype; - int flags; - vec3_t mins, maxs; -} aas_bbox_t; - -//============ settings =========== - -//reachability to another area -typedef struct aas_reachability_s -{ - int areanum; //number of the reachable area - int facenum; //number of the face towards the other area - int edgenum; //number of the edge towards the other area - vec3_t start; //start point of inter area movement - vec3_t end; //end point of inter area movement - int traveltype; //type of travel required to get to the area - unsigned short int traveltime;//travel time of the inter area movement -} aas_reachability_t; - -//area settings -typedef struct aas_areasettings_s -{ - //could also add all kind of statistic fields - int contents; //contents of the convex area - int areaflags; //several area flags - int presencetype; //how a bot can be present in this convex area - int cluster; //cluster the area belongs to, if negative it's a portal - int clusterareanum; //number of the area in the cluster - int numreachableareas; //number of reachable areas from this one - int firstreachablearea; //first reachable area in the reachable area index -} aas_areasettings_t; - -//cluster portal -typedef struct aas_portal_s -{ - int areanum; //area that is the actual portal - int frontcluster; //cluster at front of portal - int backcluster; //cluster at back of portal - int clusterareanum[2]; //number of the area in the front and back cluster -} aas_portal_t; - -//cluster portal index -typedef int aas_portalindex_t; - -//cluster -typedef struct aas_cluster_s -{ - int numareas; //number of areas in the cluster - int numreachabilityareas; //number of areas with reachabilities - int numportals; //number of cluster portals - int firstportal; //first cluster portal in the index -} aas_cluster_t; - -//============ 3d definition ============ - -typedef vec3_t aas_vertex_t; - -//just a plane in the third dimension -typedef struct aas_plane_s -{ - vec3_t normal; //normal vector of the plane - float dist; //distance of the plane (normal vector * distance = point in plane) - int type; -} aas_plane_t; - -//edge -typedef struct aas_edge_s -{ - int v[2]; //numbers of the vertexes of this edge -} aas_edge_t; - -//edge index, negative if vertexes are reversed -typedef int aas_edgeindex_t; - -//a face bounds a convex area, often it will also seperate two convex areas -typedef struct aas_face_s -{ - int planenum; //number of the plane this face is in - int faceflags; //face flags (no use to create face settings for just this field) - int numedges; //number of edges in the boundary of the face - int firstedge; //first edge in the edge index - int frontarea; //convex area at the front of this face - int backarea; //convex area at the back of this face -} aas_face_t; - -//face index, stores a negative index if backside of face -typedef int aas_faceindex_t; - -//convex area with a boundary of faces -typedef struct aas_area_s -{ - int areanum; //number of this area - //3d definition - int numfaces; //number of faces used for the boundary of the convex area - int firstface; //first face in the face index used for the boundary of the convex area - vec3_t mins; //mins of the convex area - vec3_t maxs; //maxs of the convex area - vec3_t center; //'center' of the convex area -} aas_area_t; - -//nodes of the bsp tree -typedef struct aas_node_s -{ - int planenum; - int children[2]; //child nodes of this node, or convex areas as leaves when negative - //when a child is zero it's a solid leaf -} aas_node_t; - -//=========== aas file =============== - -//header lump -typedef struct -{ - int fileofs; - int filelen; -} aas_lump_t; - -//aas file header -typedef struct aas_header_s -{ - int ident; - int version; - int bspchecksum; - //data entries - aas_lump_t lumps[AAS_LUMPS]; -} aas_header_t; - - -//====== additional information ====== -/* - -- when a node child is a solid leaf the node child number is zero -- two adjacent areas (sharing a plane at opposite sides) share a face - this face is a portal between the areas -- when an area uses a face from the faceindex with a positive index - then the face plane normal points into the area -- the face edges are stored counter clockwise using the edgeindex -- two adjacent convex areas (sharing a face) only share One face - this is a simple result of the areas being convex -- the convex areas can't have a mixture of ground and gap faces - other mixtures of faces in one area are allowed -- areas with the AREACONTENTS_CLUSTERPORTAL in the settings have - cluster number zero -- edge zero is a dummy -- face zero is a dummy -- area zero is a dummy -- node zero is a dummy -*/ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + + +//NOTE: int = default signed +// default long + +#define AASID (('S'<<24)+('A'<<16)+('A'<<8)+'E') +#define AASVERSION_OLD 4 +#define AASVERSION 5 + +//presence types +#define PRESENCE_NONE 1 +#define PRESENCE_NORMAL 2 +#define PRESENCE_CROUCH 4 + +//travel types +#define MAX_TRAVELTYPES 32 +#define TRAVEL_INVALID 1 //temporary not possible +#define TRAVEL_WALK 2 //walking +#define TRAVEL_CROUCH 3 //crouching +#define TRAVEL_BARRIERJUMP 4 //jumping onto a barrier +#define TRAVEL_JUMP 5 //jumping +#define TRAVEL_LADDER 6 //climbing a ladder +#define TRAVEL_WALKOFFLEDGE 7 //walking of a ledge +#define TRAVEL_SWIM 8 //swimming +#define TRAVEL_WATERJUMP 9 //jump out of the water +#define TRAVEL_TELEPORT 10 //teleportation +#define TRAVEL_ELEVATOR 11 //travel by elevator +#define TRAVEL_ROCKETJUMP 12 //rocket jumping required for travel +#define TRAVEL_BFGJUMP 13 //bfg jumping required for travel +#define TRAVEL_GRAPPLEHOOK 14 //grappling hook required for travel +#define TRAVEL_DOUBLEJUMP 15 //double jump +#define TRAVEL_RAMPJUMP 16 //ramp jump +#define TRAVEL_STRAFEJUMP 17 //strafe jump +#define TRAVEL_JUMPPAD 18 //jump pad +#define TRAVEL_FUNCBOB 19 //func bob + +//face flags +#define FACE_SOLID 1 //just solid at the other side +#define FACE_LADDER 2 //ladder +#define FACE_GROUND 4 //standing on ground when in this face +#define FACE_GAP 8 //gap in the ground +#define FACE_LIQUID 16 +#define FACE_LIQUIDSURFACE 32 + +//area contents +#define AREACONTENTS_WATER 1 +#define AREACONTENTS_LAVA 2 +#define AREACONTENTS_SLIME 4 +#define AREACONTENTS_CLUSTERPORTAL 8 +#define AREACONTENTS_TELEPORTAL 16 +#define AREACONTENTS_ROUTEPORTAL 32 +#define AREACONTENTS_TELEPORTER 64 +#define AREACONTENTS_JUMPPAD 128 +#define AREACONTENTS_DONOTENTER 256 +#define AREACONTENTS_VIEWPORTAL 512 + +//area flags +#define AREA_GROUNDED 1 //bot can stand on the ground +#define AREA_LADDER 2 //area contains one or more ladder faces +#define AREA_LIQUID 4 //area contains a liquid + +//aas file header lumps +#define AAS_LUMPS 14 +#define AASLUMP_BBOXES 0 +#define AASLUMP_VERTEXES 1 +#define AASLUMP_PLANES 2 +#define AASLUMP_EDGES 3 +#define AASLUMP_EDGEINDEX 4 +#define AASLUMP_FACES 5 +#define AASLUMP_FACEINDEX 6 +#define AASLUMP_AREAS 7 +#define AASLUMP_AREASETTINGS 8 +#define AASLUMP_REACHABILITY 9 +#define AASLUMP_NODES 10 +#define AASLUMP_PORTALS 11 +#define AASLUMP_PORTALINDEX 12 +#define AASLUMP_CLUSTERS 13 + +//========== bounding box ========= + +//bounding box +typedef struct aas_bbox_s +{ + int presencetype; + int flags; + vec3_t mins, maxs; +} aas_bbox_t; + +//============ settings =========== + +//reachability to another area +typedef struct aas_reachability_s +{ + int areanum; //number of the reachable area + int facenum; //number of the face towards the other area + int edgenum; //number of the edge towards the other area + vec3_t start; //start point of inter area movement + vec3_t end; //end point of inter area movement + int traveltype; //type of travel required to get to the area + unsigned short int traveltime;//travel time of the inter area movement +} aas_reachability_t; + +//area settings +typedef struct aas_areasettings_s +{ + //could also add all kind of statistic fields + int contents; //contents of the convex area + int areaflags; //several area flags + int presencetype; //how a bot can be present in this convex area + int cluster; //cluster the area belongs to, if negative it's a portal + int clusterareanum; //number of the area in the cluster + int numreachableareas; //number of reachable areas from this one + int firstreachablearea; //first reachable area in the reachable area index +} aas_areasettings_t; + +//cluster portal +typedef struct aas_portal_s +{ + int areanum; //area that is the actual portal + int frontcluster; //cluster at front of portal + int backcluster; //cluster at back of portal + int clusterareanum[2]; //number of the area in the front and back cluster +} aas_portal_t; + +//cluster portal index +typedef int aas_portalindex_t; + +//cluster +typedef struct aas_cluster_s +{ + int numareas; //number of areas in the cluster + int numreachabilityareas; //number of areas with reachabilities + int numportals; //number of cluster portals + int firstportal; //first cluster portal in the index +} aas_cluster_t; + +//============ 3d definition ============ + +typedef vec3_t aas_vertex_t; + +//just a plane in the third dimension +typedef struct aas_plane_s +{ + vec3_t normal; //normal vector of the plane + float dist; //distance of the plane (normal vector * distance = point in plane) + int type; +} aas_plane_t; + +//edge +typedef struct aas_edge_s +{ + int v[2]; //numbers of the vertexes of this edge +} aas_edge_t; + +//edge index, negative if vertexes are reversed +typedef int aas_edgeindex_t; + +//a face bounds a convex area, often it will also seperate two convex areas +typedef struct aas_face_s +{ + int planenum; //number of the plane this face is in + int faceflags; //face flags (no use to create face settings for just this field) + int numedges; //number of edges in the boundary of the face + int firstedge; //first edge in the edge index + int frontarea; //convex area at the front of this face + int backarea; //convex area at the back of this face +} aas_face_t; + +//face index, stores a negative index if backside of face +typedef int aas_faceindex_t; + +//convex area with a boundary of faces +typedef struct aas_area_s +{ + int areanum; //number of this area + //3d definition + int numfaces; //number of faces used for the boundary of the convex area + int firstface; //first face in the face index used for the boundary of the convex area + vec3_t mins; //mins of the convex area + vec3_t maxs; //maxs of the convex area + vec3_t center; //'center' of the convex area +} aas_area_t; + +//nodes of the bsp tree +typedef struct aas_node_s +{ + int planenum; + int children[2]; //child nodes of this node, or convex areas as leaves when negative + //when a child is zero it's a solid leaf +} aas_node_t; + +//=========== aas file =============== + +//header lump +typedef struct +{ + int fileofs; + int filelen; +} aas_lump_t; + +//aas file header +typedef struct aas_header_s +{ + int ident; + int version; + int bspchecksum; + //data entries + aas_lump_t lumps[AAS_LUMPS]; +} aas_header_t; + + +//====== additional information ====== +/* + +- when a node child is a solid leaf the node child number is zero +- two adjacent areas (sharing a plane at opposite sides) share a face + this face is a portal between the areas +- when an area uses a face from the faceindex with a positive index + then the face plane normal points into the area +- the face edges are stored counter clockwise using the edgeindex +- two adjacent convex areas (sharing a face) only share One face + this is a simple result of the areas being convex +- the convex areas can't have a mixture of ground and gap faces + other mixtures of faces in one area are allowed +- areas with the AREACONTENTS_CLUSTERPORTAL in the settings have + cluster number zero +- edge zero is a dummy +- face zero is a dummy +- area zero is a dummy +- node zero is a dummy +*/ diff --git a/code/bspc/be_aas_bspc.c b/code/bspc/be_aas_bspc.c index 127549c..9473287 100755 --- a/code/bspc/be_aas_bspc.c +++ b/code/bspc/be_aas_bspc.c @@ -1,292 +1,292 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "../game/q_shared.h" -#include "../bspc/l_log.h" -#include "../bspc/l_qfiles.h" -#include "../botlib/l_memory.h" -#include "../botlib/l_script.h" -#include "../botlib/l_precomp.h" -#include "../botlib/l_struct.h" -#include "../botlib/aasfile.h" -#include "../game/botlib.h" -#include "../game/be_aas.h" -#include "../botlib/be_aas_def.h" -#include "../qcommon/cm_public.h" - -//#define BSPC - -extern botlib_import_t botimport; -extern qboolean capsule_collision; - -botlib_import_t botimport; -clipHandle_t worldmodel; - -void Error (char *error, ...); - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_Error(char *fmt, ...) -{ - va_list argptr; - char text[1024]; - - va_start(argptr, fmt); - vsprintf(text, fmt, argptr); - va_end(argptr); - - Error(text); -} //end of the function AAS_Error -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int Sys_MilliSeconds(void) -{ - return clock() * 1000 / CLOCKS_PER_SEC; -} //end of the function Sys_MilliSeconds -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_DebugLine(vec3_t start, vec3_t end, int color) -{ -} //end of the function AAS_DebugLine -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_ClearShownDebugLines(void) -{ -} //end of the function AAS_ClearShownDebugLines -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -char *BotImport_BSPEntityData(void) -{ - return CM_EntityString(); -} //end of the function AAS_GetEntityData -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -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 result; - - CM_BoxTrace(&result, start, end, mins, maxs, worldmodel, contentmask, capsule_collision); - - bsptrace->allsolid = result.allsolid; - bsptrace->contents = result.contents; - VectorCopy(result.endpos, bsptrace->endpos); - bsptrace->ent = result.entityNum; - bsptrace->fraction = result.fraction; - bsptrace->exp_dist = 0; - bsptrace->plane.dist = result.plane.dist; - VectorCopy(result.plane.normal, bsptrace->plane.normal); - bsptrace->plane.signbits = result.plane.signbits; - bsptrace->plane.type = result.plane.type; - bsptrace->sidenum = 0; - bsptrace->startsolid = result.startsolid; - bsptrace->surface.flags = result.surfaceFlags; -} //end of the function BotImport_Trace -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BotImport_PointContents(vec3_t p) -{ - return CM_PointContents(p, worldmodel); -} //end of the function BotImport_PointContents -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void *BotImport_GetMemory(int size) -{ - return GetMemory(size); -} //end of the function BotImport_GetMemory -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BotImport_Print(int type, char *fmt, ...) -{ - va_list argptr; - char buf[1024]; - - va_start(argptr, fmt); - vsprintf(buf, fmt, argptr); - printf(buf); - if (buf[0] != '\r') Log_Write(buf); - va_end(argptr); -} //end of the function BotImport_Print -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -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] = (mins[i] + maxs[i]) * 0.5 - max; - maxs[i] = (mins[i] + maxs[i]) * 0.5 + max; - } //end for - } //end if - if (outmins) VectorCopy(mins, outmins); - if (outmaxs) VectorCopy(maxs, outmaxs); - if (origin) VectorClear(origin); -} //end of the function BotImport_BSPModelMinsMaxsOrigin -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Com_DPrintf(char *fmt, ...) -{ - va_list argptr; - char buf[1024]; - - va_start(argptr, fmt); - vsprintf(buf, fmt, argptr); - printf(buf); - if (buf[0] != '\r') Log_Write(buf); - va_end(argptr); -} //end of the function Com_DPrintf -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int COM_Compress( char *data_p ) { - return strlen(data_p); -} -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Com_Memset (void* dest, const int val, const size_t count) { - memset(dest, val, count); -} -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Com_Memcpy (void* dest, const void* src, const size_t count) { - memcpy(dest, src, count); -} -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_InitBotImport(void) -{ - botimport.BSPEntityData = BotImport_BSPEntityData; - botimport.GetMemory = BotImport_GetMemory; - botimport.FreeMemory = FreeMemory; - botimport.Trace = BotImport_Trace; - botimport.PointContents = BotImport_PointContents; - botimport.Print = BotImport_Print; - botimport.BSPModelMinsMaxsOrigin = BotImport_BSPModelMinsMaxsOrigin; -} //end of the function AAS_InitBotImport -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_CalcReachAndClusters(struct quakefile_s *qf) -{ - float time; - - Log_Print("loading collision map...\n"); - // - if (!qf->pakfile[0]) strcpy(qf->pakfile, qf->filename); - //load the map - CM_LoadMap((char *) qf, qfalse, &aasworld.bspchecksum); - //get a handle to the world model - worldmodel = CM_InlineModel(0); // 0 = world, 1 + are bmodels - //initialize bot import structure - AAS_InitBotImport(); - //load the BSP entity string - AAS_LoadBSPFile(); - //init physics settings - AAS_InitSettings(); - //initialize AAS link heap - AAS_InitAASLinkHeap(); - //initialize the AAS linked entities for the new map - AAS_InitAASLinkedEntities(); - //reset all reachabilities and clusters - aasworld.reachabilitysize = 0; - aasworld.numclusters = 0; - //set all view portals as cluster portals in case we re-calculate the reachabilities and clusters (with -reach) - AAS_SetViewPortalsAsClusterPortals(); - //calculate reachabilities - AAS_InitReachability(); - time = 0; - while(AAS_ContinueInitReachability(time)) time++; - //calculate clusters - AAS_InitClustering(); -} //end of the function AAS_CalcReachAndClusters +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "../game/q_shared.h" +#include "../bspc/l_log.h" +#include "../bspc/l_qfiles.h" +#include "../botlib/l_memory.h" +#include "../botlib/l_script.h" +#include "../botlib/l_precomp.h" +#include "../botlib/l_struct.h" +#include "../botlib/aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "../botlib/be_aas_def.h" +#include "../qcommon/cm_public.h" + +//#define BSPC + +extern botlib_import_t botimport; +extern qboolean capsule_collision; + +botlib_import_t botimport; +clipHandle_t worldmodel; + +void Error (char *error, ...); + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Error(char *fmt, ...) +{ + va_list argptr; + char text[1024]; + + va_start(argptr, fmt); + vsprintf(text, fmt, argptr); + va_end(argptr); + + Error(text); +} //end of the function AAS_Error +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Sys_MilliSeconds(void) +{ + return clock() * 1000 / CLOCKS_PER_SEC; +} //end of the function Sys_MilliSeconds +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DebugLine(vec3_t start, vec3_t end, int color) +{ +} //end of the function AAS_DebugLine +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ClearShownDebugLines(void) +{ +} //end of the function AAS_ClearShownDebugLines +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *BotImport_BSPEntityData(void) +{ + return CM_EntityString(); +} //end of the function AAS_GetEntityData +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +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 result; + + CM_BoxTrace(&result, start, end, mins, maxs, worldmodel, contentmask, capsule_collision); + + bsptrace->allsolid = result.allsolid; + bsptrace->contents = result.contents; + VectorCopy(result.endpos, bsptrace->endpos); + bsptrace->ent = result.entityNum; + bsptrace->fraction = result.fraction; + bsptrace->exp_dist = 0; + bsptrace->plane.dist = result.plane.dist; + VectorCopy(result.plane.normal, bsptrace->plane.normal); + bsptrace->plane.signbits = result.plane.signbits; + bsptrace->plane.type = result.plane.type; + bsptrace->sidenum = 0; + bsptrace->startsolid = result.startsolid; + bsptrace->surface.flags = result.surfaceFlags; +} //end of the function BotImport_Trace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotImport_PointContents(vec3_t p) +{ + return CM_PointContents(p, worldmodel); +} //end of the function BotImport_PointContents +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void *BotImport_GetMemory(int size) +{ + return GetMemory(size); +} //end of the function BotImport_GetMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotImport_Print(int type, char *fmt, ...) +{ + va_list argptr; + char buf[1024]; + + va_start(argptr, fmt); + vsprintf(buf, fmt, argptr); + printf(buf); + if (buf[0] != '\r') Log_Write(buf); + va_end(argptr); +} //end of the function BotImport_Print +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +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] = (mins[i] + maxs[i]) * 0.5 - max; + maxs[i] = (mins[i] + maxs[i]) * 0.5 + max; + } //end for + } //end if + if (outmins) VectorCopy(mins, outmins); + if (outmaxs) VectorCopy(maxs, outmaxs); + if (origin) VectorClear(origin); +} //end of the function BotImport_BSPModelMinsMaxsOrigin +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Com_DPrintf(char *fmt, ...) +{ + va_list argptr; + char buf[1024]; + + va_start(argptr, fmt); + vsprintf(buf, fmt, argptr); + printf(buf); + if (buf[0] != '\r') Log_Write(buf); + va_end(argptr); +} //end of the function Com_DPrintf +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int COM_Compress( char *data_p ) { + return strlen(data_p); +} +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Com_Memset (void* dest, const int val, const size_t count) { + memset(dest, val, count); +} +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Com_Memcpy (void* dest, const void* src, const size_t count) { + memcpy(dest, src, count); +} +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitBotImport(void) +{ + botimport.BSPEntityData = BotImport_BSPEntityData; + botimport.GetMemory = BotImport_GetMemory; + botimport.FreeMemory = FreeMemory; + botimport.Trace = BotImport_Trace; + botimport.PointContents = BotImport_PointContents; + botimport.Print = BotImport_Print; + botimport.BSPModelMinsMaxsOrigin = BotImport_BSPModelMinsMaxsOrigin; +} //end of the function AAS_InitBotImport +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CalcReachAndClusters(struct quakefile_s *qf) +{ + float time; + + Log_Print("loading collision map...\n"); + // + if (!qf->pakfile[0]) strcpy(qf->pakfile, qf->filename); + //load the map + CM_LoadMap((char *) qf, qfalse, &aasworld.bspchecksum); + //get a handle to the world model + worldmodel = CM_InlineModel(0); // 0 = world, 1 + are bmodels + //initialize bot import structure + AAS_InitBotImport(); + //load the BSP entity string + AAS_LoadBSPFile(); + //init physics settings + AAS_InitSettings(); + //initialize AAS link heap + AAS_InitAASLinkHeap(); + //initialize the AAS linked entities for the new map + AAS_InitAASLinkedEntities(); + //reset all reachabilities and clusters + aasworld.reachabilitysize = 0; + aasworld.numclusters = 0; + //set all view portals as cluster portals in case we re-calculate the reachabilities and clusters (with -reach) + AAS_SetViewPortalsAsClusterPortals(); + //calculate reachabilities + AAS_InitReachability(); + time = 0; + while(AAS_ContinueInitReachability(time)) time++; + //calculate clusters + AAS_InitClustering(); +} //end of the function AAS_CalcReachAndClusters diff --git a/code/bspc/be_aas_bspc.h b/code/bspc/be_aas_bspc.h index 72e488a..97e2921 100755 --- a/code/bspc/be_aas_bspc.h +++ b/code/bspc/be_aas_bspc.h @@ -1,23 +1,23 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -void AAS_CalcReachAndClusters(struct quakefile_s *qf); +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +void AAS_CalcReachAndClusters(struct quakefile_s *qf); diff --git a/code/bspc/brushbsp.c b/code/bspc/brushbsp.c index 22cb0dd..d8accec 100755 --- a/code/bspc/brushbsp.c +++ b/code/bspc/brushbsp.c @@ -1,1871 +1,1871 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "qbsp.h" -#include "l_mem.h" -#include "../botlib/aasfile.h" -#include "aas_store.h" -#include "aas_cfg.h" - -#include - -/* -each side has a count of the other sides it splits - -the best split will be the one that minimizes the total split counts -of all remaining sides - -precalc side on plane table - -evaluate split side -{ -cost = 0 -for all sides - for all sides - get - if side splits side and splitside is on same child - cost++; -} -*/ - -int c_nodes; -int c_nonvis; -int c_active_brushes; -int c_solidleafnodes; -int c_totalsides; -int c_brushmemory; -int c_peak_brushmemory; -int c_nodememory; -int c_peak_totalbspmemory; - -// if a brush just barely pokes onto the other side, -// let it slide by without chopping -#define PLANESIDE_EPSILON 0.001 -//0.1 - -//#ifdef DEBUG -typedef struct cname_s -{ - int value; - char *name; -} cname_t; - -cname_t contentnames[] = -{ - {CONTENTS_SOLID,"CONTENTS_SOLID"}, - {CONTENTS_WINDOW,"CONTENTS_WINDOW"}, - {CONTENTS_AUX,"CONTENTS_AUX"}, - {CONTENTS_LAVA,"CONTENTS_LAVA"}, - {CONTENTS_SLIME,"CONTENTS_SLIME"}, - {CONTENTS_WATER,"CONTENTS_WATER"}, - {CONTENTS_MIST,"CONTENTS_MIST"}, - {LAST_VISIBLE_CONTENTS,"LAST_VISIBLE_CONTENTS"}, - - {CONTENTS_AREAPORTAL,"CONTENTS_AREAPORTAL"}, - {CONTENTS_PLAYERCLIP,"CONTENTS_PLAYERCLIP"}, - {CONTENTS_MONSTERCLIP,"CONTENTS_MONSTERCLIP"}, - {CONTENTS_CURRENT_0,"CONTENTS_CURRENT_0"}, - {CONTENTS_CURRENT_90,"CONTENTS_CURRENT_90"}, - {CONTENTS_CURRENT_180,"CONTENTS_CURRENT_180"}, - {CONTENTS_CURRENT_270,"CONTENTS_CURRENT_270"}, - {CONTENTS_CURRENT_UP,"CONTENTS_CURRENT_UP"}, - {CONTENTS_CURRENT_DOWN,"CONTENTS_CURRENT_DOWN"}, - {CONTENTS_ORIGIN,"CONTENTS_ORIGIN"}, - {CONTENTS_MONSTER,"CONTENTS_MONSTER"}, - {CONTENTS_DEADMONSTER,"CONTENTS_DEADMONSTER"}, - {CONTENTS_DETAIL,"CONTENTS_DETAIL"}, - {CONTENTS_Q2TRANSLUCENT,"CONTENTS_TRANSLUCENT"}, - {CONTENTS_LADDER,"CONTENTS_LADDER"}, - {0, 0} -}; - -void PrintContents(int contents) -{ - int i; - - for (i = 0; contentnames[i].value; i++) - { - if (contents & contentnames[i].value) - { - Log_Write("%s,", contentnames[i].name); - } //end if - } //end for -} //end of the function PrintContents - -//#endif DEBUG - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ResetBrushBSP(void) -{ - c_nodes = 0; - c_nonvis = 0; - c_active_brushes = 0; - c_solidleafnodes = 0; - c_totalsides = 0; - c_brushmemory = 0; - c_peak_brushmemory = 0; - c_nodememory = 0; - c_peak_totalbspmemory = 0; -} //end of the function ResetBrushBSP -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void FindBrushInTree (node_t *node, int brushnum) -{ - bspbrush_t *b; - - if (node->planenum == PLANENUM_LEAF) - { - for (b=node->brushlist ; b ; b=b->next) - if (b->original->brushnum == brushnum) - Log_Print ("here\n"); - return; - } - FindBrushInTree(node->children[0], brushnum); - FindBrushInTree(node->children[1], brushnum); -} //end of the function FindBrushInTree -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void DrawBrushList (bspbrush_t *brush, node_t *node) -{ - int i; - side_t *s; - - GLS_BeginScene (); - for ( ; brush ; brush=brush->next) - { - for (i=0 ; inumsides ; i++) - { - s = &brush->sides[i]; - if (!s->winding) - continue; - if (s->texinfo == TEXINFO_NODE) - GLS_Winding (s->winding, 1); - else if (!(s->flags & SFL_VISIBLE)) - GLS_Winding (s->winding, 2); - else - GLS_Winding (s->winding, 0); - } - } - GLS_EndScene (); -} //end of the function DrawBrushList -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void WriteBrushList (char *name, bspbrush_t *brush, qboolean onlyvis) -{ - int i; - side_t *s; - FILE *f; - - qprintf ("writing %s\n", name); - f = SafeOpenWrite (name); - - for ( ; brush ; brush=brush->next) - { - for (i=0 ; inumsides ; i++) - { - s = &brush->sides[i]; - if (!s->winding) - continue; - if (onlyvis && !(s->flags & SFL_VISIBLE)) - continue; - OutputWinding (brush->sides[i].winding, f); - } - } - - fclose (f); -} //end of the function WriteBrushList -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void PrintBrush (bspbrush_t *brush) -{ - int i; - - printf ("brush: %p\n", brush); - for (i=0;inumsides ; i++) - { - pw(brush->sides[i].winding); - printf ("\n"); - } //end for -} //end of the function PrintBrush -//=========================================================================== -// Sets the mins/maxs based on the windings -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BoundBrush (bspbrush_t *brush) -{ - int i, j; - winding_t *w; - - ClearBounds (brush->mins, brush->maxs); - for (i=0 ; inumsides ; i++) - { - w = brush->sides[i].winding; - if (!w) - continue; - for (j=0 ; jnumpoints ; j++) - AddPointToBounds (w->p[j], brush->mins, brush->maxs); - } -} //end of the function BoundBrush -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void CreateBrushWindings (bspbrush_t *brush) -{ - int i, j; - winding_t *w; - side_t *side; - plane_t *plane; - - for (i=0 ; inumsides ; i++) - { - side = &brush->sides[i]; - plane = &mapplanes[side->planenum]; - w = BaseWindingForPlane (plane->normal, plane->dist); - for (j=0 ; jnumsides && w; j++) - { - if (i == j) - continue; - if (brush->sides[j].flags & SFL_BEVEL) - continue; - plane = &mapplanes[brush->sides[j].planenum^1]; - ChopWindingInPlace (&w, plane->normal, plane->dist, 0); //CLIP_EPSILON); - } - - side->winding = w; - } - - BoundBrush (brush); -} //end of the function CreateBrushWindings -//=========================================================================== -// Creates a new axial brush -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bspbrush_t *BrushFromBounds (vec3_t mins, vec3_t maxs) -{ - bspbrush_t *b; - int i; - vec3_t normal; - vec_t dist; - - b = AllocBrush (6); - b->numsides = 6; - for (i=0 ; i<3 ; i++) - { - VectorClear (normal); - normal[i] = 1; - dist = maxs[i]; - b->sides[i].planenum = FindFloatPlane (normal, dist); - - normal[i] = -1; - dist = -mins[i]; - b->sides[3+i].planenum = FindFloatPlane (normal, dist); - } - - CreateBrushWindings (b); - - return b; -} //end of the function BrushFromBounds -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BrushOutOfBounds(bspbrush_t *brush, vec3_t mins, vec3_t maxs, float epsilon) -{ - int i, j, n; - winding_t *w; - side_t *side; - - for (i = 0; i < brush->numsides; i++) - { - side = &brush->sides[i]; - w = side->winding; - for (j = 0; j < w->numpoints; j++) - { - for (n = 0; n < 3; n++) - { - if (w->p[j][n] < (mins[n] + epsilon) || w->p[j][n] > (maxs[n] - epsilon)) return true; - } //end for - } //end for - } //end for - return false; -} //end of the function BrushOutOfBounds -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -vec_t BrushVolume (bspbrush_t *brush) -{ - int i; - winding_t *w; - vec3_t corner; - vec_t d, area, volume; - plane_t *plane; - - if (!brush) return 0; - - // grab the first valid point as the corner - w = NULL; - for (i = 0; i < brush->numsides; i++) - { - w = brush->sides[i].winding; - if (w) break; - } //end for - if (!w) return 0; - VectorCopy (w->p[0], corner); - - // make tetrahedrons to all other faces - volume = 0; - for ( ; i < brush->numsides; i++) - { - w = brush->sides[i].winding; - if (!w) continue; - plane = &mapplanes[brush->sides[i].planenum]; - d = -(DotProduct (corner, plane->normal) - plane->dist); - area = WindingArea(w); - volume += d * area; - } //end for - - volume /= 3; - return volume; -} //end of the function BrushVolume -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int CountBrushList (bspbrush_t *brushes) -{ - int c; - - c = 0; - for ( ; brushes; brushes = brushes->next) c++; - return c; -} //end of the function CountBrushList -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -node_t *AllocNode (void) -{ - node_t *node; - - node = GetMemory(sizeof(*node)); - memset (node, 0, sizeof(*node)); - if (numthreads == 1) - { - c_nodememory += MemorySize(node); - } //end if - return node; -} //end of the function AllocNode -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bspbrush_t *AllocBrush (int numsides) -{ - bspbrush_t *bb; - int c; - - c = (int)&(((bspbrush_t *)0)->sides[numsides]); - bb = GetMemory(c); - memset (bb, 0, c); - if (numthreads == 1) - { - c_active_brushes++; - c_brushmemory += MemorySize(bb); - if (c_brushmemory > c_peak_brushmemory) - c_peak_brushmemory = c_brushmemory; - } //end if - return bb; -} //end of the function AllocBrush -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void FreeBrush (bspbrush_t *brushes) -{ - int i; - - for (i=0 ; inumsides ; i++) - if (brushes->sides[i].winding) - FreeWinding(brushes->sides[i].winding); - if (numthreads == 1) - { - c_active_brushes--; - c_brushmemory -= MemorySize(brushes); - if (c_brushmemory < 0) c_brushmemory = 0; - } //end if - FreeMemory(brushes); -} //end of the function FreeBrush -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void FreeBrushList (bspbrush_t *brushes) -{ - bspbrush_t *next; - - for ( ; brushes; brushes = next) - { - next = brushes->next; - - FreeBrush(brushes); - } //end for -} //end of the function FreeBrushList -//=========================================================================== -// Duplicates the brush, the sides, and the windings -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bspbrush_t *CopyBrush (bspbrush_t *brush) -{ - bspbrush_t *newbrush; - int size; - int i; - - size = (int)&(((bspbrush_t *)0)->sides[brush->numsides]); - - newbrush = AllocBrush (brush->numsides); - memcpy (newbrush, brush, size); - - for (i=0 ; inumsides ; i++) - { - if (brush->sides[i].winding) - newbrush->sides[i].winding = CopyWinding (brush->sides[i].winding); - } - - return newbrush; -} //end of the function CopyBrush -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -node_t *PointInLeaf (node_t *node, vec3_t point) -{ - vec_t d; - plane_t *plane; - - while (node->planenum != PLANENUM_LEAF) - { - plane = &mapplanes[node->planenum]; - d = DotProduct (point, plane->normal) - plane->dist; - if (d > 0) - node = node->children[0]; - else - node = node->children[1]; - } - - return node; -} //end of the function PointInLeaf -//=========================================================================== -// Returns PSIDE_FRONT, PSIDE_BACK, or PSIDE_BOTH -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -#if 0 -int BoxOnPlaneSide (vec3_t mins, vec3_t maxs, plane_t *plane) -{ - int side; - int i; - vec3_t corners[2]; - vec_t dist1, dist2; - - // axial planes are easy - if (plane->type < 3) - { - side = 0; - if (maxs[plane->type] > plane->dist+PLANESIDE_EPSILON) - side |= PSIDE_FRONT; - if (mins[plane->type] < plane->dist-PLANESIDE_EPSILON) - side |= PSIDE_BACK; - return side; - } - - // create the proper leading and trailing verts for the box - - for (i=0 ; i<3 ; i++) - { - if (plane->normal[i] < 0) - { - corners[0][i] = mins[i]; - corners[1][i] = maxs[i]; - } - else - { - corners[1][i] = mins[i]; - corners[0][i] = maxs[i]; - } - } - - dist1 = DotProduct (plane->normal, corners[0]) - plane->dist; - dist2 = DotProduct (plane->normal, corners[1]) - plane->dist; - side = 0; - if (dist1 >= PLANESIDE_EPSILON) - side = PSIDE_FRONT; - if (dist2 < PLANESIDE_EPSILON) - side |= PSIDE_BACK; - - return side; -} -#else -int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, plane_t *p) -{ - float dist1, dist2; - int sides; - - // axial planes are easy - if (p->type < 3) - { - sides = 0; - if (emaxs[p->type] > p->dist+PLANESIDE_EPSILON) sides |= PSIDE_FRONT; - if (emins[p->type] < p->dist-PLANESIDE_EPSILON) sides |= PSIDE_BACK; - return sides; - } //end if - -// general case - switch (p->signbits) - { - case 0: - dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; - dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; - break; - case 1: - dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; - dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; - break; - case 2: - dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; - dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; - break; - case 3: - dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; - dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; - break; - case 4: - dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; - dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; - break; - case 5: - dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; - dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; - break; - case 6: - dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; - dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; - break; - case 7: - dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; - dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; - break; - default: - dist1 = dist2 = 0; // shut up compiler -// assert( 0 ); - break; - } - - sides = 0; - if (dist1 - p->dist >= PLANESIDE_EPSILON) sides = PSIDE_FRONT; - if (dist2 - p->dist < PLANESIDE_EPSILON) sides |= PSIDE_BACK; - -// assert(sides != 0); - - return sides; -} -#endif -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int QuickTestBrushToPlanenum (bspbrush_t *brush, int planenum, int *numsplits) -{ - int i, num; - plane_t *plane; - int s; - - *numsplits = 0; - - plane = &mapplanes[planenum]; - -#ifdef ME - //fast axial cases - if (plane->type < 3) - { - if (plane->dist + PLANESIDE_EPSILON < brush->mins[plane->type]) - return PSIDE_FRONT; - if (plane->dist - PLANESIDE_EPSILON > brush->maxs[plane->type]) - return PSIDE_BACK; - } //end if -#endif //ME*/ - - // if the brush actually uses the planenum, - // we can tell the side for sure - for (i = 0; i < brush->numsides; i++) - { - num = brush->sides[i].planenum; - if (num >= MAX_MAPFILE_PLANES) - Error ("bad planenum"); - if (num == planenum) - return PSIDE_BACK|PSIDE_FACING; - if (num == (planenum ^ 1) ) - return PSIDE_FRONT|PSIDE_FACING; - - } - - // box on plane side - s = BoxOnPlaneSide (brush->mins, brush->maxs, plane); - - // if both sides, count the visible faces split - if (s == PSIDE_BOTH) - { - *numsplits += 3; - } - - return s; -} //end of the function QuickTestBrushToPlanenum -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int TestBrushToPlanenum (bspbrush_t *brush, int planenum, - int *numsplits, qboolean *hintsplit, int *epsilonbrush) -{ - int i, j, num; - plane_t *plane; - int s = 0; - winding_t *w; - vec_t d, d_front, d_back; - int front, back; - int type; - float dist; - - *numsplits = 0; - *hintsplit = false; - - plane = &mapplanes[planenum]; - -#ifdef ME - //fast axial cases - type = plane->type; - if (type < 3) - { - dist = plane->dist; - if (dist + PLANESIDE_EPSILON < brush->mins[type]) return PSIDE_FRONT; - if (dist - PLANESIDE_EPSILON > brush->maxs[type]) return PSIDE_BACK; - if (brush->mins[type] < dist - PLANESIDE_EPSILON && - brush->maxs[type] > dist + PLANESIDE_EPSILON) s = PSIDE_BOTH; - } //end if - - if (s != PSIDE_BOTH) -#endif //ME - { - // if the brush actually uses the planenum, - // we can tell the side for sure - for (i = 0; i < brush->numsides; i++) - { - num = brush->sides[i].planenum; - if (num >= MAX_MAPFILE_PLANES) Error ("bad planenum"); - if (num == planenum) - { - //we don't need to test this side plane again - brush->sides[i].flags |= SFL_TESTED; - return PSIDE_BACK|PSIDE_FACING; - } //end if - if (num == (planenum ^ 1) ) - { - //we don't need to test this side plane again - brush->sides[i].flags |= SFL_TESTED; - return PSIDE_FRONT|PSIDE_FACING; - } //end if - } //end for - - // box on plane side - s = BoxOnPlaneSide (brush->mins, brush->maxs, plane); - - if (s != PSIDE_BOTH) return s; - } //end if - - // if both sides, count the visible faces split - d_front = d_back = 0; - - for (i = 0; i < brush->numsides; i++) - { - if (brush->sides[i].texinfo == TEXINFO_NODE) - continue; // on node, don't worry about splits - if (!(brush->sides[i].flags & SFL_VISIBLE)) - continue; // we don't care about non-visible - w = brush->sides[i].winding; - if (!w) continue; - front = back = 0; - for (j = 0; j < w->numpoints; j++) - { - d = DotProduct(w->p[j], plane->normal) - plane->dist; - if (d > d_front) d_front = d; - if (d < d_back) d_back = d; - if (d > 0.1) // PLANESIDE_EPSILON) - front = 1; - if (d < -0.1) // PLANESIDE_EPSILON) - back = 1; - } //end for - if (front && back) - { - if ( !(brush->sides[i].surf & SURF_SKIP) ) - { - (*numsplits)++; - if (brush->sides[i].surf & SURF_HINT) - { - *hintsplit = true; - } //end if - } //end if - } //end if - } //end for - - if ( (d_front > 0.0 && d_front < 1.0) - || (d_back < 0.0 && d_back > -1.0) ) - (*epsilonbrush)++; - -#if 0 - if (*numsplits == 0) - { // didn't really need to be split - if (front) s = PSIDE_FRONT; - else if (back) s = PSIDE_BACK; - else s = 0; - } -#endif - - return s; -} //end of the function TestBrushToPlanenum -//=========================================================================== -// Returns true if the winding would be crunched out of -// existance by the vertex snapping. -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -#define EDGE_LENGTH 0.2 -qboolean WindingIsTiny (winding_t *w) -{ -#if 0 - if (WindingArea (w) < 1) - return true; - return false; -#else - int i, j; - vec_t len; - vec3_t delta; - int edges; - - edges = 0; - for (i=0 ; inumpoints ; i++) - { - j = i == w->numpoints - 1 ? 0 : i+1; - VectorSubtract (w->p[j], w->p[i], delta); - len = VectorLength (delta); - if (len > EDGE_LENGTH) - { - if (++edges == 3) - return false; - } - } - return true; -#endif -} //end of the function WindingIsTiny -//=========================================================================== -// Returns true if the winding still has one of the points -// from basewinding for plane -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean WindingIsHuge (winding_t *w) -{ - int i, j; - - for (i=0 ; inumpoints ; i++) - { - for (j=0 ; j<3 ; j++) - if (w->p[i][j] < -BOGUS_RANGE+1 || w->p[i][j] > BOGUS_RANGE-1) - return true; - } - return false; -} //end of the function WindingIsHuge -//=========================================================================== -// creates a leaf out of the given nodes with the given brushes -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void LeafNode(node_t *node, bspbrush_t *brushes) -{ - bspbrush_t *b; - int i; - - node->side = NULL; - node->planenum = PLANENUM_LEAF; - node->contents = 0; - - for (b = brushes; b; b = b->next) - { - // if the brush is solid and all of its sides are on nodes, - // it eats everything - if (b->original->contents & CONTENTS_SOLID) - { - for (i=0 ; inumsides ; i++) - if (b->sides[i].texinfo != TEXINFO_NODE) - break; - if (i == b->numsides) - { - node->contents = CONTENTS_SOLID; - break; - } //end if - } //end if - node->contents |= b->original->contents; - } //end for - - if (create_aas) - { - node->expansionbboxes = 0; - node->contents = 0; - for (b = brushes; b; b = b->next) - { - node->expansionbboxes |= b->original->expansionbbox; - node->contents |= b->original->contents; - if (b->original->modelnum) - node->modelnum = b->original->modelnum; - } //end for - if (node->contents & CONTENTS_SOLID) - { - if (node->expansionbboxes != cfg.allpresencetypes) - { - node->contents &= ~CONTENTS_SOLID; - } //end if - } //end if - } //end if - - node->brushlist = brushes; -} //end of the function LeafNode -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void CheckPlaneAgainstParents (int pnum, node_t *node) -{ - node_t *p; - - for (p = node->parent; p; p = p->parent) - { - if (p->planenum == pnum) Error("Tried parent"); - } //end for -} //end of the function CheckPlaneAgainstParants -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean CheckPlaneAgainstVolume (int pnum, node_t *node) -{ - bspbrush_t *front, *back; - qboolean good; - - SplitBrush (node->volume, pnum, &front, &back); - - good = (front && back); - - if (front) FreeBrush (front); - if (back) FreeBrush (back); - - return good; -} //end of the function CheckPlaneAgaintsVolume -//=========================================================================== -// Using a hueristic, choses one of the sides out of the brushlist -// to partition the brushes with. -// Returns NULL if there are no valid planes to split with.. -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -side_t *SelectSplitSide (bspbrush_t *brushes, node_t *node) -{ - int value, bestvalue; - bspbrush_t *brush, *test; - side_t *side, *bestside; - int i, pass, numpasses; - int pnum; - int s; - int front, back, both, facing, splits; - int bsplits; - int bestsplits; - int epsilonbrush; - qboolean hintsplit = false; - - bestside = NULL; - bestvalue = -99999; - bestsplits = 0; - - // the search order goes: visible-structural, visible-detail, - // nonvisible-structural, nonvisible-detail. - // If any valid plane is available in a pass, no further - // passes will be tried. - numpasses = 2; - for (pass = 0; pass < numpasses; pass++) - { - for (brush = brushes; brush; brush = brush->next) - { - // only check detail the second pass -// if ( (pass & 1) && !(brush->original->contents & CONTENTS_DETAIL) ) -// continue; -// if ( !(pass & 1) && (brush->original->contents & CONTENTS_DETAIL) ) -// continue; - for (i = 0; i < brush->numsides; i++) - { - side = brush->sides + i; -// if (side->flags & SFL_BEVEL) -// continue; // never use a bevel as a spliter - if (!side->winding) - continue; // nothing visible, so it can't split - if (side->texinfo == TEXINFO_NODE) - continue; // allready a node splitter - if (side->flags & SFL_TESTED) - continue; // we allready have metrics for this plane -// if (side->surf & SURF_SKIP) -// continue; // skip surfaces are never chosen - -// if (!(side->flags & SFL_VISIBLE) && (pass < 2)) -// continue; // only check visible faces on first pass - - if ((side->flags & SFL_CURVE) && (pass < 1)) - continue; // only check curves the second pass - - pnum = side->planenum; - pnum &= ~1; // allways use positive facing plane - - CheckPlaneAgainstParents (pnum, node); - - if (!CheckPlaneAgainstVolume (pnum, node)) - continue; // would produce a tiny volume - - front = 0; - back = 0; - both = 0; - facing = 0; - splits = 0; - epsilonbrush = 0; - - //inner loop: optimize - for (test = brushes; test; test = test->next) - { - s = TestBrushToPlanenum (test, pnum, &bsplits, &hintsplit, &epsilonbrush); - - splits += bsplits; -// if (bsplits && (s&PSIDE_FACING) ) -// Error ("PSIDE_FACING with splits"); - - test->testside = s; - // - if (s & PSIDE_FACING) facing++; - if (s & PSIDE_FRONT) front++; - if (s & PSIDE_BACK) back++; - if (s == PSIDE_BOTH) both++; - } //end for - - // give a value estimate for using this plane - value = 5*facing - 5*splits - abs(front-back); -// value = -5*splits; -// value = 5*facing - 5*splits; - if (mapplanes[pnum].type < 3) - value+=5; // axial is better - - value -= epsilonbrush * 1000; // avoid! - - // never split a hint side except with another hint - if (hintsplit && !(side->surf & SURF_HINT) ) - value = -9999999; - - // save off the side test so we don't need - // to recalculate it when we actually seperate - // the brushes - if (value > bestvalue) - { - bestvalue = value; - bestside = side; - bestsplits = splits; - for (test = brushes; test ; test = test->next) - test->side = test->testside; - } //end if - } //end for - } //end for (brush = brushes; - - // if we found a good plane, don't bother trying any - // other passes - if (bestside) - { - if (pass > 1) - { - if (numthreads == 1) c_nonvis++; - } - if (pass > 0) node->detail_seperator = true; // not needed for vis - break; - } //end if - } //end for (pass = 0; - - // - // clear all the tested flags we set - // - for (brush = brushes ; brush ; brush=brush->next) - { - for (i = 0; i < brush->numsides; i++) - { - brush->sides[i].flags &= ~SFL_TESTED; - } //end for - } //end for - - return bestside; -} //end of the function SelectSplitSide -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BrushMostlyOnSide (bspbrush_t *brush, plane_t *plane) -{ - int i, j; - winding_t *w; - vec_t d, max; - int side; - - max = 0; - side = PSIDE_FRONT; - for (i=0 ; inumsides ; i++) - { - w = brush->sides[i].winding; - if (!w) - continue; - for (j=0 ; jnumpoints ; j++) - { - d = DotProduct (w->p[j], plane->normal) - plane->dist; - if (d > max) - { - max = d; - side = PSIDE_FRONT; - } - if (-d > max) - { - max = -d; - side = PSIDE_BACK; - } - } - } - return side; -} //end of the function BrushMostlyOnSide -//=========================================================================== -// Generates two new brushes, leaving the original -// unchanged -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void SplitBrush (bspbrush_t *brush, int planenum, - bspbrush_t **front, bspbrush_t **back) -{ - bspbrush_t *b[2]; - int i, j; - winding_t *w, *cw[2], *midwinding; - plane_t *plane, *plane2; - side_t *s, *cs; - float d, d_front, d_back; - - *front = *back = NULL; - plane = &mapplanes[planenum]; - - // check all points - d_front = d_back = 0; - for (i=0 ; inumsides ; i++) - { - w = brush->sides[i].winding; - if (!w) - continue; - for (j=0 ; jnumpoints ; j++) - { - d = DotProduct (w->p[j], plane->normal) - plane->dist; - if (d > 0 && d > d_front) - d_front = d; - if (d < 0 && d < d_back) - d_back = d; - } - } - - if (d_front < 0.2) // PLANESIDE_EPSILON) - { // only on back - *back = CopyBrush (brush); - return; - } - if (d_back > -0.2) // PLANESIDE_EPSILON) - { // only on front - *front = CopyBrush (brush); - return; - } - - // create a new winding from the split plane - - w = BaseWindingForPlane (plane->normal, plane->dist); - for (i=0 ; inumsides && w ; i++) - { - plane2 = &mapplanes[brush->sides[i].planenum ^ 1]; - ChopWindingInPlace (&w, plane2->normal, plane2->dist, 0); // PLANESIDE_EPSILON); - } - - if (!w || WindingIsTiny(w)) - { // the brush isn't really split - int side; - - side = BrushMostlyOnSide (brush, plane); - if (side == PSIDE_FRONT) - *front = CopyBrush (brush); - if (side == PSIDE_BACK) - *back = CopyBrush (brush); - //free a possible winding - if (w) FreeWinding(w); - return; - } - - if (WindingIsHuge (w)) - { - Log_Write("WARNING: huge winding\n"); - } - - midwinding = w; - - // split it for real - - for (i=0 ; i<2 ; i++) - { - b[i] = AllocBrush (brush->numsides+1); - b[i]->original = brush->original; - } - - // split all the current windings - - for (i=0 ; inumsides ; i++) - { - s = &brush->sides[i]; - w = s->winding; - if (!w) - continue; - ClipWindingEpsilon (w, plane->normal, plane->dist, - 0 /*PLANESIDE_EPSILON*/, &cw[0], &cw[1]); - for (j=0 ; j<2 ; j++) - { - if (!cw[j]) - continue; -#if 0 - if (WindingIsTiny (cw[j])) - { - FreeWinding (cw[j]); - continue; - } -#endif - cs = &b[j]->sides[b[j]->numsides]; - b[j]->numsides++; - *cs = *s; -// cs->planenum = s->planenum; -// cs->texinfo = s->texinfo; -// cs->original = s->original; - cs->winding = cw[j]; - cs->flags &= ~SFL_TESTED; - } - } - - - // see if we have valid polygons on both sides - - for (i=0 ; i<2 ; i++) - { - BoundBrush (b[i]); - for (j=0 ; j<3 ; j++) - { - if (b[i]->mins[j] < -MAX_MAP_BOUNDS || b[i]->maxs[j] > MAX_MAP_BOUNDS) - { - Log_Write("bogus brush after clip"); - break; - } - } - - if (b[i]->numsides < 3 || j < 3) - { - FreeBrush (b[i]); - b[i] = NULL; - } - } - - if ( !(b[0] && b[1]) ) - { - if (!b[0] && !b[1]) - Log_Write("split removed brush\r\n"); - else - Log_Write("split not on both sides\r\n"); - if (b[0]) - { - FreeBrush (b[0]); - *front = CopyBrush (brush); - } - if (b[1]) - { - FreeBrush (b[1]); - *back = CopyBrush (brush); - } - return; - } - - // add the midwinding to both sides - for (i=0 ; i<2 ; i++) - { - cs = &b[i]->sides[b[i]->numsides]; - b[i]->numsides++; - - cs->planenum = planenum^i^1; - cs->texinfo = TEXINFO_NODE; //never use these sides as splitters - cs->flags &= ~SFL_VISIBLE; - cs->flags &= ~SFL_TESTED; - if (i==0) - cs->winding = CopyWinding (midwinding); - else - cs->winding = midwinding; - } - -{ - vec_t v1; - int i; - - for (i = 0; i < 2; i++) - { - v1 = BrushVolume (b[i]); - if (v1 < 1.0) - { - FreeBrush(b[i]); - b[i] = NULL; - //Log_Write("tiny volume after clip"); - } - } - if (!b[0] && !b[1]) - { - Log_Write("two tiny brushes\r\n"); - } //end if -} - - *front = b[0]; - *back = b[1]; -} //end of the function SplitBrush -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void SplitBrushList (bspbrush_t *brushes, - node_t *node, bspbrush_t **front, bspbrush_t **back) -{ - bspbrush_t *brush, *newbrush, *newbrush2; - side_t *side; - int sides; - int i; - - *front = *back = NULL; - - for (brush = brushes; brush; brush = brush->next) - { - sides = brush->side; - - if (sides == PSIDE_BOTH) - { // split into two brushes - SplitBrush (brush, node->planenum, &newbrush, &newbrush2); - if (newbrush) - { - newbrush->next = *front; - *front = newbrush; - } //end if - if (newbrush2) - { - newbrush2->next = *back; - *back = newbrush2; - } //end if - continue; - } //end if - - newbrush = CopyBrush (brush); - - // if the planenum is actualy a part of the brush - // find the plane and flag it as used so it won't be tried - // as a splitter again - if (sides & PSIDE_FACING) - { - for (i=0 ; inumsides ; i++) - { - side = newbrush->sides + i; - if ( (side->planenum& ~1) == node->planenum) - side->texinfo = TEXINFO_NODE; - } //end for - } //end if - if (sides & PSIDE_FRONT) - { - newbrush->next = *front; - *front = newbrush; - continue; - } //end if - if (sides & PSIDE_BACK) - { - newbrush->next = *back; - *back = newbrush; - continue; - } //end if - } //end for -} //end of the function SplitBrushList -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void CheckBrushLists(bspbrush_t *brushlist1, bspbrush_t *brushlist2) -{ - bspbrush_t *brush1, *brush2; - - for (brush1 = brushlist1; brush1; brush1 = brush1->next) - { - for (brush2 = brushlist2; brush2; brush2 = brush2->next) - { - assert(brush1 != brush2); - } //end for - } //end for -} //end of the function CheckBrushLists -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int numrecurse = 0; - -node_t *BuildTree_r (node_t *node, bspbrush_t *brushes) -{ - node_t *newnode; - side_t *bestside; - int i, totalmem; - bspbrush_t *children[2]; - - qprintf("\r%6d", numrecurse); - numrecurse++; - - if (numthreads == 1) - { - totalmem = WindingMemory() + c_nodememory + c_brushmemory; - if (totalmem > c_peak_totalbspmemory) - c_peak_totalbspmemory = totalmem; - c_nodes++; - } //endif - - if (drawflag) - DrawBrushList(brushes, node); - - // find the best plane to use as a splitter - bestside = SelectSplitSide (brushes, node); - if (!bestside) - { - // leaf node - node->side = NULL; - node->planenum = -1; - LeafNode(node, brushes); - if (node->contents & CONTENTS_SOLID) c_solidleafnodes++; - if (create_aas) - { - //free up memory!!! - FreeBrushList(node->brushlist); - node->brushlist = NULL; - //free the node volume brush - if (node->volume) - { - FreeBrush(node->volume); - node->volume = NULL; - } //end if - } //end if - return node; - } //end if - - // this is a splitplane node - node->side = bestside; - node->planenum = bestside->planenum & ~1; // always use front facing - - //split the brush list in two for both children - SplitBrushList (brushes, node, &children[0], &children[1]); - //free the old brush list - FreeBrushList (brushes); - - // allocate children before recursing - for (i = 0; i < 2; i++) - { - newnode = AllocNode (); - newnode->parent = node; - node->children[i] = newnode; - } //end for - - //split the volume brush of the node for the children - SplitBrush (node->volume, node->planenum, &node->children[0]->volume, - &node->children[1]->volume); - - if (create_aas) - { - //free the volume brush - if (node->volume) - { - FreeBrush(node->volume); - node->volume = NULL; - } //end if - } //end if - // recursively process children - for (i = 0; i < 2; i++) - { - node->children[i] = BuildTree_r(node->children[i], children[i]); - } //end for - - return node; -} //end of the function BuildTree_r -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -node_t *firstnode; //first node in the list -node_t *lastnode; //last node in the list -int nodelistsize; //number of nodes in the list -int use_nodequeue = 0; //use nodequeue, otherwise a node stack is used -int numwaiting = 0; - -void (*AddNodeToList)(node_t *node); - -//add the node to the front of the node list -//(effectively using a node stack) -void AddNodeToStack(node_t *node) -{ - ThreadLock(); - - node->next = firstnode; - firstnode = node; - if (!lastnode) lastnode = node; - nodelistsize++; - - ThreadUnlock(); - // - ThreadSemaphoreIncrease(1); -} //end of the function AddNodeToStack -//add the node to the end of the node list -//(effectively using a node queue) -void AddNodeToQueue(node_t *node) -{ - ThreadLock(); - - node->next = NULL; - if (lastnode) lastnode->next = node; - else firstnode = node; - lastnode = node; - nodelistsize++; - - ThreadUnlock(); - // - ThreadSemaphoreIncrease(1); -} //end of the function AddNodeToQueue -//get the first node from the front of the node list -node_t *NextNodeFromList(void) -{ - node_t *node; - - ThreadLock(); - numwaiting++; - if (!firstnode) - { - if (numwaiting >= GetNumThreads()) ThreadSemaphoreIncrease(GetNumThreads()); - } //end if - ThreadUnlock(); - - ThreadSemaphoreWait(); - - ThreadLock(); - - numwaiting--; - - node = firstnode; - if (firstnode) - { - firstnode = firstnode->next; - nodelistsize--; - } //end if - if (!firstnode) lastnode = NULL; - - ThreadUnlock(); - - return node; -} //end of the function NextNodeFromList -//returns the size of the node list -int NodeListSize(void) -{ - int size; - - ThreadLock(); - size = nodelistsize; - ThreadUnlock(); - - return size; -} //end of the function NodeListSize -// -void IncreaseNodeCounter(void) -{ - ThreadLock(); - //if (verbose) printf("\r%6d", numrecurse++); - qprintf("\r%6d", numrecurse++); - //qprintf("\r%6d %d, %5d ", numrecurse++, GetNumThreads(), nodelistsize); - ThreadUnlock(); -} //end of the function IncreaseNodeCounter -//thread function, gets nodes from the nodelist and processes them -void BuildTreeThread(int threadid) -{ - node_t *newnode, *node; - side_t *bestside; - int i, totalmem; - bspbrush_t *brushes; - - for (node = NextNodeFromList(); node; ) - { - //if the nodelist isn't empty try to add another thread - //if (NodeListSize() > 10) AddThread(BuildTreeThread); - //display the number of nodes processed so far - if (numthreads == 1) - IncreaseNodeCounter(); - - brushes = node->brushlist; - - if (numthreads == 1) - { - totalmem = WindingMemory() + c_nodememory + c_brushmemory; - if (totalmem > c_peak_totalbspmemory) - { - c_peak_totalbspmemory = totalmem; - } //end if - c_nodes++; - } //endif - - if (drawflag) - { - DrawBrushList(brushes, node); - } //end if - - if (cancelconversion) - { - bestside = NULL; - } //end if - else - { - // find the best plane to use as a splitter - bestside = SelectSplitSide(brushes, node); - } //end else - //if there's no split side left - if (!bestside) - { - //create a leaf out of the node - LeafNode(node, brushes); - if (node->contents & CONTENTS_SOLID) c_solidleafnodes++; - if (create_aas) - { - //free up memory!!! - FreeBrushList(node->brushlist); - node->brushlist = NULL; - } //end if - //free the node volume brush (it is not used anymore) - if (node->volume) - { - FreeBrush(node->volume); - node->volume = NULL; - } //end if - node = NextNodeFromList(); - continue; - } //end if - - // this is a splitplane node - node->side = bestside; - node->planenum = bestside->planenum & ~1; //always use front facing - - //allocate children - for (i = 0; i < 2; i++) - { - newnode = AllocNode(); - newnode->parent = node; - node->children[i] = newnode; - } //end for - - //split the brush list in two for both children - SplitBrushList(brushes, node, &node->children[0]->brushlist, &node->children[1]->brushlist); - - CheckBrushLists(node->children[0]->brushlist, node->children[1]->brushlist); - //free the old brush list - FreeBrushList(brushes); - node->brushlist = NULL; - - //split the volume brush of the node for the children - SplitBrush(node->volume, node->planenum, &node->children[0]->volume, - &node->children[1]->volume); - - if (!node->children[0]->volume || !node->children[1]->volume) - { - Error("child without volume brush"); - } //end if - - //free the volume brush - if (node->volume) - { - FreeBrush(node->volume); - node->volume = NULL; - } //end if - //add both children to the node list - //AddNodeToList(node->children[0]); - AddNodeToList(node->children[1]); - node = node->children[0]; - } //end while - RemoveThread(threadid); -} //end of the function BuildTreeThread -//=========================================================================== -// build the bsp tree using a node list -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BuildTree(tree_t *tree) -{ - int i; - - firstnode = NULL; - lastnode = NULL; - //use a node queue or node stack - if (use_nodequeue) AddNodeToList = AddNodeToQueue; - else AddNodeToList = AddNodeToStack; - //setup thread locking - ThreadSetupLock(); - ThreadSetupSemaphore(); - numwaiting = 0; - // - Log_Print("%6d threads max\n", numthreads); - if (use_nodequeue) Log_Print("breadth first bsp building\n"); - else Log_Print("depth first bsp building\n"); - qprintf("%6d splits", 0); - //add the first node to the list - AddNodeToList(tree->headnode); - //start the threads - for (i = 0; i < numthreads; i++) - AddThread(BuildTreeThread); - //wait for all added threads to be finished - WaitForAllThreadsFinished(); - //shutdown the thread locking - ThreadShutdownLock(); - ThreadShutdownSemaphore(); -} //end of the function BuildTree -//=========================================================================== -// The incoming brush list will be freed before exiting -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -tree_t *BrushBSP(bspbrush_t *brushlist, vec3_t mins, vec3_t maxs) -{ - int i, c_faces, c_nonvisfaces, c_brushes; - bspbrush_t *b; - node_t *node; - tree_t *tree; - vec_t volume; -// vec3_t point; - - Log_Print("-------- Brush BSP ---------\n"); - - tree = Tree_Alloc(); - - c_faces = 0; - c_nonvisfaces = 0; - c_brushes = 0; - c_totalsides = 0; - for (b = brushlist; b; b = b->next) - { - c_brushes++; - - volume = BrushVolume(b); - if (volume < microvolume) - { - Log_Print("WARNING: entity %i, brush %i: microbrush\n", - b->original->entitynum, b->original->brushnum); - } //end if - - for (i=0 ; inumsides ; i++) - { - if (b->sides[i].flags & SFL_BEVEL) - continue; - if (!b->sides[i].winding) - continue; - if (b->sides[i].texinfo == TEXINFO_NODE) - continue; - if (b->sides[i].flags & SFL_VISIBLE) - { - c_faces++; - } //end if - else - { - c_nonvisfaces++; - //if (create_aas) b->sides[i].texinfo = TEXINFO_NODE; - } //end if - } //end for - c_totalsides += b->numsides; - - AddPointToBounds (b->mins, tree->mins, tree->maxs); - AddPointToBounds (b->maxs, tree->mins, tree->maxs); - } //end for - - Log_Print("%6i brushes\n", c_brushes); - Log_Print("%6i visible faces\n", c_faces); - Log_Print("%6i nonvisible faces\n", c_nonvisfaces); - Log_Print("%6i total sides\n", c_totalsides); - - c_active_brushes = c_brushes; - c_nodememory = 0; - c_brushmemory = 0; - c_peak_brushmemory = 0; - - c_nodes = 0; - c_nonvis = 0; - node = AllocNode (); - - //volume of first node (head node) - node->volume = BrushFromBounds (mins, maxs); - // - tree->headnode = node; - //just get some statistics and the mins/maxs of the node - numrecurse = 0; -// qprintf("%6d splits", numrecurse); - - tree->headnode->brushlist = brushlist; - BuildTree(tree); - - //build the bsp tree with the start node from the brushlist -// node = BuildTree_r(node, brushlist); - - //if the conversion is cancelled - if (cancelconversion) return tree; - - qprintf("\n"); - Log_Write("%6d splits\r\n", numrecurse); -// Log_Print("%6i visible nodes\n", c_nodes/2 - c_nonvis); -// Log_Print("%6i nonvis nodes\n", c_nonvis); -// Log_Print("%6i leaves\n", (c_nodes+1)/2); -// Log_Print("%6i solid leaf nodes\n", c_solidleafnodes); -// Log_Print("%6i active brushes\n", c_active_brushes); - if (numthreads == 1) - { -// Log_Print("%6i KB of node memory\n", c_nodememory >> 10); -// Log_Print("%6i KB of brush memory\n", c_brushmemory >> 10); -// Log_Print("%6i KB of peak brush memory\n", c_peak_brushmemory >> 10); -// Log_Print("%6i KB of winding memory\n", WindingMemory() >> 10); -// Log_Print("%6i KB of peak winding memory\n", WindingPeakMemory() >> 10); - Log_Print("%6i KB of peak total bsp memory\n", c_peak_totalbspmemory >> 10); - } //end if - - /* - point[0] = 1485; - point[1] = 956.125; - point[2] = 352.125; - node = PointInLeaf(tree->headnode, point); - if (node->planenum != PLANENUM_LEAF) - { - Log_Print("node not a leaf\n"); - } //end if - Log_Print("at %f %f %f:\n", point[0], point[1], point[2]); - PrintContents(node->contents); - Log_Print("node->expansionbboxes = %d\n", node->expansionbboxes); - //*/ - return tree; -} //end of the function BrushBSP - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "qbsp.h" +#include "l_mem.h" +#include "../botlib/aasfile.h" +#include "aas_store.h" +#include "aas_cfg.h" + +#include + +/* +each side has a count of the other sides it splits + +the best split will be the one that minimizes the total split counts +of all remaining sides + +precalc side on plane table + +evaluate split side +{ +cost = 0 +for all sides + for all sides + get + if side splits side and splitside is on same child + cost++; +} +*/ + +int c_nodes; +int c_nonvis; +int c_active_brushes; +int c_solidleafnodes; +int c_totalsides; +int c_brushmemory; +int c_peak_brushmemory; +int c_nodememory; +int c_peak_totalbspmemory; + +// if a brush just barely pokes onto the other side, +// let it slide by without chopping +#define PLANESIDE_EPSILON 0.001 +//0.1 + +//#ifdef DEBUG +typedef struct cname_s +{ + int value; + char *name; +} cname_t; + +cname_t contentnames[] = +{ + {CONTENTS_SOLID,"CONTENTS_SOLID"}, + {CONTENTS_WINDOW,"CONTENTS_WINDOW"}, + {CONTENTS_AUX,"CONTENTS_AUX"}, + {CONTENTS_LAVA,"CONTENTS_LAVA"}, + {CONTENTS_SLIME,"CONTENTS_SLIME"}, + {CONTENTS_WATER,"CONTENTS_WATER"}, + {CONTENTS_MIST,"CONTENTS_MIST"}, + {LAST_VISIBLE_CONTENTS,"LAST_VISIBLE_CONTENTS"}, + + {CONTENTS_AREAPORTAL,"CONTENTS_AREAPORTAL"}, + {CONTENTS_PLAYERCLIP,"CONTENTS_PLAYERCLIP"}, + {CONTENTS_MONSTERCLIP,"CONTENTS_MONSTERCLIP"}, + {CONTENTS_CURRENT_0,"CONTENTS_CURRENT_0"}, + {CONTENTS_CURRENT_90,"CONTENTS_CURRENT_90"}, + {CONTENTS_CURRENT_180,"CONTENTS_CURRENT_180"}, + {CONTENTS_CURRENT_270,"CONTENTS_CURRENT_270"}, + {CONTENTS_CURRENT_UP,"CONTENTS_CURRENT_UP"}, + {CONTENTS_CURRENT_DOWN,"CONTENTS_CURRENT_DOWN"}, + {CONTENTS_ORIGIN,"CONTENTS_ORIGIN"}, + {CONTENTS_MONSTER,"CONTENTS_MONSTER"}, + {CONTENTS_DEADMONSTER,"CONTENTS_DEADMONSTER"}, + {CONTENTS_DETAIL,"CONTENTS_DETAIL"}, + {CONTENTS_Q2TRANSLUCENT,"CONTENTS_TRANSLUCENT"}, + {CONTENTS_LADDER,"CONTENTS_LADDER"}, + {0, 0} +}; + +void PrintContents(int contents) +{ + int i; + + for (i = 0; contentnames[i].value; i++) + { + if (contents & contentnames[i].value) + { + Log_Write("%s,", contentnames[i].name); + } //end if + } //end for +} //end of the function PrintContents + +//#endif DEBUG + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ResetBrushBSP(void) +{ + c_nodes = 0; + c_nonvis = 0; + c_active_brushes = 0; + c_solidleafnodes = 0; + c_totalsides = 0; + c_brushmemory = 0; + c_peak_brushmemory = 0; + c_nodememory = 0; + c_peak_totalbspmemory = 0; +} //end of the function ResetBrushBSP +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FindBrushInTree (node_t *node, int brushnum) +{ + bspbrush_t *b; + + if (node->planenum == PLANENUM_LEAF) + { + for (b=node->brushlist ; b ; b=b->next) + if (b->original->brushnum == brushnum) + Log_Print ("here\n"); + return; + } + FindBrushInTree(node->children[0], brushnum); + FindBrushInTree(node->children[1], brushnum); +} //end of the function FindBrushInTree +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void DrawBrushList (bspbrush_t *brush, node_t *node) +{ + int i; + side_t *s; + + GLS_BeginScene (); + for ( ; brush ; brush=brush->next) + { + for (i=0 ; inumsides ; i++) + { + s = &brush->sides[i]; + if (!s->winding) + continue; + if (s->texinfo == TEXINFO_NODE) + GLS_Winding (s->winding, 1); + else if (!(s->flags & SFL_VISIBLE)) + GLS_Winding (s->winding, 2); + else + GLS_Winding (s->winding, 0); + } + } + GLS_EndScene (); +} //end of the function DrawBrushList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void WriteBrushList (char *name, bspbrush_t *brush, qboolean onlyvis) +{ + int i; + side_t *s; + FILE *f; + + qprintf ("writing %s\n", name); + f = SafeOpenWrite (name); + + for ( ; brush ; brush=brush->next) + { + for (i=0 ; inumsides ; i++) + { + s = &brush->sides[i]; + if (!s->winding) + continue; + if (onlyvis && !(s->flags & SFL_VISIBLE)) + continue; + OutputWinding (brush->sides[i].winding, f); + } + } + + fclose (f); +} //end of the function WriteBrushList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void PrintBrush (bspbrush_t *brush) +{ + int i; + + printf ("brush: %p\n", brush); + for (i=0;inumsides ; i++) + { + pw(brush->sides[i].winding); + printf ("\n"); + } //end for +} //end of the function PrintBrush +//=========================================================================== +// Sets the mins/maxs based on the windings +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BoundBrush (bspbrush_t *brush) +{ + int i, j; + winding_t *w; + + ClearBounds (brush->mins, brush->maxs); + for (i=0 ; inumsides ; i++) + { + w = brush->sides[i].winding; + if (!w) + continue; + for (j=0 ; jnumpoints ; j++) + AddPointToBounds (w->p[j], brush->mins, brush->maxs); + } +} //end of the function BoundBrush +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void CreateBrushWindings (bspbrush_t *brush) +{ + int i, j; + winding_t *w; + side_t *side; + plane_t *plane; + + for (i=0 ; inumsides ; i++) + { + side = &brush->sides[i]; + plane = &mapplanes[side->planenum]; + w = BaseWindingForPlane (plane->normal, plane->dist); + for (j=0 ; jnumsides && w; j++) + { + if (i == j) + continue; + if (brush->sides[j].flags & SFL_BEVEL) + continue; + plane = &mapplanes[brush->sides[j].planenum^1]; + ChopWindingInPlace (&w, plane->normal, plane->dist, 0); //CLIP_EPSILON); + } + + side->winding = w; + } + + BoundBrush (brush); +} //end of the function CreateBrushWindings +//=========================================================================== +// Creates a new axial brush +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *BrushFromBounds (vec3_t mins, vec3_t maxs) +{ + bspbrush_t *b; + int i; + vec3_t normal; + vec_t dist; + + b = AllocBrush (6); + b->numsides = 6; + for (i=0 ; i<3 ; i++) + { + VectorClear (normal); + normal[i] = 1; + dist = maxs[i]; + b->sides[i].planenum = FindFloatPlane (normal, dist); + + normal[i] = -1; + dist = -mins[i]; + b->sides[3+i].planenum = FindFloatPlane (normal, dist); + } + + CreateBrushWindings (b); + + return b; +} //end of the function BrushFromBounds +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BrushOutOfBounds(bspbrush_t *brush, vec3_t mins, vec3_t maxs, float epsilon) +{ + int i, j, n; + winding_t *w; + side_t *side; + + for (i = 0; i < brush->numsides; i++) + { + side = &brush->sides[i]; + w = side->winding; + for (j = 0; j < w->numpoints; j++) + { + for (n = 0; n < 3; n++) + { + if (w->p[j][n] < (mins[n] + epsilon) || w->p[j][n] > (maxs[n] - epsilon)) return true; + } //end for + } //end for + } //end for + return false; +} //end of the function BrushOutOfBounds +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +vec_t BrushVolume (bspbrush_t *brush) +{ + int i; + winding_t *w; + vec3_t corner; + vec_t d, area, volume; + plane_t *plane; + + if (!brush) return 0; + + // grab the first valid point as the corner + w = NULL; + for (i = 0; i < brush->numsides; i++) + { + w = brush->sides[i].winding; + if (w) break; + } //end for + if (!w) return 0; + VectorCopy (w->p[0], corner); + + // make tetrahedrons to all other faces + volume = 0; + for ( ; i < brush->numsides; i++) + { + w = brush->sides[i].winding; + if (!w) continue; + plane = &mapplanes[brush->sides[i].planenum]; + d = -(DotProduct (corner, plane->normal) - plane->dist); + area = WindingArea(w); + volume += d * area; + } //end for + + volume /= 3; + return volume; +} //end of the function BrushVolume +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int CountBrushList (bspbrush_t *brushes) +{ + int c; + + c = 0; + for ( ; brushes; brushes = brushes->next) c++; + return c; +} //end of the function CountBrushList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +node_t *AllocNode (void) +{ + node_t *node; + + node = GetMemory(sizeof(*node)); + memset (node, 0, sizeof(*node)); + if (numthreads == 1) + { + c_nodememory += MemorySize(node); + } //end if + return node; +} //end of the function AllocNode +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *AllocBrush (int numsides) +{ + bspbrush_t *bb; + int c; + + c = (int)&(((bspbrush_t *)0)->sides[numsides]); + bb = GetMemory(c); + memset (bb, 0, c); + if (numthreads == 1) + { + c_active_brushes++; + c_brushmemory += MemorySize(bb); + if (c_brushmemory > c_peak_brushmemory) + c_peak_brushmemory = c_brushmemory; + } //end if + return bb; +} //end of the function AllocBrush +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeBrush (bspbrush_t *brushes) +{ + int i; + + for (i=0 ; inumsides ; i++) + if (brushes->sides[i].winding) + FreeWinding(brushes->sides[i].winding); + if (numthreads == 1) + { + c_active_brushes--; + c_brushmemory -= MemorySize(brushes); + if (c_brushmemory < 0) c_brushmemory = 0; + } //end if + FreeMemory(brushes); +} //end of the function FreeBrush +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeBrushList (bspbrush_t *brushes) +{ + bspbrush_t *next; + + for ( ; brushes; brushes = next) + { + next = brushes->next; + + FreeBrush(brushes); + } //end for +} //end of the function FreeBrushList +//=========================================================================== +// Duplicates the brush, the sides, and the windings +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *CopyBrush (bspbrush_t *brush) +{ + bspbrush_t *newbrush; + int size; + int i; + + size = (int)&(((bspbrush_t *)0)->sides[brush->numsides]); + + newbrush = AllocBrush (brush->numsides); + memcpy (newbrush, brush, size); + + for (i=0 ; inumsides ; i++) + { + if (brush->sides[i].winding) + newbrush->sides[i].winding = CopyWinding (brush->sides[i].winding); + } + + return newbrush; +} //end of the function CopyBrush +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +node_t *PointInLeaf (node_t *node, vec3_t point) +{ + vec_t d; + plane_t *plane; + + while (node->planenum != PLANENUM_LEAF) + { + plane = &mapplanes[node->planenum]; + d = DotProduct (point, plane->normal) - plane->dist; + if (d > 0) + node = node->children[0]; + else + node = node->children[1]; + } + + return node; +} //end of the function PointInLeaf +//=========================================================================== +// Returns PSIDE_FRONT, PSIDE_BACK, or PSIDE_BOTH +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#if 0 +int BoxOnPlaneSide (vec3_t mins, vec3_t maxs, plane_t *plane) +{ + int side; + int i; + vec3_t corners[2]; + vec_t dist1, dist2; + + // axial planes are easy + if (plane->type < 3) + { + side = 0; + if (maxs[plane->type] > plane->dist+PLANESIDE_EPSILON) + side |= PSIDE_FRONT; + if (mins[plane->type] < plane->dist-PLANESIDE_EPSILON) + side |= PSIDE_BACK; + return side; + } + + // create the proper leading and trailing verts for the box + + for (i=0 ; i<3 ; i++) + { + if (plane->normal[i] < 0) + { + corners[0][i] = mins[i]; + corners[1][i] = maxs[i]; + } + else + { + corners[1][i] = mins[i]; + corners[0][i] = maxs[i]; + } + } + + dist1 = DotProduct (plane->normal, corners[0]) - plane->dist; + dist2 = DotProduct (plane->normal, corners[1]) - plane->dist; + side = 0; + if (dist1 >= PLANESIDE_EPSILON) + side = PSIDE_FRONT; + if (dist2 < PLANESIDE_EPSILON) + side |= PSIDE_BACK; + + return side; +} +#else +int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, plane_t *p) +{ + float dist1, dist2; + int sides; + + // axial planes are easy + if (p->type < 3) + { + sides = 0; + if (emaxs[p->type] > p->dist+PLANESIDE_EPSILON) sides |= PSIDE_FRONT; + if (emins[p->type] < p->dist-PLANESIDE_EPSILON) sides |= PSIDE_BACK; + return sides; + } //end if + +// general case + switch (p->signbits) + { + case 0: + dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; + dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; + break; + case 1: + dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; + dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; + break; + case 2: + dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; + dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; + break; + case 3: + dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; + dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; + break; + case 4: + dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; + dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; + break; + case 5: + dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; + dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; + break; + case 6: + dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; + dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; + break; + case 7: + dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; + dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; + break; + default: + dist1 = dist2 = 0; // shut up compiler +// assert( 0 ); + break; + } + + sides = 0; + if (dist1 - p->dist >= PLANESIDE_EPSILON) sides = PSIDE_FRONT; + if (dist2 - p->dist < PLANESIDE_EPSILON) sides |= PSIDE_BACK; + +// assert(sides != 0); + + return sides; +} +#endif +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int QuickTestBrushToPlanenum (bspbrush_t *brush, int planenum, int *numsplits) +{ + int i, num; + plane_t *plane; + int s; + + *numsplits = 0; + + plane = &mapplanes[planenum]; + +#ifdef ME + //fast axial cases + if (plane->type < 3) + { + if (plane->dist + PLANESIDE_EPSILON < brush->mins[plane->type]) + return PSIDE_FRONT; + if (plane->dist - PLANESIDE_EPSILON > brush->maxs[plane->type]) + return PSIDE_BACK; + } //end if +#endif //ME*/ + + // if the brush actually uses the planenum, + // we can tell the side for sure + for (i = 0; i < brush->numsides; i++) + { + num = brush->sides[i].planenum; + if (num >= MAX_MAPFILE_PLANES) + Error ("bad planenum"); + if (num == planenum) + return PSIDE_BACK|PSIDE_FACING; + if (num == (planenum ^ 1) ) + return PSIDE_FRONT|PSIDE_FACING; + + } + + // box on plane side + s = BoxOnPlaneSide (brush->mins, brush->maxs, plane); + + // if both sides, count the visible faces split + if (s == PSIDE_BOTH) + { + *numsplits += 3; + } + + return s; +} //end of the function QuickTestBrushToPlanenum +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int TestBrushToPlanenum (bspbrush_t *brush, int planenum, + int *numsplits, qboolean *hintsplit, int *epsilonbrush) +{ + int i, j, num; + plane_t *plane; + int s = 0; + winding_t *w; + vec_t d, d_front, d_back; + int front, back; + int type; + float dist; + + *numsplits = 0; + *hintsplit = false; + + plane = &mapplanes[planenum]; + +#ifdef ME + //fast axial cases + type = plane->type; + if (type < 3) + { + dist = plane->dist; + if (dist + PLANESIDE_EPSILON < brush->mins[type]) return PSIDE_FRONT; + if (dist - PLANESIDE_EPSILON > brush->maxs[type]) return PSIDE_BACK; + if (brush->mins[type] < dist - PLANESIDE_EPSILON && + brush->maxs[type] > dist + PLANESIDE_EPSILON) s = PSIDE_BOTH; + } //end if + + if (s != PSIDE_BOTH) +#endif //ME + { + // if the brush actually uses the planenum, + // we can tell the side for sure + for (i = 0; i < brush->numsides; i++) + { + num = brush->sides[i].planenum; + if (num >= MAX_MAPFILE_PLANES) Error ("bad planenum"); + if (num == planenum) + { + //we don't need to test this side plane again + brush->sides[i].flags |= SFL_TESTED; + return PSIDE_BACK|PSIDE_FACING; + } //end if + if (num == (planenum ^ 1) ) + { + //we don't need to test this side plane again + brush->sides[i].flags |= SFL_TESTED; + return PSIDE_FRONT|PSIDE_FACING; + } //end if + } //end for + + // box on plane side + s = BoxOnPlaneSide (brush->mins, brush->maxs, plane); + + if (s != PSIDE_BOTH) return s; + } //end if + + // if both sides, count the visible faces split + d_front = d_back = 0; + + for (i = 0; i < brush->numsides; i++) + { + if (brush->sides[i].texinfo == TEXINFO_NODE) + continue; // on node, don't worry about splits + if (!(brush->sides[i].flags & SFL_VISIBLE)) + continue; // we don't care about non-visible + w = brush->sides[i].winding; + if (!w) continue; + front = back = 0; + for (j = 0; j < w->numpoints; j++) + { + d = DotProduct(w->p[j], plane->normal) - plane->dist; + if (d > d_front) d_front = d; + if (d < d_back) d_back = d; + if (d > 0.1) // PLANESIDE_EPSILON) + front = 1; + if (d < -0.1) // PLANESIDE_EPSILON) + back = 1; + } //end for + if (front && back) + { + if ( !(brush->sides[i].surf & SURF_SKIP) ) + { + (*numsplits)++; + if (brush->sides[i].surf & SURF_HINT) + { + *hintsplit = true; + } //end if + } //end if + } //end if + } //end for + + if ( (d_front > 0.0 && d_front < 1.0) + || (d_back < 0.0 && d_back > -1.0) ) + (*epsilonbrush)++; + +#if 0 + if (*numsplits == 0) + { // didn't really need to be split + if (front) s = PSIDE_FRONT; + else if (back) s = PSIDE_BACK; + else s = 0; + } +#endif + + return s; +} //end of the function TestBrushToPlanenum +//=========================================================================== +// Returns true if the winding would be crunched out of +// existance by the vertex snapping. +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#define EDGE_LENGTH 0.2 +qboolean WindingIsTiny (winding_t *w) +{ +#if 0 + if (WindingArea (w) < 1) + return true; + return false; +#else + int i, j; + vec_t len; + vec3_t delta; + int edges; + + edges = 0; + for (i=0 ; inumpoints ; i++) + { + j = i == w->numpoints - 1 ? 0 : i+1; + VectorSubtract (w->p[j], w->p[i], delta); + len = VectorLength (delta); + if (len > EDGE_LENGTH) + { + if (++edges == 3) + return false; + } + } + return true; +#endif +} //end of the function WindingIsTiny +//=========================================================================== +// Returns true if the winding still has one of the points +// from basewinding for plane +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean WindingIsHuge (winding_t *w) +{ + int i, j; + + for (i=0 ; inumpoints ; i++) + { + for (j=0 ; j<3 ; j++) + if (w->p[i][j] < -BOGUS_RANGE+1 || w->p[i][j] > BOGUS_RANGE-1) + return true; + } + return false; +} //end of the function WindingIsHuge +//=========================================================================== +// creates a leaf out of the given nodes with the given brushes +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void LeafNode(node_t *node, bspbrush_t *brushes) +{ + bspbrush_t *b; + int i; + + node->side = NULL; + node->planenum = PLANENUM_LEAF; + node->contents = 0; + + for (b = brushes; b; b = b->next) + { + // if the brush is solid and all of its sides are on nodes, + // it eats everything + if (b->original->contents & CONTENTS_SOLID) + { + for (i=0 ; inumsides ; i++) + if (b->sides[i].texinfo != TEXINFO_NODE) + break; + if (i == b->numsides) + { + node->contents = CONTENTS_SOLID; + break; + } //end if + } //end if + node->contents |= b->original->contents; + } //end for + + if (create_aas) + { + node->expansionbboxes = 0; + node->contents = 0; + for (b = brushes; b; b = b->next) + { + node->expansionbboxes |= b->original->expansionbbox; + node->contents |= b->original->contents; + if (b->original->modelnum) + node->modelnum = b->original->modelnum; + } //end for + if (node->contents & CONTENTS_SOLID) + { + if (node->expansionbboxes != cfg.allpresencetypes) + { + node->contents &= ~CONTENTS_SOLID; + } //end if + } //end if + } //end if + + node->brushlist = brushes; +} //end of the function LeafNode +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void CheckPlaneAgainstParents (int pnum, node_t *node) +{ + node_t *p; + + for (p = node->parent; p; p = p->parent) + { + if (p->planenum == pnum) Error("Tried parent"); + } //end for +} //end of the function CheckPlaneAgainstParants +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean CheckPlaneAgainstVolume (int pnum, node_t *node) +{ + bspbrush_t *front, *back; + qboolean good; + + SplitBrush (node->volume, pnum, &front, &back); + + good = (front && back); + + if (front) FreeBrush (front); + if (back) FreeBrush (back); + + return good; +} //end of the function CheckPlaneAgaintsVolume +//=========================================================================== +// Using a hueristic, choses one of the sides out of the brushlist +// to partition the brushes with. +// Returns NULL if there are no valid planes to split with.. +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +side_t *SelectSplitSide (bspbrush_t *brushes, node_t *node) +{ + int value, bestvalue; + bspbrush_t *brush, *test; + side_t *side, *bestside; + int i, pass, numpasses; + int pnum; + int s; + int front, back, both, facing, splits; + int bsplits; + int bestsplits; + int epsilonbrush; + qboolean hintsplit = false; + + bestside = NULL; + bestvalue = -99999; + bestsplits = 0; + + // the search order goes: visible-structural, visible-detail, + // nonvisible-structural, nonvisible-detail. + // If any valid plane is available in a pass, no further + // passes will be tried. + numpasses = 2; + for (pass = 0; pass < numpasses; pass++) + { + for (brush = brushes; brush; brush = brush->next) + { + // only check detail the second pass +// if ( (pass & 1) && !(brush->original->contents & CONTENTS_DETAIL) ) +// continue; +// if ( !(pass & 1) && (brush->original->contents & CONTENTS_DETAIL) ) +// continue; + for (i = 0; i < brush->numsides; i++) + { + side = brush->sides + i; +// if (side->flags & SFL_BEVEL) +// continue; // never use a bevel as a spliter + if (!side->winding) + continue; // nothing visible, so it can't split + if (side->texinfo == TEXINFO_NODE) + continue; // allready a node splitter + if (side->flags & SFL_TESTED) + continue; // we allready have metrics for this plane +// if (side->surf & SURF_SKIP) +// continue; // skip surfaces are never chosen + +// if (!(side->flags & SFL_VISIBLE) && (pass < 2)) +// continue; // only check visible faces on first pass + + if ((side->flags & SFL_CURVE) && (pass < 1)) + continue; // only check curves the second pass + + pnum = side->planenum; + pnum &= ~1; // allways use positive facing plane + + CheckPlaneAgainstParents (pnum, node); + + if (!CheckPlaneAgainstVolume (pnum, node)) + continue; // would produce a tiny volume + + front = 0; + back = 0; + both = 0; + facing = 0; + splits = 0; + epsilonbrush = 0; + + //inner loop: optimize + for (test = brushes; test; test = test->next) + { + s = TestBrushToPlanenum (test, pnum, &bsplits, &hintsplit, &epsilonbrush); + + splits += bsplits; +// if (bsplits && (s&PSIDE_FACING) ) +// Error ("PSIDE_FACING with splits"); + + test->testside = s; + // + if (s & PSIDE_FACING) facing++; + if (s & PSIDE_FRONT) front++; + if (s & PSIDE_BACK) back++; + if (s == PSIDE_BOTH) both++; + } //end for + + // give a value estimate for using this plane + value = 5*facing - 5*splits - abs(front-back); +// value = -5*splits; +// value = 5*facing - 5*splits; + if (mapplanes[pnum].type < 3) + value+=5; // axial is better + + value -= epsilonbrush * 1000; // avoid! + + // never split a hint side except with another hint + if (hintsplit && !(side->surf & SURF_HINT) ) + value = -9999999; + + // save off the side test so we don't need + // to recalculate it when we actually seperate + // the brushes + if (value > bestvalue) + { + bestvalue = value; + bestside = side; + bestsplits = splits; + for (test = brushes; test ; test = test->next) + test->side = test->testside; + } //end if + } //end for + } //end for (brush = brushes; + + // if we found a good plane, don't bother trying any + // other passes + if (bestside) + { + if (pass > 1) + { + if (numthreads == 1) c_nonvis++; + } + if (pass > 0) node->detail_seperator = true; // not needed for vis + break; + } //end if + } //end for (pass = 0; + + // + // clear all the tested flags we set + // + for (brush = brushes ; brush ; brush=brush->next) + { + for (i = 0; i < brush->numsides; i++) + { + brush->sides[i].flags &= ~SFL_TESTED; + } //end for + } //end for + + return bestside; +} //end of the function SelectSplitSide +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BrushMostlyOnSide (bspbrush_t *brush, plane_t *plane) +{ + int i, j; + winding_t *w; + vec_t d, max; + int side; + + max = 0; + side = PSIDE_FRONT; + for (i=0 ; inumsides ; i++) + { + w = brush->sides[i].winding; + if (!w) + continue; + for (j=0 ; jnumpoints ; j++) + { + d = DotProduct (w->p[j], plane->normal) - plane->dist; + if (d > max) + { + max = d; + side = PSIDE_FRONT; + } + if (-d > max) + { + max = -d; + side = PSIDE_BACK; + } + } + } + return side; +} //end of the function BrushMostlyOnSide +//=========================================================================== +// Generates two new brushes, leaving the original +// unchanged +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void SplitBrush (bspbrush_t *brush, int planenum, + bspbrush_t **front, bspbrush_t **back) +{ + bspbrush_t *b[2]; + int i, j; + winding_t *w, *cw[2], *midwinding; + plane_t *plane, *plane2; + side_t *s, *cs; + float d, d_front, d_back; + + *front = *back = NULL; + plane = &mapplanes[planenum]; + + // check all points + d_front = d_back = 0; + for (i=0 ; inumsides ; i++) + { + w = brush->sides[i].winding; + if (!w) + continue; + for (j=0 ; jnumpoints ; j++) + { + d = DotProduct (w->p[j], plane->normal) - plane->dist; + if (d > 0 && d > d_front) + d_front = d; + if (d < 0 && d < d_back) + d_back = d; + } + } + + if (d_front < 0.2) // PLANESIDE_EPSILON) + { // only on back + *back = CopyBrush (brush); + return; + } + if (d_back > -0.2) // PLANESIDE_EPSILON) + { // only on front + *front = CopyBrush (brush); + return; + } + + // create a new winding from the split plane + + w = BaseWindingForPlane (plane->normal, plane->dist); + for (i=0 ; inumsides && w ; i++) + { + plane2 = &mapplanes[brush->sides[i].planenum ^ 1]; + ChopWindingInPlace (&w, plane2->normal, plane2->dist, 0); // PLANESIDE_EPSILON); + } + + if (!w || WindingIsTiny(w)) + { // the brush isn't really split + int side; + + side = BrushMostlyOnSide (brush, plane); + if (side == PSIDE_FRONT) + *front = CopyBrush (brush); + if (side == PSIDE_BACK) + *back = CopyBrush (brush); + //free a possible winding + if (w) FreeWinding(w); + return; + } + + if (WindingIsHuge (w)) + { + Log_Write("WARNING: huge winding\n"); + } + + midwinding = w; + + // split it for real + + for (i=0 ; i<2 ; i++) + { + b[i] = AllocBrush (brush->numsides+1); + b[i]->original = brush->original; + } + + // split all the current windings + + for (i=0 ; inumsides ; i++) + { + s = &brush->sides[i]; + w = s->winding; + if (!w) + continue; + ClipWindingEpsilon (w, plane->normal, plane->dist, + 0 /*PLANESIDE_EPSILON*/, &cw[0], &cw[1]); + for (j=0 ; j<2 ; j++) + { + if (!cw[j]) + continue; +#if 0 + if (WindingIsTiny (cw[j])) + { + FreeWinding (cw[j]); + continue; + } +#endif + cs = &b[j]->sides[b[j]->numsides]; + b[j]->numsides++; + *cs = *s; +// cs->planenum = s->planenum; +// cs->texinfo = s->texinfo; +// cs->original = s->original; + cs->winding = cw[j]; + cs->flags &= ~SFL_TESTED; + } + } + + + // see if we have valid polygons on both sides + + for (i=0 ; i<2 ; i++) + { + BoundBrush (b[i]); + for (j=0 ; j<3 ; j++) + { + if (b[i]->mins[j] < -MAX_MAP_BOUNDS || b[i]->maxs[j] > MAX_MAP_BOUNDS) + { + Log_Write("bogus brush after clip"); + break; + } + } + + if (b[i]->numsides < 3 || j < 3) + { + FreeBrush (b[i]); + b[i] = NULL; + } + } + + if ( !(b[0] && b[1]) ) + { + if (!b[0] && !b[1]) + Log_Write("split removed brush\r\n"); + else + Log_Write("split not on both sides\r\n"); + if (b[0]) + { + FreeBrush (b[0]); + *front = CopyBrush (brush); + } + if (b[1]) + { + FreeBrush (b[1]); + *back = CopyBrush (brush); + } + return; + } + + // add the midwinding to both sides + for (i=0 ; i<2 ; i++) + { + cs = &b[i]->sides[b[i]->numsides]; + b[i]->numsides++; + + cs->planenum = planenum^i^1; + cs->texinfo = TEXINFO_NODE; //never use these sides as splitters + cs->flags &= ~SFL_VISIBLE; + cs->flags &= ~SFL_TESTED; + if (i==0) + cs->winding = CopyWinding (midwinding); + else + cs->winding = midwinding; + } + +{ + vec_t v1; + int i; + + for (i = 0; i < 2; i++) + { + v1 = BrushVolume (b[i]); + if (v1 < 1.0) + { + FreeBrush(b[i]); + b[i] = NULL; + //Log_Write("tiny volume after clip"); + } + } + if (!b[0] && !b[1]) + { + Log_Write("two tiny brushes\r\n"); + } //end if +} + + *front = b[0]; + *back = b[1]; +} //end of the function SplitBrush +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void SplitBrushList (bspbrush_t *brushes, + node_t *node, bspbrush_t **front, bspbrush_t **back) +{ + bspbrush_t *brush, *newbrush, *newbrush2; + side_t *side; + int sides; + int i; + + *front = *back = NULL; + + for (brush = brushes; brush; brush = brush->next) + { + sides = brush->side; + + if (sides == PSIDE_BOTH) + { // split into two brushes + SplitBrush (brush, node->planenum, &newbrush, &newbrush2); + if (newbrush) + { + newbrush->next = *front; + *front = newbrush; + } //end if + if (newbrush2) + { + newbrush2->next = *back; + *back = newbrush2; + } //end if + continue; + } //end if + + newbrush = CopyBrush (brush); + + // if the planenum is actualy a part of the brush + // find the plane and flag it as used so it won't be tried + // as a splitter again + if (sides & PSIDE_FACING) + { + for (i=0 ; inumsides ; i++) + { + side = newbrush->sides + i; + if ( (side->planenum& ~1) == node->planenum) + side->texinfo = TEXINFO_NODE; + } //end for + } //end if + if (sides & PSIDE_FRONT) + { + newbrush->next = *front; + *front = newbrush; + continue; + } //end if + if (sides & PSIDE_BACK) + { + newbrush->next = *back; + *back = newbrush; + continue; + } //end if + } //end for +} //end of the function SplitBrushList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void CheckBrushLists(bspbrush_t *brushlist1, bspbrush_t *brushlist2) +{ + bspbrush_t *brush1, *brush2; + + for (brush1 = brushlist1; brush1; brush1 = brush1->next) + { + for (brush2 = brushlist2; brush2; brush2 = brush2->next) + { + assert(brush1 != brush2); + } //end for + } //end for +} //end of the function CheckBrushLists +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int numrecurse = 0; + +node_t *BuildTree_r (node_t *node, bspbrush_t *brushes) +{ + node_t *newnode; + side_t *bestside; + int i, totalmem; + bspbrush_t *children[2]; + + qprintf("\r%6d", numrecurse); + numrecurse++; + + if (numthreads == 1) + { + totalmem = WindingMemory() + c_nodememory + c_brushmemory; + if (totalmem > c_peak_totalbspmemory) + c_peak_totalbspmemory = totalmem; + c_nodes++; + } //endif + + if (drawflag) + DrawBrushList(brushes, node); + + // find the best plane to use as a splitter + bestside = SelectSplitSide (brushes, node); + if (!bestside) + { + // leaf node + node->side = NULL; + node->planenum = -1; + LeafNode(node, brushes); + if (node->contents & CONTENTS_SOLID) c_solidleafnodes++; + if (create_aas) + { + //free up memory!!! + FreeBrushList(node->brushlist); + node->brushlist = NULL; + //free the node volume brush + if (node->volume) + { + FreeBrush(node->volume); + node->volume = NULL; + } //end if + } //end if + return node; + } //end if + + // this is a splitplane node + node->side = bestside; + node->planenum = bestside->planenum & ~1; // always use front facing + + //split the brush list in two for both children + SplitBrushList (brushes, node, &children[0], &children[1]); + //free the old brush list + FreeBrushList (brushes); + + // allocate children before recursing + for (i = 0; i < 2; i++) + { + newnode = AllocNode (); + newnode->parent = node; + node->children[i] = newnode; + } //end for + + //split the volume brush of the node for the children + SplitBrush (node->volume, node->planenum, &node->children[0]->volume, + &node->children[1]->volume); + + if (create_aas) + { + //free the volume brush + if (node->volume) + { + FreeBrush(node->volume); + node->volume = NULL; + } //end if + } //end if + // recursively process children + for (i = 0; i < 2; i++) + { + node->children[i] = BuildTree_r(node->children[i], children[i]); + } //end for + + return node; +} //end of the function BuildTree_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +node_t *firstnode; //first node in the list +node_t *lastnode; //last node in the list +int nodelistsize; //number of nodes in the list +int use_nodequeue = 0; //use nodequeue, otherwise a node stack is used +int numwaiting = 0; + +void (*AddNodeToList)(node_t *node); + +//add the node to the front of the node list +//(effectively using a node stack) +void AddNodeToStack(node_t *node) +{ + ThreadLock(); + + node->next = firstnode; + firstnode = node; + if (!lastnode) lastnode = node; + nodelistsize++; + + ThreadUnlock(); + // + ThreadSemaphoreIncrease(1); +} //end of the function AddNodeToStack +//add the node to the end of the node list +//(effectively using a node queue) +void AddNodeToQueue(node_t *node) +{ + ThreadLock(); + + node->next = NULL; + if (lastnode) lastnode->next = node; + else firstnode = node; + lastnode = node; + nodelistsize++; + + ThreadUnlock(); + // + ThreadSemaphoreIncrease(1); +} //end of the function AddNodeToQueue +//get the first node from the front of the node list +node_t *NextNodeFromList(void) +{ + node_t *node; + + ThreadLock(); + numwaiting++; + if (!firstnode) + { + if (numwaiting >= GetNumThreads()) ThreadSemaphoreIncrease(GetNumThreads()); + } //end if + ThreadUnlock(); + + ThreadSemaphoreWait(); + + ThreadLock(); + + numwaiting--; + + node = firstnode; + if (firstnode) + { + firstnode = firstnode->next; + nodelistsize--; + } //end if + if (!firstnode) lastnode = NULL; + + ThreadUnlock(); + + return node; +} //end of the function NextNodeFromList +//returns the size of the node list +int NodeListSize(void) +{ + int size; + + ThreadLock(); + size = nodelistsize; + ThreadUnlock(); + + return size; +} //end of the function NodeListSize +// +void IncreaseNodeCounter(void) +{ + ThreadLock(); + //if (verbose) printf("\r%6d", numrecurse++); + qprintf("\r%6d", numrecurse++); + //qprintf("\r%6d %d, %5d ", numrecurse++, GetNumThreads(), nodelistsize); + ThreadUnlock(); +} //end of the function IncreaseNodeCounter +//thread function, gets nodes from the nodelist and processes them +void BuildTreeThread(int threadid) +{ + node_t *newnode, *node; + side_t *bestside; + int i, totalmem; + bspbrush_t *brushes; + + for (node = NextNodeFromList(); node; ) + { + //if the nodelist isn't empty try to add another thread + //if (NodeListSize() > 10) AddThread(BuildTreeThread); + //display the number of nodes processed so far + if (numthreads == 1) + IncreaseNodeCounter(); + + brushes = node->brushlist; + + if (numthreads == 1) + { + totalmem = WindingMemory() + c_nodememory + c_brushmemory; + if (totalmem > c_peak_totalbspmemory) + { + c_peak_totalbspmemory = totalmem; + } //end if + c_nodes++; + } //endif + + if (drawflag) + { + DrawBrushList(brushes, node); + } //end if + + if (cancelconversion) + { + bestside = NULL; + } //end if + else + { + // find the best plane to use as a splitter + bestside = SelectSplitSide(brushes, node); + } //end else + //if there's no split side left + if (!bestside) + { + //create a leaf out of the node + LeafNode(node, brushes); + if (node->contents & CONTENTS_SOLID) c_solidleafnodes++; + if (create_aas) + { + //free up memory!!! + FreeBrushList(node->brushlist); + node->brushlist = NULL; + } //end if + //free the node volume brush (it is not used anymore) + if (node->volume) + { + FreeBrush(node->volume); + node->volume = NULL; + } //end if + node = NextNodeFromList(); + continue; + } //end if + + // this is a splitplane node + node->side = bestside; + node->planenum = bestside->planenum & ~1; //always use front facing + + //allocate children + for (i = 0; i < 2; i++) + { + newnode = AllocNode(); + newnode->parent = node; + node->children[i] = newnode; + } //end for + + //split the brush list in two for both children + SplitBrushList(brushes, node, &node->children[0]->brushlist, &node->children[1]->brushlist); + + CheckBrushLists(node->children[0]->brushlist, node->children[1]->brushlist); + //free the old brush list + FreeBrushList(brushes); + node->brushlist = NULL; + + //split the volume brush of the node for the children + SplitBrush(node->volume, node->planenum, &node->children[0]->volume, + &node->children[1]->volume); + + if (!node->children[0]->volume || !node->children[1]->volume) + { + Error("child without volume brush"); + } //end if + + //free the volume brush + if (node->volume) + { + FreeBrush(node->volume); + node->volume = NULL; + } //end if + //add both children to the node list + //AddNodeToList(node->children[0]); + AddNodeToList(node->children[1]); + node = node->children[0]; + } //end while + RemoveThread(threadid); +} //end of the function BuildTreeThread +//=========================================================================== +// build the bsp tree using a node list +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BuildTree(tree_t *tree) +{ + int i; + + firstnode = NULL; + lastnode = NULL; + //use a node queue or node stack + if (use_nodequeue) AddNodeToList = AddNodeToQueue; + else AddNodeToList = AddNodeToStack; + //setup thread locking + ThreadSetupLock(); + ThreadSetupSemaphore(); + numwaiting = 0; + // + Log_Print("%6d threads max\n", numthreads); + if (use_nodequeue) Log_Print("breadth first bsp building\n"); + else Log_Print("depth first bsp building\n"); + qprintf("%6d splits", 0); + //add the first node to the list + AddNodeToList(tree->headnode); + //start the threads + for (i = 0; i < numthreads; i++) + AddThread(BuildTreeThread); + //wait for all added threads to be finished + WaitForAllThreadsFinished(); + //shutdown the thread locking + ThreadShutdownLock(); + ThreadShutdownSemaphore(); +} //end of the function BuildTree +//=========================================================================== +// The incoming brush list will be freed before exiting +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +tree_t *BrushBSP(bspbrush_t *brushlist, vec3_t mins, vec3_t maxs) +{ + int i, c_faces, c_nonvisfaces, c_brushes; + bspbrush_t *b; + node_t *node; + tree_t *tree; + vec_t volume; +// vec3_t point; + + Log_Print("-------- Brush BSP ---------\n"); + + tree = Tree_Alloc(); + + c_faces = 0; + c_nonvisfaces = 0; + c_brushes = 0; + c_totalsides = 0; + for (b = brushlist; b; b = b->next) + { + c_brushes++; + + volume = BrushVolume(b); + if (volume < microvolume) + { + Log_Print("WARNING: entity %i, brush %i: microbrush\n", + b->original->entitynum, b->original->brushnum); + } //end if + + for (i=0 ; inumsides ; i++) + { + if (b->sides[i].flags & SFL_BEVEL) + continue; + if (!b->sides[i].winding) + continue; + if (b->sides[i].texinfo == TEXINFO_NODE) + continue; + if (b->sides[i].flags & SFL_VISIBLE) + { + c_faces++; + } //end if + else + { + c_nonvisfaces++; + //if (create_aas) b->sides[i].texinfo = TEXINFO_NODE; + } //end if + } //end for + c_totalsides += b->numsides; + + AddPointToBounds (b->mins, tree->mins, tree->maxs); + AddPointToBounds (b->maxs, tree->mins, tree->maxs); + } //end for + + Log_Print("%6i brushes\n", c_brushes); + Log_Print("%6i visible faces\n", c_faces); + Log_Print("%6i nonvisible faces\n", c_nonvisfaces); + Log_Print("%6i total sides\n", c_totalsides); + + c_active_brushes = c_brushes; + c_nodememory = 0; + c_brushmemory = 0; + c_peak_brushmemory = 0; + + c_nodes = 0; + c_nonvis = 0; + node = AllocNode (); + + //volume of first node (head node) + node->volume = BrushFromBounds (mins, maxs); + // + tree->headnode = node; + //just get some statistics and the mins/maxs of the node + numrecurse = 0; +// qprintf("%6d splits", numrecurse); + + tree->headnode->brushlist = brushlist; + BuildTree(tree); + + //build the bsp tree with the start node from the brushlist +// node = BuildTree_r(node, brushlist); + + //if the conversion is cancelled + if (cancelconversion) return tree; + + qprintf("\n"); + Log_Write("%6d splits\r\n", numrecurse); +// Log_Print("%6i visible nodes\n", c_nodes/2 - c_nonvis); +// Log_Print("%6i nonvis nodes\n", c_nonvis); +// Log_Print("%6i leaves\n", (c_nodes+1)/2); +// Log_Print("%6i solid leaf nodes\n", c_solidleafnodes); +// Log_Print("%6i active brushes\n", c_active_brushes); + if (numthreads == 1) + { +// Log_Print("%6i KB of node memory\n", c_nodememory >> 10); +// Log_Print("%6i KB of brush memory\n", c_brushmemory >> 10); +// Log_Print("%6i KB of peak brush memory\n", c_peak_brushmemory >> 10); +// Log_Print("%6i KB of winding memory\n", WindingMemory() >> 10); +// Log_Print("%6i KB of peak winding memory\n", WindingPeakMemory() >> 10); + Log_Print("%6i KB of peak total bsp memory\n", c_peak_totalbspmemory >> 10); + } //end if + + /* + point[0] = 1485; + point[1] = 956.125; + point[2] = 352.125; + node = PointInLeaf(tree->headnode, point); + if (node->planenum != PLANENUM_LEAF) + { + Log_Print("node not a leaf\n"); + } //end if + Log_Print("at %f %f %f:\n", point[0], point[1], point[2]); + PrintContents(node->contents); + Log_Print("node->expansionbboxes = %d\n", node->expansionbboxes); + //*/ + return tree; +} //end of the function BrushBSP + diff --git a/code/bspc/bspc.c b/code/bspc/bspc.c index 4f58b53..e19076e 100755 --- a/code/bspc/bspc.c +++ b/code/bspc/bspc.c @@ -1,991 +1,991 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#if defined(WIN32) || defined(_WIN32) -#include -#include -#include -#include -#else -#include -#include -#include -#include -#endif -#include "qbsp.h" -#include "l_mem.h" -#include "../botlib/aasfile.h" -#include "../botlib/be_aas_cluster.h" -#include "../botlib/be_aas_optimize.h" -#include "aas_create.h" -#include "aas_store.h" -#include "aas_file.h" -#include "aas_cfg.h" -#include "be_aas_bspc.h" - -extern int use_nodequeue; //brushbsp.c -extern int calcgrapplereach; //be_aas_reach.c - -float subdivide_size = 240; -char source[1024]; -char name[1024]; -vec_t microvolume = 1.0; -char outbase[32]; -int entity_num; -aas_settings_t aassettings; - -qboolean noprune; //don't prune nodes (bspc.c) -qboolean glview; //create a gl view -qboolean nodetail; //don't use detail brushes (map.c) -qboolean fulldetail; //use but don't mark detail brushes (map.c) -qboolean onlyents; //only process the entities (bspc.c) -qboolean nomerge; //don't merge bsp node faces (faces.c) -qboolean nowater; //don't use the water brushes (map.c) -qboolean nocsg; //don't carve intersecting brushes (bspc.c) -qboolean noweld; //use unique face vertexes (faces.c) -qboolean noshare; //don't share bsp edges (faces.c) -qboolean nosubdiv; //don't subdivide bsp node faces (faces.c) -qboolean notjunc; //don't create tjunctions (edge melting) (faces.c) -qboolean optimize; //enable optimisation -qboolean leaktest; //perform a leak test -qboolean verboseentities; -qboolean freetree; //free the bsp tree when not needed anymore -qboolean create_aas; //create an .AAS file -qboolean nobrushmerge; //don't merge brushes -qboolean lessbrushes; //create less brushes instead of correct texture placement -qboolean cancelconversion; //true if the conversion is being cancelled -qboolean noliquids; //no liquids when writing map file -qboolean forcesidesvisible; //force all brush sides to be visible when loaded from bsp -qboolean capsule_collision = 0; - -/* -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ProcessWorldModel (void) -{ - entity_t *e; - tree_t *tree; - qboolean leaked; - int brush_start, brush_end; - - e = &entities[entity_num]; - - brush_start = e->firstbrush; - brush_end = brush_start + e->numbrushes; - leaked = false; - - //process the whole world in one time - tree = ProcessWorldBrushes(brush_start, brush_end); - //create the bsp tree portals - MakeTreePortals(tree); - //mark all leafs that can be reached by entities - if (FloodEntities(tree)) - { - FillOutside(tree->headnode); - } //end if - else - { - Log_Print("**** leaked ****\n"); - leaked = true; - LeakFile(tree); - if (leaktest) - { - Log_Print("--- MAP LEAKED ---\n"); - exit(0); - } //end if - } //end else - - MarkVisibleSides (tree, brush_start, brush_end); - - FloodAreas (tree); - -#ifndef ME - if (glview) WriteGLView(tree, source); -#endif - MakeFaces(tree->headnode); - FixTjuncs(tree->headnode); - - //NOTE: Never prune the nodes because the portals - // are screwed when prunning is done and as - // a result portal writing will crash - //if (!noprune) PruneNodes(tree->headnode); - - WriteBSP(tree->headnode); - - if (!leaked) WritePortalFile(tree); - - Tree_Free(tree); -} //end of the function ProcessWorldModel -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ProcessSubModel (void) -{ - entity_t *e; - int start, end; - tree_t *tree; - bspbrush_t *list; - vec3_t mins, maxs; - - e = &entities[entity_num]; - - start = e->firstbrush; - end = start + e->numbrushes; - - mins[0] = mins[1] = mins[2] = -4096; - maxs[0] = maxs[1] = maxs[2] = 4096; - list = MakeBspBrushList(start, end, mins, maxs); - if (!nocsg) list = ChopBrushes (list); - tree = BrushBSP (list, mins, maxs); - MakeTreePortals (tree); - MarkVisibleSides (tree, start, end); - MakeFaces (tree->headnode); - FixTjuncs (tree->headnode); - WriteBSP (tree->headnode); - Tree_Free(tree); -} //end of the function ProcessSubModel -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ProcessModels (void) -{ - BeginBSPFile(); - - for (entity_num = 0; entity_num < num_entities; entity_num++) - { - if (!entities[entity_num].numbrushes) - continue; - - Log_Print("############### model %i ###############\n", nummodels); - BeginModel(); - if (entity_num == 0) ProcessWorldModel(); - else ProcessSubModel(); - EndModel(); - - if (!verboseentities) - verbose = false; // don't bother printing submodels - } //end for - EndBSPFile(); -} //end of the function ProcessModels -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Win_Map2Bsp(char *bspfilename) -{ - double start, end; - char path[1024]; - - start = I_FloatTime(); - - ThreadSetDefault(); - //yeah sure Carmack - //numthreads = 1; // multiple threads aren't helping... - - strcpy(source, ExpandArg(bspfilename)); - StripExtension(source); - - //delete portal and line files - sprintf(path, "%s.prt", source); - remove(path); - sprintf(path, "%s.lin", source); - remove(path); - - strcpy(name, ExpandArg(bspfilename)); - DefaultExtension(name, ".map"); // might be .reg - - Q2_AllocMaxBSP(); - // - SetModelNumbers(); - SetLightStyles(); - ProcessModels(); - //write the BSP - Q2_WriteBSPFile(bspfilename); - - Q2_FreeMaxBSP(); - - end = I_FloatTime(); - Log_Print("%5.0f seconds elapsed\n", end-start); -} //end of the function Win_Map2Bsp -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Map2Bsp(char *mapfilename, char *outputfilename) -{ - double start, end; - char path[1024]; - - start = I_FloatTime (); - - ThreadSetDefault (); - //yeah sure Carmack - //numthreads = 1; //multiple threads aren't helping... - //SetQdirFromPath(bspfilename); - - strcpy(source, ExpandArg(mapfilename)); - StripExtension(source); - - // delete portal and line files - sprintf(path, "%s.prt", source); - remove(path); - sprintf(path, "%s.lin", source); - remove(path); - - strcpy(name, ExpandArg(mapfilename)); - DefaultExtension(name, ".map"); // might be .reg - - // - // if onlyents, just grab the entites and resave - // - if (onlyents) - { - char out[1024]; - - Q2_AllocMaxBSP(); - sprintf (out, "%s.bsp", source); - Q2_LoadBSPFile(out, 0, 0); - num_entities = 0; - - Q2_LoadMapFile(name); - SetModelNumbers(); - SetLightStyles(); - - Q2_UnparseEntities(); - - Q2_WriteBSPFile(out); - // - Q2_FreeMaxBSP(); - } //end if - else - { - // - // start from scratch - // - Q2_AllocMaxBSP(); - //load the map - Q2_LoadMapFile(name); - //create the .bsp file - SetModelNumbers(); - SetLightStyles(); - ProcessModels(); - //write the BSP - Q2_WriteBSPFile(outputfilename); - // - Q2_FreeMaxBSP(); - } //end else - - end = I_FloatTime(); - Log_Print("%5.0f seconds elapsed\n", end-start); -} //end of the function Map2Bsp -*/ -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AASOuputFile(quakefile_t *qf, char *outputpath, char *filename) -{ - char ext[MAX_PATH]; - - // - if (strlen(outputpath)) - { - strcpy(filename, outputpath); - //append the bsp file base - AppendPathSeperator(filename, MAX_PATH); - ExtractFileBase(qf->origname, &filename[strlen(filename)]); - //append .aas - strcat(filename, ".aas"); - return; - } //end if - // - ExtractFileExtension(qf->filename, ext); - if (!stricmp(ext, "pk3") || !stricmp(ext, "pak") || !stricmp(ext, "sin")) - { - strcpy(filename, qf->filename); - while(strlen(filename) && - filename[strlen(filename)-1] != '\\' && - filename[strlen(filename)-1] != '/') - { - filename[strlen(filename)-1] = '\0'; - } //end while - strcat(filename, "maps"); - if (access(filename, 0x04)) CreatePath(filename); - //append the bsp file base - AppendPathSeperator(filename, MAX_PATH); - ExtractFileBase(qf->origname, &filename[strlen(filename)]); - //append .aas - strcat(filename, ".aas"); - } //end if - else - { - strcpy(filename, qf->filename); - while(strlen(filename) && - filename[strlen(filename)-1] != '.') - { - filename[strlen(filename)-1] = '\0'; - } //end while - strcat(filename, "aas"); - } //end else -} //end of the function AASOutputFile -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void CreateAASFilesForAllBSPFiles(char *quakepath) -{ -#if defined(WIN32)|defined(_WIN32) - WIN32_FIND_DATA filedata; - HWND handle; - struct _stat statbuf; -#else - glob_t globbuf; - struct stat statbuf; - int j; -#endif - int done; - char filter[_MAX_PATH], bspfilter[_MAX_PATH], aasfilter[_MAX_PATH]; - char aasfile[_MAX_PATH], buf[_MAX_PATH], foldername[_MAX_PATH]; - quakefile_t *qf, *qf2, *files, *bspfiles, *aasfiles; - - strcpy(filter, quakepath); - AppendPathSeperator(filter, sizeof(filter)); - strcat(filter, "*"); - -#if defined(WIN32)|defined(_WIN32) - handle = FindFirstFile(filter, &filedata); - done = (handle == INVALID_HANDLE_VALUE); - while(!done) - { - _splitpath(filter, foldername, NULL, NULL, NULL); - _splitpath(filter, NULL, &foldername[strlen(foldername)], NULL, NULL); - AppendPathSeperator(foldername, _MAX_PATH); - strcat(foldername, filedata.cFileName); - _stat(foldername, &statbuf); -#else - glob(filter, 0, NULL, &globbuf); - for (j = 0; j < globbuf.gl_pathc; j++) - { - strcpy(foldername, globbuf.gl_pathv[j]); - stat(foldername, &statbuf); -#endif - //if it is a folder - if (statbuf.st_mode & S_IFDIR) - { - // - AppendPathSeperator(foldername, sizeof(foldername)); - //get all the bsp files - strcpy(bspfilter, foldername); - strcat(bspfilter, "maps/*.bsp"); - files = FindQuakeFiles(bspfilter); - strcpy(bspfilter, foldername); - strcat(bspfilter, "*.pk3/maps/*.bsp"); - bspfiles = FindQuakeFiles(bspfilter); - for (qf = bspfiles; qf; qf = qf->next) if (!qf->next) break; - if (qf) qf->next = files; - else bspfiles = files; - //get all the aas files - strcpy(aasfilter, foldername); - strcat(aasfilter, "maps/*.aas"); - files = FindQuakeFiles(aasfilter); - strcpy(aasfilter, foldername); - strcat(aasfilter, "*.pk3/maps/*.aas"); - aasfiles = FindQuakeFiles(aasfilter); - for (qf = aasfiles; qf; qf = qf->next) if (!qf->next) break; - if (qf) qf->next = files; - else aasfiles = files; - // - for (qf = bspfiles; qf; qf = qf->next) - { - sprintf(aasfile, "%s/%s", qf->pakfile, qf->origname); - Log_Print("found %s\n", aasfile); - strcpy(&aasfile[strlen(aasfile)-strlen(".bsp")], ".aas"); - for (qf2 = aasfiles; qf2; qf2 = qf2->next) - { - sprintf(buf, "%s/%s", qf2->pakfile, qf2->origname); - if (!stricmp(aasfile, buf)) - { - Log_Print("found %s\n", buf); - break; - } //end if - } //end for - } //end for - } //end if -#if defined(WIN32)|defined(_WIN32) - //find the next file - done = !FindNextFile(handle, &filedata); - } //end while -#else - } //end for - globfree(&globbuf); -#endif -} //end of the function CreateAASFilesForAllBSPFiles -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -quakefile_t *GetArgumentFiles(int argc, char *argv[], int *i, char *ext) -{ - quakefile_t *qfiles, *lastqf, *qf; - int j; - char buf[1024]; - - qfiles = NULL; - lastqf = NULL; - for (; (*i)+1 < argc && argv[(*i)+1][0] != '-'; (*i)++) - { - strcpy(buf, argv[(*i)+1]); - for (j = strlen(buf)-1; j >= strlen(buf)-4; j--) - if (buf[j] == '.') break; - if (j >= strlen(buf)-4) - strcpy(&buf[j+1], ext); - qf = FindQuakeFiles(buf); - if (!qf) continue; - if (lastqf) lastqf->next = qf; - else qfiles = qf; - lastqf = qf; - while(lastqf->next) lastqf = lastqf->next; - } //end for - return qfiles; -} //end of the function GetArgumentFiles -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== - -#define COMP_BSP2MAP 1 -#define COMP_BSP2AAS 2 -#define COMP_REACH 3 -#define COMP_CLUSTER 4 -#define COMP_AASOPTIMIZE 5 -#define COMP_AASINFO 6 - -int main (int argc, char **argv) -{ - int i, comp = 0; - char outputpath[MAX_PATH] = ""; - char filename[MAX_PATH] = "unknown"; - quakefile_t *qfiles, *qf; - double start_time; - - myargc = argc; - myargv = argv; - - start_time = I_FloatTime(); - - Log_Open("bspc.log"); //open a log file - Log_Print("BSPC version "BSPC_VERSION", %s %s\n", __DATE__, __TIME__); - - DefaultCfg(); - for (i = 1; i < argc; i++) - { - if (!stricmp(argv[i],"-threads")) - { - if (i + 1 >= argc) {i = 0; break;} - numthreads = atoi(argv[++i]); - Log_Print("threads = %d\n", numthreads); - } //end if - else if (!stricmp(argv[i], "-noverbose")) - { - Log_Print("verbose = false\n"); - verbose = false; - } //end else if - else if (!stricmp(argv[i], "-nocsg")) - { - Log_Print("nocsg = true\n"); - nocsg = true; - } //end else if - else if (!stricmp(argv[i], "-optimize")) - { - Log_Print("optimize = true\n"); - optimize = true; - } //end else if - /* - else if (!stricmp(argv[i],"-glview")) - { - glview = true; - } //end else if - else if (!stricmp(argv[i], "-draw")) - { - Log_Print("drawflag = true\n"); - drawflag = true; - } //end else if - else if (!stricmp(argv[i], "-noweld")) - { - Log_Print("noweld = true\n"); - noweld = true; - } //end else if - else if (!stricmp(argv[i], "-noshare")) - { - Log_Print("noshare = true\n"); - noshare = true; - } //end else if - else if (!stricmp(argv[i], "-notjunc")) - { - Log_Print("notjunc = true\n"); - notjunc = true; - } //end else if - else if (!stricmp(argv[i], "-nowater")) - { - Log_Print("nowater = true\n"); - nowater = true; - } //end else if - else if (!stricmp(argv[i], "-noprune")) - { - Log_Print("noprune = true\n"); - noprune = true; - } //end else if - else if (!stricmp(argv[i], "-nomerge")) - { - Log_Print("nomerge = true\n"); - nomerge = true; - } //end else if - else if (!stricmp(argv[i], "-nosubdiv")) - { - Log_Print("nosubdiv = true\n"); - nosubdiv = true; - } //end else if - else if (!stricmp(argv[i], "-nodetail")) - { - Log_Print("nodetail = true\n"); - nodetail = true; - } //end else if - else if (!stricmp(argv[i], "-fulldetail")) - { - Log_Print("fulldetail = true\n"); - fulldetail = true; - } //end else if - else if (!stricmp(argv[i], "-onlyents")) - { - Log_Print("onlyents = true\n"); - onlyents = true; - } //end else if - else if (!stricmp(argv[i], "-micro")) - { - if (i + 1 >= argc) {i = 0; break;} - microvolume = atof(argv[++i]); - Log_Print("microvolume = %f\n", microvolume); - } //end else if - else if (!stricmp(argv[i], "-leaktest")) - { - Log_Print("leaktest = true\n"); - leaktest = true; - } //end else if - else if (!stricmp(argv[i], "-verboseentities")) - { - Log_Print("verboseentities = true\n"); - verboseentities = true; - } //end else if - else if (!stricmp(argv[i], "-chop")) - { - if (i + 1 >= argc) {i = 0; break;} - subdivide_size = atof(argv[++i]); - Log_Print("subdivide_size = %f\n", subdivide_size); - } //end else if - else if (!stricmp (argv[i], "-tmpout")) - { - strcpy (outbase, "/tmp"); - Log_Print("temp output\n"); - } //end else if - */ -#ifdef ME - else if (!stricmp(argv[i], "-freetree")) - { - freetree = true; - Log_Print("freetree = true\n"); - } //end else if - else if (!stricmp(argv[i], "-grapplereach")) - { - calcgrapplereach = true; - Log_Print("grapplereach = true\n"); - } //end else if - else if (!stricmp(argv[i], "-nobrushmerge")) - { - nobrushmerge = true; - Log_Print("nobrushmerge = true\n"); - } //end else if - else if (!stricmp(argv[i], "-noliquids")) - { - noliquids = true; - Log_Print("noliquids = true\n"); - } //end else if - else if (!stricmp(argv[i], "-forcesidesvisible")) - { - forcesidesvisible = true; - Log_Print("forcesidesvisible = true\n"); - } //end else if - else if (!stricmp(argv[i], "-output")) - { - if (i + 1 >= argc) {i = 0; break;} - if (access(argv[i+1], 0x04)) Warning("the folder %s does not exist", argv[i+1]); - strcpy(outputpath, argv[++i]); - } //end else if - else if (!stricmp(argv[i], "-breadthfirst")) - { - use_nodequeue = true; - Log_Print("breadthfirst = true\n"); - } //end else if - else if (!stricmp(argv[i], "-capsule")) - { - capsule_collision = true; - Log_Print("capsule_collision = true\n"); - } //end else if - else if (!stricmp(argv[i], "-cfg")) - { - if (i + 1 >= argc) {i = 0; break;} - if (!LoadCfgFile(argv[++i])) - exit(0); - } //end else if - else if (!stricmp(argv[i], "-bsp2map")) - { - if (i + 1 >= argc) {i = 0; break;} - comp = COMP_BSP2MAP; - qfiles = GetArgumentFiles(argc, argv, &i, "bsp"); - } //end else if - else if (!stricmp(argv[i], "-bsp2aas")) - { - if (i + 1 >= argc) {i = 0; break;} - comp = COMP_BSP2AAS; - qfiles = GetArgumentFiles(argc, argv, &i, "bsp"); - } //end else if - else if (!stricmp(argv[i], "-aasall")) - { - if (i + 1 >= argc) {i = 0; break;} - CreateAASFilesForAllBSPFiles(argv[++i]); - } //end else if - else if (!stricmp(argv[i], "-reach")) - { - if (i + 1 >= argc) {i = 0; break;} - comp = COMP_REACH; - qfiles = GetArgumentFiles(argc, argv, &i, "bsp"); - } //end else if - else if (!stricmp(argv[i], "-cluster")) - { - if (i + 1 >= argc) {i = 0; break;} - comp = COMP_CLUSTER; - qfiles = GetArgumentFiles(argc, argv, &i, "bsp"); - } //end else if - else if (!stricmp(argv[i], "-aasinfo")) - { - if (i + 1 >= argc) {i = 0; break;} - comp = COMP_AASINFO; - qfiles = GetArgumentFiles(argc, argv, &i, "aas"); - } //end else if - else if (!stricmp(argv[i], "-aasopt")) - { - if (i + 1 >= argc) {i = 0; break;} - comp = COMP_AASOPTIMIZE; - qfiles = GetArgumentFiles(argc, argv, &i, "aas"); - } //end else if -#endif //ME - else - { - Log_Print("unknown parameter %s\n", argv[i]); - break; - } //end else - } //end for - - //if there are parameters and there's no mismatch in one of the parameters - if (argc > 1 && i == argc) - { - switch(comp) - { - case COMP_BSP2MAP: - { - if (!qfiles) Log_Print("no files found\n"); - for (qf = qfiles; qf; qf = qf->next) - { - //copy the output path - strcpy(filename, outputpath); - //append the bsp file base - AppendPathSeperator(filename, MAX_PATH); - ExtractFileBase(qf->origname, &filename[strlen(filename)]); - //append .map - strcat(filename, ".map"); - // - Log_Print("bsp2map: %s to %s\n", qf->origname, filename); - if (qf->type != QFILETYPE_BSP) Warning("%s is probably not a BSP file\n", qf->origname); - // - LoadMapFromBSP(qf); - //write the map file - WriteMapFile(filename); - } //end for - break; - } //end case - case COMP_BSP2AAS: - { - if (!qfiles) Log_Print("no files found\n"); - for (qf = qfiles; qf; qf = qf->next) - { - AASOuputFile(qf, outputpath, filename); - // - Log_Print("bsp2aas: %s to %s\n", qf->origname, filename); - if (qf->type != QFILETYPE_BSP) Warning("%s is probably not a BSP file\n", qf->origname); - //set before map loading - create_aas = 1; - LoadMapFromBSP(qf); - //create the AAS file - AAS_Create(filename); - //if it's a Quake3 map calculate the reachabilities and clusters - if (loadedmaptype == MAPTYPE_QUAKE3) AAS_CalcReachAndClusters(qf); - // - if (optimize) AAS_Optimize(); - // - //write out the stored AAS file - if (!AAS_WriteAASFile(filename)) - { - Error("error writing %s\n", filename); - } //end if - //deallocate memory - AAS_FreeMaxAAS(); - } //end for - break; - } //end case - case COMP_REACH: - { - if (!qfiles) Log_Print("no files found\n"); - for (qf = qfiles; qf; qf = qf->next) - { - AASOuputFile(qf, outputpath, filename); - // - Log_Print("reach: %s to %s\n", qf->origname, filename); - if (qf->type != QFILETYPE_BSP) Warning("%s is probably not a BSP file\n", qf->origname); - //if the AAS file exists in the output directory - if (!access(filename, 0x04)) - { - if (!AAS_LoadAASFile(filename, 0, 0)) - { - Error("error loading aas file %s\n", filename); - } //end if - //assume it's a Quake3 BSP file - loadedmaptype = MAPTYPE_QUAKE3; - } //end if - else - { - Warning("AAS file %s not found in output folder\n", filename); - Log_Print("creating %s...\n", filename); - //set before map loading - create_aas = 1; - LoadMapFromBSP(qf); - //create the AAS file - AAS_Create(filename); - } //end else - //if it's a Quake3 map calculate the reachabilities and clusters - if (loadedmaptype == MAPTYPE_QUAKE3) - { - AAS_CalcReachAndClusters(qf); - } //end if - // - if (optimize) AAS_Optimize(); - //write out the stored AAS file - if (!AAS_WriteAASFile(filename)) - { - Error("error writing %s\n", filename); - } //end if - //deallocate memory - AAS_FreeMaxAAS(); - } //end for - break; - } //end case - case COMP_CLUSTER: - { - if (!qfiles) Log_Print("no files found\n"); - for (qf = qfiles; qf; qf = qf->next) - { - AASOuputFile(qf, outputpath, filename); - // - Log_Print("cluster: %s to %s\n", qf->origname, filename); - if (qf->type != QFILETYPE_BSP) Warning("%s is probably not a BSP file\n", qf->origname); - //if the AAS file exists in the output directory - if (!access(filename, 0x04)) - { - if (!AAS_LoadAASFile(filename, 0, 0)) - { - Error("error loading aas file %s\n", filename); - } //end if - //assume it's a Quake3 BSP file - loadedmaptype = MAPTYPE_QUAKE3; - //if it's a Quake3 map calculate the clusters - if (loadedmaptype == MAPTYPE_QUAKE3) - { - aasworld.numclusters = 0; - AAS_InitBotImport(); - AAS_InitClustering(); - } //end if - } //end if - else - { - Warning("AAS file %s not found in output folder\n", filename); - Log_Print("creating %s...\n", filename); - //set before map loading - create_aas = 1; - LoadMapFromBSP(qf); - //create the AAS file - AAS_Create(filename); - //if it's a Quake3 map calculate the reachabilities and clusters - if (loadedmaptype == MAPTYPE_QUAKE3) AAS_CalcReachAndClusters(qf); - } //end else - // - if (optimize) AAS_Optimize(); - //write out the stored AAS file - if (!AAS_WriteAASFile(filename)) - { - Error("error writing %s\n", filename); - } //end if - //deallocate memory - AAS_FreeMaxAAS(); - } //end for - break; - } //end case - case COMP_AASOPTIMIZE: - { - if (!qfiles) Log_Print("no files found\n"); - for (qf = qfiles; qf; qf = qf->next) - { - AASOuputFile(qf, outputpath, filename); - // - Log_Print("optimizing: %s to %s\n", qf->origname, filename); - if (qf->type != QFILETYPE_AAS) Warning("%s is probably not a AAS file\n", qf->origname); - // - AAS_InitBotImport(); - // - if (!AAS_LoadAASFile(qf->filename, qf->offset, qf->length)) - { - Error("error loading aas file %s\n", qf->filename); - } //end if - AAS_Optimize(); - //write out the stored AAS file - if (!AAS_WriteAASFile(filename)) - { - Error("error writing %s\n", filename); - } //end if - //deallocate memory - AAS_FreeMaxAAS(); - } //end for - break; - } //end case - case COMP_AASINFO: - { - if (!qfiles) Log_Print("no files found\n"); - for (qf = qfiles; qf; qf = qf->next) - { - AASOuputFile(qf, outputpath, filename); - // - Log_Print("aas info for: %s\n", filename); - if (qf->type != QFILETYPE_AAS) Warning("%s is probably not a AAS file\n", qf->origname); - // - AAS_InitBotImport(); - // - if (!AAS_LoadAASFile(qf->filename, qf->offset, qf->length)) - { - Error("error loading aas file %s\n", qf->filename); - } //end if - AAS_ShowTotals(); - } //end for - } //end case - default: - { - Log_Print("don't know what to do\n"); - break; - } //end default - } //end switch - } //end if - else - { - Log_Print("Usage: bspc [- [- ...]]\n" -#if defined(WIN32) || defined(_WIN32) - "Example 1: bspc -bsp2aas d:\\quake3\\baseq3\\maps\\mymap?.bsp\n" - "Example 2: bspc -bsp2aas d:\\quake3\\baseq3\\pak0.pk3\\maps/q3dm*.bsp\n" -#else - "Example 1: bspc -bsp2aas /quake3/baseq3/maps/mymap?.bsp\n" - "Example 2: bspc -bsp2aas /quake3/baseq3/pak0.pk3/maps/q3dm*.bsp\n" -#endif - "\n" - "Switches:\n" - //" bsp2map <[pakfilter/]filter.bsp> = convert BSP to MAP\n" - //" aasall = create AAS files for all BSPs\n" - " bsp2aas <[pakfilter/]filter.bsp> = convert BSP to AAS\n" - " reach = compute reachability & clusters\n" - " cluster = compute clusters\n" - " aasopt = optimize aas file\n" - " aasinfo = show AAS file info\n" - " output = set output path\n" - " threads = set number of threads to X\n" - " cfg = use this cfg file\n" - " optimize = enable optimization\n" - " noverbose = disable verbose output\n" - " breadthfirst = breadth first bsp building\n" - " nobrushmerge = don't merge brushes\n" - " noliquids = don't write liquids to map\n" - " freetree = free the bsp tree\n" - " nocsg = disables brush chopping\n" - " forcesidesvisible = force all sides to be visible\n" - " grapplereach = calculate grapple reachabilities\n" - -/* " glview = output a GL view\n" - " draw = enables drawing\n" - " noweld = disables weld\n" - " noshare = disables sharing\n" - " notjunc = disables juncs\n" - " nowater = disables water brushes\n" - " noprune = disables node prunes\n" - " nomerge = disables face merging\n" - " nosubdiv = disables subdeviding\n" - " nodetail = disables detail brushes\n" - " fulldetail = enables full detail\n" - " onlyents = only compile entities with bsp\n" - " micro \n" - " = sets the micro volume to the given float\n" - " leaktest = perform a leak test\n" - " verboseentities\n" - " = enable entity verbose mode\n" - " chop \n" - " = sets the subdivide size to the given float\n"*/ - "\n"); - } //end else - Log_Print("BSPC run time is %5.0f seconds\n", I_FloatTime() - start_time); - Log_Close(); //close the log file - return 0; -} //end of the function main - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#if defined(WIN32) || defined(_WIN32) +#include +#include +#include +#include +#else +#include +#include +#include +#include +#endif +#include "qbsp.h" +#include "l_mem.h" +#include "../botlib/aasfile.h" +#include "../botlib/be_aas_cluster.h" +#include "../botlib/be_aas_optimize.h" +#include "aas_create.h" +#include "aas_store.h" +#include "aas_file.h" +#include "aas_cfg.h" +#include "be_aas_bspc.h" + +extern int use_nodequeue; //brushbsp.c +extern int calcgrapplereach; //be_aas_reach.c + +float subdivide_size = 240; +char source[1024]; +char name[1024]; +vec_t microvolume = 1.0; +char outbase[32]; +int entity_num; +aas_settings_t aassettings; + +qboolean noprune; //don't prune nodes (bspc.c) +qboolean glview; //create a gl view +qboolean nodetail; //don't use detail brushes (map.c) +qboolean fulldetail; //use but don't mark detail brushes (map.c) +qboolean onlyents; //only process the entities (bspc.c) +qboolean nomerge; //don't merge bsp node faces (faces.c) +qboolean nowater; //don't use the water brushes (map.c) +qboolean nocsg; //don't carve intersecting brushes (bspc.c) +qboolean noweld; //use unique face vertexes (faces.c) +qboolean noshare; //don't share bsp edges (faces.c) +qboolean nosubdiv; //don't subdivide bsp node faces (faces.c) +qboolean notjunc; //don't create tjunctions (edge melting) (faces.c) +qboolean optimize; //enable optimisation +qboolean leaktest; //perform a leak test +qboolean verboseentities; +qboolean freetree; //free the bsp tree when not needed anymore +qboolean create_aas; //create an .AAS file +qboolean nobrushmerge; //don't merge brushes +qboolean lessbrushes; //create less brushes instead of correct texture placement +qboolean cancelconversion; //true if the conversion is being cancelled +qboolean noliquids; //no liquids when writing map file +qboolean forcesidesvisible; //force all brush sides to be visible when loaded from bsp +qboolean capsule_collision = 0; + +/* +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ProcessWorldModel (void) +{ + entity_t *e; + tree_t *tree; + qboolean leaked; + int brush_start, brush_end; + + e = &entities[entity_num]; + + brush_start = e->firstbrush; + brush_end = brush_start + e->numbrushes; + leaked = false; + + //process the whole world in one time + tree = ProcessWorldBrushes(brush_start, brush_end); + //create the bsp tree portals + MakeTreePortals(tree); + //mark all leafs that can be reached by entities + if (FloodEntities(tree)) + { + FillOutside(tree->headnode); + } //end if + else + { + Log_Print("**** leaked ****\n"); + leaked = true; + LeakFile(tree); + if (leaktest) + { + Log_Print("--- MAP LEAKED ---\n"); + exit(0); + } //end if + } //end else + + MarkVisibleSides (tree, brush_start, brush_end); + + FloodAreas (tree); + +#ifndef ME + if (glview) WriteGLView(tree, source); +#endif + MakeFaces(tree->headnode); + FixTjuncs(tree->headnode); + + //NOTE: Never prune the nodes because the portals + // are screwed when prunning is done and as + // a result portal writing will crash + //if (!noprune) PruneNodes(tree->headnode); + + WriteBSP(tree->headnode); + + if (!leaked) WritePortalFile(tree); + + Tree_Free(tree); +} //end of the function ProcessWorldModel +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ProcessSubModel (void) +{ + entity_t *e; + int start, end; + tree_t *tree; + bspbrush_t *list; + vec3_t mins, maxs; + + e = &entities[entity_num]; + + start = e->firstbrush; + end = start + e->numbrushes; + + mins[0] = mins[1] = mins[2] = -4096; + maxs[0] = maxs[1] = maxs[2] = 4096; + list = MakeBspBrushList(start, end, mins, maxs); + if (!nocsg) list = ChopBrushes (list); + tree = BrushBSP (list, mins, maxs); + MakeTreePortals (tree); + MarkVisibleSides (tree, start, end); + MakeFaces (tree->headnode); + FixTjuncs (tree->headnode); + WriteBSP (tree->headnode); + Tree_Free(tree); +} //end of the function ProcessSubModel +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ProcessModels (void) +{ + BeginBSPFile(); + + for (entity_num = 0; entity_num < num_entities; entity_num++) + { + if (!entities[entity_num].numbrushes) + continue; + + Log_Print("############### model %i ###############\n", nummodels); + BeginModel(); + if (entity_num == 0) ProcessWorldModel(); + else ProcessSubModel(); + EndModel(); + + if (!verboseentities) + verbose = false; // don't bother printing submodels + } //end for + EndBSPFile(); +} //end of the function ProcessModels +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Win_Map2Bsp(char *bspfilename) +{ + double start, end; + char path[1024]; + + start = I_FloatTime(); + + ThreadSetDefault(); + //yeah sure Carmack + //numthreads = 1; // multiple threads aren't helping... + + strcpy(source, ExpandArg(bspfilename)); + StripExtension(source); + + //delete portal and line files + sprintf(path, "%s.prt", source); + remove(path); + sprintf(path, "%s.lin", source); + remove(path); + + strcpy(name, ExpandArg(bspfilename)); + DefaultExtension(name, ".map"); // might be .reg + + Q2_AllocMaxBSP(); + // + SetModelNumbers(); + SetLightStyles(); + ProcessModels(); + //write the BSP + Q2_WriteBSPFile(bspfilename); + + Q2_FreeMaxBSP(); + + end = I_FloatTime(); + Log_Print("%5.0f seconds elapsed\n", end-start); +} //end of the function Win_Map2Bsp +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Map2Bsp(char *mapfilename, char *outputfilename) +{ + double start, end; + char path[1024]; + + start = I_FloatTime (); + + ThreadSetDefault (); + //yeah sure Carmack + //numthreads = 1; //multiple threads aren't helping... + //SetQdirFromPath(bspfilename); + + strcpy(source, ExpandArg(mapfilename)); + StripExtension(source); + + // delete portal and line files + sprintf(path, "%s.prt", source); + remove(path); + sprintf(path, "%s.lin", source); + remove(path); + + strcpy(name, ExpandArg(mapfilename)); + DefaultExtension(name, ".map"); // might be .reg + + // + // if onlyents, just grab the entites and resave + // + if (onlyents) + { + char out[1024]; + + Q2_AllocMaxBSP(); + sprintf (out, "%s.bsp", source); + Q2_LoadBSPFile(out, 0, 0); + num_entities = 0; + + Q2_LoadMapFile(name); + SetModelNumbers(); + SetLightStyles(); + + Q2_UnparseEntities(); + + Q2_WriteBSPFile(out); + // + Q2_FreeMaxBSP(); + } //end if + else + { + // + // start from scratch + // + Q2_AllocMaxBSP(); + //load the map + Q2_LoadMapFile(name); + //create the .bsp file + SetModelNumbers(); + SetLightStyles(); + ProcessModels(); + //write the BSP + Q2_WriteBSPFile(outputfilename); + // + Q2_FreeMaxBSP(); + } //end else + + end = I_FloatTime(); + Log_Print("%5.0f seconds elapsed\n", end-start); +} //end of the function Map2Bsp +*/ +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AASOuputFile(quakefile_t *qf, char *outputpath, char *filename) +{ + char ext[MAX_PATH]; + + // + if (strlen(outputpath)) + { + strcpy(filename, outputpath); + //append the bsp file base + AppendPathSeperator(filename, MAX_PATH); + ExtractFileBase(qf->origname, &filename[strlen(filename)]); + //append .aas + strcat(filename, ".aas"); + return; + } //end if + // + ExtractFileExtension(qf->filename, ext); + if (!stricmp(ext, "pk3") || !stricmp(ext, "pak") || !stricmp(ext, "sin")) + { + strcpy(filename, qf->filename); + while(strlen(filename) && + filename[strlen(filename)-1] != '\\' && + filename[strlen(filename)-1] != '/') + { + filename[strlen(filename)-1] = '\0'; + } //end while + strcat(filename, "maps"); + if (access(filename, 0x04)) CreatePath(filename); + //append the bsp file base + AppendPathSeperator(filename, MAX_PATH); + ExtractFileBase(qf->origname, &filename[strlen(filename)]); + //append .aas + strcat(filename, ".aas"); + } //end if + else + { + strcpy(filename, qf->filename); + while(strlen(filename) && + filename[strlen(filename)-1] != '.') + { + filename[strlen(filename)-1] = '\0'; + } //end while + strcat(filename, "aas"); + } //end else +} //end of the function AASOutputFile +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void CreateAASFilesForAllBSPFiles(char *quakepath) +{ +#if defined(WIN32)|defined(_WIN32) + WIN32_FIND_DATA filedata; + HWND handle; + struct _stat statbuf; +#else + glob_t globbuf; + struct stat statbuf; + int j; +#endif + int done; + char filter[_MAX_PATH], bspfilter[_MAX_PATH], aasfilter[_MAX_PATH]; + char aasfile[_MAX_PATH], buf[_MAX_PATH], foldername[_MAX_PATH]; + quakefile_t *qf, *qf2, *files, *bspfiles, *aasfiles; + + strcpy(filter, quakepath); + AppendPathSeperator(filter, sizeof(filter)); + strcat(filter, "*"); + +#if defined(WIN32)|defined(_WIN32) + handle = FindFirstFile(filter, &filedata); + done = (handle == INVALID_HANDLE_VALUE); + while(!done) + { + _splitpath(filter, foldername, NULL, NULL, NULL); + _splitpath(filter, NULL, &foldername[strlen(foldername)], NULL, NULL); + AppendPathSeperator(foldername, _MAX_PATH); + strcat(foldername, filedata.cFileName); + _stat(foldername, &statbuf); +#else + glob(filter, 0, NULL, &globbuf); + for (j = 0; j < globbuf.gl_pathc; j++) + { + strcpy(foldername, globbuf.gl_pathv[j]); + stat(foldername, &statbuf); +#endif + //if it is a folder + if (statbuf.st_mode & S_IFDIR) + { + // + AppendPathSeperator(foldername, sizeof(foldername)); + //get all the bsp files + strcpy(bspfilter, foldername); + strcat(bspfilter, "maps/*.bsp"); + files = FindQuakeFiles(bspfilter); + strcpy(bspfilter, foldername); + strcat(bspfilter, "*.pk3/maps/*.bsp"); + bspfiles = FindQuakeFiles(bspfilter); + for (qf = bspfiles; qf; qf = qf->next) if (!qf->next) break; + if (qf) qf->next = files; + else bspfiles = files; + //get all the aas files + strcpy(aasfilter, foldername); + strcat(aasfilter, "maps/*.aas"); + files = FindQuakeFiles(aasfilter); + strcpy(aasfilter, foldername); + strcat(aasfilter, "*.pk3/maps/*.aas"); + aasfiles = FindQuakeFiles(aasfilter); + for (qf = aasfiles; qf; qf = qf->next) if (!qf->next) break; + if (qf) qf->next = files; + else aasfiles = files; + // + for (qf = bspfiles; qf; qf = qf->next) + { + sprintf(aasfile, "%s/%s", qf->pakfile, qf->origname); + Log_Print("found %s\n", aasfile); + strcpy(&aasfile[strlen(aasfile)-strlen(".bsp")], ".aas"); + for (qf2 = aasfiles; qf2; qf2 = qf2->next) + { + sprintf(buf, "%s/%s", qf2->pakfile, qf2->origname); + if (!stricmp(aasfile, buf)) + { + Log_Print("found %s\n", buf); + break; + } //end if + } //end for + } //end for + } //end if +#if defined(WIN32)|defined(_WIN32) + //find the next file + done = !FindNextFile(handle, &filedata); + } //end while +#else + } //end for + globfree(&globbuf); +#endif +} //end of the function CreateAASFilesForAllBSPFiles +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +quakefile_t *GetArgumentFiles(int argc, char *argv[], int *i, char *ext) +{ + quakefile_t *qfiles, *lastqf, *qf; + int j; + char buf[1024]; + + qfiles = NULL; + lastqf = NULL; + for (; (*i)+1 < argc && argv[(*i)+1][0] != '-'; (*i)++) + { + strcpy(buf, argv[(*i)+1]); + for (j = strlen(buf)-1; j >= strlen(buf)-4; j--) + if (buf[j] == '.') break; + if (j >= strlen(buf)-4) + strcpy(&buf[j+1], ext); + qf = FindQuakeFiles(buf); + if (!qf) continue; + if (lastqf) lastqf->next = qf; + else qfiles = qf; + lastqf = qf; + while(lastqf->next) lastqf = lastqf->next; + } //end for + return qfiles; +} //end of the function GetArgumentFiles +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== + +#define COMP_BSP2MAP 1 +#define COMP_BSP2AAS 2 +#define COMP_REACH 3 +#define COMP_CLUSTER 4 +#define COMP_AASOPTIMIZE 5 +#define COMP_AASINFO 6 + +int main (int argc, char **argv) +{ + int i, comp = 0; + char outputpath[MAX_PATH] = ""; + char filename[MAX_PATH] = "unknown"; + quakefile_t *qfiles, *qf; + double start_time; + + myargc = argc; + myargv = argv; + + start_time = I_FloatTime(); + + Log_Open("bspc.log"); //open a log file + Log_Print("BSPC version "BSPC_VERSION", %s %s\n", __DATE__, __TIME__); + + DefaultCfg(); + for (i = 1; i < argc; i++) + { + if (!stricmp(argv[i],"-threads")) + { + if (i + 1 >= argc) {i = 0; break;} + numthreads = atoi(argv[++i]); + Log_Print("threads = %d\n", numthreads); + } //end if + else if (!stricmp(argv[i], "-noverbose")) + { + Log_Print("verbose = false\n"); + verbose = false; + } //end else if + else if (!stricmp(argv[i], "-nocsg")) + { + Log_Print("nocsg = true\n"); + nocsg = true; + } //end else if + else if (!stricmp(argv[i], "-optimize")) + { + Log_Print("optimize = true\n"); + optimize = true; + } //end else if + /* + else if (!stricmp(argv[i],"-glview")) + { + glview = true; + } //end else if + else if (!stricmp(argv[i], "-draw")) + { + Log_Print("drawflag = true\n"); + drawflag = true; + } //end else if + else if (!stricmp(argv[i], "-noweld")) + { + Log_Print("noweld = true\n"); + noweld = true; + } //end else if + else if (!stricmp(argv[i], "-noshare")) + { + Log_Print("noshare = true\n"); + noshare = true; + } //end else if + else if (!stricmp(argv[i], "-notjunc")) + { + Log_Print("notjunc = true\n"); + notjunc = true; + } //end else if + else if (!stricmp(argv[i], "-nowater")) + { + Log_Print("nowater = true\n"); + nowater = true; + } //end else if + else if (!stricmp(argv[i], "-noprune")) + { + Log_Print("noprune = true\n"); + noprune = true; + } //end else if + else if (!stricmp(argv[i], "-nomerge")) + { + Log_Print("nomerge = true\n"); + nomerge = true; + } //end else if + else if (!stricmp(argv[i], "-nosubdiv")) + { + Log_Print("nosubdiv = true\n"); + nosubdiv = true; + } //end else if + else if (!stricmp(argv[i], "-nodetail")) + { + Log_Print("nodetail = true\n"); + nodetail = true; + } //end else if + else if (!stricmp(argv[i], "-fulldetail")) + { + Log_Print("fulldetail = true\n"); + fulldetail = true; + } //end else if + else if (!stricmp(argv[i], "-onlyents")) + { + Log_Print("onlyents = true\n"); + onlyents = true; + } //end else if + else if (!stricmp(argv[i], "-micro")) + { + if (i + 1 >= argc) {i = 0; break;} + microvolume = atof(argv[++i]); + Log_Print("microvolume = %f\n", microvolume); + } //end else if + else if (!stricmp(argv[i], "-leaktest")) + { + Log_Print("leaktest = true\n"); + leaktest = true; + } //end else if + else if (!stricmp(argv[i], "-verboseentities")) + { + Log_Print("verboseentities = true\n"); + verboseentities = true; + } //end else if + else if (!stricmp(argv[i], "-chop")) + { + if (i + 1 >= argc) {i = 0; break;} + subdivide_size = atof(argv[++i]); + Log_Print("subdivide_size = %f\n", subdivide_size); + } //end else if + else if (!stricmp (argv[i], "-tmpout")) + { + strcpy (outbase, "/tmp"); + Log_Print("temp output\n"); + } //end else if + */ +#ifdef ME + else if (!stricmp(argv[i], "-freetree")) + { + freetree = true; + Log_Print("freetree = true\n"); + } //end else if + else if (!stricmp(argv[i], "-grapplereach")) + { + calcgrapplereach = true; + Log_Print("grapplereach = true\n"); + } //end else if + else if (!stricmp(argv[i], "-nobrushmerge")) + { + nobrushmerge = true; + Log_Print("nobrushmerge = true\n"); + } //end else if + else if (!stricmp(argv[i], "-noliquids")) + { + noliquids = true; + Log_Print("noliquids = true\n"); + } //end else if + else if (!stricmp(argv[i], "-forcesidesvisible")) + { + forcesidesvisible = true; + Log_Print("forcesidesvisible = true\n"); + } //end else if + else if (!stricmp(argv[i], "-output")) + { + if (i + 1 >= argc) {i = 0; break;} + if (access(argv[i+1], 0x04)) Warning("the folder %s does not exist", argv[i+1]); + strcpy(outputpath, argv[++i]); + } //end else if + else if (!stricmp(argv[i], "-breadthfirst")) + { + use_nodequeue = true; + Log_Print("breadthfirst = true\n"); + } //end else if + else if (!stricmp(argv[i], "-capsule")) + { + capsule_collision = true; + Log_Print("capsule_collision = true\n"); + } //end else if + else if (!stricmp(argv[i], "-cfg")) + { + if (i + 1 >= argc) {i = 0; break;} + if (!LoadCfgFile(argv[++i])) + exit(0); + } //end else if + else if (!stricmp(argv[i], "-bsp2map")) + { + if (i + 1 >= argc) {i = 0; break;} + comp = COMP_BSP2MAP; + qfiles = GetArgumentFiles(argc, argv, &i, "bsp"); + } //end else if + else if (!stricmp(argv[i], "-bsp2aas")) + { + if (i + 1 >= argc) {i = 0; break;} + comp = COMP_BSP2AAS; + qfiles = GetArgumentFiles(argc, argv, &i, "bsp"); + } //end else if + else if (!stricmp(argv[i], "-aasall")) + { + if (i + 1 >= argc) {i = 0; break;} + CreateAASFilesForAllBSPFiles(argv[++i]); + } //end else if + else if (!stricmp(argv[i], "-reach")) + { + if (i + 1 >= argc) {i = 0; break;} + comp = COMP_REACH; + qfiles = GetArgumentFiles(argc, argv, &i, "bsp"); + } //end else if + else if (!stricmp(argv[i], "-cluster")) + { + if (i + 1 >= argc) {i = 0; break;} + comp = COMP_CLUSTER; + qfiles = GetArgumentFiles(argc, argv, &i, "bsp"); + } //end else if + else if (!stricmp(argv[i], "-aasinfo")) + { + if (i + 1 >= argc) {i = 0; break;} + comp = COMP_AASINFO; + qfiles = GetArgumentFiles(argc, argv, &i, "aas"); + } //end else if + else if (!stricmp(argv[i], "-aasopt")) + { + if (i + 1 >= argc) {i = 0; break;} + comp = COMP_AASOPTIMIZE; + qfiles = GetArgumentFiles(argc, argv, &i, "aas"); + } //end else if +#endif //ME + else + { + Log_Print("unknown parameter %s\n", argv[i]); + break; + } //end else + } //end for + + //if there are parameters and there's no mismatch in one of the parameters + if (argc > 1 && i == argc) + { + switch(comp) + { + case COMP_BSP2MAP: + { + if (!qfiles) Log_Print("no files found\n"); + for (qf = qfiles; qf; qf = qf->next) + { + //copy the output path + strcpy(filename, outputpath); + //append the bsp file base + AppendPathSeperator(filename, MAX_PATH); + ExtractFileBase(qf->origname, &filename[strlen(filename)]); + //append .map + strcat(filename, ".map"); + // + Log_Print("bsp2map: %s to %s\n", qf->origname, filename); + if (qf->type != QFILETYPE_BSP) Warning("%s is probably not a BSP file\n", qf->origname); + // + LoadMapFromBSP(qf); + //write the map file + WriteMapFile(filename); + } //end for + break; + } //end case + case COMP_BSP2AAS: + { + if (!qfiles) Log_Print("no files found\n"); + for (qf = qfiles; qf; qf = qf->next) + { + AASOuputFile(qf, outputpath, filename); + // + Log_Print("bsp2aas: %s to %s\n", qf->origname, filename); + if (qf->type != QFILETYPE_BSP) Warning("%s is probably not a BSP file\n", qf->origname); + //set before map loading + create_aas = 1; + LoadMapFromBSP(qf); + //create the AAS file + AAS_Create(filename); + //if it's a Quake3 map calculate the reachabilities and clusters + if (loadedmaptype == MAPTYPE_QUAKE3) AAS_CalcReachAndClusters(qf); + // + if (optimize) AAS_Optimize(); + // + //write out the stored AAS file + if (!AAS_WriteAASFile(filename)) + { + Error("error writing %s\n", filename); + } //end if + //deallocate memory + AAS_FreeMaxAAS(); + } //end for + break; + } //end case + case COMP_REACH: + { + if (!qfiles) Log_Print("no files found\n"); + for (qf = qfiles; qf; qf = qf->next) + { + AASOuputFile(qf, outputpath, filename); + // + Log_Print("reach: %s to %s\n", qf->origname, filename); + if (qf->type != QFILETYPE_BSP) Warning("%s is probably not a BSP file\n", qf->origname); + //if the AAS file exists in the output directory + if (!access(filename, 0x04)) + { + if (!AAS_LoadAASFile(filename, 0, 0)) + { + Error("error loading aas file %s\n", filename); + } //end if + //assume it's a Quake3 BSP file + loadedmaptype = MAPTYPE_QUAKE3; + } //end if + else + { + Warning("AAS file %s not found in output folder\n", filename); + Log_Print("creating %s...\n", filename); + //set before map loading + create_aas = 1; + LoadMapFromBSP(qf); + //create the AAS file + AAS_Create(filename); + } //end else + //if it's a Quake3 map calculate the reachabilities and clusters + if (loadedmaptype == MAPTYPE_QUAKE3) + { + AAS_CalcReachAndClusters(qf); + } //end if + // + if (optimize) AAS_Optimize(); + //write out the stored AAS file + if (!AAS_WriteAASFile(filename)) + { + Error("error writing %s\n", filename); + } //end if + //deallocate memory + AAS_FreeMaxAAS(); + } //end for + break; + } //end case + case COMP_CLUSTER: + { + if (!qfiles) Log_Print("no files found\n"); + for (qf = qfiles; qf; qf = qf->next) + { + AASOuputFile(qf, outputpath, filename); + // + Log_Print("cluster: %s to %s\n", qf->origname, filename); + if (qf->type != QFILETYPE_BSP) Warning("%s is probably not a BSP file\n", qf->origname); + //if the AAS file exists in the output directory + if (!access(filename, 0x04)) + { + if (!AAS_LoadAASFile(filename, 0, 0)) + { + Error("error loading aas file %s\n", filename); + } //end if + //assume it's a Quake3 BSP file + loadedmaptype = MAPTYPE_QUAKE3; + //if it's a Quake3 map calculate the clusters + if (loadedmaptype == MAPTYPE_QUAKE3) + { + aasworld.numclusters = 0; + AAS_InitBotImport(); + AAS_InitClustering(); + } //end if + } //end if + else + { + Warning("AAS file %s not found in output folder\n", filename); + Log_Print("creating %s...\n", filename); + //set before map loading + create_aas = 1; + LoadMapFromBSP(qf); + //create the AAS file + AAS_Create(filename); + //if it's a Quake3 map calculate the reachabilities and clusters + if (loadedmaptype == MAPTYPE_QUAKE3) AAS_CalcReachAndClusters(qf); + } //end else + // + if (optimize) AAS_Optimize(); + //write out the stored AAS file + if (!AAS_WriteAASFile(filename)) + { + Error("error writing %s\n", filename); + } //end if + //deallocate memory + AAS_FreeMaxAAS(); + } //end for + break; + } //end case + case COMP_AASOPTIMIZE: + { + if (!qfiles) Log_Print("no files found\n"); + for (qf = qfiles; qf; qf = qf->next) + { + AASOuputFile(qf, outputpath, filename); + // + Log_Print("optimizing: %s to %s\n", qf->origname, filename); + if (qf->type != QFILETYPE_AAS) Warning("%s is probably not a AAS file\n", qf->origname); + // + AAS_InitBotImport(); + // + if (!AAS_LoadAASFile(qf->filename, qf->offset, qf->length)) + { + Error("error loading aas file %s\n", qf->filename); + } //end if + AAS_Optimize(); + //write out the stored AAS file + if (!AAS_WriteAASFile(filename)) + { + Error("error writing %s\n", filename); + } //end if + //deallocate memory + AAS_FreeMaxAAS(); + } //end for + break; + } //end case + case COMP_AASINFO: + { + if (!qfiles) Log_Print("no files found\n"); + for (qf = qfiles; qf; qf = qf->next) + { + AASOuputFile(qf, outputpath, filename); + // + Log_Print("aas info for: %s\n", filename); + if (qf->type != QFILETYPE_AAS) Warning("%s is probably not a AAS file\n", qf->origname); + // + AAS_InitBotImport(); + // + if (!AAS_LoadAASFile(qf->filename, qf->offset, qf->length)) + { + Error("error loading aas file %s\n", qf->filename); + } //end if + AAS_ShowTotals(); + } //end for + } //end case + default: + { + Log_Print("don't know what to do\n"); + break; + } //end default + } //end switch + } //end if + else + { + Log_Print("Usage: bspc [- [- ...]]\n" +#if defined(WIN32) || defined(_WIN32) + "Example 1: bspc -bsp2aas d:\\quake3\\baseq3\\maps\\mymap?.bsp\n" + "Example 2: bspc -bsp2aas d:\\quake3\\baseq3\\pak0.pk3\\maps/q3dm*.bsp\n" +#else + "Example 1: bspc -bsp2aas /quake3/baseq3/maps/mymap?.bsp\n" + "Example 2: bspc -bsp2aas /quake3/baseq3/pak0.pk3/maps/q3dm*.bsp\n" +#endif + "\n" + "Switches:\n" + //" bsp2map <[pakfilter/]filter.bsp> = convert BSP to MAP\n" + //" aasall = create AAS files for all BSPs\n" + " bsp2aas <[pakfilter/]filter.bsp> = convert BSP to AAS\n" + " reach = compute reachability & clusters\n" + " cluster = compute clusters\n" + " aasopt = optimize aas file\n" + " aasinfo = show AAS file info\n" + " output = set output path\n" + " threads = set number of threads to X\n" + " cfg = use this cfg file\n" + " optimize = enable optimization\n" + " noverbose = disable verbose output\n" + " breadthfirst = breadth first bsp building\n" + " nobrushmerge = don't merge brushes\n" + " noliquids = don't write liquids to map\n" + " freetree = free the bsp tree\n" + " nocsg = disables brush chopping\n" + " forcesidesvisible = force all sides to be visible\n" + " grapplereach = calculate grapple reachabilities\n" + +/* " glview = output a GL view\n" + " draw = enables drawing\n" + " noweld = disables weld\n" + " noshare = disables sharing\n" + " notjunc = disables juncs\n" + " nowater = disables water brushes\n" + " noprune = disables node prunes\n" + " nomerge = disables face merging\n" + " nosubdiv = disables subdeviding\n" + " nodetail = disables detail brushes\n" + " fulldetail = enables full detail\n" + " onlyents = only compile entities with bsp\n" + " micro \n" + " = sets the micro volume to the given float\n" + " leaktest = perform a leak test\n" + " verboseentities\n" + " = enable entity verbose mode\n" + " chop \n" + " = sets the subdivide size to the given float\n"*/ + "\n"); + } //end else + Log_Print("BSPC run time is %5.0f seconds\n", I_FloatTime() - start_time); + Log_Close(); //close the log file + return 0; +} //end of the function main + diff --git a/code/bspc/bspc.sln b/code/bspc/bspc.sln index 79dc8ae..b09fb52 100755 --- a/code/bspc/bspc.sln +++ b/code/bspc/bspc.sln @@ -1,28 +1,28 @@ -Microsoft Visual Studio Solution File, Format Version 8.00 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bspc", "bspc.vcproj", "{4E4EBC16-F345-4667-84E1-86633BAFDAE6}" - ProjectSection(ProjectDependencies) = postProject - EndProjectSection -EndProject -Global - GlobalSection(SourceCodeControl) = preSolution - SccNumberOfProjects = 1 - SccProjectUniqueName0 = bspc.vcproj - SccProjectName0 = \u0022$/source/code/bspc\u0022,\u0020IGAAAAAA - SccLocalPath0 = . - SccProvider0 = MSSCCI:Perforce\u0020SCM - EndGlobalSection - GlobalSection(SolutionConfiguration) = preSolution - Debug = Debug - Release = Release - EndGlobalSection - GlobalSection(ProjectConfiguration) = postSolution - {4E4EBC16-F345-4667-84E1-86633BAFDAE6}.Debug.ActiveCfg = Debug|Win32 - {4E4EBC16-F345-4667-84E1-86633BAFDAE6}.Debug.Build.0 = Debug|Win32 - {4E4EBC16-F345-4667-84E1-86633BAFDAE6}.Release.ActiveCfg = Release|Win32 - {4E4EBC16-F345-4667-84E1-86633BAFDAE6}.Release.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - EndGlobalSection - GlobalSection(ExtensibilityAddIns) = postSolution - EndGlobalSection -EndGlobal +Microsoft Visual Studio Solution File, Format Version 8.00 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bspc", "bspc.vcproj", "{4E4EBC16-F345-4667-84E1-86633BAFDAE6}" + ProjectSection(ProjectDependencies) = postProject + EndProjectSection +EndProject +Global + GlobalSection(SourceCodeControl) = preSolution + SccNumberOfProjects = 1 + SccProjectUniqueName0 = bspc.vcproj + SccProjectName0 = \u0022$/source/code/bspc\u0022,\u0020IGAAAAAA + SccLocalPath0 = . + SccProvider0 = MSSCCI:Perforce\u0020SCM + EndGlobalSection + GlobalSection(SolutionConfiguration) = preSolution + Debug = Debug + Release = Release + EndGlobalSection + GlobalSection(ProjectConfiguration) = postSolution + {4E4EBC16-F345-4667-84E1-86633BAFDAE6}.Debug.ActiveCfg = Debug|Win32 + {4E4EBC16-F345-4667-84E1-86633BAFDAE6}.Debug.Build.0 = Debug|Win32 + {4E4EBC16-F345-4667-84E1-86633BAFDAE6}.Release.ActiveCfg = Release|Win32 + {4E4EBC16-F345-4667-84E1-86633BAFDAE6}.Release.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + EndGlobalSection + GlobalSection(ExtensibilityAddIns) = postSolution + EndGlobalSection +EndGlobal diff --git a/code/bspc/bspc.vcproj b/code/bspc/bspc.vcproj index 6171e1a..0fb1bd7 100755 --- a/code/bspc/bspc.vcproj +++ b/code/bspc/bspc.vcproj @@ -1,1381 +1,1381 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/bspc/cfgq3.c b/code/bspc/cfgq3.c index 588509c..c0598ae 100755 --- a/code/bspc/cfgq3.c +++ b/code/bspc/cfgq3.c @@ -1,84 +1,84 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -//=========================================================================== -// BSPC configuration file -// Quake3 -//=========================================================================== - -#define PRESENCE_NONE 1 -#define PRESENCE_NORMAL 2 -#define PRESENCE_CROUCH 4 - -bbox //30x30x56 -{ - presencetype PRESENCE_NORMAL - flags 0x0000 - mins {-15, -15, -24} - maxs {15, 15, 32} -} //end bbox - -bbox //30x30x40 -{ - presencetype PRESENCE_CROUCH - flags 0x0001 - mins {-15, -15, -24} - maxs {15, 15, 16} -} //end bbox - -settings -{ - phys_gravitydirection {0, 0, -1} - phys_friction 6 - phys_stopspeed 100 - phys_gravity 800 - phys_waterfriction 1 - phys_watergravity 400 - phys_maxvelocity 320 - phys_maxwalkvelocity 320 - phys_maxcrouchvelocity 100 - phys_maxswimvelocity 150 - phys_maxacceleration 2200 - phys_airaccelerate 0 - phys_maxstep 18 - phys_maxsteepness 0.7 - phys_maxwaterjump 19 - phys_maxbarrier 33 - phys_jumpvel 270 - phys_falldelta5 40 - phys_falldelta10 60 - rs_waterjump 400 - rs_teleport 50 - rs_barrierjump 100 - rs_startcrouch 300 - rs_startgrapple 500 - rs_startwalkoffledge 70 - rs_startjump 300 - rs_rocketjump 500 - rs_bfgjump 500 - rs_jumppad 250 - rs_aircontrolledjumppad 300 - rs_funcbob 300 - rs_startelevator 50 - rs_falldamage5 300 - rs_falldamage10 500 - rs_maxjumpfallheight 450 -} //end settings +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +//=========================================================================== +// BSPC configuration file +// Quake3 +//=========================================================================== + +#define PRESENCE_NONE 1 +#define PRESENCE_NORMAL 2 +#define PRESENCE_CROUCH 4 + +bbox //30x30x56 +{ + presencetype PRESENCE_NORMAL + flags 0x0000 + mins {-15, -15, -24} + maxs {15, 15, 32} +} //end bbox + +bbox //30x30x40 +{ + presencetype PRESENCE_CROUCH + flags 0x0001 + mins {-15, -15, -24} + maxs {15, 15, 16} +} //end bbox + +settings +{ + phys_gravitydirection {0, 0, -1} + phys_friction 6 + phys_stopspeed 100 + phys_gravity 800 + phys_waterfriction 1 + phys_watergravity 400 + phys_maxvelocity 320 + phys_maxwalkvelocity 320 + phys_maxcrouchvelocity 100 + phys_maxswimvelocity 150 + phys_maxacceleration 2200 + phys_airaccelerate 0 + phys_maxstep 18 + phys_maxsteepness 0.7 + phys_maxwaterjump 19 + phys_maxbarrier 33 + phys_jumpvel 270 + phys_falldelta5 40 + phys_falldelta10 60 + rs_waterjump 400 + rs_teleport 50 + rs_barrierjump 100 + rs_startcrouch 300 + rs_startgrapple 500 + rs_startwalkoffledge 70 + rs_startjump 300 + rs_rocketjump 500 + rs_bfgjump 500 + rs_jumppad 250 + rs_aircontrolledjumppad 300 + rs_funcbob 300 + rs_startelevator 50 + rs_falldamage5 300 + rs_falldamage10 500 + rs_maxjumpfallheight 450 +} //end settings diff --git a/code/bspc/csg.c b/code/bspc/csg.c index c141075..fc7c327 100755 --- a/code/bspc/csg.c +++ b/code/bspc/csg.c @@ -1,1005 +1,1005 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "qbsp.h" - -/* - -tag all brushes with original contents -brushes may contain multiple contents -there will be no brush overlap after csg phase - -*/ - -int minplanenums[3]; -int maxplanenums[3]; - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void CheckBSPBrush(bspbrush_t *brush) -{ - int i, j; - plane_t *plane1, *plane2; - - //check if the brush is convex... flipped planes make a brush non-convex - for (i = 0; i < brush->numsides; i++) - { - for (j = 0; j < brush->numsides; j++) - { - if (i == j) continue; - plane1 = &mapplanes[brush->sides[i].planenum]; - plane2 = &mapplanes[brush->sides[j].planenum]; - // - if (WindingsNonConvex(brush->sides[i].winding, - brush->sides[j].winding, - plane1->normal, plane2->normal, - plane1->dist, plane2->dist)) - { - Log_Print("non convex brush"); - break; - } //end if - } //end for - } //end for - BoundBrush(brush); - //check for out of bound brushes - for (i = 0; i < 3; i++) - { - if (brush->mins[i] < -MAX_MAP_BOUNDS || brush->maxs[i] > MAX_MAP_BOUNDS) - { - Log_Print("brush: bounds out of range\n"); - Log_Print("ob->mins[%d] = %f, ob->maxs[%d] = %f\n", i, brush->mins[i], i, brush->maxs[i]); - break; - } //end if - if (brush->mins[i] > MAX_MAP_BOUNDS || brush->maxs[i] < -MAX_MAP_BOUNDS) - { - Log_Print("brush: no visible sides on brush\n"); - Log_Print("ob->mins[%d] = %f, ob->maxs[%d] = %f\n", i, brush->mins[i], i, brush->maxs[i]); - break; - } //end if - } //end for -} //end of the function CheckBSPBrush -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void BSPBrushWindings(bspbrush_t *brush) -{ - int i, j; - winding_t *w; - plane_t *plane; - - for (i = 0; i < brush->numsides; i++) - { - plane = &mapplanes[brush->sides[i].planenum]; - w = BaseWindingForPlane(plane->normal, plane->dist); - for (j = 0; j < brush->numsides && w; j++) - { - if (i == j) continue; - plane = &mapplanes[brush->sides[j].planenum^1]; - ChopWindingInPlace(&w, plane->normal, plane->dist, 0); //CLIP_EPSILON); - } //end for - brush->sides[i].winding = w; - } //end for -} //end of the function BSPBrushWindings -//=========================================================================== -// NOTE: can't keep brush->original intact -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bspbrush_t *TryMergeBrushes(bspbrush_t *brush1, bspbrush_t *brush2) -{ - int i, j, k, n, shared; - side_t *side1, *side2, *cs; - plane_t *plane1, *plane2; - bspbrush_t *newbrush; - - //check for bounding box overlapp - for (i = 0; i < 3; i++) - { - if (brush1->mins[i] > brush2->maxs[i] + 2 - || brush1->maxs[i] < brush2->mins[i] - 2) - { - return NULL; - } //end if - } //end for - // - shared = 0; - //check if the brush is convex... flipped planes make a brush non-convex - for (i = 0; i < brush1->numsides; i++) - { - side1 = &brush1->sides[i]; - //don't check the "shared" sides - for (k = 0; k < brush2->numsides; k++) - { - side2 = &brush2->sides[k]; - if (side1->planenum == (side2->planenum^1)) - { - shared++; - //there may only be ONE shared side - if (shared > 1) return NULL; - break; - } //end if - } //end for - if (k < brush2->numsides) continue; - // - for (j = 0; j < brush2->numsides; j++) - { - side2 = &brush2->sides[j]; - //don't check the "shared" sides - for (n = 0; n < brush1->numsides; n++) - { - side1 = &brush1->sides[n]; - if (side1->planenum == (side2->planenum^1)) break; - } //end for - if (n < brush1->numsides) continue; - // - side1 = &brush1->sides[i]; - //if the side is in the same plane - //* - if (side1->planenum == side2->planenum) - { - if (side1->texinfo != TEXINFO_NODE && - side2->texinfo != TEXINFO_NODE && - side1->texinfo != side2->texinfo) return NULL; - continue; - } //end if - // - plane1 = &mapplanes[side1->planenum]; - plane2 = &mapplanes[side2->planenum]; - // - if (WindingsNonConvex(side1->winding, side2->winding, - plane1->normal, plane2->normal, - plane1->dist, plane2->dist)) - { - return NULL; - } //end if - } //end for - } //end for - newbrush = AllocBrush(brush1->numsides + brush2->numsides); - newbrush->original = brush1->original; - newbrush->numsides = 0; - //newbrush->side = brush1->side; //brush contents - //fix texinfos for sides lying in the same plane - for (i = 0; i < brush1->numsides; i++) - { - side1 = &brush1->sides[i]; - // - for (n = 0; n < brush2->numsides; n++) - { - side2 = &brush2->sides[n]; - //if both sides are in the same plane get the texinfo right - if (side1->planenum == side2->planenum) - { - if (side1->texinfo == TEXINFO_NODE) side1->texinfo = side2->texinfo; - if (side2->texinfo == TEXINFO_NODE) side2->texinfo = side1->texinfo; - } //end if - } //end for - } //end for - // - for (i = 0; i < brush1->numsides; i++) - { - side1 = &brush1->sides[i]; - //don't add the "shared" sides - for (n = 0; n < brush2->numsides; n++) - { - side2 = &brush2->sides[n]; - if (side1->planenum == (side2->planenum ^ 1)) break; - } //end for - if (n < brush2->numsides) continue; - // - for (n = 0; n < newbrush->numsides; n++) - { - cs = &newbrush->sides[n]; - if (cs->planenum == side1->planenum) - { - Log_Print("brush duplicate plane\n"); - break; - } //end if - } //end if - if (n < newbrush->numsides) continue; - //add this side - cs = &newbrush->sides[newbrush->numsides]; - newbrush->numsides++; - *cs = *side1; - } //end for - for (j = 0; j < brush2->numsides; j++) - { - side2 = &brush2->sides[j]; - for (n = 0; n < brush1->numsides; n++) - { - side1 = &brush1->sides[n]; - //if the side is in the same plane - if (side2->planenum == side1->planenum) break; - //don't add the "shared" sides - if (side2->planenum == (side1->planenum ^ 1)) break; - } //end for - if (n < brush1->numsides) continue; - // - for (n = 0; n < newbrush->numsides; n++) - { - cs = &newbrush->sides[n]; - if (cs->planenum == side2->planenum) - { - Log_Print("brush duplicate plane\n"); - break; - } //end if - } //end if - if (n < newbrush->numsides) continue; - //add this side - cs = &newbrush->sides[newbrush->numsides]; - newbrush->numsides++; - *cs = *side2; - } //end for - BSPBrushWindings(newbrush); - BoundBrush(newbrush); - CheckBSPBrush(newbrush); - return newbrush; -} //end of the function TryMergeBrushes -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bspbrush_t *MergeBrushes(bspbrush_t *brushlist) -{ - int nummerges, merged; - bspbrush_t *b1, *b2, *tail, *newbrush, *newbrushlist; - bspbrush_t *lastb2; - - if (!brushlist) return NULL; - - qprintf("%5d brushes merged", nummerges = 0); - do - { - for (tail = brushlist; tail; tail = tail->next) - { - if (!tail->next) break; - } //end for - merged = 0; - newbrushlist = NULL; - for (b1 = brushlist; b1; b1 = brushlist) - { - lastb2 = b1; - for (b2 = b1->next; b2; b2 = b2->next) - { - //if the brushes don't have the same contents - if (b1->original->contents != b2->original->contents || - b1->original->expansionbbox != b2->original->expansionbbox) newbrush = NULL; - else newbrush = TryMergeBrushes(b1, b2); - if (newbrush) - { - tail->next = newbrush; - lastb2->next = b2->next; - brushlist = brushlist->next; - FreeBrush(b1); - FreeBrush(b2); - for (tail = brushlist; tail; tail = tail->next) - { - if (!tail->next) break; - } //end for - merged++; - qprintf("\r%5d", nummerges++); - break; - } //end if - lastb2 = b2; - } //end for - //if b1 can't be merged with any of the other brushes - if (!b2) - { - brushlist = brushlist->next; - //keep b1 - b1->next = newbrushlist; - newbrushlist = b1; - } //end else - } //end for - brushlist = newbrushlist; - } while(merged); - qprintf("\n"); - return newbrushlist; -} //end of the function MergeBrushes -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void SplitBrush2 (bspbrush_t *brush, int planenum, - bspbrush_t **front, bspbrush_t **back) -{ - SplitBrush (brush, planenum, front, back); -#if 0 - if (*front && (*front)->sides[(*front)->numsides-1].texinfo == -1) - (*front)->sides[(*front)->numsides-1].texinfo = (*front)->sides[0].texinfo; // not -1 - if (*back && (*back)->sides[(*back)->numsides-1].texinfo == -1) - (*back)->sides[(*back)->numsides-1].texinfo = (*back)->sides[0].texinfo; // not -1 -#endif -} //end of the function SplitBrush2 -//=========================================================================== -// Returns a list of brushes that remain after B is subtracted from A. -// May by empty if A is contained inside B. -// The originals are undisturbed. -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bspbrush_t *SubtractBrush (bspbrush_t *a, bspbrush_t *b) -{ // a - b = out (list) - int i; - bspbrush_t *front, *back; - bspbrush_t *out, *in; - - in = a; - out = NULL; - for (i = 0; i < b->numsides && in; i++) - { - SplitBrush2(in, b->sides[i].planenum, &front, &back); - if (in != a) FreeBrush(in); - if (front) - { // add to list - front->next = out; - out = front; - } //end if - in = back; - } //end for - if (in) - { - FreeBrush (in); - } //end if - else - { // didn't really intersect - FreeBrushList (out); - return a; - } //end else - return out; -} //end of the function SubtractBrush -//=========================================================================== -// Returns a single brush made up by the intersection of the -// two provided brushes, or NULL if they are disjoint. -// -// The originals are undisturbed. -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bspbrush_t *IntersectBrush (bspbrush_t *a, bspbrush_t *b) -{ - int i; - bspbrush_t *front, *back; - bspbrush_t *in; - - in = a; - for (i=0 ; inumsides && in ; i++) - { - SplitBrush2(in, b->sides[i].planenum, &front, &back); - if (in != a) FreeBrush(in); - if (front) FreeBrush(front); - in = back; - } //end for - - if (in == a) return NULL; - - in->next = NULL; - return in; -} //end of the function IntersectBrush -//=========================================================================== -// Returns true if the two brushes definately do not intersect. -// There will be false negatives for some non-axial combinations. -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean BrushesDisjoint (bspbrush_t *a, bspbrush_t *b) -{ - int i, j; - - // check bounding boxes - for (i=0 ; i<3 ; i++) - if (a->mins[i] >= b->maxs[i] - || a->maxs[i] <= b->mins[i]) - return true; // bounding boxes don't overlap - - // check for opposing planes - for (i=0 ; inumsides ; i++) - { - for (j=0 ; jnumsides ; j++) - { - if (a->sides[i].planenum == - (b->sides[j].planenum^1) ) - return true; // opposite planes, so not touching - } - } - - return false; // might intersect -} //end of the function BrushesDisjoint -//=========================================================================== -// Returns a content word for the intersection of two brushes. -// Some combinations will generate a combination (water + clip), -// but most will be the stronger of the two contents. -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int IntersectionContents (int c1, int c2) -{ - int out; - - out = c1 | c2; - - if (out & CONTENTS_SOLID) out = CONTENTS_SOLID; - - return out; -} //end of the function IntersectionContents -//=========================================================================== -// Any planes shared with the box edge will be set to no texinfo -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bspbrush_t *ClipBrushToBox(bspbrush_t *brush, vec3_t clipmins, vec3_t clipmaxs) -{ - int i, j; - bspbrush_t *front, *back; - int p; - - for (j=0 ; j<2 ; j++) - { - if (brush->maxs[j] > clipmaxs[j]) - { - SplitBrush (brush, maxplanenums[j], &front, &back); - if (front) - FreeBrush (front); - brush = back; - if (!brush) - return NULL; - } - if (brush->mins[j] < clipmins[j]) - { - SplitBrush (brush, minplanenums[j], &front, &back); - if (back) - FreeBrush (back); - brush = front; - if (!brush) - return NULL; - } - } - - // remove any colinear faces - - for (i=0 ; inumsides ; i++) - { - p = brush->sides[i].planenum & ~1; - if (p == maxplanenums[0] || p == maxplanenums[1] - || p == minplanenums[0] || p == minplanenums[1]) - { - brush->sides[i].texinfo = TEXINFO_NODE; - brush->sides[i].flags &= ~SFL_VISIBLE; - } - } - return brush; -} //end of the function ClipBrushToBox -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bspbrush_t *MakeBspBrushList(int startbrush, int endbrush, - vec3_t clipmins, vec3_t clipmaxs) -{ - mapbrush_t *mb; - bspbrush_t *brushlist, *newbrush; - int i, j; - int c_faces; - int c_brushes; - int numsides; - int vis; - vec3_t normal; - float dist; - - for (i=0 ; i<2 ; i++) - { - VectorClear (normal); - normal[i] = 1; - dist = clipmaxs[i]; - maxplanenums[i] = FindFloatPlane(normal, dist); - dist = clipmins[i]; - minplanenums[i] = FindFloatPlane(normal, dist); - } - - brushlist = NULL; - c_faces = 0; - c_brushes = 0; - - for (i=startbrush ; inumsides; - if (!numsides) - continue; - - // make sure the brush has at least one face showing - vis = 0; - for (j=0 ; joriginal_sides[j].flags & SFL_VISIBLE) && mb->original_sides[j].winding) - vis++; -#if 0 - if (!vis) - continue; // no faces at all -#endif - // if the brush is outside the clip area, skip it - for (j=0 ; j<3 ; j++) - if (mb->mins[j] >= clipmaxs[j] - || mb->maxs[j] <= clipmins[j]) - break; - if (j != 3) - continue; - - // - // make a copy of the brush - // - newbrush = AllocBrush (mb->numsides); - newbrush->original = mb; - newbrush->numsides = mb->numsides; - memcpy (newbrush->sides, mb->original_sides, numsides*sizeof(side_t)); - for (j=0 ; jsides[j].winding) - newbrush->sides[j].winding = CopyWinding (newbrush->sides[j].winding); - if (newbrush->sides[j].surf & SURF_HINT) - newbrush->sides[j].flags |= SFL_VISIBLE; // hints are always visible - } - VectorCopy (mb->mins, newbrush->mins); - VectorCopy (mb->maxs, newbrush->maxs); - - // - // carve off anything outside the clip box - // - newbrush = ClipBrushToBox (newbrush, clipmins, clipmaxs); - if (!newbrush) - continue; - - c_faces += vis; - c_brushes++; - - newbrush->next = brushlist; - brushlist = newbrush; - } - - return brushlist; -} //end of the function MakeBspBrushList -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bspbrush_t *AddBrushListToTail (bspbrush_t *list, bspbrush_t *tail) -{ - bspbrush_t *walk, *next; - - for (walk=list ; walk ; walk=next) - { // add to end of list - next = walk->next; - walk->next = NULL; - tail->next = walk; - tail = walk; - } //end for - return tail; -} //end of the function AddBrushListToTail -//=========================================================================== -// Builds a new list that doesn't hold the given brush -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bspbrush_t *CullList(bspbrush_t *list, bspbrush_t *skip1) -{ - bspbrush_t *newlist; - bspbrush_t *next; - - newlist = NULL; - - for ( ; list ; list = next) - { - next = list->next; - if (list == skip1) - { - FreeBrush (list); - continue; - } - list->next = newlist; - newlist = list; - } - return newlist; -} //end of the function CullList -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -/* -void WriteBrushMap(char *name, bspbrush_t *list) -{ - FILE *f; - side_t *s; - int i; - winding_t *w; - - Log_Print("writing %s\n", name); - f = fopen (name, "wb"); - if (!f) - Error ("Can't write %s\b", name); - - fprintf (f, "{\n\"classname\" \"worldspawn\"\n"); - - for ( ; list ; list=list->next ) - { - fprintf (f, "{\n"); - for (i=0,s=list->sides ; inumsides ; i++,s++) - { - w = BaseWindingForPlane (mapplanes[s->planenum].normal, mapplanes[s->planenum].dist); - - fprintf (f,"( %i %i %i ) ", (int)w->p[0][0], (int)w->p[0][1], (int)w->p[0][2]); - fprintf (f,"( %i %i %i ) ", (int)w->p[1][0], (int)w->p[1][1], (int)w->p[1][2]); - fprintf (f,"( %i %i %i ) ", (int)w->p[2][0], (int)w->p[2][1], (int)w->p[2][2]); - - fprintf (f, "%s 0 0 0 1 1\n", texinfo[s->texinfo].texture); - FreeWinding (w); - } - fprintf (f, "}\n"); - } - fprintf (f, "}\n"); - - fclose (f); -} //end of the function WriteBrushMap -*/ -//=========================================================================== -// Returns true if b1 is allowed to bite b2 -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean BrushGE (bspbrush_t *b1, bspbrush_t *b2) -{ -#ifdef ME - if (create_aas) - { - if (b1->original->expansionbbox != b2->original->expansionbbox) - { - return false; - } //end if - //never have something else bite a ladder brush - //never have a ladder brush bite something else - if ( (b1->original->contents & CONTENTS_LADDER) - && !(b2->original->contents & CONTENTS_LADDER)) - { - return false; - } //end if - } //end if -#endif //ME - // detail brushes never bite structural brushes - if ( (b1->original->contents & CONTENTS_DETAIL) - && !(b2->original->contents & CONTENTS_DETAIL) ) - { - return false; - } //end if - if (b1->original->contents & CONTENTS_SOLID) - { - return true; - } //end if - return false; -} //end of the function BrushGE -//=========================================================================== -// Carves any intersecting solid brushes into the minimum number -// of non-intersecting brushes. -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bspbrush_t *ChopBrushes (bspbrush_t *head) -{ - bspbrush_t *b1, *b2, *next; - bspbrush_t *tail; - bspbrush_t *keep; - bspbrush_t *sub, *sub2; - int c1, c2; - int num_csg_iterations; - - Log_Print("-------- Brush CSG ---------\n"); - Log_Print("%6d original brushes\n", CountBrushList (head)); - - num_csg_iterations = 0; - qprintf("%6d output brushes", num_csg_iterations); - -#if 0 - if (startbrush == 0) - WriteBrushList ("before.gl", head, false); -#endif - keep = NULL; - -newlist: - // find tail - if (!head) return NULL; - - for (tail = head; tail->next; tail = tail->next) - ; - - for (b1=head ; b1 ; b1=next) - { - next = b1->next; - - //if the conversion is cancelled - if (cancelconversion) - { - b1->next = keep; - keep = b1; - continue; - } //end if - - for (b2 = b1->next; b2; b2 = b2->next) - { - if (BrushesDisjoint (b1, b2)) - continue; - - sub = NULL; - sub2 = NULL; - c1 = 999999; - c2 = 999999; - - if (BrushGE (b2, b1)) - { - sub = SubtractBrush (b1, b2); - if (sub == b1) - { - continue; // didn't really intersect - } //end if - if (!sub) - { // b1 is swallowed by b2 - head = CullList (b1, b1); - goto newlist; - } - c1 = CountBrushList (sub); - } - - if ( BrushGE (b1, b2) ) - { - sub2 = SubtractBrush (b2, b1); - if (sub2 == b2) - continue; // didn't really intersect - if (!sub2) - { // b2 is swallowed by b1 - FreeBrushList (sub); - head = CullList (b1, b2); - goto newlist; - } - c2 = CountBrushList (sub2); - } - - if (!sub && !sub2) - continue; // neither one can bite - - // only accept if it didn't fragment - // (commenting this out allows full fragmentation) - if (c1 > 1 && c2 > 1) - { - if (sub2) - FreeBrushList (sub2); - if (sub) - FreeBrushList (sub); - continue; - } - - if (c1 < c2) - { - if (sub2) FreeBrushList (sub2); - tail = AddBrushListToTail (sub, tail); - head = CullList (b1, b1); - goto newlist; - } //end if - else - { - if (sub) FreeBrushList (sub); - tail = AddBrushListToTail (sub2, tail); - head = CullList (b1, b2); - goto newlist; - } //end else - } //end for - - if (!b2) - { // b1 is no longer intersecting anything, so keep it - b1->next = keep; - keep = b1; - } //end if - num_csg_iterations++; - qprintf("\r%6d", num_csg_iterations); - } //end for - - if (cancelconversion) return keep; - // - qprintf("\n"); - Log_Write("%6d output brushes\r\n", num_csg_iterations); - -#if 0 - { - WriteBrushList ("after.gl", keep, false); - WriteBrushMap ("after.map", keep); - } -#endif - - return keep; -} //end of the function ChopBrushes -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bspbrush_t *InitialBrushList (bspbrush_t *list) -{ - bspbrush_t *b; - bspbrush_t *out, *newb; - int i; - - // only return brushes that have visible faces - out = NULL; - for (b=list ; b ; b=b->next) - { -#if 0 - for (i=0 ; inumsides ; i++) - if (b->sides[i].flags & SFL_VISIBLE) - break; - if (i == b->numsides) - continue; -#endif - newb = CopyBrush (b); - newb->next = out; - out = newb; - - // clear visible, so it must be set by MarkVisibleFaces_r - // to be used in the optimized list - for (i=0 ; inumsides ; i++) - { - newb->sides[i].original = &b->sides[i]; -// newb->sides[i].visible = true; - b->sides[i].flags &= ~SFL_VISIBLE; - } - } - - return out; -} //end of the function InitialBrushList -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bspbrush_t *OptimizedBrushList (bspbrush_t *list) -{ - bspbrush_t *b; - bspbrush_t *out, *newb; - int i; - - // only return brushes that have visible faces - out = NULL; - for (b=list ; b ; b=b->next) - { - for (i=0 ; inumsides ; i++) - if (b->sides[i].flags & SFL_VISIBLE) - break; - if (i == b->numsides) - continue; - newb = CopyBrush (b); - newb->next = out; - out = newb; - } //end for - -// WriteBrushList ("vis.gl", out, true); - return out; -} //end of the function OptimizeBrushList -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -tree_t *ProcessWorldBrushes(int brush_start, int brush_end) -{ - bspbrush_t *brushes; - tree_t *tree; - node_t *node; - vec3_t mins, maxs; - - //take the whole world - mins[0] = map_mins[0] - 8; - mins[1] = map_mins[1] - 8; - mins[2] = map_mins[2] - 8; - - maxs[0] = map_maxs[0] + 8; - maxs[1] = map_maxs[1] + 8; - maxs[2] = map_maxs[2] + 8; - - //reset the brush bsp - ResetBrushBSP(); - - // the makelist and chopbrushes could be cached between the passes... - - //create a list with brushes that are within the given mins/maxs - //some brushes will be cut and only the part that falls within the - //mins/maxs will be in the bush list - brushes = MakeBspBrushList(brush_start, brush_end, mins, maxs); - // - - if (!brushes) - { - node = AllocNode (); - node->planenum = PLANENUM_LEAF; - node->contents = CONTENTS_SOLID; - - tree = Tree_Alloc(); - tree->headnode = node; - VectorCopy(mins, tree->mins); - VectorCopy(maxs, tree->maxs); - } //end if - else - { - //Carves any intersecting solid brushes into the minimum number - //of non-intersecting brushes. - if (!nocsg) - { - brushes = ChopBrushes(brushes); - /* - if (create_aas) - { - brushes = MergeBrushes(brushes); - } //end if*/ - } //end if - //if the conversion is cancelled - if (cancelconversion) - { - FreeBrushList(brushes); - return NULL; - } //end if - //create the actual bsp tree - tree = BrushBSP(brushes, mins, maxs); - } //end else - //return the tree - return tree; -} //end of the function ProcessWorldBrushes +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "qbsp.h" + +/* + +tag all brushes with original contents +brushes may contain multiple contents +there will be no brush overlap after csg phase + +*/ + +int minplanenums[3]; +int maxplanenums[3]; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void CheckBSPBrush(bspbrush_t *brush) +{ + int i, j; + plane_t *plane1, *plane2; + + //check if the brush is convex... flipped planes make a brush non-convex + for (i = 0; i < brush->numsides; i++) + { + for (j = 0; j < brush->numsides; j++) + { + if (i == j) continue; + plane1 = &mapplanes[brush->sides[i].planenum]; + plane2 = &mapplanes[brush->sides[j].planenum]; + // + if (WindingsNonConvex(brush->sides[i].winding, + brush->sides[j].winding, + plane1->normal, plane2->normal, + plane1->dist, plane2->dist)) + { + Log_Print("non convex brush"); + break; + } //end if + } //end for + } //end for + BoundBrush(brush); + //check for out of bound brushes + for (i = 0; i < 3; i++) + { + if (brush->mins[i] < -MAX_MAP_BOUNDS || brush->maxs[i] > MAX_MAP_BOUNDS) + { + Log_Print("brush: bounds out of range\n"); + Log_Print("ob->mins[%d] = %f, ob->maxs[%d] = %f\n", i, brush->mins[i], i, brush->maxs[i]); + break; + } //end if + if (brush->mins[i] > MAX_MAP_BOUNDS || brush->maxs[i] < -MAX_MAP_BOUNDS) + { + Log_Print("brush: no visible sides on brush\n"); + Log_Print("ob->mins[%d] = %f, ob->maxs[%d] = %f\n", i, brush->mins[i], i, brush->maxs[i]); + break; + } //end if + } //end for +} //end of the function CheckBSPBrush +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BSPBrushWindings(bspbrush_t *brush) +{ + int i, j; + winding_t *w; + plane_t *plane; + + for (i = 0; i < brush->numsides; i++) + { + plane = &mapplanes[brush->sides[i].planenum]; + w = BaseWindingForPlane(plane->normal, plane->dist); + for (j = 0; j < brush->numsides && w; j++) + { + if (i == j) continue; + plane = &mapplanes[brush->sides[j].planenum^1]; + ChopWindingInPlace(&w, plane->normal, plane->dist, 0); //CLIP_EPSILON); + } //end for + brush->sides[i].winding = w; + } //end for +} //end of the function BSPBrushWindings +//=========================================================================== +// NOTE: can't keep brush->original intact +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *TryMergeBrushes(bspbrush_t *brush1, bspbrush_t *brush2) +{ + int i, j, k, n, shared; + side_t *side1, *side2, *cs; + plane_t *plane1, *plane2; + bspbrush_t *newbrush; + + //check for bounding box overlapp + for (i = 0; i < 3; i++) + { + if (brush1->mins[i] > brush2->maxs[i] + 2 + || brush1->maxs[i] < brush2->mins[i] - 2) + { + return NULL; + } //end if + } //end for + // + shared = 0; + //check if the brush is convex... flipped planes make a brush non-convex + for (i = 0; i < brush1->numsides; i++) + { + side1 = &brush1->sides[i]; + //don't check the "shared" sides + for (k = 0; k < brush2->numsides; k++) + { + side2 = &brush2->sides[k]; + if (side1->planenum == (side2->planenum^1)) + { + shared++; + //there may only be ONE shared side + if (shared > 1) return NULL; + break; + } //end if + } //end for + if (k < brush2->numsides) continue; + // + for (j = 0; j < brush2->numsides; j++) + { + side2 = &brush2->sides[j]; + //don't check the "shared" sides + for (n = 0; n < brush1->numsides; n++) + { + side1 = &brush1->sides[n]; + if (side1->planenum == (side2->planenum^1)) break; + } //end for + if (n < brush1->numsides) continue; + // + side1 = &brush1->sides[i]; + //if the side is in the same plane + //* + if (side1->planenum == side2->planenum) + { + if (side1->texinfo != TEXINFO_NODE && + side2->texinfo != TEXINFO_NODE && + side1->texinfo != side2->texinfo) return NULL; + continue; + } //end if + // + plane1 = &mapplanes[side1->planenum]; + plane2 = &mapplanes[side2->planenum]; + // + if (WindingsNonConvex(side1->winding, side2->winding, + plane1->normal, plane2->normal, + plane1->dist, plane2->dist)) + { + return NULL; + } //end if + } //end for + } //end for + newbrush = AllocBrush(brush1->numsides + brush2->numsides); + newbrush->original = brush1->original; + newbrush->numsides = 0; + //newbrush->side = brush1->side; //brush contents + //fix texinfos for sides lying in the same plane + for (i = 0; i < brush1->numsides; i++) + { + side1 = &brush1->sides[i]; + // + for (n = 0; n < brush2->numsides; n++) + { + side2 = &brush2->sides[n]; + //if both sides are in the same plane get the texinfo right + if (side1->planenum == side2->planenum) + { + if (side1->texinfo == TEXINFO_NODE) side1->texinfo = side2->texinfo; + if (side2->texinfo == TEXINFO_NODE) side2->texinfo = side1->texinfo; + } //end if + } //end for + } //end for + // + for (i = 0; i < brush1->numsides; i++) + { + side1 = &brush1->sides[i]; + //don't add the "shared" sides + for (n = 0; n < brush2->numsides; n++) + { + side2 = &brush2->sides[n]; + if (side1->planenum == (side2->planenum ^ 1)) break; + } //end for + if (n < brush2->numsides) continue; + // + for (n = 0; n < newbrush->numsides; n++) + { + cs = &newbrush->sides[n]; + if (cs->planenum == side1->planenum) + { + Log_Print("brush duplicate plane\n"); + break; + } //end if + } //end if + if (n < newbrush->numsides) continue; + //add this side + cs = &newbrush->sides[newbrush->numsides]; + newbrush->numsides++; + *cs = *side1; + } //end for + for (j = 0; j < brush2->numsides; j++) + { + side2 = &brush2->sides[j]; + for (n = 0; n < brush1->numsides; n++) + { + side1 = &brush1->sides[n]; + //if the side is in the same plane + if (side2->planenum == side1->planenum) break; + //don't add the "shared" sides + if (side2->planenum == (side1->planenum ^ 1)) break; + } //end for + if (n < brush1->numsides) continue; + // + for (n = 0; n < newbrush->numsides; n++) + { + cs = &newbrush->sides[n]; + if (cs->planenum == side2->planenum) + { + Log_Print("brush duplicate plane\n"); + break; + } //end if + } //end if + if (n < newbrush->numsides) continue; + //add this side + cs = &newbrush->sides[newbrush->numsides]; + newbrush->numsides++; + *cs = *side2; + } //end for + BSPBrushWindings(newbrush); + BoundBrush(newbrush); + CheckBSPBrush(newbrush); + return newbrush; +} //end of the function TryMergeBrushes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *MergeBrushes(bspbrush_t *brushlist) +{ + int nummerges, merged; + bspbrush_t *b1, *b2, *tail, *newbrush, *newbrushlist; + bspbrush_t *lastb2; + + if (!brushlist) return NULL; + + qprintf("%5d brushes merged", nummerges = 0); + do + { + for (tail = brushlist; tail; tail = tail->next) + { + if (!tail->next) break; + } //end for + merged = 0; + newbrushlist = NULL; + for (b1 = brushlist; b1; b1 = brushlist) + { + lastb2 = b1; + for (b2 = b1->next; b2; b2 = b2->next) + { + //if the brushes don't have the same contents + if (b1->original->contents != b2->original->contents || + b1->original->expansionbbox != b2->original->expansionbbox) newbrush = NULL; + else newbrush = TryMergeBrushes(b1, b2); + if (newbrush) + { + tail->next = newbrush; + lastb2->next = b2->next; + brushlist = brushlist->next; + FreeBrush(b1); + FreeBrush(b2); + for (tail = brushlist; tail; tail = tail->next) + { + if (!tail->next) break; + } //end for + merged++; + qprintf("\r%5d", nummerges++); + break; + } //end if + lastb2 = b2; + } //end for + //if b1 can't be merged with any of the other brushes + if (!b2) + { + brushlist = brushlist->next; + //keep b1 + b1->next = newbrushlist; + newbrushlist = b1; + } //end else + } //end for + brushlist = newbrushlist; + } while(merged); + qprintf("\n"); + return newbrushlist; +} //end of the function MergeBrushes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void SplitBrush2 (bspbrush_t *brush, int planenum, + bspbrush_t **front, bspbrush_t **back) +{ + SplitBrush (brush, planenum, front, back); +#if 0 + if (*front && (*front)->sides[(*front)->numsides-1].texinfo == -1) + (*front)->sides[(*front)->numsides-1].texinfo = (*front)->sides[0].texinfo; // not -1 + if (*back && (*back)->sides[(*back)->numsides-1].texinfo == -1) + (*back)->sides[(*back)->numsides-1].texinfo = (*back)->sides[0].texinfo; // not -1 +#endif +} //end of the function SplitBrush2 +//=========================================================================== +// Returns a list of brushes that remain after B is subtracted from A. +// May by empty if A is contained inside B. +// The originals are undisturbed. +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *SubtractBrush (bspbrush_t *a, bspbrush_t *b) +{ // a - b = out (list) + int i; + bspbrush_t *front, *back; + bspbrush_t *out, *in; + + in = a; + out = NULL; + for (i = 0; i < b->numsides && in; i++) + { + SplitBrush2(in, b->sides[i].planenum, &front, &back); + if (in != a) FreeBrush(in); + if (front) + { // add to list + front->next = out; + out = front; + } //end if + in = back; + } //end for + if (in) + { + FreeBrush (in); + } //end if + else + { // didn't really intersect + FreeBrushList (out); + return a; + } //end else + return out; +} //end of the function SubtractBrush +//=========================================================================== +// Returns a single brush made up by the intersection of the +// two provided brushes, or NULL if they are disjoint. +// +// The originals are undisturbed. +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *IntersectBrush (bspbrush_t *a, bspbrush_t *b) +{ + int i; + bspbrush_t *front, *back; + bspbrush_t *in; + + in = a; + for (i=0 ; inumsides && in ; i++) + { + SplitBrush2(in, b->sides[i].planenum, &front, &back); + if (in != a) FreeBrush(in); + if (front) FreeBrush(front); + in = back; + } //end for + + if (in == a) return NULL; + + in->next = NULL; + return in; +} //end of the function IntersectBrush +//=========================================================================== +// Returns true if the two brushes definately do not intersect. +// There will be false negatives for some non-axial combinations. +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean BrushesDisjoint (bspbrush_t *a, bspbrush_t *b) +{ + int i, j; + + // check bounding boxes + for (i=0 ; i<3 ; i++) + if (a->mins[i] >= b->maxs[i] + || a->maxs[i] <= b->mins[i]) + return true; // bounding boxes don't overlap + + // check for opposing planes + for (i=0 ; inumsides ; i++) + { + for (j=0 ; jnumsides ; j++) + { + if (a->sides[i].planenum == + (b->sides[j].planenum^1) ) + return true; // opposite planes, so not touching + } + } + + return false; // might intersect +} //end of the function BrushesDisjoint +//=========================================================================== +// Returns a content word for the intersection of two brushes. +// Some combinations will generate a combination (water + clip), +// but most will be the stronger of the two contents. +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int IntersectionContents (int c1, int c2) +{ + int out; + + out = c1 | c2; + + if (out & CONTENTS_SOLID) out = CONTENTS_SOLID; + + return out; +} //end of the function IntersectionContents +//=========================================================================== +// Any planes shared with the box edge will be set to no texinfo +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *ClipBrushToBox(bspbrush_t *brush, vec3_t clipmins, vec3_t clipmaxs) +{ + int i, j; + bspbrush_t *front, *back; + int p; + + for (j=0 ; j<2 ; j++) + { + if (brush->maxs[j] > clipmaxs[j]) + { + SplitBrush (brush, maxplanenums[j], &front, &back); + if (front) + FreeBrush (front); + brush = back; + if (!brush) + return NULL; + } + if (brush->mins[j] < clipmins[j]) + { + SplitBrush (brush, minplanenums[j], &front, &back); + if (back) + FreeBrush (back); + brush = front; + if (!brush) + return NULL; + } + } + + // remove any colinear faces + + for (i=0 ; inumsides ; i++) + { + p = brush->sides[i].planenum & ~1; + if (p == maxplanenums[0] || p == maxplanenums[1] + || p == minplanenums[0] || p == minplanenums[1]) + { + brush->sides[i].texinfo = TEXINFO_NODE; + brush->sides[i].flags &= ~SFL_VISIBLE; + } + } + return brush; +} //end of the function ClipBrushToBox +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *MakeBspBrushList(int startbrush, int endbrush, + vec3_t clipmins, vec3_t clipmaxs) +{ + mapbrush_t *mb; + bspbrush_t *brushlist, *newbrush; + int i, j; + int c_faces; + int c_brushes; + int numsides; + int vis; + vec3_t normal; + float dist; + + for (i=0 ; i<2 ; i++) + { + VectorClear (normal); + normal[i] = 1; + dist = clipmaxs[i]; + maxplanenums[i] = FindFloatPlane(normal, dist); + dist = clipmins[i]; + minplanenums[i] = FindFloatPlane(normal, dist); + } + + brushlist = NULL; + c_faces = 0; + c_brushes = 0; + + for (i=startbrush ; inumsides; + if (!numsides) + continue; + + // make sure the brush has at least one face showing + vis = 0; + for (j=0 ; joriginal_sides[j].flags & SFL_VISIBLE) && mb->original_sides[j].winding) + vis++; +#if 0 + if (!vis) + continue; // no faces at all +#endif + // if the brush is outside the clip area, skip it + for (j=0 ; j<3 ; j++) + if (mb->mins[j] >= clipmaxs[j] + || mb->maxs[j] <= clipmins[j]) + break; + if (j != 3) + continue; + + // + // make a copy of the brush + // + newbrush = AllocBrush (mb->numsides); + newbrush->original = mb; + newbrush->numsides = mb->numsides; + memcpy (newbrush->sides, mb->original_sides, numsides*sizeof(side_t)); + for (j=0 ; jsides[j].winding) + newbrush->sides[j].winding = CopyWinding (newbrush->sides[j].winding); + if (newbrush->sides[j].surf & SURF_HINT) + newbrush->sides[j].flags |= SFL_VISIBLE; // hints are always visible + } + VectorCopy (mb->mins, newbrush->mins); + VectorCopy (mb->maxs, newbrush->maxs); + + // + // carve off anything outside the clip box + // + newbrush = ClipBrushToBox (newbrush, clipmins, clipmaxs); + if (!newbrush) + continue; + + c_faces += vis; + c_brushes++; + + newbrush->next = brushlist; + brushlist = newbrush; + } + + return brushlist; +} //end of the function MakeBspBrushList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *AddBrushListToTail (bspbrush_t *list, bspbrush_t *tail) +{ + bspbrush_t *walk, *next; + + for (walk=list ; walk ; walk=next) + { // add to end of list + next = walk->next; + walk->next = NULL; + tail->next = walk; + tail = walk; + } //end for + return tail; +} //end of the function AddBrushListToTail +//=========================================================================== +// Builds a new list that doesn't hold the given brush +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *CullList(bspbrush_t *list, bspbrush_t *skip1) +{ + bspbrush_t *newlist; + bspbrush_t *next; + + newlist = NULL; + + for ( ; list ; list = next) + { + next = list->next; + if (list == skip1) + { + FreeBrush (list); + continue; + } + list->next = newlist; + newlist = list; + } + return newlist; +} //end of the function CullList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +/* +void WriteBrushMap(char *name, bspbrush_t *list) +{ + FILE *f; + side_t *s; + int i; + winding_t *w; + + Log_Print("writing %s\n", name); + f = fopen (name, "wb"); + if (!f) + Error ("Can't write %s\b", name); + + fprintf (f, "{\n\"classname\" \"worldspawn\"\n"); + + for ( ; list ; list=list->next ) + { + fprintf (f, "{\n"); + for (i=0,s=list->sides ; inumsides ; i++,s++) + { + w = BaseWindingForPlane (mapplanes[s->planenum].normal, mapplanes[s->planenum].dist); + + fprintf (f,"( %i %i %i ) ", (int)w->p[0][0], (int)w->p[0][1], (int)w->p[0][2]); + fprintf (f,"( %i %i %i ) ", (int)w->p[1][0], (int)w->p[1][1], (int)w->p[1][2]); + fprintf (f,"( %i %i %i ) ", (int)w->p[2][0], (int)w->p[2][1], (int)w->p[2][2]); + + fprintf (f, "%s 0 0 0 1 1\n", texinfo[s->texinfo].texture); + FreeWinding (w); + } + fprintf (f, "}\n"); + } + fprintf (f, "}\n"); + + fclose (f); +} //end of the function WriteBrushMap +*/ +//=========================================================================== +// Returns true if b1 is allowed to bite b2 +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean BrushGE (bspbrush_t *b1, bspbrush_t *b2) +{ +#ifdef ME + if (create_aas) + { + if (b1->original->expansionbbox != b2->original->expansionbbox) + { + return false; + } //end if + //never have something else bite a ladder brush + //never have a ladder brush bite something else + if ( (b1->original->contents & CONTENTS_LADDER) + && !(b2->original->contents & CONTENTS_LADDER)) + { + return false; + } //end if + } //end if +#endif //ME + // detail brushes never bite structural brushes + if ( (b1->original->contents & CONTENTS_DETAIL) + && !(b2->original->contents & CONTENTS_DETAIL) ) + { + return false; + } //end if + if (b1->original->contents & CONTENTS_SOLID) + { + return true; + } //end if + return false; +} //end of the function BrushGE +//=========================================================================== +// Carves any intersecting solid brushes into the minimum number +// of non-intersecting brushes. +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *ChopBrushes (bspbrush_t *head) +{ + bspbrush_t *b1, *b2, *next; + bspbrush_t *tail; + bspbrush_t *keep; + bspbrush_t *sub, *sub2; + int c1, c2; + int num_csg_iterations; + + Log_Print("-------- Brush CSG ---------\n"); + Log_Print("%6d original brushes\n", CountBrushList (head)); + + num_csg_iterations = 0; + qprintf("%6d output brushes", num_csg_iterations); + +#if 0 + if (startbrush == 0) + WriteBrushList ("before.gl", head, false); +#endif + keep = NULL; + +newlist: + // find tail + if (!head) return NULL; + + for (tail = head; tail->next; tail = tail->next) + ; + + for (b1=head ; b1 ; b1=next) + { + next = b1->next; + + //if the conversion is cancelled + if (cancelconversion) + { + b1->next = keep; + keep = b1; + continue; + } //end if + + for (b2 = b1->next; b2; b2 = b2->next) + { + if (BrushesDisjoint (b1, b2)) + continue; + + sub = NULL; + sub2 = NULL; + c1 = 999999; + c2 = 999999; + + if (BrushGE (b2, b1)) + { + sub = SubtractBrush (b1, b2); + if (sub == b1) + { + continue; // didn't really intersect + } //end if + if (!sub) + { // b1 is swallowed by b2 + head = CullList (b1, b1); + goto newlist; + } + c1 = CountBrushList (sub); + } + + if ( BrushGE (b1, b2) ) + { + sub2 = SubtractBrush (b2, b1); + if (sub2 == b2) + continue; // didn't really intersect + if (!sub2) + { // b2 is swallowed by b1 + FreeBrushList (sub); + head = CullList (b1, b2); + goto newlist; + } + c2 = CountBrushList (sub2); + } + + if (!sub && !sub2) + continue; // neither one can bite + + // only accept if it didn't fragment + // (commenting this out allows full fragmentation) + if (c1 > 1 && c2 > 1) + { + if (sub2) + FreeBrushList (sub2); + if (sub) + FreeBrushList (sub); + continue; + } + + if (c1 < c2) + { + if (sub2) FreeBrushList (sub2); + tail = AddBrushListToTail (sub, tail); + head = CullList (b1, b1); + goto newlist; + } //end if + else + { + if (sub) FreeBrushList (sub); + tail = AddBrushListToTail (sub2, tail); + head = CullList (b1, b2); + goto newlist; + } //end else + } //end for + + if (!b2) + { // b1 is no longer intersecting anything, so keep it + b1->next = keep; + keep = b1; + } //end if + num_csg_iterations++; + qprintf("\r%6d", num_csg_iterations); + } //end for + + if (cancelconversion) return keep; + // + qprintf("\n"); + Log_Write("%6d output brushes\r\n", num_csg_iterations); + +#if 0 + { + WriteBrushList ("after.gl", keep, false); + WriteBrushMap ("after.map", keep); + } +#endif + + return keep; +} //end of the function ChopBrushes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *InitialBrushList (bspbrush_t *list) +{ + bspbrush_t *b; + bspbrush_t *out, *newb; + int i; + + // only return brushes that have visible faces + out = NULL; + for (b=list ; b ; b=b->next) + { +#if 0 + for (i=0 ; inumsides ; i++) + if (b->sides[i].flags & SFL_VISIBLE) + break; + if (i == b->numsides) + continue; +#endif + newb = CopyBrush (b); + newb->next = out; + out = newb; + + // clear visible, so it must be set by MarkVisibleFaces_r + // to be used in the optimized list + for (i=0 ; inumsides ; i++) + { + newb->sides[i].original = &b->sides[i]; +// newb->sides[i].visible = true; + b->sides[i].flags &= ~SFL_VISIBLE; + } + } + + return out; +} //end of the function InitialBrushList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *OptimizedBrushList (bspbrush_t *list) +{ + bspbrush_t *b; + bspbrush_t *out, *newb; + int i; + + // only return brushes that have visible faces + out = NULL; + for (b=list ; b ; b=b->next) + { + for (i=0 ; inumsides ; i++) + if (b->sides[i].flags & SFL_VISIBLE) + break; + if (i == b->numsides) + continue; + newb = CopyBrush (b); + newb->next = out; + out = newb; + } //end for + +// WriteBrushList ("vis.gl", out, true); + return out; +} //end of the function OptimizeBrushList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +tree_t *ProcessWorldBrushes(int brush_start, int brush_end) +{ + bspbrush_t *brushes; + tree_t *tree; + node_t *node; + vec3_t mins, maxs; + + //take the whole world + mins[0] = map_mins[0] - 8; + mins[1] = map_mins[1] - 8; + mins[2] = map_mins[2] - 8; + + maxs[0] = map_maxs[0] + 8; + maxs[1] = map_maxs[1] + 8; + maxs[2] = map_maxs[2] + 8; + + //reset the brush bsp + ResetBrushBSP(); + + // the makelist and chopbrushes could be cached between the passes... + + //create a list with brushes that are within the given mins/maxs + //some brushes will be cut and only the part that falls within the + //mins/maxs will be in the bush list + brushes = MakeBspBrushList(brush_start, brush_end, mins, maxs); + // + + if (!brushes) + { + node = AllocNode (); + node->planenum = PLANENUM_LEAF; + node->contents = CONTENTS_SOLID; + + tree = Tree_Alloc(); + tree->headnode = node; + VectorCopy(mins, tree->mins); + VectorCopy(maxs, tree->maxs); + } //end if + else + { + //Carves any intersecting solid brushes into the minimum number + //of non-intersecting brushes. + if (!nocsg) + { + brushes = ChopBrushes(brushes); + /* + if (create_aas) + { + brushes = MergeBrushes(brushes); + } //end if*/ + } //end if + //if the conversion is cancelled + if (cancelconversion) + { + FreeBrushList(brushes); + return NULL; + } //end if + //create the actual bsp tree + tree = BrushBSP(brushes, mins, maxs); + } //end else + //return the tree + return tree; +} //end of the function ProcessWorldBrushes diff --git a/code/bspc/faces.c b/code/bspc/faces.c index bcd3ef9..f05f5ae 100755 --- a/code/bspc/faces.c +++ b/code/bspc/faces.c @@ -1,978 +1,978 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// faces.c - -#include "qbsp.h" -#include "l_mem.h" - -/* - - some faces will be removed before saving, but still form nodes: - - the insides of sky volumes - meeting planes of different water current volumes - -*/ - -// undefine for dumb linear searches -#define USE_HASHING - -#define INTEGRAL_EPSILON 0.01 -#define POINT_EPSILON 0.5 -#define OFF_EPSILON 0.5 - -int c_merge; -int c_subdivide; - -int c_totalverts; -int c_uniqueverts; -int c_degenerate; -int c_tjunctions; -int c_faceoverflows; -int c_facecollapse; -int c_badstartverts; - -#define MAX_SUPERVERTS 512 -int superverts[MAX_SUPERVERTS]; -int numsuperverts; - -face_t *edgefaces[MAX_MAP_EDGES][2]; -int firstmodeledge = 1; -int firstmodelface; - -int c_tryedges; - -vec3_t edge_dir; -vec3_t edge_start; -vec_t edge_len; - -int num_edge_verts; -int edge_verts[MAX_MAP_VERTS]; - -face_t *NewFaceFromFace (face_t *f); - -//=========================================================================== - -typedef struct hashvert_s -{ - struct hashvert_s *next; - int num; -} hashvert_t; - - -#define HASH_SIZE 64 - - -int vertexchain[MAX_MAP_VERTS]; // the next vertex in a hash chain -int hashverts[HASH_SIZE*HASH_SIZE]; // a vertex number, or 0 for no verts - -face_t *edgefaces[MAX_MAP_EDGES][2]; - -//============================================================================ - - -unsigned HashVec (vec3_t vec) -{ - int x, y; - - x = (4096 + (int)(vec[0]+0.5)) >> 7; - y = (4096 + (int)(vec[1]+0.5)) >> 7; - - if ( x < 0 || x >= HASH_SIZE || y < 0 || y >= HASH_SIZE ) - Error ("HashVec: point outside valid range"); - - return y*HASH_SIZE + x; -} - -#ifdef USE_HASHING -/* -============= -GetVertex - -Uses hashing -============= -*/ -int GetVertexnum (vec3_t in) -{ - int h; - int i; - float *p; - vec3_t vert; - int vnum; - - c_totalverts++; - - for (i=0 ; i<3 ; i++) - { - if ( fabs(in[i] - Q_rint(in[i])) < INTEGRAL_EPSILON) - vert[i] = Q_rint(in[i]); - else - vert[i] = in[i]; - } - - h = HashVec (vert); - - for (vnum=hashverts[h] ; vnum ; vnum=vertexchain[vnum]) - { - p = dvertexes[vnum].point; - if ( fabs(p[0]-vert[0]) 4096) - Error ("GetVertexnum: outside +/- 4096"); - } - - // search for an existing vertex match - for (i=0, dv=dvertexes ; ipoint[j]; - if ( d > POINT_EPSILON || d < -POINT_EPSILON) - break; - } - if (j == 3) - return i; // a match - } - - // new point - if (numvertexes == MAX_MAP_VERTS) - Error ("MAX_MAP_VERTS"); - VectorCopy (v, dv->point); - numvertexes++; - c_uniqueverts++; - - return numvertexes-1; -} -#endif - - -/* -================== -FaceFromSuperverts - -The faces vertexes have been added to the superverts[] array, -and there may be more there than can be held in a face (MAXEDGES). - -If less, the faces vertexnums[] will be filled in, otherwise -face will reference a tree of split[] faces until all of the -vertexnums can be added. - -superverts[base] will become face->vertexnums[0], and the others -will be circularly filled in. -================== -*/ -void FaceFromSuperverts (node_t *node, face_t *f, int base) -{ - face_t *newf; - int remaining; - int i; - - remaining = numsuperverts; - while (remaining > MAXEDGES) - { // must split into two faces, because of vertex overload - c_faceoverflows++; - - newf = f->split[0] = NewFaceFromFace (f); - newf = f->split[0]; - newf->next = node->faces; - node->faces = newf; - - newf->numpoints = MAXEDGES; - for (i=0 ; ivertexnums[i] = superverts[(i+base)%numsuperverts]; - - f->split[1] = NewFaceFromFace (f); - f = f->split[1]; - f->next = node->faces; - node->faces = f; - - remaining -= (MAXEDGES-2); - base = (base+MAXEDGES-1)%numsuperverts; - } - - // copy the vertexes back to the face - f->numpoints = remaining; - for (i=0 ; ivertexnums[i] = superverts[(i+base)%numsuperverts]; -} - - -/* -================== -EmitFaceVertexes -================== -*/ -void EmitFaceVertexes (node_t *node, face_t *f) -{ - winding_t *w; - int i; - - if (f->merged || f->split[0] || f->split[1]) - return; - - w = f->w; - for (i=0 ; inumpoints ; i++) - { - if (noweld) - { // make every point unique - if (numvertexes == MAX_MAP_VERTS) - Error ("MAX_MAP_VERTS"); - superverts[i] = numvertexes; - VectorCopy (w->p[i], dvertexes[numvertexes].point); - numvertexes++; - c_uniqueverts++; - c_totalverts++; - } - else - superverts[i] = GetVertexnum (w->p[i]); - } - numsuperverts = w->numpoints; - - // this may fragment the face if > MAXEDGES - FaceFromSuperverts (node, f, 0); -} - -/* -================== -EmitVertexes_r -================== -*/ -void EmitVertexes_r (node_t *node) -{ - int i; - face_t *f; - - if (node->planenum == PLANENUM_LEAF) - return; - - for (f=node->faces ; f ; f=f->next) - { - EmitFaceVertexes (node, f); - } - - for (i=0 ; i<2 ; i++) - EmitVertexes_r (node->children[i]); -} - - -#ifdef USE_HASHING -/* -========== -FindEdgeVerts - -Uses the hash tables to cut down to a small number -========== -*/ -void FindEdgeVerts (vec3_t v1, vec3_t v2) -{ - int x1, x2, y1, y2, t; - int x, y; - int vnum; - -#if 0 -{ - int i; - num_edge_verts = numvertexes-1; - for (i=0 ; i> 7; - y1 = (4096 + (int)(v1[1]+0.5)) >> 7; - x2 = (4096 + (int)(v2[0]+0.5)) >> 7; - y2 = (4096 + (int)(v2[1]+0.5)) >> 7; - - if (x1 > x2) - { - t = x1; - x1 = x2; - x2 = t; - } - if (y1 > y2) - { - t = y1; - y1 = y2; - y2 = t; - } -#if 0 - x1--; - x2++; - y1--; - y2++; - if (x1 < 0) - x1 = 0; - if (x2 >= HASH_SIZE) - x2 = HASH_SIZE; - if (y1 < 0) - y1 = 0; - if (y2 >= HASH_SIZE) - y2 = HASH_SIZE; -#endif - num_edge_verts = 0; - for (x=x1 ; x <= x2 ; x++) - { - for (y=y1 ; y <= y2 ; y++) - { - for (vnum=hashverts[y*HASH_SIZE+x] ; vnum ; vnum=vertexchain[vnum]) - { - edge_verts[num_edge_verts++] = vnum; - } - } - } -} - -#else -/* -========== -FindEdgeVerts - -Forced a dumb check of everything -========== -*/ -void FindEdgeVerts (vec3_t v1, vec3_t v2) -{ - int i; - - num_edge_verts = numvertexes-1; - for (i=0 ; i= end) - continue; // off an end - VectorMA (edge_start, dist, edge_dir, exact); - VectorSubtract (p, exact, off); - error = VectorLength (off); - - if (fabs(error) > OFF_EPSILON) - continue; // not on the edge - - // break the edge - c_tjunctions++; - TestEdge (start, dist, p1, j, k+1); - TestEdge (dist, end, j, p2, k+1); - return; - } - - // the edge p1 to p2 is now free of tjunctions - if (numsuperverts >= MAX_SUPERVERTS) - Error ("MAX_SUPERVERTS"); - superverts[numsuperverts] = p1; - numsuperverts++; -} - -/* -================== -FixFaceEdges - -================== -*/ -void FixFaceEdges (node_t *node, face_t *f) -{ - int p1, p2; - int i; - vec3_t e2; - vec_t len; - int count[MAX_SUPERVERTS], start[MAX_SUPERVERTS]; - int base; - - if (f->merged || f->split[0] || f->split[1]) - return; - - numsuperverts = 0; - - for (i=0 ; inumpoints ; i++) - { - p1 = f->vertexnums[i]; - p2 = f->vertexnums[(i+1)%f->numpoints]; - - VectorCopy (dvertexes[p1].point, edge_start); - VectorCopy (dvertexes[p2].point, e2); - - FindEdgeVerts (edge_start, e2); - - VectorSubtract (e2, edge_start, edge_dir); - len = VectorNormalize(edge_dir); - - start[i] = numsuperverts; - TestEdge (0, len, p1, p2, 0); - - count[i] = numsuperverts - start[i]; - } - - if (numsuperverts < 3) - { // entire face collapsed - f->numpoints = 0; - c_facecollapse++; - return; - } - - // we want to pick a vertex that doesn't have tjunctions - // on either side, which can cause artifacts on trifans, - // especially underwater - for (i=0 ; inumpoints ; i++) - { - if (count[i] == 1 && count[(i+f->numpoints-1)%f->numpoints] == 1) - break; - } - if (i == f->numpoints) - { - f->badstartvert = true; - c_badstartverts++; - base = 0; - } - else - { // rotate the vertex order - base = start[i]; - } - - // this may fragment the face if > MAXEDGES - FaceFromSuperverts (node, f, base); -} - -/* -================== -FixEdges_r -================== -*/ -void FixEdges_r (node_t *node) -{ - int i; - face_t *f; - - if (node->planenum == PLANENUM_LEAF) - return; - - for (f=node->faces ; f ; f=f->next) - FixFaceEdges (node, f); - - for (i=0 ; i<2 ; i++) - FixEdges_r (node->children[i]); -} - -/* -=========== -FixTjuncs - -=========== -*/ -void FixTjuncs (node_t *headnode) -{ - // snap and merge all vertexes - qprintf ("---- snap verts ----\n"); - memset (hashverts, 0, sizeof(hashverts)); - c_totalverts = 0; - c_uniqueverts = 0; - c_faceoverflows = 0; - EmitVertexes_r (headnode); - qprintf ("%i unique from %i\n", c_uniqueverts, c_totalverts); - - // break edges on tjunctions - qprintf ("---- tjunc ----\n"); - c_tryedges = 0; - c_degenerate = 0; - c_facecollapse = 0; - c_tjunctions = 0; - if (!notjunc) - FixEdges_r (headnode); - qprintf ("%5i edges degenerated\n", c_degenerate); - qprintf ("%5i faces degenerated\n", c_facecollapse); - qprintf ("%5i edges added by tjunctions\n", c_tjunctions); - qprintf ("%5i faces added by tjunctions\n", c_faceoverflows); - qprintf ("%5i bad start verts\n", c_badstartverts); -} - - -//======================================================== - -int c_faces; - -face_t *AllocFace (void) -{ - face_t *f; - - f = GetMemory(sizeof(*f)); - memset (f, 0, sizeof(*f)); - c_faces++; - - return f; -} - -face_t *NewFaceFromFace (face_t *f) -{ - face_t *newf; - - newf = AllocFace (); - *newf = *f; - newf->merged = NULL; - newf->split[0] = newf->split[1] = NULL; - newf->w = NULL; - return newf; -} - -void FreeFace (face_t *f) -{ - if (f->w) - FreeWinding (f->w); - FreeMemory(f); - c_faces--; -} - -//======================================================== - -/* -================== -GetEdge - -Called by writebsp. -Don't allow four way edges -================== -*/ -int GetEdge2 (int v1, int v2, face_t *f) -{ - dedge_t *edge; - int i; - - c_tryedges++; - - if (!noshare) - { - for (i=firstmodeledge ; i < numedges ; i++) - { - edge = &dedges[i]; - if (v1 == edge->v[1] && v2 == edge->v[0] - && edgefaces[i][0]->contents == f->contents) - { - if (edgefaces[i][1]) - // printf ("WARNING: multiple backward edge\n"); - continue; - edgefaces[i][1] = f; - return -i; - } - #if 0 - if (v1 == edge->v[0] && v2 == edge->v[1]) - { - printf ("WARNING: multiple forward edge\n"); - return i; - } - #endif - } - } - -// emit an edge - if (numedges >= MAX_MAP_EDGES) - Error ("numedges == MAX_MAP_EDGES"); - edge = &dedges[numedges]; - numedges++; - edge->v[0] = v1; - edge->v[1] = v2; - edgefaces[numedges-1][0] = f; - - return numedges-1; -} - -/* -=========================================================================== - -FACE MERGING - -=========================================================================== -*/ - -/* -============= -TryMerge - -If two polygons share a common edge and the edges that meet at the -common points are both inside the other polygons, merge them - -Returns NULL if the faces couldn't be merged, or the new face. -The originals will NOT be freed. -============= -*/ -face_t *TryMerge (face_t *f1, face_t *f2, vec3_t planenormal) -{ - face_t *newf; - winding_t *nw; - - if (!f1->w || !f2->w) - return NULL; - if (f1->texinfo != f2->texinfo) - return NULL; - if (f1->planenum != f2->planenum) // on front and back sides - return NULL; - if (f1->contents != f2->contents) - return NULL; - - - nw = TryMergeWinding (f1->w, f2->w, planenormal); - if (!nw) - return NULL; - - c_merge++; - newf = NewFaceFromFace (f1); - newf->w = nw; - - f1->merged = newf; - f2->merged = newf; - - return newf; -} - -/* -=============== -MergeNodeFaces -=============== -*/ -void MergeNodeFaces (node_t *node) -{ - face_t *f1, *f2, *end; - face_t *merged; - plane_t *plane; - - plane = &mapplanes[node->planenum]; - merged = NULL; - - for (f1 = node->faces ; f1 ; f1 = f1->next) - { - if (f1->merged || f1->split[0] || f1->split[1]) - continue; - - for (f2 = node->faces ; f2 != f1 ; f2=f2->next) - { - if (f2->merged || f2->split[0] || f2->split[1]) - continue; - - //IDBUG: always passes the face's node's normal to TryMerge() - //regardless of which side the face is on. Approximately 50% of - //the time the face will be on the other side of node, and thus - //the result of the convex/concave test in TryMergeWinding(), - //which depends on the normal, is flipped. This causes faces - //that shouldn't be merged to be merged and faces that - //should be merged to not be merged. - //the following added line fixes this bug - //thanks to: Alexander Malmberg - plane = &mapplanes[f1->planenum]; - // - merged = TryMerge (f1, f2, plane->normal); - if (!merged) - continue; - - // add merged to the end of the node face list - // so it will be checked against all the faces again - for (end = node->faces ; end->next ; end = end->next) - ; - merged->next = NULL; - end->next = merged; - break; - } - } -} - -//===================================================================== - -/* -=============== -SubdivideFace - -Chop up faces that are larger than we want in the surface cache -=============== -*/ -void SubdivideFace (node_t *node, face_t *f) -{ - float mins, maxs; - vec_t v; - int axis, i; - texinfo_t *tex; - vec3_t temp; - vec_t dist; - winding_t *w, *frontw, *backw; - - if (f->merged) - return; - -// special (non-surface cached) faces don't need subdivision - tex = &texinfo[f->texinfo]; - - if ( tex->flags & (SURF_WARP|SURF_SKY) ) - { - return; - } - - for (axis = 0 ; axis < 2 ; axis++) - { - while (1) - { - mins = 999999; - maxs = -999999; - - VectorCopy (tex->vecs[axis], temp); - w = f->w; - for (i=0 ; inumpoints ; i++) - { - v = DotProduct (w->p[i], temp); - if (v < mins) - mins = v; - if (v > maxs) - maxs = v; - } -#if 0 - if (maxs - mins <= 0) - Error ("zero extents"); -#endif - if (axis == 2) - { // allow double high walls - if (maxs - mins <= subdivide_size/* *2 */) - break; - } - else if (maxs - mins <= subdivide_size) - break; - - // split it - c_subdivide++; - - v = VectorNormalize (temp); - - dist = (mins + subdivide_size - 16)/v; - - ClipWindingEpsilon (w, temp, dist, ON_EPSILON, &frontw, &backw); - if (!frontw || !backw) - Error ("SubdivideFace: didn't split the polygon"); - - f->split[0] = NewFaceFromFace (f); - f->split[0]->w = frontw; - f->split[0]->next = node->faces; - node->faces = f->split[0]; - - f->split[1] = NewFaceFromFace (f); - f->split[1]->w = backw; - f->split[1]->next = node->faces; - node->faces = f->split[1]; - - SubdivideFace (node, f->split[0]); - SubdivideFace (node, f->split[1]); - return; - } - } -} - -void SubdivideNodeFaces (node_t *node) -{ - face_t *f; - - for (f = node->faces ; f ; f=f->next) - { - SubdivideFace (node, f); - } -} - -//=========================================================================== - -int c_nodefaces; - - -/* -============ -FaceFromPortal - -============ -*/ -face_t *FaceFromPortal (portal_t *p, int pside) -{ - face_t *f; - side_t *side; - - side = p->side; - if (!side) - return NULL; // portal does not bridge different visible contents - - f = AllocFace (); - - f->texinfo = side->texinfo; - f->planenum = (side->planenum & ~1) | pside; - f->portal = p; - - if ( (p->nodes[pside]->contents & CONTENTS_WINDOW) - && VisibleContents(p->nodes[!pside]->contents^p->nodes[pside]->contents) == CONTENTS_WINDOW ) - return NULL; // don't show insides of windows - - if (pside) - { - f->w = ReverseWinding(p->winding); - f->contents = p->nodes[1]->contents; - } - else - { - f->w = CopyWinding(p->winding); - f->contents = p->nodes[0]->contents; - } - return f; -} - - -/* -=============== -MakeFaces_r - -If a portal will make a visible face, -mark the side that originally created it - - solid / empty : solid - solid / water : solid - water / empty : water - water / water : none -=============== -*/ -void MakeFaces_r (node_t *node) -{ - portal_t *p; - int s; - - // recurse down to leafs - if (node->planenum != PLANENUM_LEAF) - { - MakeFaces_r (node->children[0]); - MakeFaces_r (node->children[1]); - - // merge together all visible faces on the node - if (!nomerge) - MergeNodeFaces (node); - if (!nosubdiv) - SubdivideNodeFaces (node); - - return; - } - - // solid leafs never have visible faces - if (node->contents & CONTENTS_SOLID) - return; - - // see which portals are valid - for (p=node->portals ; p ; p = p->next[s]) - { - s = (p->nodes[1] == node); - - p->face[s] = FaceFromPortal (p, s); - if (p->face[s]) - { - c_nodefaces++; - p->face[s]->next = p->onnode->faces; - p->onnode->faces = p->face[s]; - } - } -} - -/* -============ -MakeFaces -============ -*/ -void MakeFaces (node_t *node) -{ - qprintf ("--- MakeFaces ---\n"); - c_merge = 0; - c_subdivide = 0; - c_nodefaces = 0; - - MakeFaces_r (node); - - qprintf ("%5i makefaces\n", c_nodefaces); - qprintf ("%5i merged\n", c_merge); - qprintf ("%5i subdivided\n", c_subdivide); -} +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// faces.c + +#include "qbsp.h" +#include "l_mem.h" + +/* + + some faces will be removed before saving, but still form nodes: + + the insides of sky volumes + meeting planes of different water current volumes + +*/ + +// undefine for dumb linear searches +#define USE_HASHING + +#define INTEGRAL_EPSILON 0.01 +#define POINT_EPSILON 0.5 +#define OFF_EPSILON 0.5 + +int c_merge; +int c_subdivide; + +int c_totalverts; +int c_uniqueverts; +int c_degenerate; +int c_tjunctions; +int c_faceoverflows; +int c_facecollapse; +int c_badstartverts; + +#define MAX_SUPERVERTS 512 +int superverts[MAX_SUPERVERTS]; +int numsuperverts; + +face_t *edgefaces[MAX_MAP_EDGES][2]; +int firstmodeledge = 1; +int firstmodelface; + +int c_tryedges; + +vec3_t edge_dir; +vec3_t edge_start; +vec_t edge_len; + +int num_edge_verts; +int edge_verts[MAX_MAP_VERTS]; + +face_t *NewFaceFromFace (face_t *f); + +//=========================================================================== + +typedef struct hashvert_s +{ + struct hashvert_s *next; + int num; +} hashvert_t; + + +#define HASH_SIZE 64 + + +int vertexchain[MAX_MAP_VERTS]; // the next vertex in a hash chain +int hashverts[HASH_SIZE*HASH_SIZE]; // a vertex number, or 0 for no verts + +face_t *edgefaces[MAX_MAP_EDGES][2]; + +//============================================================================ + + +unsigned HashVec (vec3_t vec) +{ + int x, y; + + x = (4096 + (int)(vec[0]+0.5)) >> 7; + y = (4096 + (int)(vec[1]+0.5)) >> 7; + + if ( x < 0 || x >= HASH_SIZE || y < 0 || y >= HASH_SIZE ) + Error ("HashVec: point outside valid range"); + + return y*HASH_SIZE + x; +} + +#ifdef USE_HASHING +/* +============= +GetVertex + +Uses hashing +============= +*/ +int GetVertexnum (vec3_t in) +{ + int h; + int i; + float *p; + vec3_t vert; + int vnum; + + c_totalverts++; + + for (i=0 ; i<3 ; i++) + { + if ( fabs(in[i] - Q_rint(in[i])) < INTEGRAL_EPSILON) + vert[i] = Q_rint(in[i]); + else + vert[i] = in[i]; + } + + h = HashVec (vert); + + for (vnum=hashverts[h] ; vnum ; vnum=vertexchain[vnum]) + { + p = dvertexes[vnum].point; + if ( fabs(p[0]-vert[0]) 4096) + Error ("GetVertexnum: outside +/- 4096"); + } + + // search for an existing vertex match + for (i=0, dv=dvertexes ; ipoint[j]; + if ( d > POINT_EPSILON || d < -POINT_EPSILON) + break; + } + if (j == 3) + return i; // a match + } + + // new point + if (numvertexes == MAX_MAP_VERTS) + Error ("MAX_MAP_VERTS"); + VectorCopy (v, dv->point); + numvertexes++; + c_uniqueverts++; + + return numvertexes-1; +} +#endif + + +/* +================== +FaceFromSuperverts + +The faces vertexes have been added to the superverts[] array, +and there may be more there than can be held in a face (MAXEDGES). + +If less, the faces vertexnums[] will be filled in, otherwise +face will reference a tree of split[] faces until all of the +vertexnums can be added. + +superverts[base] will become face->vertexnums[0], and the others +will be circularly filled in. +================== +*/ +void FaceFromSuperverts (node_t *node, face_t *f, int base) +{ + face_t *newf; + int remaining; + int i; + + remaining = numsuperverts; + while (remaining > MAXEDGES) + { // must split into two faces, because of vertex overload + c_faceoverflows++; + + newf = f->split[0] = NewFaceFromFace (f); + newf = f->split[0]; + newf->next = node->faces; + node->faces = newf; + + newf->numpoints = MAXEDGES; + for (i=0 ; ivertexnums[i] = superverts[(i+base)%numsuperverts]; + + f->split[1] = NewFaceFromFace (f); + f = f->split[1]; + f->next = node->faces; + node->faces = f; + + remaining -= (MAXEDGES-2); + base = (base+MAXEDGES-1)%numsuperverts; + } + + // copy the vertexes back to the face + f->numpoints = remaining; + for (i=0 ; ivertexnums[i] = superverts[(i+base)%numsuperverts]; +} + + +/* +================== +EmitFaceVertexes +================== +*/ +void EmitFaceVertexes (node_t *node, face_t *f) +{ + winding_t *w; + int i; + + if (f->merged || f->split[0] || f->split[1]) + return; + + w = f->w; + for (i=0 ; inumpoints ; i++) + { + if (noweld) + { // make every point unique + if (numvertexes == MAX_MAP_VERTS) + Error ("MAX_MAP_VERTS"); + superverts[i] = numvertexes; + VectorCopy (w->p[i], dvertexes[numvertexes].point); + numvertexes++; + c_uniqueverts++; + c_totalverts++; + } + else + superverts[i] = GetVertexnum (w->p[i]); + } + numsuperverts = w->numpoints; + + // this may fragment the face if > MAXEDGES + FaceFromSuperverts (node, f, 0); +} + +/* +================== +EmitVertexes_r +================== +*/ +void EmitVertexes_r (node_t *node) +{ + int i; + face_t *f; + + if (node->planenum == PLANENUM_LEAF) + return; + + for (f=node->faces ; f ; f=f->next) + { + EmitFaceVertexes (node, f); + } + + for (i=0 ; i<2 ; i++) + EmitVertexes_r (node->children[i]); +} + + +#ifdef USE_HASHING +/* +========== +FindEdgeVerts + +Uses the hash tables to cut down to a small number +========== +*/ +void FindEdgeVerts (vec3_t v1, vec3_t v2) +{ + int x1, x2, y1, y2, t; + int x, y; + int vnum; + +#if 0 +{ + int i; + num_edge_verts = numvertexes-1; + for (i=0 ; i> 7; + y1 = (4096 + (int)(v1[1]+0.5)) >> 7; + x2 = (4096 + (int)(v2[0]+0.5)) >> 7; + y2 = (4096 + (int)(v2[1]+0.5)) >> 7; + + if (x1 > x2) + { + t = x1; + x1 = x2; + x2 = t; + } + if (y1 > y2) + { + t = y1; + y1 = y2; + y2 = t; + } +#if 0 + x1--; + x2++; + y1--; + y2++; + if (x1 < 0) + x1 = 0; + if (x2 >= HASH_SIZE) + x2 = HASH_SIZE; + if (y1 < 0) + y1 = 0; + if (y2 >= HASH_SIZE) + y2 = HASH_SIZE; +#endif + num_edge_verts = 0; + for (x=x1 ; x <= x2 ; x++) + { + for (y=y1 ; y <= y2 ; y++) + { + for (vnum=hashverts[y*HASH_SIZE+x] ; vnum ; vnum=vertexchain[vnum]) + { + edge_verts[num_edge_verts++] = vnum; + } + } + } +} + +#else +/* +========== +FindEdgeVerts + +Forced a dumb check of everything +========== +*/ +void FindEdgeVerts (vec3_t v1, vec3_t v2) +{ + int i; + + num_edge_verts = numvertexes-1; + for (i=0 ; i= end) + continue; // off an end + VectorMA (edge_start, dist, edge_dir, exact); + VectorSubtract (p, exact, off); + error = VectorLength (off); + + if (fabs(error) > OFF_EPSILON) + continue; // not on the edge + + // break the edge + c_tjunctions++; + TestEdge (start, dist, p1, j, k+1); + TestEdge (dist, end, j, p2, k+1); + return; + } + + // the edge p1 to p2 is now free of tjunctions + if (numsuperverts >= MAX_SUPERVERTS) + Error ("MAX_SUPERVERTS"); + superverts[numsuperverts] = p1; + numsuperverts++; +} + +/* +================== +FixFaceEdges + +================== +*/ +void FixFaceEdges (node_t *node, face_t *f) +{ + int p1, p2; + int i; + vec3_t e2; + vec_t len; + int count[MAX_SUPERVERTS], start[MAX_SUPERVERTS]; + int base; + + if (f->merged || f->split[0] || f->split[1]) + return; + + numsuperverts = 0; + + for (i=0 ; inumpoints ; i++) + { + p1 = f->vertexnums[i]; + p2 = f->vertexnums[(i+1)%f->numpoints]; + + VectorCopy (dvertexes[p1].point, edge_start); + VectorCopy (dvertexes[p2].point, e2); + + FindEdgeVerts (edge_start, e2); + + VectorSubtract (e2, edge_start, edge_dir); + len = VectorNormalize(edge_dir); + + start[i] = numsuperverts; + TestEdge (0, len, p1, p2, 0); + + count[i] = numsuperverts - start[i]; + } + + if (numsuperverts < 3) + { // entire face collapsed + f->numpoints = 0; + c_facecollapse++; + return; + } + + // we want to pick a vertex that doesn't have tjunctions + // on either side, which can cause artifacts on trifans, + // especially underwater + for (i=0 ; inumpoints ; i++) + { + if (count[i] == 1 && count[(i+f->numpoints-1)%f->numpoints] == 1) + break; + } + if (i == f->numpoints) + { + f->badstartvert = true; + c_badstartverts++; + base = 0; + } + else + { // rotate the vertex order + base = start[i]; + } + + // this may fragment the face if > MAXEDGES + FaceFromSuperverts (node, f, base); +} + +/* +================== +FixEdges_r +================== +*/ +void FixEdges_r (node_t *node) +{ + int i; + face_t *f; + + if (node->planenum == PLANENUM_LEAF) + return; + + for (f=node->faces ; f ; f=f->next) + FixFaceEdges (node, f); + + for (i=0 ; i<2 ; i++) + FixEdges_r (node->children[i]); +} + +/* +=========== +FixTjuncs + +=========== +*/ +void FixTjuncs (node_t *headnode) +{ + // snap and merge all vertexes + qprintf ("---- snap verts ----\n"); + memset (hashverts, 0, sizeof(hashverts)); + c_totalverts = 0; + c_uniqueverts = 0; + c_faceoverflows = 0; + EmitVertexes_r (headnode); + qprintf ("%i unique from %i\n", c_uniqueverts, c_totalverts); + + // break edges on tjunctions + qprintf ("---- tjunc ----\n"); + c_tryedges = 0; + c_degenerate = 0; + c_facecollapse = 0; + c_tjunctions = 0; + if (!notjunc) + FixEdges_r (headnode); + qprintf ("%5i edges degenerated\n", c_degenerate); + qprintf ("%5i faces degenerated\n", c_facecollapse); + qprintf ("%5i edges added by tjunctions\n", c_tjunctions); + qprintf ("%5i faces added by tjunctions\n", c_faceoverflows); + qprintf ("%5i bad start verts\n", c_badstartverts); +} + + +//======================================================== + +int c_faces; + +face_t *AllocFace (void) +{ + face_t *f; + + f = GetMemory(sizeof(*f)); + memset (f, 0, sizeof(*f)); + c_faces++; + + return f; +} + +face_t *NewFaceFromFace (face_t *f) +{ + face_t *newf; + + newf = AllocFace (); + *newf = *f; + newf->merged = NULL; + newf->split[0] = newf->split[1] = NULL; + newf->w = NULL; + return newf; +} + +void FreeFace (face_t *f) +{ + if (f->w) + FreeWinding (f->w); + FreeMemory(f); + c_faces--; +} + +//======================================================== + +/* +================== +GetEdge + +Called by writebsp. +Don't allow four way edges +================== +*/ +int GetEdge2 (int v1, int v2, face_t *f) +{ + dedge_t *edge; + int i; + + c_tryedges++; + + if (!noshare) + { + for (i=firstmodeledge ; i < numedges ; i++) + { + edge = &dedges[i]; + if (v1 == edge->v[1] && v2 == edge->v[0] + && edgefaces[i][0]->contents == f->contents) + { + if (edgefaces[i][1]) + // printf ("WARNING: multiple backward edge\n"); + continue; + edgefaces[i][1] = f; + return -i; + } + #if 0 + if (v1 == edge->v[0] && v2 == edge->v[1]) + { + printf ("WARNING: multiple forward edge\n"); + return i; + } + #endif + } + } + +// emit an edge + if (numedges >= MAX_MAP_EDGES) + Error ("numedges == MAX_MAP_EDGES"); + edge = &dedges[numedges]; + numedges++; + edge->v[0] = v1; + edge->v[1] = v2; + edgefaces[numedges-1][0] = f; + + return numedges-1; +} + +/* +=========================================================================== + +FACE MERGING + +=========================================================================== +*/ + +/* +============= +TryMerge + +If two polygons share a common edge and the edges that meet at the +common points are both inside the other polygons, merge them + +Returns NULL if the faces couldn't be merged, or the new face. +The originals will NOT be freed. +============= +*/ +face_t *TryMerge (face_t *f1, face_t *f2, vec3_t planenormal) +{ + face_t *newf; + winding_t *nw; + + if (!f1->w || !f2->w) + return NULL; + if (f1->texinfo != f2->texinfo) + return NULL; + if (f1->planenum != f2->planenum) // on front and back sides + return NULL; + if (f1->contents != f2->contents) + return NULL; + + + nw = TryMergeWinding (f1->w, f2->w, planenormal); + if (!nw) + return NULL; + + c_merge++; + newf = NewFaceFromFace (f1); + newf->w = nw; + + f1->merged = newf; + f2->merged = newf; + + return newf; +} + +/* +=============== +MergeNodeFaces +=============== +*/ +void MergeNodeFaces (node_t *node) +{ + face_t *f1, *f2, *end; + face_t *merged; + plane_t *plane; + + plane = &mapplanes[node->planenum]; + merged = NULL; + + for (f1 = node->faces ; f1 ; f1 = f1->next) + { + if (f1->merged || f1->split[0] || f1->split[1]) + continue; + + for (f2 = node->faces ; f2 != f1 ; f2=f2->next) + { + if (f2->merged || f2->split[0] || f2->split[1]) + continue; + + //IDBUG: always passes the face's node's normal to TryMerge() + //regardless of which side the face is on. Approximately 50% of + //the time the face will be on the other side of node, and thus + //the result of the convex/concave test in TryMergeWinding(), + //which depends on the normal, is flipped. This causes faces + //that shouldn't be merged to be merged and faces that + //should be merged to not be merged. + //the following added line fixes this bug + //thanks to: Alexander Malmberg + plane = &mapplanes[f1->planenum]; + // + merged = TryMerge (f1, f2, plane->normal); + if (!merged) + continue; + + // add merged to the end of the node face list + // so it will be checked against all the faces again + for (end = node->faces ; end->next ; end = end->next) + ; + merged->next = NULL; + end->next = merged; + break; + } + } +} + +//===================================================================== + +/* +=============== +SubdivideFace + +Chop up faces that are larger than we want in the surface cache +=============== +*/ +void SubdivideFace (node_t *node, face_t *f) +{ + float mins, maxs; + vec_t v; + int axis, i; + texinfo_t *tex; + vec3_t temp; + vec_t dist; + winding_t *w, *frontw, *backw; + + if (f->merged) + return; + +// special (non-surface cached) faces don't need subdivision + tex = &texinfo[f->texinfo]; + + if ( tex->flags & (SURF_WARP|SURF_SKY) ) + { + return; + } + + for (axis = 0 ; axis < 2 ; axis++) + { + while (1) + { + mins = 999999; + maxs = -999999; + + VectorCopy (tex->vecs[axis], temp); + w = f->w; + for (i=0 ; inumpoints ; i++) + { + v = DotProduct (w->p[i], temp); + if (v < mins) + mins = v; + if (v > maxs) + maxs = v; + } +#if 0 + if (maxs - mins <= 0) + Error ("zero extents"); +#endif + if (axis == 2) + { // allow double high walls + if (maxs - mins <= subdivide_size/* *2 */) + break; + } + else if (maxs - mins <= subdivide_size) + break; + + // split it + c_subdivide++; + + v = VectorNormalize (temp); + + dist = (mins + subdivide_size - 16)/v; + + ClipWindingEpsilon (w, temp, dist, ON_EPSILON, &frontw, &backw); + if (!frontw || !backw) + Error ("SubdivideFace: didn't split the polygon"); + + f->split[0] = NewFaceFromFace (f); + f->split[0]->w = frontw; + f->split[0]->next = node->faces; + node->faces = f->split[0]; + + f->split[1] = NewFaceFromFace (f); + f->split[1]->w = backw; + f->split[1]->next = node->faces; + node->faces = f->split[1]; + + SubdivideFace (node, f->split[0]); + SubdivideFace (node, f->split[1]); + return; + } + } +} + +void SubdivideNodeFaces (node_t *node) +{ + face_t *f; + + for (f = node->faces ; f ; f=f->next) + { + SubdivideFace (node, f); + } +} + +//=========================================================================== + +int c_nodefaces; + + +/* +============ +FaceFromPortal + +============ +*/ +face_t *FaceFromPortal (portal_t *p, int pside) +{ + face_t *f; + side_t *side; + + side = p->side; + if (!side) + return NULL; // portal does not bridge different visible contents + + f = AllocFace (); + + f->texinfo = side->texinfo; + f->planenum = (side->planenum & ~1) | pside; + f->portal = p; + + if ( (p->nodes[pside]->contents & CONTENTS_WINDOW) + && VisibleContents(p->nodes[!pside]->contents^p->nodes[pside]->contents) == CONTENTS_WINDOW ) + return NULL; // don't show insides of windows + + if (pside) + { + f->w = ReverseWinding(p->winding); + f->contents = p->nodes[1]->contents; + } + else + { + f->w = CopyWinding(p->winding); + f->contents = p->nodes[0]->contents; + } + return f; +} + + +/* +=============== +MakeFaces_r + +If a portal will make a visible face, +mark the side that originally created it + + solid / empty : solid + solid / water : solid + water / empty : water + water / water : none +=============== +*/ +void MakeFaces_r (node_t *node) +{ + portal_t *p; + int s; + + // recurse down to leafs + if (node->planenum != PLANENUM_LEAF) + { + MakeFaces_r (node->children[0]); + MakeFaces_r (node->children[1]); + + // merge together all visible faces on the node + if (!nomerge) + MergeNodeFaces (node); + if (!nosubdiv) + SubdivideNodeFaces (node); + + return; + } + + // solid leafs never have visible faces + if (node->contents & CONTENTS_SOLID) + return; + + // see which portals are valid + for (p=node->portals ; p ; p = p->next[s]) + { + s = (p->nodes[1] == node); + + p->face[s] = FaceFromPortal (p, s); + if (p->face[s]) + { + c_nodefaces++; + p->face[s]->next = p->onnode->faces; + p->onnode->faces = p->face[s]; + } + } +} + +/* +============ +MakeFaces +============ +*/ +void MakeFaces (node_t *node) +{ + qprintf ("--- MakeFaces ---\n"); + c_merge = 0; + c_subdivide = 0; + c_nodefaces = 0; + + MakeFaces_r (node); + + qprintf ("%5i makefaces\n", c_nodefaces); + qprintf ("%5i merged\n", c_merge); + qprintf ("%5i subdivided\n", c_subdivide); +} diff --git a/code/bspc/gldraw.c b/code/bspc/gldraw.c index 9f7d6f7..18ed2c8 100755 --- a/code/bspc/gldraw.c +++ b/code/bspc/gldraw.c @@ -1,232 +1,232 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include -#include -#include -#include - -#include "qbsp.h" - -// can't use the glvertex3fv functions, because the vec3_t fields -// could be either floats or doubles, depending on DOUBLEVEC_T - -qboolean drawflag; -vec3_t draw_mins, draw_maxs; - - -#define WIN_SIZE 512 - -void InitWindow (void) -{ - auxInitDisplayMode (AUX_SINGLE | AUX_RGB); - auxInitPosition (0, 0, WIN_SIZE, WIN_SIZE); - auxInitWindow ("qcsg"); -} - -void Draw_ClearWindow (void) -{ - static int init; - int w, h, g; - vec_t mx, my; - - if (!drawflag) - return; - - if (!init) - { - init = true; - InitWindow (); - } - - glClearColor (1,0.8,0.8,0); - glClear (GL_COLOR_BUFFER_BIT); - - w = (draw_maxs[0] - draw_mins[0]); - h = (draw_maxs[1] - draw_mins[1]); - - mx = draw_mins[0] + w/2; - my = draw_mins[1] + h/2; - - g = w > h ? w : h; - - glLoadIdentity (); - gluPerspective (90, 1, 2, 16384); - gluLookAt (mx, my, draw_maxs[2] + g/2, mx , my, draw_maxs[2], 0, 1, 0); - - glColor3f (0,0,0); -// glPolygonMode (GL_FRONT_AND_BACK, GL_LINE); - glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); - glDisable (GL_DEPTH_TEST); - glEnable (GL_BLEND); - glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - -#if 0 - glColor4f (1,0,0,0.5); - glBegin (GL_POLYGON); - - glVertex3f (0, 500, 0); - glVertex3f (0, 900, 0); - glVertex3f (0, 900, 100); - glVertex3f (0, 500, 100); - - glEnd (); -#endif - - glFlush (); - -} - -void Draw_SetRed (void) -{ - if (!drawflag) - return; - - glColor3f (1,0,0); -} - -void Draw_SetGrey (void) -{ - if (!drawflag) - return; - - glColor3f (0.5,0.5,0.5); -} - -void Draw_SetBlack (void) -{ - if (!drawflag) - return; - - glColor3f (0,0,0); -} - -void DrawWinding (winding_t *w) -{ - int i; - - if (!drawflag) - return; - - glColor4f (0,0,0,0.5); - glBegin (GL_LINE_LOOP); - for (i=0 ; inumpoints ; i++) - glVertex3f (w->p[i][0],w->p[i][1],w->p[i][2] ); - glEnd (); - - glColor4f (0,1,0,0.3); - glBegin (GL_POLYGON); - for (i=0 ; inumpoints ; i++) - glVertex3f (w->p[i][0],w->p[i][1],w->p[i][2] ); - glEnd (); - - glFlush (); -} - -void DrawAuxWinding (winding_t *w) -{ - int i; - - if (!drawflag) - return; - - glColor4f (0,0,0,0.5); - glBegin (GL_LINE_LOOP); - for (i=0 ; inumpoints ; i++) - glVertex3f (w->p[i][0],w->p[i][1],w->p[i][2] ); - glEnd (); - - glColor4f (1,0,0,0.3); - glBegin (GL_POLYGON); - for (i=0 ; inumpoints ; i++) - glVertex3f (w->p[i][0],w->p[i][1],w->p[i][2] ); - glEnd (); - - glFlush (); -} - -//============================================================ - -#define GLSERV_PORT 25001 - -qboolean wins_init; -int draw_socket; - -void GLS_BeginScene (void) -{ - WSADATA winsockdata; - WORD wVersionRequested; - struct sockaddr_in address; - int r; - - if (!wins_init) - { - wins_init = true; - - wVersionRequested = MAKEWORD(1, 1); - - r = WSAStartup (MAKEWORD(1, 1), &winsockdata); - - if (r) - Error ("Winsock initialization failed."); - - } - - // connect a socket to the server - - draw_socket = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP); - if (draw_socket == -1) - Error ("draw_socket failed"); - - address.sin_family = AF_INET; - address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - address.sin_port = GLSERV_PORT; - r = connect (draw_socket, (struct sockaddr *)&address, sizeof(address)); - if (r == -1) - { - closesocket (draw_socket); - draw_socket = 0; - } -} - -void GLS_Winding (winding_t *w, int code) -{ - byte buf[1024]; - int i, j; - - if (!draw_socket) - return; - - ((int *)buf)[0] = w->numpoints; - ((int *)buf)[1] = code; - for (i=0 ; inumpoints ; i++) - for (j=0 ; j<3 ; j++) - ((float *)buf)[2+i*3+j] = w->p[i][j]; - - send (draw_socket, buf, w->numpoints*12+8, 0); -} - -void GLS_EndScene (void) -{ - closesocket (draw_socket); - draw_socket = 0; -} +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include +#include +#include +#include + +#include "qbsp.h" + +// can't use the glvertex3fv functions, because the vec3_t fields +// could be either floats or doubles, depending on DOUBLEVEC_T + +qboolean drawflag; +vec3_t draw_mins, draw_maxs; + + +#define WIN_SIZE 512 + +void InitWindow (void) +{ + auxInitDisplayMode (AUX_SINGLE | AUX_RGB); + auxInitPosition (0, 0, WIN_SIZE, WIN_SIZE); + auxInitWindow ("qcsg"); +} + +void Draw_ClearWindow (void) +{ + static int init; + int w, h, g; + vec_t mx, my; + + if (!drawflag) + return; + + if (!init) + { + init = true; + InitWindow (); + } + + glClearColor (1,0.8,0.8,0); + glClear (GL_COLOR_BUFFER_BIT); + + w = (draw_maxs[0] - draw_mins[0]); + h = (draw_maxs[1] - draw_mins[1]); + + mx = draw_mins[0] + w/2; + my = draw_mins[1] + h/2; + + g = w > h ? w : h; + + glLoadIdentity (); + gluPerspective (90, 1, 2, 16384); + gluLookAt (mx, my, draw_maxs[2] + g/2, mx , my, draw_maxs[2], 0, 1, 0); + + glColor3f (0,0,0); +// glPolygonMode (GL_FRONT_AND_BACK, GL_LINE); + glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); + glDisable (GL_DEPTH_TEST); + glEnable (GL_BLEND); + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + +#if 0 + glColor4f (1,0,0,0.5); + glBegin (GL_POLYGON); + + glVertex3f (0, 500, 0); + glVertex3f (0, 900, 0); + glVertex3f (0, 900, 100); + glVertex3f (0, 500, 100); + + glEnd (); +#endif + + glFlush (); + +} + +void Draw_SetRed (void) +{ + if (!drawflag) + return; + + glColor3f (1,0,0); +} + +void Draw_SetGrey (void) +{ + if (!drawflag) + return; + + glColor3f (0.5,0.5,0.5); +} + +void Draw_SetBlack (void) +{ + if (!drawflag) + return; + + glColor3f (0,0,0); +} + +void DrawWinding (winding_t *w) +{ + int i; + + if (!drawflag) + return; + + glColor4f (0,0,0,0.5); + glBegin (GL_LINE_LOOP); + for (i=0 ; inumpoints ; i++) + glVertex3f (w->p[i][0],w->p[i][1],w->p[i][2] ); + glEnd (); + + glColor4f (0,1,0,0.3); + glBegin (GL_POLYGON); + for (i=0 ; inumpoints ; i++) + glVertex3f (w->p[i][0],w->p[i][1],w->p[i][2] ); + glEnd (); + + glFlush (); +} + +void DrawAuxWinding (winding_t *w) +{ + int i; + + if (!drawflag) + return; + + glColor4f (0,0,0,0.5); + glBegin (GL_LINE_LOOP); + for (i=0 ; inumpoints ; i++) + glVertex3f (w->p[i][0],w->p[i][1],w->p[i][2] ); + glEnd (); + + glColor4f (1,0,0,0.3); + glBegin (GL_POLYGON); + for (i=0 ; inumpoints ; i++) + glVertex3f (w->p[i][0],w->p[i][1],w->p[i][2] ); + glEnd (); + + glFlush (); +} + +//============================================================ + +#define GLSERV_PORT 25001 + +qboolean wins_init; +int draw_socket; + +void GLS_BeginScene (void) +{ + WSADATA winsockdata; + WORD wVersionRequested; + struct sockaddr_in address; + int r; + + if (!wins_init) + { + wins_init = true; + + wVersionRequested = MAKEWORD(1, 1); + + r = WSAStartup (MAKEWORD(1, 1), &winsockdata); + + if (r) + Error ("Winsock initialization failed."); + + } + + // connect a socket to the server + + draw_socket = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP); + if (draw_socket == -1) + Error ("draw_socket failed"); + + address.sin_family = AF_INET; + address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + address.sin_port = GLSERV_PORT; + r = connect (draw_socket, (struct sockaddr *)&address, sizeof(address)); + if (r == -1) + { + closesocket (draw_socket); + draw_socket = 0; + } +} + +void GLS_Winding (winding_t *w, int code) +{ + byte buf[1024]; + int i, j; + + if (!draw_socket) + return; + + ((int *)buf)[0] = w->numpoints; + ((int *)buf)[1] = code; + for (i=0 ; inumpoints ; i++) + for (j=0 ; j<3 ; j++) + ((float *)buf)[2+i*3+j] = w->p[i][j]; + + send (draw_socket, buf, w->numpoints*12+8, 0); +} + +void GLS_EndScene (void) +{ + closesocket (draw_socket); + draw_socket = 0; +} diff --git a/code/bspc/glfile.c b/code/bspc/glfile.c index 179a836..e32821b 100755 --- a/code/bspc/glfile.c +++ b/code/bspc/glfile.c @@ -1,149 +1,149 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "qbsp.h" - -int c_glfaces; - -int PortalVisibleSides (portal_t *p) -{ - int fcon, bcon; - - if (!p->onnode) - return 0; // outside - - fcon = p->nodes[0]->contents; - bcon = p->nodes[1]->contents; - - // same contents never create a face - if (fcon == bcon) - return 0; - - // FIXME: is this correct now? - if (!fcon) - return 1; - if (!bcon) - return 2; - return 0; -} - -void OutputWinding (winding_t *w, FILE *glview) -{ - static int level = 128; - vec_t light; - int i; - - fprintf (glview, "%i\n", w->numpoints); - level+=28; - light = (level&255)/255.0; - for (i=0 ; inumpoints ; i++) - { - fprintf (glview, "%6.3f %6.3f %6.3f %6.3f %6.3f %6.3f\n", - w->p[i][0], - w->p[i][1], - w->p[i][2], - light, - light, - light); - } - fprintf (glview, "\n"); -} - -/* -============= -OutputPortal -============= -*/ -void OutputPortal (portal_t *p, FILE *glview) -{ - winding_t *w; - int sides; - - sides = PortalVisibleSides (p); - if (!sides) - return; - - c_glfaces++; - - w = p->winding; - - if (sides == 2) // back side - w = ReverseWinding (w); - - OutputWinding (w, glview); - - if (sides == 2) - FreeWinding(w); -} - -/* -============= -WriteGLView_r -============= -*/ -void WriteGLView_r (node_t *node, FILE *glview) -{ - portal_t *p, *nextp; - - if (node->planenum != PLANENUM_LEAF) - { - WriteGLView_r (node->children[0], glview); - WriteGLView_r (node->children[1], glview); - return; - } - - // write all the portals - for (p=node->portals ; p ; p=nextp) - { - if (p->nodes[0] == node) - { - OutputPortal (p, glview); - nextp = p->next[0]; - } - else - nextp = p->next[1]; - } -} - -/* -============= -WriteGLView -============= -*/ -void WriteGLView (tree_t *tree, char *source) -{ - char name[1024]; - FILE *glview; - - c_glfaces = 0; - sprintf (name, "%s%s.gl",outbase, source); - printf ("Writing %s\n", name); - - glview = fopen (name, "w"); - if (!glview) - Error ("Couldn't open %s", name); - WriteGLView_r (tree->headnode, glview); - fclose (glview); - - printf ("%5i c_glfaces\n", c_glfaces); -} - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "qbsp.h" + +int c_glfaces; + +int PortalVisibleSides (portal_t *p) +{ + int fcon, bcon; + + if (!p->onnode) + return 0; // outside + + fcon = p->nodes[0]->contents; + bcon = p->nodes[1]->contents; + + // same contents never create a face + if (fcon == bcon) + return 0; + + // FIXME: is this correct now? + if (!fcon) + return 1; + if (!bcon) + return 2; + return 0; +} + +void OutputWinding (winding_t *w, FILE *glview) +{ + static int level = 128; + vec_t light; + int i; + + fprintf (glview, "%i\n", w->numpoints); + level+=28; + light = (level&255)/255.0; + for (i=0 ; inumpoints ; i++) + { + fprintf (glview, "%6.3f %6.3f %6.3f %6.3f %6.3f %6.3f\n", + w->p[i][0], + w->p[i][1], + w->p[i][2], + light, + light, + light); + } + fprintf (glview, "\n"); +} + +/* +============= +OutputPortal +============= +*/ +void OutputPortal (portal_t *p, FILE *glview) +{ + winding_t *w; + int sides; + + sides = PortalVisibleSides (p); + if (!sides) + return; + + c_glfaces++; + + w = p->winding; + + if (sides == 2) // back side + w = ReverseWinding (w); + + OutputWinding (w, glview); + + if (sides == 2) + FreeWinding(w); +} + +/* +============= +WriteGLView_r +============= +*/ +void WriteGLView_r (node_t *node, FILE *glview) +{ + portal_t *p, *nextp; + + if (node->planenum != PLANENUM_LEAF) + { + WriteGLView_r (node->children[0], glview); + WriteGLView_r (node->children[1], glview); + return; + } + + // write all the portals + for (p=node->portals ; p ; p=nextp) + { + if (p->nodes[0] == node) + { + OutputPortal (p, glview); + nextp = p->next[0]; + } + else + nextp = p->next[1]; + } +} + +/* +============= +WriteGLView +============= +*/ +void WriteGLView (tree_t *tree, char *source) +{ + char name[1024]; + FILE *glview; + + c_glfaces = 0; + sprintf (name, "%s%s.gl",outbase, source); + printf ("Writing %s\n", name); + + glview = fopen (name, "w"); + if (!glview) + Error ("Couldn't open %s", name); + WriteGLView_r (tree->headnode, glview); + fclose (glview); + + printf ("%5i c_glfaces\n", c_glfaces); +} + diff --git a/code/bspc/l_bsp_ent.c b/code/bspc/l_bsp_ent.c index 8260179..0cc8f8e 100755 --- a/code/bspc/l_bsp_ent.c +++ b/code/bspc/l_bsp_ent.c @@ -1,180 +1,180 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "l_cmd.h" -#include "l_math.h" -#include "l_mem.h" -#include "l_log.h" -#include "../botlib/l_script.h" -#include "l_bsp_ent.h" - -#define MAX_KEY 32 -#define MAX_VALUE 1024 - -int num_entities; -entity_t entities[MAX_MAP_ENTITIES]; - -void StripTrailing(char *e) -{ - char *s; - - s = e + strlen(e)-1; - while (s >= e && *s <= 32) - { - *s = 0; - s--; - } -} - -/* -================= -ParseEpair -================= -*/ -epair_t *ParseEpair(script_t *script) -{ - epair_t *e; - token_t token; - - e = GetMemory(sizeof(epair_t)); - memset (e, 0, sizeof(epair_t)); - - PS_ExpectAnyToken(script, &token); - StripDoubleQuotes(token.string); - if (strlen(token.string) >= MAX_KEY-1) - Error ("ParseEpair: token %s too long", token.string); - e->key = copystring(token.string); - PS_ExpectAnyToken(script, &token); - StripDoubleQuotes(token.string); - if (strlen(token.string) >= MAX_VALUE-1) - Error ("ParseEpair: token %s too long", token.string); - e->value = copystring(token.string); - - // strip trailing spaces - StripTrailing(e->key); - StripTrailing(e->value); - - return e; -} //end of the function ParseEpair - - -/* -================ -ParseEntity -================ -*/ -qboolean ParseEntity(script_t *script) -{ - epair_t *e; - entity_t *mapent; - token_t token; - - if (!PS_ReadToken(script, &token)) - return false; - - if (strcmp(token.string, "{")) - Error ("ParseEntity: { not found"); - - if (num_entities == MAX_MAP_ENTITIES) - Error ("num_entities == MAX_MAP_ENTITIES"); - - mapent = &entities[num_entities]; - num_entities++; - - do - { - if (!PS_ReadToken(script, &token)) - Error ("ParseEntity: EOF without closing brace"); - if (!strcmp(token.string, "}") ) - break; - PS_UnreadLastToken(script); - e = ParseEpair(script); - e->next = mapent->epairs; - mapent->epairs = e; - } while (1); - - return true; -} //end of the function ParseEntity - -void PrintEntity (entity_t *ent) -{ - epair_t *ep; - - printf ("------- entity %p -------\n", ent); - for (ep=ent->epairs ; ep ; ep=ep->next) - { - printf ("%s = %s\n", ep->key, ep->value); - } - -} - -void SetKeyValue (entity_t *ent, char *key, char *value) -{ - epair_t *ep; - - for (ep=ent->epairs ; ep ; ep=ep->next) - if (!strcmp (ep->key, key) ) - { - FreeMemory(ep->value); - ep->value = copystring(value); - return; - } - ep = GetMemory(sizeof(*ep)); - ep->next = ent->epairs; - ent->epairs = ep; - ep->key = copystring(key); - ep->value = copystring(value); -} - -char *ValueForKey (entity_t *ent, char *key) -{ - epair_t *ep; - - for (ep=ent->epairs ; ep ; ep=ep->next) - if (!strcmp (ep->key, key) ) - return ep->value; - return ""; -} - -vec_t FloatForKey (entity_t *ent, char *key) -{ - char *k; - - k = ValueForKey (ent, key); - return atof(k); -} - -void GetVectorForKey (entity_t *ent, char *key, vec3_t vec) -{ - char *k; - double v1, v2, v3; - - k = ValueForKey (ent, key); -// scanf into doubles, then assign, so it is vec_t size independent - v1 = v2 = v3 = 0; - sscanf (k, "%lf %lf %lf", &v1, &v2, &v3); - vec[0] = v1; - vec[1] = v2; - vec[2] = v3; -} - - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "l_cmd.h" +#include "l_math.h" +#include "l_mem.h" +#include "l_log.h" +#include "../botlib/l_script.h" +#include "l_bsp_ent.h" + +#define MAX_KEY 32 +#define MAX_VALUE 1024 + +int num_entities; +entity_t entities[MAX_MAP_ENTITIES]; + +void StripTrailing(char *e) +{ + char *s; + + s = e + strlen(e)-1; + while (s >= e && *s <= 32) + { + *s = 0; + s--; + } +} + +/* +================= +ParseEpair +================= +*/ +epair_t *ParseEpair(script_t *script) +{ + epair_t *e; + token_t token; + + e = GetMemory(sizeof(epair_t)); + memset (e, 0, sizeof(epair_t)); + + PS_ExpectAnyToken(script, &token); + StripDoubleQuotes(token.string); + if (strlen(token.string) >= MAX_KEY-1) + Error ("ParseEpair: token %s too long", token.string); + e->key = copystring(token.string); + PS_ExpectAnyToken(script, &token); + StripDoubleQuotes(token.string); + if (strlen(token.string) >= MAX_VALUE-1) + Error ("ParseEpair: token %s too long", token.string); + e->value = copystring(token.string); + + // strip trailing spaces + StripTrailing(e->key); + StripTrailing(e->value); + + return e; +} //end of the function ParseEpair + + +/* +================ +ParseEntity +================ +*/ +qboolean ParseEntity(script_t *script) +{ + epair_t *e; + entity_t *mapent; + token_t token; + + if (!PS_ReadToken(script, &token)) + return false; + + if (strcmp(token.string, "{")) + Error ("ParseEntity: { not found"); + + if (num_entities == MAX_MAP_ENTITIES) + Error ("num_entities == MAX_MAP_ENTITIES"); + + mapent = &entities[num_entities]; + num_entities++; + + do + { + if (!PS_ReadToken(script, &token)) + Error ("ParseEntity: EOF without closing brace"); + if (!strcmp(token.string, "}") ) + break; + PS_UnreadLastToken(script); + e = ParseEpair(script); + e->next = mapent->epairs; + mapent->epairs = e; + } while (1); + + return true; +} //end of the function ParseEntity + +void PrintEntity (entity_t *ent) +{ + epair_t *ep; + + printf ("------- entity %p -------\n", ent); + for (ep=ent->epairs ; ep ; ep=ep->next) + { + printf ("%s = %s\n", ep->key, ep->value); + } + +} + +void SetKeyValue (entity_t *ent, char *key, char *value) +{ + epair_t *ep; + + for (ep=ent->epairs ; ep ; ep=ep->next) + if (!strcmp (ep->key, key) ) + { + FreeMemory(ep->value); + ep->value = copystring(value); + return; + } + ep = GetMemory(sizeof(*ep)); + ep->next = ent->epairs; + ent->epairs = ep; + ep->key = copystring(key); + ep->value = copystring(value); +} + +char *ValueForKey (entity_t *ent, char *key) +{ + epair_t *ep; + + for (ep=ent->epairs ; ep ; ep=ep->next) + if (!strcmp (ep->key, key) ) + return ep->value; + return ""; +} + +vec_t FloatForKey (entity_t *ent, char *key) +{ + char *k; + + k = ValueForKey (ent, key); + return atof(k); +} + +void GetVectorForKey (entity_t *ent, char *key, vec3_t vec) +{ + char *k; + double v1, v2, v3; + + k = ValueForKey (ent, key); +// scanf into doubles, then assign, so it is vec_t size independent + v1 = v2 = v3 = 0; + sscanf (k, "%lf %lf %lf", &v1, &v2, &v3); + vec[0] = v1; + vec[1] = v2; + vec[2] = v3; +} + + diff --git a/code/bspc/l_bsp_ent.h b/code/bspc/l_bsp_ent.h index 05dd162..461b91c 100755 --- a/code/bspc/l_bsp_ent.h +++ b/code/bspc/l_bsp_ent.h @@ -1,58 +1,58 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#ifndef MAX_MAP_ENTITIES -#define MAX_MAP_ENTITIES 2048 -#endif - -typedef struct epair_s -{ - struct epair_s *next; - char *key; - char *value; -} epair_t; - -typedef struct -{ - vec3_t origin; - int firstbrush; - int numbrushes; - epair_t *epairs; - // only valid for func_areaportals - int areaportalnum; - int portalareas[2]; - int modelnum; //for bsp 2 map conversion - qboolean wasdetail; //for SIN -} entity_t; - -extern int num_entities; -extern entity_t entities[MAX_MAP_ENTITIES]; - -void StripTrailing(char *e); -void SetKeyValue(entity_t *ent, char *key, char *value); -char *ValueForKey(entity_t *ent, char *key); // will return "" if not present -vec_t FloatForKey(entity_t *ent, char *key); -void GetVectorForKey(entity_t *ent, char *key, vec3_t vec); -qboolean ParseEntity(script_t *script); -epair_t *ParseEpair(script_t *script); -void PrintEntity(entity_t *ent); - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#ifndef MAX_MAP_ENTITIES +#define MAX_MAP_ENTITIES 2048 +#endif + +typedef struct epair_s +{ + struct epair_s *next; + char *key; + char *value; +} epair_t; + +typedef struct +{ + vec3_t origin; + int firstbrush; + int numbrushes; + epair_t *epairs; + // only valid for func_areaportals + int areaportalnum; + int portalareas[2]; + int modelnum; //for bsp 2 map conversion + qboolean wasdetail; //for SIN +} entity_t; + +extern int num_entities; +extern entity_t entities[MAX_MAP_ENTITIES]; + +void StripTrailing(char *e); +void SetKeyValue(entity_t *ent, char *key, char *value); +char *ValueForKey(entity_t *ent, char *key); // will return "" if not present +vec_t FloatForKey(entity_t *ent, char *key); +void GetVectorForKey(entity_t *ent, char *key, vec3_t vec); +qboolean ParseEntity(script_t *script); +epair_t *ParseEpair(script_t *script); +void PrintEntity(entity_t *ent); + diff --git a/code/bspc/l_bsp_hl.c b/code/bspc/l_bsp_hl.c index 5b922b5..48cb7e8 100755 --- a/code/bspc/l_bsp_hl.c +++ b/code/bspc/l_bsp_hl.c @@ -1,888 +1,888 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -#include "l_cmd.h" -#include "l_math.h" -#include "l_mem.h" -#include "l_log.h" -#include "../botlib/l_script.h" -#include "l_bsp_hl.h" -#include "l_bsp_ent.h" - -//============================================================================= - -int hl_nummodels; -hl_dmodel_t *hl_dmodels;//[HL_MAX_MAP_MODELS]; -int hl_dmodels_checksum; - -int hl_visdatasize; -byte *hl_dvisdata;//[HL_MAX_MAP_VISIBILITY]; -int hl_dvisdata_checksum; - -int hl_lightdatasize; -byte *hl_dlightdata;//[HL_MAX_MAP_LIGHTING]; -int hl_dlightdata_checksum; - -int hl_texdatasize; -byte *hl_dtexdata;//[HL_MAX_MAP_MIPTEX]; // (dmiptexlump_t) -int hl_dtexdata_checksum; - -int hl_entdatasize; -char *hl_dentdata;//[HL_MAX_MAP_ENTSTRING]; -int hl_dentdata_checksum; - -int hl_numleafs; -hl_dleaf_t *hl_dleafs;//[HL_MAX_MAP_LEAFS]; -int hl_dleafs_checksum; - -int hl_numplanes; -hl_dplane_t *hl_dplanes;//[HL_MAX_MAP_PLANES]; -int hl_dplanes_checksum; - -int hl_numvertexes; -hl_dvertex_t *hl_dvertexes;//[HL_MAX_MAP_VERTS]; -int hl_dvertexes_checksum; - -int hl_numnodes; -hl_dnode_t *hl_dnodes;//[HL_MAX_MAP_NODES]; -int hl_dnodes_checksum; - -int hl_numtexinfo; -hl_texinfo_t *hl_texinfo;//[HL_MAX_MAP_TEXINFO]; -int hl_texinfo_checksum; - -int hl_numfaces; -hl_dface_t *hl_dfaces;//[HL_MAX_MAP_FACES]; -int hl_dfaces_checksum; - -int hl_numclipnodes; -hl_dclipnode_t *hl_dclipnodes;//[HL_MAX_MAP_CLIPNODES]; -int hl_dclipnodes_checksum; - -int hl_numedges; -hl_dedge_t *hl_dedges;//[HL_MAX_MAP_EDGES]; -int hl_dedges_checksum; - -int hl_nummarksurfaces; -unsigned short *hl_dmarksurfaces;//[HL_MAX_MAP_MARKSURFACES]; -int hl_dmarksurfaces_checksum; - -int hl_numsurfedges; -int *hl_dsurfedges;//[HL_MAX_MAP_SURFEDGES]; -int hl_dsurfedges_checksum; - -//int num_entities; -//entity_t entities[HL_MAX_MAP_ENTITIES]; - - -//#ifdef //ME - -int hl_bspallocated = false; -int hl_allocatedbspmem = 0; - -void HL_AllocMaxBSP(void) -{ - //models - hl_nummodels = 0; - hl_dmodels = (hl_dmodel_t *) GetMemory(HL_MAX_MAP_MODELS * sizeof(hl_dmodel_t)); - hl_allocatedbspmem = HL_MAX_MAP_MODELS * sizeof(hl_dmodel_t); - //visibility - hl_visdatasize = 0; - hl_dvisdata = (byte *) GetMemory(HL_MAX_MAP_VISIBILITY * sizeof(byte)); - hl_allocatedbspmem += HL_MAX_MAP_VISIBILITY * sizeof(byte); - //light data - hl_lightdatasize = 0; - hl_dlightdata = (byte *) GetMemory(HL_MAX_MAP_LIGHTING * sizeof(byte)); - hl_allocatedbspmem += HL_MAX_MAP_LIGHTING * sizeof(byte); - //texture data - hl_texdatasize = 0; - hl_dtexdata = (byte *) GetMemory(HL_MAX_MAP_MIPTEX * sizeof(byte)); // (dmiptexlump_t) - hl_allocatedbspmem += HL_MAX_MAP_MIPTEX * sizeof(byte); - //entities - hl_entdatasize = 0; - hl_dentdata = (char *) GetMemory(HL_MAX_MAP_ENTSTRING * sizeof(char)); - hl_allocatedbspmem += HL_MAX_MAP_ENTSTRING * sizeof(char); - //leaves - hl_numleafs = 0; - hl_dleafs = (hl_dleaf_t *) GetMemory(HL_MAX_MAP_LEAFS * sizeof(hl_dleaf_t)); - hl_allocatedbspmem += HL_MAX_MAP_LEAFS * sizeof(hl_dleaf_t); - //planes - hl_numplanes = 0; - hl_dplanes = (hl_dplane_t *) GetMemory(HL_MAX_MAP_PLANES * sizeof(hl_dplane_t)); - hl_allocatedbspmem += HL_MAX_MAP_PLANES * sizeof(hl_dplane_t); - //vertexes - hl_numvertexes = 0; - hl_dvertexes = (hl_dvertex_t *) GetMemory(HL_MAX_MAP_VERTS * sizeof(hl_dvertex_t)); - hl_allocatedbspmem += HL_MAX_MAP_VERTS * sizeof(hl_dvertex_t); - //nodes - hl_numnodes = 0; - hl_dnodes = (hl_dnode_t *) GetMemory(HL_MAX_MAP_NODES * sizeof(hl_dnode_t)); - hl_allocatedbspmem += HL_MAX_MAP_NODES * sizeof(hl_dnode_t); - //texture info - hl_numtexinfo = 0; - hl_texinfo = (hl_texinfo_t *) GetMemory(HL_MAX_MAP_TEXINFO * sizeof(hl_texinfo_t)); - hl_allocatedbspmem += HL_MAX_MAP_TEXINFO * sizeof(hl_texinfo_t); - //faces - hl_numfaces = 0; - hl_dfaces = (hl_dface_t *) GetMemory(HL_MAX_MAP_FACES * sizeof(hl_dface_t)); - hl_allocatedbspmem += HL_MAX_MAP_FACES * sizeof(hl_dface_t); - //clip nodes - hl_numclipnodes = 0; - hl_dclipnodes = (hl_dclipnode_t *) GetMemory(HL_MAX_MAP_CLIPNODES * sizeof(hl_dclipnode_t)); - hl_allocatedbspmem += HL_MAX_MAP_CLIPNODES * sizeof(hl_dclipnode_t); - //edges - hl_numedges = 0; - hl_dedges = (hl_dedge_t *) GetMemory(HL_MAX_MAP_EDGES * sizeof(hl_dedge_t)); - hl_allocatedbspmem += HL_MAX_MAP_EDGES, sizeof(hl_dedge_t); - //mark surfaces - hl_nummarksurfaces = 0; - hl_dmarksurfaces = (unsigned short *) GetMemory(HL_MAX_MAP_MARKSURFACES * sizeof(unsigned short)); - hl_allocatedbspmem += HL_MAX_MAP_MARKSURFACES * sizeof(unsigned short); - //surface edges - hl_numsurfedges = 0; - hl_dsurfedges = (int *) GetMemory(HL_MAX_MAP_SURFEDGES * sizeof(int)); - hl_allocatedbspmem += HL_MAX_MAP_SURFEDGES * sizeof(int); - //print allocated memory - Log_Print("allocated "); - PrintMemorySize(hl_allocatedbspmem); - Log_Print(" of BSP memory\n"); -} //end of the function HL_AllocMaxBSP - -void HL_FreeMaxBSP(void) -{ - //models - hl_nummodels = 0; - FreeMemory(hl_dmodels); - hl_dmodels = NULL; - //visibility - hl_visdatasize = 0; - FreeMemory(hl_dvisdata); - hl_dvisdata = NULL; - //light data - hl_lightdatasize = 0; - FreeMemory(hl_dlightdata); - hl_dlightdata = NULL; - //texture data - hl_texdatasize = 0; - FreeMemory(hl_dtexdata); - hl_dtexdata = NULL; - //entities - hl_entdatasize = 0; - FreeMemory(hl_dentdata); - hl_dentdata = NULL; - //leaves - hl_numleafs = 0; - FreeMemory(hl_dleafs); - hl_dleafs = NULL; - //planes - hl_numplanes = 0; - FreeMemory(hl_dplanes); - hl_dplanes = NULL; - //vertexes - hl_numvertexes = 0; - FreeMemory(hl_dvertexes); - hl_dvertexes = NULL; - //nodes - hl_numnodes = 0; - FreeMemory(hl_dnodes); - hl_dnodes = NULL; - //texture info - hl_numtexinfo = 0; - FreeMemory(hl_texinfo); - hl_texinfo = NULL; - //faces - hl_numfaces = 0; - FreeMemory(hl_dfaces); - hl_dfaces = NULL; - //clip nodes - hl_numclipnodes = 0; - FreeMemory(hl_dclipnodes); - hl_dclipnodes = NULL; - //edges - hl_numedges = 0; - FreeMemory(hl_dedges); - hl_dedges = NULL; - //mark surfaces - hl_nummarksurfaces = 0; - FreeMemory(hl_dmarksurfaces); - hl_dmarksurfaces = NULL; - //surface edges - hl_numsurfedges = 0; - FreeMemory(hl_dsurfedges); - hl_dsurfedges = NULL; - // - Log_Print("freed "); - PrintMemorySize(hl_allocatedbspmem); - Log_Print(" of BSP memory\n"); - hl_allocatedbspmem = 0; -} //end of the function HL_FreeMaxBSP -//#endif //ME - -/* -=============== -FastChecksum -=============== -*/ - -int FastChecksum(void *buffer, int bytes) -{ - int checksum = 0; - - while( bytes-- ) - checksum = (checksum << 4) ^ *((char *)buffer)++; - - return checksum; -} - -/* -=============== -HL_CompressVis -=============== -*/ -int HL_CompressVis(byte *vis, byte *dest) -{ - int j; - int rep; - int visrow; - byte *dest_p; - - dest_p = dest; - visrow = (hl_numleafs + 7)>>3; - - for (j=0 ; j>3; - out = decompressed; - - do - { - if (*in) - { - *out++ = *in++; - continue; - } - - c = in[1]; - in += 2; - while (c) - { - *out++ = 0; - c--; - } - } while (out - decompressed < row); -} - -//============================================================================= - -/* -============= -HL_SwapBSPFile - -Byte swaps all data in a bsp file. -============= -*/ -void HL_SwapBSPFile (qboolean todisk) -{ - int i, j, c; - hl_dmodel_t *d; - hl_dmiptexlump_t *mtl; - - -// models - for (i = 0; i < hl_nummodels; i++) - { - d = &hl_dmodels[i]; - - for (j = 0; j < HL_MAX_MAP_HULLS; j++) - d->headnode[j] = LittleLong(d->headnode[j]); - - d->visleafs = LittleLong(d->visleafs); - d->firstface = LittleLong(d->firstface); - d->numfaces = LittleLong(d->numfaces); - - for (j = 0; j < 3; j++) - { - d->mins[j] = LittleFloat(d->mins[j]); - d->maxs[j] = LittleFloat(d->maxs[j]); - d->origin[j] = LittleFloat(d->origin[j]); - } - } - -// -// vertexes -// - for (i = 0; i < hl_numvertexes; i++) - { - for (j = 0; j < 3; j++) - hl_dvertexes[i].point[j] = LittleFloat (hl_dvertexes[i].point[j]); - } - -// -// planes -// - for (i=0 ; inummiptex; - else - c = LittleLong(mtl->nummiptex); - mtl->nummiptex = LittleLong (mtl->nummiptex); - for (i=0 ; idataofs[i] = LittleLong(mtl->dataofs[i]); - } - -// -// marksurfaces -// - for (i=0 ; ilumps[lump].filelen; - ofs = hl_header->lumps[lump].fileofs; - - if (length % size) { - Error ("LoadBSPFile: odd lump size"); - } - // somehow things got out of range - if ((length/size) > maxsize) { - printf("WARNING: exceeded max size for lump %d size %d > maxsize %d\n", lump, (length/size), maxsize); - length = maxsize * size; - } - if ( ofs + length > hl_fileLength ) { - printf("WARNING: exceeded file length for lump %d\n", lump); - length = hl_fileLength - ofs; - if ( length <= 0 ) { - return 0; - } - } - - memcpy (dest, (byte *)hl_header + ofs, length); - - return length / size; -} - -/* -============= -HL_LoadBSPFile -============= -*/ -void HL_LoadBSPFile (char *filename, int offset, int length) -{ - int i; - -// -// load the file header -// - hl_fileLength = LoadFile (filename, (void **)&hl_header, offset, length); - -// swap the header - for (i=0 ; i< sizeof(hl_dheader_t)/4 ; i++) - ((int *)hl_header)[i] = LittleLong ( ((int *)hl_header)[i]); - - if (hl_header->version != HL_BSPVERSION) - Error ("%s is version %i, not %i", filename, hl_header->version, HL_BSPVERSION); - - hl_nummodels = HL_CopyLump (HL_LUMP_MODELS, hl_dmodels, sizeof(hl_dmodel_t), HL_MAX_MAP_MODELS ); - hl_numvertexes = HL_CopyLump (HL_LUMP_VERTEXES, hl_dvertexes, sizeof(hl_dvertex_t), HL_MAX_MAP_VERTS ); - hl_numplanes = HL_CopyLump (HL_LUMP_PLANES, hl_dplanes, sizeof(hl_dplane_t), HL_MAX_MAP_PLANES ); - hl_numleafs = HL_CopyLump (HL_LUMP_LEAFS, hl_dleafs, sizeof(hl_dleaf_t), HL_MAX_MAP_LEAFS ); - hl_numnodes = HL_CopyLump (HL_LUMP_NODES, hl_dnodes, sizeof(hl_dnode_t), HL_MAX_MAP_NODES ); - hl_numtexinfo = HL_CopyLump (HL_LUMP_TEXINFO, hl_texinfo, sizeof(hl_texinfo_t), HL_MAX_MAP_TEXINFO ); - hl_numclipnodes = HL_CopyLump (HL_LUMP_CLIPNODES, hl_dclipnodes, sizeof(hl_dclipnode_t), HL_MAX_MAP_CLIPNODES ); - hl_numfaces = HL_CopyLump (HL_LUMP_FACES, hl_dfaces, sizeof(hl_dface_t), HL_MAX_MAP_FACES ); - hl_nummarksurfaces = HL_CopyLump (HL_LUMP_MARKSURFACES, hl_dmarksurfaces, sizeof(hl_dmarksurfaces[0]), HL_MAX_MAP_MARKSURFACES ); - hl_numsurfedges = HL_CopyLump (HL_LUMP_SURFEDGES, hl_dsurfedges, sizeof(hl_dsurfedges[0]), HL_MAX_MAP_SURFEDGES ); - hl_numedges = HL_CopyLump (HL_LUMP_EDGES, hl_dedges, sizeof(hl_dedge_t), HL_MAX_MAP_EDGES ); - - hl_texdatasize = HL_CopyLump (HL_LUMP_TEXTURES, hl_dtexdata, 1, HL_MAX_MAP_MIPTEX ); - hl_visdatasize = HL_CopyLump (HL_LUMP_VISIBILITY, hl_dvisdata, 1, HL_MAX_MAP_VISIBILITY ); - hl_lightdatasize = HL_CopyLump (HL_LUMP_LIGHTING, hl_dlightdata, 1, HL_MAX_MAP_LIGHTING ); - hl_entdatasize = HL_CopyLump (HL_LUMP_ENTITIES, hl_dentdata, 1, HL_MAX_MAP_ENTSTRING ); - - FreeMemory(hl_header); // everything has been copied out - -// -// swap everything -// - HL_SwapBSPFile (false); - - hl_dmodels_checksum = FastChecksum( hl_dmodels, hl_nummodels*sizeof(hl_dmodels[0]) ); - hl_dvertexes_checksum = FastChecksum( hl_dvertexes, hl_numvertexes*sizeof(hl_dvertexes[0]) ); - hl_dplanes_checksum = FastChecksum( hl_dplanes, hl_numplanes*sizeof(hl_dplanes[0]) ); - hl_dleafs_checksum = FastChecksum( hl_dleafs, hl_numleafs*sizeof(hl_dleafs[0]) ); - hl_dnodes_checksum = FastChecksum( hl_dnodes, hl_numnodes*sizeof(hl_dnodes[0]) ); - hl_texinfo_checksum = FastChecksum( hl_texinfo, hl_numtexinfo*sizeof(hl_texinfo[0]) ); - hl_dclipnodes_checksum = FastChecksum( hl_dclipnodes, hl_numclipnodes*sizeof(hl_dclipnodes[0]) ); - hl_dfaces_checksum = FastChecksum( hl_dfaces, hl_numfaces*sizeof(hl_dfaces[0]) ); - hl_dmarksurfaces_checksum = FastChecksum( hl_dmarksurfaces, hl_nummarksurfaces*sizeof(hl_dmarksurfaces[0]) ); - hl_dsurfedges_checksum = FastChecksum( hl_dsurfedges, hl_numsurfedges*sizeof(hl_dsurfedges[0]) ); - hl_dedges_checksum = FastChecksum( hl_dedges, hl_numedges*sizeof(hl_dedges[0]) ); - hl_dtexdata_checksum = FastChecksum( hl_dtexdata, hl_numedges*sizeof(hl_dtexdata[0]) ); - hl_dvisdata_checksum = FastChecksum( hl_dvisdata, hl_visdatasize*sizeof(hl_dvisdata[0]) ); - hl_dlightdata_checksum = FastChecksum( hl_dlightdata, hl_lightdatasize*sizeof(hl_dlightdata[0]) ); - hl_dentdata_checksum = FastChecksum( hl_dentdata, hl_entdatasize*sizeof(hl_dentdata[0]) ); - -} - -//============================================================================ - -FILE *wadfile; -hl_dheader_t outheader; - -void HL_AddLump (int lumpnum, void *data, int len) -{ - hl_lump_t *lump; - - lump = &hl_header->lumps[lumpnum]; - - lump->fileofs = LittleLong( ftell(wadfile) ); - lump->filelen = LittleLong(len); - SafeWrite (wadfile, data, (len+3)&~3); -} - -/* -============= -HL_WriteBSPFile - -Swaps the bsp file in place, so it should not be referenced again -============= -*/ -void HL_WriteBSPFile (char *filename) -{ - hl_header = &outheader; - memset (hl_header, 0, sizeof(hl_dheader_t)); - - HL_SwapBSPFile (true); - - hl_header->version = LittleLong (HL_BSPVERSION); - - wadfile = SafeOpenWrite (filename); - SafeWrite (wadfile, hl_header, sizeof(hl_dheader_t)); // overwritten later - - HL_AddLump (HL_LUMP_PLANES, hl_dplanes, hl_numplanes*sizeof(hl_dplane_t)); - HL_AddLump (HL_LUMP_LEAFS, hl_dleafs, hl_numleafs*sizeof(hl_dleaf_t)); - HL_AddLump (HL_LUMP_VERTEXES, hl_dvertexes, hl_numvertexes*sizeof(hl_dvertex_t)); - HL_AddLump (HL_LUMP_NODES, hl_dnodes, hl_numnodes*sizeof(hl_dnode_t)); - HL_AddLump (HL_LUMP_TEXINFO, hl_texinfo, hl_numtexinfo*sizeof(hl_texinfo_t)); - HL_AddLump (HL_LUMP_FACES, hl_dfaces, hl_numfaces*sizeof(hl_dface_t)); - HL_AddLump (HL_LUMP_CLIPNODES, hl_dclipnodes, hl_numclipnodes*sizeof(hl_dclipnode_t)); - HL_AddLump (HL_LUMP_MARKSURFACES, hl_dmarksurfaces, hl_nummarksurfaces*sizeof(hl_dmarksurfaces[0])); - HL_AddLump (HL_LUMP_SURFEDGES, hl_dsurfedges, hl_numsurfedges*sizeof(hl_dsurfedges[0])); - HL_AddLump (HL_LUMP_EDGES, hl_dedges, hl_numedges*sizeof(hl_dedge_t)); - HL_AddLump (HL_LUMP_MODELS, hl_dmodels, hl_nummodels*sizeof(hl_dmodel_t)); - - HL_AddLump (HL_LUMP_LIGHTING, hl_dlightdata, hl_lightdatasize); - HL_AddLump (HL_LUMP_VISIBILITY, hl_dvisdata, hl_visdatasize); - HL_AddLump (HL_LUMP_ENTITIES, hl_dentdata, hl_entdatasize); - HL_AddLump (HL_LUMP_TEXTURES, hl_dtexdata, hl_texdatasize); - - fseek (wadfile, 0, SEEK_SET); - SafeWrite (wadfile, hl_header, sizeof(hl_dheader_t)); - fclose (wadfile); -} - -//============================================================================ - -#define ENTRIES(a) (sizeof(a)/sizeof(*(a))) -#define ENTRYSIZE(a) (sizeof(*(a))) - -ArrayUsage( char *szItem, int items, int maxitems, int itemsize ) -{ - float percentage = maxitems ? items * 100.0 / maxitems : 0.0; - - qprintf("%-12s %7i/%-7i %7i/%-7i (%4.1f%%)", - szItem, items, maxitems, items * itemsize, maxitems * itemsize, percentage ); - if ( percentage > 80.0 ) - qprintf( "VERY FULL!\n" ); - else if ( percentage > 95.0 ) - qprintf( "SIZE DANGER!\n" ); - else if ( percentage > 99.9 ) - qprintf( "SIZE OVERFLOW!!!\n" ); - else - qprintf( "\n" ); - return items * itemsize; -} - -GlobUsage( char *szItem, int itemstorage, int maxstorage ) -{ - float percentage = maxstorage ? itemstorage * 100.0 / maxstorage : 0.0; - - qprintf("%-12s [variable] %7i/%-7i (%4.1f%%)", - szItem, itemstorage, maxstorage, percentage ); - if ( percentage > 80.0 ) - qprintf( "VERY FULL!\n" ); - else if ( percentage > 95.0 ) - qprintf( "SIZE DANGER!\n" ); - else if ( percentage > 99.9 ) - qprintf( "SIZE OVERFLOW!!!\n" ); - else - qprintf( "\n" ); - return itemstorage; -} - -/* -============= -HL_PrintBSPFileSizes - -Dumps info about current file -============= -*/ -void HL_PrintBSPFileSizes(void) -{ - int numtextures = hl_texdatasize ? ((hl_dmiptexlump_t*)hl_dtexdata)->nummiptex : 0; - int totalmemory = 0; - - qprintf("\n"); - qprintf("Object names Objects/Maxobjs Memory / Maxmem Fullness\n" ); - qprintf("------------ --------------- --------------- --------\n" ); - - totalmemory += ArrayUsage( "models", hl_nummodels, ENTRIES(hl_dmodels), ENTRYSIZE(hl_dmodels) ); - totalmemory += ArrayUsage( "planes", hl_numplanes, ENTRIES(hl_dplanes), ENTRYSIZE(hl_dplanes) ); - totalmemory += ArrayUsage( "vertexes", hl_numvertexes, ENTRIES(hl_dvertexes), ENTRYSIZE(hl_dvertexes) ); - totalmemory += ArrayUsage( "nodes", hl_numnodes, ENTRIES(hl_dnodes), ENTRYSIZE(hl_dnodes) ); - totalmemory += ArrayUsage( "texinfos", hl_numtexinfo, ENTRIES(hl_texinfo), ENTRYSIZE(hl_texinfo) ); - totalmemory += ArrayUsage( "faces", hl_numfaces, ENTRIES(hl_dfaces), ENTRYSIZE(hl_dfaces) ); - totalmemory += ArrayUsage( "clipnodes", hl_numclipnodes, ENTRIES(hl_dclipnodes), ENTRYSIZE(hl_dclipnodes) ); - totalmemory += ArrayUsage( "leaves", hl_numleafs, ENTRIES(hl_dleafs), ENTRYSIZE(hl_dleafs) ); - totalmemory += ArrayUsage( "marksurfaces",hl_nummarksurfaces,ENTRIES(hl_dmarksurfaces),ENTRYSIZE(hl_dmarksurfaces) ); - totalmemory += ArrayUsage( "surfedges", hl_numsurfedges, ENTRIES(hl_dsurfedges), ENTRYSIZE(hl_dsurfedges) ); - totalmemory += ArrayUsage( "edges", hl_numedges, ENTRIES(hl_dedges), ENTRYSIZE(hl_dedges) ); - - totalmemory += GlobUsage( "texdata", hl_texdatasize, sizeof(hl_dtexdata) ); - totalmemory += GlobUsage( "lightdata", hl_lightdatasize, sizeof(hl_dlightdata) ); - totalmemory += GlobUsage( "visdata", hl_visdatasize, sizeof(hl_dvisdata) ); - totalmemory += GlobUsage( "entdata", hl_entdatasize, sizeof(hl_dentdata) ); - - qprintf( "=== Total BSP file data space used: %d bytes ===\n\n", totalmemory ); -} - - - -/* -================= -ParseEpair -================= -* / -epair_t *ParseEpair (void) -{ - epair_t *e; - - e = malloc (sizeof(epair_t)); - memset (e, 0, sizeof(epair_t)); - - if (strlen(token) >= MAX_KEY-1) - Error ("ParseEpar: token too long"); - e->key = copystring(token); - GetToken (false); - if (strlen(token) >= MAX_VALUE-1) - Error ("ParseEpar: token too long"); - e->value = copystring(token); - - return e; -} //*/ - - -/* -================ -ParseEntity -================ -* / -qboolean ParseEntity (void) -{ - epair_t *e; - entity_t *mapent; - - if (!GetToken (true)) - return false; - - if (strcmp (token, "{") ) - Error ("ParseEntity: { not found"); - - if (num_entities == HL_MAX_MAP_ENTITIES) - Error ("num_entities == HL_MAX_MAP_ENTITIES"); - - mapent = &entities[num_entities]; - num_entities++; - - do - { - if (!GetToken (true)) - Error ("ParseEntity: EOF without closing brace"); - if (!strcmp (token, "}") ) - break; - e = ParseEpair (); - e->next = mapent->epairs; - mapent->epairs = e; - } while (1); - - return true; -} //*/ - -/* -================ -ParseEntities - -Parses the dentdata string into entities -================ -*/ -void HL_ParseEntities (void) -{ - script_t *script; - - num_entities = 0; - script = LoadScriptMemory(hl_dentdata, hl_entdatasize, "*Half-Life bsp file"); - SetScriptFlags(script, SCFL_NOSTRINGWHITESPACES | - SCFL_NOSTRINGESCAPECHARS); - - while(ParseEntity(script)) - { - } //end while - - FreeScript(script); -} //end of the function HL_ParseEntities - - -/* -================ -UnparseEntities - -Generates the dentdata string from all the entities -================ -*/ -void HL_UnparseEntities (void) -{ - char *buf, *end; - epair_t *ep; - char line[2048]; - int i; - - buf = hl_dentdata; - end = buf; - *end = 0; - - for (i=0 ; inext) - { - sprintf (line, "\"%s\" \"%s\"\n", ep->key, ep->value); - strcat (end, line); - end += strlen(line); - } - strcat (end,"}\n"); - end += 2; - - if (end > buf + HL_MAX_MAP_ENTSTRING) - Error ("Entity text too long"); - } - hl_entdatasize = end - buf + 1; -} //end of the function HL_UnparseEntities - - -/* -void SetKeyValue (entity_t *ent, char *key, char *value) -{ - epair_t *ep; - - for (ep=ent->epairs ; ep ; ep=ep->next) - if (!strcmp (ep->key, key) ) - { - free (ep->value); - ep->value = copystring(value); - return; - } - ep = malloc (sizeof(*ep)); - ep->next = ent->epairs; - ent->epairs = ep; - ep->key = copystring(key); - ep->value = copystring(value); -} - -char *ValueForKey (entity_t *ent, char *key) -{ - epair_t *ep; - - for (ep=ent->epairs ; ep ; ep=ep->next) - if (!strcmp (ep->key, key) ) - return ep->value; - return ""; -} - -vec_t FloatForKey (entity_t *ent, char *key) -{ - char *k; - - k = ValueForKey (ent, key); - return atof(k); -} - -void GetVectorForKey (entity_t *ent, char *key, vec3_t vec) -{ - char *k; - double v1, v2, v3; - - k = ValueForKey (ent, key); -// scanf into doubles, then assign, so it is vec_t size independent - v1 = v2 = v3 = 0; - sscanf (k, "%lf %lf %lf", &v1, &v2, &v3); - vec[0] = v1; - vec[1] = v2; - vec[2] = v3; -} //*/ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +#include "l_cmd.h" +#include "l_math.h" +#include "l_mem.h" +#include "l_log.h" +#include "../botlib/l_script.h" +#include "l_bsp_hl.h" +#include "l_bsp_ent.h" + +//============================================================================= + +int hl_nummodels; +hl_dmodel_t *hl_dmodels;//[HL_MAX_MAP_MODELS]; +int hl_dmodels_checksum; + +int hl_visdatasize; +byte *hl_dvisdata;//[HL_MAX_MAP_VISIBILITY]; +int hl_dvisdata_checksum; + +int hl_lightdatasize; +byte *hl_dlightdata;//[HL_MAX_MAP_LIGHTING]; +int hl_dlightdata_checksum; + +int hl_texdatasize; +byte *hl_dtexdata;//[HL_MAX_MAP_MIPTEX]; // (dmiptexlump_t) +int hl_dtexdata_checksum; + +int hl_entdatasize; +char *hl_dentdata;//[HL_MAX_MAP_ENTSTRING]; +int hl_dentdata_checksum; + +int hl_numleafs; +hl_dleaf_t *hl_dleafs;//[HL_MAX_MAP_LEAFS]; +int hl_dleafs_checksum; + +int hl_numplanes; +hl_dplane_t *hl_dplanes;//[HL_MAX_MAP_PLANES]; +int hl_dplanes_checksum; + +int hl_numvertexes; +hl_dvertex_t *hl_dvertexes;//[HL_MAX_MAP_VERTS]; +int hl_dvertexes_checksum; + +int hl_numnodes; +hl_dnode_t *hl_dnodes;//[HL_MAX_MAP_NODES]; +int hl_dnodes_checksum; + +int hl_numtexinfo; +hl_texinfo_t *hl_texinfo;//[HL_MAX_MAP_TEXINFO]; +int hl_texinfo_checksum; + +int hl_numfaces; +hl_dface_t *hl_dfaces;//[HL_MAX_MAP_FACES]; +int hl_dfaces_checksum; + +int hl_numclipnodes; +hl_dclipnode_t *hl_dclipnodes;//[HL_MAX_MAP_CLIPNODES]; +int hl_dclipnodes_checksum; + +int hl_numedges; +hl_dedge_t *hl_dedges;//[HL_MAX_MAP_EDGES]; +int hl_dedges_checksum; + +int hl_nummarksurfaces; +unsigned short *hl_dmarksurfaces;//[HL_MAX_MAP_MARKSURFACES]; +int hl_dmarksurfaces_checksum; + +int hl_numsurfedges; +int *hl_dsurfedges;//[HL_MAX_MAP_SURFEDGES]; +int hl_dsurfedges_checksum; + +//int num_entities; +//entity_t entities[HL_MAX_MAP_ENTITIES]; + + +//#ifdef //ME + +int hl_bspallocated = false; +int hl_allocatedbspmem = 0; + +void HL_AllocMaxBSP(void) +{ + //models + hl_nummodels = 0; + hl_dmodels = (hl_dmodel_t *) GetMemory(HL_MAX_MAP_MODELS * sizeof(hl_dmodel_t)); + hl_allocatedbspmem = HL_MAX_MAP_MODELS * sizeof(hl_dmodel_t); + //visibility + hl_visdatasize = 0; + hl_dvisdata = (byte *) GetMemory(HL_MAX_MAP_VISIBILITY * sizeof(byte)); + hl_allocatedbspmem += HL_MAX_MAP_VISIBILITY * sizeof(byte); + //light data + hl_lightdatasize = 0; + hl_dlightdata = (byte *) GetMemory(HL_MAX_MAP_LIGHTING * sizeof(byte)); + hl_allocatedbspmem += HL_MAX_MAP_LIGHTING * sizeof(byte); + //texture data + hl_texdatasize = 0; + hl_dtexdata = (byte *) GetMemory(HL_MAX_MAP_MIPTEX * sizeof(byte)); // (dmiptexlump_t) + hl_allocatedbspmem += HL_MAX_MAP_MIPTEX * sizeof(byte); + //entities + hl_entdatasize = 0; + hl_dentdata = (char *) GetMemory(HL_MAX_MAP_ENTSTRING * sizeof(char)); + hl_allocatedbspmem += HL_MAX_MAP_ENTSTRING * sizeof(char); + //leaves + hl_numleafs = 0; + hl_dleafs = (hl_dleaf_t *) GetMemory(HL_MAX_MAP_LEAFS * sizeof(hl_dleaf_t)); + hl_allocatedbspmem += HL_MAX_MAP_LEAFS * sizeof(hl_dleaf_t); + //planes + hl_numplanes = 0; + hl_dplanes = (hl_dplane_t *) GetMemory(HL_MAX_MAP_PLANES * sizeof(hl_dplane_t)); + hl_allocatedbspmem += HL_MAX_MAP_PLANES * sizeof(hl_dplane_t); + //vertexes + hl_numvertexes = 0; + hl_dvertexes = (hl_dvertex_t *) GetMemory(HL_MAX_MAP_VERTS * sizeof(hl_dvertex_t)); + hl_allocatedbspmem += HL_MAX_MAP_VERTS * sizeof(hl_dvertex_t); + //nodes + hl_numnodes = 0; + hl_dnodes = (hl_dnode_t *) GetMemory(HL_MAX_MAP_NODES * sizeof(hl_dnode_t)); + hl_allocatedbspmem += HL_MAX_MAP_NODES * sizeof(hl_dnode_t); + //texture info + hl_numtexinfo = 0; + hl_texinfo = (hl_texinfo_t *) GetMemory(HL_MAX_MAP_TEXINFO * sizeof(hl_texinfo_t)); + hl_allocatedbspmem += HL_MAX_MAP_TEXINFO * sizeof(hl_texinfo_t); + //faces + hl_numfaces = 0; + hl_dfaces = (hl_dface_t *) GetMemory(HL_MAX_MAP_FACES * sizeof(hl_dface_t)); + hl_allocatedbspmem += HL_MAX_MAP_FACES * sizeof(hl_dface_t); + //clip nodes + hl_numclipnodes = 0; + hl_dclipnodes = (hl_dclipnode_t *) GetMemory(HL_MAX_MAP_CLIPNODES * sizeof(hl_dclipnode_t)); + hl_allocatedbspmem += HL_MAX_MAP_CLIPNODES * sizeof(hl_dclipnode_t); + //edges + hl_numedges = 0; + hl_dedges = (hl_dedge_t *) GetMemory(HL_MAX_MAP_EDGES * sizeof(hl_dedge_t)); + hl_allocatedbspmem += HL_MAX_MAP_EDGES, sizeof(hl_dedge_t); + //mark surfaces + hl_nummarksurfaces = 0; + hl_dmarksurfaces = (unsigned short *) GetMemory(HL_MAX_MAP_MARKSURFACES * sizeof(unsigned short)); + hl_allocatedbspmem += HL_MAX_MAP_MARKSURFACES * sizeof(unsigned short); + //surface edges + hl_numsurfedges = 0; + hl_dsurfedges = (int *) GetMemory(HL_MAX_MAP_SURFEDGES * sizeof(int)); + hl_allocatedbspmem += HL_MAX_MAP_SURFEDGES * sizeof(int); + //print allocated memory + Log_Print("allocated "); + PrintMemorySize(hl_allocatedbspmem); + Log_Print(" of BSP memory\n"); +} //end of the function HL_AllocMaxBSP + +void HL_FreeMaxBSP(void) +{ + //models + hl_nummodels = 0; + FreeMemory(hl_dmodels); + hl_dmodels = NULL; + //visibility + hl_visdatasize = 0; + FreeMemory(hl_dvisdata); + hl_dvisdata = NULL; + //light data + hl_lightdatasize = 0; + FreeMemory(hl_dlightdata); + hl_dlightdata = NULL; + //texture data + hl_texdatasize = 0; + FreeMemory(hl_dtexdata); + hl_dtexdata = NULL; + //entities + hl_entdatasize = 0; + FreeMemory(hl_dentdata); + hl_dentdata = NULL; + //leaves + hl_numleafs = 0; + FreeMemory(hl_dleafs); + hl_dleafs = NULL; + //planes + hl_numplanes = 0; + FreeMemory(hl_dplanes); + hl_dplanes = NULL; + //vertexes + hl_numvertexes = 0; + FreeMemory(hl_dvertexes); + hl_dvertexes = NULL; + //nodes + hl_numnodes = 0; + FreeMemory(hl_dnodes); + hl_dnodes = NULL; + //texture info + hl_numtexinfo = 0; + FreeMemory(hl_texinfo); + hl_texinfo = NULL; + //faces + hl_numfaces = 0; + FreeMemory(hl_dfaces); + hl_dfaces = NULL; + //clip nodes + hl_numclipnodes = 0; + FreeMemory(hl_dclipnodes); + hl_dclipnodes = NULL; + //edges + hl_numedges = 0; + FreeMemory(hl_dedges); + hl_dedges = NULL; + //mark surfaces + hl_nummarksurfaces = 0; + FreeMemory(hl_dmarksurfaces); + hl_dmarksurfaces = NULL; + //surface edges + hl_numsurfedges = 0; + FreeMemory(hl_dsurfedges); + hl_dsurfedges = NULL; + // + Log_Print("freed "); + PrintMemorySize(hl_allocatedbspmem); + Log_Print(" of BSP memory\n"); + hl_allocatedbspmem = 0; +} //end of the function HL_FreeMaxBSP +//#endif //ME + +/* +=============== +FastChecksum +=============== +*/ + +int FastChecksum(void *buffer, int bytes) +{ + int checksum = 0; + + while( bytes-- ) + checksum = (checksum << 4) ^ *((char *)buffer)++; + + return checksum; +} + +/* +=============== +HL_CompressVis +=============== +*/ +int HL_CompressVis(byte *vis, byte *dest) +{ + int j; + int rep; + int visrow; + byte *dest_p; + + dest_p = dest; + visrow = (hl_numleafs + 7)>>3; + + for (j=0 ; j>3; + out = decompressed; + + do + { + if (*in) + { + *out++ = *in++; + continue; + } + + c = in[1]; + in += 2; + while (c) + { + *out++ = 0; + c--; + } + } while (out - decompressed < row); +} + +//============================================================================= + +/* +============= +HL_SwapBSPFile + +Byte swaps all data in a bsp file. +============= +*/ +void HL_SwapBSPFile (qboolean todisk) +{ + int i, j, c; + hl_dmodel_t *d; + hl_dmiptexlump_t *mtl; + + +// models + for (i = 0; i < hl_nummodels; i++) + { + d = &hl_dmodels[i]; + + for (j = 0; j < HL_MAX_MAP_HULLS; j++) + d->headnode[j] = LittleLong(d->headnode[j]); + + d->visleafs = LittleLong(d->visleafs); + d->firstface = LittleLong(d->firstface); + d->numfaces = LittleLong(d->numfaces); + + for (j = 0; j < 3; j++) + { + d->mins[j] = LittleFloat(d->mins[j]); + d->maxs[j] = LittleFloat(d->maxs[j]); + d->origin[j] = LittleFloat(d->origin[j]); + } + } + +// +// vertexes +// + for (i = 0; i < hl_numvertexes; i++) + { + for (j = 0; j < 3; j++) + hl_dvertexes[i].point[j] = LittleFloat (hl_dvertexes[i].point[j]); + } + +// +// planes +// + for (i=0 ; inummiptex; + else + c = LittleLong(mtl->nummiptex); + mtl->nummiptex = LittleLong (mtl->nummiptex); + for (i=0 ; idataofs[i] = LittleLong(mtl->dataofs[i]); + } + +// +// marksurfaces +// + for (i=0 ; ilumps[lump].filelen; + ofs = hl_header->lumps[lump].fileofs; + + if (length % size) { + Error ("LoadBSPFile: odd lump size"); + } + // somehow things got out of range + if ((length/size) > maxsize) { + printf("WARNING: exceeded max size for lump %d size %d > maxsize %d\n", lump, (length/size), maxsize); + length = maxsize * size; + } + if ( ofs + length > hl_fileLength ) { + printf("WARNING: exceeded file length for lump %d\n", lump); + length = hl_fileLength - ofs; + if ( length <= 0 ) { + return 0; + } + } + + memcpy (dest, (byte *)hl_header + ofs, length); + + return length / size; +} + +/* +============= +HL_LoadBSPFile +============= +*/ +void HL_LoadBSPFile (char *filename, int offset, int length) +{ + int i; + +// +// load the file header +// + hl_fileLength = LoadFile (filename, (void **)&hl_header, offset, length); + +// swap the header + for (i=0 ; i< sizeof(hl_dheader_t)/4 ; i++) + ((int *)hl_header)[i] = LittleLong ( ((int *)hl_header)[i]); + + if (hl_header->version != HL_BSPVERSION) + Error ("%s is version %i, not %i", filename, hl_header->version, HL_BSPVERSION); + + hl_nummodels = HL_CopyLump (HL_LUMP_MODELS, hl_dmodels, sizeof(hl_dmodel_t), HL_MAX_MAP_MODELS ); + hl_numvertexes = HL_CopyLump (HL_LUMP_VERTEXES, hl_dvertexes, sizeof(hl_dvertex_t), HL_MAX_MAP_VERTS ); + hl_numplanes = HL_CopyLump (HL_LUMP_PLANES, hl_dplanes, sizeof(hl_dplane_t), HL_MAX_MAP_PLANES ); + hl_numleafs = HL_CopyLump (HL_LUMP_LEAFS, hl_dleafs, sizeof(hl_dleaf_t), HL_MAX_MAP_LEAFS ); + hl_numnodes = HL_CopyLump (HL_LUMP_NODES, hl_dnodes, sizeof(hl_dnode_t), HL_MAX_MAP_NODES ); + hl_numtexinfo = HL_CopyLump (HL_LUMP_TEXINFO, hl_texinfo, sizeof(hl_texinfo_t), HL_MAX_MAP_TEXINFO ); + hl_numclipnodes = HL_CopyLump (HL_LUMP_CLIPNODES, hl_dclipnodes, sizeof(hl_dclipnode_t), HL_MAX_MAP_CLIPNODES ); + hl_numfaces = HL_CopyLump (HL_LUMP_FACES, hl_dfaces, sizeof(hl_dface_t), HL_MAX_MAP_FACES ); + hl_nummarksurfaces = HL_CopyLump (HL_LUMP_MARKSURFACES, hl_dmarksurfaces, sizeof(hl_dmarksurfaces[0]), HL_MAX_MAP_MARKSURFACES ); + hl_numsurfedges = HL_CopyLump (HL_LUMP_SURFEDGES, hl_dsurfedges, sizeof(hl_dsurfedges[0]), HL_MAX_MAP_SURFEDGES ); + hl_numedges = HL_CopyLump (HL_LUMP_EDGES, hl_dedges, sizeof(hl_dedge_t), HL_MAX_MAP_EDGES ); + + hl_texdatasize = HL_CopyLump (HL_LUMP_TEXTURES, hl_dtexdata, 1, HL_MAX_MAP_MIPTEX ); + hl_visdatasize = HL_CopyLump (HL_LUMP_VISIBILITY, hl_dvisdata, 1, HL_MAX_MAP_VISIBILITY ); + hl_lightdatasize = HL_CopyLump (HL_LUMP_LIGHTING, hl_dlightdata, 1, HL_MAX_MAP_LIGHTING ); + hl_entdatasize = HL_CopyLump (HL_LUMP_ENTITIES, hl_dentdata, 1, HL_MAX_MAP_ENTSTRING ); + + FreeMemory(hl_header); // everything has been copied out + +// +// swap everything +// + HL_SwapBSPFile (false); + + hl_dmodels_checksum = FastChecksum( hl_dmodels, hl_nummodels*sizeof(hl_dmodels[0]) ); + hl_dvertexes_checksum = FastChecksum( hl_dvertexes, hl_numvertexes*sizeof(hl_dvertexes[0]) ); + hl_dplanes_checksum = FastChecksum( hl_dplanes, hl_numplanes*sizeof(hl_dplanes[0]) ); + hl_dleafs_checksum = FastChecksum( hl_dleafs, hl_numleafs*sizeof(hl_dleafs[0]) ); + hl_dnodes_checksum = FastChecksum( hl_dnodes, hl_numnodes*sizeof(hl_dnodes[0]) ); + hl_texinfo_checksum = FastChecksum( hl_texinfo, hl_numtexinfo*sizeof(hl_texinfo[0]) ); + hl_dclipnodes_checksum = FastChecksum( hl_dclipnodes, hl_numclipnodes*sizeof(hl_dclipnodes[0]) ); + hl_dfaces_checksum = FastChecksum( hl_dfaces, hl_numfaces*sizeof(hl_dfaces[0]) ); + hl_dmarksurfaces_checksum = FastChecksum( hl_dmarksurfaces, hl_nummarksurfaces*sizeof(hl_dmarksurfaces[0]) ); + hl_dsurfedges_checksum = FastChecksum( hl_dsurfedges, hl_numsurfedges*sizeof(hl_dsurfedges[0]) ); + hl_dedges_checksum = FastChecksum( hl_dedges, hl_numedges*sizeof(hl_dedges[0]) ); + hl_dtexdata_checksum = FastChecksum( hl_dtexdata, hl_numedges*sizeof(hl_dtexdata[0]) ); + hl_dvisdata_checksum = FastChecksum( hl_dvisdata, hl_visdatasize*sizeof(hl_dvisdata[0]) ); + hl_dlightdata_checksum = FastChecksum( hl_dlightdata, hl_lightdatasize*sizeof(hl_dlightdata[0]) ); + hl_dentdata_checksum = FastChecksum( hl_dentdata, hl_entdatasize*sizeof(hl_dentdata[0]) ); + +} + +//============================================================================ + +FILE *wadfile; +hl_dheader_t outheader; + +void HL_AddLump (int lumpnum, void *data, int len) +{ + hl_lump_t *lump; + + lump = &hl_header->lumps[lumpnum]; + + lump->fileofs = LittleLong( ftell(wadfile) ); + lump->filelen = LittleLong(len); + SafeWrite (wadfile, data, (len+3)&~3); +} + +/* +============= +HL_WriteBSPFile + +Swaps the bsp file in place, so it should not be referenced again +============= +*/ +void HL_WriteBSPFile (char *filename) +{ + hl_header = &outheader; + memset (hl_header, 0, sizeof(hl_dheader_t)); + + HL_SwapBSPFile (true); + + hl_header->version = LittleLong (HL_BSPVERSION); + + wadfile = SafeOpenWrite (filename); + SafeWrite (wadfile, hl_header, sizeof(hl_dheader_t)); // overwritten later + + HL_AddLump (HL_LUMP_PLANES, hl_dplanes, hl_numplanes*sizeof(hl_dplane_t)); + HL_AddLump (HL_LUMP_LEAFS, hl_dleafs, hl_numleafs*sizeof(hl_dleaf_t)); + HL_AddLump (HL_LUMP_VERTEXES, hl_dvertexes, hl_numvertexes*sizeof(hl_dvertex_t)); + HL_AddLump (HL_LUMP_NODES, hl_dnodes, hl_numnodes*sizeof(hl_dnode_t)); + HL_AddLump (HL_LUMP_TEXINFO, hl_texinfo, hl_numtexinfo*sizeof(hl_texinfo_t)); + HL_AddLump (HL_LUMP_FACES, hl_dfaces, hl_numfaces*sizeof(hl_dface_t)); + HL_AddLump (HL_LUMP_CLIPNODES, hl_dclipnodes, hl_numclipnodes*sizeof(hl_dclipnode_t)); + HL_AddLump (HL_LUMP_MARKSURFACES, hl_dmarksurfaces, hl_nummarksurfaces*sizeof(hl_dmarksurfaces[0])); + HL_AddLump (HL_LUMP_SURFEDGES, hl_dsurfedges, hl_numsurfedges*sizeof(hl_dsurfedges[0])); + HL_AddLump (HL_LUMP_EDGES, hl_dedges, hl_numedges*sizeof(hl_dedge_t)); + HL_AddLump (HL_LUMP_MODELS, hl_dmodels, hl_nummodels*sizeof(hl_dmodel_t)); + + HL_AddLump (HL_LUMP_LIGHTING, hl_dlightdata, hl_lightdatasize); + HL_AddLump (HL_LUMP_VISIBILITY, hl_dvisdata, hl_visdatasize); + HL_AddLump (HL_LUMP_ENTITIES, hl_dentdata, hl_entdatasize); + HL_AddLump (HL_LUMP_TEXTURES, hl_dtexdata, hl_texdatasize); + + fseek (wadfile, 0, SEEK_SET); + SafeWrite (wadfile, hl_header, sizeof(hl_dheader_t)); + fclose (wadfile); +} + +//============================================================================ + +#define ENTRIES(a) (sizeof(a)/sizeof(*(a))) +#define ENTRYSIZE(a) (sizeof(*(a))) + +ArrayUsage( char *szItem, int items, int maxitems, int itemsize ) +{ + float percentage = maxitems ? items * 100.0 / maxitems : 0.0; + + qprintf("%-12s %7i/%-7i %7i/%-7i (%4.1f%%)", + szItem, items, maxitems, items * itemsize, maxitems * itemsize, percentage ); + if ( percentage > 80.0 ) + qprintf( "VERY FULL!\n" ); + else if ( percentage > 95.0 ) + qprintf( "SIZE DANGER!\n" ); + else if ( percentage > 99.9 ) + qprintf( "SIZE OVERFLOW!!!\n" ); + else + qprintf( "\n" ); + return items * itemsize; +} + +GlobUsage( char *szItem, int itemstorage, int maxstorage ) +{ + float percentage = maxstorage ? itemstorage * 100.0 / maxstorage : 0.0; + + qprintf("%-12s [variable] %7i/%-7i (%4.1f%%)", + szItem, itemstorage, maxstorage, percentage ); + if ( percentage > 80.0 ) + qprintf( "VERY FULL!\n" ); + else if ( percentage > 95.0 ) + qprintf( "SIZE DANGER!\n" ); + else if ( percentage > 99.9 ) + qprintf( "SIZE OVERFLOW!!!\n" ); + else + qprintf( "\n" ); + return itemstorage; +} + +/* +============= +HL_PrintBSPFileSizes + +Dumps info about current file +============= +*/ +void HL_PrintBSPFileSizes(void) +{ + int numtextures = hl_texdatasize ? ((hl_dmiptexlump_t*)hl_dtexdata)->nummiptex : 0; + int totalmemory = 0; + + qprintf("\n"); + qprintf("Object names Objects/Maxobjs Memory / Maxmem Fullness\n" ); + qprintf("------------ --------------- --------------- --------\n" ); + + totalmemory += ArrayUsage( "models", hl_nummodels, ENTRIES(hl_dmodels), ENTRYSIZE(hl_dmodels) ); + totalmemory += ArrayUsage( "planes", hl_numplanes, ENTRIES(hl_dplanes), ENTRYSIZE(hl_dplanes) ); + totalmemory += ArrayUsage( "vertexes", hl_numvertexes, ENTRIES(hl_dvertexes), ENTRYSIZE(hl_dvertexes) ); + totalmemory += ArrayUsage( "nodes", hl_numnodes, ENTRIES(hl_dnodes), ENTRYSIZE(hl_dnodes) ); + totalmemory += ArrayUsage( "texinfos", hl_numtexinfo, ENTRIES(hl_texinfo), ENTRYSIZE(hl_texinfo) ); + totalmemory += ArrayUsage( "faces", hl_numfaces, ENTRIES(hl_dfaces), ENTRYSIZE(hl_dfaces) ); + totalmemory += ArrayUsage( "clipnodes", hl_numclipnodes, ENTRIES(hl_dclipnodes), ENTRYSIZE(hl_dclipnodes) ); + totalmemory += ArrayUsage( "leaves", hl_numleafs, ENTRIES(hl_dleafs), ENTRYSIZE(hl_dleafs) ); + totalmemory += ArrayUsage( "marksurfaces",hl_nummarksurfaces,ENTRIES(hl_dmarksurfaces),ENTRYSIZE(hl_dmarksurfaces) ); + totalmemory += ArrayUsage( "surfedges", hl_numsurfedges, ENTRIES(hl_dsurfedges), ENTRYSIZE(hl_dsurfedges) ); + totalmemory += ArrayUsage( "edges", hl_numedges, ENTRIES(hl_dedges), ENTRYSIZE(hl_dedges) ); + + totalmemory += GlobUsage( "texdata", hl_texdatasize, sizeof(hl_dtexdata) ); + totalmemory += GlobUsage( "lightdata", hl_lightdatasize, sizeof(hl_dlightdata) ); + totalmemory += GlobUsage( "visdata", hl_visdatasize, sizeof(hl_dvisdata) ); + totalmemory += GlobUsage( "entdata", hl_entdatasize, sizeof(hl_dentdata) ); + + qprintf( "=== Total BSP file data space used: %d bytes ===\n\n", totalmemory ); +} + + + +/* +================= +ParseEpair +================= +* / +epair_t *ParseEpair (void) +{ + epair_t *e; + + e = malloc (sizeof(epair_t)); + memset (e, 0, sizeof(epair_t)); + + if (strlen(token) >= MAX_KEY-1) + Error ("ParseEpar: token too long"); + e->key = copystring(token); + GetToken (false); + if (strlen(token) >= MAX_VALUE-1) + Error ("ParseEpar: token too long"); + e->value = copystring(token); + + return e; +} //*/ + + +/* +================ +ParseEntity +================ +* / +qboolean ParseEntity (void) +{ + epair_t *e; + entity_t *mapent; + + if (!GetToken (true)) + return false; + + if (strcmp (token, "{") ) + Error ("ParseEntity: { not found"); + + if (num_entities == HL_MAX_MAP_ENTITIES) + Error ("num_entities == HL_MAX_MAP_ENTITIES"); + + mapent = &entities[num_entities]; + num_entities++; + + do + { + if (!GetToken (true)) + Error ("ParseEntity: EOF without closing brace"); + if (!strcmp (token, "}") ) + break; + e = ParseEpair (); + e->next = mapent->epairs; + mapent->epairs = e; + } while (1); + + return true; +} //*/ + +/* +================ +ParseEntities + +Parses the dentdata string into entities +================ +*/ +void HL_ParseEntities (void) +{ + script_t *script; + + num_entities = 0; + script = LoadScriptMemory(hl_dentdata, hl_entdatasize, "*Half-Life bsp file"); + SetScriptFlags(script, SCFL_NOSTRINGWHITESPACES | + SCFL_NOSTRINGESCAPECHARS); + + while(ParseEntity(script)) + { + } //end while + + FreeScript(script); +} //end of the function HL_ParseEntities + + +/* +================ +UnparseEntities + +Generates the dentdata string from all the entities +================ +*/ +void HL_UnparseEntities (void) +{ + char *buf, *end; + epair_t *ep; + char line[2048]; + int i; + + buf = hl_dentdata; + end = buf; + *end = 0; + + for (i=0 ; inext) + { + sprintf (line, "\"%s\" \"%s\"\n", ep->key, ep->value); + strcat (end, line); + end += strlen(line); + } + strcat (end,"}\n"); + end += 2; + + if (end > buf + HL_MAX_MAP_ENTSTRING) + Error ("Entity text too long"); + } + hl_entdatasize = end - buf + 1; +} //end of the function HL_UnparseEntities + + +/* +void SetKeyValue (entity_t *ent, char *key, char *value) +{ + epair_t *ep; + + for (ep=ent->epairs ; ep ; ep=ep->next) + if (!strcmp (ep->key, key) ) + { + free (ep->value); + ep->value = copystring(value); + return; + } + ep = malloc (sizeof(*ep)); + ep->next = ent->epairs; + ent->epairs = ep; + ep->key = copystring(key); + ep->value = copystring(value); +} + +char *ValueForKey (entity_t *ent, char *key) +{ + epair_t *ep; + + for (ep=ent->epairs ; ep ; ep=ep->next) + if (!strcmp (ep->key, key) ) + return ep->value; + return ""; +} + +vec_t FloatForKey (entity_t *ent, char *key) +{ + char *k; + + k = ValueForKey (ent, key); + return atof(k); +} + +void GetVectorForKey (entity_t *ent, char *key, vec3_t vec) +{ + char *k; + double v1, v2, v3; + + k = ValueForKey (ent, key); +// scanf into doubles, then assign, so it is vec_t size independent + v1 = v2 = v3 = 0; + sscanf (k, "%lf %lf %lf", &v1, &v2, &v3); + vec[0] = v1; + vec[1] = v2; + vec[2] = v3; +} //*/ diff --git a/code/bspc/l_bsp_hl.h b/code/bspc/l_bsp_hl.h index 0089b1e..4146202 100755 --- a/code/bspc/l_bsp_hl.h +++ b/code/bspc/l_bsp_hl.h @@ -1,314 +1,314 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// upper design bounds - -#define HL_MAX_MAP_HULLS 4 - -#define HL_MAX_MAP_MODELS 400 -#define HL_MAX_MAP_BRUSHES 4096 -#define HL_MAX_MAP_ENTITIES 1024 -#define HL_MAX_MAP_ENTSTRING (128*1024) - -#define HL_MAX_MAP_PLANES 32767 -#define HL_MAX_MAP_NODES 32767 // because negative shorts are contents -#define HL_MAX_MAP_CLIPNODES 32767 // -#define HL_MAX_MAP_LEAFS 8192 -#define HL_MAX_MAP_VERTS 65535 -#define HL_MAX_MAP_FACES 65535 -#define HL_MAX_MAP_MARKSURFACES 65535 -#define HL_MAX_MAP_TEXINFO 8192 -#define HL_MAX_MAP_EDGES 256000 -#define HL_MAX_MAP_SURFEDGES 512000 -#define HL_MAX_MAP_TEXTURES 512 -#define HL_MAX_MAP_MIPTEX 0x200000 -#define HL_MAX_MAP_LIGHTING 0x200000 -#define HL_MAX_MAP_VISIBILITY 0x200000 - -#define HL_MAX_MAP_PORTALS 65536 - -// key / value pair sizes - -#define MAX_KEY 32 -#define MAX_VALUE 1024 - -//============================================================================= - - -#define HL_BSPVERSION 30 -#define HL_TOOLVERSION 2 - - -typedef struct -{ - int fileofs, filelen; -} hl_lump_t; - -#define HL_LUMP_ENTITIES 0 -#define HL_LUMP_PLANES 1 -#define HL_LUMP_TEXTURES 2 -#define HL_LUMP_VERTEXES 3 -#define HL_LUMP_VISIBILITY 4 -#define HL_LUMP_NODES 5 -#define HL_LUMP_TEXINFO 6 -#define HL_LUMP_FACES 7 -#define HL_LUMP_LIGHTING 8 -#define HL_LUMP_CLIPNODES 9 -#define HL_LUMP_LEAFS 10 -#define HL_LUMP_MARKSURFACES 11 -#define HL_LUMP_EDGES 12 -#define HL_LUMP_SURFEDGES 13 -#define HL_LUMP_MODELS 14 - -#define HL_HEADER_LUMPS 15 - -typedef struct -{ - float mins[3], maxs[3]; - float origin[3]; - int headnode[HL_MAX_MAP_HULLS]; - int visleafs; // not including the solid leaf 0 - int firstface, numfaces; -} hl_dmodel_t; - -typedef struct -{ - int version; - hl_lump_t lumps[HL_HEADER_LUMPS]; -} hl_dheader_t; - -typedef struct -{ - int nummiptex; - int dataofs[4]; // [nummiptex] -} hl_dmiptexlump_t; - -#define MIPLEVELS 4 -typedef struct hl_miptex_s -{ - char name[16]; - unsigned width, height; - unsigned offsets[MIPLEVELS]; // four mip maps stored -} hl_miptex_t; - - -typedef struct -{ - float point[3]; -} hl_dvertex_t; - - -// 0-2 are axial planes -#define PLANE_X 0 -#define PLANE_Y 1 -#define PLANE_Z 2 - -// 3-5 are non-axial planes snapped to the nearest -#define PLANE_ANYX 3 -#define PLANE_ANYY 4 -#define PLANE_ANYZ 5 - -typedef struct -{ - float normal[3]; - float dist; - int type; // PLANE_X - PLANE_ANYZ ?remove? trivial to regenerate -} hl_dplane_t; - - - -#define HL_CONTENTS_EMPTY -1 -#define HL_CONTENTS_SOLID -2 -#define HL_CONTENTS_WATER -3 -#define HL_CONTENTS_SLIME -4 -#define HL_CONTENTS_LAVA -5 -#define HL_CONTENTS_SKY -6 -#define HL_CONTENTS_ORIGIN -7 // removed at csg time -#define HL_CONTENTS_CLIP -8 // changed to contents_solid - -#define HL_CONTENTS_CURRENT_0 -9 -#define HL_CONTENTS_CURRENT_90 -10 -#define HL_CONTENTS_CURRENT_180 -11 -#define HL_CONTENTS_CURRENT_270 -12 -#define HL_CONTENTS_CURRENT_UP -13 -#define HL_CONTENTS_CURRENT_DOWN -14 - -#define HL_CONTENTS_TRANSLUCENT -15 - -// !!! if this is changed, it must be changed in asm_i386.h too !!! -typedef struct -{ - int planenum; - short children[2]; // negative numbers are -(leafs+1), not nodes - short mins[3]; // for sphere culling - short maxs[3]; - unsigned short firstface; - unsigned short numfaces; // counting both sides -} hl_dnode_t; - -typedef struct -{ - int planenum; - short children[2]; // negative numbers are contents -} hl_dclipnode_t; - - -typedef struct hl_texinfo_s -{ - float vecs[2][4]; // [s/t][xyz offset] - int miptex; - int flags; -} hl_texinfo_t; -#define TEX_SPECIAL 1 // sky or slime, no lightmap or 256 subdivision - -// note that edge 0 is never used, because negative edge nums are used for -// counterclockwise use of the edge in a face -typedef struct -{ - unsigned short v[2]; // vertex numbers -} hl_dedge_t; - -#define MAXLIGHTMAPS 4 -typedef struct -{ - short planenum; - short side; - - int firstedge; // we must support > 64k edges - short numedges; - short texinfo; - -// lighting info - byte styles[MAXLIGHTMAPS]; - int lightofs; // start of [numstyles*surfsize] samples -} hl_dface_t; - - -#define AMBIENT_WATER 0 -#define AMBIENT_SKY 1 -#define AMBIENT_SLIME 2 -#define AMBIENT_LAVA 3 - -#define NUM_AMBIENTS 4 // automatic ambient sounds - -// leaf 0 is the generic HL_CONTENTS_SOLID leaf, used for all solid areas -// all other leafs need visibility info -typedef struct -{ - int contents; - int visofs; // -1 = no visibility info - - short mins[3]; // for frustum culling - short maxs[3]; - - unsigned short firstmarksurface; - unsigned short nummarksurfaces; - - byte ambient_level[NUM_AMBIENTS]; -} hl_dleaf_t; - - -//============================================================================ - -#ifndef QUAKE_GAME - -#define ANGLE_UP -1 -#define ANGLE_DOWN -2 - - -// the utilities get to be lazy and just use large static arrays - -extern int hl_nummodels; -extern hl_dmodel_t *hl_dmodels;//[MAX_MAP_MODELS]; -extern int hl_dmodels_checksum; - -extern int hl_visdatasize; -extern byte *hl_dvisdata;//[MAX_MAP_VISIBILITY]; -extern int hl_dvisdata_checksum; - -extern int hl_lightdatasize; -extern byte *hl_dlightdata;//[MAX_MAP_LIGHTING]; -extern int hl_dlightdata_checksum; - -extern int hl_texdatasize; -extern byte *hl_dtexdata;//[MAX_MAP_MIPTEX]; // (dmiptexlump_t) -extern int hl_dtexdata_checksum; - -extern int hl_entdatasize; -extern char *hl_dentdata;//[MAX_MAP_ENTSTRING]; -extern int hl_dentdata_checksum; - -extern int hl_numleafs; -extern hl_dleaf_t *hl_dleafs;//[MAX_MAP_LEAFS]; -extern int hl_dleafs_checksum; - -extern int hl_numplanes; -extern hl_dplane_t *hl_dplanes;//[MAX_MAP_PLANES]; -extern int hl_dplanes_checksum; - -extern int hl_numvertexes; -extern hl_dvertex_t *hl_dvertexes;//[MAX_MAP_VERTS]; -extern int hl_dvertexes_checksum; - -extern int hl_numnodes; -extern hl_dnode_t *hl_dnodes;//[MAX_MAP_NODES]; -extern int hl_dnodes_checksum; - -extern int hl_numtexinfo; -extern hl_texinfo_t *hl_texinfo;//[MAX_MAP_TEXINFO]; -extern int hl_texinfo_checksum; - -extern int hl_numfaces; -extern hl_dface_t *hl_dfaces;//[MAX_MAP_FACES]; -extern int hl_dfaces_checksum; - -extern int hl_numclipnodes; -extern hl_dclipnode_t *hl_dclipnodes;//[MAX_MAP_CLIPNODES]; -extern int hl_dclipnodes_checksum; - -extern int hl_numedges; -extern hl_dedge_t *hl_dedges;//[MAX_MAP_EDGES]; -extern int hl_dedges_checksum; - -extern int hl_nummarksurfaces; -extern unsigned short *hl_dmarksurfaces;//[MAX_MAP_MARKSURFACES]; -extern int hl_dmarksurfaces_checksum; - -extern int hl_numsurfedges; -extern int *hl_dsurfedges;//[MAX_MAP_SURFEDGES]; -extern int hl_dsurfedges_checksum; - -int FastChecksum(void *buffer, int bytes); - -void HL_AllocMaxBSP(void); -void HL_FreeMaxBSP(void); - -void HL_DecompressVis(byte *in, byte *decompressed); -int HL_CompressVis(byte *vis, byte *dest); - -void HL_LoadBSPFile(char *filename, int offset, int length); -void HL_WriteBSPFile(char *filename); -void HL_PrintBSPFileSizes(void); -void HL_PrintBSPFileSizes(void); -void HL_ParseEntities(void); -void HL_UnparseEntities(void); - -#endif +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// upper design bounds + +#define HL_MAX_MAP_HULLS 4 + +#define HL_MAX_MAP_MODELS 400 +#define HL_MAX_MAP_BRUSHES 4096 +#define HL_MAX_MAP_ENTITIES 1024 +#define HL_MAX_MAP_ENTSTRING (128*1024) + +#define HL_MAX_MAP_PLANES 32767 +#define HL_MAX_MAP_NODES 32767 // because negative shorts are contents +#define HL_MAX_MAP_CLIPNODES 32767 // +#define HL_MAX_MAP_LEAFS 8192 +#define HL_MAX_MAP_VERTS 65535 +#define HL_MAX_MAP_FACES 65535 +#define HL_MAX_MAP_MARKSURFACES 65535 +#define HL_MAX_MAP_TEXINFO 8192 +#define HL_MAX_MAP_EDGES 256000 +#define HL_MAX_MAP_SURFEDGES 512000 +#define HL_MAX_MAP_TEXTURES 512 +#define HL_MAX_MAP_MIPTEX 0x200000 +#define HL_MAX_MAP_LIGHTING 0x200000 +#define HL_MAX_MAP_VISIBILITY 0x200000 + +#define HL_MAX_MAP_PORTALS 65536 + +// key / value pair sizes + +#define MAX_KEY 32 +#define MAX_VALUE 1024 + +//============================================================================= + + +#define HL_BSPVERSION 30 +#define HL_TOOLVERSION 2 + + +typedef struct +{ + int fileofs, filelen; +} hl_lump_t; + +#define HL_LUMP_ENTITIES 0 +#define HL_LUMP_PLANES 1 +#define HL_LUMP_TEXTURES 2 +#define HL_LUMP_VERTEXES 3 +#define HL_LUMP_VISIBILITY 4 +#define HL_LUMP_NODES 5 +#define HL_LUMP_TEXINFO 6 +#define HL_LUMP_FACES 7 +#define HL_LUMP_LIGHTING 8 +#define HL_LUMP_CLIPNODES 9 +#define HL_LUMP_LEAFS 10 +#define HL_LUMP_MARKSURFACES 11 +#define HL_LUMP_EDGES 12 +#define HL_LUMP_SURFEDGES 13 +#define HL_LUMP_MODELS 14 + +#define HL_HEADER_LUMPS 15 + +typedef struct +{ + float mins[3], maxs[3]; + float origin[3]; + int headnode[HL_MAX_MAP_HULLS]; + int visleafs; // not including the solid leaf 0 + int firstface, numfaces; +} hl_dmodel_t; + +typedef struct +{ + int version; + hl_lump_t lumps[HL_HEADER_LUMPS]; +} hl_dheader_t; + +typedef struct +{ + int nummiptex; + int dataofs[4]; // [nummiptex] +} hl_dmiptexlump_t; + +#define MIPLEVELS 4 +typedef struct hl_miptex_s +{ + char name[16]; + unsigned width, height; + unsigned offsets[MIPLEVELS]; // four mip maps stored +} hl_miptex_t; + + +typedef struct +{ + float point[3]; +} hl_dvertex_t; + + +// 0-2 are axial planes +#define PLANE_X 0 +#define PLANE_Y 1 +#define PLANE_Z 2 + +// 3-5 are non-axial planes snapped to the nearest +#define PLANE_ANYX 3 +#define PLANE_ANYY 4 +#define PLANE_ANYZ 5 + +typedef struct +{ + float normal[3]; + float dist; + int type; // PLANE_X - PLANE_ANYZ ?remove? trivial to regenerate +} hl_dplane_t; + + + +#define HL_CONTENTS_EMPTY -1 +#define HL_CONTENTS_SOLID -2 +#define HL_CONTENTS_WATER -3 +#define HL_CONTENTS_SLIME -4 +#define HL_CONTENTS_LAVA -5 +#define HL_CONTENTS_SKY -6 +#define HL_CONTENTS_ORIGIN -7 // removed at csg time +#define HL_CONTENTS_CLIP -8 // changed to contents_solid + +#define HL_CONTENTS_CURRENT_0 -9 +#define HL_CONTENTS_CURRENT_90 -10 +#define HL_CONTENTS_CURRENT_180 -11 +#define HL_CONTENTS_CURRENT_270 -12 +#define HL_CONTENTS_CURRENT_UP -13 +#define HL_CONTENTS_CURRENT_DOWN -14 + +#define HL_CONTENTS_TRANSLUCENT -15 + +// !!! if this is changed, it must be changed in asm_i386.h too !!! +typedef struct +{ + int planenum; + short children[2]; // negative numbers are -(leafs+1), not nodes + short mins[3]; // for sphere culling + short maxs[3]; + unsigned short firstface; + unsigned short numfaces; // counting both sides +} hl_dnode_t; + +typedef struct +{ + int planenum; + short children[2]; // negative numbers are contents +} hl_dclipnode_t; + + +typedef struct hl_texinfo_s +{ + float vecs[2][4]; // [s/t][xyz offset] + int miptex; + int flags; +} hl_texinfo_t; +#define TEX_SPECIAL 1 // sky or slime, no lightmap or 256 subdivision + +// note that edge 0 is never used, because negative edge nums are used for +// counterclockwise use of the edge in a face +typedef struct +{ + unsigned short v[2]; // vertex numbers +} hl_dedge_t; + +#define MAXLIGHTMAPS 4 +typedef struct +{ + short planenum; + short side; + + int firstedge; // we must support > 64k edges + short numedges; + short texinfo; + +// lighting info + byte styles[MAXLIGHTMAPS]; + int lightofs; // start of [numstyles*surfsize] samples +} hl_dface_t; + + +#define AMBIENT_WATER 0 +#define AMBIENT_SKY 1 +#define AMBIENT_SLIME 2 +#define AMBIENT_LAVA 3 + +#define NUM_AMBIENTS 4 // automatic ambient sounds + +// leaf 0 is the generic HL_CONTENTS_SOLID leaf, used for all solid areas +// all other leafs need visibility info +typedef struct +{ + int contents; + int visofs; // -1 = no visibility info + + short mins[3]; // for frustum culling + short maxs[3]; + + unsigned short firstmarksurface; + unsigned short nummarksurfaces; + + byte ambient_level[NUM_AMBIENTS]; +} hl_dleaf_t; + + +//============================================================================ + +#ifndef QUAKE_GAME + +#define ANGLE_UP -1 +#define ANGLE_DOWN -2 + + +// the utilities get to be lazy and just use large static arrays + +extern int hl_nummodels; +extern hl_dmodel_t *hl_dmodels;//[MAX_MAP_MODELS]; +extern int hl_dmodels_checksum; + +extern int hl_visdatasize; +extern byte *hl_dvisdata;//[MAX_MAP_VISIBILITY]; +extern int hl_dvisdata_checksum; + +extern int hl_lightdatasize; +extern byte *hl_dlightdata;//[MAX_MAP_LIGHTING]; +extern int hl_dlightdata_checksum; + +extern int hl_texdatasize; +extern byte *hl_dtexdata;//[MAX_MAP_MIPTEX]; // (dmiptexlump_t) +extern int hl_dtexdata_checksum; + +extern int hl_entdatasize; +extern char *hl_dentdata;//[MAX_MAP_ENTSTRING]; +extern int hl_dentdata_checksum; + +extern int hl_numleafs; +extern hl_dleaf_t *hl_dleafs;//[MAX_MAP_LEAFS]; +extern int hl_dleafs_checksum; + +extern int hl_numplanes; +extern hl_dplane_t *hl_dplanes;//[MAX_MAP_PLANES]; +extern int hl_dplanes_checksum; + +extern int hl_numvertexes; +extern hl_dvertex_t *hl_dvertexes;//[MAX_MAP_VERTS]; +extern int hl_dvertexes_checksum; + +extern int hl_numnodes; +extern hl_dnode_t *hl_dnodes;//[MAX_MAP_NODES]; +extern int hl_dnodes_checksum; + +extern int hl_numtexinfo; +extern hl_texinfo_t *hl_texinfo;//[MAX_MAP_TEXINFO]; +extern int hl_texinfo_checksum; + +extern int hl_numfaces; +extern hl_dface_t *hl_dfaces;//[MAX_MAP_FACES]; +extern int hl_dfaces_checksum; + +extern int hl_numclipnodes; +extern hl_dclipnode_t *hl_dclipnodes;//[MAX_MAP_CLIPNODES]; +extern int hl_dclipnodes_checksum; + +extern int hl_numedges; +extern hl_dedge_t *hl_dedges;//[MAX_MAP_EDGES]; +extern int hl_dedges_checksum; + +extern int hl_nummarksurfaces; +extern unsigned short *hl_dmarksurfaces;//[MAX_MAP_MARKSURFACES]; +extern int hl_dmarksurfaces_checksum; + +extern int hl_numsurfedges; +extern int *hl_dsurfedges;//[MAX_MAP_SURFEDGES]; +extern int hl_dsurfedges_checksum; + +int FastChecksum(void *buffer, int bytes); + +void HL_AllocMaxBSP(void); +void HL_FreeMaxBSP(void); + +void HL_DecompressVis(byte *in, byte *decompressed); +int HL_CompressVis(byte *vis, byte *dest); + +void HL_LoadBSPFile(char *filename, int offset, int length); +void HL_WriteBSPFile(char *filename); +void HL_PrintBSPFileSizes(void); +void HL_PrintBSPFileSizes(void); +void HL_ParseEntities(void); +void HL_UnparseEntities(void); + +#endif diff --git a/code/bspc/l_bsp_q1.c b/code/bspc/l_bsp_q1.c index c68b5c8..7ac1553 100755 --- a/code/bspc/l_bsp_q1.c +++ b/code/bspc/l_bsp_q1.c @@ -1,620 +1,620 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "l_cmd.h" -#include "l_math.h" -#include "l_mem.h" -#include "l_log.h" -#include "../botlib/l_script.h" -#include "l_bsp_q1.h" -#include "l_bsp_ent.h" - -//============================================================================= - -int q1_nummodels; -q1_dmodel_t *q1_dmodels;//[MAX_MAP_MODELS]; - -int q1_visdatasize; -byte *q1_dvisdata;//[MAX_MAP_VISIBILITY]; - -int q1_lightdatasize; -byte *q1_dlightdata;//[MAX_MAP_LIGHTING]; - -int q1_texdatasize; -byte *q1_dtexdata;//[MAX_MAP_MIPTEX]; // (dmiptexlump_t) - -int q1_entdatasize; -char *q1_dentdata;//[MAX_MAP_ENTSTRING]; - -int q1_numleafs; -q1_dleaf_t *q1_dleafs;//[MAX_MAP_LEAFS]; - -int q1_numplanes; -q1_dplane_t *q1_dplanes;//[MAX_MAP_PLANES]; - -int q1_numvertexes; -q1_dvertex_t *q1_dvertexes;//[MAX_MAP_VERTS]; - -int q1_numnodes; -q1_dnode_t *q1_dnodes;//[MAX_MAP_NODES]; - -int q1_numtexinfo; -q1_texinfo_t *q1_texinfo;//[MAX_MAP_TEXINFO]; - -int q1_numfaces; -q1_dface_t *q1_dfaces;//[MAX_MAP_FACES]; - -int q1_numclipnodes; -q1_dclipnode_t *q1_dclipnodes;//[MAX_MAP_CLIPNODES]; - -int q1_numedges; -q1_dedge_t *q1_dedges;//[MAX_MAP_EDGES]; - -int q1_nummarksurfaces; -unsigned short *q1_dmarksurfaces;//[MAX_MAP_MARKSURFACES]; - -int q1_numsurfedges; -int *q1_dsurfedges;//[MAX_MAP_SURFEDGES]; - -//============================================================================= - -int q1_bspallocated = false; -int q1_allocatedbspmem = 0; - -void Q1_AllocMaxBSP(void) -{ - //models - q1_nummodels = 0; - q1_dmodels = (q1_dmodel_t *) GetMemory(Q1_MAX_MAP_MODELS * sizeof(q1_dmodel_t)); - q1_allocatedbspmem = Q1_MAX_MAP_MODELS * sizeof(q1_dmodel_t); - //visibility - q1_visdatasize = 0; - q1_dvisdata = (byte *) GetMemory(Q1_MAX_MAP_VISIBILITY * sizeof(byte)); - q1_allocatedbspmem += Q1_MAX_MAP_VISIBILITY * sizeof(byte); - //light data - q1_lightdatasize = 0; - q1_dlightdata = (byte *) GetMemory(Q1_MAX_MAP_LIGHTING * sizeof(byte)); - q1_allocatedbspmem += Q1_MAX_MAP_LIGHTING * sizeof(byte); - //texture data - q1_texdatasize = 0; - q1_dtexdata = (byte *) GetMemory(Q1_MAX_MAP_MIPTEX * sizeof(byte)); // (dmiptexlump_t) - q1_allocatedbspmem += Q1_MAX_MAP_MIPTEX * sizeof(byte); - //entities - q1_entdatasize = 0; - q1_dentdata = (char *) GetMemory(Q1_MAX_MAP_ENTSTRING * sizeof(char)); - q1_allocatedbspmem += Q1_MAX_MAP_ENTSTRING * sizeof(char); - //leaves - q1_numleafs = 0; - q1_dleafs = (q1_dleaf_t *) GetMemory(Q1_MAX_MAP_LEAFS * sizeof(q1_dleaf_t)); - q1_allocatedbspmem += Q1_MAX_MAP_LEAFS * sizeof(q1_dleaf_t); - //planes - q1_numplanes = 0; - q1_dplanes = (q1_dplane_t *) GetMemory(Q1_MAX_MAP_PLANES * sizeof(q1_dplane_t)); - q1_allocatedbspmem += Q1_MAX_MAP_PLANES * sizeof(q1_dplane_t); - //vertexes - q1_numvertexes = 0; - q1_dvertexes = (q1_dvertex_t *) GetMemory(Q1_MAX_MAP_VERTS * sizeof(q1_dvertex_t)); - q1_allocatedbspmem += Q1_MAX_MAP_VERTS * sizeof(q1_dvertex_t); - //nodes - q1_numnodes = 0; - q1_dnodes = (q1_dnode_t *) GetMemory(Q1_MAX_MAP_NODES * sizeof(q1_dnode_t)); - q1_allocatedbspmem += Q1_MAX_MAP_NODES * sizeof(q1_dnode_t); - //texture info - q1_numtexinfo = 0; - q1_texinfo = (q1_texinfo_t *) GetMemory(Q1_MAX_MAP_TEXINFO * sizeof(q1_texinfo_t)); - q1_allocatedbspmem += Q1_MAX_MAP_TEXINFO * sizeof(q1_texinfo_t); - //faces - q1_numfaces = 0; - q1_dfaces = (q1_dface_t *) GetMemory(Q1_MAX_MAP_FACES * sizeof(q1_dface_t)); - q1_allocatedbspmem += Q1_MAX_MAP_FACES * sizeof(q1_dface_t); - //clip nodes - q1_numclipnodes = 0; - q1_dclipnodes = (q1_dclipnode_t *) GetMemory(Q1_MAX_MAP_CLIPNODES * sizeof(q1_dclipnode_t)); - q1_allocatedbspmem += Q1_MAX_MAP_CLIPNODES * sizeof(q1_dclipnode_t); - //edges - q1_numedges = 0; - q1_dedges = (q1_dedge_t *) GetMemory(Q1_MAX_MAP_EDGES * sizeof(q1_dedge_t)); - q1_allocatedbspmem += Q1_MAX_MAP_EDGES, sizeof(q1_dedge_t); - //mark surfaces - q1_nummarksurfaces = 0; - q1_dmarksurfaces = (unsigned short *) GetMemory(Q1_MAX_MAP_MARKSURFACES * sizeof(unsigned short)); - q1_allocatedbspmem += Q1_MAX_MAP_MARKSURFACES * sizeof(unsigned short); - //surface edges - q1_numsurfedges = 0; - q1_dsurfedges = (int *) GetMemory(Q1_MAX_MAP_SURFEDGES * sizeof(int)); - q1_allocatedbspmem += Q1_MAX_MAP_SURFEDGES * sizeof(int); - //print allocated memory - Log_Print("allocated "); - PrintMemorySize(q1_allocatedbspmem); - Log_Print(" of BSP memory\n"); -} //end of the function Q1_AllocMaxBSP - -void Q1_FreeMaxBSP(void) -{ - //models - q1_nummodels = 0; - FreeMemory(q1_dmodels); - q1_dmodels = NULL; - //visibility - q1_visdatasize = 0; - FreeMemory(q1_dvisdata); - q1_dvisdata = NULL; - //light data - q1_lightdatasize = 0; - FreeMemory(q1_dlightdata); - q1_dlightdata = NULL; - //texture data - q1_texdatasize = 0; - FreeMemory(q1_dtexdata); - q1_dtexdata = NULL; - //entities - q1_entdatasize = 0; - FreeMemory(q1_dentdata); - q1_dentdata = NULL; - //leaves - q1_numleafs = 0; - FreeMemory(q1_dleafs); - q1_dleafs = NULL; - //planes - q1_numplanes = 0; - FreeMemory(q1_dplanes); - q1_dplanes = NULL; - //vertexes - q1_numvertexes = 0; - FreeMemory(q1_dvertexes); - q1_dvertexes = NULL; - //nodes - q1_numnodes = 0; - FreeMemory(q1_dnodes); - q1_dnodes = NULL; - //texture info - q1_numtexinfo = 0; - FreeMemory(q1_texinfo); - q1_texinfo = NULL; - //faces - q1_numfaces = 0; - FreeMemory(q1_dfaces); - q1_dfaces = NULL; - //clip nodes - q1_numclipnodes = 0; - FreeMemory(q1_dclipnodes); - q1_dclipnodes = NULL; - //edges - q1_numedges = 0; - FreeMemory(q1_dedges); - q1_dedges = NULL; - //mark surfaces - q1_nummarksurfaces = 0; - FreeMemory(q1_dmarksurfaces); - q1_dmarksurfaces = NULL; - //surface edges - q1_numsurfedges = 0; - FreeMemory(q1_dsurfedges); - q1_dsurfedges = NULL; - // - Log_Print("freed "); - PrintMemorySize(q1_allocatedbspmem); - Log_Print(" of BSP memory\n"); - q1_allocatedbspmem = 0; -} //end of the function Q1_FreeMaxBSP -//#endif //ME - -/* -============= -Q1_SwapBSPFile - -Byte swaps all data in a bsp file. -============= -*/ -void Q1_SwapBSPFile (qboolean todisk) -{ - int i, j, c; - q1_dmodel_t *d; - q1_dmiptexlump_t *mtl; - - -// models - for (i=0 ; iheadnode[j] = LittleLong (d->headnode[j]); - - d->visleafs = LittleLong (d->visleafs); - d->firstface = LittleLong (d->firstface); - d->numfaces = LittleLong (d->numfaces); - - for (j=0 ; j<3 ; j++) - { - d->mins[j] = LittleFloat(d->mins[j]); - d->maxs[j] = LittleFloat(d->maxs[j]); - d->origin[j] = LittleFloat(d->origin[j]); - } - } - -// -// vertexes -// - for (i=0 ; inummiptex; - else - c = LittleLong(mtl->nummiptex); - mtl->nummiptex = LittleLong (mtl->nummiptex); - for (i=0 ; idataofs[i] = LittleLong(mtl->dataofs[i]); - } - -// -// marksurfaces -// - for (i=0 ; ilumps[lump].filelen; - ofs = q1_header->lumps[lump].fileofs; - - if (length % size) { - Error ("LoadBSPFile: odd lump size"); - } - // somehow things got out of range - if ((length/size) > maxsize) { - printf("WARNING: exceeded max size for lump %d size %d > maxsize %d\n", lump, (length/size), maxsize); - length = maxsize * size; - } - if ( ofs + length > q1_fileLength ) { - printf("WARNING: exceeded file length for lump %d\n", lump); - length = q1_fileLength - ofs; - if ( length <= 0 ) { - return 0; - } - } - - memcpy (dest, (byte *)q1_header + ofs, length); - - return length / size; -} - -/* -============= -Q1_LoadBSPFile -============= -*/ -void Q1_LoadBSPFile(char *filename, int offset, int length) -{ - int i; - -// -// load the file header -// - q1_fileLength = LoadFile(filename, (void **)&q1_header, offset, length); - -// swap the header - for (i=0 ; i< sizeof(q1_dheader_t)/4 ; i++) - ((int *)q1_header)[i] = LittleLong ( ((int *)q1_header)[i]); - - if (q1_header->version != Q1_BSPVERSION) - Error ("%s is version %i, not %i", filename, i, Q1_BSPVERSION); - - q1_nummodels = Q1_CopyLump (Q1_LUMP_MODELS, q1_dmodels, sizeof(q1_dmodel_t), Q1_MAX_MAP_MODELS ); - q1_numvertexes = Q1_CopyLump (Q1_LUMP_VERTEXES, q1_dvertexes, sizeof(q1_dvertex_t), Q1_MAX_MAP_VERTS ); - q1_numplanes = Q1_CopyLump (Q1_LUMP_PLANES, q1_dplanes, sizeof(q1_dplane_t), Q1_MAX_MAP_PLANES ); - q1_numleafs = Q1_CopyLump (Q1_LUMP_LEAFS, q1_dleafs, sizeof(q1_dleaf_t), Q1_MAX_MAP_LEAFS ); - q1_numnodes = Q1_CopyLump (Q1_LUMP_NODES, q1_dnodes, sizeof(q1_dnode_t), Q1_MAX_MAP_NODES ); - q1_numtexinfo = Q1_CopyLump (Q1_LUMP_TEXINFO, q1_texinfo, sizeof(q1_texinfo_t), Q1_MAX_MAP_TEXINFO ); - q1_numclipnodes = Q1_CopyLump (Q1_LUMP_CLIPNODES, q1_dclipnodes, sizeof(q1_dclipnode_t), Q1_MAX_MAP_CLIPNODES ); - q1_numfaces = Q1_CopyLump (Q1_LUMP_FACES, q1_dfaces, sizeof(q1_dface_t), Q1_MAX_MAP_FACES ); - q1_nummarksurfaces = Q1_CopyLump (Q1_LUMP_MARKSURFACES, q1_dmarksurfaces, sizeof(q1_dmarksurfaces[0]), Q1_MAX_MAP_MARKSURFACES ); - q1_numsurfedges = Q1_CopyLump (Q1_LUMP_SURFEDGES, q1_dsurfedges, sizeof(q1_dsurfedges[0]), Q1_MAX_MAP_SURFEDGES ); - q1_numedges = Q1_CopyLump (Q1_LUMP_EDGES, q1_dedges, sizeof(q1_dedge_t), Q1_MAX_MAP_EDGES ); - - q1_texdatasize = Q1_CopyLump (Q1_LUMP_TEXTURES, q1_dtexdata, 1, Q1_MAX_MAP_MIPTEX ); - q1_visdatasize = Q1_CopyLump (Q1_LUMP_VISIBILITY, q1_dvisdata, 1, Q1_MAX_MAP_VISIBILITY ); - q1_lightdatasize = Q1_CopyLump (Q1_LUMP_LIGHTING, q1_dlightdata, 1, Q1_MAX_MAP_LIGHTING ); - q1_entdatasize = Q1_CopyLump (Q1_LUMP_ENTITIES, q1_dentdata, 1, Q1_MAX_MAP_ENTSTRING ); - - FreeMemory(q1_header); // everything has been copied out - -// -// swap everything -// - Q1_SwapBSPFile (false); -} - -//============================================================================ - -FILE *q1_wadfile; -q1_dheader_t q1_outheader; - -void Q1_AddLump (int lumpnum, void *data, int len) -{ - q1_lump_t *lump; - - lump = &q1_header->lumps[lumpnum]; - - lump->fileofs = LittleLong(ftell(q1_wadfile)); - lump->filelen = LittleLong(len); - SafeWrite(q1_wadfile, data, (len+3)&~3); -} - -/* -============= -Q1_WriteBSPFile - -Swaps the bsp file in place, so it should not be referenced again -============= -*/ -void Q1_WriteBSPFile (char *filename) -{ - q1_header = &q1_outheader; - memset (q1_header, 0, sizeof(q1_dheader_t)); - - Q1_SwapBSPFile (true); - - q1_header->version = LittleLong (Q1_BSPVERSION); - - q1_wadfile = SafeOpenWrite (filename); - SafeWrite (q1_wadfile, q1_header, sizeof(q1_dheader_t)); // overwritten later - - Q1_AddLump (Q1_LUMP_PLANES, q1_dplanes, q1_numplanes*sizeof(q1_dplane_t)); - Q1_AddLump (Q1_LUMP_LEAFS, q1_dleafs, q1_numleafs*sizeof(q1_dleaf_t)); - Q1_AddLump (Q1_LUMP_VERTEXES, q1_dvertexes, q1_numvertexes*sizeof(q1_dvertex_t)); - Q1_AddLump (Q1_LUMP_NODES, q1_dnodes, q1_numnodes*sizeof(q1_dnode_t)); - Q1_AddLump (Q1_LUMP_TEXINFO, q1_texinfo, q1_numtexinfo*sizeof(q1_texinfo_t)); - Q1_AddLump (Q1_LUMP_FACES, q1_dfaces, q1_numfaces*sizeof(q1_dface_t)); - Q1_AddLump (Q1_LUMP_CLIPNODES, q1_dclipnodes, q1_numclipnodes*sizeof(q1_dclipnode_t)); - Q1_AddLump (Q1_LUMP_MARKSURFACES, q1_dmarksurfaces, q1_nummarksurfaces*sizeof(q1_dmarksurfaces[0])); - Q1_AddLump (Q1_LUMP_SURFEDGES, q1_dsurfedges, q1_numsurfedges*sizeof(q1_dsurfedges[0])); - Q1_AddLump (Q1_LUMP_EDGES, q1_dedges, q1_numedges*sizeof(q1_dedge_t)); - Q1_AddLump (Q1_LUMP_MODELS, q1_dmodels, q1_nummodels*sizeof(q1_dmodel_t)); - - Q1_AddLump (Q1_LUMP_LIGHTING, q1_dlightdata, q1_lightdatasize); - Q1_AddLump (Q1_LUMP_VISIBILITY, q1_dvisdata, q1_visdatasize); - Q1_AddLump (Q1_LUMP_ENTITIES, q1_dentdata, q1_entdatasize); - Q1_AddLump (Q1_LUMP_TEXTURES, q1_dtexdata, q1_texdatasize); - - fseek (q1_wadfile, 0, SEEK_SET); - SafeWrite (q1_wadfile, q1_header, sizeof(q1_dheader_t)); - fclose (q1_wadfile); -} - -//============================================================================ - -/* -============= -Q1_PrintBSPFileSizes - -Dumps info about current file -============= -*/ -void Q1_PrintBSPFileSizes (void) -{ - printf ("%5i planes %6i\n" - ,q1_numplanes, (int)(q1_numplanes*sizeof(q1_dplane_t))); - printf ("%5i vertexes %6i\n" - ,q1_numvertexes, (int)(q1_numvertexes*sizeof(q1_dvertex_t))); - printf ("%5i nodes %6i\n" - ,q1_numnodes, (int)(q1_numnodes*sizeof(q1_dnode_t))); - printf ("%5i texinfo %6i\n" - ,q1_numtexinfo, (int)(q1_numtexinfo*sizeof(q1_texinfo_t))); - printf ("%5i faces %6i\n" - ,q1_numfaces, (int)(q1_numfaces*sizeof(q1_dface_t))); - printf ("%5i clipnodes %6i\n" - ,q1_numclipnodes, (int)(q1_numclipnodes*sizeof(q1_dclipnode_t))); - printf ("%5i leafs %6i\n" - ,q1_numleafs, (int)(q1_numleafs*sizeof(q1_dleaf_t))); - printf ("%5i marksurfaces %6i\n" - ,q1_nummarksurfaces, (int)(q1_nummarksurfaces*sizeof(q1_dmarksurfaces[0]))); - printf ("%5i surfedges %6i\n" - ,q1_numsurfedges, (int)(q1_numsurfedges*sizeof(q1_dmarksurfaces[0]))); - printf ("%5i edges %6i\n" - ,q1_numedges, (int)(q1_numedges*sizeof(q1_dedge_t))); - if (!q1_texdatasize) - printf (" 0 textures 0\n"); - else - printf ("%5i textures %6i\n",((q1_dmiptexlump_t*)q1_dtexdata)->nummiptex, q1_texdatasize); - printf (" lightdata %6i\n", q1_lightdatasize); - printf (" visdata %6i\n", q1_visdatasize); - printf (" entdata %6i\n", q1_entdatasize); -} //end of the function Q1_PrintBSPFileSizes - - -/* -================ -Q1_ParseEntities - -Parses the dentdata string into entities -================ -*/ -void Q1_ParseEntities (void) -{ - script_t *script; - - num_entities = 0; - script = LoadScriptMemory(q1_dentdata, q1_entdatasize, "*Quake1 bsp file"); - SetScriptFlags(script, SCFL_NOSTRINGWHITESPACES | - SCFL_NOSTRINGESCAPECHARS); - - while(ParseEntity(script)) - { - } //end while - - FreeScript(script); -} //end of the function Q1_ParseEntities - - -/* -================ -Q1_UnparseEntities - -Generates the dentdata string from all the entities -================ -*/ -void Q1_UnparseEntities (void) -{ - char *buf, *end; - epair_t *ep; - char line[2048]; - int i; - - buf = q1_dentdata; - end = buf; - *end = 0; - - for (i=0 ; inext) - { - sprintf (line, "\"%s\" \"%s\"\n", ep->key, ep->value); - strcat (end, line); - end += strlen(line); - } - strcat (end,"}\n"); - end += 2; - - if (end > buf + Q1_MAX_MAP_ENTSTRING) - Error ("Entity text too long"); - } - q1_entdatasize = end - buf + 1; -} //end of the function Q1_UnparseEntities +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "l_cmd.h" +#include "l_math.h" +#include "l_mem.h" +#include "l_log.h" +#include "../botlib/l_script.h" +#include "l_bsp_q1.h" +#include "l_bsp_ent.h" + +//============================================================================= + +int q1_nummodels; +q1_dmodel_t *q1_dmodels;//[MAX_MAP_MODELS]; + +int q1_visdatasize; +byte *q1_dvisdata;//[MAX_MAP_VISIBILITY]; + +int q1_lightdatasize; +byte *q1_dlightdata;//[MAX_MAP_LIGHTING]; + +int q1_texdatasize; +byte *q1_dtexdata;//[MAX_MAP_MIPTEX]; // (dmiptexlump_t) + +int q1_entdatasize; +char *q1_dentdata;//[MAX_MAP_ENTSTRING]; + +int q1_numleafs; +q1_dleaf_t *q1_dleafs;//[MAX_MAP_LEAFS]; + +int q1_numplanes; +q1_dplane_t *q1_dplanes;//[MAX_MAP_PLANES]; + +int q1_numvertexes; +q1_dvertex_t *q1_dvertexes;//[MAX_MAP_VERTS]; + +int q1_numnodes; +q1_dnode_t *q1_dnodes;//[MAX_MAP_NODES]; + +int q1_numtexinfo; +q1_texinfo_t *q1_texinfo;//[MAX_MAP_TEXINFO]; + +int q1_numfaces; +q1_dface_t *q1_dfaces;//[MAX_MAP_FACES]; + +int q1_numclipnodes; +q1_dclipnode_t *q1_dclipnodes;//[MAX_MAP_CLIPNODES]; + +int q1_numedges; +q1_dedge_t *q1_dedges;//[MAX_MAP_EDGES]; + +int q1_nummarksurfaces; +unsigned short *q1_dmarksurfaces;//[MAX_MAP_MARKSURFACES]; + +int q1_numsurfedges; +int *q1_dsurfedges;//[MAX_MAP_SURFEDGES]; + +//============================================================================= + +int q1_bspallocated = false; +int q1_allocatedbspmem = 0; + +void Q1_AllocMaxBSP(void) +{ + //models + q1_nummodels = 0; + q1_dmodels = (q1_dmodel_t *) GetMemory(Q1_MAX_MAP_MODELS * sizeof(q1_dmodel_t)); + q1_allocatedbspmem = Q1_MAX_MAP_MODELS * sizeof(q1_dmodel_t); + //visibility + q1_visdatasize = 0; + q1_dvisdata = (byte *) GetMemory(Q1_MAX_MAP_VISIBILITY * sizeof(byte)); + q1_allocatedbspmem += Q1_MAX_MAP_VISIBILITY * sizeof(byte); + //light data + q1_lightdatasize = 0; + q1_dlightdata = (byte *) GetMemory(Q1_MAX_MAP_LIGHTING * sizeof(byte)); + q1_allocatedbspmem += Q1_MAX_MAP_LIGHTING * sizeof(byte); + //texture data + q1_texdatasize = 0; + q1_dtexdata = (byte *) GetMemory(Q1_MAX_MAP_MIPTEX * sizeof(byte)); // (dmiptexlump_t) + q1_allocatedbspmem += Q1_MAX_MAP_MIPTEX * sizeof(byte); + //entities + q1_entdatasize = 0; + q1_dentdata = (char *) GetMemory(Q1_MAX_MAP_ENTSTRING * sizeof(char)); + q1_allocatedbspmem += Q1_MAX_MAP_ENTSTRING * sizeof(char); + //leaves + q1_numleafs = 0; + q1_dleafs = (q1_dleaf_t *) GetMemory(Q1_MAX_MAP_LEAFS * sizeof(q1_dleaf_t)); + q1_allocatedbspmem += Q1_MAX_MAP_LEAFS * sizeof(q1_dleaf_t); + //planes + q1_numplanes = 0; + q1_dplanes = (q1_dplane_t *) GetMemory(Q1_MAX_MAP_PLANES * sizeof(q1_dplane_t)); + q1_allocatedbspmem += Q1_MAX_MAP_PLANES * sizeof(q1_dplane_t); + //vertexes + q1_numvertexes = 0; + q1_dvertexes = (q1_dvertex_t *) GetMemory(Q1_MAX_MAP_VERTS * sizeof(q1_dvertex_t)); + q1_allocatedbspmem += Q1_MAX_MAP_VERTS * sizeof(q1_dvertex_t); + //nodes + q1_numnodes = 0; + q1_dnodes = (q1_dnode_t *) GetMemory(Q1_MAX_MAP_NODES * sizeof(q1_dnode_t)); + q1_allocatedbspmem += Q1_MAX_MAP_NODES * sizeof(q1_dnode_t); + //texture info + q1_numtexinfo = 0; + q1_texinfo = (q1_texinfo_t *) GetMemory(Q1_MAX_MAP_TEXINFO * sizeof(q1_texinfo_t)); + q1_allocatedbspmem += Q1_MAX_MAP_TEXINFO * sizeof(q1_texinfo_t); + //faces + q1_numfaces = 0; + q1_dfaces = (q1_dface_t *) GetMemory(Q1_MAX_MAP_FACES * sizeof(q1_dface_t)); + q1_allocatedbspmem += Q1_MAX_MAP_FACES * sizeof(q1_dface_t); + //clip nodes + q1_numclipnodes = 0; + q1_dclipnodes = (q1_dclipnode_t *) GetMemory(Q1_MAX_MAP_CLIPNODES * sizeof(q1_dclipnode_t)); + q1_allocatedbspmem += Q1_MAX_MAP_CLIPNODES * sizeof(q1_dclipnode_t); + //edges + q1_numedges = 0; + q1_dedges = (q1_dedge_t *) GetMemory(Q1_MAX_MAP_EDGES * sizeof(q1_dedge_t)); + q1_allocatedbspmem += Q1_MAX_MAP_EDGES, sizeof(q1_dedge_t); + //mark surfaces + q1_nummarksurfaces = 0; + q1_dmarksurfaces = (unsigned short *) GetMemory(Q1_MAX_MAP_MARKSURFACES * sizeof(unsigned short)); + q1_allocatedbspmem += Q1_MAX_MAP_MARKSURFACES * sizeof(unsigned short); + //surface edges + q1_numsurfedges = 0; + q1_dsurfedges = (int *) GetMemory(Q1_MAX_MAP_SURFEDGES * sizeof(int)); + q1_allocatedbspmem += Q1_MAX_MAP_SURFEDGES * sizeof(int); + //print allocated memory + Log_Print("allocated "); + PrintMemorySize(q1_allocatedbspmem); + Log_Print(" of BSP memory\n"); +} //end of the function Q1_AllocMaxBSP + +void Q1_FreeMaxBSP(void) +{ + //models + q1_nummodels = 0; + FreeMemory(q1_dmodels); + q1_dmodels = NULL; + //visibility + q1_visdatasize = 0; + FreeMemory(q1_dvisdata); + q1_dvisdata = NULL; + //light data + q1_lightdatasize = 0; + FreeMemory(q1_dlightdata); + q1_dlightdata = NULL; + //texture data + q1_texdatasize = 0; + FreeMemory(q1_dtexdata); + q1_dtexdata = NULL; + //entities + q1_entdatasize = 0; + FreeMemory(q1_dentdata); + q1_dentdata = NULL; + //leaves + q1_numleafs = 0; + FreeMemory(q1_dleafs); + q1_dleafs = NULL; + //planes + q1_numplanes = 0; + FreeMemory(q1_dplanes); + q1_dplanes = NULL; + //vertexes + q1_numvertexes = 0; + FreeMemory(q1_dvertexes); + q1_dvertexes = NULL; + //nodes + q1_numnodes = 0; + FreeMemory(q1_dnodes); + q1_dnodes = NULL; + //texture info + q1_numtexinfo = 0; + FreeMemory(q1_texinfo); + q1_texinfo = NULL; + //faces + q1_numfaces = 0; + FreeMemory(q1_dfaces); + q1_dfaces = NULL; + //clip nodes + q1_numclipnodes = 0; + FreeMemory(q1_dclipnodes); + q1_dclipnodes = NULL; + //edges + q1_numedges = 0; + FreeMemory(q1_dedges); + q1_dedges = NULL; + //mark surfaces + q1_nummarksurfaces = 0; + FreeMemory(q1_dmarksurfaces); + q1_dmarksurfaces = NULL; + //surface edges + q1_numsurfedges = 0; + FreeMemory(q1_dsurfedges); + q1_dsurfedges = NULL; + // + Log_Print("freed "); + PrintMemorySize(q1_allocatedbspmem); + Log_Print(" of BSP memory\n"); + q1_allocatedbspmem = 0; +} //end of the function Q1_FreeMaxBSP +//#endif //ME + +/* +============= +Q1_SwapBSPFile + +Byte swaps all data in a bsp file. +============= +*/ +void Q1_SwapBSPFile (qboolean todisk) +{ + int i, j, c; + q1_dmodel_t *d; + q1_dmiptexlump_t *mtl; + + +// models + for (i=0 ; iheadnode[j] = LittleLong (d->headnode[j]); + + d->visleafs = LittleLong (d->visleafs); + d->firstface = LittleLong (d->firstface); + d->numfaces = LittleLong (d->numfaces); + + for (j=0 ; j<3 ; j++) + { + d->mins[j] = LittleFloat(d->mins[j]); + d->maxs[j] = LittleFloat(d->maxs[j]); + d->origin[j] = LittleFloat(d->origin[j]); + } + } + +// +// vertexes +// + for (i=0 ; inummiptex; + else + c = LittleLong(mtl->nummiptex); + mtl->nummiptex = LittleLong (mtl->nummiptex); + for (i=0 ; idataofs[i] = LittleLong(mtl->dataofs[i]); + } + +// +// marksurfaces +// + for (i=0 ; ilumps[lump].filelen; + ofs = q1_header->lumps[lump].fileofs; + + if (length % size) { + Error ("LoadBSPFile: odd lump size"); + } + // somehow things got out of range + if ((length/size) > maxsize) { + printf("WARNING: exceeded max size for lump %d size %d > maxsize %d\n", lump, (length/size), maxsize); + length = maxsize * size; + } + if ( ofs + length > q1_fileLength ) { + printf("WARNING: exceeded file length for lump %d\n", lump); + length = q1_fileLength - ofs; + if ( length <= 0 ) { + return 0; + } + } + + memcpy (dest, (byte *)q1_header + ofs, length); + + return length / size; +} + +/* +============= +Q1_LoadBSPFile +============= +*/ +void Q1_LoadBSPFile(char *filename, int offset, int length) +{ + int i; + +// +// load the file header +// + q1_fileLength = LoadFile(filename, (void **)&q1_header, offset, length); + +// swap the header + for (i=0 ; i< sizeof(q1_dheader_t)/4 ; i++) + ((int *)q1_header)[i] = LittleLong ( ((int *)q1_header)[i]); + + if (q1_header->version != Q1_BSPVERSION) + Error ("%s is version %i, not %i", filename, i, Q1_BSPVERSION); + + q1_nummodels = Q1_CopyLump (Q1_LUMP_MODELS, q1_dmodels, sizeof(q1_dmodel_t), Q1_MAX_MAP_MODELS ); + q1_numvertexes = Q1_CopyLump (Q1_LUMP_VERTEXES, q1_dvertexes, sizeof(q1_dvertex_t), Q1_MAX_MAP_VERTS ); + q1_numplanes = Q1_CopyLump (Q1_LUMP_PLANES, q1_dplanes, sizeof(q1_dplane_t), Q1_MAX_MAP_PLANES ); + q1_numleafs = Q1_CopyLump (Q1_LUMP_LEAFS, q1_dleafs, sizeof(q1_dleaf_t), Q1_MAX_MAP_LEAFS ); + q1_numnodes = Q1_CopyLump (Q1_LUMP_NODES, q1_dnodes, sizeof(q1_dnode_t), Q1_MAX_MAP_NODES ); + q1_numtexinfo = Q1_CopyLump (Q1_LUMP_TEXINFO, q1_texinfo, sizeof(q1_texinfo_t), Q1_MAX_MAP_TEXINFO ); + q1_numclipnodes = Q1_CopyLump (Q1_LUMP_CLIPNODES, q1_dclipnodes, sizeof(q1_dclipnode_t), Q1_MAX_MAP_CLIPNODES ); + q1_numfaces = Q1_CopyLump (Q1_LUMP_FACES, q1_dfaces, sizeof(q1_dface_t), Q1_MAX_MAP_FACES ); + q1_nummarksurfaces = Q1_CopyLump (Q1_LUMP_MARKSURFACES, q1_dmarksurfaces, sizeof(q1_dmarksurfaces[0]), Q1_MAX_MAP_MARKSURFACES ); + q1_numsurfedges = Q1_CopyLump (Q1_LUMP_SURFEDGES, q1_dsurfedges, sizeof(q1_dsurfedges[0]), Q1_MAX_MAP_SURFEDGES ); + q1_numedges = Q1_CopyLump (Q1_LUMP_EDGES, q1_dedges, sizeof(q1_dedge_t), Q1_MAX_MAP_EDGES ); + + q1_texdatasize = Q1_CopyLump (Q1_LUMP_TEXTURES, q1_dtexdata, 1, Q1_MAX_MAP_MIPTEX ); + q1_visdatasize = Q1_CopyLump (Q1_LUMP_VISIBILITY, q1_dvisdata, 1, Q1_MAX_MAP_VISIBILITY ); + q1_lightdatasize = Q1_CopyLump (Q1_LUMP_LIGHTING, q1_dlightdata, 1, Q1_MAX_MAP_LIGHTING ); + q1_entdatasize = Q1_CopyLump (Q1_LUMP_ENTITIES, q1_dentdata, 1, Q1_MAX_MAP_ENTSTRING ); + + FreeMemory(q1_header); // everything has been copied out + +// +// swap everything +// + Q1_SwapBSPFile (false); +} + +//============================================================================ + +FILE *q1_wadfile; +q1_dheader_t q1_outheader; + +void Q1_AddLump (int lumpnum, void *data, int len) +{ + q1_lump_t *lump; + + lump = &q1_header->lumps[lumpnum]; + + lump->fileofs = LittleLong(ftell(q1_wadfile)); + lump->filelen = LittleLong(len); + SafeWrite(q1_wadfile, data, (len+3)&~3); +} + +/* +============= +Q1_WriteBSPFile + +Swaps the bsp file in place, so it should not be referenced again +============= +*/ +void Q1_WriteBSPFile (char *filename) +{ + q1_header = &q1_outheader; + memset (q1_header, 0, sizeof(q1_dheader_t)); + + Q1_SwapBSPFile (true); + + q1_header->version = LittleLong (Q1_BSPVERSION); + + q1_wadfile = SafeOpenWrite (filename); + SafeWrite (q1_wadfile, q1_header, sizeof(q1_dheader_t)); // overwritten later + + Q1_AddLump (Q1_LUMP_PLANES, q1_dplanes, q1_numplanes*sizeof(q1_dplane_t)); + Q1_AddLump (Q1_LUMP_LEAFS, q1_dleafs, q1_numleafs*sizeof(q1_dleaf_t)); + Q1_AddLump (Q1_LUMP_VERTEXES, q1_dvertexes, q1_numvertexes*sizeof(q1_dvertex_t)); + Q1_AddLump (Q1_LUMP_NODES, q1_dnodes, q1_numnodes*sizeof(q1_dnode_t)); + Q1_AddLump (Q1_LUMP_TEXINFO, q1_texinfo, q1_numtexinfo*sizeof(q1_texinfo_t)); + Q1_AddLump (Q1_LUMP_FACES, q1_dfaces, q1_numfaces*sizeof(q1_dface_t)); + Q1_AddLump (Q1_LUMP_CLIPNODES, q1_dclipnodes, q1_numclipnodes*sizeof(q1_dclipnode_t)); + Q1_AddLump (Q1_LUMP_MARKSURFACES, q1_dmarksurfaces, q1_nummarksurfaces*sizeof(q1_dmarksurfaces[0])); + Q1_AddLump (Q1_LUMP_SURFEDGES, q1_dsurfedges, q1_numsurfedges*sizeof(q1_dsurfedges[0])); + Q1_AddLump (Q1_LUMP_EDGES, q1_dedges, q1_numedges*sizeof(q1_dedge_t)); + Q1_AddLump (Q1_LUMP_MODELS, q1_dmodels, q1_nummodels*sizeof(q1_dmodel_t)); + + Q1_AddLump (Q1_LUMP_LIGHTING, q1_dlightdata, q1_lightdatasize); + Q1_AddLump (Q1_LUMP_VISIBILITY, q1_dvisdata, q1_visdatasize); + Q1_AddLump (Q1_LUMP_ENTITIES, q1_dentdata, q1_entdatasize); + Q1_AddLump (Q1_LUMP_TEXTURES, q1_dtexdata, q1_texdatasize); + + fseek (q1_wadfile, 0, SEEK_SET); + SafeWrite (q1_wadfile, q1_header, sizeof(q1_dheader_t)); + fclose (q1_wadfile); +} + +//============================================================================ + +/* +============= +Q1_PrintBSPFileSizes + +Dumps info about current file +============= +*/ +void Q1_PrintBSPFileSizes (void) +{ + printf ("%5i planes %6i\n" + ,q1_numplanes, (int)(q1_numplanes*sizeof(q1_dplane_t))); + printf ("%5i vertexes %6i\n" + ,q1_numvertexes, (int)(q1_numvertexes*sizeof(q1_dvertex_t))); + printf ("%5i nodes %6i\n" + ,q1_numnodes, (int)(q1_numnodes*sizeof(q1_dnode_t))); + printf ("%5i texinfo %6i\n" + ,q1_numtexinfo, (int)(q1_numtexinfo*sizeof(q1_texinfo_t))); + printf ("%5i faces %6i\n" + ,q1_numfaces, (int)(q1_numfaces*sizeof(q1_dface_t))); + printf ("%5i clipnodes %6i\n" + ,q1_numclipnodes, (int)(q1_numclipnodes*sizeof(q1_dclipnode_t))); + printf ("%5i leafs %6i\n" + ,q1_numleafs, (int)(q1_numleafs*sizeof(q1_dleaf_t))); + printf ("%5i marksurfaces %6i\n" + ,q1_nummarksurfaces, (int)(q1_nummarksurfaces*sizeof(q1_dmarksurfaces[0]))); + printf ("%5i surfedges %6i\n" + ,q1_numsurfedges, (int)(q1_numsurfedges*sizeof(q1_dmarksurfaces[0]))); + printf ("%5i edges %6i\n" + ,q1_numedges, (int)(q1_numedges*sizeof(q1_dedge_t))); + if (!q1_texdatasize) + printf (" 0 textures 0\n"); + else + printf ("%5i textures %6i\n",((q1_dmiptexlump_t*)q1_dtexdata)->nummiptex, q1_texdatasize); + printf (" lightdata %6i\n", q1_lightdatasize); + printf (" visdata %6i\n", q1_visdatasize); + printf (" entdata %6i\n", q1_entdatasize); +} //end of the function Q1_PrintBSPFileSizes + + +/* +================ +Q1_ParseEntities + +Parses the dentdata string into entities +================ +*/ +void Q1_ParseEntities (void) +{ + script_t *script; + + num_entities = 0; + script = LoadScriptMemory(q1_dentdata, q1_entdatasize, "*Quake1 bsp file"); + SetScriptFlags(script, SCFL_NOSTRINGWHITESPACES | + SCFL_NOSTRINGESCAPECHARS); + + while(ParseEntity(script)) + { + } //end while + + FreeScript(script); +} //end of the function Q1_ParseEntities + + +/* +================ +Q1_UnparseEntities + +Generates the dentdata string from all the entities +================ +*/ +void Q1_UnparseEntities (void) +{ + char *buf, *end; + epair_t *ep; + char line[2048]; + int i; + + buf = q1_dentdata; + end = buf; + *end = 0; + + for (i=0 ; inext) + { + sprintf (line, "\"%s\" \"%s\"\n", ep->key, ep->value); + strcat (end, line); + end += strlen(line); + } + strcat (end,"}\n"); + end += 2; + + if (end > buf + Q1_MAX_MAP_ENTSTRING) + Error ("Entity text too long"); + } + q1_entdatasize = end - buf + 1; +} //end of the function Q1_UnparseEntities diff --git a/code/bspc/l_bsp_q1.h b/code/bspc/l_bsp_q1.h index 1c2cd92..7cf459c 100755 --- a/code/bspc/l_bsp_q1.h +++ b/code/bspc/l_bsp_q1.h @@ -1,275 +1,275 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - - -// upper design bounds - -#define Q1_MAX_MAP_HULLS 4 - -#define Q1_MAX_MAP_MODELS 256 -#define Q1_MAX_MAP_BRUSHES 4096 -#define Q1_MAX_MAP_ENTITIES 1024 -#define Q1_MAX_MAP_ENTSTRING 65536 - -#define Q1_MAX_MAP_PLANES 8192 -#define Q1_MAX_MAP_NODES 32767 // because negative shorts are contents -#define Q1_MAX_MAP_CLIPNODES 32767 // -#define Q1_MAX_MAP_LEAFS 32767 // -#define Q1_MAX_MAP_VERTS 65535 -#define Q1_MAX_MAP_FACES 65535 -#define Q1_MAX_MAP_MARKSURFACES 65535 -#define Q1_MAX_MAP_TEXINFO 4096 -#define Q1_MAX_MAP_EDGES 256000 -#define Q1_MAX_MAP_SURFEDGES 512000 -#define Q1_MAX_MAP_MIPTEX 0x200000 -#define Q1_MAX_MAP_LIGHTING 0x100000 -#define Q1_MAX_MAP_VISIBILITY 0x100000 - -// key / value pair sizes - -#define MAX_KEY 32 -#define MAX_VALUE 1024 - -//============================================================================= - - -#define Q1_BSPVERSION 29 - -typedef struct -{ - int fileofs, filelen; -} q1_lump_t; - -#define Q1_LUMP_ENTITIES 0 -#define Q1_LUMP_PLANES 1 -#define Q1_LUMP_TEXTURES 2 -#define Q1_LUMP_VERTEXES 3 -#define Q1_LUMP_VISIBILITY 4 -#define Q1_LUMP_NODES 5 -#define Q1_LUMP_TEXINFO 6 -#define Q1_LUMP_FACES 7 -#define Q1_LUMP_LIGHTING 8 -#define Q1_LUMP_CLIPNODES 9 -#define Q1_LUMP_LEAFS 10 -#define Q1_LUMP_MARKSURFACES 11 -#define Q1_LUMP_EDGES 12 -#define Q1_LUMP_SURFEDGES 13 -#define Q1_LUMP_MODELS 14 - -#define Q1_HEADER_LUMPS 15 - -typedef struct -{ - float mins[3], maxs[3]; - float origin[3]; - int headnode[Q1_MAX_MAP_HULLS]; - int visleafs; // not including the solid leaf 0 - int firstface, numfaces; -} q1_dmodel_t; - -typedef struct -{ - int version; - q1_lump_t lumps[Q1_HEADER_LUMPS]; -} q1_dheader_t; - -typedef struct -{ - int nummiptex; - int dataofs[4]; // [nummiptex] -} q1_dmiptexlump_t; - -#define MIPLEVELS 4 -typedef struct q1_miptex_s -{ - char name[16]; - unsigned width, height; - unsigned offsets[MIPLEVELS]; // four mip maps stored -} q1_miptex_t; - - -typedef struct -{ - float point[3]; -} q1_dvertex_t; - - -// 0-2 are axial planes -#define PLANE_X 0 -#define PLANE_Y 1 -#define PLANE_Z 2 - -// 3-5 are non-axial planes snapped to the nearest -#define PLANE_ANYX 3 -#define PLANE_ANYY 4 -#define PLANE_ANYZ 5 - -typedef struct -{ - float normal[3]; - float dist; - int type; // PLANE_X - PLANE_ANYZ ?remove? trivial to regenerate -} q1_dplane_t; - - - -#define Q1_CONTENTS_EMPTY -1 -#define Q1_CONTENTS_SOLID -2 -#define Q1_CONTENTS_WATER -3 -#define Q1_CONTENTS_SLIME -4 -#define Q1_CONTENTS_LAVA -5 -#define Q1_CONTENTS_SKY -6 - -// !!! if this is changed, it must be changed in asm_i386.h too !!! -typedef struct -{ - int planenum; - short children[2]; // negative numbers are -(leafs+1), not nodes - short mins[3]; // for sphere culling - short maxs[3]; - unsigned short firstface; - unsigned short numfaces; // counting both sides -} q1_dnode_t; - -typedef struct -{ - int planenum; - short children[2]; // negative numbers are contents -} q1_dclipnode_t; - - -typedef struct q1_texinfo_s -{ - float vecs[2][4]; // [s/t][xyz offset] - int miptex; - int flags; -} q1_texinfo_t; -#define TEX_SPECIAL 1 // sky or slime, no lightmap or 256 subdivision - -// note that edge 0 is never used, because negative edge nums are used for -// counterclockwise use of the edge in a face -typedef struct -{ - unsigned short v[2]; // vertex numbers -} q1_dedge_t; - -#define MAXLIGHTMAPS 4 -typedef struct -{ - short planenum; - short side; - - int firstedge; // we must support > 64k edges - short numedges; - short texinfo; - -// lighting info - byte styles[MAXLIGHTMAPS]; - int lightofs; // start of [numstyles*surfsize] samples -} q1_dface_t; - - - -#define AMBIENT_WATER 0 -#define AMBIENT_SKY 1 -#define AMBIENT_SLIME 2 -#define AMBIENT_LAVA 3 - -#define NUM_AMBIENTS 4 // automatic ambient sounds - -// leaf 0 is the generic Q1_CONTENTS_SOLID leaf, used for all solid areas -// all other leafs need visibility info -typedef struct -{ - int contents; - int visofs; // -1 = no visibility info - - short mins[3]; // for frustum culling - short maxs[3]; - - unsigned short firstmarksurface; - unsigned short nummarksurfaces; - - byte ambient_level[NUM_AMBIENTS]; -} q1_dleaf_t; - -//============================================================================ - -#ifndef QUAKE_GAME - -// the utilities get to be lazy and just use large static arrays - -extern int q1_nummodels; -extern q1_dmodel_t *q1_dmodels;//[MAX_MAP_MODELS]; - -extern int q1_visdatasize; -extern byte *q1_dvisdata;//[MAX_MAP_VISIBILITY]; - -extern int q1_lightdatasize; -extern byte *q1_dlightdata;//[MAX_MAP_LIGHTING]; - -extern int q1_texdatasize; -extern byte *q1_dtexdata;//[MAX_MAP_MIPTEX]; // (dmiptexlump_t) - -extern int q1_entdatasize; -extern char *q1_dentdata;//[MAX_MAP_ENTSTRING]; - -extern int q1_numleafs; -extern q1_dleaf_t *q1_dleafs;//[MAX_MAP_LEAFS]; - -extern int q1_numplanes; -extern q1_dplane_t *q1_dplanes;//[MAX_MAP_PLANES]; - -extern int q1_numvertexes; -extern q1_dvertex_t *q1_dvertexes;//[MAX_MAP_VERTS]; - -extern int q1_numnodes; -extern q1_dnode_t *q1_dnodes;//[MAX_MAP_NODES]; - -extern int q1_numtexinfo; -extern q1_texinfo_t *q1_texinfo;//[MAX_MAP_TEXINFO]; - -extern int q1_numfaces; -extern q1_dface_t *q1_dfaces;//[MAX_MAP_FACES]; - -extern int q1_numclipnodes; -extern q1_dclipnode_t *q1_dclipnodes;//[MAX_MAP_CLIPNODES]; - -extern int q1_numedges; -extern q1_dedge_t *q1_dedges;//[MAX_MAP_EDGES]; - -extern int q1_nummarksurfaces; -extern unsigned short *q1_dmarksurfaces;//[MAX_MAP_MARKSURFACES]; - -extern int q1_numsurfedges; -extern int *q1_dsurfedges;//[MAX_MAP_SURFEDGES]; - - -void Q1_AllocMaxBSP(void); -void Q1_FreeMaxBSP(void); -void Q1_LoadBSPFile(char *filename, int offset, int length); -void Q1_WriteBSPFile(char *filename); -void Q1_PrintBSPFileSizes(void); -void Q1_ParseEntities(void); -void Q1_UnparseEntities(void); - -#endif +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + + +// upper design bounds + +#define Q1_MAX_MAP_HULLS 4 + +#define Q1_MAX_MAP_MODELS 256 +#define Q1_MAX_MAP_BRUSHES 4096 +#define Q1_MAX_MAP_ENTITIES 1024 +#define Q1_MAX_MAP_ENTSTRING 65536 + +#define Q1_MAX_MAP_PLANES 8192 +#define Q1_MAX_MAP_NODES 32767 // because negative shorts are contents +#define Q1_MAX_MAP_CLIPNODES 32767 // +#define Q1_MAX_MAP_LEAFS 32767 // +#define Q1_MAX_MAP_VERTS 65535 +#define Q1_MAX_MAP_FACES 65535 +#define Q1_MAX_MAP_MARKSURFACES 65535 +#define Q1_MAX_MAP_TEXINFO 4096 +#define Q1_MAX_MAP_EDGES 256000 +#define Q1_MAX_MAP_SURFEDGES 512000 +#define Q1_MAX_MAP_MIPTEX 0x200000 +#define Q1_MAX_MAP_LIGHTING 0x100000 +#define Q1_MAX_MAP_VISIBILITY 0x100000 + +// key / value pair sizes + +#define MAX_KEY 32 +#define MAX_VALUE 1024 + +//============================================================================= + + +#define Q1_BSPVERSION 29 + +typedef struct +{ + int fileofs, filelen; +} q1_lump_t; + +#define Q1_LUMP_ENTITIES 0 +#define Q1_LUMP_PLANES 1 +#define Q1_LUMP_TEXTURES 2 +#define Q1_LUMP_VERTEXES 3 +#define Q1_LUMP_VISIBILITY 4 +#define Q1_LUMP_NODES 5 +#define Q1_LUMP_TEXINFO 6 +#define Q1_LUMP_FACES 7 +#define Q1_LUMP_LIGHTING 8 +#define Q1_LUMP_CLIPNODES 9 +#define Q1_LUMP_LEAFS 10 +#define Q1_LUMP_MARKSURFACES 11 +#define Q1_LUMP_EDGES 12 +#define Q1_LUMP_SURFEDGES 13 +#define Q1_LUMP_MODELS 14 + +#define Q1_HEADER_LUMPS 15 + +typedef struct +{ + float mins[3], maxs[3]; + float origin[3]; + int headnode[Q1_MAX_MAP_HULLS]; + int visleafs; // not including the solid leaf 0 + int firstface, numfaces; +} q1_dmodel_t; + +typedef struct +{ + int version; + q1_lump_t lumps[Q1_HEADER_LUMPS]; +} q1_dheader_t; + +typedef struct +{ + int nummiptex; + int dataofs[4]; // [nummiptex] +} q1_dmiptexlump_t; + +#define MIPLEVELS 4 +typedef struct q1_miptex_s +{ + char name[16]; + unsigned width, height; + unsigned offsets[MIPLEVELS]; // four mip maps stored +} q1_miptex_t; + + +typedef struct +{ + float point[3]; +} q1_dvertex_t; + + +// 0-2 are axial planes +#define PLANE_X 0 +#define PLANE_Y 1 +#define PLANE_Z 2 + +// 3-5 are non-axial planes snapped to the nearest +#define PLANE_ANYX 3 +#define PLANE_ANYY 4 +#define PLANE_ANYZ 5 + +typedef struct +{ + float normal[3]; + float dist; + int type; // PLANE_X - PLANE_ANYZ ?remove? trivial to regenerate +} q1_dplane_t; + + + +#define Q1_CONTENTS_EMPTY -1 +#define Q1_CONTENTS_SOLID -2 +#define Q1_CONTENTS_WATER -3 +#define Q1_CONTENTS_SLIME -4 +#define Q1_CONTENTS_LAVA -5 +#define Q1_CONTENTS_SKY -6 + +// !!! if this is changed, it must be changed in asm_i386.h too !!! +typedef struct +{ + int planenum; + short children[2]; // negative numbers are -(leafs+1), not nodes + short mins[3]; // for sphere culling + short maxs[3]; + unsigned short firstface; + unsigned short numfaces; // counting both sides +} q1_dnode_t; + +typedef struct +{ + int planenum; + short children[2]; // negative numbers are contents +} q1_dclipnode_t; + + +typedef struct q1_texinfo_s +{ + float vecs[2][4]; // [s/t][xyz offset] + int miptex; + int flags; +} q1_texinfo_t; +#define TEX_SPECIAL 1 // sky or slime, no lightmap or 256 subdivision + +// note that edge 0 is never used, because negative edge nums are used for +// counterclockwise use of the edge in a face +typedef struct +{ + unsigned short v[2]; // vertex numbers +} q1_dedge_t; + +#define MAXLIGHTMAPS 4 +typedef struct +{ + short planenum; + short side; + + int firstedge; // we must support > 64k edges + short numedges; + short texinfo; + +// lighting info + byte styles[MAXLIGHTMAPS]; + int lightofs; // start of [numstyles*surfsize] samples +} q1_dface_t; + + + +#define AMBIENT_WATER 0 +#define AMBIENT_SKY 1 +#define AMBIENT_SLIME 2 +#define AMBIENT_LAVA 3 + +#define NUM_AMBIENTS 4 // automatic ambient sounds + +// leaf 0 is the generic Q1_CONTENTS_SOLID leaf, used for all solid areas +// all other leafs need visibility info +typedef struct +{ + int contents; + int visofs; // -1 = no visibility info + + short mins[3]; // for frustum culling + short maxs[3]; + + unsigned short firstmarksurface; + unsigned short nummarksurfaces; + + byte ambient_level[NUM_AMBIENTS]; +} q1_dleaf_t; + +//============================================================================ + +#ifndef QUAKE_GAME + +// the utilities get to be lazy and just use large static arrays + +extern int q1_nummodels; +extern q1_dmodel_t *q1_dmodels;//[MAX_MAP_MODELS]; + +extern int q1_visdatasize; +extern byte *q1_dvisdata;//[MAX_MAP_VISIBILITY]; + +extern int q1_lightdatasize; +extern byte *q1_dlightdata;//[MAX_MAP_LIGHTING]; + +extern int q1_texdatasize; +extern byte *q1_dtexdata;//[MAX_MAP_MIPTEX]; // (dmiptexlump_t) + +extern int q1_entdatasize; +extern char *q1_dentdata;//[MAX_MAP_ENTSTRING]; + +extern int q1_numleafs; +extern q1_dleaf_t *q1_dleafs;//[MAX_MAP_LEAFS]; + +extern int q1_numplanes; +extern q1_dplane_t *q1_dplanes;//[MAX_MAP_PLANES]; + +extern int q1_numvertexes; +extern q1_dvertex_t *q1_dvertexes;//[MAX_MAP_VERTS]; + +extern int q1_numnodes; +extern q1_dnode_t *q1_dnodes;//[MAX_MAP_NODES]; + +extern int q1_numtexinfo; +extern q1_texinfo_t *q1_texinfo;//[MAX_MAP_TEXINFO]; + +extern int q1_numfaces; +extern q1_dface_t *q1_dfaces;//[MAX_MAP_FACES]; + +extern int q1_numclipnodes; +extern q1_dclipnode_t *q1_dclipnodes;//[MAX_MAP_CLIPNODES]; + +extern int q1_numedges; +extern q1_dedge_t *q1_dedges;//[MAX_MAP_EDGES]; + +extern int q1_nummarksurfaces; +extern unsigned short *q1_dmarksurfaces;//[MAX_MAP_MARKSURFACES]; + +extern int q1_numsurfedges; +extern int *q1_dsurfedges;//[MAX_MAP_SURFEDGES]; + + +void Q1_AllocMaxBSP(void); +void Q1_FreeMaxBSP(void); +void Q1_LoadBSPFile(char *filename, int offset, int length); +void Q1_WriteBSPFile(char *filename); +void Q1_PrintBSPFileSizes(void); +void Q1_ParseEntities(void); +void Q1_UnparseEntities(void); + +#endif diff --git a/code/bspc/l_bsp_q2.c b/code/bspc/l_bsp_q2.c index d5e9844..2d97579 100755 --- a/code/bspc/l_bsp_q2.c +++ b/code/bspc/l_bsp_q2.c @@ -1,1134 +1,1134 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "l_cmd.h" -#include "l_math.h" -#include "l_mem.h" -#include "l_log.h" -#include "l_poly.h" -#include "../botlib/l_script.h" -#include "q2files.h" -#include "l_bsp_q2.h" -#include "l_bsp_ent.h" - -#define q2_dmodel_t dmodel_t -#define q2_lump_t lump_t -#define q2_dheader_t dheader_t -#define q2_dmodel_t dmodel_t -#define q2_dvertex_t dvertex_t -#define q2_dplane_t dplane_t -#define q2_dnode_t dnode_t -#define q2_texinfo_t texinfo_t -#define q2_dedge_t dedge_t -#define q2_dface_t dface_t -#define q2_dleaf_t dleaf_t -#define q2_dbrushside_t dbrushside_t -#define q2_dbrush_t dbrush_t -#define q2_dvis_t dvis_t -#define q2_dareaportal_t dareaportal_t -#define q2_darea_t darea_t - -#define q2_nummodels nummodels -#define q2_dmodels dmodels -#define q2_numleafs numleafs -#define q2_dleafs dleafs -#define q2_numplanes numplanes -#define q2_dplanes dplanes -#define q2_numvertexes numvertexes -#define q2_dvertexes dvertexes -#define q2_numnodes numnodes -#define q2_dnodes dnodes -#define q2_numtexinfo numtexinfo -#define q2_texinfo texinfo -#define q2_numfaces numfaces -#define q2_dfaces dfaces -#define q2_numedges numedges -#define q2_dedges dedges -#define q2_numleaffaces numleaffaces -#define q2_dleaffaces dleaffaces -#define q2_numleafbrushes numleafbrushes -#define q2_dleafbrushes dleafbrushes -#define q2_dsurfedges dsurfedges -#define q2_numbrushes numbrushes -#define q2_dbrushes dbrushes -#define q2_numbrushsides numbrushsides -#define q2_dbrushsides dbrushsides -#define q2_numareas numareas -#define q2_dareas dareas -#define q2_numareaportals numareaportals -#define q2_dareaportals dareaportals - -void GetLeafNums (void); - -//============================================================================= - -int nummodels; -dmodel_t *dmodels;//[MAX_MAP_MODELS]; - -int visdatasize; -byte *dvisdata;//[MAX_MAP_VISIBILITY]; -dvis_t *dvis;// = (dvis_t *)dvisdata; - -int lightdatasize; -byte *dlightdata;//[MAX_MAP_LIGHTING]; - -int entdatasize; -char *dentdata;//[MAX_MAP_ENTSTRING]; - -int numleafs; -dleaf_t *dleafs;//[MAX_MAP_LEAFS]; - -int numplanes; -dplane_t *dplanes;//[MAX_MAP_PLANES]; - -int numvertexes; -dvertex_t *dvertexes;//[MAX_MAP_VERTS]; - -int numnodes; -dnode_t *dnodes;//[MAX_MAP_NODES]; - -//NOTE: must be static for q2 .map to q2 .bsp -int numtexinfo; -texinfo_t texinfo[MAX_MAP_TEXINFO]; - -int numfaces; -dface_t *dfaces;//[MAX_MAP_FACES]; - -int numedges; -dedge_t *dedges;//[MAX_MAP_EDGES]; - -int numleaffaces; -unsigned short *dleaffaces;//[MAX_MAP_LEAFFACES]; - -int numleafbrushes; -unsigned short *dleafbrushes;//[MAX_MAP_LEAFBRUSHES]; - -int numsurfedges; -int *dsurfedges;//[MAX_MAP_SURFEDGES]; - -int numbrushes; -dbrush_t *dbrushes;//[MAX_MAP_BRUSHES]; - -int numbrushsides; -dbrushside_t *dbrushsides;//[MAX_MAP_BRUSHSIDES]; - -int numareas; -darea_t *dareas;//[MAX_MAP_AREAS]; - -int numareaportals; -dareaportal_t *dareaportals;//[MAX_MAP_AREAPORTALS]; - -#define MAX_MAP_DPOP 256 -byte dpop[MAX_MAP_DPOP]; - -// -char brushsidetextured[MAX_MAP_BRUSHSIDES]; - -//#ifdef ME - -int bspallocated = false; -int allocatedbspmem = 0; - -void Q2_AllocMaxBSP(void) -{ - //models - nummodels = 0; - dmodels = (dmodel_t *) GetClearedMemory(MAX_MAP_MODELS * sizeof(dmodel_t)); - allocatedbspmem += MAX_MAP_MODELS * sizeof(dmodel_t); - //vis data - visdatasize = 0; - dvisdata = (byte *) GetClearedMemory(MAX_MAP_VISIBILITY * sizeof(byte)); - dvis = (dvis_t *) dvisdata; - allocatedbspmem += MAX_MAP_VISIBILITY * sizeof(byte); - //light data - lightdatasize = 0; - dlightdata = (byte *) GetClearedMemory(MAX_MAP_LIGHTING * sizeof(byte)); - allocatedbspmem += MAX_MAP_LIGHTING * sizeof(byte); - //entity data - entdatasize = 0; - dentdata = (char *) GetClearedMemory(MAX_MAP_ENTSTRING * sizeof(char)); - allocatedbspmem += MAX_MAP_ENTSTRING * sizeof(char); - //leafs - numleafs = 0; - dleafs = (dleaf_t *) GetClearedMemory(MAX_MAP_LEAFS * sizeof(dleaf_t)); - allocatedbspmem += MAX_MAP_LEAFS * sizeof(dleaf_t); - //planes - numplanes = 0; - dplanes = (dplane_t *) GetClearedMemory(MAX_MAP_PLANES * sizeof(dplane_t)); - allocatedbspmem += MAX_MAP_PLANES * sizeof(dplane_t); - //vertexes - numvertexes = 0; - dvertexes = (dvertex_t *) GetClearedMemory(MAX_MAP_VERTS * sizeof(dvertex_t)); - allocatedbspmem += MAX_MAP_VERTS * sizeof(dvertex_t); - //nodes - numnodes = 0; - dnodes = (dnode_t *) GetClearedMemory(MAX_MAP_NODES * sizeof(dnode_t)); - allocatedbspmem += MAX_MAP_NODES * sizeof(dnode_t); - /* - //texture info - numtexinfo = 0; - texinfo = (texinfo_t *) GetClearedMemory(MAX_MAP_TEXINFO * sizeof(texinfo_t)); - allocatedbspmem += MAX_MAP_TEXINFO * sizeof(texinfo_t); - //*/ - //faces - numfaces = 0; - dfaces = (dface_t *) GetClearedMemory(MAX_MAP_FACES * sizeof(dface_t)); - allocatedbspmem += MAX_MAP_FACES * sizeof(dface_t); - //edges - numedges = 0; - dedges = (dedge_t *) GetClearedMemory(MAX_MAP_EDGES * sizeof(dedge_t)); - allocatedbspmem += MAX_MAP_EDGES * sizeof(dedge_t); - //leaf faces - numleaffaces = 0; - dleaffaces = (unsigned short *) GetClearedMemory(MAX_MAP_LEAFFACES * sizeof(unsigned short)); - allocatedbspmem += MAX_MAP_LEAFFACES * sizeof(unsigned short); - //leaf brushes - numleafbrushes = 0; - dleafbrushes = (unsigned short *) GetClearedMemory(MAX_MAP_LEAFBRUSHES * sizeof(unsigned short)); - allocatedbspmem += MAX_MAP_LEAFBRUSHES * sizeof(unsigned short); - //surface edges - numsurfedges = 0; - dsurfedges = (int *) GetClearedMemory(MAX_MAP_SURFEDGES * sizeof(int)); - allocatedbspmem += MAX_MAP_SURFEDGES * sizeof(int); - //brushes - numbrushes = 0; - dbrushes = (dbrush_t *) GetClearedMemory(MAX_MAP_BRUSHES * sizeof(dbrush_t)); - allocatedbspmem += MAX_MAP_BRUSHES * sizeof(dbrush_t); - //brushsides - numbrushsides = 0; - dbrushsides = (dbrushside_t *) GetClearedMemory(MAX_MAP_BRUSHSIDES * sizeof(dbrushside_t)); - allocatedbspmem += MAX_MAP_BRUSHSIDES * sizeof(dbrushside_t); - //areas - numareas = 0; - dareas = (darea_t *) GetClearedMemory(MAX_MAP_AREAS * sizeof(darea_t)); - allocatedbspmem += MAX_MAP_AREAS * sizeof(darea_t); - //area portals - numareaportals = 0; - dareaportals = (dareaportal_t *) GetClearedMemory(MAX_MAP_AREAPORTALS * sizeof(dareaportal_t)); - allocatedbspmem += MAX_MAP_AREAPORTALS * sizeof(dareaportal_t); - //print allocated memory - Log_Print("allocated "); - PrintMemorySize(allocatedbspmem); - Log_Print(" of BSP memory\n"); -} //end of the function Q2_AllocMaxBSP - -void Q2_FreeMaxBSP(void) -{ - //models - nummodels = 0; - FreeMemory(dmodels); - dmodels = NULL; - //vis data - visdatasize = 0; - FreeMemory(dvisdata); - dvisdata = NULL; - dvis = NULL; - //light data - lightdatasize = 0; - FreeMemory(dlightdata); - dlightdata = NULL; - //entity data - entdatasize = 0; - FreeMemory(dentdata); - dentdata = NULL; - //leafs - numleafs = 0; - FreeMemory(dleafs); - dleafs = NULL; - //planes - numplanes = 0; - FreeMemory(dplanes); - dplanes = NULL; - //vertexes - numvertexes = 0; - FreeMemory(dvertexes); - dvertexes = NULL; - //nodes - numnodes = 0; - FreeMemory(dnodes); - dnodes = NULL; - /* - //texture info - numtexinfo = 0; - FreeMemory(texinfo); - texinfo = NULL; - //*/ - //faces - numfaces = 0; - FreeMemory(dfaces); - dfaces = NULL; - //edges - numedges = 0; - FreeMemory(dedges); - dedges = NULL; - //leaf faces - numleaffaces = 0; - FreeMemory(dleaffaces); - dleaffaces = NULL; - //leaf brushes - numleafbrushes = 0; - FreeMemory(dleafbrushes); - dleafbrushes = NULL; - //surface edges - numsurfedges = 0; - FreeMemory(dsurfedges); - dsurfedges = NULL; - //brushes - numbrushes = 0; - FreeMemory(dbrushes); - dbrushes = NULL; - //brushsides - numbrushsides = 0; - FreeMemory(dbrushsides); - dbrushsides = NULL; - //areas - numareas = 0; - FreeMemory(dareas); - dareas = NULL; - //area portals - numareaportals = 0; - FreeMemory(dareaportals); - dareaportals = NULL; - // - Log_Print("freed "); - PrintMemorySize(allocatedbspmem); - Log_Print(" of BSP memory\n"); - allocatedbspmem = 0; -} //end of the function Q2_FreeMaxBSP - -#define WCONVEX_EPSILON 0.5 - -int InsideWinding(winding_t *w, vec3_t point, int planenum) -{ - int i; - float dist; - vec_t *v1, *v2; - vec3_t normal, edgevec; - dplane_t *plane; - - for (i = 1; i <= w->numpoints; i++) - { - v1 = w->p[i % w->numpoints]; - v2 = w->p[(i + 1) % w->numpoints]; - - VectorSubtract(v2, v1, edgevec); - plane = &dplanes[planenum]; - CrossProduct(plane->normal, edgevec, normal); - VectorNormalize(normal); - dist = DotProduct(normal, v1); - // - if (DotProduct(normal, point) - dist > WCONVEX_EPSILON) return false; - } //end for - return true; -} //end of the function InsideWinding - -int InsideFace(dface_t *face, vec3_t point) -{ - int i, edgenum, side; - float dist; - vec_t *v1, *v2; - vec3_t normal, edgevec; - dplane_t *plane; - - for (i = 0; i < face->numedges; i++) - { - //get the first and second vertex of the edge - edgenum = dsurfedges[face->firstedge + i]; - side = edgenum < 0; - v1 = dvertexes[dedges[abs(edgenum)].v[side]].point; - v2 = dvertexes[dedges[abs(edgenum)].v[!side]].point; - //create a plane through the edge vector, orthogonal to the face plane - //and with the normal vector pointing out of the face - VectorSubtract(v1, v2, edgevec); - plane = &dplanes[face->planenum]; - CrossProduct(plane->normal, edgevec, normal); - VectorNormalize(normal); - dist = DotProduct(normal, v1); - // - if (DotProduct(normal, point) - dist > WCONVEX_EPSILON) return false; - } //end for - return true; -} //end of the function InsideFace -//=========================================================================== -// returns the amount the face and the winding overlap -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -float Q2_FaceOnWinding(q2_dface_t *face, winding_t *winding) -{ - int i, edgenum, side; - float dist, area; - q2_dplane_t plane; - vec_t *v1, *v2; - vec3_t normal, edgevec; - winding_t *w; - - // - w = CopyWinding(winding); - memcpy(&plane, &q2_dplanes[face->planenum], sizeof(q2_dplane_t)); - //check on which side of the plane the face is - if (face->side) - { - VectorNegate(plane.normal, plane.normal); - plane.dist = -plane.dist; - } //end if - for (i = 0; i < face->numedges && w; i++) - { - //get the first and second vertex of the edge - edgenum = q2_dsurfedges[face->firstedge + i]; - side = edgenum > 0; - //if the face plane is flipped - v1 = q2_dvertexes[q2_dedges[abs(edgenum)].v[side]].point; - v2 = q2_dvertexes[q2_dedges[abs(edgenum)].v[!side]].point; - //create a plane through the edge vector, orthogonal to the face plane - //and with the normal vector pointing inward - VectorSubtract(v1, v2, edgevec); - CrossProduct(edgevec, plane.normal, normal); - VectorNormalize(normal); - dist = DotProduct(normal, v1); - // - ChopWindingInPlace(&w, normal, dist, -0.1); //CLIP_EPSILON - } //end for - if (w) - { - area = WindingArea(w); - FreeWinding(w); - return area; - } //end if - return 0; -} //end of the function Q2_FaceOnWinding -//=========================================================================== -// creates a winding for the given brush side on the given brush -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -winding_t *Q2_BrushSideWinding(dbrush_t *brush, dbrushside_t *baseside) -{ - int i; - dplane_t *baseplane, *plane; - winding_t *w; - dbrushside_t *side; - - //create a winding for the brush side with the given planenumber - baseplane = &dplanes[baseside->planenum]; - w = BaseWindingForPlane(baseplane->normal, baseplane->dist); - for (i = 0; i < brush->numsides && w; i++) - { - side = &dbrushsides[brush->firstside + i]; - //don't chop with the base plane - if (side->planenum == baseside->planenum) continue; - //also don't use planes that are almost equal - plane = &dplanes[side->planenum]; - if (DotProduct(baseplane->normal, plane->normal) > 0.999 - && fabs(baseplane->dist - plane->dist) < 0.01) continue; - // - plane = &dplanes[side->planenum^1]; - ChopWindingInPlace(&w, plane->normal, plane->dist, -0.1); //CLIP_EPSILON); - } //end for - return w; -} //end of the function Q2_BrushSideWinding -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int Q2_HintSkipBrush(dbrush_t *brush) -{ - int j; - dbrushside_t *brushside; - - for (j = 0; j < brush->numsides; j++) - { - brushside = &dbrushsides[brush->firstside + j]; - if (brushside->texinfo > 0) - { - if (texinfo[brushside->texinfo].flags & (SURF_SKIP|SURF_HINT)) - { - return true; - } //end if - } //end if - } //end for - return false; -} //end of the function Q2_HintSkipBrush -//=========================================================================== -// fix screwed brush texture references -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean WindingIsTiny(winding_t *w); - -void Q2_FixTextureReferences(void) -{ - int i, j, k, we; - dbrushside_t *brushside; - dbrush_t *brush; - dface_t *face; - winding_t *w; - - memset(brushsidetextured, false, MAX_MAP_BRUSHSIDES); - //go over all the brushes - for (i = 0; i < numbrushes; i++) - { - brush = &dbrushes[i]; - //hint brushes are not textured - if (Q2_HintSkipBrush(brush)) continue; - //go over all the sides of the brush - for (j = 0; j < brush->numsides; j++) - { - brushside = &dbrushsides[brush->firstside + j]; - // - w = Q2_BrushSideWinding(brush, brushside); - if (!w) - { - brushsidetextured[brush->firstside + j] = true; - continue; - } //end if - else - { - //RemoveEqualPoints(w, 0.2); - if (WindingIsTiny(w)) - { - FreeWinding(w); - brushsidetextured[brush->firstside + j] = true; - continue; - } //end if - else - { - we = WindingError(w); - if (we == WE_NOTENOUGHPOINTS - || we == WE_SMALLAREA - || we == WE_POINTBOGUSRANGE -// || we == WE_NONCONVEX - ) - { - FreeWinding(w); - brushsidetextured[brush->firstside + j] = true; - continue; - } //end if - } //end else - } //end else - if (WindingArea(w) < 20) - { - brushsidetextured[brush->firstside + j] = true; - } //end if - //find a face for texturing this brush - for (k = 0; k < numfaces; k++) - { - face = &dfaces[k]; - //if the face is in the same plane as the brush side - if ((face->planenum&~1) != (brushside->planenum&~1)) continue; - //if the face is partly or totally on the brush side - if (Q2_FaceOnWinding(face, w)) - { - brushside->texinfo = face->texinfo; - brushsidetextured[brush->firstside + j] = true; - break; - } //end if - } //end for - FreeWinding(w); - } //end for - } //end for -} //end of the function Q2_FixTextureReferences*/ - -//#endif //ME - - -/* -=============== -CompressVis - -=============== -*/ -int Q2_CompressVis (byte *vis, byte *dest) -{ - int j; - int rep; - int visrow; - byte *dest_p; - - dest_p = dest; -// visrow = (r_numvisleafs + 7)>>3; - visrow = (dvis->numclusters + 7)>>3; - - for (j=0 ; j>3; - row = (dvis->numclusters+7)>>3; - out = decompressed; - - do - { - if (*in) - { - *out++ = *in++; - continue; - } - - c = in[1]; - if (!c) - Error ("DecompressVis: 0 repeat"); - in += 2; - while (c) - { - *out++ = 0; - c--; - } - } while (out - decompressed < row); -} - -//============================================================================= - -/* -============= -SwapBSPFile - -Byte swaps all data in a bsp file. -============= -*/ -void Q2_SwapBSPFile (qboolean todisk) -{ - int i, j; - dmodel_t *d; - - -// models - for (i=0 ; ifirstface = LittleLong (d->firstface); - d->numfaces = LittleLong (d->numfaces); - d->headnode = LittleLong (d->headnode); - - for (j=0 ; j<3 ; j++) - { - d->mins[j] = LittleFloat(d->mins[j]); - d->maxs[j] = LittleFloat(d->maxs[j]); - d->origin[j] = LittleFloat(d->origin[j]); - } - } - -// -// vertexes -// - for (i=0 ; inumclusters; - else - j = LittleLong(dvis->numclusters); - dvis->numclusters = LittleLong (dvis->numclusters); - for (i=0 ; ibitofs[i][0] = LittleLong (dvis->bitofs[i][0]); - dvis->bitofs[i][1] = LittleLong (dvis->bitofs[i][1]); - } -} //end of the function Q2_SwapBSPFile - - -dheader_t *header; - -int Q2_CopyLump (int lump, void *dest, int size, int maxsize) -{ - int length, ofs; - - length = header->lumps[lump].filelen; - ofs = header->lumps[lump].fileofs; - - if (length % size) - Error ("LoadBSPFile: odd lump size"); - - if ((length/size) > maxsize) - Error ("Q2_LoadBSPFile: exceeded max size for lump %d size %d > maxsize %d\n", lump, (length/size), maxsize); - - memcpy (dest, (byte *)header + ofs, length); - - return length / size; -} //end of the function Q2_CopyLump - -/* -============= -LoadBSPFile -============= -*/ -void Q2_LoadBSPFile(char *filename, int offset, int length) -{ - int i; - -// -// load the file header -// - LoadFile (filename, (void **)&header, offset, length); - -// swap the header - for (i=0 ; i< sizeof(dheader_t)/4 ; i++) - ((int *)header)[i] = LittleLong ( ((int *)header)[i]); - - if (header->ident != IDBSPHEADER) - Error ("%s is not a IBSP file", filename); - if (header->version != BSPVERSION) - Error ("%s is version %i, not %i", filename, header->version, BSPVERSION); - - nummodels = Q2_CopyLump (LUMP_MODELS, dmodels, sizeof(dmodel_t), MAX_MAP_MODELS); - numvertexes = Q2_CopyLump (LUMP_VERTEXES, dvertexes, sizeof(dvertex_t), MAX_MAP_VERTS); - numplanes = Q2_CopyLump (LUMP_PLANES, dplanes, sizeof(dplane_t), MAX_MAP_PLANES); - numleafs = Q2_CopyLump (LUMP_LEAFS, dleafs, sizeof(dleaf_t), MAX_MAP_LEAFS); - numnodes = Q2_CopyLump (LUMP_NODES, dnodes, sizeof(dnode_t), MAX_MAP_NODES); - numtexinfo = Q2_CopyLump (LUMP_TEXINFO, texinfo, sizeof(texinfo_t), MAX_MAP_TEXINFO); - numfaces = Q2_CopyLump (LUMP_FACES, dfaces, sizeof(dface_t), MAX_MAP_FACES); - numleaffaces = Q2_CopyLump (LUMP_LEAFFACES, dleaffaces, sizeof(dleaffaces[0]), MAX_MAP_LEAFFACES); - numleafbrushes = Q2_CopyLump (LUMP_LEAFBRUSHES, dleafbrushes, sizeof(dleafbrushes[0]), MAX_MAP_LEAFBRUSHES); - numsurfedges = Q2_CopyLump (LUMP_SURFEDGES, dsurfedges, sizeof(dsurfedges[0]), MAX_MAP_SURFEDGES); - numedges = Q2_CopyLump (LUMP_EDGES, dedges, sizeof(dedge_t), MAX_MAP_EDGES); - numbrushes = Q2_CopyLump (LUMP_BRUSHES, dbrushes, sizeof(dbrush_t), MAX_MAP_BRUSHES); - numbrushsides = Q2_CopyLump (LUMP_BRUSHSIDES, dbrushsides, sizeof(dbrushside_t), MAX_MAP_BRUSHSIDES); - numareas = Q2_CopyLump (LUMP_AREAS, dareas, sizeof(darea_t), MAX_MAP_AREAS); - numareaportals = Q2_CopyLump (LUMP_AREAPORTALS, dareaportals, sizeof(dareaportal_t), MAX_MAP_AREAPORTALS); - - visdatasize = Q2_CopyLump (LUMP_VISIBILITY, dvisdata, 1, MAX_MAP_VISIBILITY); - lightdatasize = Q2_CopyLump (LUMP_LIGHTING, dlightdata, 1, MAX_MAP_LIGHTING); - entdatasize = Q2_CopyLump (LUMP_ENTITIES, dentdata, 1, MAX_MAP_ENTSTRING); - - Q2_CopyLump (LUMP_POP, dpop, 1, MAX_MAP_DPOP); - - FreeMemory(header); // everything has been copied out - -// -// swap everything -// - Q2_SwapBSPFile (false); - - Q2_FixTextureReferences(); -} //end of the function Q2_LoadBSPFile - - -/* -============= -LoadBSPFileTexinfo - -Only loads the texinfo lump, so qdata can scan for textures -============= -*/ -void Q2_LoadBSPFileTexinfo (char *filename) -{ - int i; - FILE *f; - int length, ofs; - - header = GetMemory(sizeof(dheader_t)); - - f = fopen (filename, "rb"); - fread (header, sizeof(dheader_t), 1, f); - -// swap the header - for (i=0 ; i< sizeof(dheader_t)/4 ; i++) - ((int *)header)[i] = LittleLong ( ((int *)header)[i]); - - if (header->ident != IDBSPHEADER) - Error ("%s is not a IBSP file", filename); - if (header->version != BSPVERSION) - Error ("%s is version %i, not %i", filename, header->version, BSPVERSION); - - - length = header->lumps[LUMP_TEXINFO].filelen; - ofs = header->lumps[LUMP_TEXINFO].fileofs; - - fseek (f, ofs, SEEK_SET); - fread (texinfo, length, 1, f); - fclose (f); - - numtexinfo = length / sizeof(texinfo_t); - - FreeMemory(header); // everything has been copied out - - Q2_SwapBSPFile (false); -} //end of the function Q2_LoadBSPFileTexinfo - - -//============================================================================ - -FILE *wadfile; -dheader_t outheader; - -void Q2_AddLump (int lumpnum, void *data, int len) -{ - lump_t *lump; - - lump = &header->lumps[lumpnum]; - - lump->fileofs = LittleLong( ftell(wadfile) ); - lump->filelen = LittleLong(len); - SafeWrite (wadfile, data, (len+3)&~3); -} //end of the function Q2_AddLump - -/* -============= -WriteBSPFile - -Swaps the bsp file in place, so it should not be referenced again -============= -*/ -void Q2_WriteBSPFile (char *filename) -{ - header = &outheader; - memset (header, 0, sizeof(dheader_t)); - - Q2_SwapBSPFile (true); - - header->ident = LittleLong (IDBSPHEADER); - header->version = LittleLong (BSPVERSION); - - wadfile = SafeOpenWrite (filename); - SafeWrite (wadfile, header, sizeof(dheader_t)); // overwritten later - - Q2_AddLump (LUMP_PLANES, dplanes, numplanes*sizeof(dplane_t)); - Q2_AddLump (LUMP_LEAFS, dleafs, numleafs*sizeof(dleaf_t)); - Q2_AddLump (LUMP_VERTEXES, dvertexes, numvertexes*sizeof(dvertex_t)); - Q2_AddLump (LUMP_NODES, dnodes, numnodes*sizeof(dnode_t)); - Q2_AddLump (LUMP_TEXINFO, texinfo, numtexinfo*sizeof(texinfo_t)); - Q2_AddLump (LUMP_FACES, dfaces, numfaces*sizeof(dface_t)); - Q2_AddLump (LUMP_BRUSHES, dbrushes, numbrushes*sizeof(dbrush_t)); - Q2_AddLump (LUMP_BRUSHSIDES, dbrushsides, numbrushsides*sizeof(dbrushside_t)); - Q2_AddLump (LUMP_LEAFFACES, dleaffaces, numleaffaces*sizeof(dleaffaces[0])); - Q2_AddLump (LUMP_LEAFBRUSHES, dleafbrushes, numleafbrushes*sizeof(dleafbrushes[0])); - Q2_AddLump (LUMP_SURFEDGES, dsurfedges, numsurfedges*sizeof(dsurfedges[0])); - Q2_AddLump (LUMP_EDGES, dedges, numedges*sizeof(dedge_t)); - Q2_AddLump (LUMP_MODELS, dmodels, nummodels*sizeof(dmodel_t)); - Q2_AddLump (LUMP_AREAS, dareas, numareas*sizeof(darea_t)); - Q2_AddLump (LUMP_AREAPORTALS, dareaportals, numareaportals*sizeof(dareaportal_t)); - - Q2_AddLump (LUMP_LIGHTING, dlightdata, lightdatasize); - Q2_AddLump (LUMP_VISIBILITY, dvisdata, visdatasize); - Q2_AddLump (LUMP_ENTITIES, dentdata, entdatasize); - Q2_AddLump (LUMP_POP, dpop, sizeof(dpop)); - - fseek (wadfile, 0, SEEK_SET); - SafeWrite (wadfile, header, sizeof(dheader_t)); - fclose (wadfile); -} //end of the function Q2_WriteBSPFile - -//============================================================================ - -/* -============= -PrintBSPFileSizes - -Dumps info about current file -============= -*/ -void Q2_PrintBSPFileSizes (void) -{ - if (!num_entities) - Q2_ParseEntities(); - - printf ("%6i models %7i\n" - ,nummodels, (int)(nummodels*sizeof(dmodel_t))); - printf ("%6i brushes %7i\n" - ,numbrushes, (int)(numbrushes*sizeof(dbrush_t))); - printf ("%6i brushsides %7i\n" - ,numbrushsides, (int)(numbrushsides*sizeof(dbrushside_t))); - printf ("%6i planes %7i\n" - ,numplanes, (int)(numplanes*sizeof(dplane_t))); - printf ("%6i texinfo %7i\n" - ,numtexinfo, (int)(numtexinfo*sizeof(texinfo_t))); - printf ("%6i entdata %7i\n", num_entities, entdatasize); - - printf ("\n"); - - printf ("%6i vertexes %7i\n" - ,numvertexes, (int)(numvertexes*sizeof(dvertex_t))); - printf ("%6i nodes %7i\n" - ,numnodes, (int)(numnodes*sizeof(dnode_t))); - printf ("%6i faces %7i\n" - ,numfaces, (int)(numfaces*sizeof(dface_t))); - printf ("%6i leafs %7i\n" - ,numleafs, (int)(numleafs*sizeof(dleaf_t))); - printf ("%6i leaffaces %7i\n" - ,numleaffaces, (int)(numleaffaces*sizeof(dleaffaces[0]))); - printf ("%6i leafbrushes %7i\n" - ,numleafbrushes, (int)(numleafbrushes*sizeof(dleafbrushes[0]))); - printf ("%6i surfedges %7i\n" - ,numsurfedges, (int)(numsurfedges*sizeof(dsurfedges[0]))); - printf ("%6i edges %7i\n" - ,numedges, (int)(numedges*sizeof(dedge_t))); -//NEW - printf ("%6i areas %7i\n" - ,numareas, (int)(numareas*sizeof(darea_t))); - printf ("%6i areaportals %7i\n" - ,numareaportals, (int)(numareaportals*sizeof(dareaportal_t))); -//ENDNEW - printf (" lightdata %7i\n", lightdatasize); - printf (" visdata %7i\n", visdatasize); -} //end of the function Q2_PrintBSPFileSizes - -/* -================ -ParseEntities - -Parses the dentdata string into entities -================ -*/ -void Q2_ParseEntities (void) -{ - script_t *script; - - num_entities = 0; - script = LoadScriptMemory(dentdata, entdatasize, "*Quake2 bsp file"); - SetScriptFlags(script, SCFL_NOSTRINGWHITESPACES | - SCFL_NOSTRINGESCAPECHARS); - - while(ParseEntity(script)) - { - } //end while - - FreeScript(script); -} //end of the function Q2_ParseEntities - - -/* -================ -UnparseEntities - -Generates the dentdata string from all the entities -================ -*/ -void Q2_UnparseEntities (void) -{ - char *buf, *end; - epair_t *ep; - char line[2048]; - int i; - char key[1024], value[1024]; - - buf = dentdata; - end = buf; - *end = 0; - - for (i=0 ; inext) - { - strcpy (key, ep->key); - StripTrailing (key); - strcpy (value, ep->value); - StripTrailing (value); - - sprintf (line, "\"%s\" \"%s\"\n", key, value); - strcat (end, line); - end += strlen(line); - } - strcat (end,"}\n"); - end += 2; - - if (end > buf + MAX_MAP_ENTSTRING) - Error ("Entity text too long"); - } - entdatasize = end - buf + 1; -} //end of the function Q2_UnparseEntities - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "l_cmd.h" +#include "l_math.h" +#include "l_mem.h" +#include "l_log.h" +#include "l_poly.h" +#include "../botlib/l_script.h" +#include "q2files.h" +#include "l_bsp_q2.h" +#include "l_bsp_ent.h" + +#define q2_dmodel_t dmodel_t +#define q2_lump_t lump_t +#define q2_dheader_t dheader_t +#define q2_dmodel_t dmodel_t +#define q2_dvertex_t dvertex_t +#define q2_dplane_t dplane_t +#define q2_dnode_t dnode_t +#define q2_texinfo_t texinfo_t +#define q2_dedge_t dedge_t +#define q2_dface_t dface_t +#define q2_dleaf_t dleaf_t +#define q2_dbrushside_t dbrushside_t +#define q2_dbrush_t dbrush_t +#define q2_dvis_t dvis_t +#define q2_dareaportal_t dareaportal_t +#define q2_darea_t darea_t + +#define q2_nummodels nummodels +#define q2_dmodels dmodels +#define q2_numleafs numleafs +#define q2_dleafs dleafs +#define q2_numplanes numplanes +#define q2_dplanes dplanes +#define q2_numvertexes numvertexes +#define q2_dvertexes dvertexes +#define q2_numnodes numnodes +#define q2_dnodes dnodes +#define q2_numtexinfo numtexinfo +#define q2_texinfo texinfo +#define q2_numfaces numfaces +#define q2_dfaces dfaces +#define q2_numedges numedges +#define q2_dedges dedges +#define q2_numleaffaces numleaffaces +#define q2_dleaffaces dleaffaces +#define q2_numleafbrushes numleafbrushes +#define q2_dleafbrushes dleafbrushes +#define q2_dsurfedges dsurfedges +#define q2_numbrushes numbrushes +#define q2_dbrushes dbrushes +#define q2_numbrushsides numbrushsides +#define q2_dbrushsides dbrushsides +#define q2_numareas numareas +#define q2_dareas dareas +#define q2_numareaportals numareaportals +#define q2_dareaportals dareaportals + +void GetLeafNums (void); + +//============================================================================= + +int nummodels; +dmodel_t *dmodels;//[MAX_MAP_MODELS]; + +int visdatasize; +byte *dvisdata;//[MAX_MAP_VISIBILITY]; +dvis_t *dvis;// = (dvis_t *)dvisdata; + +int lightdatasize; +byte *dlightdata;//[MAX_MAP_LIGHTING]; + +int entdatasize; +char *dentdata;//[MAX_MAP_ENTSTRING]; + +int numleafs; +dleaf_t *dleafs;//[MAX_MAP_LEAFS]; + +int numplanes; +dplane_t *dplanes;//[MAX_MAP_PLANES]; + +int numvertexes; +dvertex_t *dvertexes;//[MAX_MAP_VERTS]; + +int numnodes; +dnode_t *dnodes;//[MAX_MAP_NODES]; + +//NOTE: must be static for q2 .map to q2 .bsp +int numtexinfo; +texinfo_t texinfo[MAX_MAP_TEXINFO]; + +int numfaces; +dface_t *dfaces;//[MAX_MAP_FACES]; + +int numedges; +dedge_t *dedges;//[MAX_MAP_EDGES]; + +int numleaffaces; +unsigned short *dleaffaces;//[MAX_MAP_LEAFFACES]; + +int numleafbrushes; +unsigned short *dleafbrushes;//[MAX_MAP_LEAFBRUSHES]; + +int numsurfedges; +int *dsurfedges;//[MAX_MAP_SURFEDGES]; + +int numbrushes; +dbrush_t *dbrushes;//[MAX_MAP_BRUSHES]; + +int numbrushsides; +dbrushside_t *dbrushsides;//[MAX_MAP_BRUSHSIDES]; + +int numareas; +darea_t *dareas;//[MAX_MAP_AREAS]; + +int numareaportals; +dareaportal_t *dareaportals;//[MAX_MAP_AREAPORTALS]; + +#define MAX_MAP_DPOP 256 +byte dpop[MAX_MAP_DPOP]; + +// +char brushsidetextured[MAX_MAP_BRUSHSIDES]; + +//#ifdef ME + +int bspallocated = false; +int allocatedbspmem = 0; + +void Q2_AllocMaxBSP(void) +{ + //models + nummodels = 0; + dmodels = (dmodel_t *) GetClearedMemory(MAX_MAP_MODELS * sizeof(dmodel_t)); + allocatedbspmem += MAX_MAP_MODELS * sizeof(dmodel_t); + //vis data + visdatasize = 0; + dvisdata = (byte *) GetClearedMemory(MAX_MAP_VISIBILITY * sizeof(byte)); + dvis = (dvis_t *) dvisdata; + allocatedbspmem += MAX_MAP_VISIBILITY * sizeof(byte); + //light data + lightdatasize = 0; + dlightdata = (byte *) GetClearedMemory(MAX_MAP_LIGHTING * sizeof(byte)); + allocatedbspmem += MAX_MAP_LIGHTING * sizeof(byte); + //entity data + entdatasize = 0; + dentdata = (char *) GetClearedMemory(MAX_MAP_ENTSTRING * sizeof(char)); + allocatedbspmem += MAX_MAP_ENTSTRING * sizeof(char); + //leafs + numleafs = 0; + dleafs = (dleaf_t *) GetClearedMemory(MAX_MAP_LEAFS * sizeof(dleaf_t)); + allocatedbspmem += MAX_MAP_LEAFS * sizeof(dleaf_t); + //planes + numplanes = 0; + dplanes = (dplane_t *) GetClearedMemory(MAX_MAP_PLANES * sizeof(dplane_t)); + allocatedbspmem += MAX_MAP_PLANES * sizeof(dplane_t); + //vertexes + numvertexes = 0; + dvertexes = (dvertex_t *) GetClearedMemory(MAX_MAP_VERTS * sizeof(dvertex_t)); + allocatedbspmem += MAX_MAP_VERTS * sizeof(dvertex_t); + //nodes + numnodes = 0; + dnodes = (dnode_t *) GetClearedMemory(MAX_MAP_NODES * sizeof(dnode_t)); + allocatedbspmem += MAX_MAP_NODES * sizeof(dnode_t); + /* + //texture info + numtexinfo = 0; + texinfo = (texinfo_t *) GetClearedMemory(MAX_MAP_TEXINFO * sizeof(texinfo_t)); + allocatedbspmem += MAX_MAP_TEXINFO * sizeof(texinfo_t); + //*/ + //faces + numfaces = 0; + dfaces = (dface_t *) GetClearedMemory(MAX_MAP_FACES * sizeof(dface_t)); + allocatedbspmem += MAX_MAP_FACES * sizeof(dface_t); + //edges + numedges = 0; + dedges = (dedge_t *) GetClearedMemory(MAX_MAP_EDGES * sizeof(dedge_t)); + allocatedbspmem += MAX_MAP_EDGES * sizeof(dedge_t); + //leaf faces + numleaffaces = 0; + dleaffaces = (unsigned short *) GetClearedMemory(MAX_MAP_LEAFFACES * sizeof(unsigned short)); + allocatedbspmem += MAX_MAP_LEAFFACES * sizeof(unsigned short); + //leaf brushes + numleafbrushes = 0; + dleafbrushes = (unsigned short *) GetClearedMemory(MAX_MAP_LEAFBRUSHES * sizeof(unsigned short)); + allocatedbspmem += MAX_MAP_LEAFBRUSHES * sizeof(unsigned short); + //surface edges + numsurfedges = 0; + dsurfedges = (int *) GetClearedMemory(MAX_MAP_SURFEDGES * sizeof(int)); + allocatedbspmem += MAX_MAP_SURFEDGES * sizeof(int); + //brushes + numbrushes = 0; + dbrushes = (dbrush_t *) GetClearedMemory(MAX_MAP_BRUSHES * sizeof(dbrush_t)); + allocatedbspmem += MAX_MAP_BRUSHES * sizeof(dbrush_t); + //brushsides + numbrushsides = 0; + dbrushsides = (dbrushside_t *) GetClearedMemory(MAX_MAP_BRUSHSIDES * sizeof(dbrushside_t)); + allocatedbspmem += MAX_MAP_BRUSHSIDES * sizeof(dbrushside_t); + //areas + numareas = 0; + dareas = (darea_t *) GetClearedMemory(MAX_MAP_AREAS * sizeof(darea_t)); + allocatedbspmem += MAX_MAP_AREAS * sizeof(darea_t); + //area portals + numareaportals = 0; + dareaportals = (dareaportal_t *) GetClearedMemory(MAX_MAP_AREAPORTALS * sizeof(dareaportal_t)); + allocatedbspmem += MAX_MAP_AREAPORTALS * sizeof(dareaportal_t); + //print allocated memory + Log_Print("allocated "); + PrintMemorySize(allocatedbspmem); + Log_Print(" of BSP memory\n"); +} //end of the function Q2_AllocMaxBSP + +void Q2_FreeMaxBSP(void) +{ + //models + nummodels = 0; + FreeMemory(dmodels); + dmodels = NULL; + //vis data + visdatasize = 0; + FreeMemory(dvisdata); + dvisdata = NULL; + dvis = NULL; + //light data + lightdatasize = 0; + FreeMemory(dlightdata); + dlightdata = NULL; + //entity data + entdatasize = 0; + FreeMemory(dentdata); + dentdata = NULL; + //leafs + numleafs = 0; + FreeMemory(dleafs); + dleafs = NULL; + //planes + numplanes = 0; + FreeMemory(dplanes); + dplanes = NULL; + //vertexes + numvertexes = 0; + FreeMemory(dvertexes); + dvertexes = NULL; + //nodes + numnodes = 0; + FreeMemory(dnodes); + dnodes = NULL; + /* + //texture info + numtexinfo = 0; + FreeMemory(texinfo); + texinfo = NULL; + //*/ + //faces + numfaces = 0; + FreeMemory(dfaces); + dfaces = NULL; + //edges + numedges = 0; + FreeMemory(dedges); + dedges = NULL; + //leaf faces + numleaffaces = 0; + FreeMemory(dleaffaces); + dleaffaces = NULL; + //leaf brushes + numleafbrushes = 0; + FreeMemory(dleafbrushes); + dleafbrushes = NULL; + //surface edges + numsurfedges = 0; + FreeMemory(dsurfedges); + dsurfedges = NULL; + //brushes + numbrushes = 0; + FreeMemory(dbrushes); + dbrushes = NULL; + //brushsides + numbrushsides = 0; + FreeMemory(dbrushsides); + dbrushsides = NULL; + //areas + numareas = 0; + FreeMemory(dareas); + dareas = NULL; + //area portals + numareaportals = 0; + FreeMemory(dareaportals); + dareaportals = NULL; + // + Log_Print("freed "); + PrintMemorySize(allocatedbspmem); + Log_Print(" of BSP memory\n"); + allocatedbspmem = 0; +} //end of the function Q2_FreeMaxBSP + +#define WCONVEX_EPSILON 0.5 + +int InsideWinding(winding_t *w, vec3_t point, int planenum) +{ + int i; + float dist; + vec_t *v1, *v2; + vec3_t normal, edgevec; + dplane_t *plane; + + for (i = 1; i <= w->numpoints; i++) + { + v1 = w->p[i % w->numpoints]; + v2 = w->p[(i + 1) % w->numpoints]; + + VectorSubtract(v2, v1, edgevec); + plane = &dplanes[planenum]; + CrossProduct(plane->normal, edgevec, normal); + VectorNormalize(normal); + dist = DotProduct(normal, v1); + // + if (DotProduct(normal, point) - dist > WCONVEX_EPSILON) return false; + } //end for + return true; +} //end of the function InsideWinding + +int InsideFace(dface_t *face, vec3_t point) +{ + int i, edgenum, side; + float dist; + vec_t *v1, *v2; + vec3_t normal, edgevec; + dplane_t *plane; + + for (i = 0; i < face->numedges; i++) + { + //get the first and second vertex of the edge + edgenum = dsurfedges[face->firstedge + i]; + side = edgenum < 0; + v1 = dvertexes[dedges[abs(edgenum)].v[side]].point; + v2 = dvertexes[dedges[abs(edgenum)].v[!side]].point; + //create a plane through the edge vector, orthogonal to the face plane + //and with the normal vector pointing out of the face + VectorSubtract(v1, v2, edgevec); + plane = &dplanes[face->planenum]; + CrossProduct(plane->normal, edgevec, normal); + VectorNormalize(normal); + dist = DotProduct(normal, v1); + // + if (DotProduct(normal, point) - dist > WCONVEX_EPSILON) return false; + } //end for + return true; +} //end of the function InsideFace +//=========================================================================== +// returns the amount the face and the winding overlap +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float Q2_FaceOnWinding(q2_dface_t *face, winding_t *winding) +{ + int i, edgenum, side; + float dist, area; + q2_dplane_t plane; + vec_t *v1, *v2; + vec3_t normal, edgevec; + winding_t *w; + + // + w = CopyWinding(winding); + memcpy(&plane, &q2_dplanes[face->planenum], sizeof(q2_dplane_t)); + //check on which side of the plane the face is + if (face->side) + { + VectorNegate(plane.normal, plane.normal); + plane.dist = -plane.dist; + } //end if + for (i = 0; i < face->numedges && w; i++) + { + //get the first and second vertex of the edge + edgenum = q2_dsurfedges[face->firstedge + i]; + side = edgenum > 0; + //if the face plane is flipped + v1 = q2_dvertexes[q2_dedges[abs(edgenum)].v[side]].point; + v2 = q2_dvertexes[q2_dedges[abs(edgenum)].v[!side]].point; + //create a plane through the edge vector, orthogonal to the face plane + //and with the normal vector pointing inward + VectorSubtract(v1, v2, edgevec); + CrossProduct(edgevec, plane.normal, normal); + VectorNormalize(normal); + dist = DotProduct(normal, v1); + // + ChopWindingInPlace(&w, normal, dist, -0.1); //CLIP_EPSILON + } //end for + if (w) + { + area = WindingArea(w); + FreeWinding(w); + return area; + } //end if + return 0; +} //end of the function Q2_FaceOnWinding +//=========================================================================== +// creates a winding for the given brush side on the given brush +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +winding_t *Q2_BrushSideWinding(dbrush_t *brush, dbrushside_t *baseside) +{ + int i; + dplane_t *baseplane, *plane; + winding_t *w; + dbrushside_t *side; + + //create a winding for the brush side with the given planenumber + baseplane = &dplanes[baseside->planenum]; + w = BaseWindingForPlane(baseplane->normal, baseplane->dist); + for (i = 0; i < brush->numsides && w; i++) + { + side = &dbrushsides[brush->firstside + i]; + //don't chop with the base plane + if (side->planenum == baseside->planenum) continue; + //also don't use planes that are almost equal + plane = &dplanes[side->planenum]; + if (DotProduct(baseplane->normal, plane->normal) > 0.999 + && fabs(baseplane->dist - plane->dist) < 0.01) continue; + // + plane = &dplanes[side->planenum^1]; + ChopWindingInPlace(&w, plane->normal, plane->dist, -0.1); //CLIP_EPSILON); + } //end for + return w; +} //end of the function Q2_BrushSideWinding +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Q2_HintSkipBrush(dbrush_t *brush) +{ + int j; + dbrushside_t *brushside; + + for (j = 0; j < brush->numsides; j++) + { + brushside = &dbrushsides[brush->firstside + j]; + if (brushside->texinfo > 0) + { + if (texinfo[brushside->texinfo].flags & (SURF_SKIP|SURF_HINT)) + { + return true; + } //end if + } //end if + } //end for + return false; +} //end of the function Q2_HintSkipBrush +//=========================================================================== +// fix screwed brush texture references +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean WindingIsTiny(winding_t *w); + +void Q2_FixTextureReferences(void) +{ + int i, j, k, we; + dbrushside_t *brushside; + dbrush_t *brush; + dface_t *face; + winding_t *w; + + memset(brushsidetextured, false, MAX_MAP_BRUSHSIDES); + //go over all the brushes + for (i = 0; i < numbrushes; i++) + { + brush = &dbrushes[i]; + //hint brushes are not textured + if (Q2_HintSkipBrush(brush)) continue; + //go over all the sides of the brush + for (j = 0; j < brush->numsides; j++) + { + brushside = &dbrushsides[brush->firstside + j]; + // + w = Q2_BrushSideWinding(brush, brushside); + if (!w) + { + brushsidetextured[brush->firstside + j] = true; + continue; + } //end if + else + { + //RemoveEqualPoints(w, 0.2); + if (WindingIsTiny(w)) + { + FreeWinding(w); + brushsidetextured[brush->firstside + j] = true; + continue; + } //end if + else + { + we = WindingError(w); + if (we == WE_NOTENOUGHPOINTS + || we == WE_SMALLAREA + || we == WE_POINTBOGUSRANGE +// || we == WE_NONCONVEX + ) + { + FreeWinding(w); + brushsidetextured[brush->firstside + j] = true; + continue; + } //end if + } //end else + } //end else + if (WindingArea(w) < 20) + { + brushsidetextured[brush->firstside + j] = true; + } //end if + //find a face for texturing this brush + for (k = 0; k < numfaces; k++) + { + face = &dfaces[k]; + //if the face is in the same plane as the brush side + if ((face->planenum&~1) != (brushside->planenum&~1)) continue; + //if the face is partly or totally on the brush side + if (Q2_FaceOnWinding(face, w)) + { + brushside->texinfo = face->texinfo; + brushsidetextured[brush->firstside + j] = true; + break; + } //end if + } //end for + FreeWinding(w); + } //end for + } //end for +} //end of the function Q2_FixTextureReferences*/ + +//#endif //ME + + +/* +=============== +CompressVis + +=============== +*/ +int Q2_CompressVis (byte *vis, byte *dest) +{ + int j; + int rep; + int visrow; + byte *dest_p; + + dest_p = dest; +// visrow = (r_numvisleafs + 7)>>3; + visrow = (dvis->numclusters + 7)>>3; + + for (j=0 ; j>3; + row = (dvis->numclusters+7)>>3; + out = decompressed; + + do + { + if (*in) + { + *out++ = *in++; + continue; + } + + c = in[1]; + if (!c) + Error ("DecompressVis: 0 repeat"); + in += 2; + while (c) + { + *out++ = 0; + c--; + } + } while (out - decompressed < row); +} + +//============================================================================= + +/* +============= +SwapBSPFile + +Byte swaps all data in a bsp file. +============= +*/ +void Q2_SwapBSPFile (qboolean todisk) +{ + int i, j; + dmodel_t *d; + + +// models + for (i=0 ; ifirstface = LittleLong (d->firstface); + d->numfaces = LittleLong (d->numfaces); + d->headnode = LittleLong (d->headnode); + + for (j=0 ; j<3 ; j++) + { + d->mins[j] = LittleFloat(d->mins[j]); + d->maxs[j] = LittleFloat(d->maxs[j]); + d->origin[j] = LittleFloat(d->origin[j]); + } + } + +// +// vertexes +// + for (i=0 ; inumclusters; + else + j = LittleLong(dvis->numclusters); + dvis->numclusters = LittleLong (dvis->numclusters); + for (i=0 ; ibitofs[i][0] = LittleLong (dvis->bitofs[i][0]); + dvis->bitofs[i][1] = LittleLong (dvis->bitofs[i][1]); + } +} //end of the function Q2_SwapBSPFile + + +dheader_t *header; + +int Q2_CopyLump (int lump, void *dest, int size, int maxsize) +{ + int length, ofs; + + length = header->lumps[lump].filelen; + ofs = header->lumps[lump].fileofs; + + if (length % size) + Error ("LoadBSPFile: odd lump size"); + + if ((length/size) > maxsize) + Error ("Q2_LoadBSPFile: exceeded max size for lump %d size %d > maxsize %d\n", lump, (length/size), maxsize); + + memcpy (dest, (byte *)header + ofs, length); + + return length / size; +} //end of the function Q2_CopyLump + +/* +============= +LoadBSPFile +============= +*/ +void Q2_LoadBSPFile(char *filename, int offset, int length) +{ + int i; + +// +// load the file header +// + LoadFile (filename, (void **)&header, offset, length); + +// swap the header + for (i=0 ; i< sizeof(dheader_t)/4 ; i++) + ((int *)header)[i] = LittleLong ( ((int *)header)[i]); + + if (header->ident != IDBSPHEADER) + Error ("%s is not a IBSP file", filename); + if (header->version != BSPVERSION) + Error ("%s is version %i, not %i", filename, header->version, BSPVERSION); + + nummodels = Q2_CopyLump (LUMP_MODELS, dmodels, sizeof(dmodel_t), MAX_MAP_MODELS); + numvertexes = Q2_CopyLump (LUMP_VERTEXES, dvertexes, sizeof(dvertex_t), MAX_MAP_VERTS); + numplanes = Q2_CopyLump (LUMP_PLANES, dplanes, sizeof(dplane_t), MAX_MAP_PLANES); + numleafs = Q2_CopyLump (LUMP_LEAFS, dleafs, sizeof(dleaf_t), MAX_MAP_LEAFS); + numnodes = Q2_CopyLump (LUMP_NODES, dnodes, sizeof(dnode_t), MAX_MAP_NODES); + numtexinfo = Q2_CopyLump (LUMP_TEXINFO, texinfo, sizeof(texinfo_t), MAX_MAP_TEXINFO); + numfaces = Q2_CopyLump (LUMP_FACES, dfaces, sizeof(dface_t), MAX_MAP_FACES); + numleaffaces = Q2_CopyLump (LUMP_LEAFFACES, dleaffaces, sizeof(dleaffaces[0]), MAX_MAP_LEAFFACES); + numleafbrushes = Q2_CopyLump (LUMP_LEAFBRUSHES, dleafbrushes, sizeof(dleafbrushes[0]), MAX_MAP_LEAFBRUSHES); + numsurfedges = Q2_CopyLump (LUMP_SURFEDGES, dsurfedges, sizeof(dsurfedges[0]), MAX_MAP_SURFEDGES); + numedges = Q2_CopyLump (LUMP_EDGES, dedges, sizeof(dedge_t), MAX_MAP_EDGES); + numbrushes = Q2_CopyLump (LUMP_BRUSHES, dbrushes, sizeof(dbrush_t), MAX_MAP_BRUSHES); + numbrushsides = Q2_CopyLump (LUMP_BRUSHSIDES, dbrushsides, sizeof(dbrushside_t), MAX_MAP_BRUSHSIDES); + numareas = Q2_CopyLump (LUMP_AREAS, dareas, sizeof(darea_t), MAX_MAP_AREAS); + numareaportals = Q2_CopyLump (LUMP_AREAPORTALS, dareaportals, sizeof(dareaportal_t), MAX_MAP_AREAPORTALS); + + visdatasize = Q2_CopyLump (LUMP_VISIBILITY, dvisdata, 1, MAX_MAP_VISIBILITY); + lightdatasize = Q2_CopyLump (LUMP_LIGHTING, dlightdata, 1, MAX_MAP_LIGHTING); + entdatasize = Q2_CopyLump (LUMP_ENTITIES, dentdata, 1, MAX_MAP_ENTSTRING); + + Q2_CopyLump (LUMP_POP, dpop, 1, MAX_MAP_DPOP); + + FreeMemory(header); // everything has been copied out + +// +// swap everything +// + Q2_SwapBSPFile (false); + + Q2_FixTextureReferences(); +} //end of the function Q2_LoadBSPFile + + +/* +============= +LoadBSPFileTexinfo + +Only loads the texinfo lump, so qdata can scan for textures +============= +*/ +void Q2_LoadBSPFileTexinfo (char *filename) +{ + int i; + FILE *f; + int length, ofs; + + header = GetMemory(sizeof(dheader_t)); + + f = fopen (filename, "rb"); + fread (header, sizeof(dheader_t), 1, f); + +// swap the header + for (i=0 ; i< sizeof(dheader_t)/4 ; i++) + ((int *)header)[i] = LittleLong ( ((int *)header)[i]); + + if (header->ident != IDBSPHEADER) + Error ("%s is not a IBSP file", filename); + if (header->version != BSPVERSION) + Error ("%s is version %i, not %i", filename, header->version, BSPVERSION); + + + length = header->lumps[LUMP_TEXINFO].filelen; + ofs = header->lumps[LUMP_TEXINFO].fileofs; + + fseek (f, ofs, SEEK_SET); + fread (texinfo, length, 1, f); + fclose (f); + + numtexinfo = length / sizeof(texinfo_t); + + FreeMemory(header); // everything has been copied out + + Q2_SwapBSPFile (false); +} //end of the function Q2_LoadBSPFileTexinfo + + +//============================================================================ + +FILE *wadfile; +dheader_t outheader; + +void Q2_AddLump (int lumpnum, void *data, int len) +{ + lump_t *lump; + + lump = &header->lumps[lumpnum]; + + lump->fileofs = LittleLong( ftell(wadfile) ); + lump->filelen = LittleLong(len); + SafeWrite (wadfile, data, (len+3)&~3); +} //end of the function Q2_AddLump + +/* +============= +WriteBSPFile + +Swaps the bsp file in place, so it should not be referenced again +============= +*/ +void Q2_WriteBSPFile (char *filename) +{ + header = &outheader; + memset (header, 0, sizeof(dheader_t)); + + Q2_SwapBSPFile (true); + + header->ident = LittleLong (IDBSPHEADER); + header->version = LittleLong (BSPVERSION); + + wadfile = SafeOpenWrite (filename); + SafeWrite (wadfile, header, sizeof(dheader_t)); // overwritten later + + Q2_AddLump (LUMP_PLANES, dplanes, numplanes*sizeof(dplane_t)); + Q2_AddLump (LUMP_LEAFS, dleafs, numleafs*sizeof(dleaf_t)); + Q2_AddLump (LUMP_VERTEXES, dvertexes, numvertexes*sizeof(dvertex_t)); + Q2_AddLump (LUMP_NODES, dnodes, numnodes*sizeof(dnode_t)); + Q2_AddLump (LUMP_TEXINFO, texinfo, numtexinfo*sizeof(texinfo_t)); + Q2_AddLump (LUMP_FACES, dfaces, numfaces*sizeof(dface_t)); + Q2_AddLump (LUMP_BRUSHES, dbrushes, numbrushes*sizeof(dbrush_t)); + Q2_AddLump (LUMP_BRUSHSIDES, dbrushsides, numbrushsides*sizeof(dbrushside_t)); + Q2_AddLump (LUMP_LEAFFACES, dleaffaces, numleaffaces*sizeof(dleaffaces[0])); + Q2_AddLump (LUMP_LEAFBRUSHES, dleafbrushes, numleafbrushes*sizeof(dleafbrushes[0])); + Q2_AddLump (LUMP_SURFEDGES, dsurfedges, numsurfedges*sizeof(dsurfedges[0])); + Q2_AddLump (LUMP_EDGES, dedges, numedges*sizeof(dedge_t)); + Q2_AddLump (LUMP_MODELS, dmodels, nummodels*sizeof(dmodel_t)); + Q2_AddLump (LUMP_AREAS, dareas, numareas*sizeof(darea_t)); + Q2_AddLump (LUMP_AREAPORTALS, dareaportals, numareaportals*sizeof(dareaportal_t)); + + Q2_AddLump (LUMP_LIGHTING, dlightdata, lightdatasize); + Q2_AddLump (LUMP_VISIBILITY, dvisdata, visdatasize); + Q2_AddLump (LUMP_ENTITIES, dentdata, entdatasize); + Q2_AddLump (LUMP_POP, dpop, sizeof(dpop)); + + fseek (wadfile, 0, SEEK_SET); + SafeWrite (wadfile, header, sizeof(dheader_t)); + fclose (wadfile); +} //end of the function Q2_WriteBSPFile + +//============================================================================ + +/* +============= +PrintBSPFileSizes + +Dumps info about current file +============= +*/ +void Q2_PrintBSPFileSizes (void) +{ + if (!num_entities) + Q2_ParseEntities(); + + printf ("%6i models %7i\n" + ,nummodels, (int)(nummodels*sizeof(dmodel_t))); + printf ("%6i brushes %7i\n" + ,numbrushes, (int)(numbrushes*sizeof(dbrush_t))); + printf ("%6i brushsides %7i\n" + ,numbrushsides, (int)(numbrushsides*sizeof(dbrushside_t))); + printf ("%6i planes %7i\n" + ,numplanes, (int)(numplanes*sizeof(dplane_t))); + printf ("%6i texinfo %7i\n" + ,numtexinfo, (int)(numtexinfo*sizeof(texinfo_t))); + printf ("%6i entdata %7i\n", num_entities, entdatasize); + + printf ("\n"); + + printf ("%6i vertexes %7i\n" + ,numvertexes, (int)(numvertexes*sizeof(dvertex_t))); + printf ("%6i nodes %7i\n" + ,numnodes, (int)(numnodes*sizeof(dnode_t))); + printf ("%6i faces %7i\n" + ,numfaces, (int)(numfaces*sizeof(dface_t))); + printf ("%6i leafs %7i\n" + ,numleafs, (int)(numleafs*sizeof(dleaf_t))); + printf ("%6i leaffaces %7i\n" + ,numleaffaces, (int)(numleaffaces*sizeof(dleaffaces[0]))); + printf ("%6i leafbrushes %7i\n" + ,numleafbrushes, (int)(numleafbrushes*sizeof(dleafbrushes[0]))); + printf ("%6i surfedges %7i\n" + ,numsurfedges, (int)(numsurfedges*sizeof(dsurfedges[0]))); + printf ("%6i edges %7i\n" + ,numedges, (int)(numedges*sizeof(dedge_t))); +//NEW + printf ("%6i areas %7i\n" + ,numareas, (int)(numareas*sizeof(darea_t))); + printf ("%6i areaportals %7i\n" + ,numareaportals, (int)(numareaportals*sizeof(dareaportal_t))); +//ENDNEW + printf (" lightdata %7i\n", lightdatasize); + printf (" visdata %7i\n", visdatasize); +} //end of the function Q2_PrintBSPFileSizes + +/* +================ +ParseEntities + +Parses the dentdata string into entities +================ +*/ +void Q2_ParseEntities (void) +{ + script_t *script; + + num_entities = 0; + script = LoadScriptMemory(dentdata, entdatasize, "*Quake2 bsp file"); + SetScriptFlags(script, SCFL_NOSTRINGWHITESPACES | + SCFL_NOSTRINGESCAPECHARS); + + while(ParseEntity(script)) + { + } //end while + + FreeScript(script); +} //end of the function Q2_ParseEntities + + +/* +================ +UnparseEntities + +Generates the dentdata string from all the entities +================ +*/ +void Q2_UnparseEntities (void) +{ + char *buf, *end; + epair_t *ep; + char line[2048]; + int i; + char key[1024], value[1024]; + + buf = dentdata; + end = buf; + *end = 0; + + for (i=0 ; inext) + { + strcpy (key, ep->key); + StripTrailing (key); + strcpy (value, ep->value); + StripTrailing (value); + + sprintf (line, "\"%s\" \"%s\"\n", key, value); + strcat (end, line); + end += strlen(line); + } + strcat (end,"}\n"); + end += 2; + + if (end > buf + MAX_MAP_ENTSTRING) + Error ("Entity text too long"); + } + entdatasize = end - buf + 1; +} //end of the function Q2_UnparseEntities + diff --git a/code/bspc/l_bsp_q2.h b/code/bspc/l_bsp_q2.h index 8d937a8..f0720df 100755 --- a/code/bspc/l_bsp_q2.h +++ b/code/bspc/l_bsp_q2.h @@ -1,98 +1,98 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#ifndef ME -#define ME -#endif //ME - -extern int nummodels; -extern dmodel_t *dmodels;//[MAX_MAP_MODELS]; - -extern int visdatasize; -extern byte *dvisdata;//[MAX_MAP_VISIBILITY]; -extern dvis_t *dvis; - -extern int lightdatasize; -extern byte *dlightdata;//[MAX_MAP_LIGHTING]; - -extern int entdatasize; -extern char *dentdata;//[MAX_MAP_ENTSTRING]; - -extern int numleafs; -extern dleaf_t *dleafs;//[MAX_MAP_LEAFS]; - -extern int numplanes; -extern dplane_t *dplanes;//[MAX_MAP_PLANES]; - -extern int numvertexes; -extern dvertex_t *dvertexes;//[MAX_MAP_VERTS]; - -extern int numnodes; -extern dnode_t *dnodes;//[MAX_MAP_NODES]; - -extern int numtexinfo; -extern texinfo_t texinfo[MAX_MAP_TEXINFO]; - -extern int numfaces; -extern dface_t *dfaces;//[MAX_MAP_FACES]; - -extern int numedges; -extern dedge_t *dedges;//[MAX_MAP_EDGES]; - -extern int numleaffaces; -extern unsigned short *dleaffaces;//[MAX_MAP_LEAFFACES]; - -extern int numleafbrushes; -extern unsigned short *dleafbrushes;//[MAX_MAP_LEAFBRUSHES]; - -extern int numsurfedges; -extern int *dsurfedges;//[MAX_MAP_SURFEDGES]; - -extern int numareas; -extern darea_t *dareas;//[MAX_MAP_AREAS]; - -extern int numareaportals; -extern dareaportal_t *dareaportals;//[MAX_MAP_AREAPORTALS]; - -extern int numbrushes; -extern dbrush_t *dbrushes;//[MAX_MAP_BRUSHES]; - -extern int numbrushsides; -extern dbrushside_t *dbrushsides;//[MAX_MAP_BRUSHSIDES]; - -extern byte dpop[256]; - -extern char brushsidetextured[MAX_MAP_BRUSHSIDES]; - -void Q2_AllocMaxBSP(void); -void Q2_FreeMaxBSP(void); - -void Q2_DecompressVis(byte *in, byte *decompressed); -int Q2_CompressVis(byte *vis, byte *dest); - -void Q2_LoadBSPFile(char *filename, int offset, int length); -void Q2_LoadBSPFileTexinfo(char *filename); // just for qdata -void Q2_WriteBSPFile(char *filename); -void Q2_PrintBSPFileSizes(void); -void Q2_ParseEntities(void); -void Q2_UnparseEntities(void); - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#ifndef ME +#define ME +#endif //ME + +extern int nummodels; +extern dmodel_t *dmodels;//[MAX_MAP_MODELS]; + +extern int visdatasize; +extern byte *dvisdata;//[MAX_MAP_VISIBILITY]; +extern dvis_t *dvis; + +extern int lightdatasize; +extern byte *dlightdata;//[MAX_MAP_LIGHTING]; + +extern int entdatasize; +extern char *dentdata;//[MAX_MAP_ENTSTRING]; + +extern int numleafs; +extern dleaf_t *dleafs;//[MAX_MAP_LEAFS]; + +extern int numplanes; +extern dplane_t *dplanes;//[MAX_MAP_PLANES]; + +extern int numvertexes; +extern dvertex_t *dvertexes;//[MAX_MAP_VERTS]; + +extern int numnodes; +extern dnode_t *dnodes;//[MAX_MAP_NODES]; + +extern int numtexinfo; +extern texinfo_t texinfo[MAX_MAP_TEXINFO]; + +extern int numfaces; +extern dface_t *dfaces;//[MAX_MAP_FACES]; + +extern int numedges; +extern dedge_t *dedges;//[MAX_MAP_EDGES]; + +extern int numleaffaces; +extern unsigned short *dleaffaces;//[MAX_MAP_LEAFFACES]; + +extern int numleafbrushes; +extern unsigned short *dleafbrushes;//[MAX_MAP_LEAFBRUSHES]; + +extern int numsurfedges; +extern int *dsurfedges;//[MAX_MAP_SURFEDGES]; + +extern int numareas; +extern darea_t *dareas;//[MAX_MAP_AREAS]; + +extern int numareaportals; +extern dareaportal_t *dareaportals;//[MAX_MAP_AREAPORTALS]; + +extern int numbrushes; +extern dbrush_t *dbrushes;//[MAX_MAP_BRUSHES]; + +extern int numbrushsides; +extern dbrushside_t *dbrushsides;//[MAX_MAP_BRUSHSIDES]; + +extern byte dpop[256]; + +extern char brushsidetextured[MAX_MAP_BRUSHSIDES]; + +void Q2_AllocMaxBSP(void); +void Q2_FreeMaxBSP(void); + +void Q2_DecompressVis(byte *in, byte *decompressed); +int Q2_CompressVis(byte *vis, byte *dest); + +void Q2_LoadBSPFile(char *filename, int offset, int length); +void Q2_LoadBSPFileTexinfo(char *filename); // just for qdata +void Q2_WriteBSPFile(char *filename); +void Q2_PrintBSPFileSizes(void); +void Q2_ParseEntities(void); +void Q2_UnparseEntities(void); + diff --git a/code/bspc/l_bsp_q3.c b/code/bspc/l_bsp_q3.c index 19a3941..24bb766 100755 --- a/code/bspc/l_bsp_q3.c +++ b/code/bspc/l_bsp_q3.c @@ -1,824 +1,824 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "l_cmd.h" -#include "l_math.h" -#include "l_mem.h" -#include "l_log.h" -#include "l_poly.h" -#include "../botlib/l_script.h" -#include "l_qfiles.h" -#include "l_bsp_q3.h" -#include "l_bsp_ent.h" - -void Q3_ParseEntities (void); -void Q3_PrintBSPFileSizes(void); - -void GetLeafNums (void); - -//============================================================================= - -#define WCONVEX_EPSILON 0.5 - - -int q3_nummodels; -q3_dmodel_t *q3_dmodels;//[MAX_MAP_MODELS]; - -int q3_numShaders; -q3_dshader_t *q3_dshaders;//[Q3_MAX_MAP_SHADERS]; - -int q3_entdatasize; -char *q3_dentdata;//[Q3_MAX_MAP_ENTSTRING]; - -int q3_numleafs; -q3_dleaf_t *q3_dleafs;//[Q3_MAX_MAP_LEAFS]; - -int q3_numplanes; -q3_dplane_t *q3_dplanes;//[Q3_MAX_MAP_PLANES]; - -int q3_numnodes; -q3_dnode_t *q3_dnodes;//[Q3_MAX_MAP_NODES]; - -int q3_numleafsurfaces; -int *q3_dleafsurfaces;//[Q3_MAX_MAP_LEAFFACES]; - -int q3_numleafbrushes; -int *q3_dleafbrushes;//[Q3_MAX_MAP_LEAFBRUSHES]; - -int q3_numbrushes; -q3_dbrush_t *q3_dbrushes;//[Q3_MAX_MAP_BRUSHES]; - -int q3_numbrushsides; -q3_dbrushside_t *q3_dbrushsides;//[Q3_MAX_MAP_BRUSHSIDES]; - -int q3_numLightBytes; -byte *q3_lightBytes;//[Q3_MAX_MAP_LIGHTING]; - -int q3_numGridPoints; -byte *q3_gridData;//[Q3_MAX_MAP_LIGHTGRID]; - -int q3_numVisBytes; -byte *q3_visBytes;//[Q3_MAX_MAP_VISIBILITY]; - -int q3_numDrawVerts; -q3_drawVert_t *q3_drawVerts;//[Q3_MAX_MAP_DRAW_VERTS]; - -int q3_numDrawIndexes; -int *q3_drawIndexes;//[Q3_MAX_MAP_DRAW_INDEXES]; - -int q3_numDrawSurfaces; -q3_dsurface_t *q3_drawSurfaces;//[Q3_MAX_MAP_DRAW_SURFS]; - -int q3_numFogs; -q3_dfog_t *q3_dfogs;//[Q3_MAX_MAP_FOGS]; - -char q3_dbrushsidetextured[Q3_MAX_MAP_BRUSHSIDES]; - -extern qboolean forcesidesvisible; - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Q3_FreeMaxBSP(void) -{ - if (q3_dmodels) FreeMemory(q3_dmodels); - q3_dmodels = NULL; - q3_nummodels = 0; - if (q3_dshaders) FreeMemory(q3_dshaders); - q3_dshaders = NULL; - q3_numShaders = 0; - if (q3_dentdata) FreeMemory(q3_dentdata); - q3_dentdata = NULL; - q3_entdatasize = 0; - if (q3_dleafs) FreeMemory(q3_dleafs); - q3_dleafs = NULL; - q3_numleafs = 0; - if (q3_dplanes) FreeMemory(q3_dplanes); - q3_dplanes = NULL; - q3_numplanes = 0; - if (q3_dnodes) FreeMemory(q3_dnodes); - q3_dnodes = NULL; - q3_numnodes = 0; - if (q3_dleafsurfaces) FreeMemory(q3_dleafsurfaces); - q3_dleafsurfaces = NULL; - q3_numleafsurfaces = 0; - if (q3_dleafbrushes) FreeMemory(q3_dleafbrushes); - q3_dleafbrushes = NULL; - q3_numleafbrushes = 0; - if (q3_dbrushes) FreeMemory(q3_dbrushes); - q3_dbrushes = NULL; - q3_numbrushes = 0; - if (q3_dbrushsides) FreeMemory(q3_dbrushsides); - q3_dbrushsides = NULL; - q3_numbrushsides = 0; - if (q3_lightBytes) FreeMemory(q3_lightBytes); - q3_lightBytes = NULL; - q3_numLightBytes = 0; - if (q3_gridData) FreeMemory(q3_gridData); - q3_gridData = NULL; - q3_numGridPoints = 0; - if (q3_visBytes) FreeMemory(q3_visBytes); - q3_visBytes = NULL; - q3_numVisBytes = 0; - if (q3_drawVerts) FreeMemory(q3_drawVerts); - q3_drawVerts = NULL; - q3_numDrawVerts = 0; - if (q3_drawIndexes) FreeMemory(q3_drawIndexes); - q3_drawIndexes = NULL; - q3_numDrawIndexes = 0; - if (q3_drawSurfaces) FreeMemory(q3_drawSurfaces); - q3_drawSurfaces = NULL; - q3_numDrawSurfaces = 0; - if (q3_dfogs) FreeMemory(q3_dfogs); - q3_dfogs = NULL; - q3_numFogs = 0; -} //end of the function Q3_FreeMaxBSP - - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Q3_PlaneFromPoints(vec3_t p0, vec3_t p1, vec3_t p2, vec3_t normal, float *dist) -{ - vec3_t t1, t2; - - VectorSubtract(p0, p1, t1); - VectorSubtract(p2, p1, t2); - CrossProduct(t1, t2, normal); - VectorNormalize(normal); - - *dist = DotProduct(p0, normal); -} //end of the function PlaneFromPoints -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Q3_SurfacePlane(q3_dsurface_t *surface, vec3_t normal, float *dist) -{ - int i; - float *p0, *p1, *p2; - vec3_t t1, t2; - - p0 = q3_drawVerts[surface->firstVert].xyz; - for (i = 1; i < surface->numVerts-1; i++) - { - p1 = q3_drawVerts[surface->firstVert + ((i) % surface->numVerts)].xyz; - p2 = q3_drawVerts[surface->firstVert + ((i+1) % surface->numVerts)].xyz; - VectorSubtract(p0, p1, t1); - VectorSubtract(p2, p1, t2); - CrossProduct(t1, t2, normal); - VectorNormalize(normal); - if (VectorLength(normal)) break; - } //end for*/ -/* - float dot; - for (i = 0; i < surface->numVerts; i++) - { - p0 = q3_drawVerts[surface->firstVert + ((i) % surface->numVerts)].xyz; - p1 = q3_drawVerts[surface->firstVert + ((i+1) % surface->numVerts)].xyz; - p2 = q3_drawVerts[surface->firstVert + ((i+2) % surface->numVerts)].xyz; - VectorSubtract(p0, p1, t1); - VectorSubtract(p2, p1, t2); - VectorNormalize(t1); - VectorNormalize(t2); - dot = DotProduct(t1, t2); - if (dot > -0.9 && dot < 0.9 && - VectorLength(t1) > 0.1 && VectorLength(t2) > 0.1) break; - } //end for - CrossProduct(t1, t2, normal); - VectorNormalize(normal); -*/ - if (VectorLength(normal) < 0.9) - { - printf("surface %d bogus normal vector %f %f %f\n", surface - q3_drawSurfaces, normal[0], normal[1], normal[2]); - printf("t1 = %f %f %f, t2 = %f %f %f\n", t1[0], t1[1], t1[2], t2[0], t2[1], t2[2]); - for (i = 0; i < surface->numVerts; i++) - { - p1 = q3_drawVerts[surface->firstVert + ((i) % surface->numVerts)].xyz; - Log_Print("p%d = %f %f %f\n", i, p1[0], p1[1], p1[2]); - } //end for - } //end if - *dist = DotProduct(p0, normal); -} //end of the function Q3_SurfacePlane -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -q3_dplane_t *q3_surfaceplanes; - -void Q3_CreatePlanarSurfacePlanes(void) -{ - int i; - q3_dsurface_t *surface; - - Log_Print("creating planar surface planes...\n"); - q3_surfaceplanes = (q3_dplane_t *) GetClearedMemory(q3_numDrawSurfaces * sizeof(q3_dplane_t)); - - for (i = 0; i < q3_numDrawSurfaces; i++) - { - surface = &q3_drawSurfaces[i]; - if (surface->surfaceType != MST_PLANAR) continue; - Q3_SurfacePlane(surface, q3_surfaceplanes[i].normal, &q3_surfaceplanes[i].dist); - //Log_Print("normal = %f %f %f, dist = %f\n", q3_surfaceplanes[i].normal[0], - // q3_surfaceplanes[i].normal[1], - // q3_surfaceplanes[i].normal[2], q3_surfaceplanes[i].dist); - } //end for -} //end of the function Q3_CreatePlanarSurfacePlanes -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -/* -void Q3_SurfacePlane(q3_dsurface_t *surface, vec3_t normal, float *dist) -{ - //take the plane information from the lightmap vector - //VectorCopy(surface->lightmapVecs[2], normal); - //calculate plane dist with first surface vertex - //*dist = DotProduct(q3_drawVerts[surface->firstVert].xyz, normal); - Q3_PlaneFromPoints(q3_drawVerts[surface->firstVert].xyz, - q3_drawVerts[surface->firstVert+1].xyz, - q3_drawVerts[surface->firstVert+2].xyz, normal, dist); -} //end of the function Q3_SurfacePlane*/ -//=========================================================================== -// returns the amount the face and the winding overlap -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -float Q3_FaceOnWinding(q3_dsurface_t *surface, winding_t *winding) -{ - int i; - float dist, area; - q3_dplane_t plane; - vec_t *v1, *v2; - vec3_t normal, edgevec; - winding_t *w; - - //copy the winding before chopping - w = CopyWinding(winding); - //retrieve the surface plane - Q3_SurfacePlane(surface, plane.normal, &plane.dist); - //chop the winding with the surface edge planes - for (i = 0; i < surface->numVerts && w; i++) - { - v1 = q3_drawVerts[surface->firstVert + ((i) % surface->numVerts)].xyz; - v2 = q3_drawVerts[surface->firstVert + ((i+1) % surface->numVerts)].xyz; - //create a plane through the edge from v1 to v2, orthogonal to the - //surface plane and with the normal vector pointing inward - VectorSubtract(v2, v1, edgevec); - CrossProduct(edgevec, plane.normal, normal); - VectorNormalize(normal); - dist = DotProduct(normal, v1); - // - ChopWindingInPlace(&w, normal, dist, -0.1); //CLIP_EPSILON - } //end for - if (w) - { - area = WindingArea(w); - FreeWinding(w); - return area; - } //end if - return 0; -} //end of the function Q3_FaceOnWinding -//=========================================================================== -// creates a winding for the given brush side on the given brush -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -winding_t *Q3_BrushSideWinding(q3_dbrush_t *brush, q3_dbrushside_t *baseside) -{ - int i; - q3_dplane_t *baseplane, *plane; - winding_t *w; - q3_dbrushside_t *side; - - //create a winding for the brush side with the given planenumber - baseplane = &q3_dplanes[baseside->planeNum]; - w = BaseWindingForPlane(baseplane->normal, baseplane->dist); - for (i = 0; i < brush->numSides && w; i++) - { - side = &q3_dbrushsides[brush->firstSide + i]; - //don't chop with the base plane - if (side->planeNum == baseside->planeNum) continue; - //also don't use planes that are almost equal - plane = &q3_dplanes[side->planeNum]; - if (DotProduct(baseplane->normal, plane->normal) > 0.999 - && fabs(baseplane->dist - plane->dist) < 0.01) continue; - // - plane = &q3_dplanes[side->planeNum^1]; - ChopWindingInPlace(&w, plane->normal, plane->dist, -0.1); //CLIP_EPSILON); - } //end for - return w; -} //end of the function Q3_BrushSideWinding -//=========================================================================== -// fix screwed brush texture references -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean WindingIsTiny(winding_t *w); - -void Q3_FindVisibleBrushSides(void) -{ - int i, j, k, we, numtextured, numsides; - float dot; - q3_dplane_t *plane; - q3_dbrushside_t *brushside; - q3_dbrush_t *brush; - q3_dsurface_t *surface; - winding_t *w; - - memset(q3_dbrushsidetextured, false, Q3_MAX_MAP_BRUSHSIDES); - // - numsides = 0; - //create planes for the planar surfaces - Q3_CreatePlanarSurfacePlanes(); - Log_Print("searching visible brush sides...\n"); - Log_Print("%6d brush sides", numsides); - //go over all the brushes - for (i = 0; i < q3_numbrushes; i++) - { - brush = &q3_dbrushes[i]; - //go over all the sides of the brush - for (j = 0; j < brush->numSides; j++) - { - qprintf("\r%6d", numsides++); - brushside = &q3_dbrushsides[brush->firstSide + j]; - // - w = Q3_BrushSideWinding(brush, brushside); - if (!w) - { - q3_dbrushsidetextured[brush->firstSide + j] = true; - continue; - } //end if - else - { - //RemoveEqualPoints(w, 0.2); - if (WindingIsTiny(w)) - { - FreeWinding(w); - q3_dbrushsidetextured[brush->firstSide + j] = true; - continue; - } //end if - else - { - we = WindingError(w); - if (we == WE_NOTENOUGHPOINTS - || we == WE_SMALLAREA - || we == WE_POINTBOGUSRANGE -// || we == WE_NONCONVEX - ) - { - FreeWinding(w); - q3_dbrushsidetextured[brush->firstSide + j] = true; - continue; - } //end if - } //end else - } //end else - if (WindingArea(w) < 20) - { - q3_dbrushsidetextured[brush->firstSide + j] = true; - continue; - } //end if - //find a face for texturing this brush - for (k = 0; k < q3_numDrawSurfaces; k++) - { - surface = &q3_drawSurfaces[k]; - if (surface->surfaceType != MST_PLANAR) continue; - // - //Q3_SurfacePlane(surface, plane.normal, &plane.dist); - plane = &q3_surfaceplanes[k]; - //the surface plane and the brush side plane should be pretty much the same - if (fabs(fabs(plane->dist) - fabs(q3_dplanes[brushside->planeNum].dist)) > 5) continue; - dot = DotProduct(plane->normal, q3_dplanes[brushside->planeNum].normal); - if (dot > -0.9 && dot < 0.9) continue; - //if the face is partly or totally on the brush side - if (Q3_FaceOnWinding(surface, w)) - { - q3_dbrushsidetextured[brush->firstSide + j] = true; - //Log_Write("Q3_FaceOnWinding"); - break; - } //end if - } //end for - FreeWinding(w); - } //end for - } //end for - qprintf("\r%6d brush sides\n", numsides); - numtextured = 0; - for (i = 0; i < q3_numbrushsides; i++) - { - if (forcesidesvisible) q3_dbrushsidetextured[i] = true; - if (q3_dbrushsidetextured[i]) numtextured++; - } //end for - Log_Print("%d brush sides textured out of %d\n", numtextured, q3_numbrushsides); -} //end of the function Q3_FindVisibleBrushSides - -/* -============= -Q3_SwapBlock - -If all values are 32 bits, this can be used to swap everything -============= -*/ -void Q3_SwapBlock( int *block, int sizeOfBlock ) { - int i; - - sizeOfBlock >>= 2; - for ( i = 0 ; i < sizeOfBlock ; i++ ) { - block[i] = LittleLong( block[i] ); - } -} //end of the function Q3_SwapBlock - -/* -============= -Q3_SwapBSPFile - -Byte swaps all data in a bsp file. -============= -*/ -void Q3_SwapBSPFile( void ) { - int i; - - // models - Q3_SwapBlock( (int *)q3_dmodels, q3_nummodels * sizeof( q3_dmodels[0] ) ); - - // shaders (don't swap the name) - for ( i = 0 ; i < q3_numShaders ; i++ ) { - q3_dshaders[i].contentFlags = LittleLong( q3_dshaders[i].contentFlags ); - q3_dshaders[i].surfaceFlags = LittleLong( q3_dshaders[i].surfaceFlags ); - } - - // planes - Q3_SwapBlock( (int *)q3_dplanes, q3_numplanes * sizeof( q3_dplanes[0] ) ); - - // nodes - Q3_SwapBlock( (int *)q3_dnodes, q3_numnodes * sizeof( q3_dnodes[0] ) ); - - // leafs - Q3_SwapBlock( (int *)q3_dleafs, q3_numleafs * sizeof( q3_dleafs[0] ) ); - - // leaffaces - Q3_SwapBlock( (int *)q3_dleafsurfaces, q3_numleafsurfaces * sizeof( q3_dleafsurfaces[0] ) ); - - // leafbrushes - Q3_SwapBlock( (int *)q3_dleafbrushes, q3_numleafbrushes * sizeof( q3_dleafbrushes[0] ) ); - - // brushes - Q3_SwapBlock( (int *)q3_dbrushes, q3_numbrushes * sizeof( q3_dbrushes[0] ) ); - - // brushsides - Q3_SwapBlock( (int *)q3_dbrushsides, q3_numbrushsides * sizeof( q3_dbrushsides[0] ) ); - - // vis - ((int *)&q3_visBytes)[0] = LittleLong( ((int *)&q3_visBytes)[0] ); - ((int *)&q3_visBytes)[1] = LittleLong( ((int *)&q3_visBytes)[1] ); - - // drawverts (don't swap colors ) - for ( i = 0 ; i < q3_numDrawVerts ; i++ ) { - q3_drawVerts[i].lightmap[0] = LittleFloat( q3_drawVerts[i].lightmap[0] ); - q3_drawVerts[i].lightmap[1] = LittleFloat( q3_drawVerts[i].lightmap[1] ); - q3_drawVerts[i].st[0] = LittleFloat( q3_drawVerts[i].st[0] ); - q3_drawVerts[i].st[1] = LittleFloat( q3_drawVerts[i].st[1] ); - q3_drawVerts[i].xyz[0] = LittleFloat( q3_drawVerts[i].xyz[0] ); - q3_drawVerts[i].xyz[1] = LittleFloat( q3_drawVerts[i].xyz[1] ); - q3_drawVerts[i].xyz[2] = LittleFloat( q3_drawVerts[i].xyz[2] ); - q3_drawVerts[i].normal[0] = LittleFloat( q3_drawVerts[i].normal[0] ); - q3_drawVerts[i].normal[1] = LittleFloat( q3_drawVerts[i].normal[1] ); - q3_drawVerts[i].normal[2] = LittleFloat( q3_drawVerts[i].normal[2] ); - } - - // drawindexes - Q3_SwapBlock( (int *)q3_drawIndexes, q3_numDrawIndexes * sizeof( q3_drawIndexes[0] ) ); - - // drawsurfs - Q3_SwapBlock( (int *)q3_drawSurfaces, q3_numDrawSurfaces * sizeof( q3_drawSurfaces[0] ) ); - - // fogs - for ( i = 0 ; i < q3_numFogs ; i++ ) { - q3_dfogs[i].brushNum = LittleLong( q3_dfogs[i].brushNum ); - } -} - - - -/* -============= -Q3_CopyLump -============= -*/ -int Q3_CopyLump( q3_dheader_t *header, int lump, void **dest, int size ) { - int length, ofs; - - length = header->lumps[lump].filelen; - ofs = header->lumps[lump].fileofs; - - if ( length % size ) { - Error ("Q3_LoadBSPFile: odd lump size"); - } - - *dest = GetMemory(length); - - memcpy( *dest, (byte *)header + ofs, length ); - - return length / size; -} - -/* -============= -CountTriangles -============= -*/ -void CountTriangles( void ) { - int i, numTris, numPatchTris; - q3_dsurface_t *surface; - - numTris = numPatchTris = 0; - for ( i = 0; i < q3_numDrawSurfaces; i++ ) { - surface = &q3_drawSurfaces[i]; - - numTris += surface->numIndexes / 3; - - if ( surface->patchWidth ) { - numPatchTris += surface->patchWidth * surface->patchHeight * 2; - } - } - - Log_Print( "%6d triangles\n", numTris ); - Log_Print( "%6d patch tris\n", numPatchTris ); -} - -/* -============= -Q3_LoadBSPFile -============= -*/ -void Q3_LoadBSPFile(struct quakefile_s *qf) -{ - q3_dheader_t *header; - - // load the file header - //LoadFile(filename, (void **)&header, offset, length); - // - LoadQuakeFile(qf, (void **)&header); - - // swap the header - Q3_SwapBlock( (int *)header, sizeof(*header) ); - - if ( header->ident != Q3_BSP_IDENT ) { - Error( "%s is not a IBSP file", qf->filename ); - } - if ( header->version != Q3_BSP_VERSION ) { - Error( "%s is version %i, not %i", qf->filename, header->version, Q3_BSP_VERSION ); - } - - q3_numShaders = Q3_CopyLump( header, Q3_LUMP_SHADERS, (void *) &q3_dshaders, sizeof(q3_dshader_t) ); - q3_nummodels = Q3_CopyLump( header, Q3_LUMP_MODELS, (void *) &q3_dmodels, sizeof(q3_dmodel_t) ); - q3_numplanes = Q3_CopyLump( header, Q3_LUMP_PLANES, (void *) &q3_dplanes, sizeof(q3_dplane_t) ); - q3_numleafs = Q3_CopyLump( header, Q3_LUMP_LEAFS, (void *) &q3_dleafs, sizeof(q3_dleaf_t) ); - q3_numnodes = Q3_CopyLump( header, Q3_LUMP_NODES, (void *) &q3_dnodes, sizeof(q3_dnode_t) ); - q3_numleafsurfaces = Q3_CopyLump( header, Q3_LUMP_LEAFSURFACES, (void *) &q3_dleafsurfaces, sizeof(q3_dleafsurfaces[0]) ); - q3_numleafbrushes = Q3_CopyLump( header, Q3_LUMP_LEAFBRUSHES, (void *) &q3_dleafbrushes, sizeof(q3_dleafbrushes[0]) ); - q3_numbrushes = Q3_CopyLump( header, Q3_LUMP_BRUSHES, (void *) &q3_dbrushes, sizeof(q3_dbrush_t) ); - q3_numbrushsides = Q3_CopyLump( header, Q3_LUMP_BRUSHSIDES, (void *) &q3_dbrushsides, sizeof(q3_dbrushside_t) ); - q3_numDrawVerts = Q3_CopyLump( header, Q3_LUMP_DRAWVERTS, (void *) &q3_drawVerts, sizeof(q3_drawVert_t) ); - q3_numDrawSurfaces = Q3_CopyLump( header, Q3_LUMP_SURFACES, (void *) &q3_drawSurfaces, sizeof(q3_dsurface_t) ); - q3_numFogs = Q3_CopyLump( header, Q3_LUMP_FOGS, (void *) &q3_dfogs, sizeof(q3_dfog_t) ); - q3_numDrawIndexes = Q3_CopyLump( header, Q3_LUMP_DRAWINDEXES, (void *) &q3_drawIndexes, sizeof(q3_drawIndexes[0]) ); - - q3_numVisBytes = Q3_CopyLump( header, Q3_LUMP_VISIBILITY, (void *) &q3_visBytes, 1 ); - q3_numLightBytes = Q3_CopyLump( header, Q3_LUMP_LIGHTMAPS, (void *) &q3_lightBytes, 1 ); - q3_entdatasize = Q3_CopyLump( header, Q3_LUMP_ENTITIES, (void *) &q3_dentdata, 1); - - q3_numGridPoints = Q3_CopyLump( header, Q3_LUMP_LIGHTGRID, (void *) &q3_gridData, 8 ); - - CountTriangles(); - - FreeMemory( header ); // everything has been copied out - - // swap everything - Q3_SwapBSPFile(); - - Q3_FindVisibleBrushSides(); - - //Q3_PrintBSPFileSizes(); -} - - -//============================================================================ - -/* -============= -Q3_AddLump -============= -*/ -void Q3_AddLump( FILE *bspfile, q3_dheader_t *header, int lumpnum, void *data, int len ) { - q3_lump_t *lump; - - lump = &header->lumps[lumpnum]; - - lump->fileofs = LittleLong( ftell(bspfile) ); - lump->filelen = LittleLong( len ); - SafeWrite( bspfile, data, (len+3)&~3 ); -} - -/* -============= -Q3_WriteBSPFile - -Swaps the bsp file in place, so it should not be referenced again -============= -*/ -void Q3_WriteBSPFile( char *filename ) -{ - q3_dheader_t outheader, *header; - FILE *bspfile; - - header = &outheader; - memset( header, 0, sizeof(q3_dheader_t) ); - - Q3_SwapBSPFile(); - - header->ident = LittleLong( Q3_BSP_IDENT ); - header->version = LittleLong( Q3_BSP_VERSION ); - - bspfile = SafeOpenWrite( filename ); - SafeWrite( bspfile, header, sizeof(q3_dheader_t) ); // overwritten later - - Q3_AddLump( bspfile, header, Q3_LUMP_SHADERS, q3_dshaders, q3_numShaders*sizeof(q3_dshader_t) ); - Q3_AddLump( bspfile, header, Q3_LUMP_PLANES, q3_dplanes, q3_numplanes*sizeof(q3_dplane_t) ); - Q3_AddLump( bspfile, header, Q3_LUMP_LEAFS, q3_dleafs, q3_numleafs*sizeof(q3_dleaf_t) ); - Q3_AddLump( bspfile, header, Q3_LUMP_NODES, q3_dnodes, q3_numnodes*sizeof(q3_dnode_t) ); - Q3_AddLump( bspfile, header, Q3_LUMP_BRUSHES, q3_dbrushes, q3_numbrushes*sizeof(q3_dbrush_t) ); - Q3_AddLump( bspfile, header, Q3_LUMP_BRUSHSIDES, q3_dbrushsides, q3_numbrushsides*sizeof(q3_dbrushside_t) ); - Q3_AddLump( bspfile, header, Q3_LUMP_LEAFSURFACES, q3_dleafsurfaces, q3_numleafsurfaces*sizeof(q3_dleafsurfaces[0]) ); - Q3_AddLump( bspfile, header, Q3_LUMP_LEAFBRUSHES, q3_dleafbrushes, q3_numleafbrushes*sizeof(q3_dleafbrushes[0]) ); - Q3_AddLump( bspfile, header, Q3_LUMP_MODELS, q3_dmodels, q3_nummodels*sizeof(q3_dmodel_t) ); - Q3_AddLump( bspfile, header, Q3_LUMP_DRAWVERTS, q3_drawVerts, q3_numDrawVerts*sizeof(q3_drawVert_t) ); - Q3_AddLump( bspfile, header, Q3_LUMP_SURFACES, q3_drawSurfaces, q3_numDrawSurfaces*sizeof(q3_dsurface_t) ); - Q3_AddLump( bspfile, header, Q3_LUMP_VISIBILITY, q3_visBytes, q3_numVisBytes ); - Q3_AddLump( bspfile, header, Q3_LUMP_LIGHTMAPS, q3_lightBytes, q3_numLightBytes ); - Q3_AddLump( bspfile, header, Q3_LUMP_LIGHTGRID, q3_gridData, 8 * q3_numGridPoints ); - Q3_AddLump( bspfile, header, Q3_LUMP_ENTITIES, q3_dentdata, q3_entdatasize ); - Q3_AddLump( bspfile, header, Q3_LUMP_FOGS, q3_dfogs, q3_numFogs * sizeof(q3_dfog_t) ); - Q3_AddLump( bspfile, header, Q3_LUMP_DRAWINDEXES, q3_drawIndexes, q3_numDrawIndexes * sizeof(q3_drawIndexes[0]) ); - - fseek (bspfile, 0, SEEK_SET); - SafeWrite (bspfile, header, sizeof(q3_dheader_t)); - fclose (bspfile); -} - -//============================================================================ - -/* -============= -Q3_PrintBSPFileSizes - -Dumps info about current file -============= -*/ -void Q3_PrintBSPFileSizes( void ) -{ - if ( !num_entities ) - { - Q3_ParseEntities(); - } - - Log_Print ("%6i models %7i\n" - ,q3_nummodels, (int)(q3_nummodels*sizeof(q3_dmodel_t))); - Log_Print ("%6i shaders %7i\n" - ,q3_numShaders, (int)(q3_numShaders*sizeof(q3_dshader_t))); - Log_Print ("%6i brushes %7i\n" - ,q3_numbrushes, (int)(q3_numbrushes*sizeof(q3_dbrush_t))); - Log_Print ("%6i brushsides %7i\n" - ,q3_numbrushsides, (int)(q3_numbrushsides*sizeof(q3_dbrushside_t))); - Log_Print ("%6i fogs %7i\n" - ,q3_numFogs, (int)(q3_numFogs*sizeof(q3_dfog_t))); - Log_Print ("%6i planes %7i\n" - ,q3_numplanes, (int)(q3_numplanes*sizeof(q3_dplane_t))); - Log_Print ("%6i entdata %7i\n", num_entities, q3_entdatasize); - - Log_Print ("\n"); - - Log_Print ("%6i nodes %7i\n" - ,q3_numnodes, (int)(q3_numnodes*sizeof(q3_dnode_t))); - Log_Print ("%6i leafs %7i\n" - ,q3_numleafs, (int)(q3_numleafs*sizeof(q3_dleaf_t))); - Log_Print ("%6i leafsurfaces %7i\n" - ,q3_numleafsurfaces, (int)(q3_numleafsurfaces*sizeof(q3_dleafsurfaces[0]))); - Log_Print ("%6i leafbrushes %7i\n" - ,q3_numleafbrushes, (int)(q3_numleafbrushes*sizeof(q3_dleafbrushes[0]))); - Log_Print ("%6i drawverts %7i\n" - ,q3_numDrawVerts, (int)(q3_numDrawVerts*sizeof(q3_drawVerts[0]))); - Log_Print ("%6i drawindexes %7i\n" - ,q3_numDrawIndexes, (int)(q3_numDrawIndexes*sizeof(q3_drawIndexes[0]))); - Log_Print ("%6i drawsurfaces %7i\n" - ,q3_numDrawSurfaces, (int)(q3_numDrawSurfaces*sizeof(q3_drawSurfaces[0]))); - - Log_Print ("%6i lightmaps %7i\n" - ,q3_numLightBytes / (LIGHTMAP_WIDTH*LIGHTMAP_HEIGHT*3), q3_numLightBytes ); - Log_Print (" visibility %7i\n" - , q3_numVisBytes ); -} - -/* -================ -Q3_ParseEntities - -Parses the q3_dentdata string into entities -================ -*/ -void Q3_ParseEntities (void) -{ - script_t *script; - - num_entities = 0; - script = LoadScriptMemory(q3_dentdata, q3_entdatasize, "*Quake3 bsp file"); - SetScriptFlags(script, SCFL_NOSTRINGWHITESPACES | - SCFL_NOSTRINGESCAPECHARS); - - while(ParseEntity(script)) - { - } //end while - - FreeScript(script); -} //end of the function Q3_ParseEntities - - -/* -================ -Q3_UnparseEntities - -Generates the q3_dentdata string from all the entities -================ -*/ -void Q3_UnparseEntities (void) -{ - char *buf, *end; - epair_t *ep; - char line[2048]; - int i; - - buf = q3_dentdata; - end = buf; - *end = 0; - - for (i=0 ; inext) - { - sprintf (line, "\"%s\" \"%s\"\n", ep->key, ep->value); - strcat (end, line); - end += strlen(line); - } - strcat (end,"}\n"); - end += 2; - - if (end > buf + Q3_MAX_MAP_ENTSTRING) - Error ("Entity text too long"); - } - q3_entdatasize = end - buf + 1; -} //end of the function Q3_UnparseEntities - - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "l_cmd.h" +#include "l_math.h" +#include "l_mem.h" +#include "l_log.h" +#include "l_poly.h" +#include "../botlib/l_script.h" +#include "l_qfiles.h" +#include "l_bsp_q3.h" +#include "l_bsp_ent.h" + +void Q3_ParseEntities (void); +void Q3_PrintBSPFileSizes(void); + +void GetLeafNums (void); + +//============================================================================= + +#define WCONVEX_EPSILON 0.5 + + +int q3_nummodels; +q3_dmodel_t *q3_dmodels;//[MAX_MAP_MODELS]; + +int q3_numShaders; +q3_dshader_t *q3_dshaders;//[Q3_MAX_MAP_SHADERS]; + +int q3_entdatasize; +char *q3_dentdata;//[Q3_MAX_MAP_ENTSTRING]; + +int q3_numleafs; +q3_dleaf_t *q3_dleafs;//[Q3_MAX_MAP_LEAFS]; + +int q3_numplanes; +q3_dplane_t *q3_dplanes;//[Q3_MAX_MAP_PLANES]; + +int q3_numnodes; +q3_dnode_t *q3_dnodes;//[Q3_MAX_MAP_NODES]; + +int q3_numleafsurfaces; +int *q3_dleafsurfaces;//[Q3_MAX_MAP_LEAFFACES]; + +int q3_numleafbrushes; +int *q3_dleafbrushes;//[Q3_MAX_MAP_LEAFBRUSHES]; + +int q3_numbrushes; +q3_dbrush_t *q3_dbrushes;//[Q3_MAX_MAP_BRUSHES]; + +int q3_numbrushsides; +q3_dbrushside_t *q3_dbrushsides;//[Q3_MAX_MAP_BRUSHSIDES]; + +int q3_numLightBytes; +byte *q3_lightBytes;//[Q3_MAX_MAP_LIGHTING]; + +int q3_numGridPoints; +byte *q3_gridData;//[Q3_MAX_MAP_LIGHTGRID]; + +int q3_numVisBytes; +byte *q3_visBytes;//[Q3_MAX_MAP_VISIBILITY]; + +int q3_numDrawVerts; +q3_drawVert_t *q3_drawVerts;//[Q3_MAX_MAP_DRAW_VERTS]; + +int q3_numDrawIndexes; +int *q3_drawIndexes;//[Q3_MAX_MAP_DRAW_INDEXES]; + +int q3_numDrawSurfaces; +q3_dsurface_t *q3_drawSurfaces;//[Q3_MAX_MAP_DRAW_SURFS]; + +int q3_numFogs; +q3_dfog_t *q3_dfogs;//[Q3_MAX_MAP_FOGS]; + +char q3_dbrushsidetextured[Q3_MAX_MAP_BRUSHSIDES]; + +extern qboolean forcesidesvisible; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Q3_FreeMaxBSP(void) +{ + if (q3_dmodels) FreeMemory(q3_dmodels); + q3_dmodels = NULL; + q3_nummodels = 0; + if (q3_dshaders) FreeMemory(q3_dshaders); + q3_dshaders = NULL; + q3_numShaders = 0; + if (q3_dentdata) FreeMemory(q3_dentdata); + q3_dentdata = NULL; + q3_entdatasize = 0; + if (q3_dleafs) FreeMemory(q3_dleafs); + q3_dleafs = NULL; + q3_numleafs = 0; + if (q3_dplanes) FreeMemory(q3_dplanes); + q3_dplanes = NULL; + q3_numplanes = 0; + if (q3_dnodes) FreeMemory(q3_dnodes); + q3_dnodes = NULL; + q3_numnodes = 0; + if (q3_dleafsurfaces) FreeMemory(q3_dleafsurfaces); + q3_dleafsurfaces = NULL; + q3_numleafsurfaces = 0; + if (q3_dleafbrushes) FreeMemory(q3_dleafbrushes); + q3_dleafbrushes = NULL; + q3_numleafbrushes = 0; + if (q3_dbrushes) FreeMemory(q3_dbrushes); + q3_dbrushes = NULL; + q3_numbrushes = 0; + if (q3_dbrushsides) FreeMemory(q3_dbrushsides); + q3_dbrushsides = NULL; + q3_numbrushsides = 0; + if (q3_lightBytes) FreeMemory(q3_lightBytes); + q3_lightBytes = NULL; + q3_numLightBytes = 0; + if (q3_gridData) FreeMemory(q3_gridData); + q3_gridData = NULL; + q3_numGridPoints = 0; + if (q3_visBytes) FreeMemory(q3_visBytes); + q3_visBytes = NULL; + q3_numVisBytes = 0; + if (q3_drawVerts) FreeMemory(q3_drawVerts); + q3_drawVerts = NULL; + q3_numDrawVerts = 0; + if (q3_drawIndexes) FreeMemory(q3_drawIndexes); + q3_drawIndexes = NULL; + q3_numDrawIndexes = 0; + if (q3_drawSurfaces) FreeMemory(q3_drawSurfaces); + q3_drawSurfaces = NULL; + q3_numDrawSurfaces = 0; + if (q3_dfogs) FreeMemory(q3_dfogs); + q3_dfogs = NULL; + q3_numFogs = 0; +} //end of the function Q3_FreeMaxBSP + + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Q3_PlaneFromPoints(vec3_t p0, vec3_t p1, vec3_t p2, vec3_t normal, float *dist) +{ + vec3_t t1, t2; + + VectorSubtract(p0, p1, t1); + VectorSubtract(p2, p1, t2); + CrossProduct(t1, t2, normal); + VectorNormalize(normal); + + *dist = DotProduct(p0, normal); +} //end of the function PlaneFromPoints +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Q3_SurfacePlane(q3_dsurface_t *surface, vec3_t normal, float *dist) +{ + int i; + float *p0, *p1, *p2; + vec3_t t1, t2; + + p0 = q3_drawVerts[surface->firstVert].xyz; + for (i = 1; i < surface->numVerts-1; i++) + { + p1 = q3_drawVerts[surface->firstVert + ((i) % surface->numVerts)].xyz; + p2 = q3_drawVerts[surface->firstVert + ((i+1) % surface->numVerts)].xyz; + VectorSubtract(p0, p1, t1); + VectorSubtract(p2, p1, t2); + CrossProduct(t1, t2, normal); + VectorNormalize(normal); + if (VectorLength(normal)) break; + } //end for*/ +/* + float dot; + for (i = 0; i < surface->numVerts; i++) + { + p0 = q3_drawVerts[surface->firstVert + ((i) % surface->numVerts)].xyz; + p1 = q3_drawVerts[surface->firstVert + ((i+1) % surface->numVerts)].xyz; + p2 = q3_drawVerts[surface->firstVert + ((i+2) % surface->numVerts)].xyz; + VectorSubtract(p0, p1, t1); + VectorSubtract(p2, p1, t2); + VectorNormalize(t1); + VectorNormalize(t2); + dot = DotProduct(t1, t2); + if (dot > -0.9 && dot < 0.9 && + VectorLength(t1) > 0.1 && VectorLength(t2) > 0.1) break; + } //end for + CrossProduct(t1, t2, normal); + VectorNormalize(normal); +*/ + if (VectorLength(normal) < 0.9) + { + printf("surface %d bogus normal vector %f %f %f\n", surface - q3_drawSurfaces, normal[0], normal[1], normal[2]); + printf("t1 = %f %f %f, t2 = %f %f %f\n", t1[0], t1[1], t1[2], t2[0], t2[1], t2[2]); + for (i = 0; i < surface->numVerts; i++) + { + p1 = q3_drawVerts[surface->firstVert + ((i) % surface->numVerts)].xyz; + Log_Print("p%d = %f %f %f\n", i, p1[0], p1[1], p1[2]); + } //end for + } //end if + *dist = DotProduct(p0, normal); +} //end of the function Q3_SurfacePlane +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +q3_dplane_t *q3_surfaceplanes; + +void Q3_CreatePlanarSurfacePlanes(void) +{ + int i; + q3_dsurface_t *surface; + + Log_Print("creating planar surface planes...\n"); + q3_surfaceplanes = (q3_dplane_t *) GetClearedMemory(q3_numDrawSurfaces * sizeof(q3_dplane_t)); + + for (i = 0; i < q3_numDrawSurfaces; i++) + { + surface = &q3_drawSurfaces[i]; + if (surface->surfaceType != MST_PLANAR) continue; + Q3_SurfacePlane(surface, q3_surfaceplanes[i].normal, &q3_surfaceplanes[i].dist); + //Log_Print("normal = %f %f %f, dist = %f\n", q3_surfaceplanes[i].normal[0], + // q3_surfaceplanes[i].normal[1], + // q3_surfaceplanes[i].normal[2], q3_surfaceplanes[i].dist); + } //end for +} //end of the function Q3_CreatePlanarSurfacePlanes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +/* +void Q3_SurfacePlane(q3_dsurface_t *surface, vec3_t normal, float *dist) +{ + //take the plane information from the lightmap vector + //VectorCopy(surface->lightmapVecs[2], normal); + //calculate plane dist with first surface vertex + //*dist = DotProduct(q3_drawVerts[surface->firstVert].xyz, normal); + Q3_PlaneFromPoints(q3_drawVerts[surface->firstVert].xyz, + q3_drawVerts[surface->firstVert+1].xyz, + q3_drawVerts[surface->firstVert+2].xyz, normal, dist); +} //end of the function Q3_SurfacePlane*/ +//=========================================================================== +// returns the amount the face and the winding overlap +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float Q3_FaceOnWinding(q3_dsurface_t *surface, winding_t *winding) +{ + int i; + float dist, area; + q3_dplane_t plane; + vec_t *v1, *v2; + vec3_t normal, edgevec; + winding_t *w; + + //copy the winding before chopping + w = CopyWinding(winding); + //retrieve the surface plane + Q3_SurfacePlane(surface, plane.normal, &plane.dist); + //chop the winding with the surface edge planes + for (i = 0; i < surface->numVerts && w; i++) + { + v1 = q3_drawVerts[surface->firstVert + ((i) % surface->numVerts)].xyz; + v2 = q3_drawVerts[surface->firstVert + ((i+1) % surface->numVerts)].xyz; + //create a plane through the edge from v1 to v2, orthogonal to the + //surface plane and with the normal vector pointing inward + VectorSubtract(v2, v1, edgevec); + CrossProduct(edgevec, plane.normal, normal); + VectorNormalize(normal); + dist = DotProduct(normal, v1); + // + ChopWindingInPlace(&w, normal, dist, -0.1); //CLIP_EPSILON + } //end for + if (w) + { + area = WindingArea(w); + FreeWinding(w); + return area; + } //end if + return 0; +} //end of the function Q3_FaceOnWinding +//=========================================================================== +// creates a winding for the given brush side on the given brush +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +winding_t *Q3_BrushSideWinding(q3_dbrush_t *brush, q3_dbrushside_t *baseside) +{ + int i; + q3_dplane_t *baseplane, *plane; + winding_t *w; + q3_dbrushside_t *side; + + //create a winding for the brush side with the given planenumber + baseplane = &q3_dplanes[baseside->planeNum]; + w = BaseWindingForPlane(baseplane->normal, baseplane->dist); + for (i = 0; i < brush->numSides && w; i++) + { + side = &q3_dbrushsides[brush->firstSide + i]; + //don't chop with the base plane + if (side->planeNum == baseside->planeNum) continue; + //also don't use planes that are almost equal + plane = &q3_dplanes[side->planeNum]; + if (DotProduct(baseplane->normal, plane->normal) > 0.999 + && fabs(baseplane->dist - plane->dist) < 0.01) continue; + // + plane = &q3_dplanes[side->planeNum^1]; + ChopWindingInPlace(&w, plane->normal, plane->dist, -0.1); //CLIP_EPSILON); + } //end for + return w; +} //end of the function Q3_BrushSideWinding +//=========================================================================== +// fix screwed brush texture references +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean WindingIsTiny(winding_t *w); + +void Q3_FindVisibleBrushSides(void) +{ + int i, j, k, we, numtextured, numsides; + float dot; + q3_dplane_t *plane; + q3_dbrushside_t *brushside; + q3_dbrush_t *brush; + q3_dsurface_t *surface; + winding_t *w; + + memset(q3_dbrushsidetextured, false, Q3_MAX_MAP_BRUSHSIDES); + // + numsides = 0; + //create planes for the planar surfaces + Q3_CreatePlanarSurfacePlanes(); + Log_Print("searching visible brush sides...\n"); + Log_Print("%6d brush sides", numsides); + //go over all the brushes + for (i = 0; i < q3_numbrushes; i++) + { + brush = &q3_dbrushes[i]; + //go over all the sides of the brush + for (j = 0; j < brush->numSides; j++) + { + qprintf("\r%6d", numsides++); + brushside = &q3_dbrushsides[brush->firstSide + j]; + // + w = Q3_BrushSideWinding(brush, brushside); + if (!w) + { + q3_dbrushsidetextured[brush->firstSide + j] = true; + continue; + } //end if + else + { + //RemoveEqualPoints(w, 0.2); + if (WindingIsTiny(w)) + { + FreeWinding(w); + q3_dbrushsidetextured[brush->firstSide + j] = true; + continue; + } //end if + else + { + we = WindingError(w); + if (we == WE_NOTENOUGHPOINTS + || we == WE_SMALLAREA + || we == WE_POINTBOGUSRANGE +// || we == WE_NONCONVEX + ) + { + FreeWinding(w); + q3_dbrushsidetextured[brush->firstSide + j] = true; + continue; + } //end if + } //end else + } //end else + if (WindingArea(w) < 20) + { + q3_dbrushsidetextured[brush->firstSide + j] = true; + continue; + } //end if + //find a face for texturing this brush + for (k = 0; k < q3_numDrawSurfaces; k++) + { + surface = &q3_drawSurfaces[k]; + if (surface->surfaceType != MST_PLANAR) continue; + // + //Q3_SurfacePlane(surface, plane.normal, &plane.dist); + plane = &q3_surfaceplanes[k]; + //the surface plane and the brush side plane should be pretty much the same + if (fabs(fabs(plane->dist) - fabs(q3_dplanes[brushside->planeNum].dist)) > 5) continue; + dot = DotProduct(plane->normal, q3_dplanes[brushside->planeNum].normal); + if (dot > -0.9 && dot < 0.9) continue; + //if the face is partly or totally on the brush side + if (Q3_FaceOnWinding(surface, w)) + { + q3_dbrushsidetextured[brush->firstSide + j] = true; + //Log_Write("Q3_FaceOnWinding"); + break; + } //end if + } //end for + FreeWinding(w); + } //end for + } //end for + qprintf("\r%6d brush sides\n", numsides); + numtextured = 0; + for (i = 0; i < q3_numbrushsides; i++) + { + if (forcesidesvisible) q3_dbrushsidetextured[i] = true; + if (q3_dbrushsidetextured[i]) numtextured++; + } //end for + Log_Print("%d brush sides textured out of %d\n", numtextured, q3_numbrushsides); +} //end of the function Q3_FindVisibleBrushSides + +/* +============= +Q3_SwapBlock + +If all values are 32 bits, this can be used to swap everything +============= +*/ +void Q3_SwapBlock( int *block, int sizeOfBlock ) { + int i; + + sizeOfBlock >>= 2; + for ( i = 0 ; i < sizeOfBlock ; i++ ) { + block[i] = LittleLong( block[i] ); + } +} //end of the function Q3_SwapBlock + +/* +============= +Q3_SwapBSPFile + +Byte swaps all data in a bsp file. +============= +*/ +void Q3_SwapBSPFile( void ) { + int i; + + // models + Q3_SwapBlock( (int *)q3_dmodels, q3_nummodels * sizeof( q3_dmodels[0] ) ); + + // shaders (don't swap the name) + for ( i = 0 ; i < q3_numShaders ; i++ ) { + q3_dshaders[i].contentFlags = LittleLong( q3_dshaders[i].contentFlags ); + q3_dshaders[i].surfaceFlags = LittleLong( q3_dshaders[i].surfaceFlags ); + } + + // planes + Q3_SwapBlock( (int *)q3_dplanes, q3_numplanes * sizeof( q3_dplanes[0] ) ); + + // nodes + Q3_SwapBlock( (int *)q3_dnodes, q3_numnodes * sizeof( q3_dnodes[0] ) ); + + // leafs + Q3_SwapBlock( (int *)q3_dleafs, q3_numleafs * sizeof( q3_dleafs[0] ) ); + + // leaffaces + Q3_SwapBlock( (int *)q3_dleafsurfaces, q3_numleafsurfaces * sizeof( q3_dleafsurfaces[0] ) ); + + // leafbrushes + Q3_SwapBlock( (int *)q3_dleafbrushes, q3_numleafbrushes * sizeof( q3_dleafbrushes[0] ) ); + + // brushes + Q3_SwapBlock( (int *)q3_dbrushes, q3_numbrushes * sizeof( q3_dbrushes[0] ) ); + + // brushsides + Q3_SwapBlock( (int *)q3_dbrushsides, q3_numbrushsides * sizeof( q3_dbrushsides[0] ) ); + + // vis + ((int *)&q3_visBytes)[0] = LittleLong( ((int *)&q3_visBytes)[0] ); + ((int *)&q3_visBytes)[1] = LittleLong( ((int *)&q3_visBytes)[1] ); + + // drawverts (don't swap colors ) + for ( i = 0 ; i < q3_numDrawVerts ; i++ ) { + q3_drawVerts[i].lightmap[0] = LittleFloat( q3_drawVerts[i].lightmap[0] ); + q3_drawVerts[i].lightmap[1] = LittleFloat( q3_drawVerts[i].lightmap[1] ); + q3_drawVerts[i].st[0] = LittleFloat( q3_drawVerts[i].st[0] ); + q3_drawVerts[i].st[1] = LittleFloat( q3_drawVerts[i].st[1] ); + q3_drawVerts[i].xyz[0] = LittleFloat( q3_drawVerts[i].xyz[0] ); + q3_drawVerts[i].xyz[1] = LittleFloat( q3_drawVerts[i].xyz[1] ); + q3_drawVerts[i].xyz[2] = LittleFloat( q3_drawVerts[i].xyz[2] ); + q3_drawVerts[i].normal[0] = LittleFloat( q3_drawVerts[i].normal[0] ); + q3_drawVerts[i].normal[1] = LittleFloat( q3_drawVerts[i].normal[1] ); + q3_drawVerts[i].normal[2] = LittleFloat( q3_drawVerts[i].normal[2] ); + } + + // drawindexes + Q3_SwapBlock( (int *)q3_drawIndexes, q3_numDrawIndexes * sizeof( q3_drawIndexes[0] ) ); + + // drawsurfs + Q3_SwapBlock( (int *)q3_drawSurfaces, q3_numDrawSurfaces * sizeof( q3_drawSurfaces[0] ) ); + + // fogs + for ( i = 0 ; i < q3_numFogs ; i++ ) { + q3_dfogs[i].brushNum = LittleLong( q3_dfogs[i].brushNum ); + } +} + + + +/* +============= +Q3_CopyLump +============= +*/ +int Q3_CopyLump( q3_dheader_t *header, int lump, void **dest, int size ) { + int length, ofs; + + length = header->lumps[lump].filelen; + ofs = header->lumps[lump].fileofs; + + if ( length % size ) { + Error ("Q3_LoadBSPFile: odd lump size"); + } + + *dest = GetMemory(length); + + memcpy( *dest, (byte *)header + ofs, length ); + + return length / size; +} + +/* +============= +CountTriangles +============= +*/ +void CountTriangles( void ) { + int i, numTris, numPatchTris; + q3_dsurface_t *surface; + + numTris = numPatchTris = 0; + for ( i = 0; i < q3_numDrawSurfaces; i++ ) { + surface = &q3_drawSurfaces[i]; + + numTris += surface->numIndexes / 3; + + if ( surface->patchWidth ) { + numPatchTris += surface->patchWidth * surface->patchHeight * 2; + } + } + + Log_Print( "%6d triangles\n", numTris ); + Log_Print( "%6d patch tris\n", numPatchTris ); +} + +/* +============= +Q3_LoadBSPFile +============= +*/ +void Q3_LoadBSPFile(struct quakefile_s *qf) +{ + q3_dheader_t *header; + + // load the file header + //LoadFile(filename, (void **)&header, offset, length); + // + LoadQuakeFile(qf, (void **)&header); + + // swap the header + Q3_SwapBlock( (int *)header, sizeof(*header) ); + + if ( header->ident != Q3_BSP_IDENT ) { + Error( "%s is not a IBSP file", qf->filename ); + } + if ( header->version != Q3_BSP_VERSION ) { + Error( "%s is version %i, not %i", qf->filename, header->version, Q3_BSP_VERSION ); + } + + q3_numShaders = Q3_CopyLump( header, Q3_LUMP_SHADERS, (void *) &q3_dshaders, sizeof(q3_dshader_t) ); + q3_nummodels = Q3_CopyLump( header, Q3_LUMP_MODELS, (void *) &q3_dmodels, sizeof(q3_dmodel_t) ); + q3_numplanes = Q3_CopyLump( header, Q3_LUMP_PLANES, (void *) &q3_dplanes, sizeof(q3_dplane_t) ); + q3_numleafs = Q3_CopyLump( header, Q3_LUMP_LEAFS, (void *) &q3_dleafs, sizeof(q3_dleaf_t) ); + q3_numnodes = Q3_CopyLump( header, Q3_LUMP_NODES, (void *) &q3_dnodes, sizeof(q3_dnode_t) ); + q3_numleafsurfaces = Q3_CopyLump( header, Q3_LUMP_LEAFSURFACES, (void *) &q3_dleafsurfaces, sizeof(q3_dleafsurfaces[0]) ); + q3_numleafbrushes = Q3_CopyLump( header, Q3_LUMP_LEAFBRUSHES, (void *) &q3_dleafbrushes, sizeof(q3_dleafbrushes[0]) ); + q3_numbrushes = Q3_CopyLump( header, Q3_LUMP_BRUSHES, (void *) &q3_dbrushes, sizeof(q3_dbrush_t) ); + q3_numbrushsides = Q3_CopyLump( header, Q3_LUMP_BRUSHSIDES, (void *) &q3_dbrushsides, sizeof(q3_dbrushside_t) ); + q3_numDrawVerts = Q3_CopyLump( header, Q3_LUMP_DRAWVERTS, (void *) &q3_drawVerts, sizeof(q3_drawVert_t) ); + q3_numDrawSurfaces = Q3_CopyLump( header, Q3_LUMP_SURFACES, (void *) &q3_drawSurfaces, sizeof(q3_dsurface_t) ); + q3_numFogs = Q3_CopyLump( header, Q3_LUMP_FOGS, (void *) &q3_dfogs, sizeof(q3_dfog_t) ); + q3_numDrawIndexes = Q3_CopyLump( header, Q3_LUMP_DRAWINDEXES, (void *) &q3_drawIndexes, sizeof(q3_drawIndexes[0]) ); + + q3_numVisBytes = Q3_CopyLump( header, Q3_LUMP_VISIBILITY, (void *) &q3_visBytes, 1 ); + q3_numLightBytes = Q3_CopyLump( header, Q3_LUMP_LIGHTMAPS, (void *) &q3_lightBytes, 1 ); + q3_entdatasize = Q3_CopyLump( header, Q3_LUMP_ENTITIES, (void *) &q3_dentdata, 1); + + q3_numGridPoints = Q3_CopyLump( header, Q3_LUMP_LIGHTGRID, (void *) &q3_gridData, 8 ); + + CountTriangles(); + + FreeMemory( header ); // everything has been copied out + + // swap everything + Q3_SwapBSPFile(); + + Q3_FindVisibleBrushSides(); + + //Q3_PrintBSPFileSizes(); +} + + +//============================================================================ + +/* +============= +Q3_AddLump +============= +*/ +void Q3_AddLump( FILE *bspfile, q3_dheader_t *header, int lumpnum, void *data, int len ) { + q3_lump_t *lump; + + lump = &header->lumps[lumpnum]; + + lump->fileofs = LittleLong( ftell(bspfile) ); + lump->filelen = LittleLong( len ); + SafeWrite( bspfile, data, (len+3)&~3 ); +} + +/* +============= +Q3_WriteBSPFile + +Swaps the bsp file in place, so it should not be referenced again +============= +*/ +void Q3_WriteBSPFile( char *filename ) +{ + q3_dheader_t outheader, *header; + FILE *bspfile; + + header = &outheader; + memset( header, 0, sizeof(q3_dheader_t) ); + + Q3_SwapBSPFile(); + + header->ident = LittleLong( Q3_BSP_IDENT ); + header->version = LittleLong( Q3_BSP_VERSION ); + + bspfile = SafeOpenWrite( filename ); + SafeWrite( bspfile, header, sizeof(q3_dheader_t) ); // overwritten later + + Q3_AddLump( bspfile, header, Q3_LUMP_SHADERS, q3_dshaders, q3_numShaders*sizeof(q3_dshader_t) ); + Q3_AddLump( bspfile, header, Q3_LUMP_PLANES, q3_dplanes, q3_numplanes*sizeof(q3_dplane_t) ); + Q3_AddLump( bspfile, header, Q3_LUMP_LEAFS, q3_dleafs, q3_numleafs*sizeof(q3_dleaf_t) ); + Q3_AddLump( bspfile, header, Q3_LUMP_NODES, q3_dnodes, q3_numnodes*sizeof(q3_dnode_t) ); + Q3_AddLump( bspfile, header, Q3_LUMP_BRUSHES, q3_dbrushes, q3_numbrushes*sizeof(q3_dbrush_t) ); + Q3_AddLump( bspfile, header, Q3_LUMP_BRUSHSIDES, q3_dbrushsides, q3_numbrushsides*sizeof(q3_dbrushside_t) ); + Q3_AddLump( bspfile, header, Q3_LUMP_LEAFSURFACES, q3_dleafsurfaces, q3_numleafsurfaces*sizeof(q3_dleafsurfaces[0]) ); + Q3_AddLump( bspfile, header, Q3_LUMP_LEAFBRUSHES, q3_dleafbrushes, q3_numleafbrushes*sizeof(q3_dleafbrushes[0]) ); + Q3_AddLump( bspfile, header, Q3_LUMP_MODELS, q3_dmodels, q3_nummodels*sizeof(q3_dmodel_t) ); + Q3_AddLump( bspfile, header, Q3_LUMP_DRAWVERTS, q3_drawVerts, q3_numDrawVerts*sizeof(q3_drawVert_t) ); + Q3_AddLump( bspfile, header, Q3_LUMP_SURFACES, q3_drawSurfaces, q3_numDrawSurfaces*sizeof(q3_dsurface_t) ); + Q3_AddLump( bspfile, header, Q3_LUMP_VISIBILITY, q3_visBytes, q3_numVisBytes ); + Q3_AddLump( bspfile, header, Q3_LUMP_LIGHTMAPS, q3_lightBytes, q3_numLightBytes ); + Q3_AddLump( bspfile, header, Q3_LUMP_LIGHTGRID, q3_gridData, 8 * q3_numGridPoints ); + Q3_AddLump( bspfile, header, Q3_LUMP_ENTITIES, q3_dentdata, q3_entdatasize ); + Q3_AddLump( bspfile, header, Q3_LUMP_FOGS, q3_dfogs, q3_numFogs * sizeof(q3_dfog_t) ); + Q3_AddLump( bspfile, header, Q3_LUMP_DRAWINDEXES, q3_drawIndexes, q3_numDrawIndexes * sizeof(q3_drawIndexes[0]) ); + + fseek (bspfile, 0, SEEK_SET); + SafeWrite (bspfile, header, sizeof(q3_dheader_t)); + fclose (bspfile); +} + +//============================================================================ + +/* +============= +Q3_PrintBSPFileSizes + +Dumps info about current file +============= +*/ +void Q3_PrintBSPFileSizes( void ) +{ + if ( !num_entities ) + { + Q3_ParseEntities(); + } + + Log_Print ("%6i models %7i\n" + ,q3_nummodels, (int)(q3_nummodels*sizeof(q3_dmodel_t))); + Log_Print ("%6i shaders %7i\n" + ,q3_numShaders, (int)(q3_numShaders*sizeof(q3_dshader_t))); + Log_Print ("%6i brushes %7i\n" + ,q3_numbrushes, (int)(q3_numbrushes*sizeof(q3_dbrush_t))); + Log_Print ("%6i brushsides %7i\n" + ,q3_numbrushsides, (int)(q3_numbrushsides*sizeof(q3_dbrushside_t))); + Log_Print ("%6i fogs %7i\n" + ,q3_numFogs, (int)(q3_numFogs*sizeof(q3_dfog_t))); + Log_Print ("%6i planes %7i\n" + ,q3_numplanes, (int)(q3_numplanes*sizeof(q3_dplane_t))); + Log_Print ("%6i entdata %7i\n", num_entities, q3_entdatasize); + + Log_Print ("\n"); + + Log_Print ("%6i nodes %7i\n" + ,q3_numnodes, (int)(q3_numnodes*sizeof(q3_dnode_t))); + Log_Print ("%6i leafs %7i\n" + ,q3_numleafs, (int)(q3_numleafs*sizeof(q3_dleaf_t))); + Log_Print ("%6i leafsurfaces %7i\n" + ,q3_numleafsurfaces, (int)(q3_numleafsurfaces*sizeof(q3_dleafsurfaces[0]))); + Log_Print ("%6i leafbrushes %7i\n" + ,q3_numleafbrushes, (int)(q3_numleafbrushes*sizeof(q3_dleafbrushes[0]))); + Log_Print ("%6i drawverts %7i\n" + ,q3_numDrawVerts, (int)(q3_numDrawVerts*sizeof(q3_drawVerts[0]))); + Log_Print ("%6i drawindexes %7i\n" + ,q3_numDrawIndexes, (int)(q3_numDrawIndexes*sizeof(q3_drawIndexes[0]))); + Log_Print ("%6i drawsurfaces %7i\n" + ,q3_numDrawSurfaces, (int)(q3_numDrawSurfaces*sizeof(q3_drawSurfaces[0]))); + + Log_Print ("%6i lightmaps %7i\n" + ,q3_numLightBytes / (LIGHTMAP_WIDTH*LIGHTMAP_HEIGHT*3), q3_numLightBytes ); + Log_Print (" visibility %7i\n" + , q3_numVisBytes ); +} + +/* +================ +Q3_ParseEntities + +Parses the q3_dentdata string into entities +================ +*/ +void Q3_ParseEntities (void) +{ + script_t *script; + + num_entities = 0; + script = LoadScriptMemory(q3_dentdata, q3_entdatasize, "*Quake3 bsp file"); + SetScriptFlags(script, SCFL_NOSTRINGWHITESPACES | + SCFL_NOSTRINGESCAPECHARS); + + while(ParseEntity(script)) + { + } //end while + + FreeScript(script); +} //end of the function Q3_ParseEntities + + +/* +================ +Q3_UnparseEntities + +Generates the q3_dentdata string from all the entities +================ +*/ +void Q3_UnparseEntities (void) +{ + char *buf, *end; + epair_t *ep; + char line[2048]; + int i; + + buf = q3_dentdata; + end = buf; + *end = 0; + + for (i=0 ; inext) + { + sprintf (line, "\"%s\" \"%s\"\n", ep->key, ep->value); + strcat (end, line); + end += strlen(line); + } + strcat (end,"}\n"); + end += 2; + + if (end > buf + Q3_MAX_MAP_ENTSTRING) + Error ("Entity text too long"); + } + q3_entdatasize = end - buf + 1; +} //end of the function Q3_UnparseEntities + + diff --git a/code/bspc/l_bsp_q3.h b/code/bspc/l_bsp_q3.h index 6ac175c..3573c87 100755 --- a/code/bspc/l_bsp_q3.h +++ b/code/bspc/l_bsp_q3.h @@ -1,81 +1,81 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "q3files.h" -//#include "surfaceflags.h" - -extern int q3_nummodels; -extern q3_dmodel_t *q3_dmodels;//[MAX_MAP_MODELS]; - -extern int q3_numShaders; -extern q3_dshader_t *q3_dshaders;//[Q3_MAX_MAP_SHADERS]; - -extern int q3_entdatasize; -extern char *q3_dentdata;//[Q3_MAX_MAP_ENTSTRING]; - -extern int q3_numleafs; -extern q3_dleaf_t *q3_dleafs;//[Q3_MAX_MAP_LEAFS]; - -extern int q3_numplanes; -extern q3_dplane_t *q3_dplanes;//[Q3_MAX_MAP_PLANES]; - -extern int q3_numnodes; -extern q3_dnode_t *q3_dnodes;//[Q3_MAX_MAP_NODES]; - -extern int q3_numleafsurfaces; -extern int *q3_dleafsurfaces;//[Q3_MAX_MAP_LEAFFACES]; - -extern int q3_numleafbrushes; -extern int *q3_dleafbrushes;//[Q3_MAX_MAP_LEAFBRUSHES]; - -extern int q3_numbrushes; -extern q3_dbrush_t *q3_dbrushes;//[Q3_MAX_MAP_BRUSHES]; - -extern int q3_numbrushsides; -extern q3_dbrushside_t *q3_dbrushsides;//[Q3_MAX_MAP_BRUSHSIDES]; - -extern int q3_numLightBytes; -extern byte *q3_lightBytes;//[Q3_MAX_MAP_LIGHTING]; - -extern int q3_numGridPoints; -extern byte *q3_gridData;//[Q3_MAX_MAP_LIGHTGRID]; - -extern int q3_numVisBytes; -extern byte *q3_visBytes;//[Q3_MAX_MAP_VISIBILITY]; - -extern int q3_numDrawVerts; -extern q3_drawVert_t *q3_drawVerts;//[Q3_MAX_MAP_DRAW_VERTS]; - -extern int q3_numDrawIndexes; -extern int *q3_drawIndexes;//[Q3_MAX_MAP_DRAW_INDEXES]; - -extern int q3_numDrawSurfaces; -extern q3_dsurface_t *q3_drawSurfaces;//[Q3_MAX_MAP_DRAW_SURFS]; - -extern int q3_numFogs; -extern q3_dfog_t *q3_dfogs;//[Q3_MAX_MAP_FOGS]; - -extern char q3_dbrushsidetextured[Q3_MAX_MAP_BRUSHSIDES]; - -void Q3_LoadBSPFile(struct quakefile_s *qf); -void Q3_FreeMaxBSP(void); -void Q3_ParseEntities (void); +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "q3files.h" +//#include "surfaceflags.h" + +extern int q3_nummodels; +extern q3_dmodel_t *q3_dmodels;//[MAX_MAP_MODELS]; + +extern int q3_numShaders; +extern q3_dshader_t *q3_dshaders;//[Q3_MAX_MAP_SHADERS]; + +extern int q3_entdatasize; +extern char *q3_dentdata;//[Q3_MAX_MAP_ENTSTRING]; + +extern int q3_numleafs; +extern q3_dleaf_t *q3_dleafs;//[Q3_MAX_MAP_LEAFS]; + +extern int q3_numplanes; +extern q3_dplane_t *q3_dplanes;//[Q3_MAX_MAP_PLANES]; + +extern int q3_numnodes; +extern q3_dnode_t *q3_dnodes;//[Q3_MAX_MAP_NODES]; + +extern int q3_numleafsurfaces; +extern int *q3_dleafsurfaces;//[Q3_MAX_MAP_LEAFFACES]; + +extern int q3_numleafbrushes; +extern int *q3_dleafbrushes;//[Q3_MAX_MAP_LEAFBRUSHES]; + +extern int q3_numbrushes; +extern q3_dbrush_t *q3_dbrushes;//[Q3_MAX_MAP_BRUSHES]; + +extern int q3_numbrushsides; +extern q3_dbrushside_t *q3_dbrushsides;//[Q3_MAX_MAP_BRUSHSIDES]; + +extern int q3_numLightBytes; +extern byte *q3_lightBytes;//[Q3_MAX_MAP_LIGHTING]; + +extern int q3_numGridPoints; +extern byte *q3_gridData;//[Q3_MAX_MAP_LIGHTGRID]; + +extern int q3_numVisBytes; +extern byte *q3_visBytes;//[Q3_MAX_MAP_VISIBILITY]; + +extern int q3_numDrawVerts; +extern q3_drawVert_t *q3_drawVerts;//[Q3_MAX_MAP_DRAW_VERTS]; + +extern int q3_numDrawIndexes; +extern int *q3_drawIndexes;//[Q3_MAX_MAP_DRAW_INDEXES]; + +extern int q3_numDrawSurfaces; +extern q3_dsurface_t *q3_drawSurfaces;//[Q3_MAX_MAP_DRAW_SURFS]; + +extern int q3_numFogs; +extern q3_dfog_t *q3_dfogs;//[Q3_MAX_MAP_FOGS]; + +extern char q3_dbrushsidetextured[Q3_MAX_MAP_BRUSHSIDES]; + +void Q3_LoadBSPFile(struct quakefile_s *qf); +void Q3_FreeMaxBSP(void); +void Q3_ParseEntities (void); diff --git a/code/bspc/l_bsp_sin.c b/code/bspc/l_bsp_sin.c index de51528..2c3c223 100755 --- a/code/bspc/l_bsp_sin.c +++ b/code/bspc/l_bsp_sin.c @@ -1,1186 +1,1186 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "l_cmd.h" -#include "l_math.h" -#include "l_mem.h" -#include "l_log.h" -#include "l_poly.h" -#include "../botlib/l_script.h" -#include "l_bsp_ent.h" -#include "l_bsp_sin.h" - -void GetLeafNums (void); - -//============================================================================= - -int sin_nummodels; -sin_dmodel_t *sin_dmodels;//[SIN_MAX_MAP_MODELS]; - -int sin_visdatasize; -byte *sin_dvisdata;//[SIN_MAX_MAP_VISIBILITY]; -sin_dvis_t *sin_dvis;// = (sin_dvis_t *)sin_sin_dvisdata; - -int sin_lightdatasize; -byte *sin_dlightdata;//[SIN_MAX_MAP_LIGHTING]; - -int sin_entdatasize; -char *sin_dentdata;//[SIN_MAX_MAP_ENTSTRING]; - -int sin_numleafs; -sin_dleaf_t *sin_dleafs;//[SIN_MAX_MAP_LEAFS]; - -int sin_numplanes; -sin_dplane_t *sin_dplanes;//[SIN_MAX_MAP_PLANES]; - -int sin_numvertexes; -sin_dvertex_t *sin_dvertexes;//[SIN_MAX_MAP_VERTS]; - -int sin_numnodes; -sin_dnode_t *sin_dnodes;//[SIN_MAX_MAP_NODES]; - -int sin_numtexinfo; -sin_texinfo_t *sin_texinfo;//[SIN_MAX_MAP_sin_texinfo]; - -int sin_numfaces; -sin_dface_t *sin_dfaces;//[SIN_MAX_MAP_FACES]; - -int sin_numedges; -sin_dedge_t *sin_dedges;//[SIN_MAX_MAP_EDGES]; - -int sin_numleaffaces; -unsigned short *sin_dleaffaces;//[SIN_MAX_MAP_LEAFFACES]; - -int sin_numleafbrushes; -unsigned short *sin_dleafbrushes;//[SIN_MAX_MAP_LEAFBRUSHES]; - -int sin_numsurfedges; -int *sin_dsurfedges;//[SIN_MAX_MAP_SURFEDGES]; - -int sin_numbrushes; -sin_dbrush_t *sin_dbrushes;//[SIN_MAX_MAP_BRUSHES]; - -int sin_numbrushsides; -sin_dbrushside_t *sin_dbrushsides;//[SIN_MAX_MAP_BRUSHSIDES]; - -int sin_numareas; -sin_darea_t *sin_dareas;//[SIN_MAX_MAP_AREAS]; - -int sin_numareaportals; -sin_dareaportal_t *sin_dareaportals;//[SIN_MAX_MAP_AREAPORTALS]; - -int sin_numlightinfo; -sin_lightvalue_t *sin_lightinfo;//[SIN_MAX_MAP_LIGHTINFO]; - -byte sin_dpop[256]; - -char sin_dbrushsidetextured[SIN_MAX_MAP_BRUSHSIDES]; - -int sin_bspallocated = false; -int sin_allocatedbspmem = 0; - -void Sin_AllocMaxBSP(void) -{ - //models - sin_nummodels = 0; - sin_dmodels = (sin_dmodel_t *) GetClearedMemory(SIN_MAX_MAP_MODELS * sizeof(sin_dmodel_t)); - sin_allocatedbspmem += SIN_MAX_MAP_MODELS * sizeof(sin_dmodel_t); - //vis data - sin_visdatasize = 0; - sin_dvisdata = (byte *) GetClearedMemory(SIN_MAX_MAP_VISIBILITY * sizeof(byte)); - sin_dvis = (sin_dvis_t *) sin_dvisdata; - sin_allocatedbspmem += SIN_MAX_MAP_VISIBILITY * sizeof(byte); - //light data - sin_lightdatasize = 0; - sin_dlightdata = (byte *) GetClearedMemory(SIN_MAX_MAP_LIGHTING * sizeof(byte)); - sin_allocatedbspmem += SIN_MAX_MAP_LIGHTING * sizeof(byte); - //entity data - sin_entdatasize = 0; - sin_dentdata = (char *) GetClearedMemory(SIN_MAX_MAP_ENTSTRING * sizeof(char)); - sin_allocatedbspmem += SIN_MAX_MAP_ENTSTRING * sizeof(char); - //leafs - sin_numleafs = 0; - sin_dleafs = (sin_dleaf_t *) GetClearedMemory(SIN_MAX_MAP_LEAFS * sizeof(sin_dleaf_t)); - sin_allocatedbspmem += SIN_MAX_MAP_LEAFS * sizeof(sin_dleaf_t); - //planes - sin_numplanes = 0; - sin_dplanes = (sin_dplane_t *) GetClearedMemory(SIN_MAX_MAP_PLANES * sizeof(sin_dplane_t)); - sin_allocatedbspmem += SIN_MAX_MAP_PLANES * sizeof(sin_dplane_t); - //vertexes - sin_numvertexes = 0; - sin_dvertexes = (sin_dvertex_t *) GetClearedMemory(SIN_MAX_MAP_VERTS * sizeof(sin_dvertex_t)); - sin_allocatedbspmem += SIN_MAX_MAP_VERTS * sizeof(sin_dvertex_t); - //nodes - sin_numnodes = 0; - sin_dnodes = (sin_dnode_t *) GetClearedMemory(SIN_MAX_MAP_NODES * sizeof(sin_dnode_t)); - sin_allocatedbspmem += SIN_MAX_MAP_NODES * sizeof(sin_dnode_t); - //texture info - sin_numtexinfo = 0; - sin_texinfo = (sin_texinfo_t *) GetClearedMemory(SIN_MAX_MAP_TEXINFO * sizeof(sin_texinfo_t)); - sin_allocatedbspmem += SIN_MAX_MAP_TEXINFO * sizeof(sin_texinfo_t); - //faces - sin_numfaces = 0; - sin_dfaces = (sin_dface_t *) GetClearedMemory(SIN_MAX_MAP_FACES * sizeof(sin_dface_t)); - sin_allocatedbspmem += SIN_MAX_MAP_FACES * sizeof(sin_dface_t); - //edges - sin_numedges = 0; - sin_dedges = (sin_dedge_t *) GetClearedMemory(SIN_MAX_MAP_EDGES * sizeof(sin_dedge_t)); - sin_allocatedbspmem += SIN_MAX_MAP_EDGES * sizeof(sin_dedge_t); - //leaf faces - sin_numleaffaces = 0; - sin_dleaffaces = (unsigned short *) GetClearedMemory(SIN_MAX_MAP_LEAFFACES * sizeof(unsigned short)); - sin_allocatedbspmem += SIN_MAX_MAP_LEAFFACES * sizeof(unsigned short); - //leaf brushes - sin_numleafbrushes = 0; - sin_dleafbrushes = (unsigned short *) GetClearedMemory(SIN_MAX_MAP_LEAFBRUSHES * sizeof(unsigned short)); - sin_allocatedbspmem += SIN_MAX_MAP_LEAFBRUSHES * sizeof(unsigned short); - //surface edges - sin_numsurfedges = 0; - sin_dsurfedges = (int *) GetClearedMemory(SIN_MAX_MAP_SURFEDGES * sizeof(int)); - sin_allocatedbspmem += SIN_MAX_MAP_SURFEDGES * sizeof(int); - //brushes - sin_numbrushes = 0; - sin_dbrushes = (sin_dbrush_t *) GetClearedMemory(SIN_MAX_MAP_BRUSHES * sizeof(sin_dbrush_t)); - sin_allocatedbspmem += SIN_MAX_MAP_BRUSHES * sizeof(sin_dbrush_t); - //brushsides - sin_numbrushsides = 0; - sin_dbrushsides = (sin_dbrushside_t *) GetClearedMemory(SIN_MAX_MAP_BRUSHSIDES * sizeof(sin_dbrushside_t)); - sin_allocatedbspmem += SIN_MAX_MAP_BRUSHSIDES * sizeof(sin_dbrushside_t); - //areas - sin_numareas = 0; - sin_dareas = (sin_darea_t *) GetClearedMemory(SIN_MAX_MAP_AREAS * sizeof(sin_darea_t)); - sin_allocatedbspmem += SIN_MAX_MAP_AREAS * sizeof(sin_darea_t); - //area portals - sin_numareaportals = 0; - sin_dareaportals = (sin_dareaportal_t *) GetClearedMemory(SIN_MAX_MAP_AREAPORTALS * sizeof(sin_dareaportal_t)); - sin_allocatedbspmem += SIN_MAX_MAP_AREAPORTALS * sizeof(sin_dareaportal_t); - //light info - sin_numlightinfo = 0; - sin_lightinfo = (sin_lightvalue_t *) GetClearedMemory(SIN_MAX_MAP_LIGHTINFO * sizeof(sin_lightvalue_t)); - sin_allocatedbspmem += SIN_MAX_MAP_LIGHTINFO * sizeof(sin_lightvalue_t); - //print allocated memory - Log_Print("allocated "); - PrintMemorySize(sin_allocatedbspmem); - Log_Print(" of BSP memory\n"); -} //end of the function Sin_AllocMaxBSP - -void Sin_FreeMaxBSP(void) -{ - //models - sin_nummodels = 0; - FreeMemory(sin_dmodels); - sin_dmodels = NULL; - //vis data - sin_visdatasize = 0; - FreeMemory(sin_dvisdata); - sin_dvisdata = NULL; - sin_dvis = NULL; - //light data - sin_lightdatasize = 0; - FreeMemory(sin_dlightdata); - sin_dlightdata = NULL; - //entity data - sin_entdatasize = 0; - FreeMemory(sin_dentdata); - sin_dentdata = NULL; - //leafs - sin_numleafs = 0; - FreeMemory(sin_dleafs); - sin_dleafs = NULL; - //planes - sin_numplanes = 0; - FreeMemory(sin_dplanes); - sin_dplanes = NULL; - //vertexes - sin_numvertexes = 0; - FreeMemory(sin_dvertexes); - sin_dvertexes = NULL; - //nodes - sin_numnodes = 0; - FreeMemory(sin_dnodes); - sin_dnodes = NULL; - //texture info - sin_numtexinfo = 0; - FreeMemory(sin_texinfo); - sin_texinfo = NULL; - //faces - sin_numfaces = 0; - FreeMemory(sin_dfaces); - sin_dfaces = NULL; - //edges - sin_numedges = 0; - FreeMemory(sin_dedges); - sin_dedges = NULL; - //leaf faces - sin_numleaffaces = 0; - FreeMemory(sin_dleaffaces); - sin_dleaffaces = NULL; - //leaf brushes - sin_numleafbrushes = 0; - FreeMemory(sin_dleafbrushes); - sin_dleafbrushes = NULL; - //surface edges - sin_numsurfedges = 0; - FreeMemory(sin_dsurfedges); - sin_dsurfedges = NULL; - //brushes - sin_numbrushes = 0; - FreeMemory(sin_dbrushes); - sin_dbrushes = NULL; - //brushsides - sin_numbrushsides = 0; - FreeMemory(sin_dbrushsides); - sin_dbrushsides = NULL; - //areas - sin_numareas = 0; - FreeMemory(sin_dareas); - sin_dareas = NULL; - //area portals - sin_numareaportals = 0; - FreeMemory(sin_dareaportals); - sin_dareaportals = NULL; - //light info - sin_numlightinfo = 0; - FreeMemory(sin_lightinfo); - sin_lightinfo = NULL; - // - Log_Print("freed "); - PrintMemorySize(sin_allocatedbspmem); - Log_Print(" of BSP memory\n"); - sin_allocatedbspmem = 0; -} //end of the function Sin_FreeMaxBSP - -#define WCONVEX_EPSILON 0.5 - -//=========================================================================== -// returns the amount the face and the winding overlap -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -float Sin_FaceOnWinding(sin_dface_t *face, winding_t *winding) -{ - int i, edgenum, side; - float dist, area; - sin_dplane_t plane; - vec_t *v1, *v2; - vec3_t normal, edgevec; - winding_t *w; - - // - w = CopyWinding(winding); - memcpy(&plane, &sin_dplanes[face->planenum], sizeof(sin_dplane_t)); - //check on which side of the plane the face is - if (face->side) - { - VectorNegate(plane.normal, plane.normal); - plane.dist = -plane.dist; - } //end if - for (i = 0; i < face->numedges && w; i++) - { - //get the first and second vertex of the edge - edgenum = sin_dsurfedges[face->firstedge + i]; - side = edgenum > 0; - //if the face plane is flipped - v1 = sin_dvertexes[sin_dedges[abs(edgenum)].v[side]].point; - v2 = sin_dvertexes[sin_dedges[abs(edgenum)].v[!side]].point; - //create a plane through the edge vector, orthogonal to the face plane - //and with the normal vector pointing out of the face - VectorSubtract(v1, v2, edgevec); - CrossProduct(edgevec, plane.normal, normal); - VectorNormalize(normal); - dist = DotProduct(normal, v1); - // - ChopWindingInPlace(&w, normal, dist, 0.9); //CLIP_EPSILON - } //end for - if (w) - { - area = WindingArea(w); - FreeWinding(w); - return area; - } //end if - return 0; -} //end of the function Sin_FaceOnWinding -//=========================================================================== -// creates a winding for the given brush side on the given brush -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -winding_t *Sin_BrushSideWinding(sin_dbrush_t *brush, sin_dbrushside_t *baseside) -{ - int i; - sin_dplane_t *baseplane, *plane; - sin_dbrushside_t *side; - winding_t *w; - - //create a winding for the brush side with the given planenumber - baseplane = &sin_dplanes[baseside->planenum]; - w = BaseWindingForPlane(baseplane->normal, baseplane->dist); - for (i = 0; i < brush->numsides && w; i++) - { - side = &sin_dbrushsides[brush->firstside + i]; - //don't chop with the base plane - if (side->planenum == baseside->planenum) continue; - //also don't use planes that are almost equal - plane = &sin_dplanes[side->planenum]; - if (DotProduct(baseplane->normal, plane->normal) > 0.999 - && fabs(baseplane->dist - plane->dist) < 0.01) continue; - // - plane = &sin_dplanes[side->planenum^1]; - ChopWindingInPlace(&w, plane->normal, plane->dist, 0); //CLIP_EPSILON); - } //end for - return w; -} //end of the function Sin_BrushSideWinding -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int Sin_HintSkipBrush(sin_dbrush_t *brush) -{ - int j; - sin_dbrushside_t *brushside; - - for (j = 0; j < brush->numsides; j++) - { - brushside = &sin_dbrushsides[brush->firstside + j]; - if (brushside->texinfo > 0) - { - if (sin_texinfo[brushside->texinfo].flags & (SURF_SKIP|SURF_HINT)) - { - return true; - } //end if - } //end if - } //end for - return false; -} //end of the function Sin_HintSkipBrush -//=========================================================================== -// fix screwed brush texture references -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean WindingIsTiny(winding_t *w); - -void Sin_FixTextureReferences(void) -{ - int i, j, k, we; - sin_dbrushside_t *brushside; - sin_dbrush_t *brush; - sin_dface_t *face; - winding_t *w; - - memset(sin_dbrushsidetextured, false, SIN_MAX_MAP_BRUSHSIDES); - //go over all the brushes - for (i = 0; i < sin_numbrushes; i++) - { - brush = &sin_dbrushes[i]; - //hint brushes are not textured - if (Sin_HintSkipBrush(brush)) continue; - //go over all the sides of the brush - for (j = 0; j < brush->numsides; j++) - { - brushside = &sin_dbrushsides[brush->firstside + j]; - // - w = Sin_BrushSideWinding(brush, brushside); - if (!w) - { - sin_dbrushsidetextured[brush->firstside + j] = true; - continue; - } //end if - else - { - //RemoveEqualPoints(w, 0.2); - if (WindingIsTiny(w)) - { - FreeWinding(w); - sin_dbrushsidetextured[brush->firstside + j] = true; - continue; - } //end if - else - { - we = WindingError(w); - if (we == WE_NOTENOUGHPOINTS - || we == WE_SMALLAREA - || we == WE_POINTBOGUSRANGE -// || we == WE_NONCONVEX - ) - { - FreeWinding(w); - sin_dbrushsidetextured[brush->firstside + j] = true; - continue; - } //end if - } //end else - } //end else - if (WindingArea(w) < 20) - { - sin_dbrushsidetextured[brush->firstside + j] = true; - } //end if - //find a face for texturing this brush - for (k = 0; k < sin_numfaces; k++) - { - face = &sin_dfaces[k]; - //if the face is in the same plane as the brush side - if ((face->planenum&~1) != (brushside->planenum&~1)) continue; - //if the face is partly or totally on the brush side - if (Sin_FaceOnWinding(face, w)) - { - brushside->texinfo = face->texinfo; - sin_dbrushsidetextured[brush->firstside + j] = true; - break; - } //end if - } //end for - FreeWinding(w); - } //end for - } //end for -} //end of the function Sin_FixTextureReferences*/ - -/* -=============== -CompressVis - -=============== -*/ -int Sin_CompressVis (byte *vis, byte *dest) -{ - int j; - int rep; - int visrow; - byte *dest_p; - - dest_p = dest; -// visrow = (r_numvisleafs + 7)>>3; - visrow = (sin_dvis->numclusters + 7)>>3; - - for (j=0 ; j>3; - row = (sin_dvis->numclusters+7)>>3; - out = decompressed; - - do - { - if (*in) - { - *out++ = *in++; - continue; - } - - c = in[1]; - if (!c) - Error ("DecompressVis: 0 repeat"); - in += 2; - while (c) - { - *out++ = 0; - c--; - } - } while (out - decompressed < row); -} //end of the function Sin_DecompressVis - -//============================================================================= - -/* -============= -Sin_SwapBSPFile - -Byte swaps all data in a bsp file. -============= -*/ -void Sin_SwapBSPFile (qboolean todisk) -{ - int i, j; - sin_dmodel_t *d; - - -// models - for (i=0 ; ifirstface = LittleLong (d->firstface); - d->numfaces = LittleLong (d->numfaces); - d->headnode = LittleLong (d->headnode); - - for (j=0 ; j<3 ; j++) - { - d->mins[j] = LittleFloat(d->mins[j]); - d->maxs[j] = LittleFloat(d->maxs[j]); - d->origin[j] = LittleFloat(d->origin[j]); - } - } - -// -// vertexes -// - for (i=0 ; inumclusters; - else - j = LittleLong(sin_dvis->numclusters); - sin_dvis->numclusters = LittleLong (sin_dvis->numclusters); - for (i=0 ; ibitofs[i][0] = LittleLong (sin_dvis->bitofs[i][0]); - sin_dvis->bitofs[i][1] = LittleLong (sin_dvis->bitofs[i][1]); - } -} //end of the function Sin_SwapBSPFile - - -sin_dheader_t *header; -#ifdef SIN -int Sin_CopyLump (int lump, void *dest, int size, int maxsize) -{ - int length, ofs; - - length = header->lumps[lump].filelen; - ofs = header->lumps[lump].fileofs; - - if (length % size) - Error ("Sin_LoadBSPFile: odd lump size"); - - if ((length/size) > maxsize) - Error ("Sin_LoadBSPFile: exceeded max size for lump %d size %d > maxsize %d\n", lump, (length/size), maxsize ); - - memcpy (dest, (byte *)header + ofs, length); - - return length / size; -} -#else -int Sin_CopyLump (int lump, void *dest, int size) -{ - int length, ofs; - - length = header->lumps[lump].filelen; - ofs = header->lumps[lump].fileofs; - - if (length % size) - Error ("Sin_LoadBSPFile: odd lump size"); - - memcpy (dest, (byte *)header + ofs, length); - - return length / size; -} -#endif - -/* -============= -Sin_LoadBSPFile -============= -*/ -void Sin_LoadBSPFile(char *filename, int offset, int length) -{ - int i; - -// -// load the file header -// - LoadFile (filename, (void **)&header, offset, length); - -// swap the header - for (i=0 ; i< sizeof(sin_dheader_t)/4 ; i++) - ((int *)header)[i] = LittleLong ( ((int *)header)[i]); - - if (header->ident != SIN_BSPHEADER && header->ident != SINGAME_BSPHEADER) - Error ("%s is not a IBSP file", filename); - if (header->version != SIN_BSPVERSION && header->version != SINGAME_BSPVERSION) - Error ("%s is version %i, not %i", filename, header->version, SIN_BSPVERSION); - -#ifdef SIN - sin_nummodels = Sin_CopyLump (SIN_LUMP_MODELS, sin_dmodels, sizeof(sin_dmodel_t), SIN_MAX_MAP_MODELS); - sin_numvertexes = Sin_CopyLump (SIN_LUMP_VERTEXES, sin_dvertexes, sizeof(sin_dvertex_t), SIN_MAX_MAP_VERTS); - sin_numplanes = Sin_CopyLump (SIN_LUMP_PLANES, sin_dplanes, sizeof(sin_dplane_t), SIN_MAX_MAP_PLANES); - sin_numleafs = Sin_CopyLump (SIN_LUMP_LEAFS, sin_dleafs, sizeof(sin_dleaf_t), SIN_MAX_MAP_LEAFS); - sin_numnodes = Sin_CopyLump (SIN_LUMP_NODES, sin_dnodes, sizeof(sin_dnode_t), SIN_MAX_MAP_NODES); - sin_numtexinfo = Sin_CopyLump (SIN_LUMP_TEXINFO, sin_texinfo, sizeof(sin_texinfo_t), SIN_MAX_MAP_TEXINFO); - sin_numfaces = Sin_CopyLump (SIN_LUMP_FACES, sin_dfaces, sizeof(sin_dface_t), SIN_MAX_MAP_FACES); - sin_numleaffaces = Sin_CopyLump (SIN_LUMP_LEAFFACES, sin_dleaffaces, sizeof(sin_dleaffaces[0]), SIN_MAX_MAP_LEAFFACES); - sin_numleafbrushes = Sin_CopyLump (SIN_LUMP_LEAFBRUSHES, sin_dleafbrushes, sizeof(sin_dleafbrushes[0]), SIN_MAX_MAP_LEAFBRUSHES); - sin_numsurfedges = Sin_CopyLump (SIN_LUMP_SURFEDGES, sin_dsurfedges, sizeof(sin_dsurfedges[0]), SIN_MAX_MAP_SURFEDGES); - sin_numedges = Sin_CopyLump (SIN_LUMP_EDGES, sin_dedges, sizeof(sin_dedge_t), SIN_MAX_MAP_EDGES); - sin_numbrushes = Sin_CopyLump (SIN_LUMP_BRUSHES, sin_dbrushes, sizeof(sin_dbrush_t), SIN_MAX_MAP_BRUSHES); - sin_numbrushsides = Sin_CopyLump (SIN_LUMP_BRUSHSIDES, sin_dbrushsides, sizeof(sin_dbrushside_t), SIN_MAX_MAP_BRUSHSIDES); - sin_numareas = Sin_CopyLump (SIN_LUMP_AREAS, sin_dareas, sizeof(sin_darea_t), SIN_MAX_MAP_AREAS); - sin_numareaportals = Sin_CopyLump (SIN_LUMP_AREAPORTALS, sin_dareaportals, sizeof(sin_dareaportal_t), SIN_MAX_MAP_AREAPORTALS); - sin_numlightinfo = Sin_CopyLump (SIN_LUMP_LIGHTINFO, sin_lightinfo, sizeof(sin_lightvalue_t), SIN_MAX_MAP_LIGHTINFO); - - sin_visdatasize = Sin_CopyLump (SIN_LUMP_VISIBILITY, sin_dvisdata, 1, SIN_MAX_MAP_VISIBILITY); - sin_lightdatasize = Sin_CopyLump (SIN_LUMP_LIGHTING, sin_dlightdata, 1, SIN_MAX_MAP_LIGHTING); - sin_entdatasize = Sin_CopyLump (SIN_LUMP_ENTITIES, sin_dentdata, 1, SIN_MAX_MAP_ENTSTRING); - - Sin_CopyLump (SIN_LUMP_POP, sin_dpop, 1, sizeof(sin_dpop)); -#else - sin_nummodels = Sin_CopyLump (SIN_LUMP_MODELS, sin_dmodels, sizeof(sin_dmodel_t)); - sin_numvertexes = Sin_CopyLump (SIN_LUMP_VERTEXES, sin_dvertexes, sizeof(sin_dvertex_t)); - sin_numplanes = Sin_CopyLump (SIN_LUMP_PLANES, sin_dplanes, sizeof(sin_dplane_t)); - sin_numleafs = Sin_CopyLump (SIN_LUMP_LEAFS, sin_dleafs, sizeof(sin_dleaf_t)); - sin_numnodes = Sin_CopyLump (SIN_LUMP_NODES, sin_dnodes, sizeof(sin_dnode_t)); - sin_numtexinfo = Sin_CopyLump (SIN_LUMP_TEXINFO, sin_texinfo, sizeof(sin_texinfo_t)); - sin_numfaces = Sin_CopyLump (SIN_LUMP_FACES, sin_dfaces, sizeof(sin_dface_t)); - sin_numleaffaces = Sin_CopyLump (SIN_LUMP_LEAFFACES, sin_dleaffaces, sizeof(sin_dleaffaces[0])); - sin_numleafbrushes = Sin_CopyLump (SIN_LUMP_LEAFBRUSHES, sin_dleafbrushes, sizeof(sin_dleafbrushes[0])); - sin_numsurfedges = Sin_CopyLump (SIN_LUMP_SURFEDGES, sin_dsurfedges, sizeof(sin_dsurfedges[0])); - sin_numedges = Sin_CopyLump (SIN_LUMP_EDGES, sin_dedges, sizeof(sin_dedge_t)); - sin_numbrushes = Sin_CopyLump (SIN_LUMP_BRUSHES, sin_dbrushes, sizeof(sin_dbrush_t)); - sin_numbrushsides = Sin_CopyLump (SIN_LUMP_BRUSHSIDES, sin_dbrushsides, sizeof(sin_dbrushside_t)); - sin_numareas = Sin_CopyLump (SIN_LUMP_AREAS, sin_dareas, sizeof(sin_darea_t)); - sin_numareaportals = Sin_CopyLump (SIN_LUMP_AREAPORTALS, sin_dareaportals, sizeof(sin_dareaportal_t)); - - sin_visdatasize = Sin_CopyLump (SIN_LUMP_VISIBILITY, sin_dvisdata, 1); - sin_lightdatasize = Sin_CopyLump (SIN_LUMP_LIGHTING, sin_dlightdata, 1); - sin_entdatasize = Sin_CopyLump (SIN_LUMP_ENTITIES, sin_dentdata, 1); - - Sin_CopyLump (SIN_LUMP_POP, sin_dpop, 1); -#endif - - FreeMemory(header); // everything has been copied out - -// -// swap everything -// - Sin_SwapBSPFile (false); -} //end of the function Sin_LoadBSPFile - -/* -============= -Sin_LoadBSPFilesTexinfo - -Only loads the sin_texinfo lump, so qdata can scan for textures -============= -*/ -void Sin_LoadBSPFileTexinfo (char *filename) -{ - int i; - FILE *f; - int length, ofs; - - header = GetMemory(sizeof(sin_dheader_t)); - - f = fopen (filename, "rb"); - fread (header, sizeof(sin_dheader_t), 1, f); - -// swap the header - for (i=0 ; i< sizeof(sin_dheader_t)/4 ; i++) - ((int *)header)[i] = LittleLong ( ((int *)header)[i]); - - if (header->ident != SIN_BSPHEADER && header->ident != SINGAME_BSPHEADER) - Error ("%s is not a IBSP file", filename); - if (header->version != SIN_BSPVERSION && header->version != SINGAME_BSPVERSION) - Error ("%s is version %i, not %i", filename, header->version, SIN_BSPVERSION); - - - length = header->lumps[SIN_LUMP_TEXINFO].filelen; - ofs = header->lumps[SIN_LUMP_TEXINFO].fileofs; - - fseek (f, ofs, SEEK_SET); - fread (sin_texinfo, length, 1, f); - fclose (f); - - sin_numtexinfo = length / sizeof(sin_texinfo_t); - - FreeMemory(header); // everything has been copied out - - Sin_SwapBSPFile (false); -} //end of the function Sin_LoadBSPFilesTexinfo - - -//============================================================================ - -FILE *wadfile; -sin_dheader_t outheader; - -#ifdef SIN -void Sin_AddLump (int lumpnum, void *data, int len, int size, int maxsize) -{ - sin_lump_t *lump; - int totallength; - - totallength = len*size; - - if (len > maxsize) - Error ("Sin_WriteBSPFile: exceeded max size for lump %d size %d > maxsize %d\n", lumpnum, len, maxsize ); - - lump = &header->lumps[lumpnum]; - - lump->fileofs = LittleLong( ftell(wadfile) ); - lump->filelen = LittleLong(totallength); - SafeWrite (wadfile, data, (totallength+3)&~3); -} -#else -void Sin_AddLump (int lumpnum, void *data, int len) -{ - sin_lump_t *lump; - - lump = &header->lumps[lumpnum]; - - lump->fileofs = LittleLong( ftell(wadfile) ); - lump->filelen = LittleLong(len); - SafeWrite (wadfile, data, (len+3)&~3); -} -#endif -/* -============= -Sin_WriteBSPFile - -Swaps the bsp file in place, so it should not be referenced again -============= -*/ -void Sin_WriteBSPFile (char *filename) -{ - header = &outheader; - memset (header, 0, sizeof(sin_dheader_t)); - - Sin_SwapBSPFile (true); - - header->ident = LittleLong (SIN_BSPHEADER); - header->version = LittleLong (SIN_BSPVERSION); - - wadfile = SafeOpenWrite (filename); - SafeWrite (wadfile, header, sizeof(sin_dheader_t)); // overwritten later - -#ifdef SIN - Sin_AddLump (SIN_LUMP_PLANES, sin_dplanes, sin_numplanes, sizeof(sin_dplane_t), SIN_MAX_MAP_PLANES); - Sin_AddLump (SIN_LUMP_LEAFS, sin_dleafs, sin_numleafs, sizeof(sin_dleaf_t), SIN_MAX_MAP_LEAFS); - Sin_AddLump (SIN_LUMP_VERTEXES, sin_dvertexes, sin_numvertexes, sizeof(sin_dvertex_t), SIN_MAX_MAP_VERTS); - Sin_AddLump (SIN_LUMP_NODES, sin_dnodes, sin_numnodes, sizeof(sin_dnode_t), SIN_MAX_MAP_NODES); - Sin_AddLump (SIN_LUMP_TEXINFO, sin_texinfo, sin_numtexinfo, sizeof(sin_texinfo_t), SIN_MAX_MAP_TEXINFO); - Sin_AddLump (SIN_LUMP_FACES, sin_dfaces, sin_numfaces, sizeof(sin_dface_t), SIN_MAX_MAP_FACES); - Sin_AddLump (SIN_LUMP_BRUSHES, sin_dbrushes, sin_numbrushes, sizeof(sin_dbrush_t), SIN_MAX_MAP_BRUSHES); - Sin_AddLump (SIN_LUMP_BRUSHSIDES, sin_dbrushsides, sin_numbrushsides, sizeof(sin_dbrushside_t), SIN_MAX_MAP_BRUSHSIDES); - Sin_AddLump (SIN_LUMP_LEAFFACES, sin_dleaffaces, sin_numleaffaces, sizeof(sin_dleaffaces[0]), SIN_MAX_MAP_LEAFFACES); - Sin_AddLump (SIN_LUMP_LEAFBRUSHES, sin_dleafbrushes, sin_numleafbrushes, sizeof(sin_dleafbrushes[0]), SIN_MAX_MAP_LEAFBRUSHES); - Sin_AddLump (SIN_LUMP_SURFEDGES, sin_dsurfedges, sin_numsurfedges, sizeof(sin_dsurfedges[0]), SIN_MAX_MAP_SURFEDGES); - Sin_AddLump (SIN_LUMP_EDGES, sin_dedges, sin_numedges, sizeof(sin_dedge_t), SIN_MAX_MAP_EDGES); - Sin_AddLump (SIN_LUMP_MODELS, sin_dmodels, sin_nummodels, sizeof(sin_dmodel_t), SIN_MAX_MAP_MODELS); - Sin_AddLump (SIN_LUMP_AREAS, sin_dareas, sin_numareas, sizeof(sin_darea_t), SIN_MAX_MAP_AREAS); - Sin_AddLump (SIN_LUMP_AREAPORTALS, sin_dareaportals, sin_numareaportals, sizeof(sin_dareaportal_t), SIN_MAX_MAP_AREAPORTALS); - Sin_AddLump (SIN_LUMP_LIGHTINFO, sin_lightinfo, sin_numlightinfo, sizeof(sin_lightvalue_t), SIN_MAX_MAP_LIGHTINFO); - - Sin_AddLump (SIN_LUMP_LIGHTING, sin_dlightdata, sin_lightdatasize, 1, SIN_MAX_MAP_LIGHTING); - Sin_AddLump (SIN_LUMP_VISIBILITY, sin_dvisdata, sin_visdatasize, 1, SIN_MAX_MAP_VISIBILITY); - Sin_AddLump (SIN_LUMP_ENTITIES, sin_dentdata, sin_entdatasize, 1, SIN_MAX_MAP_ENTSTRING); - Sin_AddLump (SIN_LUMP_POP, sin_dpop, sizeof(sin_dpop), 1, sizeof(sin_dpop)); -#else - Sin_AddLump (SIN_LUMP_PLANES, sin_dplanes, sin_numplanes*sizeof(sin_dplane_t)); - Sin_AddLump (SIN_LUMP_LEAFS, sin_dleafs, sin_numleafs*sizeof(sin_dleaf_t)); - Sin_AddLump (SIN_LUMP_VERTEXES, sin_dvertexes, sin_numvertexes*sizeof(sin_dvertex_t)); - Sin_AddLump (SIN_LUMP_NODES, sin_dnodes, sin_numnodes*sizeof(sin_dnode_t)); - Sin_AddLump (SIN_LUMP_TEXINFO, sin_texinfo, sin_numtexinfo*sizeof(sin_texinfo_t)); - Sin_AddLump (SIN_LUMP_FACES, sin_dfaces, sin_numfaces*sizeof(sin_dface_t)); - Sin_AddLump (SIN_LUMP_BRUSHES, sin_dbrushes, sin_numbrushes*sizeof(sin_dbrush_t)); - Sin_AddLump (SIN_LUMP_BRUSHSIDES, sin_dbrushsides, sin_numbrushsides*sizeof(sin_dbrushside_t)); - Sin_AddLump (SIN_LUMP_LEAFFACES, sin_dleaffaces, sin_numleaffaces*sizeof(sin_dleaffaces[0])); - Sin_AddLump (SIN_LUMP_LEAFBRUSHES, sin_dleafbrushes, sin_numleafbrushes*sizeof(sin_dleafbrushes[0])); - Sin_AddLump (SIN_LUMP_SURFEDGES, sin_dsurfedges, sin_numsurfedges*sizeof(sin_dsurfedges[0])); - Sin_AddLump (SIN_LUMP_EDGES, sin_dedges, sin_numedges*sizeof(sin_dedge_t)); - Sin_AddLump (SIN_LUMP_MODELS, sin_dmodels, sin_nummodels*sizeof(sin_dmodel_t)); - Sin_AddLump (SIN_LUMP_AREAS, sin_dareas, sin_numareas*sizeof(sin_darea_t)); - Sin_AddLump (SIN_LUMP_AREAPORTALS, sin_dareaportals, sin_numareaportals*sizeof(sin_dareaportal_t)); - - Sin_AddLump (SIN_LUMP_LIGHTING, sin_dlightdata, sin_lightdatasize); - Sin_AddLump (SIN_LUMP_VISIBILITY, sin_dvisdata, sin_visdatasize); - Sin_AddLump (SIN_LUMP_ENTITIES, sin_dentdata, sin_entdatasize); - Sin_AddLump (SIN_LUMP_POP, sin_dpop, sizeof(sin_dpop)); -#endif - - fseek (wadfile, 0, SEEK_SET); - SafeWrite (wadfile, header, sizeof(sin_dheader_t)); - fclose (wadfile); -} - -//============================================================================ - - -//============================================ - -/* -================ -ParseEntities - -Parses the sin_dentdata string into entities -================ -*/ -void Sin_ParseEntities (void) -{ - script_t *script; - - num_entities = 0; - script = LoadScriptMemory(sin_dentdata, sin_entdatasize, "*sin bsp file"); - SetScriptFlags(script, SCFL_NOSTRINGWHITESPACES | - SCFL_NOSTRINGESCAPECHARS); - - while(ParseEntity(script)) - { - } //end while - - FreeScript(script); -} //end of the function Sin_ParseEntities - - -/* -================ -UnparseEntities - -Generates the sin_dentdata string from all the entities -================ -*/ -void Sin_UnparseEntities (void) -{ - char *buf, *end; - epair_t *ep; - char line[2048]; - int i; - char key[1024], value[1024]; - - buf = sin_dentdata; - end = buf; - *end = 0; - - for (i=0 ; inext) - { - strcpy (key, ep->key); - StripTrailing (key); - strcpy (value, ep->value); - StripTrailing (value); - - sprintf (line, "\"%s\" \"%s\"\n", key, value); - strcat (end, line); - end += strlen(line); - } - strcat (end,"}\n"); - end += 2; - - if (end > buf + SIN_MAX_MAP_ENTSTRING) - Error ("Entity text too long"); - } - sin_entdatasize = end - buf + 1; -} //end of the function Sin_UnparseEntities - -#ifdef SIN -void FreeValueKeys(entity_t *ent) -{ - epair_t *ep,*next; - - for (ep=ent->epairs ; ep ; ep=next) - { - next = ep->next; - FreeMemory(ep->value); - FreeMemory(ep->key); - FreeMemory(ep); - } - ent->epairs = NULL; -} -#endif - -/* -============= -Sin_PrintBSPFileSizes - -Dumps info about current file -============= -*/ -void Sin_PrintBSPFileSizes (void) -{ - if (!num_entities) - Sin_ParseEntities (); - - Log_Print("%6i models %7i\n" - ,sin_nummodels, (int)(sin_nummodels*sizeof(sin_dmodel_t))); - Log_Print("%6i brushes %7i\n" - ,sin_numbrushes, (int)(sin_numbrushes*sizeof(sin_dbrush_t))); - Log_Print("%6i brushsides %7i\n" - ,sin_numbrushsides, (int)(sin_numbrushsides*sizeof(sin_dbrushside_t))); - Log_Print("%6i planes %7i\n" - ,sin_numplanes, (int)(sin_numplanes*sizeof(sin_dplane_t))); - Log_Print("%6i texinfo %7i\n" - ,sin_numtexinfo, (int)(sin_numtexinfo*sizeof(sin_texinfo_t))); -#ifdef SIN - Log_Print("%6i lightinfo %7i\n" - ,sin_numlightinfo, (int)(sin_numlightinfo*sizeof(sin_lightvalue_t))); -#endif - Log_Print("%6i entdata %7i\n", num_entities, sin_entdatasize); - - Log_Print("\n"); - - Log_Print("%6i vertexes %7i\n" - ,sin_numvertexes, (int)(sin_numvertexes*sizeof(sin_dvertex_t))); - Log_Print("%6i nodes %7i\n" - ,sin_numnodes, (int)(sin_numnodes*sizeof(sin_dnode_t))); - Log_Print("%6i faces %7i\n" - ,sin_numfaces, (int)(sin_numfaces*sizeof(sin_dface_t))); - Log_Print("%6i leafs %7i\n" - ,sin_numleafs, (int)(sin_numleafs*sizeof(sin_dleaf_t))); - Log_Print("%6i leaffaces %7i\n" - ,sin_numleaffaces, (int)(sin_numleaffaces*sizeof(sin_dleaffaces[0]))); - Log_Print("%6i leafbrushes %7i\n" - ,sin_numleafbrushes, (int)(sin_numleafbrushes*sizeof(sin_dleafbrushes[0]))); - Log_Print("%6i surfedges %7i\n" - ,sin_numsurfedges, (int)(sin_numsurfedges*sizeof(sin_dsurfedges[0]))); - Log_Print("%6i edges %7i\n" - ,sin_numedges, (int)(sin_numedges*sizeof(sin_dedge_t))); - Log_Print(" lightdata %7i\n", sin_lightdatasize); - Log_Print(" visdata %7i\n", sin_visdatasize); -} +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "l_cmd.h" +#include "l_math.h" +#include "l_mem.h" +#include "l_log.h" +#include "l_poly.h" +#include "../botlib/l_script.h" +#include "l_bsp_ent.h" +#include "l_bsp_sin.h" + +void GetLeafNums (void); + +//============================================================================= + +int sin_nummodels; +sin_dmodel_t *sin_dmodels;//[SIN_MAX_MAP_MODELS]; + +int sin_visdatasize; +byte *sin_dvisdata;//[SIN_MAX_MAP_VISIBILITY]; +sin_dvis_t *sin_dvis;// = (sin_dvis_t *)sin_sin_dvisdata; + +int sin_lightdatasize; +byte *sin_dlightdata;//[SIN_MAX_MAP_LIGHTING]; + +int sin_entdatasize; +char *sin_dentdata;//[SIN_MAX_MAP_ENTSTRING]; + +int sin_numleafs; +sin_dleaf_t *sin_dleafs;//[SIN_MAX_MAP_LEAFS]; + +int sin_numplanes; +sin_dplane_t *sin_dplanes;//[SIN_MAX_MAP_PLANES]; + +int sin_numvertexes; +sin_dvertex_t *sin_dvertexes;//[SIN_MAX_MAP_VERTS]; + +int sin_numnodes; +sin_dnode_t *sin_dnodes;//[SIN_MAX_MAP_NODES]; + +int sin_numtexinfo; +sin_texinfo_t *sin_texinfo;//[SIN_MAX_MAP_sin_texinfo]; + +int sin_numfaces; +sin_dface_t *sin_dfaces;//[SIN_MAX_MAP_FACES]; + +int sin_numedges; +sin_dedge_t *sin_dedges;//[SIN_MAX_MAP_EDGES]; + +int sin_numleaffaces; +unsigned short *sin_dleaffaces;//[SIN_MAX_MAP_LEAFFACES]; + +int sin_numleafbrushes; +unsigned short *sin_dleafbrushes;//[SIN_MAX_MAP_LEAFBRUSHES]; + +int sin_numsurfedges; +int *sin_dsurfedges;//[SIN_MAX_MAP_SURFEDGES]; + +int sin_numbrushes; +sin_dbrush_t *sin_dbrushes;//[SIN_MAX_MAP_BRUSHES]; + +int sin_numbrushsides; +sin_dbrushside_t *sin_dbrushsides;//[SIN_MAX_MAP_BRUSHSIDES]; + +int sin_numareas; +sin_darea_t *sin_dareas;//[SIN_MAX_MAP_AREAS]; + +int sin_numareaportals; +sin_dareaportal_t *sin_dareaportals;//[SIN_MAX_MAP_AREAPORTALS]; + +int sin_numlightinfo; +sin_lightvalue_t *sin_lightinfo;//[SIN_MAX_MAP_LIGHTINFO]; + +byte sin_dpop[256]; + +char sin_dbrushsidetextured[SIN_MAX_MAP_BRUSHSIDES]; + +int sin_bspallocated = false; +int sin_allocatedbspmem = 0; + +void Sin_AllocMaxBSP(void) +{ + //models + sin_nummodels = 0; + sin_dmodels = (sin_dmodel_t *) GetClearedMemory(SIN_MAX_MAP_MODELS * sizeof(sin_dmodel_t)); + sin_allocatedbspmem += SIN_MAX_MAP_MODELS * sizeof(sin_dmodel_t); + //vis data + sin_visdatasize = 0; + sin_dvisdata = (byte *) GetClearedMemory(SIN_MAX_MAP_VISIBILITY * sizeof(byte)); + sin_dvis = (sin_dvis_t *) sin_dvisdata; + sin_allocatedbspmem += SIN_MAX_MAP_VISIBILITY * sizeof(byte); + //light data + sin_lightdatasize = 0; + sin_dlightdata = (byte *) GetClearedMemory(SIN_MAX_MAP_LIGHTING * sizeof(byte)); + sin_allocatedbspmem += SIN_MAX_MAP_LIGHTING * sizeof(byte); + //entity data + sin_entdatasize = 0; + sin_dentdata = (char *) GetClearedMemory(SIN_MAX_MAP_ENTSTRING * sizeof(char)); + sin_allocatedbspmem += SIN_MAX_MAP_ENTSTRING * sizeof(char); + //leafs + sin_numleafs = 0; + sin_dleafs = (sin_dleaf_t *) GetClearedMemory(SIN_MAX_MAP_LEAFS * sizeof(sin_dleaf_t)); + sin_allocatedbspmem += SIN_MAX_MAP_LEAFS * sizeof(sin_dleaf_t); + //planes + sin_numplanes = 0; + sin_dplanes = (sin_dplane_t *) GetClearedMemory(SIN_MAX_MAP_PLANES * sizeof(sin_dplane_t)); + sin_allocatedbspmem += SIN_MAX_MAP_PLANES * sizeof(sin_dplane_t); + //vertexes + sin_numvertexes = 0; + sin_dvertexes = (sin_dvertex_t *) GetClearedMemory(SIN_MAX_MAP_VERTS * sizeof(sin_dvertex_t)); + sin_allocatedbspmem += SIN_MAX_MAP_VERTS * sizeof(sin_dvertex_t); + //nodes + sin_numnodes = 0; + sin_dnodes = (sin_dnode_t *) GetClearedMemory(SIN_MAX_MAP_NODES * sizeof(sin_dnode_t)); + sin_allocatedbspmem += SIN_MAX_MAP_NODES * sizeof(sin_dnode_t); + //texture info + sin_numtexinfo = 0; + sin_texinfo = (sin_texinfo_t *) GetClearedMemory(SIN_MAX_MAP_TEXINFO * sizeof(sin_texinfo_t)); + sin_allocatedbspmem += SIN_MAX_MAP_TEXINFO * sizeof(sin_texinfo_t); + //faces + sin_numfaces = 0; + sin_dfaces = (sin_dface_t *) GetClearedMemory(SIN_MAX_MAP_FACES * sizeof(sin_dface_t)); + sin_allocatedbspmem += SIN_MAX_MAP_FACES * sizeof(sin_dface_t); + //edges + sin_numedges = 0; + sin_dedges = (sin_dedge_t *) GetClearedMemory(SIN_MAX_MAP_EDGES * sizeof(sin_dedge_t)); + sin_allocatedbspmem += SIN_MAX_MAP_EDGES * sizeof(sin_dedge_t); + //leaf faces + sin_numleaffaces = 0; + sin_dleaffaces = (unsigned short *) GetClearedMemory(SIN_MAX_MAP_LEAFFACES * sizeof(unsigned short)); + sin_allocatedbspmem += SIN_MAX_MAP_LEAFFACES * sizeof(unsigned short); + //leaf brushes + sin_numleafbrushes = 0; + sin_dleafbrushes = (unsigned short *) GetClearedMemory(SIN_MAX_MAP_LEAFBRUSHES * sizeof(unsigned short)); + sin_allocatedbspmem += SIN_MAX_MAP_LEAFBRUSHES * sizeof(unsigned short); + //surface edges + sin_numsurfedges = 0; + sin_dsurfedges = (int *) GetClearedMemory(SIN_MAX_MAP_SURFEDGES * sizeof(int)); + sin_allocatedbspmem += SIN_MAX_MAP_SURFEDGES * sizeof(int); + //brushes + sin_numbrushes = 0; + sin_dbrushes = (sin_dbrush_t *) GetClearedMemory(SIN_MAX_MAP_BRUSHES * sizeof(sin_dbrush_t)); + sin_allocatedbspmem += SIN_MAX_MAP_BRUSHES * sizeof(sin_dbrush_t); + //brushsides + sin_numbrushsides = 0; + sin_dbrushsides = (sin_dbrushside_t *) GetClearedMemory(SIN_MAX_MAP_BRUSHSIDES * sizeof(sin_dbrushside_t)); + sin_allocatedbspmem += SIN_MAX_MAP_BRUSHSIDES * sizeof(sin_dbrushside_t); + //areas + sin_numareas = 0; + sin_dareas = (sin_darea_t *) GetClearedMemory(SIN_MAX_MAP_AREAS * sizeof(sin_darea_t)); + sin_allocatedbspmem += SIN_MAX_MAP_AREAS * sizeof(sin_darea_t); + //area portals + sin_numareaportals = 0; + sin_dareaportals = (sin_dareaportal_t *) GetClearedMemory(SIN_MAX_MAP_AREAPORTALS * sizeof(sin_dareaportal_t)); + sin_allocatedbspmem += SIN_MAX_MAP_AREAPORTALS * sizeof(sin_dareaportal_t); + //light info + sin_numlightinfo = 0; + sin_lightinfo = (sin_lightvalue_t *) GetClearedMemory(SIN_MAX_MAP_LIGHTINFO * sizeof(sin_lightvalue_t)); + sin_allocatedbspmem += SIN_MAX_MAP_LIGHTINFO * sizeof(sin_lightvalue_t); + //print allocated memory + Log_Print("allocated "); + PrintMemorySize(sin_allocatedbspmem); + Log_Print(" of BSP memory\n"); +} //end of the function Sin_AllocMaxBSP + +void Sin_FreeMaxBSP(void) +{ + //models + sin_nummodels = 0; + FreeMemory(sin_dmodels); + sin_dmodels = NULL; + //vis data + sin_visdatasize = 0; + FreeMemory(sin_dvisdata); + sin_dvisdata = NULL; + sin_dvis = NULL; + //light data + sin_lightdatasize = 0; + FreeMemory(sin_dlightdata); + sin_dlightdata = NULL; + //entity data + sin_entdatasize = 0; + FreeMemory(sin_dentdata); + sin_dentdata = NULL; + //leafs + sin_numleafs = 0; + FreeMemory(sin_dleafs); + sin_dleafs = NULL; + //planes + sin_numplanes = 0; + FreeMemory(sin_dplanes); + sin_dplanes = NULL; + //vertexes + sin_numvertexes = 0; + FreeMemory(sin_dvertexes); + sin_dvertexes = NULL; + //nodes + sin_numnodes = 0; + FreeMemory(sin_dnodes); + sin_dnodes = NULL; + //texture info + sin_numtexinfo = 0; + FreeMemory(sin_texinfo); + sin_texinfo = NULL; + //faces + sin_numfaces = 0; + FreeMemory(sin_dfaces); + sin_dfaces = NULL; + //edges + sin_numedges = 0; + FreeMemory(sin_dedges); + sin_dedges = NULL; + //leaf faces + sin_numleaffaces = 0; + FreeMemory(sin_dleaffaces); + sin_dleaffaces = NULL; + //leaf brushes + sin_numleafbrushes = 0; + FreeMemory(sin_dleafbrushes); + sin_dleafbrushes = NULL; + //surface edges + sin_numsurfedges = 0; + FreeMemory(sin_dsurfedges); + sin_dsurfedges = NULL; + //brushes + sin_numbrushes = 0; + FreeMemory(sin_dbrushes); + sin_dbrushes = NULL; + //brushsides + sin_numbrushsides = 0; + FreeMemory(sin_dbrushsides); + sin_dbrushsides = NULL; + //areas + sin_numareas = 0; + FreeMemory(sin_dareas); + sin_dareas = NULL; + //area portals + sin_numareaportals = 0; + FreeMemory(sin_dareaportals); + sin_dareaportals = NULL; + //light info + sin_numlightinfo = 0; + FreeMemory(sin_lightinfo); + sin_lightinfo = NULL; + // + Log_Print("freed "); + PrintMemorySize(sin_allocatedbspmem); + Log_Print(" of BSP memory\n"); + sin_allocatedbspmem = 0; +} //end of the function Sin_FreeMaxBSP + +#define WCONVEX_EPSILON 0.5 + +//=========================================================================== +// returns the amount the face and the winding overlap +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float Sin_FaceOnWinding(sin_dface_t *face, winding_t *winding) +{ + int i, edgenum, side; + float dist, area; + sin_dplane_t plane; + vec_t *v1, *v2; + vec3_t normal, edgevec; + winding_t *w; + + // + w = CopyWinding(winding); + memcpy(&plane, &sin_dplanes[face->planenum], sizeof(sin_dplane_t)); + //check on which side of the plane the face is + if (face->side) + { + VectorNegate(plane.normal, plane.normal); + plane.dist = -plane.dist; + } //end if + for (i = 0; i < face->numedges && w; i++) + { + //get the first and second vertex of the edge + edgenum = sin_dsurfedges[face->firstedge + i]; + side = edgenum > 0; + //if the face plane is flipped + v1 = sin_dvertexes[sin_dedges[abs(edgenum)].v[side]].point; + v2 = sin_dvertexes[sin_dedges[abs(edgenum)].v[!side]].point; + //create a plane through the edge vector, orthogonal to the face plane + //and with the normal vector pointing out of the face + VectorSubtract(v1, v2, edgevec); + CrossProduct(edgevec, plane.normal, normal); + VectorNormalize(normal); + dist = DotProduct(normal, v1); + // + ChopWindingInPlace(&w, normal, dist, 0.9); //CLIP_EPSILON + } //end for + if (w) + { + area = WindingArea(w); + FreeWinding(w); + return area; + } //end if + return 0; +} //end of the function Sin_FaceOnWinding +//=========================================================================== +// creates a winding for the given brush side on the given brush +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +winding_t *Sin_BrushSideWinding(sin_dbrush_t *brush, sin_dbrushside_t *baseside) +{ + int i; + sin_dplane_t *baseplane, *plane; + sin_dbrushside_t *side; + winding_t *w; + + //create a winding for the brush side with the given planenumber + baseplane = &sin_dplanes[baseside->planenum]; + w = BaseWindingForPlane(baseplane->normal, baseplane->dist); + for (i = 0; i < brush->numsides && w; i++) + { + side = &sin_dbrushsides[brush->firstside + i]; + //don't chop with the base plane + if (side->planenum == baseside->planenum) continue; + //also don't use planes that are almost equal + plane = &sin_dplanes[side->planenum]; + if (DotProduct(baseplane->normal, plane->normal) > 0.999 + && fabs(baseplane->dist - plane->dist) < 0.01) continue; + // + plane = &sin_dplanes[side->planenum^1]; + ChopWindingInPlace(&w, plane->normal, plane->dist, 0); //CLIP_EPSILON); + } //end for + return w; +} //end of the function Sin_BrushSideWinding +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Sin_HintSkipBrush(sin_dbrush_t *brush) +{ + int j; + sin_dbrushside_t *brushside; + + for (j = 0; j < brush->numsides; j++) + { + brushside = &sin_dbrushsides[brush->firstside + j]; + if (brushside->texinfo > 0) + { + if (sin_texinfo[brushside->texinfo].flags & (SURF_SKIP|SURF_HINT)) + { + return true; + } //end if + } //end if + } //end for + return false; +} //end of the function Sin_HintSkipBrush +//=========================================================================== +// fix screwed brush texture references +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean WindingIsTiny(winding_t *w); + +void Sin_FixTextureReferences(void) +{ + int i, j, k, we; + sin_dbrushside_t *brushside; + sin_dbrush_t *brush; + sin_dface_t *face; + winding_t *w; + + memset(sin_dbrushsidetextured, false, SIN_MAX_MAP_BRUSHSIDES); + //go over all the brushes + for (i = 0; i < sin_numbrushes; i++) + { + brush = &sin_dbrushes[i]; + //hint brushes are not textured + if (Sin_HintSkipBrush(brush)) continue; + //go over all the sides of the brush + for (j = 0; j < brush->numsides; j++) + { + brushside = &sin_dbrushsides[brush->firstside + j]; + // + w = Sin_BrushSideWinding(brush, brushside); + if (!w) + { + sin_dbrushsidetextured[brush->firstside + j] = true; + continue; + } //end if + else + { + //RemoveEqualPoints(w, 0.2); + if (WindingIsTiny(w)) + { + FreeWinding(w); + sin_dbrushsidetextured[brush->firstside + j] = true; + continue; + } //end if + else + { + we = WindingError(w); + if (we == WE_NOTENOUGHPOINTS + || we == WE_SMALLAREA + || we == WE_POINTBOGUSRANGE +// || we == WE_NONCONVEX + ) + { + FreeWinding(w); + sin_dbrushsidetextured[brush->firstside + j] = true; + continue; + } //end if + } //end else + } //end else + if (WindingArea(w) < 20) + { + sin_dbrushsidetextured[brush->firstside + j] = true; + } //end if + //find a face for texturing this brush + for (k = 0; k < sin_numfaces; k++) + { + face = &sin_dfaces[k]; + //if the face is in the same plane as the brush side + if ((face->planenum&~1) != (brushside->planenum&~1)) continue; + //if the face is partly or totally on the brush side + if (Sin_FaceOnWinding(face, w)) + { + brushside->texinfo = face->texinfo; + sin_dbrushsidetextured[brush->firstside + j] = true; + break; + } //end if + } //end for + FreeWinding(w); + } //end for + } //end for +} //end of the function Sin_FixTextureReferences*/ + +/* +=============== +CompressVis + +=============== +*/ +int Sin_CompressVis (byte *vis, byte *dest) +{ + int j; + int rep; + int visrow; + byte *dest_p; + + dest_p = dest; +// visrow = (r_numvisleafs + 7)>>3; + visrow = (sin_dvis->numclusters + 7)>>3; + + for (j=0 ; j>3; + row = (sin_dvis->numclusters+7)>>3; + out = decompressed; + + do + { + if (*in) + { + *out++ = *in++; + continue; + } + + c = in[1]; + if (!c) + Error ("DecompressVis: 0 repeat"); + in += 2; + while (c) + { + *out++ = 0; + c--; + } + } while (out - decompressed < row); +} //end of the function Sin_DecompressVis + +//============================================================================= + +/* +============= +Sin_SwapBSPFile + +Byte swaps all data in a bsp file. +============= +*/ +void Sin_SwapBSPFile (qboolean todisk) +{ + int i, j; + sin_dmodel_t *d; + + +// models + for (i=0 ; ifirstface = LittleLong (d->firstface); + d->numfaces = LittleLong (d->numfaces); + d->headnode = LittleLong (d->headnode); + + for (j=0 ; j<3 ; j++) + { + d->mins[j] = LittleFloat(d->mins[j]); + d->maxs[j] = LittleFloat(d->maxs[j]); + d->origin[j] = LittleFloat(d->origin[j]); + } + } + +// +// vertexes +// + for (i=0 ; inumclusters; + else + j = LittleLong(sin_dvis->numclusters); + sin_dvis->numclusters = LittleLong (sin_dvis->numclusters); + for (i=0 ; ibitofs[i][0] = LittleLong (sin_dvis->bitofs[i][0]); + sin_dvis->bitofs[i][1] = LittleLong (sin_dvis->bitofs[i][1]); + } +} //end of the function Sin_SwapBSPFile + + +sin_dheader_t *header; +#ifdef SIN +int Sin_CopyLump (int lump, void *dest, int size, int maxsize) +{ + int length, ofs; + + length = header->lumps[lump].filelen; + ofs = header->lumps[lump].fileofs; + + if (length % size) + Error ("Sin_LoadBSPFile: odd lump size"); + + if ((length/size) > maxsize) + Error ("Sin_LoadBSPFile: exceeded max size for lump %d size %d > maxsize %d\n", lump, (length/size), maxsize ); + + memcpy (dest, (byte *)header + ofs, length); + + return length / size; +} +#else +int Sin_CopyLump (int lump, void *dest, int size) +{ + int length, ofs; + + length = header->lumps[lump].filelen; + ofs = header->lumps[lump].fileofs; + + if (length % size) + Error ("Sin_LoadBSPFile: odd lump size"); + + memcpy (dest, (byte *)header + ofs, length); + + return length / size; +} +#endif + +/* +============= +Sin_LoadBSPFile +============= +*/ +void Sin_LoadBSPFile(char *filename, int offset, int length) +{ + int i; + +// +// load the file header +// + LoadFile (filename, (void **)&header, offset, length); + +// swap the header + for (i=0 ; i< sizeof(sin_dheader_t)/4 ; i++) + ((int *)header)[i] = LittleLong ( ((int *)header)[i]); + + if (header->ident != SIN_BSPHEADER && header->ident != SINGAME_BSPHEADER) + Error ("%s is not a IBSP file", filename); + if (header->version != SIN_BSPVERSION && header->version != SINGAME_BSPVERSION) + Error ("%s is version %i, not %i", filename, header->version, SIN_BSPVERSION); + +#ifdef SIN + sin_nummodels = Sin_CopyLump (SIN_LUMP_MODELS, sin_dmodels, sizeof(sin_dmodel_t), SIN_MAX_MAP_MODELS); + sin_numvertexes = Sin_CopyLump (SIN_LUMP_VERTEXES, sin_dvertexes, sizeof(sin_dvertex_t), SIN_MAX_MAP_VERTS); + sin_numplanes = Sin_CopyLump (SIN_LUMP_PLANES, sin_dplanes, sizeof(sin_dplane_t), SIN_MAX_MAP_PLANES); + sin_numleafs = Sin_CopyLump (SIN_LUMP_LEAFS, sin_dleafs, sizeof(sin_dleaf_t), SIN_MAX_MAP_LEAFS); + sin_numnodes = Sin_CopyLump (SIN_LUMP_NODES, sin_dnodes, sizeof(sin_dnode_t), SIN_MAX_MAP_NODES); + sin_numtexinfo = Sin_CopyLump (SIN_LUMP_TEXINFO, sin_texinfo, sizeof(sin_texinfo_t), SIN_MAX_MAP_TEXINFO); + sin_numfaces = Sin_CopyLump (SIN_LUMP_FACES, sin_dfaces, sizeof(sin_dface_t), SIN_MAX_MAP_FACES); + sin_numleaffaces = Sin_CopyLump (SIN_LUMP_LEAFFACES, sin_dleaffaces, sizeof(sin_dleaffaces[0]), SIN_MAX_MAP_LEAFFACES); + sin_numleafbrushes = Sin_CopyLump (SIN_LUMP_LEAFBRUSHES, sin_dleafbrushes, sizeof(sin_dleafbrushes[0]), SIN_MAX_MAP_LEAFBRUSHES); + sin_numsurfedges = Sin_CopyLump (SIN_LUMP_SURFEDGES, sin_dsurfedges, sizeof(sin_dsurfedges[0]), SIN_MAX_MAP_SURFEDGES); + sin_numedges = Sin_CopyLump (SIN_LUMP_EDGES, sin_dedges, sizeof(sin_dedge_t), SIN_MAX_MAP_EDGES); + sin_numbrushes = Sin_CopyLump (SIN_LUMP_BRUSHES, sin_dbrushes, sizeof(sin_dbrush_t), SIN_MAX_MAP_BRUSHES); + sin_numbrushsides = Sin_CopyLump (SIN_LUMP_BRUSHSIDES, sin_dbrushsides, sizeof(sin_dbrushside_t), SIN_MAX_MAP_BRUSHSIDES); + sin_numareas = Sin_CopyLump (SIN_LUMP_AREAS, sin_dareas, sizeof(sin_darea_t), SIN_MAX_MAP_AREAS); + sin_numareaportals = Sin_CopyLump (SIN_LUMP_AREAPORTALS, sin_dareaportals, sizeof(sin_dareaportal_t), SIN_MAX_MAP_AREAPORTALS); + sin_numlightinfo = Sin_CopyLump (SIN_LUMP_LIGHTINFO, sin_lightinfo, sizeof(sin_lightvalue_t), SIN_MAX_MAP_LIGHTINFO); + + sin_visdatasize = Sin_CopyLump (SIN_LUMP_VISIBILITY, sin_dvisdata, 1, SIN_MAX_MAP_VISIBILITY); + sin_lightdatasize = Sin_CopyLump (SIN_LUMP_LIGHTING, sin_dlightdata, 1, SIN_MAX_MAP_LIGHTING); + sin_entdatasize = Sin_CopyLump (SIN_LUMP_ENTITIES, sin_dentdata, 1, SIN_MAX_MAP_ENTSTRING); + + Sin_CopyLump (SIN_LUMP_POP, sin_dpop, 1, sizeof(sin_dpop)); +#else + sin_nummodels = Sin_CopyLump (SIN_LUMP_MODELS, sin_dmodels, sizeof(sin_dmodel_t)); + sin_numvertexes = Sin_CopyLump (SIN_LUMP_VERTEXES, sin_dvertexes, sizeof(sin_dvertex_t)); + sin_numplanes = Sin_CopyLump (SIN_LUMP_PLANES, sin_dplanes, sizeof(sin_dplane_t)); + sin_numleafs = Sin_CopyLump (SIN_LUMP_LEAFS, sin_dleafs, sizeof(sin_dleaf_t)); + sin_numnodes = Sin_CopyLump (SIN_LUMP_NODES, sin_dnodes, sizeof(sin_dnode_t)); + sin_numtexinfo = Sin_CopyLump (SIN_LUMP_TEXINFO, sin_texinfo, sizeof(sin_texinfo_t)); + sin_numfaces = Sin_CopyLump (SIN_LUMP_FACES, sin_dfaces, sizeof(sin_dface_t)); + sin_numleaffaces = Sin_CopyLump (SIN_LUMP_LEAFFACES, sin_dleaffaces, sizeof(sin_dleaffaces[0])); + sin_numleafbrushes = Sin_CopyLump (SIN_LUMP_LEAFBRUSHES, sin_dleafbrushes, sizeof(sin_dleafbrushes[0])); + sin_numsurfedges = Sin_CopyLump (SIN_LUMP_SURFEDGES, sin_dsurfedges, sizeof(sin_dsurfedges[0])); + sin_numedges = Sin_CopyLump (SIN_LUMP_EDGES, sin_dedges, sizeof(sin_dedge_t)); + sin_numbrushes = Sin_CopyLump (SIN_LUMP_BRUSHES, sin_dbrushes, sizeof(sin_dbrush_t)); + sin_numbrushsides = Sin_CopyLump (SIN_LUMP_BRUSHSIDES, sin_dbrushsides, sizeof(sin_dbrushside_t)); + sin_numareas = Sin_CopyLump (SIN_LUMP_AREAS, sin_dareas, sizeof(sin_darea_t)); + sin_numareaportals = Sin_CopyLump (SIN_LUMP_AREAPORTALS, sin_dareaportals, sizeof(sin_dareaportal_t)); + + sin_visdatasize = Sin_CopyLump (SIN_LUMP_VISIBILITY, sin_dvisdata, 1); + sin_lightdatasize = Sin_CopyLump (SIN_LUMP_LIGHTING, sin_dlightdata, 1); + sin_entdatasize = Sin_CopyLump (SIN_LUMP_ENTITIES, sin_dentdata, 1); + + Sin_CopyLump (SIN_LUMP_POP, sin_dpop, 1); +#endif + + FreeMemory(header); // everything has been copied out + +// +// swap everything +// + Sin_SwapBSPFile (false); +} //end of the function Sin_LoadBSPFile + +/* +============= +Sin_LoadBSPFilesTexinfo + +Only loads the sin_texinfo lump, so qdata can scan for textures +============= +*/ +void Sin_LoadBSPFileTexinfo (char *filename) +{ + int i; + FILE *f; + int length, ofs; + + header = GetMemory(sizeof(sin_dheader_t)); + + f = fopen (filename, "rb"); + fread (header, sizeof(sin_dheader_t), 1, f); + +// swap the header + for (i=0 ; i< sizeof(sin_dheader_t)/4 ; i++) + ((int *)header)[i] = LittleLong ( ((int *)header)[i]); + + if (header->ident != SIN_BSPHEADER && header->ident != SINGAME_BSPHEADER) + Error ("%s is not a IBSP file", filename); + if (header->version != SIN_BSPVERSION && header->version != SINGAME_BSPVERSION) + Error ("%s is version %i, not %i", filename, header->version, SIN_BSPVERSION); + + + length = header->lumps[SIN_LUMP_TEXINFO].filelen; + ofs = header->lumps[SIN_LUMP_TEXINFO].fileofs; + + fseek (f, ofs, SEEK_SET); + fread (sin_texinfo, length, 1, f); + fclose (f); + + sin_numtexinfo = length / sizeof(sin_texinfo_t); + + FreeMemory(header); // everything has been copied out + + Sin_SwapBSPFile (false); +} //end of the function Sin_LoadBSPFilesTexinfo + + +//============================================================================ + +FILE *wadfile; +sin_dheader_t outheader; + +#ifdef SIN +void Sin_AddLump (int lumpnum, void *data, int len, int size, int maxsize) +{ + sin_lump_t *lump; + int totallength; + + totallength = len*size; + + if (len > maxsize) + Error ("Sin_WriteBSPFile: exceeded max size for lump %d size %d > maxsize %d\n", lumpnum, len, maxsize ); + + lump = &header->lumps[lumpnum]; + + lump->fileofs = LittleLong( ftell(wadfile) ); + lump->filelen = LittleLong(totallength); + SafeWrite (wadfile, data, (totallength+3)&~3); +} +#else +void Sin_AddLump (int lumpnum, void *data, int len) +{ + sin_lump_t *lump; + + lump = &header->lumps[lumpnum]; + + lump->fileofs = LittleLong( ftell(wadfile) ); + lump->filelen = LittleLong(len); + SafeWrite (wadfile, data, (len+3)&~3); +} +#endif +/* +============= +Sin_WriteBSPFile + +Swaps the bsp file in place, so it should not be referenced again +============= +*/ +void Sin_WriteBSPFile (char *filename) +{ + header = &outheader; + memset (header, 0, sizeof(sin_dheader_t)); + + Sin_SwapBSPFile (true); + + header->ident = LittleLong (SIN_BSPHEADER); + header->version = LittleLong (SIN_BSPVERSION); + + wadfile = SafeOpenWrite (filename); + SafeWrite (wadfile, header, sizeof(sin_dheader_t)); // overwritten later + +#ifdef SIN + Sin_AddLump (SIN_LUMP_PLANES, sin_dplanes, sin_numplanes, sizeof(sin_dplane_t), SIN_MAX_MAP_PLANES); + Sin_AddLump (SIN_LUMP_LEAFS, sin_dleafs, sin_numleafs, sizeof(sin_dleaf_t), SIN_MAX_MAP_LEAFS); + Sin_AddLump (SIN_LUMP_VERTEXES, sin_dvertexes, sin_numvertexes, sizeof(sin_dvertex_t), SIN_MAX_MAP_VERTS); + Sin_AddLump (SIN_LUMP_NODES, sin_dnodes, sin_numnodes, sizeof(sin_dnode_t), SIN_MAX_MAP_NODES); + Sin_AddLump (SIN_LUMP_TEXINFO, sin_texinfo, sin_numtexinfo, sizeof(sin_texinfo_t), SIN_MAX_MAP_TEXINFO); + Sin_AddLump (SIN_LUMP_FACES, sin_dfaces, sin_numfaces, sizeof(sin_dface_t), SIN_MAX_MAP_FACES); + Sin_AddLump (SIN_LUMP_BRUSHES, sin_dbrushes, sin_numbrushes, sizeof(sin_dbrush_t), SIN_MAX_MAP_BRUSHES); + Sin_AddLump (SIN_LUMP_BRUSHSIDES, sin_dbrushsides, sin_numbrushsides, sizeof(sin_dbrushside_t), SIN_MAX_MAP_BRUSHSIDES); + Sin_AddLump (SIN_LUMP_LEAFFACES, sin_dleaffaces, sin_numleaffaces, sizeof(sin_dleaffaces[0]), SIN_MAX_MAP_LEAFFACES); + Sin_AddLump (SIN_LUMP_LEAFBRUSHES, sin_dleafbrushes, sin_numleafbrushes, sizeof(sin_dleafbrushes[0]), SIN_MAX_MAP_LEAFBRUSHES); + Sin_AddLump (SIN_LUMP_SURFEDGES, sin_dsurfedges, sin_numsurfedges, sizeof(sin_dsurfedges[0]), SIN_MAX_MAP_SURFEDGES); + Sin_AddLump (SIN_LUMP_EDGES, sin_dedges, sin_numedges, sizeof(sin_dedge_t), SIN_MAX_MAP_EDGES); + Sin_AddLump (SIN_LUMP_MODELS, sin_dmodels, sin_nummodels, sizeof(sin_dmodel_t), SIN_MAX_MAP_MODELS); + Sin_AddLump (SIN_LUMP_AREAS, sin_dareas, sin_numareas, sizeof(sin_darea_t), SIN_MAX_MAP_AREAS); + Sin_AddLump (SIN_LUMP_AREAPORTALS, sin_dareaportals, sin_numareaportals, sizeof(sin_dareaportal_t), SIN_MAX_MAP_AREAPORTALS); + Sin_AddLump (SIN_LUMP_LIGHTINFO, sin_lightinfo, sin_numlightinfo, sizeof(sin_lightvalue_t), SIN_MAX_MAP_LIGHTINFO); + + Sin_AddLump (SIN_LUMP_LIGHTING, sin_dlightdata, sin_lightdatasize, 1, SIN_MAX_MAP_LIGHTING); + Sin_AddLump (SIN_LUMP_VISIBILITY, sin_dvisdata, sin_visdatasize, 1, SIN_MAX_MAP_VISIBILITY); + Sin_AddLump (SIN_LUMP_ENTITIES, sin_dentdata, sin_entdatasize, 1, SIN_MAX_MAP_ENTSTRING); + Sin_AddLump (SIN_LUMP_POP, sin_dpop, sizeof(sin_dpop), 1, sizeof(sin_dpop)); +#else + Sin_AddLump (SIN_LUMP_PLANES, sin_dplanes, sin_numplanes*sizeof(sin_dplane_t)); + Sin_AddLump (SIN_LUMP_LEAFS, sin_dleafs, sin_numleafs*sizeof(sin_dleaf_t)); + Sin_AddLump (SIN_LUMP_VERTEXES, sin_dvertexes, sin_numvertexes*sizeof(sin_dvertex_t)); + Sin_AddLump (SIN_LUMP_NODES, sin_dnodes, sin_numnodes*sizeof(sin_dnode_t)); + Sin_AddLump (SIN_LUMP_TEXINFO, sin_texinfo, sin_numtexinfo*sizeof(sin_texinfo_t)); + Sin_AddLump (SIN_LUMP_FACES, sin_dfaces, sin_numfaces*sizeof(sin_dface_t)); + Sin_AddLump (SIN_LUMP_BRUSHES, sin_dbrushes, sin_numbrushes*sizeof(sin_dbrush_t)); + Sin_AddLump (SIN_LUMP_BRUSHSIDES, sin_dbrushsides, sin_numbrushsides*sizeof(sin_dbrushside_t)); + Sin_AddLump (SIN_LUMP_LEAFFACES, sin_dleaffaces, sin_numleaffaces*sizeof(sin_dleaffaces[0])); + Sin_AddLump (SIN_LUMP_LEAFBRUSHES, sin_dleafbrushes, sin_numleafbrushes*sizeof(sin_dleafbrushes[0])); + Sin_AddLump (SIN_LUMP_SURFEDGES, sin_dsurfedges, sin_numsurfedges*sizeof(sin_dsurfedges[0])); + Sin_AddLump (SIN_LUMP_EDGES, sin_dedges, sin_numedges*sizeof(sin_dedge_t)); + Sin_AddLump (SIN_LUMP_MODELS, sin_dmodels, sin_nummodels*sizeof(sin_dmodel_t)); + Sin_AddLump (SIN_LUMP_AREAS, sin_dareas, sin_numareas*sizeof(sin_darea_t)); + Sin_AddLump (SIN_LUMP_AREAPORTALS, sin_dareaportals, sin_numareaportals*sizeof(sin_dareaportal_t)); + + Sin_AddLump (SIN_LUMP_LIGHTING, sin_dlightdata, sin_lightdatasize); + Sin_AddLump (SIN_LUMP_VISIBILITY, sin_dvisdata, sin_visdatasize); + Sin_AddLump (SIN_LUMP_ENTITIES, sin_dentdata, sin_entdatasize); + Sin_AddLump (SIN_LUMP_POP, sin_dpop, sizeof(sin_dpop)); +#endif + + fseek (wadfile, 0, SEEK_SET); + SafeWrite (wadfile, header, sizeof(sin_dheader_t)); + fclose (wadfile); +} + +//============================================================================ + + +//============================================ + +/* +================ +ParseEntities + +Parses the sin_dentdata string into entities +================ +*/ +void Sin_ParseEntities (void) +{ + script_t *script; + + num_entities = 0; + script = LoadScriptMemory(sin_dentdata, sin_entdatasize, "*sin bsp file"); + SetScriptFlags(script, SCFL_NOSTRINGWHITESPACES | + SCFL_NOSTRINGESCAPECHARS); + + while(ParseEntity(script)) + { + } //end while + + FreeScript(script); +} //end of the function Sin_ParseEntities + + +/* +================ +UnparseEntities + +Generates the sin_dentdata string from all the entities +================ +*/ +void Sin_UnparseEntities (void) +{ + char *buf, *end; + epair_t *ep; + char line[2048]; + int i; + char key[1024], value[1024]; + + buf = sin_dentdata; + end = buf; + *end = 0; + + for (i=0 ; inext) + { + strcpy (key, ep->key); + StripTrailing (key); + strcpy (value, ep->value); + StripTrailing (value); + + sprintf (line, "\"%s\" \"%s\"\n", key, value); + strcat (end, line); + end += strlen(line); + } + strcat (end,"}\n"); + end += 2; + + if (end > buf + SIN_MAX_MAP_ENTSTRING) + Error ("Entity text too long"); + } + sin_entdatasize = end - buf + 1; +} //end of the function Sin_UnparseEntities + +#ifdef SIN +void FreeValueKeys(entity_t *ent) +{ + epair_t *ep,*next; + + for (ep=ent->epairs ; ep ; ep=next) + { + next = ep->next; + FreeMemory(ep->value); + FreeMemory(ep->key); + FreeMemory(ep); + } + ent->epairs = NULL; +} +#endif + +/* +============= +Sin_PrintBSPFileSizes + +Dumps info about current file +============= +*/ +void Sin_PrintBSPFileSizes (void) +{ + if (!num_entities) + Sin_ParseEntities (); + + Log_Print("%6i models %7i\n" + ,sin_nummodels, (int)(sin_nummodels*sizeof(sin_dmodel_t))); + Log_Print("%6i brushes %7i\n" + ,sin_numbrushes, (int)(sin_numbrushes*sizeof(sin_dbrush_t))); + Log_Print("%6i brushsides %7i\n" + ,sin_numbrushsides, (int)(sin_numbrushsides*sizeof(sin_dbrushside_t))); + Log_Print("%6i planes %7i\n" + ,sin_numplanes, (int)(sin_numplanes*sizeof(sin_dplane_t))); + Log_Print("%6i texinfo %7i\n" + ,sin_numtexinfo, (int)(sin_numtexinfo*sizeof(sin_texinfo_t))); +#ifdef SIN + Log_Print("%6i lightinfo %7i\n" + ,sin_numlightinfo, (int)(sin_numlightinfo*sizeof(sin_lightvalue_t))); +#endif + Log_Print("%6i entdata %7i\n", num_entities, sin_entdatasize); + + Log_Print("\n"); + + Log_Print("%6i vertexes %7i\n" + ,sin_numvertexes, (int)(sin_numvertexes*sizeof(sin_dvertex_t))); + Log_Print("%6i nodes %7i\n" + ,sin_numnodes, (int)(sin_numnodes*sizeof(sin_dnode_t))); + Log_Print("%6i faces %7i\n" + ,sin_numfaces, (int)(sin_numfaces*sizeof(sin_dface_t))); + Log_Print("%6i leafs %7i\n" + ,sin_numleafs, (int)(sin_numleafs*sizeof(sin_dleaf_t))); + Log_Print("%6i leaffaces %7i\n" + ,sin_numleaffaces, (int)(sin_numleaffaces*sizeof(sin_dleaffaces[0]))); + Log_Print("%6i leafbrushes %7i\n" + ,sin_numleafbrushes, (int)(sin_numleafbrushes*sizeof(sin_dleafbrushes[0]))); + Log_Print("%6i surfedges %7i\n" + ,sin_numsurfedges, (int)(sin_numsurfedges*sizeof(sin_dsurfedges[0]))); + Log_Print("%6i edges %7i\n" + ,sin_numedges, (int)(sin_numedges*sizeof(sin_dedge_t))); + Log_Print(" lightdata %7i\n", sin_lightdatasize); + Log_Print(" visdata %7i\n", sin_visdatasize); +} diff --git a/code/bspc/l_bsp_sin.h b/code/bspc/l_bsp_sin.h index 93cb69f..862674b 100755 --- a/code/bspc/l_bsp_sin.h +++ b/code/bspc/l_bsp_sin.h @@ -1,106 +1,106 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "sinfiles.h" - -#define SINGAME_BSPHEADER (('P'<<24)+('S'<<16)+('B'<<8)+'R') //RBSP -#define SINGAME_BSPVERSION 1 - -#define SIN_BSPHEADER (('P'<<24)+('S'<<16)+('B'<<8)+'I') //IBSP -#define SIN_BSPVERSION 41 - - -extern int sin_nummodels; -extern sin_dmodel_t *sin_dmodels;//[MAX_MAP_MODELS]; - -extern int sin_visdatasize; -extern byte *sin_dvisdata;//[MAX_MAP_VISIBILITY]; -extern sin_dvis_t *sin_dvis;// = (dvis_t *)sin_sin_dvisdata; - -extern int sin_lightdatasize; -extern byte *sin_dlightdata;//[MAX_MAP_LIGHTING]; - -extern int sin_entdatasize; -extern char *sin_dentdata;//[MAX_MAP_ENTSTRING]; - -extern int sin_numleafs; -extern sin_dleaf_t *sin_dleafs;//[MAX_MAP_LEAFS]; - -extern int sin_numplanes; -extern sin_dplane_t *sin_dplanes;//[MAX_MAP_PLANES]; - -extern int sin_numvertexes; -extern sin_dvertex_t *sin_dvertexes;//[MAX_MAP_VERTS]; - -extern int sin_numnodes; -extern sin_dnode_t *sin_dnodes;//[MAX_MAP_NODES]; - -extern int sin_numtexinfo; -extern sin_texinfo_t *sin_texinfo;//[MAX_MAP_sin_texinfo]; - -extern int sin_numfaces; -extern sin_dface_t *sin_dfaces;//[MAX_MAP_FACES]; - -extern int sin_numedges; -extern sin_dedge_t *sin_dedges;//[MAX_MAP_EDGES]; - -extern int sin_numleaffaces; -extern unsigned short *sin_dleaffaces;//[MAX_MAP_LEAFFACES]; - -extern int sin_numleafbrushes; -extern unsigned short *sin_dleafbrushes;//[MAX_MAP_LEAFBRUSHES]; - -extern int sin_numsurfedges; -extern int *sin_dsurfedges;//[MAX_MAP_SURFEDGES]; - -extern int sin_numbrushes; -extern sin_dbrush_t *sin_dbrushes;//[MAX_MAP_BRUSHES]; - -extern int sin_numbrushsides; -extern sin_dbrushside_t *sin_dbrushsides;//[MAX_MAP_BRUSHSIDES]; - -extern int sin_numareas; -extern sin_darea_t *sin_dareas;//[MAX_MAP_AREAS]; - -extern int sin_numareaportals; -extern sin_dareaportal_t *sin_dareaportals;//[MAX_MAP_AREAPORTALS]; - -extern int sin_numlightinfo; -extern sin_lightvalue_t *sin_lightinfo;//[MAX_MAP_LIGHTINFO]; - -extern byte sin_dpop[256]; - -extern char sin_dbrushsidetextured[SIN_MAX_MAP_BRUSHSIDES]; - -void Sin_AllocMaxBSP(void); -void Sin_FreeMaxBSP(void); - -void Sin_DecompressVis(byte *in, byte *decompressed); -int Sin_CompressVis(byte *vis, byte *dest); - -void Sin_LoadBSPFile (char *filename, int offset, int length); -void Sin_LoadBSPFileTexinfo (char *filename); // just for qdata -void Sin_WriteBSPFile (char *filename); -void Sin_PrintBSPFileSizes (void); -void Sin_ParseEntities(void); -void Sin_UnparseEntities(void); - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "sinfiles.h" + +#define SINGAME_BSPHEADER (('P'<<24)+('S'<<16)+('B'<<8)+'R') //RBSP +#define SINGAME_BSPVERSION 1 + +#define SIN_BSPHEADER (('P'<<24)+('S'<<16)+('B'<<8)+'I') //IBSP +#define SIN_BSPVERSION 41 + + +extern int sin_nummodels; +extern sin_dmodel_t *sin_dmodels;//[MAX_MAP_MODELS]; + +extern int sin_visdatasize; +extern byte *sin_dvisdata;//[MAX_MAP_VISIBILITY]; +extern sin_dvis_t *sin_dvis;// = (dvis_t *)sin_sin_dvisdata; + +extern int sin_lightdatasize; +extern byte *sin_dlightdata;//[MAX_MAP_LIGHTING]; + +extern int sin_entdatasize; +extern char *sin_dentdata;//[MAX_MAP_ENTSTRING]; + +extern int sin_numleafs; +extern sin_dleaf_t *sin_dleafs;//[MAX_MAP_LEAFS]; + +extern int sin_numplanes; +extern sin_dplane_t *sin_dplanes;//[MAX_MAP_PLANES]; + +extern int sin_numvertexes; +extern sin_dvertex_t *sin_dvertexes;//[MAX_MAP_VERTS]; + +extern int sin_numnodes; +extern sin_dnode_t *sin_dnodes;//[MAX_MAP_NODES]; + +extern int sin_numtexinfo; +extern sin_texinfo_t *sin_texinfo;//[MAX_MAP_sin_texinfo]; + +extern int sin_numfaces; +extern sin_dface_t *sin_dfaces;//[MAX_MAP_FACES]; + +extern int sin_numedges; +extern sin_dedge_t *sin_dedges;//[MAX_MAP_EDGES]; + +extern int sin_numleaffaces; +extern unsigned short *sin_dleaffaces;//[MAX_MAP_LEAFFACES]; + +extern int sin_numleafbrushes; +extern unsigned short *sin_dleafbrushes;//[MAX_MAP_LEAFBRUSHES]; + +extern int sin_numsurfedges; +extern int *sin_dsurfedges;//[MAX_MAP_SURFEDGES]; + +extern int sin_numbrushes; +extern sin_dbrush_t *sin_dbrushes;//[MAX_MAP_BRUSHES]; + +extern int sin_numbrushsides; +extern sin_dbrushside_t *sin_dbrushsides;//[MAX_MAP_BRUSHSIDES]; + +extern int sin_numareas; +extern sin_darea_t *sin_dareas;//[MAX_MAP_AREAS]; + +extern int sin_numareaportals; +extern sin_dareaportal_t *sin_dareaportals;//[MAX_MAP_AREAPORTALS]; + +extern int sin_numlightinfo; +extern sin_lightvalue_t *sin_lightinfo;//[MAX_MAP_LIGHTINFO]; + +extern byte sin_dpop[256]; + +extern char sin_dbrushsidetextured[SIN_MAX_MAP_BRUSHSIDES]; + +void Sin_AllocMaxBSP(void); +void Sin_FreeMaxBSP(void); + +void Sin_DecompressVis(byte *in, byte *decompressed); +int Sin_CompressVis(byte *vis, byte *dest); + +void Sin_LoadBSPFile (char *filename, int offset, int length); +void Sin_LoadBSPFileTexinfo (char *filename); // just for qdata +void Sin_WriteBSPFile (char *filename); +void Sin_PrintBSPFileSizes (void); +void Sin_ParseEntities(void); +void Sin_UnparseEntities(void); + diff --git a/code/bspc/l_cmd.c b/code/bspc/l_cmd.c index 35fef7b..e35104b 100755 --- a/code/bspc/l_cmd.c +++ b/code/bspc/l_cmd.c @@ -1,1230 +1,1230 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -// cmdlib.c - -#include "l_cmd.h" -#include "l_log.h" -#include "l_mem.h" -#include -#include - -#ifndef SIN -#define SIN -#endif //SIN - -#if defined(WIN32) || defined(_WIN32) -#include -#else -#include -#endif - -#ifdef NeXT -#include -#endif - -#define BASEDIRNAME "quake2" -#define PATHSEPERATOR '/' - -// set these before calling CheckParm -int myargc; -char **myargv; - -char com_token[1024]; -qboolean com_eof; - -qboolean archive; -char archivedir[1024]; - - -/* -=================== -ExpandWildcards - -Mimic unix command line expansion -=================== -*/ -#define MAX_EX_ARGC 1024 -int ex_argc; -char *ex_argv[MAX_EX_ARGC]; -#ifdef _WIN32 -#include "io.h" -void ExpandWildcards (int *argc, char ***argv) -{ - struct _finddata_t fileinfo; - int handle; - int i; - char filename[1024]; - char filebase[1024]; - char *path; - - ex_argc = 0; - for (i=0 ; i<*argc ; i++) - { - path = (*argv)[i]; - if ( path[0] == '-' - || ( !strstr(path, "*") && !strstr(path, "?") ) ) - { - ex_argv[ex_argc++] = path; - continue; - } - - handle = _findfirst (path, &fileinfo); - if (handle == -1) - return; - - ExtractFilePath (path, filebase); - - do - { - sprintf (filename, "%s%s", filebase, fileinfo.name); - ex_argv[ex_argc++] = copystring (filename); - } while (_findnext( handle, &fileinfo ) != -1); - - _findclose (handle); - } - - *argc = ex_argc; - *argv = ex_argv; -} -#else -void ExpandWildcards (int *argc, char ***argv) -{ -} -#endif - -#ifdef WINBSPC - -#include - -HWND program_hwnd; - -void SetProgramHandle(HWND hwnd) -{ - program_hwnd = hwnd; -} //end of the function SetProgramHandle - -/* -================= -Error - -For abnormal program terminations in windowed apps -================= -*/ -void Error (char *error, ...) -{ - va_list argptr; - char text[1024]; - char text2[1024]; - int err; - - err = GetLastError (); - - va_start(argptr, error); - vsprintf(text, error, argptr); - va_end(argptr); - - sprintf(text2, "%s\nGetLastError() = %i", text, err); - MessageBox(program_hwnd, text2, "Error", 0 /* MB_OK */ ); - - Log_Write(text); - Log_Close(); - - exit(1); -} //end of the function Error - -void Warning(char *szFormat, ...) -{ - char szBuffer[256]; - va_list argptr; - - va_start (argptr, szFormat); - vsprintf(szBuffer, szFormat, argptr); - va_end (argptr); - - MessageBox(program_hwnd, szBuffer, "Warning", MB_OK); - - Log_Write(szBuffer); -} //end of the function Warning - - -#else -/* -================= -Error - -For abnormal program terminations in console apps -================= -*/ -void Error (char *error, ...) -{ - va_list argptr; - char text[1024]; - - va_start(argptr, error); - vsprintf(text, error, argptr); - va_end(argptr); - printf("ERROR: %s\n", text); - - Log_Write(text); - Log_Close(); - - exit (1); -} //end of the function Error - -void Warning(char *warning, ...) -{ - va_list argptr; - char text[1024]; - - va_start(argptr, warning); - vsprintf(text, warning, argptr); - va_end(argptr); - printf("WARNING: %s\n", text); - - Log_Write(text); -} //end of the function Warning - -#endif - -//only printf if in verbose mode -qboolean verbose = true; - -void qprintf(char *format, ...) -{ - va_list argptr; -#ifdef WINBSPC - char buf[2048]; -#endif //WINBSPC - - if (!verbose) - return; - - va_start(argptr,format); -#ifdef WINBSPC - vsprintf(buf, format, argptr); - WinBSPCPrint(buf); -#else - vprintf(format, argptr); -#endif //WINBSPC - va_end(argptr); -} //end of the function qprintf - -void Com_Error(int level, char *error, ...) -{ - va_list argptr; - char text[1024]; - - va_start(argptr, error); - vsprintf(text, error, argptr); - va_end(argptr); - Error(text); -} //end of the funcion Com_Error - -void Com_Printf( const char *fmt, ... ) -{ - va_list argptr; - char text[1024]; - - va_start(argptr, fmt); - vsprintf(text, fmt, argptr); - va_end(argptr); - Log_Print(text); -} //end of the funcion Com_Printf - -/* - -qdir will hold the path up to the quake directory, including the slash - - f:\quake\ - /raid/quake/ - -gamedir will hold qdir + the game directory (id1, id2, etc) - - */ - -char qdir[1024]; -char gamedir[1024]; - -void SetQdirFromPath (char *path) -{ - char temp[1024]; - char *c; - int len; - - if (!(path[0] == '/' || path[0] == '\\' || path[1] == ':')) - { // path is partial - Q_getwd (temp); - strcat (temp, path); - path = temp; - } - - // search for "quake2" in path - - len = strlen(BASEDIRNAME); - for (c=path+strlen(path)-1 ; c != path ; c--) - if (!Q_strncasecmp (c, BASEDIRNAME, len)) - { - strncpy (qdir, path, c+len+1-path); - qprintf ("qdir: %s\n", qdir); - c += len+1; - while (*c) - { - if (*c == '/' || *c == '\\') - { - strncpy (gamedir, path, c+1-path); - qprintf ("gamedir: %s\n", gamedir); - return; - } - c++; - } - Error ("No gamedir in %s", path); - return; - } - Error ("SetQdirFromPath: no '%s' in %s", BASEDIRNAME, path); -} - -char *ExpandArg (char *path) -{ - static char full[1024]; - - if (path[0] != '/' && path[0] != '\\' && path[1] != ':') - { - Q_getwd (full); - strcat (full, path); - } - else - strcpy (full, path); - return full; -} - -char *ExpandPath (char *path) -{ - static char full[1024]; - if (!qdir) - Error ("ExpandPath called without qdir set"); - if (path[0] == '/' || path[0] == '\\' || path[1] == ':') - return path; - sprintf (full, "%s%s", qdir, path); - return full; -} - -char *ExpandPathAndArchive (char *path) -{ - char *expanded; - char archivename[1024]; - - expanded = ExpandPath (path); - - if (archive) - { - sprintf (archivename, "%s/%s", archivedir, path); - QCopyFile (expanded, archivename); - } - return expanded; -} - - -char *copystring(char *s) -{ - char *b; - b = GetMemory(strlen(s)+1); - strcpy (b, s); - return b; -} - - - -/* -================ -I_FloatTime -================ -*/ -double I_FloatTime (void) -{ - time_t t; - - time (&t); - - return t; -#if 0 -// more precise, less portable - struct timeval tp; - struct timezone tzp; - static int secbase; - - gettimeofday(&tp, &tzp); - - if (!secbase) - { - secbase = tp.tv_sec; - return tp.tv_usec/1000000.0; - } - - return (tp.tv_sec - secbase) + tp.tv_usec/1000000.0; -#endif -} - -void Q_getwd (char *out) -{ -#if defined(WIN32) || defined(_WIN32) - getcwd (out, 256); - strcat (out, "\\"); -#else - getwd(out); - strcat(out, "/"); -#endif -} - - -void Q_mkdir (char *path) -{ -#ifdef WIN32 - if (_mkdir (path) != -1) - return; -#else - if (mkdir (path, 0777) != -1) - return; -#endif - if (errno != EEXIST) - Error ("mkdir %s: %s",path, strerror(errno)); -} - -/* -============ -FileTime - -returns -1 if not present -============ -*/ -int FileTime (char *path) -{ - struct stat buf; - - if (stat (path,&buf) == -1) - return -1; - - return buf.st_mtime; -} - - - -/* -============== -COM_Parse - -Parse a token out of a string -============== -*/ -char *COM_Parse (char *data) -{ - int c; - int len; - - len = 0; - com_token[0] = 0; - - if (!data) - return NULL; - -// skip whitespace -skipwhite: - while ( (c = *data) <= ' ') - { - if (c == 0) - { - com_eof = true; - return NULL; // end of file; - } - data++; - } - -// skip // comments - if (c=='/' && data[1] == '/') - { - while (*data && *data != '\n') - data++; - goto skipwhite; - } - - -// handle quoted strings specially - if (c == '\"') - { - data++; - do - { - c = *data++; - if (c=='\"') - { - com_token[len] = 0; - return data; - } - com_token[len] = c; - len++; - } while (1); - } - -// parse single characters - if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':') - { - com_token[len] = c; - len++; - com_token[len] = 0; - return data+1; - } - -// parse a regular word - do - { - com_token[len] = c; - data++; - len++; - c = *data; - if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':') - break; - } while (c>32); - - com_token[len] = 0; - return data; -} - - -int Q_strncasecmp (char *s1, char *s2, int n) -{ - int c1, c2; - - do - { - c1 = *s1++; - c2 = *s2++; - - if (!n--) - return 0; // strings are equal until end point - - if (c1 != c2) - { - if (c1 >= 'a' && c1 <= 'z') - c1 -= ('a' - 'A'); - if (c2 >= 'a' && c2 <= 'z') - c2 -= ('a' - 'A'); - if (c1 != c2) - return -1; // strings not equal - } - } while (c1); - - return 0; // strings are equal -} - -int Q_strcasecmp (char *s1, char *s2) -{ - return Q_strncasecmp (s1, s2, 99999); -} - -int Q_stricmp (char *s1, char *s2) -{ - return Q_strncasecmp (s1, s2, 99999); -} - -void Q_strncpyz( char *dest, const char *src, int destsize ) { - strncpy( dest, src, destsize-1 ); - dest[destsize-1] = 0; -} - -char *strupr (char *start) -{ - char *in; - in = start; - while (*in) - { - *in = toupper(*in); - in++; - } - return start; -} - -char *strlower (char *start) -{ - char *in; - in = start; - while (*in) - { - *in = tolower(*in); - in++; - } - return start; -} - - -/* -============================================================================= - - MISC FUNCTIONS - -============================================================================= -*/ - - -/* -================= -CheckParm - -Checks for the given parameter in the program's command line arguments -Returns the argument number (1 to argc-1) or 0 if not present -================= -*/ -int CheckParm (char *check) -{ - int i; - - for (i = 1;i 0 && path[length] != PATHSEPERATOR) - length--; - path[length] = 0; -} - -void StripExtension (char *path) -{ - int length; - - length = strlen(path)-1; - while (length > 0 && path[length] != '.') - { - length--; - if (path[length] == '/') - return; // no extension - } - if (length) - path[length] = 0; -} - - -/* -==================== -Extract file parts -==================== -*/ -// FIXME: should include the slash, otherwise -// backing to an empty path will be wrong when appending a slash -void ExtractFilePath (char *path, char *dest) -{ - char *src; - - src = path + strlen(path) - 1; - -// -// back up until a \ or the start -// - while (src != path && *(src-1) != '\\' && *(src-1) != '/') - src--; - - memcpy (dest, path, src-path); - dest[src-path] = 0; -} - -void ExtractFileBase (char *path, char *dest) -{ - char *src; - - src = path + strlen(path) - 1; - -// -// back up until a \ or the start -// - while (src != path && *(src-1) != '\\' && *(src-1) != '/') - src--; - - while (*src && *src != '.') - { - *dest++ = *src++; - } - *dest = 0; -} - -void ExtractFileExtension (char *path, char *dest) -{ - char *src; - - src = path + strlen(path) - 1; - -// -// back up until a . or the start -// - while (src != path && *(src-1) != '.') - src--; - if (src == path) - { - *dest = 0; // no extension - return; - } - - strcpy (dest,src); -} - - -/* -============== -ParseNum / ParseHex -============== -*/ -int ParseHex (char *hex) -{ - char *str; - int num; - - num = 0; - str = hex; - - while (*str) - { - num <<= 4; - if (*str >= '0' && *str <= '9') - num += *str-'0'; - else if (*str >= 'a' && *str <= 'f') - num += 10 + *str-'a'; - else if (*str >= 'A' && *str <= 'F') - num += 10 + *str-'A'; - else - Error ("Bad hex number: %s",hex); - str++; - } - - return num; -} - - -int ParseNum (char *str) -{ - if (str[0] == '$') - return ParseHex (str+1); - if (str[0] == '0' && str[1] == 'x') - return ParseHex (str+2); - return atol (str); -} - - - -/* -============================================================================ - - BYTE ORDER FUNCTIONS - -============================================================================ -*/ - -#ifdef _SGI_SOURCE -#define __BIG_ENDIAN__ -#endif - -#ifdef __BIG_ENDIAN__ - -short LittleShort (short l) -{ - byte b1,b2; - - b1 = l&255; - b2 = (l>>8)&255; - - return (b1<<8) + b2; -} - -short BigShort (short l) -{ - return l; -} - - -int LittleLong (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 BigLong (int l) -{ - return l; -} - - -float LittleFloat (float l) -{ - union {byte b[4]; float f;} in, out; - - in.f = l; - out.b[0] = in.b[3]; - out.b[1] = in.b[2]; - out.b[2] = in.b[1]; - out.b[3] = in.b[0]; - - return out.f; -} - -float BigFloat (float l) -{ - return l; -} - -#ifdef SIN -unsigned short LittleUnsignedShort (unsigned short l) -{ - byte b1,b2; - - b1 = l&255; - b2 = (l>>8)&255; - - return (b1<<8) + b2; -} - -unsigned short BigUnsignedShort (unsigned short l) -{ - return l; -} - -unsigned LittleUnsigned (unsigned l) -{ - byte b1,b2,b3,b4; - - b1 = l&255; - b2 = (l>>8)&255; - b3 = (l>>16)&255; - b4 = (l>>24)&255; - - return ((unsigned)b1<<24) + ((unsigned)b2<<16) + ((unsigned)b3<<8) + b4; -} - -unsigned BigUnsigned (unsigned l) -{ - return l; -} -#endif - - -#else - - -short BigShort (short l) -{ - byte b1,b2; - - b1 = l&255; - b2 = (l>>8)&255; - - return (b1<<8) + b2; -} - -short LittleShort (short l) -{ - return l; -} - - -int BigLong (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 LittleLong (int l) -{ - return l; -} - -float BigFloat (float l) -{ - union {byte b[4]; float f;} in, out; - - in.f = l; - out.b[0] = in.b[3]; - out.b[1] = in.b[2]; - out.b[2] = in.b[1]; - out.b[3] = in.b[0]; - - return out.f; -} - -float LittleFloat (float l) -{ - return l; -} - -#ifdef SIN -unsigned short BigUnsignedShort (unsigned short l) -{ - byte b1,b2; - - b1 = l&255; - b2 = (l>>8)&255; - - return (b1<<8) + b2; -} - -unsigned short LittleUnsignedShort (unsigned short l) -{ - return l; -} - - -unsigned BigUnsigned (unsigned l) -{ - byte b1,b2,b3,b4; - - b1 = l&255; - b2 = (l>>8)&255; - b3 = (l>>16)&255; - b4 = (l>>24)&255; - - return ((unsigned)b1<<24) + ((unsigned)b2<<16) + ((unsigned)b3<<8) + b4; -} - -unsigned LittleUnsigned (unsigned l) -{ - return l; -} -#endif - - -#endif - - -//======================================================= - - -// FIXME: byte swap? - -// this is a 16 bit, non-reflected CRC using the polynomial 0x1021 -// and the initial and final xor values shown below... in other words, the -// CCITT standard CRC used by XMODEM - -#define CRC_INIT_VALUE 0xffff -#define CRC_XOR_VALUE 0x0000 - -static unsigned short crctable[256] = -{ - 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, - 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, - 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, - 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, - 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, - 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, - 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, - 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, - 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, - 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, - 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, - 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, - 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, - 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, - 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, - 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, - 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, - 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, - 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, - 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, - 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, - 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, - 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, - 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, - 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, - 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, - 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, - 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, - 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, - 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, - 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, - 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 -}; - -void CRC_Init(unsigned short *crcvalue) -{ - *crcvalue = CRC_INIT_VALUE; -} - -void CRC_ProcessByte(unsigned short *crcvalue, byte data) -{ - *crcvalue = (*crcvalue << 8) ^ crctable[(*crcvalue >> 8) ^ data]; -} - -unsigned short CRC_Value(unsigned short crcvalue) -{ - return crcvalue ^ CRC_XOR_VALUE; -} -//============================================================================= - -/* -============ -CreatePath -============ -*/ -void CreatePath (char *path) -{ - char *ofs, c; - - if (path[1] == ':') - path += 2; - - for (ofs = path+1 ; *ofs ; ofs++) - { - c = *ofs; - if (c == '/' || c == '\\') - { // create the directory - *ofs = 0; - Q_mkdir (path); - *ofs = c; - } - } -} - - -/* -============ -QCopyFile - - Used to archive source files -============ -*/ -void QCopyFile (char *from, char *to) -{ - void *buffer; - int length; - - length = LoadFile (from, &buffer, 0, 0); - CreatePath (to); - SaveFile (to, buffer, length); - FreeMemory(buffer); -} - -void FS_FreeFile(void *buf) -{ - FreeMemory(buf); -} //end of the function FS_FreeFile - -int FS_ReadFileAndCache(const char *qpath, void **buffer) -{ - return LoadFile((char *) qpath, buffer, 0, 0); -} //end of the function FS_ReadFileAndCache - -int FS_FOpenFileRead( const char *filename, FILE **file, qboolean uniqueFILE ) -{ - *file = fopen(filename, "rb"); - return (*file != NULL); -} //end of the function FS_FOpenFileRead +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// cmdlib.c + +#include "l_cmd.h" +#include "l_log.h" +#include "l_mem.h" +#include +#include + +#ifndef SIN +#define SIN +#endif //SIN + +#if defined(WIN32) || defined(_WIN32) +#include +#else +#include +#endif + +#ifdef NeXT +#include +#endif + +#define BASEDIRNAME "quake2" +#define PATHSEPERATOR '/' + +// set these before calling CheckParm +int myargc; +char **myargv; + +char com_token[1024]; +qboolean com_eof; + +qboolean archive; +char archivedir[1024]; + + +/* +=================== +ExpandWildcards + +Mimic unix command line expansion +=================== +*/ +#define MAX_EX_ARGC 1024 +int ex_argc; +char *ex_argv[MAX_EX_ARGC]; +#ifdef _WIN32 +#include "io.h" +void ExpandWildcards (int *argc, char ***argv) +{ + struct _finddata_t fileinfo; + int handle; + int i; + char filename[1024]; + char filebase[1024]; + char *path; + + ex_argc = 0; + for (i=0 ; i<*argc ; i++) + { + path = (*argv)[i]; + if ( path[0] == '-' + || ( !strstr(path, "*") && !strstr(path, "?") ) ) + { + ex_argv[ex_argc++] = path; + continue; + } + + handle = _findfirst (path, &fileinfo); + if (handle == -1) + return; + + ExtractFilePath (path, filebase); + + do + { + sprintf (filename, "%s%s", filebase, fileinfo.name); + ex_argv[ex_argc++] = copystring (filename); + } while (_findnext( handle, &fileinfo ) != -1); + + _findclose (handle); + } + + *argc = ex_argc; + *argv = ex_argv; +} +#else +void ExpandWildcards (int *argc, char ***argv) +{ +} +#endif + +#ifdef WINBSPC + +#include + +HWND program_hwnd; + +void SetProgramHandle(HWND hwnd) +{ + program_hwnd = hwnd; +} //end of the function SetProgramHandle + +/* +================= +Error + +For abnormal program terminations in windowed apps +================= +*/ +void Error (char *error, ...) +{ + va_list argptr; + char text[1024]; + char text2[1024]; + int err; + + err = GetLastError (); + + va_start(argptr, error); + vsprintf(text, error, argptr); + va_end(argptr); + + sprintf(text2, "%s\nGetLastError() = %i", text, err); + MessageBox(program_hwnd, text2, "Error", 0 /* MB_OK */ ); + + Log_Write(text); + Log_Close(); + + exit(1); +} //end of the function Error + +void Warning(char *szFormat, ...) +{ + char szBuffer[256]; + va_list argptr; + + va_start (argptr, szFormat); + vsprintf(szBuffer, szFormat, argptr); + va_end (argptr); + + MessageBox(program_hwnd, szBuffer, "Warning", MB_OK); + + Log_Write(szBuffer); +} //end of the function Warning + + +#else +/* +================= +Error + +For abnormal program terminations in console apps +================= +*/ +void Error (char *error, ...) +{ + va_list argptr; + char text[1024]; + + va_start(argptr, error); + vsprintf(text, error, argptr); + va_end(argptr); + printf("ERROR: %s\n", text); + + Log_Write(text); + Log_Close(); + + exit (1); +} //end of the function Error + +void Warning(char *warning, ...) +{ + va_list argptr; + char text[1024]; + + va_start(argptr, warning); + vsprintf(text, warning, argptr); + va_end(argptr); + printf("WARNING: %s\n", text); + + Log_Write(text); +} //end of the function Warning + +#endif + +//only printf if in verbose mode +qboolean verbose = true; + +void qprintf(char *format, ...) +{ + va_list argptr; +#ifdef WINBSPC + char buf[2048]; +#endif //WINBSPC + + if (!verbose) + return; + + va_start(argptr,format); +#ifdef WINBSPC + vsprintf(buf, format, argptr); + WinBSPCPrint(buf); +#else + vprintf(format, argptr); +#endif //WINBSPC + va_end(argptr); +} //end of the function qprintf + +void Com_Error(int level, char *error, ...) +{ + va_list argptr; + char text[1024]; + + va_start(argptr, error); + vsprintf(text, error, argptr); + va_end(argptr); + Error(text); +} //end of the funcion Com_Error + +void Com_Printf( const char *fmt, ... ) +{ + va_list argptr; + char text[1024]; + + va_start(argptr, fmt); + vsprintf(text, fmt, argptr); + va_end(argptr); + Log_Print(text); +} //end of the funcion Com_Printf + +/* + +qdir will hold the path up to the quake directory, including the slash + + f:\quake\ + /raid/quake/ + +gamedir will hold qdir + the game directory (id1, id2, etc) + + */ + +char qdir[1024]; +char gamedir[1024]; + +void SetQdirFromPath (char *path) +{ + char temp[1024]; + char *c; + int len; + + if (!(path[0] == '/' || path[0] == '\\' || path[1] == ':')) + { // path is partial + Q_getwd (temp); + strcat (temp, path); + path = temp; + } + + // search for "quake2" in path + + len = strlen(BASEDIRNAME); + for (c=path+strlen(path)-1 ; c != path ; c--) + if (!Q_strncasecmp (c, BASEDIRNAME, len)) + { + strncpy (qdir, path, c+len+1-path); + qprintf ("qdir: %s\n", qdir); + c += len+1; + while (*c) + { + if (*c == '/' || *c == '\\') + { + strncpy (gamedir, path, c+1-path); + qprintf ("gamedir: %s\n", gamedir); + return; + } + c++; + } + Error ("No gamedir in %s", path); + return; + } + Error ("SetQdirFromPath: no '%s' in %s", BASEDIRNAME, path); +} + +char *ExpandArg (char *path) +{ + static char full[1024]; + + if (path[0] != '/' && path[0] != '\\' && path[1] != ':') + { + Q_getwd (full); + strcat (full, path); + } + else + strcpy (full, path); + return full; +} + +char *ExpandPath (char *path) +{ + static char full[1024]; + if (!qdir) + Error ("ExpandPath called without qdir set"); + if (path[0] == '/' || path[0] == '\\' || path[1] == ':') + return path; + sprintf (full, "%s%s", qdir, path); + return full; +} + +char *ExpandPathAndArchive (char *path) +{ + char *expanded; + char archivename[1024]; + + expanded = ExpandPath (path); + + if (archive) + { + sprintf (archivename, "%s/%s", archivedir, path); + QCopyFile (expanded, archivename); + } + return expanded; +} + + +char *copystring(char *s) +{ + char *b; + b = GetMemory(strlen(s)+1); + strcpy (b, s); + return b; +} + + + +/* +================ +I_FloatTime +================ +*/ +double I_FloatTime (void) +{ + time_t t; + + time (&t); + + return t; +#if 0 +// more precise, less portable + struct timeval tp; + struct timezone tzp; + static int secbase; + + gettimeofday(&tp, &tzp); + + if (!secbase) + { + secbase = tp.tv_sec; + return tp.tv_usec/1000000.0; + } + + return (tp.tv_sec - secbase) + tp.tv_usec/1000000.0; +#endif +} + +void Q_getwd (char *out) +{ +#if defined(WIN32) || defined(_WIN32) + getcwd (out, 256); + strcat (out, "\\"); +#else + getwd(out); + strcat(out, "/"); +#endif +} + + +void Q_mkdir (char *path) +{ +#ifdef WIN32 + if (_mkdir (path) != -1) + return; +#else + if (mkdir (path, 0777) != -1) + return; +#endif + if (errno != EEXIST) + Error ("mkdir %s: %s",path, strerror(errno)); +} + +/* +============ +FileTime + +returns -1 if not present +============ +*/ +int FileTime (char *path) +{ + struct stat buf; + + if (stat (path,&buf) == -1) + return -1; + + return buf.st_mtime; +} + + + +/* +============== +COM_Parse + +Parse a token out of a string +============== +*/ +char *COM_Parse (char *data) +{ + int c; + int len; + + len = 0; + com_token[0] = 0; + + if (!data) + return NULL; + +// skip whitespace +skipwhite: + while ( (c = *data) <= ' ') + { + if (c == 0) + { + com_eof = true; + return NULL; // end of file; + } + data++; + } + +// skip // comments + if (c=='/' && data[1] == '/') + { + while (*data && *data != '\n') + data++; + goto skipwhite; + } + + +// handle quoted strings specially + if (c == '\"') + { + data++; + do + { + c = *data++; + if (c=='\"') + { + com_token[len] = 0; + return data; + } + com_token[len] = c; + len++; + } while (1); + } + +// parse single characters + if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':') + { + com_token[len] = c; + len++; + com_token[len] = 0; + return data+1; + } + +// parse a regular word + do + { + com_token[len] = c; + data++; + len++; + c = *data; + if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':') + break; + } while (c>32); + + com_token[len] = 0; + return data; +} + + +int Q_strncasecmp (char *s1, char *s2, int n) +{ + int c1, c2; + + do + { + c1 = *s1++; + c2 = *s2++; + + if (!n--) + return 0; // strings are equal until end point + + if (c1 != c2) + { + if (c1 >= 'a' && c1 <= 'z') + c1 -= ('a' - 'A'); + if (c2 >= 'a' && c2 <= 'z') + c2 -= ('a' - 'A'); + if (c1 != c2) + return -1; // strings not equal + } + } while (c1); + + return 0; // strings are equal +} + +int Q_strcasecmp (char *s1, char *s2) +{ + return Q_strncasecmp (s1, s2, 99999); +} + +int Q_stricmp (char *s1, char *s2) +{ + return Q_strncasecmp (s1, s2, 99999); +} + +void Q_strncpyz( char *dest, const char *src, int destsize ) { + strncpy( dest, src, destsize-1 ); + dest[destsize-1] = 0; +} + +char *strupr (char *start) +{ + char *in; + in = start; + while (*in) + { + *in = toupper(*in); + in++; + } + return start; +} + +char *strlower (char *start) +{ + char *in; + in = start; + while (*in) + { + *in = tolower(*in); + in++; + } + return start; +} + + +/* +============================================================================= + + MISC FUNCTIONS + +============================================================================= +*/ + + +/* +================= +CheckParm + +Checks for the given parameter in the program's command line arguments +Returns the argument number (1 to argc-1) or 0 if not present +================= +*/ +int CheckParm (char *check) +{ + int i; + + for (i = 1;i 0 && path[length] != PATHSEPERATOR) + length--; + path[length] = 0; +} + +void StripExtension (char *path) +{ + int length; + + length = strlen(path)-1; + while (length > 0 && path[length] != '.') + { + length--; + if (path[length] == '/') + return; // no extension + } + if (length) + path[length] = 0; +} + + +/* +==================== +Extract file parts +==================== +*/ +// FIXME: should include the slash, otherwise +// backing to an empty path will be wrong when appending a slash +void ExtractFilePath (char *path, char *dest) +{ + char *src; + + src = path + strlen(path) - 1; + +// +// back up until a \ or the start +// + while (src != path && *(src-1) != '\\' && *(src-1) != '/') + src--; + + memcpy (dest, path, src-path); + dest[src-path] = 0; +} + +void ExtractFileBase (char *path, char *dest) +{ + char *src; + + src = path + strlen(path) - 1; + +// +// back up until a \ or the start +// + while (src != path && *(src-1) != '\\' && *(src-1) != '/') + src--; + + while (*src && *src != '.') + { + *dest++ = *src++; + } + *dest = 0; +} + +void ExtractFileExtension (char *path, char *dest) +{ + char *src; + + src = path + strlen(path) - 1; + +// +// back up until a . or the start +// + while (src != path && *(src-1) != '.') + src--; + if (src == path) + { + *dest = 0; // no extension + return; + } + + strcpy (dest,src); +} + + +/* +============== +ParseNum / ParseHex +============== +*/ +int ParseHex (char *hex) +{ + char *str; + int num; + + num = 0; + str = hex; + + while (*str) + { + num <<= 4; + if (*str >= '0' && *str <= '9') + num += *str-'0'; + else if (*str >= 'a' && *str <= 'f') + num += 10 + *str-'a'; + else if (*str >= 'A' && *str <= 'F') + num += 10 + *str-'A'; + else + Error ("Bad hex number: %s",hex); + str++; + } + + return num; +} + + +int ParseNum (char *str) +{ + if (str[0] == '$') + return ParseHex (str+1); + if (str[0] == '0' && str[1] == 'x') + return ParseHex (str+2); + return atol (str); +} + + + +/* +============================================================================ + + BYTE ORDER FUNCTIONS + +============================================================================ +*/ + +#ifdef _SGI_SOURCE +#define __BIG_ENDIAN__ +#endif + +#ifdef __BIG_ENDIAN__ + +short LittleShort (short l) +{ + byte b1,b2; + + b1 = l&255; + b2 = (l>>8)&255; + + return (b1<<8) + b2; +} + +short BigShort (short l) +{ + return l; +} + + +int LittleLong (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 BigLong (int l) +{ + return l; +} + + +float LittleFloat (float l) +{ + union {byte b[4]; float f;} in, out; + + in.f = l; + out.b[0] = in.b[3]; + out.b[1] = in.b[2]; + out.b[2] = in.b[1]; + out.b[3] = in.b[0]; + + return out.f; +} + +float BigFloat (float l) +{ + return l; +} + +#ifdef SIN +unsigned short LittleUnsignedShort (unsigned short l) +{ + byte b1,b2; + + b1 = l&255; + b2 = (l>>8)&255; + + return (b1<<8) + b2; +} + +unsigned short BigUnsignedShort (unsigned short l) +{ + return l; +} + +unsigned LittleUnsigned (unsigned l) +{ + byte b1,b2,b3,b4; + + b1 = l&255; + b2 = (l>>8)&255; + b3 = (l>>16)&255; + b4 = (l>>24)&255; + + return ((unsigned)b1<<24) + ((unsigned)b2<<16) + ((unsigned)b3<<8) + b4; +} + +unsigned BigUnsigned (unsigned l) +{ + return l; +} +#endif + + +#else + + +short BigShort (short l) +{ + byte b1,b2; + + b1 = l&255; + b2 = (l>>8)&255; + + return (b1<<8) + b2; +} + +short LittleShort (short l) +{ + return l; +} + + +int BigLong (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 LittleLong (int l) +{ + return l; +} + +float BigFloat (float l) +{ + union {byte b[4]; float f;} in, out; + + in.f = l; + out.b[0] = in.b[3]; + out.b[1] = in.b[2]; + out.b[2] = in.b[1]; + out.b[3] = in.b[0]; + + return out.f; +} + +float LittleFloat (float l) +{ + return l; +} + +#ifdef SIN +unsigned short BigUnsignedShort (unsigned short l) +{ + byte b1,b2; + + b1 = l&255; + b2 = (l>>8)&255; + + return (b1<<8) + b2; +} + +unsigned short LittleUnsignedShort (unsigned short l) +{ + return l; +} + + +unsigned BigUnsigned (unsigned l) +{ + byte b1,b2,b3,b4; + + b1 = l&255; + b2 = (l>>8)&255; + b3 = (l>>16)&255; + b4 = (l>>24)&255; + + return ((unsigned)b1<<24) + ((unsigned)b2<<16) + ((unsigned)b3<<8) + b4; +} + +unsigned LittleUnsigned (unsigned l) +{ + return l; +} +#endif + + +#endif + + +//======================================================= + + +// FIXME: byte swap? + +// this is a 16 bit, non-reflected CRC using the polynomial 0x1021 +// and the initial and final xor values shown below... in other words, the +// CCITT standard CRC used by XMODEM + +#define CRC_INIT_VALUE 0xffff +#define CRC_XOR_VALUE 0x0000 + +static unsigned short crctable[256] = +{ + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, + 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, + 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, + 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, + 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, + 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, + 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, + 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, + 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, + 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, + 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, + 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, + 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, + 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, + 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, + 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, + 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, + 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, + 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, + 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, + 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, + 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, + 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 +}; + +void CRC_Init(unsigned short *crcvalue) +{ + *crcvalue = CRC_INIT_VALUE; +} + +void CRC_ProcessByte(unsigned short *crcvalue, byte data) +{ + *crcvalue = (*crcvalue << 8) ^ crctable[(*crcvalue >> 8) ^ data]; +} + +unsigned short CRC_Value(unsigned short crcvalue) +{ + return crcvalue ^ CRC_XOR_VALUE; +} +//============================================================================= + +/* +============ +CreatePath +============ +*/ +void CreatePath (char *path) +{ + char *ofs, c; + + if (path[1] == ':') + path += 2; + + for (ofs = path+1 ; *ofs ; ofs++) + { + c = *ofs; + if (c == '/' || c == '\\') + { // create the directory + *ofs = 0; + Q_mkdir (path); + *ofs = c; + } + } +} + + +/* +============ +QCopyFile + + Used to archive source files +============ +*/ +void QCopyFile (char *from, char *to) +{ + void *buffer; + int length; + + length = LoadFile (from, &buffer, 0, 0); + CreatePath (to); + SaveFile (to, buffer, length); + FreeMemory(buffer); +} + +void FS_FreeFile(void *buf) +{ + FreeMemory(buf); +} //end of the function FS_FreeFile + +int FS_ReadFileAndCache(const char *qpath, void **buffer) +{ + return LoadFile((char *) qpath, buffer, 0, 0); +} //end of the function FS_ReadFileAndCache + +int FS_FOpenFileRead( const char *filename, FILE **file, qboolean uniqueFILE ) +{ + *file = fopen(filename, "rb"); + return (*file != NULL); +} //end of the function FS_FOpenFileRead diff --git a/code/bspc/l_cmd.h b/code/bspc/l_cmd.h index b5497b4..613cafa 100755 --- a/code/bspc/l_cmd.h +++ b/code/bspc/l_cmd.h @@ -1,157 +1,157 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// cmdlib.h - -#ifndef SIN -#define SIN -#endif //SIN - -#ifndef __CMDLIB__ -#define __CMDLIB__ - -#ifdef _WIN32 -#pragma warning(disable : 4244) // MIPS -#pragma warning(disable : 4136) // X86 -#pragma warning(disable : 4051) // ALPHA - -#pragma warning(disable : 4018) // signed/unsigned mismatch -#pragma warning(disable : 4305) // truncate from double to float -#endif - -#include -#include -#include -#include -#include -#include -#include - -#ifndef __BYTEBOOL__ -#define __BYTEBOOL__ -typedef enum {false, true} qboolean; -typedef unsigned char byte; -#endif - -// the dec offsetof macro doesnt work very well... -#define myoffsetof(type,identifier) ((size_t)&((type *)0)->identifier) - - -// set these before calling CheckParm -extern int myargc; -extern char **myargv; - -char *strupr (char *in); -char *strlower (char *in); -int Q_strncasecmp (char *s1, char *s2, int n); -int Q_strcasecmp (char *s1, char *s2); -void Q_getwd (char *out); - -int Q_filelength (FILE *f); -int FileTime (char *path); - -void Q_mkdir (char *path); - -extern char qdir[1024]; -extern char gamedir[1024]; -void SetQdirFromPath (char *path); -char *ExpandArg (char *path); // from cmd line -char *ExpandPath (char *path); // from scripts -char *ExpandPathAndArchive (char *path); - - -double I_FloatTime (void); - -void Error(char *error, ...); -void Warning(char *warning, ...); - -int CheckParm (char *check); - -FILE *SafeOpenWrite (char *filename); -FILE *SafeOpenRead (char *filename); -void SafeRead (FILE *f, void *buffer, int count); -void SafeWrite (FILE *f, void *buffer, int count); - -int LoadFile (char *filename, void **bufferptr, int offset, int length); -int TryLoadFile (char *filename, void **bufferptr); -void SaveFile (char *filename, void *buffer, int count); -qboolean FileExists (char *filename); - -void DefaultExtension (char *path, char *extension); -void DefaultPath (char *path, char *basepath); -void StripFilename (char *path); -void StripExtension (char *path); - -void ExtractFilePath (char *path, char *dest); -void ExtractFileBase (char *path, char *dest); -void ExtractFileExtension (char *path, char *dest); - -int ParseNum (char *str); - -short BigShort (short l); -short LittleShort (short l); -int BigLong (int l); -int LittleLong (int l); -float BigFloat (float l); -float LittleFloat (float l); - -#ifdef SIN -unsigned short BigUnsignedShort (unsigned short l); -unsigned short LittleUnsignedShort (unsigned short l); -unsigned BigUnsigned (unsigned l); -unsigned LittleUnsigned (unsigned l); -#endif - - -char *COM_Parse (char *data); - -extern char com_token[1024]; -extern qboolean com_eof; - -char *copystring(char *s); - - -void CRC_Init(unsigned short *crcvalue); -void CRC_ProcessByte(unsigned short *crcvalue, byte data); -unsigned short CRC_Value(unsigned short crcvalue); - -void CreatePath (char *path); -void QCopyFile (char *from, char *to); - -extern qboolean archive; -extern char archivedir[1024]; - - -extern qboolean verbose; -void qprintf (char *format, ...); - -void ExpandWildcards (int *argc, char ***argv); - - -// for compression routines -typedef struct -{ - byte *data; - int count; -} cblock_t; - -#endif - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// cmdlib.h + +#ifndef SIN +#define SIN +#endif //SIN + +#ifndef __CMDLIB__ +#define __CMDLIB__ + +#ifdef _WIN32 +#pragma warning(disable : 4244) // MIPS +#pragma warning(disable : 4136) // X86 +#pragma warning(disable : 4051) // ALPHA + +#pragma warning(disable : 4018) // signed/unsigned mismatch +#pragma warning(disable : 4305) // truncate from double to float +#endif + +#include +#include +#include +#include +#include +#include +#include + +#ifndef __BYTEBOOL__ +#define __BYTEBOOL__ +typedef enum {false, true} qboolean; +typedef unsigned char byte; +#endif + +// the dec offsetof macro doesnt work very well... +#define myoffsetof(type,identifier) ((size_t)&((type *)0)->identifier) + + +// set these before calling CheckParm +extern int myargc; +extern char **myargv; + +char *strupr (char *in); +char *strlower (char *in); +int Q_strncasecmp (char *s1, char *s2, int n); +int Q_strcasecmp (char *s1, char *s2); +void Q_getwd (char *out); + +int Q_filelength (FILE *f); +int FileTime (char *path); + +void Q_mkdir (char *path); + +extern char qdir[1024]; +extern char gamedir[1024]; +void SetQdirFromPath (char *path); +char *ExpandArg (char *path); // from cmd line +char *ExpandPath (char *path); // from scripts +char *ExpandPathAndArchive (char *path); + + +double I_FloatTime (void); + +void Error(char *error, ...); +void Warning(char *warning, ...); + +int CheckParm (char *check); + +FILE *SafeOpenWrite (char *filename); +FILE *SafeOpenRead (char *filename); +void SafeRead (FILE *f, void *buffer, int count); +void SafeWrite (FILE *f, void *buffer, int count); + +int LoadFile (char *filename, void **bufferptr, int offset, int length); +int TryLoadFile (char *filename, void **bufferptr); +void SaveFile (char *filename, void *buffer, int count); +qboolean FileExists (char *filename); + +void DefaultExtension (char *path, char *extension); +void DefaultPath (char *path, char *basepath); +void StripFilename (char *path); +void StripExtension (char *path); + +void ExtractFilePath (char *path, char *dest); +void ExtractFileBase (char *path, char *dest); +void ExtractFileExtension (char *path, char *dest); + +int ParseNum (char *str); + +short BigShort (short l); +short LittleShort (short l); +int BigLong (int l); +int LittleLong (int l); +float BigFloat (float l); +float LittleFloat (float l); + +#ifdef SIN +unsigned short BigUnsignedShort (unsigned short l); +unsigned short LittleUnsignedShort (unsigned short l); +unsigned BigUnsigned (unsigned l); +unsigned LittleUnsigned (unsigned l); +#endif + + +char *COM_Parse (char *data); + +extern char com_token[1024]; +extern qboolean com_eof; + +char *copystring(char *s); + + +void CRC_Init(unsigned short *crcvalue); +void CRC_ProcessByte(unsigned short *crcvalue, byte data); +unsigned short CRC_Value(unsigned short crcvalue); + +void CreatePath (char *path); +void QCopyFile (char *from, char *to); + +extern qboolean archive; +extern char archivedir[1024]; + + +extern qboolean verbose; +void qprintf (char *format, ...); + +void ExpandWildcards (int *argc, char ***argv); + + +// for compression routines +typedef struct +{ + byte *data; + int count; +} cblock_t; + +#endif + diff --git a/code/bspc/l_log.c b/code/bspc/l_log.c index b685ed3..6a69c2b 100755 --- a/code/bspc/l_log.c +++ b/code/bspc/l_log.c @@ -1,215 +1,215 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include -#include -#include - -#include "qbsp.h" - -#define MAX_LOGFILENAMESIZE 1024 - -typedef struct logfile_s -{ - char filename[MAX_LOGFILENAMESIZE]; - FILE *fp; - int numwrites; -} logfile_t; - -logfile_t logfile; - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Log_Open(char *filename) -{ - if (!filename || !strlen(filename)) - { - printf("openlog \n"); - return; - } //end if - if (logfile.fp) - { - printf("log file %s is already opened\n", logfile.filename); - return; - } //end if - logfile.fp = fopen(filename, "wb"); - if (!logfile.fp) - { - printf("can't open the log file %s\n", filename); - return; - } //end if - strncpy(logfile.filename, filename, MAX_LOGFILENAMESIZE); - printf("Opened log %s\n", logfile.filename); -} //end of the function Log_Create -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Log_Close(void) -{ - if (!logfile.fp) - { - printf("no log file to close\n"); - return; - } //end if - if (fclose(logfile.fp)) - { - printf("can't close log file %s\n", logfile.filename); - return; - } //end if - logfile.fp = NULL; - printf("Closed log %s\n", logfile.filename); -} //end of the function Log_Close -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Log_Shutdown(void) -{ - if (logfile.fp) Log_Close(); -} //end of the function Log_Shutdown -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Log_UnifyEndOfLine(char *buf) -{ - int i; - - for (i = 0; buf[i]; i++) - { - if (buf[i] == '\n') - { - if (i <= 0 || buf[i-1] != '\r') - { - memmove(&buf[i+1], &buf[i], strlen(&buf[i])+1); - buf[i] = '\r'; - i++; - } //end if - } //end if - } //end for -} //end of the function Log_UnifyEndOfLine -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Log_Print(char *fmt, ...) -{ - va_list ap; - char buf[2048]; - - va_start(ap, fmt); - vsprintf(buf, fmt, ap); - va_end(ap); - - if (verbose) - { -#ifdef WINBSPC - WinBSPCPrint(buf); -#else - printf("%s", buf); -#endif //WINBSPS - } //end if - - if (logfile.fp) - { - Log_UnifyEndOfLine(buf); - fprintf(logfile.fp, "%s", buf); - fflush(logfile.fp); - } //end if -} //end of the function Log_Print -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Log_Write(char *fmt, ...) -{ - va_list ap; - char buf[2048]; - - if (!logfile.fp) return; - va_start(ap, fmt); - vsprintf(buf, fmt, ap); - va_end(ap); - Log_UnifyEndOfLine(buf); - fprintf(logfile.fp, "%s", buf); - fflush(logfile.fp); -} //end of the function Log_Write -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Log_WriteTimeStamped(char *fmt, ...) -{ - va_list ap; - - if (!logfile.fp) return; -/* fprintf(logfile.fp, "%d %02d:%02d:%02d:%02d ", - logfile.numwrites, - (int) (botlibglobals.time / 60 / 60), - (int) (botlibglobals.time / 60), - (int) (botlibglobals.time), - (int) ((int) (botlibglobals.time * 100)) - - ((int) botlibglobals.time) * 100);*/ - va_start(ap, fmt); - vfprintf(logfile.fp, fmt, ap); - va_end(ap); - logfile.numwrites++; - fflush(logfile.fp); -} //end of the function Log_Write -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -FILE *Log_FileStruct(void) -{ - return logfile.fp; -} //end of the function Log_FileStruct -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Log_Flush(void) -{ - if (logfile.fp) fflush(logfile.fp); -} //end of the function Log_Flush - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include +#include +#include + +#include "qbsp.h" + +#define MAX_LOGFILENAMESIZE 1024 + +typedef struct logfile_s +{ + char filename[MAX_LOGFILENAMESIZE]; + FILE *fp; + int numwrites; +} logfile_t; + +logfile_t logfile; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Log_Open(char *filename) +{ + if (!filename || !strlen(filename)) + { + printf("openlog \n"); + return; + } //end if + if (logfile.fp) + { + printf("log file %s is already opened\n", logfile.filename); + return; + } //end if + logfile.fp = fopen(filename, "wb"); + if (!logfile.fp) + { + printf("can't open the log file %s\n", filename); + return; + } //end if + strncpy(logfile.filename, filename, MAX_LOGFILENAMESIZE); + printf("Opened log %s\n", logfile.filename); +} //end of the function Log_Create +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Log_Close(void) +{ + if (!logfile.fp) + { + printf("no log file to close\n"); + return; + } //end if + if (fclose(logfile.fp)) + { + printf("can't close log file %s\n", logfile.filename); + return; + } //end if + logfile.fp = NULL; + printf("Closed log %s\n", logfile.filename); +} //end of the function Log_Close +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Log_Shutdown(void) +{ + if (logfile.fp) Log_Close(); +} //end of the function Log_Shutdown +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Log_UnifyEndOfLine(char *buf) +{ + int i; + + for (i = 0; buf[i]; i++) + { + if (buf[i] == '\n') + { + if (i <= 0 || buf[i-1] != '\r') + { + memmove(&buf[i+1], &buf[i], strlen(&buf[i])+1); + buf[i] = '\r'; + i++; + } //end if + } //end if + } //end for +} //end of the function Log_UnifyEndOfLine +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Log_Print(char *fmt, ...) +{ + va_list ap; + char buf[2048]; + + va_start(ap, fmt); + vsprintf(buf, fmt, ap); + va_end(ap); + + if (verbose) + { +#ifdef WINBSPC + WinBSPCPrint(buf); +#else + printf("%s", buf); +#endif //WINBSPS + } //end if + + if (logfile.fp) + { + Log_UnifyEndOfLine(buf); + fprintf(logfile.fp, "%s", buf); + fflush(logfile.fp); + } //end if +} //end of the function Log_Print +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Log_Write(char *fmt, ...) +{ + va_list ap; + char buf[2048]; + + if (!logfile.fp) return; + va_start(ap, fmt); + vsprintf(buf, fmt, ap); + va_end(ap); + Log_UnifyEndOfLine(buf); + fprintf(logfile.fp, "%s", buf); + fflush(logfile.fp); +} //end of the function Log_Write +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Log_WriteTimeStamped(char *fmt, ...) +{ + va_list ap; + + if (!logfile.fp) return; +/* fprintf(logfile.fp, "%d %02d:%02d:%02d:%02d ", + logfile.numwrites, + (int) (botlibglobals.time / 60 / 60), + (int) (botlibglobals.time / 60), + (int) (botlibglobals.time), + (int) ((int) (botlibglobals.time * 100)) - + ((int) botlibglobals.time) * 100);*/ + va_start(ap, fmt); + vfprintf(logfile.fp, fmt, ap); + va_end(ap); + logfile.numwrites++; + fflush(logfile.fp); +} //end of the function Log_Write +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +FILE *Log_FileStruct(void) +{ + return logfile.fp; +} //end of the function Log_FileStruct +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Log_Flush(void) +{ + if (logfile.fp) fflush(logfile.fp); +} //end of the function Log_Flush + diff --git a/code/bspc/l_log.h b/code/bspc/l_log.h index eff7bf6..b9f1be4 100755 --- a/code/bspc/l_log.h +++ b/code/bspc/l_log.h @@ -1,42 +1,42 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -//open a log file -void Log_Open(char *filename); -//close the current log file -void Log_Close(void); -//close log file if present -void Log_Shutdown(void); -//print on stdout and write to the current opened log file -void Log_Print(char *fmt, ...); -//write to the current opened log file -void Log_Write(char *fmt, ...); -//write to the current opened log file with a time stamp -void Log_WriteTimeStamped(char *fmt, ...); -//returns the log file structure -FILE *Log_FileStruct(void); -//flush log file -void Log_Flush(void); - -#ifdef WINBSPC -void WinBSPCPrint(char *str); -#endif //WINBSPC +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +//open a log file +void Log_Open(char *filename); +//close the current log file +void Log_Close(void); +//close log file if present +void Log_Shutdown(void); +//print on stdout and write to the current opened log file +void Log_Print(char *fmt, ...); +//write to the current opened log file +void Log_Write(char *fmt, ...); +//write to the current opened log file with a time stamp +void Log_WriteTimeStamped(char *fmt, ...); +//returns the log file structure +FILE *Log_FileStruct(void); +//flush log file +void Log_Flush(void); + +#ifdef WINBSPC +void WinBSPCPrint(char *str); +#endif //WINBSPC diff --git a/code/bspc/l_math.c b/code/bspc/l_math.c index caf3419..8d26a05 100755 --- a/code/bspc/l_math.c +++ b/code/bspc/l_math.c @@ -1,289 +1,289 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// mathlib.c -- math primitives - -#include "l_cmd.h" -#include "l_math.h" - -vec3_t vec3_origin = {0,0,0}; - -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); - sy = sin(angle); - cy = cos(angle); - angle = angles[PITCH] * (M_PI*2 / 360); - sp = sin(angle); - cp = cos(angle); - angle = angles[ROLL] * (M_PI*2 / 360); - sr = sin(angle); - cr = cos(angle); - - if (forward) - { - forward[0] = cp*cy; - forward[1] = cp*sy; - forward[2] = -sp; - } - if (right) - { - right[0] = (-1*sr*sp*cy+-1*cr*-sy); - right[1] = (-1*sr*sp*sy+-1*cr*cy); - right[2] = -1*sr*cp; - } - if (up) - { - up[0] = (cr*sp*cy+-sr*-sy); - up[1] = (cr*sp*sy+-sr*cy); - up[2] = cr*cp; - } -} - -/* -================= -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 = fabs( mins[i] ); - b = fabs( maxs[i] ); - corner[i] = a > b ? a : b; - } - - return VectorLength (corner); -} - -/* -================ -R_ConcatRotations -================ -*/ -void R_ConcatRotations (float in1[3][3], float in2[3][3], float out[3][3]) -{ - out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + - in1[0][2] * in2[2][0]; - out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + - in1[0][2] * in2[2][1]; - out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + - in1[0][2] * in2[2][2]; - out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + - in1[1][2] * in2[2][0]; - out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + - in1[1][2] * in2[2][1]; - out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + - in1[1][2] * in2[2][2]; - out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + - in1[2][2] * in2[2][0]; - out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + - in1[2][2] * in2[2][1]; - out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + - in1[2][2] * in2[2][2]; -} - -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; -} - -float VectorLengthSquared(vec3_t v) { - return DotProduct(v, v); -} - -double VectorLength(vec3_t v) -{ - int i; - double length; - - length = 0; - for (i=0 ; i< 3 ; i++) - length += v[i]*v[i]; - length = sqrt (length); // FIXME - - return length; -} - -qboolean VectorCompare (vec3_t v1, vec3_t v2) -{ - int i; - - for (i=0 ; i<3 ; i++) - if (fabs(v1[i]-v2[i]) > EQUAL_EPSILON) - return false; - - return true; -} - -vec_t Q_rint (vec_t in) -{ - return floor(in + 0.5); -} - -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]; -} - -void _VectorMA (vec3_t va, double scale, vec3_t vb, vec3_t vc) -{ - vc[0] = va[0] + scale*vb[0]; - vc[1] = va[1] + scale*vb[1]; - vc[2] = va[2] + scale*vb[2]; -} - -vec_t _DotProduct (vec3_t v1, vec3_t v2) -{ - return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]; -} - -void _VectorSubtract (vec3_t va, vec3_t vb, vec3_t out) -{ - out[0] = va[0]-vb[0]; - out[1] = va[1]-vb[1]; - out[2] = va[2]-vb[2]; -} - -void _VectorAdd (vec3_t va, vec3_t vb, vec3_t out) -{ - out[0] = va[0]+vb[0]; - out[1] = va[1]+vb[1]; - out[2] = va[2]+vb[2]; -} - -void _VectorCopy (vec3_t in, vec3_t out) -{ - out[0] = in[0]; - out[1] = in[1]; - out[2] = in[2]; -} - -void _VectorScale (vec3_t v, vec_t scale, vec3_t out) -{ - out[0] = v[0] * scale; - out[1] = v[1] * scale; - out[2] = v[2] * scale; -} - -vec_t VectorNormalize(vec3_t inout) -{ - vec_t length, ilength; - - length = sqrt (inout[0]*inout[0] + inout[1]*inout[1] + inout[2]*inout[2]); - if (length == 0) - { - VectorClear (inout); - return 0; - } - - ilength = 1.0/length; - inout[0] = inout[0]*ilength; - inout[1] = inout[1]*ilength; - inout[2] = inout[2]*ilength; - - return length; -} - -vec_t VectorNormalize2(const vec3_t in, vec3_t out) -{ - vec_t length, ilength; - - length = sqrt (in[0]*in[0] + in[1]*in[1] + in[2]*in[2]); - if (length == 0) - { - VectorClear (out); - return 0; - } - - ilength = 1.0/length; - out[0] = in[0]*ilength; - out[1] = in[1]*ilength; - out[2] = in[2]*ilength; - - return length; -} - -vec_t ColorNormalize (vec3_t in, vec3_t out) -{ - float max, scale; - - max = in[0]; - if (in[1] > max) - max = in[1]; - if (in[2] > max) - max = in[2]; - - if (max == 0) - return 0; - - scale = 1.0 / max; - - VectorScale (in, scale, out); - - return max; -} - - - -void VectorInverse (vec3_t v) -{ - v[0] = -v[0]; - v[1] = -v[1]; - v[2] = -v[2]; -} - -void ClearBounds(vec3_t mins, vec3_t maxs) -{ - mins[0] = mins[1] = mins[2] = 99999; - maxs[0] = maxs[1] = maxs[2] = -99999; -} - -void AddPointToBounds(const vec3_t v, vec3_t mins, vec3_t maxs) -{ - int i; - vec_t val; - - for (i=0 ; i<3 ; i++) - { - val = v[i]; - if (val < mins[i]) - mins[i] = val; - if (val > maxs[i]) - maxs[i] = val; - } -} +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// mathlib.c -- math primitives + +#include "l_cmd.h" +#include "l_math.h" + +vec3_t vec3_origin = {0,0,0}; + +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); + sy = sin(angle); + cy = cos(angle); + angle = angles[PITCH] * (M_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); + angle = angles[ROLL] * (M_PI*2 / 360); + sr = sin(angle); + cr = cos(angle); + + if (forward) + { + forward[0] = cp*cy; + forward[1] = cp*sy; + forward[2] = -sp; + } + if (right) + { + right[0] = (-1*sr*sp*cy+-1*cr*-sy); + right[1] = (-1*sr*sp*sy+-1*cr*cy); + right[2] = -1*sr*cp; + } + if (up) + { + up[0] = (cr*sp*cy+-sr*-sy); + up[1] = (cr*sp*sy+-sr*cy); + up[2] = cr*cp; + } +} + +/* +================= +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 = fabs( mins[i] ); + b = fabs( maxs[i] ); + corner[i] = a > b ? a : b; + } + + return VectorLength (corner); +} + +/* +================ +R_ConcatRotations +================ +*/ +void R_ConcatRotations (float in1[3][3], float in2[3][3], float out[3][3]) +{ + out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + + in1[0][2] * in2[2][0]; + out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + + in1[0][2] * in2[2][1]; + out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + + in1[0][2] * in2[2][2]; + out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + + in1[1][2] * in2[2][0]; + out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + + in1[1][2] * in2[2][1]; + out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + + in1[1][2] * in2[2][2]; + out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + + in1[2][2] * in2[2][0]; + out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + + in1[2][2] * in2[2][1]; + out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + + in1[2][2] * in2[2][2]; +} + +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; +} + +float VectorLengthSquared(vec3_t v) { + return DotProduct(v, v); +} + +double VectorLength(vec3_t v) +{ + int i; + double length; + + length = 0; + for (i=0 ; i< 3 ; i++) + length += v[i]*v[i]; + length = sqrt (length); // FIXME + + return length; +} + +qboolean VectorCompare (vec3_t v1, vec3_t v2) +{ + int i; + + for (i=0 ; i<3 ; i++) + if (fabs(v1[i]-v2[i]) > EQUAL_EPSILON) + return false; + + return true; +} + +vec_t Q_rint (vec_t in) +{ + return floor(in + 0.5); +} + +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]; +} + +void _VectorMA (vec3_t va, double scale, vec3_t vb, vec3_t vc) +{ + vc[0] = va[0] + scale*vb[0]; + vc[1] = va[1] + scale*vb[1]; + vc[2] = va[2] + scale*vb[2]; +} + +vec_t _DotProduct (vec3_t v1, vec3_t v2) +{ + return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]; +} + +void _VectorSubtract (vec3_t va, vec3_t vb, vec3_t out) +{ + out[0] = va[0]-vb[0]; + out[1] = va[1]-vb[1]; + out[2] = va[2]-vb[2]; +} + +void _VectorAdd (vec3_t va, vec3_t vb, vec3_t out) +{ + out[0] = va[0]+vb[0]; + out[1] = va[1]+vb[1]; + out[2] = va[2]+vb[2]; +} + +void _VectorCopy (vec3_t in, vec3_t out) +{ + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; +} + +void _VectorScale (vec3_t v, vec_t scale, vec3_t out) +{ + out[0] = v[0] * scale; + out[1] = v[1] * scale; + out[2] = v[2] * scale; +} + +vec_t VectorNormalize(vec3_t inout) +{ + vec_t length, ilength; + + length = sqrt (inout[0]*inout[0] + inout[1]*inout[1] + inout[2]*inout[2]); + if (length == 0) + { + VectorClear (inout); + return 0; + } + + ilength = 1.0/length; + inout[0] = inout[0]*ilength; + inout[1] = inout[1]*ilength; + inout[2] = inout[2]*ilength; + + return length; +} + +vec_t VectorNormalize2(const vec3_t in, vec3_t out) +{ + vec_t length, ilength; + + length = sqrt (in[0]*in[0] + in[1]*in[1] + in[2]*in[2]); + if (length == 0) + { + VectorClear (out); + return 0; + } + + ilength = 1.0/length; + out[0] = in[0]*ilength; + out[1] = in[1]*ilength; + out[2] = in[2]*ilength; + + return length; +} + +vec_t ColorNormalize (vec3_t in, vec3_t out) +{ + float max, scale; + + max = in[0]; + if (in[1] > max) + max = in[1]; + if (in[2] > max) + max = in[2]; + + if (max == 0) + return 0; + + scale = 1.0 / max; + + VectorScale (in, scale, out); + + return max; +} + + + +void VectorInverse (vec3_t v) +{ + v[0] = -v[0]; + v[1] = -v[1]; + v[2] = -v[2]; +} + +void ClearBounds(vec3_t mins, vec3_t maxs) +{ + mins[0] = mins[1] = mins[2] = 99999; + maxs[0] = maxs[1] = maxs[2] = -99999; +} + +void AddPointToBounds(const vec3_t v, vec3_t mins, vec3_t maxs) +{ + int i; + vec_t val; + + for (i=0 ; i<3 ; i++) + { + val = v[i]; + if (val < mins[i]) + mins[i] = val; + if (val > maxs[i]) + maxs[i] = val; + } +} diff --git a/code/bspc/l_math.h b/code/bspc/l_math.h index 1df7b61..4cd4a6c 100755 --- a/code/bspc/l_math.h +++ b/code/bspc/l_math.h @@ -1,93 +1,93 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -#ifndef __MATHLIB__ -#define __MATHLIB__ - -// mathlib.h - -#include - -#ifdef DOUBLEVEC_T -typedef double vec_t; -#else -typedef float vec_t; -#endif -typedef vec_t vec3_t[3]; -typedef vec_t vec4_t[4]; - -#define SIDE_FRONT 0 -#define SIDE_ON 2 -#define SIDE_BACK 1 -#define SIDE_CROSS -2 - -#define PITCH 0 -#define YAW 1 -#define ROLL 2 - -#define Q_PI 3.14159265358979323846 - -#define DEG2RAD( a ) ( a * M_PI ) / 180.0F - -#ifndef M_PI -#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h -#endif - -extern vec3_t vec3_origin; - -#define EQUAL_EPSILON 0.001 - -qboolean VectorCompare (vec3_t v1, vec3_t v2); - -#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 Vector4Copy(a,b) {b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];} -#define VectorScale(v, s, o) ((o)[0]=(v)[0]*(s),(o)[1]=(v)[1]*(s),(o)[2]=(v)[2]*(s)) -#define VectorClear(x) {x[0] = x[1] = x[2] = 0;} -#define VectorNegate(x, y) {y[0]=-x[0];y[1]=-x[1];y[2]=-x[2];} -#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)) - -vec_t Q_rint (vec_t in); -vec_t _DotProduct (vec3_t v1, vec3_t v2); -void _VectorSubtract (vec3_t va, vec3_t vb, vec3_t out); -void _VectorAdd (vec3_t va, vec3_t vb, vec3_t out); -void _VectorCopy (vec3_t in, vec3_t out); -void _VectorScale (vec3_t v, vec_t scale, vec3_t out); -void _VectorMA(vec3_t va, double scale, vec3_t vb, vec3_t vc); - -double VectorLength(vec3_t v); -void CrossProduct(const vec3_t v1, const vec3_t v2, vec3_t cross); -vec_t VectorNormalize(vec3_t inout); -vec_t ColorNormalize(vec3_t in, vec3_t out); -vec_t VectorNormalize2(const vec3_t v, vec3_t out); -void VectorInverse (vec3_t v); - -void ClearBounds (vec3_t mins, vec3_t maxs); -void AddPointToBounds (const vec3_t v, vec3_t mins, vec3_t maxs); - -void AngleVectors (const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up); -void R_ConcatRotations (float in1[3][3], float in2[3][3], float out[3][3]); -void RotatePoint(vec3_t point, float matrix[3][3]); -void CreateRotationMatrix(vec3_t angles, float matrix[3][3]); - -#endif +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +#ifndef __MATHLIB__ +#define __MATHLIB__ + +// mathlib.h + +#include + +#ifdef DOUBLEVEC_T +typedef double vec_t; +#else +typedef float vec_t; +#endif +typedef vec_t vec3_t[3]; +typedef vec_t vec4_t[4]; + +#define SIDE_FRONT 0 +#define SIDE_ON 2 +#define SIDE_BACK 1 +#define SIDE_CROSS -2 + +#define PITCH 0 +#define YAW 1 +#define ROLL 2 + +#define Q_PI 3.14159265358979323846 + +#define DEG2RAD( a ) ( a * M_PI ) / 180.0F + +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +extern vec3_t vec3_origin; + +#define EQUAL_EPSILON 0.001 + +qboolean VectorCompare (vec3_t v1, vec3_t v2); + +#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 Vector4Copy(a,b) {b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];} +#define VectorScale(v, s, o) ((o)[0]=(v)[0]*(s),(o)[1]=(v)[1]*(s),(o)[2]=(v)[2]*(s)) +#define VectorClear(x) {x[0] = x[1] = x[2] = 0;} +#define VectorNegate(x, y) {y[0]=-x[0];y[1]=-x[1];y[2]=-x[2];} +#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)) + +vec_t Q_rint (vec_t in); +vec_t _DotProduct (vec3_t v1, vec3_t v2); +void _VectorSubtract (vec3_t va, vec3_t vb, vec3_t out); +void _VectorAdd (vec3_t va, vec3_t vb, vec3_t out); +void _VectorCopy (vec3_t in, vec3_t out); +void _VectorScale (vec3_t v, vec_t scale, vec3_t out); +void _VectorMA(vec3_t va, double scale, vec3_t vb, vec3_t vc); + +double VectorLength(vec3_t v); +void CrossProduct(const vec3_t v1, const vec3_t v2, vec3_t cross); +vec_t VectorNormalize(vec3_t inout); +vec_t ColorNormalize(vec3_t in, vec3_t out); +vec_t VectorNormalize2(const vec3_t v, vec3_t out); +void VectorInverse (vec3_t v); + +void ClearBounds (vec3_t mins, vec3_t maxs); +void AddPointToBounds (const vec3_t v, vec3_t mins, vec3_t maxs); + +void AngleVectors (const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up); +void R_ConcatRotations (float in1[3][3], float in2[3][3], float out[3][3]); +void RotatePoint(vec3_t point, float matrix[3][3]); +void CreateRotationMatrix(vec3_t angles, float matrix[3][3]); + +#endif diff --git a/code/bspc/l_mem.c b/code/bspc/l_mem.c index 831543e..31e02b2 100755 --- a/code/bspc/l_mem.c +++ b/code/bspc/l_mem.c @@ -1,441 +1,441 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "qbsp.h" -#include "l_log.h" - -int allocedmemory; - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void PrintMemorySize(unsigned long size) -{ - unsigned long number1, number2, number3; - number1 = size >> 20; - number2 = (size & 0xFFFFF) >> 10; - number3 = (size & 0x3FF); - if (number1) Log_Print("%ld MB", number1); - if (number1 && number2) Log_Print(" and "); - if (number2) Log_Print("%ld KB", number2); - if (number2 && number3) Log_Print(" and "); - if (number3) Log_Print("%ld bytes", number3); -} //end of the function PrintFileSize - -#ifndef MEMDEBUG -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int MemorySize(void *ptr) -{ -#if defined(WIN32) || defined(_WIN32) - #ifdef __WATCOMC__ - //Intel 32 bits memory addressing, 16 bytes aligned - return (_msize(ptr) + 15) >> 4 << 4; - #else - return _msize(ptr); - #endif -#else - return 0; -#endif -} //end of the function MemorySize -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void *GetClearedMemory(int size) -{ - void *ptr; - - ptr = (void *) malloc(size); - if (!ptr) Error("out of memory"); - memset(ptr, 0, size); - allocedmemory += MemorySize(ptr); - return ptr; -} //end of the function GetClearedMemory -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void *GetMemory(unsigned long size) -{ - void *ptr; - ptr = malloc(size); - if (!ptr) Error("out of memory"); - allocedmemory += MemorySize(ptr); - return ptr; -} //end of the function GetMemory -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void FreeMemory(void *ptr) -{ - allocedmemory -= MemorySize(ptr); - free(ptr); -} //end of the function FreeMemory -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int TotalAllocatedMemory(void) -{ - return allocedmemory; -} //end of the function TotalAllocatedMemory - -#else - -#define MEM_ID 0x12345678l - -int totalmemorysize; -int numblocks; - -typedef struct memoryblock_s -{ - unsigned long int id; - void *ptr; - int size; -#ifdef MEMDEBUG - char *label; - char *file; - int line; -#endif //MEMDEBUG - struct memoryblock_s *prev, *next; -} memoryblock_t; - -memoryblock_t *memory; - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void LinkMemoryBlock(memoryblock_t *block) -{ - block->prev = NULL; - block->next = memory; - if (memory) memory->prev = block; - memory = block; -} //end of the function LinkMemoryBlock -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void UnlinkMemoryBlock(memoryblock_t *block) -{ - if (block->prev) block->prev->next = block->next; - else memory = block->next; - if (block->next) block->next->prev = block->prev; -} //end of the function UnlinkMemoryBlock -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -#ifdef MEMDEBUG -void *GetMemoryDebug(unsigned long size, char *label, char *file, int line) -#else -void *GetMemory(unsigned long size) -#endif //MEMDEBUG -{ - void *ptr; - memoryblock_t *block; - - ptr = malloc(size + sizeof(memoryblock_t)); - block = (memoryblock_t *) ptr; - block->id = MEM_ID; - block->ptr = (char *) ptr + sizeof(memoryblock_t); - block->size = size + sizeof(memoryblock_t); -#ifdef MEMDEBUG - block->label = label; - block->file = file; - block->line = line; -#endif //MEMDEBUG - LinkMemoryBlock(block); - totalmemorysize += block->size; - numblocks++; - return block->ptr; -} //end of the function GetMemoryDebug -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -#ifdef MEMDEBUG -void *GetClearedMemoryDebug(unsigned long size, char *label, char *file, int line) -#else -void *GetClearedMemory(unsigned long size) -#endif //MEMDEBUG -{ - void *ptr; -#ifdef MEMDEBUG - ptr = GetMemoryDebug(size, label, file, line); -#else - ptr = GetMemory(size); -#endif //MEMDEBUG - memset(ptr, 0, size); - return ptr; -} //end of the function GetClearedMemoryLabelled -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void *GetClearedHunkMemory(unsigned long size) -{ - return GetClearedMemory(size); -} //end of the function GetClearedHunkMemory -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void *GetHunkMemory(unsigned long size) -{ - return GetMemory(size); -} //end of the function GetHunkMemory -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -memoryblock_t *BlockFromPointer(void *ptr, char *str) -{ - memoryblock_t *block; - - if (!ptr) - { -#ifdef MEMDEBUG - //char *crash = (char *) NULL; - //crash[0] = 1; - Error("%s: NULL pointer\n", str); -#endif MEMDEBUG - return NULL; - } //end if - block = (memoryblock_t *) ((char *) ptr - sizeof(memoryblock_t)); - if (block->id != MEM_ID) - { - Error("%s: invalid memory block\n", str); - } //end if - if (block->ptr != ptr) - { - - Error("%s: memory block pointer invalid\n", str); - } //end if - return block; -} //end of the function BlockFromPointer -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void FreeMemory(void *ptr) -{ - memoryblock_t *block; - - block = BlockFromPointer(ptr, "FreeMemory"); - if (!block) return; - UnlinkMemoryBlock(block); - totalmemorysize -= block->size; - numblocks--; - // - free(block); -} //end of the function FreeMemory -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int MemoryByteSize(void *ptr) -{ - memoryblock_t *block; - - block = BlockFromPointer(ptr, "MemoryByteSize"); - if (!block) return 0; - return block->size; -} //end of the function MemoryByteSize -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int MemorySize(void *ptr) -{ - return MemoryByteSize(ptr); -} //end of the function MemorySize -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void PrintUsedMemorySize(void) -{ - printf("total botlib memory: %d KB\n", totalmemorysize >> 10); - printf("total memory blocks: %d\n", numblocks); -} //end of the function PrintUsedMemorySize -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void PrintMemoryLabels(void) -{ - memoryblock_t *block; - int i; - - PrintUsedMemorySize(); - i = 0; - for (block = memory; block; block = block->next) - { -#ifdef MEMDEBUG - Log_Write("%6d, %p, %8d: %24s line %6d: %s", i, block->ptr, block->size, block->file, block->line, block->label); -#endif //MEMDEBUG - i++; - } //end for -} //end of the function PrintMemoryLabels -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void DumpMemory(void) -{ - memoryblock_t *block; - - for (block = memory; block; block = memory) - { - FreeMemory(block->ptr); - } //end for - totalmemorysize = 0; -} //end of the function DumpMemory -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int TotalAllocatedMemory(void) -{ - return totalmemorysize; -} //end of the function TotalAllocatedMemory -#endif - -//=========================================================================== -// Q3 Hunk and Z_ memory management -//=========================================================================== - -typedef struct memhunk_s -{ - void *ptr; - struct memhunk_s *next; -} memhunk_t; - -memhunk_t *memhunk_high; -memhunk_t *memhunk_low; -int memhunk_high_size = 16 * 1024 * 1024; -int memhunk_low_size = 0; - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Hunk_ClearHigh(void) -{ - memhunk_t *h, *nexth; - - for (h = memhunk_high; h; h = nexth) - { - nexth = h->next; - FreeMemory(h); - } //end for - memhunk_high = NULL; - memhunk_high_size = 16 * 1024 * 1024; -} //end of the function Hunk_ClearHigh -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void *Hunk_Alloc(int size) -{ - memhunk_t *h; - - if (!size) return (void *) memhunk_high_size; - // - h = GetClearedMemory(size + sizeof(memhunk_t)); - h->ptr = (char *) h + sizeof(memhunk_t); - h->next = memhunk_high; - memhunk_high = h; - memhunk_high_size -= size; - return h->ptr; -} //end of the function Hunk_Alloc -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void *Z_Malloc(int size) -{ - return GetClearedMemory(size); -} //end of the function Z_Malloc -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Z_Free (void *ptr) -{ - FreeMemory(ptr); -} //end of the function Z_Free +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "qbsp.h" +#include "l_log.h" + +int allocedmemory; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void PrintMemorySize(unsigned long size) +{ + unsigned long number1, number2, number3; + number1 = size >> 20; + number2 = (size & 0xFFFFF) >> 10; + number3 = (size & 0x3FF); + if (number1) Log_Print("%ld MB", number1); + if (number1 && number2) Log_Print(" and "); + if (number2) Log_Print("%ld KB", number2); + if (number2 && number3) Log_Print(" and "); + if (number3) Log_Print("%ld bytes", number3); +} //end of the function PrintFileSize + +#ifndef MEMDEBUG +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int MemorySize(void *ptr) +{ +#if defined(WIN32) || defined(_WIN32) + #ifdef __WATCOMC__ + //Intel 32 bits memory addressing, 16 bytes aligned + return (_msize(ptr) + 15) >> 4 << 4; + #else + return _msize(ptr); + #endif +#else + return 0; +#endif +} //end of the function MemorySize +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void *GetClearedMemory(int size) +{ + void *ptr; + + ptr = (void *) malloc(size); + if (!ptr) Error("out of memory"); + memset(ptr, 0, size); + allocedmemory += MemorySize(ptr); + return ptr; +} //end of the function GetClearedMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void *GetMemory(unsigned long size) +{ + void *ptr; + ptr = malloc(size); + if (!ptr) Error("out of memory"); + allocedmemory += MemorySize(ptr); + return ptr; +} //end of the function GetMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeMemory(void *ptr) +{ + allocedmemory -= MemorySize(ptr); + free(ptr); +} //end of the function FreeMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int TotalAllocatedMemory(void) +{ + return allocedmemory; +} //end of the function TotalAllocatedMemory + +#else + +#define MEM_ID 0x12345678l + +int totalmemorysize; +int numblocks; + +typedef struct memoryblock_s +{ + unsigned long int id; + void *ptr; + int size; +#ifdef MEMDEBUG + char *label; + char *file; + int line; +#endif //MEMDEBUG + struct memoryblock_s *prev, *next; +} memoryblock_t; + +memoryblock_t *memory; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void LinkMemoryBlock(memoryblock_t *block) +{ + block->prev = NULL; + block->next = memory; + if (memory) memory->prev = block; + memory = block; +} //end of the function LinkMemoryBlock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void UnlinkMemoryBlock(memoryblock_t *block) +{ + if (block->prev) block->prev->next = block->next; + else memory = block->next; + if (block->next) block->next->prev = block->prev; +} //end of the function UnlinkMemoryBlock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetMemoryDebug(unsigned long size, char *label, char *file, int line) +#else +void *GetMemory(unsigned long size) +#endif //MEMDEBUG +{ + void *ptr; + memoryblock_t *block; + + ptr = malloc(size + sizeof(memoryblock_t)); + block = (memoryblock_t *) ptr; + block->id = MEM_ID; + block->ptr = (char *) ptr + sizeof(memoryblock_t); + block->size = size + sizeof(memoryblock_t); +#ifdef MEMDEBUG + block->label = label; + block->file = file; + block->line = line; +#endif //MEMDEBUG + LinkMemoryBlock(block); + totalmemorysize += block->size; + numblocks++; + return block->ptr; +} //end of the function GetMemoryDebug +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetClearedMemoryDebug(unsigned long size, char *label, char *file, int line) +#else +void *GetClearedMemory(unsigned long size) +#endif //MEMDEBUG +{ + void *ptr; +#ifdef MEMDEBUG + ptr = GetMemoryDebug(size, label, file, line); +#else + ptr = GetMemory(size); +#endif //MEMDEBUG + memset(ptr, 0, size); + return ptr; +} //end of the function GetClearedMemoryLabelled +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void *GetClearedHunkMemory(unsigned long size) +{ + return GetClearedMemory(size); +} //end of the function GetClearedHunkMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void *GetHunkMemory(unsigned long size) +{ + return GetMemory(size); +} //end of the function GetHunkMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +memoryblock_t *BlockFromPointer(void *ptr, char *str) +{ + memoryblock_t *block; + + if (!ptr) + { +#ifdef MEMDEBUG + //char *crash = (char *) NULL; + //crash[0] = 1; + Error("%s: NULL pointer\n", str); +#endif MEMDEBUG + return NULL; + } //end if + block = (memoryblock_t *) ((char *) ptr - sizeof(memoryblock_t)); + if (block->id != MEM_ID) + { + Error("%s: invalid memory block\n", str); + } //end if + if (block->ptr != ptr) + { + + Error("%s: memory block pointer invalid\n", str); + } //end if + return block; +} //end of the function BlockFromPointer +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeMemory(void *ptr) +{ + memoryblock_t *block; + + block = BlockFromPointer(ptr, "FreeMemory"); + if (!block) return; + UnlinkMemoryBlock(block); + totalmemorysize -= block->size; + numblocks--; + // + free(block); +} //end of the function FreeMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int MemoryByteSize(void *ptr) +{ + memoryblock_t *block; + + block = BlockFromPointer(ptr, "MemoryByteSize"); + if (!block) return 0; + return block->size; +} //end of the function MemoryByteSize +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int MemorySize(void *ptr) +{ + return MemoryByteSize(ptr); +} //end of the function MemorySize +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void PrintUsedMemorySize(void) +{ + printf("total botlib memory: %d KB\n", totalmemorysize >> 10); + printf("total memory blocks: %d\n", numblocks); +} //end of the function PrintUsedMemorySize +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void PrintMemoryLabels(void) +{ + memoryblock_t *block; + int i; + + PrintUsedMemorySize(); + i = 0; + for (block = memory; block; block = block->next) + { +#ifdef MEMDEBUG + Log_Write("%6d, %p, %8d: %24s line %6d: %s", i, block->ptr, block->size, block->file, block->line, block->label); +#endif //MEMDEBUG + i++; + } //end for +} //end of the function PrintMemoryLabels +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void DumpMemory(void) +{ + memoryblock_t *block; + + for (block = memory; block; block = memory) + { + FreeMemory(block->ptr); + } //end for + totalmemorysize = 0; +} //end of the function DumpMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int TotalAllocatedMemory(void) +{ + return totalmemorysize; +} //end of the function TotalAllocatedMemory +#endif + +//=========================================================================== +// Q3 Hunk and Z_ memory management +//=========================================================================== + +typedef struct memhunk_s +{ + void *ptr; + struct memhunk_s *next; +} memhunk_t; + +memhunk_t *memhunk_high; +memhunk_t *memhunk_low; +int memhunk_high_size = 16 * 1024 * 1024; +int memhunk_low_size = 0; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Hunk_ClearHigh(void) +{ + memhunk_t *h, *nexth; + + for (h = memhunk_high; h; h = nexth) + { + nexth = h->next; + FreeMemory(h); + } //end for + memhunk_high = NULL; + memhunk_high_size = 16 * 1024 * 1024; +} //end of the function Hunk_ClearHigh +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void *Hunk_Alloc(int size) +{ + memhunk_t *h; + + if (!size) return (void *) memhunk_high_size; + // + h = GetClearedMemory(size + sizeof(memhunk_t)); + h->ptr = (char *) h + sizeof(memhunk_t); + h->next = memhunk_high; + memhunk_high = h; + memhunk_high_size -= size; + return h->ptr; +} //end of the function Hunk_Alloc +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void *Z_Malloc(int size) +{ + return GetClearedMemory(size); +} //end of the function Z_Malloc +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Z_Free (void *ptr) +{ + FreeMemory(ptr); +} //end of the function Z_Free diff --git a/code/bspc/l_mem.h b/code/bspc/l_mem.h index d517743..ba3b0d3 100755 --- a/code/bspc/l_mem.h +++ b/code/bspc/l_mem.h @@ -1,51 +1,51 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - - -//============================================================================= - -// memory.h -//#define MEMDEBUG -#undef MEMDEBUG - -#ifndef MEMDEBUG - -void *GetClearedMemory(int size); -void *GetMemory(unsigned long size); - -#else - -#define GetMemory(size) GetMemoryDebug(size, #size, __FILE__, __LINE__); -#define GetClearedMemory(size) GetClearedMemoryDebug(size, #size, __FILE__, __LINE__); -//allocate a memory block of the given size -void *GetMemoryDebug(unsigned long size, char *label, char *file, int line); -//allocate a memory block of the given size and clear it -void *GetClearedMemoryDebug(unsigned long size, char *label, char *file, int line); -// -void PrintMemoryLabels(void); -#endif //MEMDEBUG - -void FreeMemory(void *ptr); -int MemorySize(void *ptr); -void PrintMemorySize(unsigned long size); -int TotalAllocatedMemory(void); - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + + +//============================================================================= + +// memory.h +//#define MEMDEBUG +#undef MEMDEBUG + +#ifndef MEMDEBUG + +void *GetClearedMemory(int size); +void *GetMemory(unsigned long size); + +#else + +#define GetMemory(size) GetMemoryDebug(size, #size, __FILE__, __LINE__); +#define GetClearedMemory(size) GetClearedMemoryDebug(size, #size, __FILE__, __LINE__); +//allocate a memory block of the given size +void *GetMemoryDebug(unsigned long size, char *label, char *file, int line); +//allocate a memory block of the given size and clear it +void *GetClearedMemoryDebug(unsigned long size, char *label, char *file, int line); +// +void PrintMemoryLabels(void); +#endif //MEMDEBUG + +void FreeMemory(void *ptr); +int MemorySize(void *ptr); +void PrintMemorySize(unsigned long size); +int TotalAllocatedMemory(void); + diff --git a/code/bspc/l_poly.c b/code/bspc/l_poly.c index fea2bef..7627d20 100755 --- a/code/bspc/l_poly.c +++ b/code/bspc/l_poly.c @@ -1,1411 +1,1411 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include -#include "l_cmd.h" -#include "l_math.h" -#include "l_poly.h" -#include "l_log.h" -#include "l_mem.h" - -#define BOGUS_RANGE 65535 - -extern int numthreads; - -// 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; -int c_windingmemory; -int c_peak_windingmemory; - -char windingerror[1024]; - -void pw(winding_t *w) -{ - int i; - for (i=0 ; inumpoints ; i++) - printf ("(%5.3f, %5.3f, %5.3f)\n",w->p[i][0], w->p[i][1],w->p[i][2]); -} - - -void ResetWindings(void) -{ - c_active_windings = 0; - c_peak_windings = 0; - c_winding_allocs = 0; - c_winding_points = 0; - c_windingmemory = 0; - c_peak_windingmemory = 0; - - strcpy(windingerror, ""); -} //end of the function ResetWindings -/* -============= -AllocWinding -============= -*/ -winding_t *AllocWinding (int points) -{ - winding_t *w; - int s; - - s = sizeof(vec_t)*3*points + sizeof(int); - w = GetMemory(s); - memset(w, 0, s); - - if (numthreads == 1) - { - c_winding_allocs++; - c_winding_points += points; - c_active_windings++; - if (c_active_windings > c_peak_windings) - c_peak_windings = c_active_windings; - c_windingmemory += MemorySize(w); - if (c_windingmemory > c_peak_windingmemory) - c_peak_windingmemory = c_windingmemory; - } //end if - return w; -} //end of the function AllocWinding - -void FreeWinding (winding_t *w) -{ - if (*(unsigned *)w == 0xdeaddead) - Error ("FreeWinding: freed a freed winding"); - - if (numthreads == 1) - { - c_active_windings--; - c_windingmemory -= MemorySize(w); - } //end if - - *(unsigned *)w = 0xdeaddead; - - FreeMemory(w); -} //end of the function FreeWinding - -int WindingMemory(void) -{ - return c_windingmemory; -} //end of the function WindingMemory - -int WindingPeakMemory(void) -{ - return c_peak_windingmemory; -} //end of the function WindingPeakMemory - -int ActiveWindings(void) -{ - return c_active_windings; -} //end of the function ActiveWindings -/* -============ -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); - VectorNormalize(v1); - VectorNormalize(v2); - if (DotProduct(v1, v2) < 0.999) - { - if (nump >= MAX_POINTS_ON_WINDING) - Error("RemoveColinearPoints: MAX_POINTS_ON_WINDING"); - VectorCopy (w->p[i], p[nump]); - nump++; - } - } - - if (nump == w->numpoints) - return; - - if (numthreads == 1) - 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; - int i; - - //find two vectors each longer than 0.5 units - for (i = 0; i < w->numpoints; i++) - { - VectorSubtract(w->p[(i+1) % w->numpoints], w->p[i], v1); - VectorSubtract(w->p[(i+2) % w->numpoints], w->p[i], v2); - if (VectorLength(v1) > 0.5 && VectorLength(v2) > 0.5) break; - } //end for - CrossProduct(v2, v1, normal); - VectorNormalize(normal); - *dist = DotProduct(w->p[0], normal); -} //end of the function WindingPlane - -/* -============= -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] = 99999; - maxs[0] = maxs[1] = maxs[2] = -99999; - - 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 = -BOGUS_RANGE; - x = -1; - for (i=0 ; i<3; i++) - { - v = fabs(normal[i]); - if (v > max) - { - x = i; - max = v; - } - } - if (x==-1) - Error ("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); - VectorNormalize (vup); - - VectorScale (normal, dist, org); - - CrossProduct (vup, normal, vright); - - VectorScale (vup, BOGUS_RANGE, vup); - VectorScale (vright, BOGUS_RANGE, 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]; - //MrElusive: DOH can't use statics when unsing multithreading!!! - 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) - Error ("ClipWinding: points exceeded estimate"); - if (f->numpoints > MAX_POINTS_ON_WINDING || b->numpoints > MAX_POINTS_ON_WINDING) - Error ("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]; - //MrElusive: DOH can't use statics when unsing multithreading!!! - 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) - Error ("ClipWinding: points exceeded estimate"); - if (f->numpoints > MAX_POINTS_ON_WINDING) - Error ("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) - Error ("CheckWinding: %i points",w->numpoints); - - area = WindingArea(w); - if (area < 1) - Error ("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] > BOGUS_RANGE || p1[j] < -BOGUS_RANGE) - Error ("CheckWinding: 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) - Error ("CheckWinding: point off plane"); - - // check the edge isnt degenerate - p2 = w->p[j]; - VectorSubtract (p2, p1, dir); - - if (VectorLength (dir) < ON_EPSILON) - Error ("CheckWinding: degenerate edge"); - - CrossProduct (facenormal, dir, edgenormal); - VectorNormalize (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) - Error ("CheckWinding: non-convex"); - } - } -} - - -/* -============ -WindingOnPlaneSide -============ -*/ -int WindingOnPlaneSide (winding_t *w, vec3_t normal, vec_t dist) -{ - qboolean front, back; - int i; - vec_t d; - - front = false; - back = false; - for (i=0 ; inumpoints ; i++) - { - d = DotProduct (w->p[i], normal) - dist; - if (d < -ON_EPSILON) - { - if (front) - return SIDE_CROSS; - back = true; - continue; - } - if (d > ON_EPSILON) - { - if (back) - return SIDE_CROSS; - front = true; - continue; - } - } - - if (back) - return SIDE_BACK; - if (front) - return SIDE_FRONT; - return SIDE_ON; -} - -//#ifdef ME - #define CONTINUOUS_EPSILON 0.005 -//#else -// #define CONTINUOUS_EPSILON 0.001 -//#endif - -/* -============= -TryMergeWinding - -If two polygons share a common edge and the edges that meet at the -common points are both inside the other polygons, merge them - -Returns NULL if the faces couldn't be merged, or the new face. -The originals will NOT be freed. -============= -*/ - -winding_t *TryMergeWinding (winding_t *f1, winding_t *f2, vec3_t planenormal) -{ - vec_t *p1, *p2, *p3, *p4, *back; - winding_t *newf; - int i, j, k, l; - vec3_t normal, delta; - vec_t dot; - qboolean keep1, keep2; - - - // - // find a common edge - // - p1 = p2 = NULL; // stop compiler warning - j = 0; // - - for (i = 0; i < f1->numpoints; i++) - { - p1 = f1->p[i]; - p2 = f1->p[(i+1) % f1->numpoints]; - for (j = 0; j < f2->numpoints; j++) - { - p3 = f2->p[j]; - p4 = f2->p[(j+1) % f2->numpoints]; - for (k = 0; k < 3; k++) - { - if (fabs(p1[k] - p4[k]) > 0.1)//EQUAL_EPSILON) //ME - break; - if (fabs(p2[k] - p3[k]) > 0.1)//EQUAL_EPSILON) //ME - break; - } //end for - if (k==3) - break; - } //end for - if (j < f2->numpoints) - break; - } //end for - - if (i == f1->numpoints) - return NULL; // no matching edges - - // - // check slope of connected lines - // if the slopes are colinear, the point can be removed - // - back = f1->p[(i+f1->numpoints-1)%f1->numpoints]; - VectorSubtract (p1, back, delta); - CrossProduct (planenormal, delta, normal); - VectorNormalize (normal); - - back = f2->p[(j+2)%f2->numpoints]; - VectorSubtract (back, p1, delta); - dot = DotProduct (delta, normal); - if (dot > CONTINUOUS_EPSILON) - return NULL; // not a convex polygon - keep1 = (qboolean)(dot < -CONTINUOUS_EPSILON); - - back = f1->p[(i+2)%f1->numpoints]; - VectorSubtract (back, p2, delta); - CrossProduct (planenormal, delta, normal); - VectorNormalize (normal); - - back = f2->p[(j+f2->numpoints-1)%f2->numpoints]; - VectorSubtract (back, p2, delta); - dot = DotProduct (delta, normal); - if (dot > CONTINUOUS_EPSILON) - return NULL; // not a convex polygon - keep2 = (qboolean)(dot < -CONTINUOUS_EPSILON); - - // - // build the new polygon - // - newf = AllocWinding (f1->numpoints + f2->numpoints); - - // copy first polygon - for (k=(i+1)%f1->numpoints ; k != i ; k=(k+1)%f1->numpoints) - { - if (k==(i+1)%f1->numpoints && !keep2) - continue; - - VectorCopy (f1->p[k], newf->p[newf->numpoints]); - newf->numpoints++; - } - - // copy second polygon - for (l= (j+1)%f2->numpoints ; l != j ; l=(l+1)%f2->numpoints) - { - if (l==(j+1)%f2->numpoints && !keep1) - continue; - VectorCopy (f2->p[l], newf->p[newf->numpoints]); - newf->numpoints++; - } - - return newf; -} - -//#ifdef ME -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -winding_t *MergeWindings(winding_t *w1, winding_t *w2, vec3_t planenormal) -{ - winding_t *neww; - float dist; - int i, j, n, found, insertafter; - int sides[MAX_POINTS_ON_WINDING+4]; - vec3_t newp[MAX_POINTS_ON_WINDING+4]; - int numpoints; - vec3_t edgevec, sepnormal, v; - - RemoveEqualPoints(w1, 0.2); - numpoints = w1->numpoints; - memcpy(newp, w1->p, w1->numpoints * sizeof(vec3_t)); - // - for (i = 0; i < w2->numpoints; i++) - { - VectorCopy(w2->p[i], v); - for (j = 0; j < numpoints; j++) - { - VectorSubtract(newp[(j+1)%numpoints], - newp[(j)%numpoints], edgevec); - CrossProduct(edgevec, planenormal, sepnormal); - VectorNormalize(sepnormal); - if (VectorLength(sepnormal) < 0.9) - { - //remove the point from the new winding - for (n = j; n < numpoints-1; n++) - { - VectorCopy(newp[n+1], newp[n]); - sides[n] = sides[n+1]; - } //end for - numpoints--; - j--; - Log_Print("MergeWindings: degenerate edge on winding %f %f %f\n", sepnormal[0], - sepnormal[1], - sepnormal[2]); - continue; - } //end if - dist = DotProduct(newp[(j)%numpoints], sepnormal); - if (DotProduct(v, sepnormal) - dist < -0.1) sides[j] = SIDE_BACK; - else sides[j] = SIDE_FRONT; - } //end for - //remove all unnecesary points - for (j = 0; j < numpoints;) - { - if (sides[j] == SIDE_BACK - && sides[(j+1)%numpoints] == SIDE_BACK) - { - //remove the point from the new winding - for (n = (j+1)%numpoints; n < numpoints-1; n++) - { - VectorCopy(newp[n+1], newp[n]); - sides[n] = sides[n+1]; - } //end for - numpoints--; - } //end if - else - { - j++; - } //end else - } //end for - // - found = false; - for (j = 0; j < numpoints; j++) - { - if (sides[j] == SIDE_FRONT - && sides[(j+1)%numpoints] == SIDE_BACK) - { - if (found) Log_Print("Warning: MergeWindings: front to back found twice\n"); - found = true; - } //end if - } //end for - // - for (j = 0; j < numpoints; j++) - { - if (sides[j] == SIDE_FRONT - && sides[(j+1)%numpoints] == SIDE_BACK) - { - insertafter = (j+1)%numpoints; - //insert the new point after j+1 - for (n = numpoints-1; n > insertafter; n--) - { - VectorCopy(newp[n], newp[n+1]); - } //end for - numpoints++; - VectorCopy(v, newp[(insertafter+1)%numpoints]); - break; - } //end if - } //end for - } //end for - neww = AllocWinding(numpoints); - neww->numpoints = numpoints; - memcpy(neww->p, newp, numpoints * sizeof(vec3_t)); - RemoveColinearPoints(neww); - return neww; -} //end of the function MergeWindings -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -char *WindingErrorString(void) -{ - return windingerror; -} //end of the function WindingErrorString -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int WindingError(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) - { - sprintf(windingerror, "winding %i points", w->numpoints); - return WE_NOTENOUGHPOINTS; - } //end if - - area = WindingArea(w); - if (area < 1) - { - sprintf(windingerror, "winding %f area", area); - return WE_SMALLAREA; - } //end if - - WindingPlane (w, facenormal, &facedist); - - for (i=0 ; inumpoints ; i++) - { - p1 = w->p[i]; - - for (j=0 ; j<3 ; j++) - { - if (p1[j] > BOGUS_RANGE || p1[j] < -BOGUS_RANGE) - { - sprintf(windingerror, "winding point %d BUGUS_RANGE \'%f %f %f\'", j, p1[0], p1[1], p1[2]); - return WE_POINTBOGUSRANGE; - } //end if - } //end for - - 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) - { - sprintf(windingerror, "winding point %d off plane", i); - return WE_POINTOFFPLANE; - } //end if - - // check the edge isnt degenerate - p2 = w->p[j]; - VectorSubtract (p2, p1, dir); - - if (VectorLength (dir) < ON_EPSILON) - { - sprintf(windingerror, "winding degenerate edge %d-%d", i, j); - return WE_DEGENERATEEDGE; - } //end if - - CrossProduct (facenormal, dir, edgenormal); - VectorNormalize (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) - { - sprintf(windingerror, "winding non-convex"); - return WE_NONCONVEX; - } //end if - } //end for - } //end for - return WE_NONE; -} //end of the function WindingError -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void RemoveEqualPoints(winding_t *w, float epsilon) -{ - int i, nump; - vec3_t v; - vec3_t p[MAX_POINTS_ON_WINDING]; - - VectorCopy(w->p[0], p[0]); - nump = 1; - for (i = 1; i < w->numpoints; i++) - { - VectorSubtract(w->p[i], p[nump-1], v); - if (VectorLength(v) > epsilon) - { - if (nump >= MAX_POINTS_ON_WINDING) - Error("RemoveColinearPoints: MAX_POINTS_ON_WINDING"); - VectorCopy (w->p[i], p[nump]); - nump++; - } //end if - } //end for - - if (nump == w->numpoints) - return; - - w->numpoints = nump; - memcpy(w->p, p, nump * sizeof(p[0])); -} //end of the function RemoveEqualPoints -//=========================================================================== -// adds the given point to a winding at the given spot -// (for instance when spot is zero then the point is added at position zero) -// the original winding is NOT freed -// -// Parameter: - -// Returns: the new winding with the added point -// Changes Globals: - -//=========================================================================== -winding_t *AddWindingPoint(winding_t *w, vec3_t point, int spot) -{ - int i, j; - winding_t *neww; - - if (spot > w->numpoints) - { - Error("AddWindingPoint: num > w->numpoints"); - } //end if - if (spot < 0) - { - Error("AddWindingPoint: num < 0"); - } //end if - neww = AllocWinding(w->numpoints + 1); - neww->numpoints = w->numpoints + 1; - for (i = 0, j = 0; i < neww->numpoints; i++) - { - if (i == spot) - { - VectorCopy(point, neww->p[i]); - } //end if - else - { - VectorCopy(w->p[j], neww->p[i]); - j++; - } //end else - } //end for - return neww; -} //end of the function AddWindingPoint -//=========================================================================== -// the position where the new point should be added in the winding is -// stored in *spot -// -// Parameter: - -// Returns: true if the point is on the winding -// Changes Globals: - -//=========================================================================== -#define MELT_ON_EPSILON 0.2 - -int PointOnWinding(winding_t *w, vec3_t normal, float dist, vec3_t point, int *spot) -{ - int i, j; - vec3_t v1, v2; - vec3_t edgenormal, edgevec; - float edgedist, dot; - - *spot = 0; - //the point must be on the winding plane - dot = DotProduct(point, normal) - dist; - if (dot < -MELT_ON_EPSILON || dot > MELT_ON_EPSILON) return false; - // - for (i = 0; i < w->numpoints; i++) - { - j = (i+1) % w->numpoints; - //get a plane orthogonal to the winding plane through the edge - VectorSubtract(w->p[j], w->p[i], edgevec); - CrossProduct(normal, edgevec, edgenormal); - VectorNormalize(edgenormal); - edgedist = DotProduct(edgenormal, w->p[i]); - //point must be not too far from the plane - dot = DotProduct(point, edgenormal) - edgedist; - if (dot < -MELT_ON_EPSILON || dot > MELT_ON_EPSILON) continue; - //vector from first point of winding to the point to test - VectorSubtract(point, w->p[i], v1); - //vector from second point of winding to the point to test - VectorSubtract(point, w->p[j], v2); - //if the length of the vector is not larger than 0.5 units then - //the point is assumend to be the same as one of the winding points - if (VectorNormalize(v1) < 0.5) return false; - if (VectorNormalize(v2) < 0.5) return false; - //point must be between the two winding points - //(the two vectors must be directed towards each other, and on the - //same straight line) - if (DotProduct(v1, v2) < -0.99) - { - *spot = i + 1; - return true; - } //end if - } //end for - return false; -} //end of the function PointOnWinding -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int FindPlaneSeperatingWindings(winding_t *w1, winding_t *w2, vec3_t dir, - vec3_t normal, float *dist) -{ - int i, i2, j, j2, n; - int sides1[3], sides2[3]; - float dist1, dist2, dot, diff; - vec3_t normal1, normal2; - vec3_t v1, v2; - - for (i = 0; i < w1->numpoints; i++) - { - i2 = (i+1) % w1->numpoints; - // - VectorSubtract(w1->p[i2], w1->p[i], v1); - if (VectorLength(v1) < 0.1) - { - //Log_Write("FindPlaneSeperatingWindings: winding1 with degenerate edge\r\n"); - continue; - } //end if - CrossProduct(v1, dir, normal1); - VectorNormalize(normal1); - dist1 = DotProduct(normal1, w1->p[i]); - // - for (j = 0; j < w2->numpoints; j++) - { - j2 = (j+1) % w2->numpoints; - // - VectorSubtract(w2->p[j2], w2->p[j], v2); - if (VectorLength(v2) < 0.1) - { - //Log_Write("FindPlaneSeperatingWindings: winding2 with degenerate edge\r\n"); - continue; - } //end if - CrossProduct(v2, dir, normal2); - VectorNormalize(normal2); - dist2 = DotProduct(normal2, w2->p[j]); - // - diff = dist1 - dist2; - if (diff < -0.1 || diff > 0.1) - { - dist2 = -dist2; - VectorNegate(normal2, normal2); - diff = dist1 - dist2; - if (diff < -0.1 || diff > 0.1) continue; - } //end if - //check if the normal vectors are equal - for (n = 0; n < 3; n++) - { - diff = normal1[n] - normal2[n]; - if (diff < -0.0001 || diff > 0.0001) break; - } //end for - if (n != 3) continue; - //check on which side of the seperating plane the points of - //the first winding are - sides1[0] = sides1[1] = sides1[2] = 0; - for (n = 0; n < w1->numpoints; n++) - { - dot = DotProduct(w1->p[n], normal1) - dist1; - if (dot > 0.1) sides1[0]++; - else if (dot < -0.1) sides1[1]++; - else sides1[2]++; - } //end for - //check on which side of the seperating plane the points of - //the second winding are - sides2[0] = sides2[1] = sides2[2] = 0; - for (n = 0; n < w2->numpoints; n++) - { - //used normal1 and dist1 (they are equal to normal2 and dist2) - dot = DotProduct(w2->p[n], normal1) - dist1; - if (dot > 0.1) sides2[0]++; - else if (dot < -0.1) sides2[1]++; - else sides2[2]++; - } //end for - //if the first winding has points at both sides - if (sides1[0] && sides1[1]) - { - Log_Write("FindPlaneSeperatingWindings: winding1 non-convex\r\n"); - continue; - } //end if - //if the second winding has points at both sides - if (sides2[0] && sides2[1]) - { - Log_Write("FindPlaneSeperatingWindings: winding2 non-convex\r\n"); - continue; - } //end if - // - if ((!sides1[0] && !sides1[1]) || (!sides2[0] && !sides2[1])) - { - //don't use one of the winding planes as the seperating plane - continue; - } //end if - //the windings must be at different sides of the seperating plane - if ((!sides1[0] && !sides2[1]) || (!sides1[1] && !sides2[0])) - { - VectorCopy(normal1, normal); - *dist = dist1; - return true; - } //end if - } //end for - } //end for - return false; -} //end of the function FindPlaneSeperatingWindings -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -#define WCONVEX_EPSILON 0.2 - -int WindingsNonConvex(winding_t *w1, winding_t *w2, - vec3_t normal1, vec3_t normal2, - float dist1, float dist2) -{ - int i; - - if (!w1 || !w2) return false; - - //check if one of the points of face1 is at the back of the plane of face2 - for (i = 0; i < w1->numpoints; i++) - { - if (DotProduct(normal2, w1->p[i]) - dist2 > WCONVEX_EPSILON) return true; - } //end for - //check if one of the points of face2 is at the back of the plane of face1 - for (i = 0; i < w2->numpoints; i++) - { - if (DotProduct(normal1, w2->p[i]) - dist1 > WCONVEX_EPSILON) return true; - } //end for - - return false; -} //end of the function WindingsNonConvex -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -/* -#define VERTEX_EPSILON 0.5 - -qboolean EqualVertexes(vec3_t v1, vec3_t v2) -{ - float diff; - - diff = v1[0] - v2[0]; - if (diff > -VERTEX_EPSILON && diff < VERTEX_EPSILON) - { - diff = v1[1] - v2[1]; - if (diff > -VERTEX_EPSILON && diff < VERTEX_EPSILON) - { - diff = v1[2] - v2[2]; - if (diff > -VERTEX_EPSILON && diff < VERTEX_EPSILON) - { - return true; - } //end if - } //end if - } //end if - return false; -} //end of the function EqualVertexes - -#define CONTINUOUS_EPSILON 0.001 - -winding_t *AAS_MergeWindings(winding_t *w1, winding_t *w2, vec3_t windingnormal) -{ - int n, i, k; - vec3_t normal, delta; - winding_t *winding, *neww; - float dist, dot; - int p1, p2; - int points[2][64]; - int numpoints[2] = {0, 0}; - int newnumpoints; - int keep[2]; - - if (!FindPlaneSeperatingWindings(w1, w2, windingnormal, normal, &dist)) return NULL; - - //for both windings - for (n = 0; n < 2; n++) - { - if (n == 0) winding = w1; - else winding = w2; - //get the points of the winding which are on the seperating plane - for (i = 0; i < winding->numpoints; i++) - { - dot = DotProduct(winding->p[i], normal) - dist; - if (dot > -ON_EPSILON && dot < ON_EPSILON) - { - //don't allow more than 64 points on the seperating plane - if (numpoints[n] >= 64) Error("AAS_MergeWindings: more than 64 points on seperating plane\n"); - points[n][numpoints[n]++] = i; - } //end if - } //end for - //there must be at least two points of each winding on the seperating plane - if (numpoints[n] < 2) return NULL; - } //end for - - //if the first point of winding1 (which is on the seperating plane) is unequal - //to the last point of winding2 (which is on the seperating plane) - if (!EqualVertexes(w1->p[points[0][0]], w2->p[points[1][numpoints[1]-1]])) - { - return NULL; - } //end if - //if the last point of winding1 (which is on the seperating plane) is unequal - //to the first point of winding2 (which is on the seperating plane) - if (!EqualVertexes(w1->p[points[0][numpoints[0]-1]], w2->p[points[1][0]])) - { - return NULL; - } //end if - // - // check slope of connected lines - // if the slopes are colinear, the point can be removed - // - //first point of winding1 which is on the seperating plane - p1 = points[0][0]; - //point before p1 - p2 = (p1 + w1->numpoints - 1) % w1->numpoints; - VectorSubtract(w1->p[p1], w1->p[p2], delta); - CrossProduct(windingnormal, delta, normal); - VectorNormalize(normal, normal); - - //last point of winding2 which is on the seperating plane - p1 = points[1][numpoints[1]-1]; - //point after p1 - p2 = (p1 + 1) % w2->numpoints; - VectorSubtract(w2->p[p2], w2->p[p1], delta); - dot = DotProduct(delta, normal); - if (dot > CONTINUOUS_EPSILON) return NULL; //merging would create a non-convex polygon - keep[0] = (qboolean)(dot < -CONTINUOUS_EPSILON); - - //first point of winding2 which is on the seperating plane - p1 = points[1][0]; - //point before p1 - p2 = (p1 + w2->numpoints - 1) % w2->numpoints; - VectorSubtract(w2->p[p1], w2->p[p2], delta); - CrossProduct(windingnormal, delta, normal); - VectorNormalize(normal, normal); - - //last point of winding1 which is on the seperating plane - p1 = points[0][numpoints[0]-1]; - //point after p1 - p2 = (p1 + 1) % w1->numpoints; - VectorSubtract(w1->p[p2], w1->p[p1], delta); - dot = DotProduct(delta, normal); - if (dot > CONTINUOUS_EPSILON) return NULL; //merging would create a non-convex polygon - keep[1] = (qboolean)(dot < -CONTINUOUS_EPSILON); - - //number of points on the new winding - newnumpoints = w1->numpoints - numpoints[0] + w2->numpoints - numpoints[1] + 2; - //allocate the winding - neww = AllocWinding(newnumpoints); - neww->numpoints = newnumpoints; - //copy all the points - k = 0; - //for both windings - for (n = 0; n < 2; n++) - { - if (n == 0) winding = w1; - else winding = w2; - //copy the points of the winding starting with the last point on the - //seperating plane and ending before the first point on the seperating plane - for (i = points[n][numpoints[n]-1]; i != points[n][0]; i = (i+1)%winding->numpoints) - { - if (k >= newnumpoints) - { - Log_Print("numpoints[0] = %d\n", numpoints[0]); - Log_Print("numpoints[1] = %d\n", numpoints[1]); - Error("AAS_MergeWindings: k = %d >= newnumpoints = %d\n", k, newnumpoints); - } //end if - VectorCopy(winding->p[i], neww->p[k]); - k++; - } //end for - } //end for - RemoveEqualPoints(neww); - if (!WindingIsOk(neww, 1)) - { - Log_Print("AAS_MergeWindings: winding not ok after merging\n"); - FreeWinding(neww); - return NULL; - } //end if - return neww; -} //end of the function AAS_MergeWindings*/ -//#endif //ME +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include +#include "l_cmd.h" +#include "l_math.h" +#include "l_poly.h" +#include "l_log.h" +#include "l_mem.h" + +#define BOGUS_RANGE 65535 + +extern int numthreads; + +// 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; +int c_windingmemory; +int c_peak_windingmemory; + +char windingerror[1024]; + +void pw(winding_t *w) +{ + int i; + for (i=0 ; inumpoints ; i++) + printf ("(%5.3f, %5.3f, %5.3f)\n",w->p[i][0], w->p[i][1],w->p[i][2]); +} + + +void ResetWindings(void) +{ + c_active_windings = 0; + c_peak_windings = 0; + c_winding_allocs = 0; + c_winding_points = 0; + c_windingmemory = 0; + c_peak_windingmemory = 0; + + strcpy(windingerror, ""); +} //end of the function ResetWindings +/* +============= +AllocWinding +============= +*/ +winding_t *AllocWinding (int points) +{ + winding_t *w; + int s; + + s = sizeof(vec_t)*3*points + sizeof(int); + w = GetMemory(s); + memset(w, 0, s); + + if (numthreads == 1) + { + c_winding_allocs++; + c_winding_points += points; + c_active_windings++; + if (c_active_windings > c_peak_windings) + c_peak_windings = c_active_windings; + c_windingmemory += MemorySize(w); + if (c_windingmemory > c_peak_windingmemory) + c_peak_windingmemory = c_windingmemory; + } //end if + return w; +} //end of the function AllocWinding + +void FreeWinding (winding_t *w) +{ + if (*(unsigned *)w == 0xdeaddead) + Error ("FreeWinding: freed a freed winding"); + + if (numthreads == 1) + { + c_active_windings--; + c_windingmemory -= MemorySize(w); + } //end if + + *(unsigned *)w = 0xdeaddead; + + FreeMemory(w); +} //end of the function FreeWinding + +int WindingMemory(void) +{ + return c_windingmemory; +} //end of the function WindingMemory + +int WindingPeakMemory(void) +{ + return c_peak_windingmemory; +} //end of the function WindingPeakMemory + +int ActiveWindings(void) +{ + return c_active_windings; +} //end of the function ActiveWindings +/* +============ +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); + VectorNormalize(v1); + VectorNormalize(v2); + if (DotProduct(v1, v2) < 0.999) + { + if (nump >= MAX_POINTS_ON_WINDING) + Error("RemoveColinearPoints: MAX_POINTS_ON_WINDING"); + VectorCopy (w->p[i], p[nump]); + nump++; + } + } + + if (nump == w->numpoints) + return; + + if (numthreads == 1) + 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; + int i; + + //find two vectors each longer than 0.5 units + for (i = 0; i < w->numpoints; i++) + { + VectorSubtract(w->p[(i+1) % w->numpoints], w->p[i], v1); + VectorSubtract(w->p[(i+2) % w->numpoints], w->p[i], v2); + if (VectorLength(v1) > 0.5 && VectorLength(v2) > 0.5) break; + } //end for + CrossProduct(v2, v1, normal); + VectorNormalize(normal); + *dist = DotProduct(w->p[0], normal); +} //end of the function WindingPlane + +/* +============= +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] = 99999; + maxs[0] = maxs[1] = maxs[2] = -99999; + + 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 = -BOGUS_RANGE; + x = -1; + for (i=0 ; i<3; i++) + { + v = fabs(normal[i]); + if (v > max) + { + x = i; + max = v; + } + } + if (x==-1) + Error ("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); + VectorNormalize (vup); + + VectorScale (normal, dist, org); + + CrossProduct (vup, normal, vright); + + VectorScale (vup, BOGUS_RANGE, vup); + VectorScale (vright, BOGUS_RANGE, 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]; + //MrElusive: DOH can't use statics when unsing multithreading!!! + 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) + Error ("ClipWinding: points exceeded estimate"); + if (f->numpoints > MAX_POINTS_ON_WINDING || b->numpoints > MAX_POINTS_ON_WINDING) + Error ("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]; + //MrElusive: DOH can't use statics when unsing multithreading!!! + 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) + Error ("ClipWinding: points exceeded estimate"); + if (f->numpoints > MAX_POINTS_ON_WINDING) + Error ("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) + Error ("CheckWinding: %i points",w->numpoints); + + area = WindingArea(w); + if (area < 1) + Error ("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] > BOGUS_RANGE || p1[j] < -BOGUS_RANGE) + Error ("CheckWinding: 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) + Error ("CheckWinding: point off plane"); + + // check the edge isnt degenerate + p2 = w->p[j]; + VectorSubtract (p2, p1, dir); + + if (VectorLength (dir) < ON_EPSILON) + Error ("CheckWinding: degenerate edge"); + + CrossProduct (facenormal, dir, edgenormal); + VectorNormalize (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) + Error ("CheckWinding: non-convex"); + } + } +} + + +/* +============ +WindingOnPlaneSide +============ +*/ +int WindingOnPlaneSide (winding_t *w, vec3_t normal, vec_t dist) +{ + qboolean front, back; + int i; + vec_t d; + + front = false; + back = false; + for (i=0 ; inumpoints ; i++) + { + d = DotProduct (w->p[i], normal) - dist; + if (d < -ON_EPSILON) + { + if (front) + return SIDE_CROSS; + back = true; + continue; + } + if (d > ON_EPSILON) + { + if (back) + return SIDE_CROSS; + front = true; + continue; + } + } + + if (back) + return SIDE_BACK; + if (front) + return SIDE_FRONT; + return SIDE_ON; +} + +//#ifdef ME + #define CONTINUOUS_EPSILON 0.005 +//#else +// #define CONTINUOUS_EPSILON 0.001 +//#endif + +/* +============= +TryMergeWinding + +If two polygons share a common edge and the edges that meet at the +common points are both inside the other polygons, merge them + +Returns NULL if the faces couldn't be merged, or the new face. +The originals will NOT be freed. +============= +*/ + +winding_t *TryMergeWinding (winding_t *f1, winding_t *f2, vec3_t planenormal) +{ + vec_t *p1, *p2, *p3, *p4, *back; + winding_t *newf; + int i, j, k, l; + vec3_t normal, delta; + vec_t dot; + qboolean keep1, keep2; + + + // + // find a common edge + // + p1 = p2 = NULL; // stop compiler warning + j = 0; // + + for (i = 0; i < f1->numpoints; i++) + { + p1 = f1->p[i]; + p2 = f1->p[(i+1) % f1->numpoints]; + for (j = 0; j < f2->numpoints; j++) + { + p3 = f2->p[j]; + p4 = f2->p[(j+1) % f2->numpoints]; + for (k = 0; k < 3; k++) + { + if (fabs(p1[k] - p4[k]) > 0.1)//EQUAL_EPSILON) //ME + break; + if (fabs(p2[k] - p3[k]) > 0.1)//EQUAL_EPSILON) //ME + break; + } //end for + if (k==3) + break; + } //end for + if (j < f2->numpoints) + break; + } //end for + + if (i == f1->numpoints) + return NULL; // no matching edges + + // + // check slope of connected lines + // if the slopes are colinear, the point can be removed + // + back = f1->p[(i+f1->numpoints-1)%f1->numpoints]; + VectorSubtract (p1, back, delta); + CrossProduct (planenormal, delta, normal); + VectorNormalize (normal); + + back = f2->p[(j+2)%f2->numpoints]; + VectorSubtract (back, p1, delta); + dot = DotProduct (delta, normal); + if (dot > CONTINUOUS_EPSILON) + return NULL; // not a convex polygon + keep1 = (qboolean)(dot < -CONTINUOUS_EPSILON); + + back = f1->p[(i+2)%f1->numpoints]; + VectorSubtract (back, p2, delta); + CrossProduct (planenormal, delta, normal); + VectorNormalize (normal); + + back = f2->p[(j+f2->numpoints-1)%f2->numpoints]; + VectorSubtract (back, p2, delta); + dot = DotProduct (delta, normal); + if (dot > CONTINUOUS_EPSILON) + return NULL; // not a convex polygon + keep2 = (qboolean)(dot < -CONTINUOUS_EPSILON); + + // + // build the new polygon + // + newf = AllocWinding (f1->numpoints + f2->numpoints); + + // copy first polygon + for (k=(i+1)%f1->numpoints ; k != i ; k=(k+1)%f1->numpoints) + { + if (k==(i+1)%f1->numpoints && !keep2) + continue; + + VectorCopy (f1->p[k], newf->p[newf->numpoints]); + newf->numpoints++; + } + + // copy second polygon + for (l= (j+1)%f2->numpoints ; l != j ; l=(l+1)%f2->numpoints) + { + if (l==(j+1)%f2->numpoints && !keep1) + continue; + VectorCopy (f2->p[l], newf->p[newf->numpoints]); + newf->numpoints++; + } + + return newf; +} + +//#ifdef ME +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +winding_t *MergeWindings(winding_t *w1, winding_t *w2, vec3_t planenormal) +{ + winding_t *neww; + float dist; + int i, j, n, found, insertafter; + int sides[MAX_POINTS_ON_WINDING+4]; + vec3_t newp[MAX_POINTS_ON_WINDING+4]; + int numpoints; + vec3_t edgevec, sepnormal, v; + + RemoveEqualPoints(w1, 0.2); + numpoints = w1->numpoints; + memcpy(newp, w1->p, w1->numpoints * sizeof(vec3_t)); + // + for (i = 0; i < w2->numpoints; i++) + { + VectorCopy(w2->p[i], v); + for (j = 0; j < numpoints; j++) + { + VectorSubtract(newp[(j+1)%numpoints], + newp[(j)%numpoints], edgevec); + CrossProduct(edgevec, planenormal, sepnormal); + VectorNormalize(sepnormal); + if (VectorLength(sepnormal) < 0.9) + { + //remove the point from the new winding + for (n = j; n < numpoints-1; n++) + { + VectorCopy(newp[n+1], newp[n]); + sides[n] = sides[n+1]; + } //end for + numpoints--; + j--; + Log_Print("MergeWindings: degenerate edge on winding %f %f %f\n", sepnormal[0], + sepnormal[1], + sepnormal[2]); + continue; + } //end if + dist = DotProduct(newp[(j)%numpoints], sepnormal); + if (DotProduct(v, sepnormal) - dist < -0.1) sides[j] = SIDE_BACK; + else sides[j] = SIDE_FRONT; + } //end for + //remove all unnecesary points + for (j = 0; j < numpoints;) + { + if (sides[j] == SIDE_BACK + && sides[(j+1)%numpoints] == SIDE_BACK) + { + //remove the point from the new winding + for (n = (j+1)%numpoints; n < numpoints-1; n++) + { + VectorCopy(newp[n+1], newp[n]); + sides[n] = sides[n+1]; + } //end for + numpoints--; + } //end if + else + { + j++; + } //end else + } //end for + // + found = false; + for (j = 0; j < numpoints; j++) + { + if (sides[j] == SIDE_FRONT + && sides[(j+1)%numpoints] == SIDE_BACK) + { + if (found) Log_Print("Warning: MergeWindings: front to back found twice\n"); + found = true; + } //end if + } //end for + // + for (j = 0; j < numpoints; j++) + { + if (sides[j] == SIDE_FRONT + && sides[(j+1)%numpoints] == SIDE_BACK) + { + insertafter = (j+1)%numpoints; + //insert the new point after j+1 + for (n = numpoints-1; n > insertafter; n--) + { + VectorCopy(newp[n], newp[n+1]); + } //end for + numpoints++; + VectorCopy(v, newp[(insertafter+1)%numpoints]); + break; + } //end if + } //end for + } //end for + neww = AllocWinding(numpoints); + neww->numpoints = numpoints; + memcpy(neww->p, newp, numpoints * sizeof(vec3_t)); + RemoveColinearPoints(neww); + return neww; +} //end of the function MergeWindings +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *WindingErrorString(void) +{ + return windingerror; +} //end of the function WindingErrorString +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WindingError(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) + { + sprintf(windingerror, "winding %i points", w->numpoints); + return WE_NOTENOUGHPOINTS; + } //end if + + area = WindingArea(w); + if (area < 1) + { + sprintf(windingerror, "winding %f area", area); + return WE_SMALLAREA; + } //end if + + WindingPlane (w, facenormal, &facedist); + + for (i=0 ; inumpoints ; i++) + { + p1 = w->p[i]; + + for (j=0 ; j<3 ; j++) + { + if (p1[j] > BOGUS_RANGE || p1[j] < -BOGUS_RANGE) + { + sprintf(windingerror, "winding point %d BUGUS_RANGE \'%f %f %f\'", j, p1[0], p1[1], p1[2]); + return WE_POINTBOGUSRANGE; + } //end if + } //end for + + 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) + { + sprintf(windingerror, "winding point %d off plane", i); + return WE_POINTOFFPLANE; + } //end if + + // check the edge isnt degenerate + p2 = w->p[j]; + VectorSubtract (p2, p1, dir); + + if (VectorLength (dir) < ON_EPSILON) + { + sprintf(windingerror, "winding degenerate edge %d-%d", i, j); + return WE_DEGENERATEEDGE; + } //end if + + CrossProduct (facenormal, dir, edgenormal); + VectorNormalize (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) + { + sprintf(windingerror, "winding non-convex"); + return WE_NONCONVEX; + } //end if + } //end for + } //end for + return WE_NONE; +} //end of the function WindingError +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void RemoveEqualPoints(winding_t *w, float epsilon) +{ + int i, nump; + vec3_t v; + vec3_t p[MAX_POINTS_ON_WINDING]; + + VectorCopy(w->p[0], p[0]); + nump = 1; + for (i = 1; i < w->numpoints; i++) + { + VectorSubtract(w->p[i], p[nump-1], v); + if (VectorLength(v) > epsilon) + { + if (nump >= MAX_POINTS_ON_WINDING) + Error("RemoveColinearPoints: MAX_POINTS_ON_WINDING"); + VectorCopy (w->p[i], p[nump]); + nump++; + } //end if + } //end for + + if (nump == w->numpoints) + return; + + w->numpoints = nump; + memcpy(w->p, p, nump * sizeof(p[0])); +} //end of the function RemoveEqualPoints +//=========================================================================== +// adds the given point to a winding at the given spot +// (for instance when spot is zero then the point is added at position zero) +// the original winding is NOT freed +// +// Parameter: - +// Returns: the new winding with the added point +// Changes Globals: - +//=========================================================================== +winding_t *AddWindingPoint(winding_t *w, vec3_t point, int spot) +{ + int i, j; + winding_t *neww; + + if (spot > w->numpoints) + { + Error("AddWindingPoint: num > w->numpoints"); + } //end if + if (spot < 0) + { + Error("AddWindingPoint: num < 0"); + } //end if + neww = AllocWinding(w->numpoints + 1); + neww->numpoints = w->numpoints + 1; + for (i = 0, j = 0; i < neww->numpoints; i++) + { + if (i == spot) + { + VectorCopy(point, neww->p[i]); + } //end if + else + { + VectorCopy(w->p[j], neww->p[i]); + j++; + } //end else + } //end for + return neww; +} //end of the function AddWindingPoint +//=========================================================================== +// the position where the new point should be added in the winding is +// stored in *spot +// +// Parameter: - +// Returns: true if the point is on the winding +// Changes Globals: - +//=========================================================================== +#define MELT_ON_EPSILON 0.2 + +int PointOnWinding(winding_t *w, vec3_t normal, float dist, vec3_t point, int *spot) +{ + int i, j; + vec3_t v1, v2; + vec3_t edgenormal, edgevec; + float edgedist, dot; + + *spot = 0; + //the point must be on the winding plane + dot = DotProduct(point, normal) - dist; + if (dot < -MELT_ON_EPSILON || dot > MELT_ON_EPSILON) return false; + // + for (i = 0; i < w->numpoints; i++) + { + j = (i+1) % w->numpoints; + //get a plane orthogonal to the winding plane through the edge + VectorSubtract(w->p[j], w->p[i], edgevec); + CrossProduct(normal, edgevec, edgenormal); + VectorNormalize(edgenormal); + edgedist = DotProduct(edgenormal, w->p[i]); + //point must be not too far from the plane + dot = DotProduct(point, edgenormal) - edgedist; + if (dot < -MELT_ON_EPSILON || dot > MELT_ON_EPSILON) continue; + //vector from first point of winding to the point to test + VectorSubtract(point, w->p[i], v1); + //vector from second point of winding to the point to test + VectorSubtract(point, w->p[j], v2); + //if the length of the vector is not larger than 0.5 units then + //the point is assumend to be the same as one of the winding points + if (VectorNormalize(v1) < 0.5) return false; + if (VectorNormalize(v2) < 0.5) return false; + //point must be between the two winding points + //(the two vectors must be directed towards each other, and on the + //same straight line) + if (DotProduct(v1, v2) < -0.99) + { + *spot = i + 1; + return true; + } //end if + } //end for + return false; +} //end of the function PointOnWinding +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int FindPlaneSeperatingWindings(winding_t *w1, winding_t *w2, vec3_t dir, + vec3_t normal, float *dist) +{ + int i, i2, j, j2, n; + int sides1[3], sides2[3]; + float dist1, dist2, dot, diff; + vec3_t normal1, normal2; + vec3_t v1, v2; + + for (i = 0; i < w1->numpoints; i++) + { + i2 = (i+1) % w1->numpoints; + // + VectorSubtract(w1->p[i2], w1->p[i], v1); + if (VectorLength(v1) < 0.1) + { + //Log_Write("FindPlaneSeperatingWindings: winding1 with degenerate edge\r\n"); + continue; + } //end if + CrossProduct(v1, dir, normal1); + VectorNormalize(normal1); + dist1 = DotProduct(normal1, w1->p[i]); + // + for (j = 0; j < w2->numpoints; j++) + { + j2 = (j+1) % w2->numpoints; + // + VectorSubtract(w2->p[j2], w2->p[j], v2); + if (VectorLength(v2) < 0.1) + { + //Log_Write("FindPlaneSeperatingWindings: winding2 with degenerate edge\r\n"); + continue; + } //end if + CrossProduct(v2, dir, normal2); + VectorNormalize(normal2); + dist2 = DotProduct(normal2, w2->p[j]); + // + diff = dist1 - dist2; + if (diff < -0.1 || diff > 0.1) + { + dist2 = -dist2; + VectorNegate(normal2, normal2); + diff = dist1 - dist2; + if (diff < -0.1 || diff > 0.1) continue; + } //end if + //check if the normal vectors are equal + for (n = 0; n < 3; n++) + { + diff = normal1[n] - normal2[n]; + if (diff < -0.0001 || diff > 0.0001) break; + } //end for + if (n != 3) continue; + //check on which side of the seperating plane the points of + //the first winding are + sides1[0] = sides1[1] = sides1[2] = 0; + for (n = 0; n < w1->numpoints; n++) + { + dot = DotProduct(w1->p[n], normal1) - dist1; + if (dot > 0.1) sides1[0]++; + else if (dot < -0.1) sides1[1]++; + else sides1[2]++; + } //end for + //check on which side of the seperating plane the points of + //the second winding are + sides2[0] = sides2[1] = sides2[2] = 0; + for (n = 0; n < w2->numpoints; n++) + { + //used normal1 and dist1 (they are equal to normal2 and dist2) + dot = DotProduct(w2->p[n], normal1) - dist1; + if (dot > 0.1) sides2[0]++; + else if (dot < -0.1) sides2[1]++; + else sides2[2]++; + } //end for + //if the first winding has points at both sides + if (sides1[0] && sides1[1]) + { + Log_Write("FindPlaneSeperatingWindings: winding1 non-convex\r\n"); + continue; + } //end if + //if the second winding has points at both sides + if (sides2[0] && sides2[1]) + { + Log_Write("FindPlaneSeperatingWindings: winding2 non-convex\r\n"); + continue; + } //end if + // + if ((!sides1[0] && !sides1[1]) || (!sides2[0] && !sides2[1])) + { + //don't use one of the winding planes as the seperating plane + continue; + } //end if + //the windings must be at different sides of the seperating plane + if ((!sides1[0] && !sides2[1]) || (!sides1[1] && !sides2[0])) + { + VectorCopy(normal1, normal); + *dist = dist1; + return true; + } //end if + } //end for + } //end for + return false; +} //end of the function FindPlaneSeperatingWindings +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#define WCONVEX_EPSILON 0.2 + +int WindingsNonConvex(winding_t *w1, winding_t *w2, + vec3_t normal1, vec3_t normal2, + float dist1, float dist2) +{ + int i; + + if (!w1 || !w2) return false; + + //check if one of the points of face1 is at the back of the plane of face2 + for (i = 0; i < w1->numpoints; i++) + { + if (DotProduct(normal2, w1->p[i]) - dist2 > WCONVEX_EPSILON) return true; + } //end for + //check if one of the points of face2 is at the back of the plane of face1 + for (i = 0; i < w2->numpoints; i++) + { + if (DotProduct(normal1, w2->p[i]) - dist1 > WCONVEX_EPSILON) return true; + } //end for + + return false; +} //end of the function WindingsNonConvex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +/* +#define VERTEX_EPSILON 0.5 + +qboolean EqualVertexes(vec3_t v1, vec3_t v2) +{ + float diff; + + diff = v1[0] - v2[0]; + if (diff > -VERTEX_EPSILON && diff < VERTEX_EPSILON) + { + diff = v1[1] - v2[1]; + if (diff > -VERTEX_EPSILON && diff < VERTEX_EPSILON) + { + diff = v1[2] - v2[2]; + if (diff > -VERTEX_EPSILON && diff < VERTEX_EPSILON) + { + return true; + } //end if + } //end if + } //end if + return false; +} //end of the function EqualVertexes + +#define CONTINUOUS_EPSILON 0.001 + +winding_t *AAS_MergeWindings(winding_t *w1, winding_t *w2, vec3_t windingnormal) +{ + int n, i, k; + vec3_t normal, delta; + winding_t *winding, *neww; + float dist, dot; + int p1, p2; + int points[2][64]; + int numpoints[2] = {0, 0}; + int newnumpoints; + int keep[2]; + + if (!FindPlaneSeperatingWindings(w1, w2, windingnormal, normal, &dist)) return NULL; + + //for both windings + for (n = 0; n < 2; n++) + { + if (n == 0) winding = w1; + else winding = w2; + //get the points of the winding which are on the seperating plane + for (i = 0; i < winding->numpoints; i++) + { + dot = DotProduct(winding->p[i], normal) - dist; + if (dot > -ON_EPSILON && dot < ON_EPSILON) + { + //don't allow more than 64 points on the seperating plane + if (numpoints[n] >= 64) Error("AAS_MergeWindings: more than 64 points on seperating plane\n"); + points[n][numpoints[n]++] = i; + } //end if + } //end for + //there must be at least two points of each winding on the seperating plane + if (numpoints[n] < 2) return NULL; + } //end for + + //if the first point of winding1 (which is on the seperating plane) is unequal + //to the last point of winding2 (which is on the seperating plane) + if (!EqualVertexes(w1->p[points[0][0]], w2->p[points[1][numpoints[1]-1]])) + { + return NULL; + } //end if + //if the last point of winding1 (which is on the seperating plane) is unequal + //to the first point of winding2 (which is on the seperating plane) + if (!EqualVertexes(w1->p[points[0][numpoints[0]-1]], w2->p[points[1][0]])) + { + return NULL; + } //end if + // + // check slope of connected lines + // if the slopes are colinear, the point can be removed + // + //first point of winding1 which is on the seperating plane + p1 = points[0][0]; + //point before p1 + p2 = (p1 + w1->numpoints - 1) % w1->numpoints; + VectorSubtract(w1->p[p1], w1->p[p2], delta); + CrossProduct(windingnormal, delta, normal); + VectorNormalize(normal, normal); + + //last point of winding2 which is on the seperating plane + p1 = points[1][numpoints[1]-1]; + //point after p1 + p2 = (p1 + 1) % w2->numpoints; + VectorSubtract(w2->p[p2], w2->p[p1], delta); + dot = DotProduct(delta, normal); + if (dot > CONTINUOUS_EPSILON) return NULL; //merging would create a non-convex polygon + keep[0] = (qboolean)(dot < -CONTINUOUS_EPSILON); + + //first point of winding2 which is on the seperating plane + p1 = points[1][0]; + //point before p1 + p2 = (p1 + w2->numpoints - 1) % w2->numpoints; + VectorSubtract(w2->p[p1], w2->p[p2], delta); + CrossProduct(windingnormal, delta, normal); + VectorNormalize(normal, normal); + + //last point of winding1 which is on the seperating plane + p1 = points[0][numpoints[0]-1]; + //point after p1 + p2 = (p1 + 1) % w1->numpoints; + VectorSubtract(w1->p[p2], w1->p[p1], delta); + dot = DotProduct(delta, normal); + if (dot > CONTINUOUS_EPSILON) return NULL; //merging would create a non-convex polygon + keep[1] = (qboolean)(dot < -CONTINUOUS_EPSILON); + + //number of points on the new winding + newnumpoints = w1->numpoints - numpoints[0] + w2->numpoints - numpoints[1] + 2; + //allocate the winding + neww = AllocWinding(newnumpoints); + neww->numpoints = newnumpoints; + //copy all the points + k = 0; + //for both windings + for (n = 0; n < 2; n++) + { + if (n == 0) winding = w1; + else winding = w2; + //copy the points of the winding starting with the last point on the + //seperating plane and ending before the first point on the seperating plane + for (i = points[n][numpoints[n]-1]; i != points[n][0]; i = (i+1)%winding->numpoints) + { + if (k >= newnumpoints) + { + Log_Print("numpoints[0] = %d\n", numpoints[0]); + Log_Print("numpoints[1] = %d\n", numpoints[1]); + Error("AAS_MergeWindings: k = %d >= newnumpoints = %d\n", k, newnumpoints); + } //end if + VectorCopy(winding->p[i], neww->p[k]); + k++; + } //end for + } //end for + RemoveEqualPoints(neww); + if (!WindingIsOk(neww, 1)) + { + Log_Print("AAS_MergeWindings: winding not ok after merging\n"); + FreeWinding(neww); + return NULL; + } //end if + return neww; +} //end of the function AAS_MergeWindings*/ +//#endif //ME diff --git a/code/bspc/l_poly.h b/code/bspc/l_poly.h index a14b834..f3191eb 100755 --- a/code/bspc/l_poly.h +++ b/code/bspc/l_poly.h @@ -1,120 +1,120 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -//a winding gives the bounding points of a convex polygon -typedef struct -{ - int numpoints; - vec3_t p[4]; //variable sized -} winding_t; - -#define MAX_POINTS_ON_WINDING 96 - -//you can define on_epsilon in the makefile as tighter -#ifndef ON_EPSILON -#define ON_EPSILON 0.1 -#endif -//winding errors -#define WE_NONE 0 -#define WE_NOTENOUGHPOINTS 1 -#define WE_SMALLAREA 2 -#define WE_POINTBOGUSRANGE 3 -#define WE_POINTOFFPLANE 4 -#define WE_DEGENERATEEDGE 5 -#define WE_NONCONVEX 6 - -//allocates a winding -winding_t *AllocWinding (int points); -//returns the area of the winding -vec_t WindingArea (winding_t *w); -//gives the center of the winding -void WindingCenter (winding_t *w, vec3_t center); -//clips the given winding to the given plane and gives the front -//and back part of the clipped winding -void ClipWindingEpsilon (winding_t *in, vec3_t normal, vec_t dist, - vec_t epsilon, winding_t **front, winding_t **back); -//returns the fragment of the given winding 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); -//returns a copy of the given winding -winding_t *CopyWinding (winding_t *w); -//returns the reversed winding of the given one -winding_t *ReverseWinding (winding_t *w); -//returns a base winding for the given plane -winding_t *BaseWindingForPlane (vec3_t normal, vec_t dist); -//checks the winding for errors -void CheckWinding (winding_t *w); -//returns the plane normal and dist the winding is in -void WindingPlane(winding_t *w, vec3_t normal, vec_t *dist); -//removes colinear points from the winding -void RemoveColinearPoints(winding_t *w); -//returns on which side of the plane the winding is situated -int WindingOnPlaneSide(winding_t *w, vec3_t normal, vec_t dist); -//frees the winding -void FreeWinding(winding_t *w); -//gets the bounds of the winding -void WindingBounds(winding_t *w, vec3_t mins, vec3_t maxs); -//chops the winding with the given plane, the original winding is freed if clipped -void ChopWindingInPlace (winding_t **w, vec3_t normal, vec_t dist, vec_t epsilon); -//prints the winding points on STDOUT -void pw(winding_t *w); -//try to merge the two windings which are in the given plane -//the original windings are undisturbed -//the merged winding is returned when merging was possible -//NULL is returned otherwise -winding_t *TryMergeWinding (winding_t *f1, winding_t *f2, vec3_t planenormal); -//brute force winding merging... creates a convex winding out of -//the two whatsoever -winding_t *MergeWindings(winding_t *w1, winding_t *w2, vec3_t planenormal); - -//#ifdef ME -void ResetWindings(void); -//returns the amount of winding memory -int WindingMemory(void); -int WindingPeakMemory(void); -int ActiveWindings(void); -//returns the winding error string -char *WindingErrorString(void); -//returns one of the WE_ flags when the winding has errors -int WindingError(winding_t *w); -//removes equal points from the winding -void RemoveEqualPoints(winding_t *w, float epsilon); -//returns a winding with a point added at the given spot to the -//given winding, original winding is NOT freed -winding_t *AddWindingPoint(winding_t *w, vec3_t point, int spot); -//returns true if the point is on one of the winding 'edges' -//when the point is on one of the edged the number of the first -//point of the edge is stored in 'spot' -int PointOnWinding(winding_t *w, vec3_t normal, float dist, vec3_t point, int *spot); -//find a plane seperating the two windings -//true is returned when the windings area adjacent -//the seperating plane normal and distance area stored in 'normal' and 'dist' -//this plane will contain both the piece of common edge of the two windings -//and the vector 'dir' -int FindPlaneSeperatingWindings(winding_t *w1, winding_t *w2, vec3_t dir, - vec3_t normal, float *dist); -// -int WindingsNonConvex(winding_t *w1, winding_t *w2, - vec3_t normal1, vec3_t normal2, - float dist1, float dist2); -//#endif //ME - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +//a winding gives the bounding points of a convex polygon +typedef struct +{ + int numpoints; + vec3_t p[4]; //variable sized +} winding_t; + +#define MAX_POINTS_ON_WINDING 96 + +//you can define on_epsilon in the makefile as tighter +#ifndef ON_EPSILON +#define ON_EPSILON 0.1 +#endif +//winding errors +#define WE_NONE 0 +#define WE_NOTENOUGHPOINTS 1 +#define WE_SMALLAREA 2 +#define WE_POINTBOGUSRANGE 3 +#define WE_POINTOFFPLANE 4 +#define WE_DEGENERATEEDGE 5 +#define WE_NONCONVEX 6 + +//allocates a winding +winding_t *AllocWinding (int points); +//returns the area of the winding +vec_t WindingArea (winding_t *w); +//gives the center of the winding +void WindingCenter (winding_t *w, vec3_t center); +//clips the given winding to the given plane and gives the front +//and back part of the clipped winding +void ClipWindingEpsilon (winding_t *in, vec3_t normal, vec_t dist, + vec_t epsilon, winding_t **front, winding_t **back); +//returns the fragment of the given winding 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); +//returns a copy of the given winding +winding_t *CopyWinding (winding_t *w); +//returns the reversed winding of the given one +winding_t *ReverseWinding (winding_t *w); +//returns a base winding for the given plane +winding_t *BaseWindingForPlane (vec3_t normal, vec_t dist); +//checks the winding for errors +void CheckWinding (winding_t *w); +//returns the plane normal and dist the winding is in +void WindingPlane(winding_t *w, vec3_t normal, vec_t *dist); +//removes colinear points from the winding +void RemoveColinearPoints(winding_t *w); +//returns on which side of the plane the winding is situated +int WindingOnPlaneSide(winding_t *w, vec3_t normal, vec_t dist); +//frees the winding +void FreeWinding(winding_t *w); +//gets the bounds of the winding +void WindingBounds(winding_t *w, vec3_t mins, vec3_t maxs); +//chops the winding with the given plane, the original winding is freed if clipped +void ChopWindingInPlace (winding_t **w, vec3_t normal, vec_t dist, vec_t epsilon); +//prints the winding points on STDOUT +void pw(winding_t *w); +//try to merge the two windings which are in the given plane +//the original windings are undisturbed +//the merged winding is returned when merging was possible +//NULL is returned otherwise +winding_t *TryMergeWinding (winding_t *f1, winding_t *f2, vec3_t planenormal); +//brute force winding merging... creates a convex winding out of +//the two whatsoever +winding_t *MergeWindings(winding_t *w1, winding_t *w2, vec3_t planenormal); + +//#ifdef ME +void ResetWindings(void); +//returns the amount of winding memory +int WindingMemory(void); +int WindingPeakMemory(void); +int ActiveWindings(void); +//returns the winding error string +char *WindingErrorString(void); +//returns one of the WE_ flags when the winding has errors +int WindingError(winding_t *w); +//removes equal points from the winding +void RemoveEqualPoints(winding_t *w, float epsilon); +//returns a winding with a point added at the given spot to the +//given winding, original winding is NOT freed +winding_t *AddWindingPoint(winding_t *w, vec3_t point, int spot); +//returns true if the point is on one of the winding 'edges' +//when the point is on one of the edged the number of the first +//point of the edge is stored in 'spot' +int PointOnWinding(winding_t *w, vec3_t normal, float dist, vec3_t point, int *spot); +//find a plane seperating the two windings +//true is returned when the windings area adjacent +//the seperating plane normal and distance area stored in 'normal' and 'dist' +//this plane will contain both the piece of common edge of the two windings +//and the vector 'dir' +int FindPlaneSeperatingWindings(winding_t *w1, winding_t *w2, vec3_t dir, + vec3_t normal, float *dist); +// +int WindingsNonConvex(winding_t *w1, winding_t *w2, + vec3_t normal1, vec3_t normal2, + float dist1, float dist2); +//#endif //ME + diff --git a/code/bspc/l_qfiles.c b/code/bspc/l_qfiles.c index 32cb6de..e2e8d10 100755 --- a/code/bspc/l_qfiles.c +++ b/code/bspc/l_qfiles.c @@ -1,663 +1,663 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#if defined(WIN32)|defined(_WIN32) -#include -#include -#include -#else -#include -#include -#include -#endif - -#include "qbsp.h" - -//file extensions with their type -typedef struct qfile_exttype_s -{ - char *extension; - int type; -} qfile_exttyp_t; - -qfile_exttyp_t quakefiletypes[] = -{ - {QFILEEXT_UNKNOWN, QFILETYPE_UNKNOWN}, - {QFILEEXT_PAK, QFILETYPE_PAK}, - {QFILEEXT_PK3, QFILETYPE_PK3}, - {QFILEEXT_SIN, QFILETYPE_PAK}, - {QFILEEXT_BSP, QFILETYPE_BSP}, - {QFILEEXT_MAP, QFILETYPE_MAP}, - {QFILEEXT_MDL, QFILETYPE_MDL}, - {QFILEEXT_MD2, QFILETYPE_MD2}, - {QFILEEXT_MD3, QFILETYPE_MD3}, - {QFILEEXT_WAL, QFILETYPE_WAL}, - {QFILEEXT_WAV, QFILETYPE_WAV}, - {QFILEEXT_AAS, QFILETYPE_AAS}, - {NULL, 0} -}; - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int QuakeFileExtensionType(char *extension) -{ - int i; - - for (i = 0; quakefiletypes[i].extension; i++) - { - if (!stricmp(extension, quakefiletypes[i].extension)) - { - return quakefiletypes[i].type; - } //end if - } //end for - return QFILETYPE_UNKNOWN; -} //end of the function QuakeFileExtensionType -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -char *QuakeFileTypeExtension(int type) -{ - int i; - - for (i = 0; quakefiletypes[i].extension; i++) - { - if (quakefiletypes[i].type == type) - { - return quakefiletypes[i].extension; - } //end if - } //end for - return QFILEEXT_UNKNOWN; -} //end of the function QuakeFileExtension -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int QuakeFileType(char *filename) -{ - char ext[_MAX_PATH] = "."; - - ExtractFileExtension(filename, ext+1); - return QuakeFileExtensionType(ext); -} //end of the function QuakeFileTypeFromFileName -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -char *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; - } //end if - else - { - if (toupper(str1[j]) != toupper(str2[j])) break; - } //end else - } //end for - if (!str2[j]) return str1; - } //end for - return NULL; -} //end of the function StringContains -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int FileFilter(char *filter, char *filename, int casesensitive) -{ - char buf[1024]; - char *ptr; - int i, found; - - while(*filter) - { - if (*filter == '*') - { - filter++; - for (i = 0; *filter; i++) - { - if (*filter == '*' || *filter == '?') break; - buf[i] = *filter; - filter++; - } //end for - buf[i] = '\0'; - if (strlen(buf)) - { - ptr = StringContains(filename, buf, casesensitive); - if (!ptr) return false; - filename = ptr + strlen(buf); - } //end if - } //end if - else if (*filter == '?') - { - filter++; - filename++; - } //end else if - else if (*filter == '[' && *(filter+1) == '[') - { - filter++; - } //end if - else if (*filter == '[') - { - filter++; - found = false; - while(*filter && !found) - { - if (*filter == ']' && *(filter+1) != ']') break; - if (*(filter+1) == '-' && *(filter+2) && (*(filter+2) != ']' || *(filter+3) == ']')) - { - if (casesensitive) - { - if (*filename >= *filter && *filename <= *(filter+2)) found = true; - } //end if - else - { - if (toupper(*filename) >= toupper(*filter) && - toupper(*filename) <= toupper(*(filter+2))) found = true; - } //end else - filter += 3; - } //end if - else - { - if (casesensitive) - { - if (*filter == *filename) found = true; - } //end if - else - { - if (toupper(*filter) == toupper(*filename)) found = true; - } //end else - filter++; - } //end else - } //end while - if (!found) return false; - while(*filter) - { - if (*filter == ']' && *(filter+1) != ']') break; - filter++; - } //end while - filter++; - filename++; - } //end else if - else - { - if (casesensitive) - { - if (*filter != *filename) return false; - } //end if - else - { - if (toupper(*filter) != toupper(*filename)) return false; - } //end else - filter++; - filename++; - } //end else - } //end while - return true; -} //end of the function FileFilter -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -quakefile_t *FindQuakeFilesInZip(char *zipfile, char *filter) -{ - unzFile uf; - int err; - unz_global_info gi; - char filename_inzip[MAX_PATH]; - unz_file_info file_info; - int i; - quakefile_t *qfiles, *lastqf, *qf; - - uf = unzOpen(zipfile); - err = unzGetGlobalInfo(uf, &gi); - - if (err != UNZ_OK) return NULL; - - unzGoToFirstFile(uf); - - qfiles = NULL; - lastqf = NULL; - 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; - - ConvertPath(filename_inzip); - if (FileFilter(filter, filename_inzip, false)) - { - qf = malloc(sizeof(quakefile_t)); - if (!qf) Error("out of memory"); - memset(qf, 0, sizeof(quakefile_t)); - strcpy(qf->pakfile, zipfile); - strcpy(qf->filename, zipfile); - strcpy(qf->origname, filename_inzip); - qf->zipfile = true; - //memcpy( &buildBuffer[i].zipfileinfo, (unz_s*)uf, sizeof(unz_s)); - memcpy(&qf->zipinfo, (unz_s*)uf, sizeof(unz_s)); - qf->offset = 0; - qf->length = file_info.uncompressed_size; - qf->type = QuakeFileType(filename_inzip); - //add the file ot the list - qf->next = NULL; - if (lastqf) lastqf->next = qf; - else qfiles = qf; - lastqf = qf; - } //end if - unzGoToNextFile(uf); - } //end for - - unzClose(uf); - - return qfiles; -} //end of the function FindQuakeFilesInZip -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -quakefile_t *FindQuakeFilesInPak(char *pakfile, char *filter) -{ - FILE *fp; - dpackheader_t packheader; - dsinpackfile_t *packfiles; - dpackfile_t *idpackfiles; - quakefile_t *qfiles, *lastqf, *qf; - int numpackdirs, i; - - qfiles = NULL; - lastqf = NULL; - //open the pak file - fp = fopen(pakfile, "rb"); - if (!fp) - { - Warning("can't open pak file %s", pakfile); - return NULL; - } //end if - //read pak header, check for valid pak id and seek to the dir entries - if ((fread(&packheader, 1, sizeof(dpackheader_t), fp) != sizeof(dpackheader_t)) - || (packheader.ident != IDPAKHEADER && packheader.ident != SINPAKHEADER) - || (fseek(fp, LittleLong(packheader.dirofs), SEEK_SET)) - ) - { - fclose(fp); - Warning("invalid pak file %s", pakfile); - return NULL; - } //end if - //if it is a pak file from id software - if (packheader.ident == IDPAKHEADER) - { - //number of dir entries in the pak file - numpackdirs = LittleLong(packheader.dirlen) / sizeof(dpackfile_t); - idpackfiles = (dpackfile_t *) malloc(numpackdirs * sizeof(dpackfile_t)); - if (!idpackfiles) Error("out of memory"); - //read the dir entry - if (fread(idpackfiles, sizeof(dpackfile_t), numpackdirs, fp) != numpackdirs) - { - fclose(fp); - free(idpackfiles); - Warning("can't read the Quake pak file dir entries from %s", pakfile); - return NULL; - } //end if - fclose(fp); - //convert to sin pack files - packfiles = (dsinpackfile_t *) malloc(numpackdirs * sizeof(dsinpackfile_t)); - if (!packfiles) Error("out of memory"); - for (i = 0; i < numpackdirs; i++) - { - strcpy(packfiles[i].name, idpackfiles[i].name); - packfiles[i].filepos = LittleLong(idpackfiles[i].filepos); - packfiles[i].filelen = LittleLong(idpackfiles[i].filelen); - } //end for - free(idpackfiles); - } //end if - else //its a Sin pack file - { - //number of dir entries in the pak file - numpackdirs = LittleLong(packheader.dirlen) / sizeof(dsinpackfile_t); - packfiles = (dsinpackfile_t *) malloc(numpackdirs * sizeof(dsinpackfile_t)); - if (!packfiles) Error("out of memory"); - //read the dir entry - if (fread(packfiles, sizeof(dsinpackfile_t), numpackdirs, fp) != numpackdirs) - { - fclose(fp); - free(packfiles); - Warning("can't read the Sin pak file dir entries from %s", pakfile); - return NULL; - } //end if - fclose(fp); - for (i = 0; i < numpackdirs; i++) - { - packfiles[i].filepos = LittleLong(packfiles[i].filepos); - packfiles[i].filelen = LittleLong(packfiles[i].filelen); - } //end for - } //end else - // - for (i = 0; i < numpackdirs; i++) - { - ConvertPath(packfiles[i].name); - if (FileFilter(filter, packfiles[i].name, false)) - { - qf = malloc(sizeof(quakefile_t)); - if (!qf) Error("out of memory"); - memset(qf, 0, sizeof(quakefile_t)); - strcpy(qf->pakfile, pakfile); - strcpy(qf->filename, pakfile); - strcpy(qf->origname, packfiles[i].name); - qf->zipfile = false; - qf->offset = packfiles[i].filepos; - qf->length = packfiles[i].filelen; - qf->type = QuakeFileType(packfiles[i].name); - //add the file ot the list - qf->next = NULL; - if (lastqf) lastqf->next = qf; - else qfiles = qf; - lastqf = qf; - } //end if - } //end for - free(packfiles); - return qfiles; -} //end of the function FindQuakeFilesInPak -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -quakefile_t *FindQuakeFilesWithPakFilter(char *pakfilter, char *filter) -{ -#if defined(WIN32)|defined(_WIN32) - WIN32_FIND_DATA filedata; - HWND handle; - struct _stat statbuf; -#else - glob_t globbuf; - struct stat statbuf; - int j; -#endif - quakefile_t *qfiles, *lastqf, *qf; - char pakfile[_MAX_PATH], filename[_MAX_PATH], *str; - int done; - - qfiles = NULL; - lastqf = NULL; - if (pakfilter && strlen(pakfilter)) - { -#if defined(WIN32)|defined(_WIN32) - handle = FindFirstFile(pakfilter, &filedata); - done = (handle == INVALID_HANDLE_VALUE); - while(!done) - { - _splitpath(pakfilter, pakfile, NULL, NULL, NULL); - _splitpath(pakfilter, NULL, &pakfile[strlen(pakfile)], NULL, NULL); - AppendPathSeperator(pakfile, _MAX_PATH); - strcat(pakfile, filedata.cFileName); - _stat(pakfile, &statbuf); -#else - glob(pakfilter, 0, NULL, &globbuf); - for (j = 0; j < globbuf.gl_pathc; j++) - { - strcpy(pakfile, globbuf.gl_pathv[j]); - stat(pakfile, &statbuf); -#endif - //if the file with .pak or .pk3 is a folder - if (statbuf.st_mode & S_IFDIR) - { - strcpy(filename, pakfilter); - AppendPathSeperator(filename, _MAX_PATH); - strcat(filename, filter); - qf = FindQuakeFilesWithPakFilter(NULL, filename); - if (lastqf) lastqf->next = qf; - else qfiles = qf; - lastqf = qf; - while(lastqf->next) lastqf = lastqf->next; - } //end if - else - { -#if defined(WIN32)|defined(_WIN32) - str = StringContains(pakfile, ".pk3", false); -#else - str = StringContains(pakfile, ".pk3", true); -#endif - if (str && str == pakfile + strlen(pakfile) - strlen(".pk3")) - { - qf = FindQuakeFilesInZip(pakfile, filter); - } //end if - else - { - qf = FindQuakeFilesInPak(pakfile, filter); - } //end else - // - if (qf) - { - if (lastqf) lastqf->next = qf; - else qfiles = qf; - lastqf = qf; - while(lastqf->next) lastqf = lastqf->next; - } //end if - } //end else - // -#if defined(WIN32)|defined(_WIN32) - //find the next file - done = !FindNextFile(handle, &filedata); - } //end while -#else - } //end for - globfree(&globbuf); -#endif - } //end if - else - { -#if defined(WIN32)|defined(_WIN32) - handle = FindFirstFile(filter, &filedata); - done = (handle == INVALID_HANDLE_VALUE); - while(!done) - { - _splitpath(filter, filename, NULL, NULL, NULL); - _splitpath(filter, NULL, &filename[strlen(filename)], NULL, NULL); - AppendPathSeperator(filename, _MAX_PATH); - strcat(filename, filedata.cFileName); -#else - glob(filter, 0, NULL, &globbuf); - for (j = 0; j < globbuf.gl_pathc; j++) - { - strcpy(filename, globbuf.gl_pathv[j]); -#endif - // - qf = malloc(sizeof(quakefile_t)); - if (!qf) Error("out of memory"); - memset(qf, 0, sizeof(quakefile_t)); - strcpy(qf->pakfile, ""); - strcpy(qf->filename, filename); - strcpy(qf->origname, filename); - qf->offset = 0; - qf->length = 0; - qf->type = QuakeFileType(filename); - //add the file ot the list - qf->next = NULL; - if (lastqf) lastqf->next = qf; - else qfiles = qf; - lastqf = qf; -#if defined(WIN32)|defined(_WIN32) - //find the next file - done = !FindNextFile(handle, &filedata); - } //end while -#else - } //end for - globfree(&globbuf); -#endif - } //end else - return qfiles; -} //end of the function FindQuakeFilesWithPakFilter -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -quakefile_t *FindQuakeFiles(char *filter) -{ - char *str; - char newfilter[_MAX_PATH]; - char pakfilter[_MAX_PATH]; - char filefilter[_MAX_PATH]; - - strcpy(newfilter, filter); - ConvertPath(newfilter); - strcpy(pakfilter, newfilter); - - str = StringContains(pakfilter, ".pak", false); - if (!str) str = StringContains(pakfilter, ".pk3", false); - - if (str) - { - str += strlen(".pak"); - if (*str) - { - *str++ = '\0'; - while(*str == '\\' || *str == '/') str++; - strcpy(filefilter, str); - return FindQuakeFilesWithPakFilter(pakfilter, filefilter); - } //end if - } //end else - return FindQuakeFilesWithPakFilter(NULL, newfilter); -} //end of the function FindQuakeFiles -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int LoadQuakeFile(quakefile_t *qf, void **bufferptr) -{ - FILE *fp; - void *buffer; - int length; - unzFile zf; - - if (qf->zipfile) - { - //open the zip file - zf = unzOpen(qf->pakfile); - //set the file pointer - qf->zipinfo.file = ((unz_s *) zf)->file; - //open the Quake file in the zip file - unzOpenCurrentFile(&qf->zipinfo); - //allocate memory for the buffer - length = qf->length; - buffer = GetMemory(length+1); - //read the Quake file from the zip file - length = unzReadCurrentFile(&qf->zipinfo, buffer, length); - //close the Quake file in the zip file - unzCloseCurrentFile(&qf->zipinfo); - //close the zip file - unzClose(zf); - - *bufferptr = buffer; - return length; - } //end if - else - { - fp = SafeOpenRead(qf->filename); - if (qf->offset) fseek(fp, qf->offset, SEEK_SET); - length = qf->length; - if (!length) length = Q_filelength(fp); - buffer = GetMemory(length+1); - ((char *)buffer)[length] = 0; - SafeRead(fp, buffer, length); - fclose(fp); - - *bufferptr = buffer; - return length; - } //end else -} //end of the function LoadQuakeFile -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int ReadQuakeFile(quakefile_t *qf, void *buffer, int offset, int length) -{ - FILE *fp; - int read; - unzFile zf; - char tmpbuf[1024]; - - if (qf->zipfile) - { - //open the zip file - zf = unzOpen(qf->pakfile); - //set the file pointer - qf->zipinfo.file = ((unz_s *) zf)->file; - //open the Quake file in the zip file - unzOpenCurrentFile(&qf->zipinfo); - // - while(offset > 0) - { - read = offset; - if (read > sizeof(tmpbuf)) read = sizeof(tmpbuf); - unzReadCurrentFile(&qf->zipinfo, tmpbuf, read); - offset -= read; - } //end while - //read the Quake file from the zip file - length = unzReadCurrentFile(&qf->zipinfo, buffer, length); - //close the Quake file in the zip file - unzCloseCurrentFile(&qf->zipinfo); - //close the zip file - unzClose(zf); - - return length; - } //end if - else - { - fp = SafeOpenRead(qf->filename); - if (qf->offset) fseek(fp, qf->offset, SEEK_SET); - if (offset) fseek(fp, offset, SEEK_CUR); - SafeRead(fp, buffer, length); - fclose(fp); - - return length; - } //end else -} //end of the function ReadQuakeFile +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#if defined(WIN32)|defined(_WIN32) +#include +#include +#include +#else +#include +#include +#include +#endif + +#include "qbsp.h" + +//file extensions with their type +typedef struct qfile_exttype_s +{ + char *extension; + int type; +} qfile_exttyp_t; + +qfile_exttyp_t quakefiletypes[] = +{ + {QFILEEXT_UNKNOWN, QFILETYPE_UNKNOWN}, + {QFILEEXT_PAK, QFILETYPE_PAK}, + {QFILEEXT_PK3, QFILETYPE_PK3}, + {QFILEEXT_SIN, QFILETYPE_PAK}, + {QFILEEXT_BSP, QFILETYPE_BSP}, + {QFILEEXT_MAP, QFILETYPE_MAP}, + {QFILEEXT_MDL, QFILETYPE_MDL}, + {QFILEEXT_MD2, QFILETYPE_MD2}, + {QFILEEXT_MD3, QFILETYPE_MD3}, + {QFILEEXT_WAL, QFILETYPE_WAL}, + {QFILEEXT_WAV, QFILETYPE_WAV}, + {QFILEEXT_AAS, QFILETYPE_AAS}, + {NULL, 0} +}; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int QuakeFileExtensionType(char *extension) +{ + int i; + + for (i = 0; quakefiletypes[i].extension; i++) + { + if (!stricmp(extension, quakefiletypes[i].extension)) + { + return quakefiletypes[i].type; + } //end if + } //end for + return QFILETYPE_UNKNOWN; +} //end of the function QuakeFileExtensionType +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *QuakeFileTypeExtension(int type) +{ + int i; + + for (i = 0; quakefiletypes[i].extension; i++) + { + if (quakefiletypes[i].type == type) + { + return quakefiletypes[i].extension; + } //end if + } //end for + return QFILEEXT_UNKNOWN; +} //end of the function QuakeFileExtension +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int QuakeFileType(char *filename) +{ + char ext[_MAX_PATH] = "."; + + ExtractFileExtension(filename, ext+1); + return QuakeFileExtensionType(ext); +} //end of the function QuakeFileTypeFromFileName +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *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; + } //end if + else + { + if (toupper(str1[j]) != toupper(str2[j])) break; + } //end else + } //end for + if (!str2[j]) return str1; + } //end for + return NULL; +} //end of the function StringContains +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int FileFilter(char *filter, char *filename, int casesensitive) +{ + char buf[1024]; + char *ptr; + int i, found; + + while(*filter) + { + if (*filter == '*') + { + filter++; + for (i = 0; *filter; i++) + { + if (*filter == '*' || *filter == '?') break; + buf[i] = *filter; + filter++; + } //end for + buf[i] = '\0'; + if (strlen(buf)) + { + ptr = StringContains(filename, buf, casesensitive); + if (!ptr) return false; + filename = ptr + strlen(buf); + } //end if + } //end if + else if (*filter == '?') + { + filter++; + filename++; + } //end else if + else if (*filter == '[' && *(filter+1) == '[') + { + filter++; + } //end if + else if (*filter == '[') + { + filter++; + found = false; + while(*filter && !found) + { + if (*filter == ']' && *(filter+1) != ']') break; + if (*(filter+1) == '-' && *(filter+2) && (*(filter+2) != ']' || *(filter+3) == ']')) + { + if (casesensitive) + { + if (*filename >= *filter && *filename <= *(filter+2)) found = true; + } //end if + else + { + if (toupper(*filename) >= toupper(*filter) && + toupper(*filename) <= toupper(*(filter+2))) found = true; + } //end else + filter += 3; + } //end if + else + { + if (casesensitive) + { + if (*filter == *filename) found = true; + } //end if + else + { + if (toupper(*filter) == toupper(*filename)) found = true; + } //end else + filter++; + } //end else + } //end while + if (!found) return false; + while(*filter) + { + if (*filter == ']' && *(filter+1) != ']') break; + filter++; + } //end while + filter++; + filename++; + } //end else if + else + { + if (casesensitive) + { + if (*filter != *filename) return false; + } //end if + else + { + if (toupper(*filter) != toupper(*filename)) return false; + } //end else + filter++; + filename++; + } //end else + } //end while + return true; +} //end of the function FileFilter +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +quakefile_t *FindQuakeFilesInZip(char *zipfile, char *filter) +{ + unzFile uf; + int err; + unz_global_info gi; + char filename_inzip[MAX_PATH]; + unz_file_info file_info; + int i; + quakefile_t *qfiles, *lastqf, *qf; + + uf = unzOpen(zipfile); + err = unzGetGlobalInfo(uf, &gi); + + if (err != UNZ_OK) return NULL; + + unzGoToFirstFile(uf); + + qfiles = NULL; + lastqf = NULL; + 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; + + ConvertPath(filename_inzip); + if (FileFilter(filter, filename_inzip, false)) + { + qf = malloc(sizeof(quakefile_t)); + if (!qf) Error("out of memory"); + memset(qf, 0, sizeof(quakefile_t)); + strcpy(qf->pakfile, zipfile); + strcpy(qf->filename, zipfile); + strcpy(qf->origname, filename_inzip); + qf->zipfile = true; + //memcpy( &buildBuffer[i].zipfileinfo, (unz_s*)uf, sizeof(unz_s)); + memcpy(&qf->zipinfo, (unz_s*)uf, sizeof(unz_s)); + qf->offset = 0; + qf->length = file_info.uncompressed_size; + qf->type = QuakeFileType(filename_inzip); + //add the file ot the list + qf->next = NULL; + if (lastqf) lastqf->next = qf; + else qfiles = qf; + lastqf = qf; + } //end if + unzGoToNextFile(uf); + } //end for + + unzClose(uf); + + return qfiles; +} //end of the function FindQuakeFilesInZip +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +quakefile_t *FindQuakeFilesInPak(char *pakfile, char *filter) +{ + FILE *fp; + dpackheader_t packheader; + dsinpackfile_t *packfiles; + dpackfile_t *idpackfiles; + quakefile_t *qfiles, *lastqf, *qf; + int numpackdirs, i; + + qfiles = NULL; + lastqf = NULL; + //open the pak file + fp = fopen(pakfile, "rb"); + if (!fp) + { + Warning("can't open pak file %s", pakfile); + return NULL; + } //end if + //read pak header, check for valid pak id and seek to the dir entries + if ((fread(&packheader, 1, sizeof(dpackheader_t), fp) != sizeof(dpackheader_t)) + || (packheader.ident != IDPAKHEADER && packheader.ident != SINPAKHEADER) + || (fseek(fp, LittleLong(packheader.dirofs), SEEK_SET)) + ) + { + fclose(fp); + Warning("invalid pak file %s", pakfile); + return NULL; + } //end if + //if it is a pak file from id software + if (packheader.ident == IDPAKHEADER) + { + //number of dir entries in the pak file + numpackdirs = LittleLong(packheader.dirlen) / sizeof(dpackfile_t); + idpackfiles = (dpackfile_t *) malloc(numpackdirs * sizeof(dpackfile_t)); + if (!idpackfiles) Error("out of memory"); + //read the dir entry + if (fread(idpackfiles, sizeof(dpackfile_t), numpackdirs, fp) != numpackdirs) + { + fclose(fp); + free(idpackfiles); + Warning("can't read the Quake pak file dir entries from %s", pakfile); + return NULL; + } //end if + fclose(fp); + //convert to sin pack files + packfiles = (dsinpackfile_t *) malloc(numpackdirs * sizeof(dsinpackfile_t)); + if (!packfiles) Error("out of memory"); + for (i = 0; i < numpackdirs; i++) + { + strcpy(packfiles[i].name, idpackfiles[i].name); + packfiles[i].filepos = LittleLong(idpackfiles[i].filepos); + packfiles[i].filelen = LittleLong(idpackfiles[i].filelen); + } //end for + free(idpackfiles); + } //end if + else //its a Sin pack file + { + //number of dir entries in the pak file + numpackdirs = LittleLong(packheader.dirlen) / sizeof(dsinpackfile_t); + packfiles = (dsinpackfile_t *) malloc(numpackdirs * sizeof(dsinpackfile_t)); + if (!packfiles) Error("out of memory"); + //read the dir entry + if (fread(packfiles, sizeof(dsinpackfile_t), numpackdirs, fp) != numpackdirs) + { + fclose(fp); + free(packfiles); + Warning("can't read the Sin pak file dir entries from %s", pakfile); + return NULL; + } //end if + fclose(fp); + for (i = 0; i < numpackdirs; i++) + { + packfiles[i].filepos = LittleLong(packfiles[i].filepos); + packfiles[i].filelen = LittleLong(packfiles[i].filelen); + } //end for + } //end else + // + for (i = 0; i < numpackdirs; i++) + { + ConvertPath(packfiles[i].name); + if (FileFilter(filter, packfiles[i].name, false)) + { + qf = malloc(sizeof(quakefile_t)); + if (!qf) Error("out of memory"); + memset(qf, 0, sizeof(quakefile_t)); + strcpy(qf->pakfile, pakfile); + strcpy(qf->filename, pakfile); + strcpy(qf->origname, packfiles[i].name); + qf->zipfile = false; + qf->offset = packfiles[i].filepos; + qf->length = packfiles[i].filelen; + qf->type = QuakeFileType(packfiles[i].name); + //add the file ot the list + qf->next = NULL; + if (lastqf) lastqf->next = qf; + else qfiles = qf; + lastqf = qf; + } //end if + } //end for + free(packfiles); + return qfiles; +} //end of the function FindQuakeFilesInPak +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +quakefile_t *FindQuakeFilesWithPakFilter(char *pakfilter, char *filter) +{ +#if defined(WIN32)|defined(_WIN32) + WIN32_FIND_DATA filedata; + HWND handle; + struct _stat statbuf; +#else + glob_t globbuf; + struct stat statbuf; + int j; +#endif + quakefile_t *qfiles, *lastqf, *qf; + char pakfile[_MAX_PATH], filename[_MAX_PATH], *str; + int done; + + qfiles = NULL; + lastqf = NULL; + if (pakfilter && strlen(pakfilter)) + { +#if defined(WIN32)|defined(_WIN32) + handle = FindFirstFile(pakfilter, &filedata); + done = (handle == INVALID_HANDLE_VALUE); + while(!done) + { + _splitpath(pakfilter, pakfile, NULL, NULL, NULL); + _splitpath(pakfilter, NULL, &pakfile[strlen(pakfile)], NULL, NULL); + AppendPathSeperator(pakfile, _MAX_PATH); + strcat(pakfile, filedata.cFileName); + _stat(pakfile, &statbuf); +#else + glob(pakfilter, 0, NULL, &globbuf); + for (j = 0; j < globbuf.gl_pathc; j++) + { + strcpy(pakfile, globbuf.gl_pathv[j]); + stat(pakfile, &statbuf); +#endif + //if the file with .pak or .pk3 is a folder + if (statbuf.st_mode & S_IFDIR) + { + strcpy(filename, pakfilter); + AppendPathSeperator(filename, _MAX_PATH); + strcat(filename, filter); + qf = FindQuakeFilesWithPakFilter(NULL, filename); + if (lastqf) lastqf->next = qf; + else qfiles = qf; + lastqf = qf; + while(lastqf->next) lastqf = lastqf->next; + } //end if + else + { +#if defined(WIN32)|defined(_WIN32) + str = StringContains(pakfile, ".pk3", false); +#else + str = StringContains(pakfile, ".pk3", true); +#endif + if (str && str == pakfile + strlen(pakfile) - strlen(".pk3")) + { + qf = FindQuakeFilesInZip(pakfile, filter); + } //end if + else + { + qf = FindQuakeFilesInPak(pakfile, filter); + } //end else + // + if (qf) + { + if (lastqf) lastqf->next = qf; + else qfiles = qf; + lastqf = qf; + while(lastqf->next) lastqf = lastqf->next; + } //end if + } //end else + // +#if defined(WIN32)|defined(_WIN32) + //find the next file + done = !FindNextFile(handle, &filedata); + } //end while +#else + } //end for + globfree(&globbuf); +#endif + } //end if + else + { +#if defined(WIN32)|defined(_WIN32) + handle = FindFirstFile(filter, &filedata); + done = (handle == INVALID_HANDLE_VALUE); + while(!done) + { + _splitpath(filter, filename, NULL, NULL, NULL); + _splitpath(filter, NULL, &filename[strlen(filename)], NULL, NULL); + AppendPathSeperator(filename, _MAX_PATH); + strcat(filename, filedata.cFileName); +#else + glob(filter, 0, NULL, &globbuf); + for (j = 0; j < globbuf.gl_pathc; j++) + { + strcpy(filename, globbuf.gl_pathv[j]); +#endif + // + qf = malloc(sizeof(quakefile_t)); + if (!qf) Error("out of memory"); + memset(qf, 0, sizeof(quakefile_t)); + strcpy(qf->pakfile, ""); + strcpy(qf->filename, filename); + strcpy(qf->origname, filename); + qf->offset = 0; + qf->length = 0; + qf->type = QuakeFileType(filename); + //add the file ot the list + qf->next = NULL; + if (lastqf) lastqf->next = qf; + else qfiles = qf; + lastqf = qf; +#if defined(WIN32)|defined(_WIN32) + //find the next file + done = !FindNextFile(handle, &filedata); + } //end while +#else + } //end for + globfree(&globbuf); +#endif + } //end else + return qfiles; +} //end of the function FindQuakeFilesWithPakFilter +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +quakefile_t *FindQuakeFiles(char *filter) +{ + char *str; + char newfilter[_MAX_PATH]; + char pakfilter[_MAX_PATH]; + char filefilter[_MAX_PATH]; + + strcpy(newfilter, filter); + ConvertPath(newfilter); + strcpy(pakfilter, newfilter); + + str = StringContains(pakfilter, ".pak", false); + if (!str) str = StringContains(pakfilter, ".pk3", false); + + if (str) + { + str += strlen(".pak"); + if (*str) + { + *str++ = '\0'; + while(*str == '\\' || *str == '/') str++; + strcpy(filefilter, str); + return FindQuakeFilesWithPakFilter(pakfilter, filefilter); + } //end if + } //end else + return FindQuakeFilesWithPakFilter(NULL, newfilter); +} //end of the function FindQuakeFiles +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int LoadQuakeFile(quakefile_t *qf, void **bufferptr) +{ + FILE *fp; + void *buffer; + int length; + unzFile zf; + + if (qf->zipfile) + { + //open the zip file + zf = unzOpen(qf->pakfile); + //set the file pointer + qf->zipinfo.file = ((unz_s *) zf)->file; + //open the Quake file in the zip file + unzOpenCurrentFile(&qf->zipinfo); + //allocate memory for the buffer + length = qf->length; + buffer = GetMemory(length+1); + //read the Quake file from the zip file + length = unzReadCurrentFile(&qf->zipinfo, buffer, length); + //close the Quake file in the zip file + unzCloseCurrentFile(&qf->zipinfo); + //close the zip file + unzClose(zf); + + *bufferptr = buffer; + return length; + } //end if + else + { + fp = SafeOpenRead(qf->filename); + if (qf->offset) fseek(fp, qf->offset, SEEK_SET); + length = qf->length; + if (!length) length = Q_filelength(fp); + buffer = GetMemory(length+1); + ((char *)buffer)[length] = 0; + SafeRead(fp, buffer, length); + fclose(fp); + + *bufferptr = buffer; + return length; + } //end else +} //end of the function LoadQuakeFile +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int ReadQuakeFile(quakefile_t *qf, void *buffer, int offset, int length) +{ + FILE *fp; + int read; + unzFile zf; + char tmpbuf[1024]; + + if (qf->zipfile) + { + //open the zip file + zf = unzOpen(qf->pakfile); + //set the file pointer + qf->zipinfo.file = ((unz_s *) zf)->file; + //open the Quake file in the zip file + unzOpenCurrentFile(&qf->zipinfo); + // + while(offset > 0) + { + read = offset; + if (read > sizeof(tmpbuf)) read = sizeof(tmpbuf); + unzReadCurrentFile(&qf->zipinfo, tmpbuf, read); + offset -= read; + } //end while + //read the Quake file from the zip file + length = unzReadCurrentFile(&qf->zipinfo, buffer, length); + //close the Quake file in the zip file + unzCloseCurrentFile(&qf->zipinfo); + //close the zip file + unzClose(zf); + + return length; + } //end if + else + { + fp = SafeOpenRead(qf->filename); + if (qf->offset) fseek(fp, qf->offset, SEEK_SET); + if (offset) fseek(fp, offset, SEEK_CUR); + SafeRead(fp, buffer, length); + fclose(fp); + + return length; + } //end else +} //end of the function ReadQuakeFile diff --git a/code/bspc/l_qfiles.h b/code/bspc/l_qfiles.h index 308c608..0f79e7f 100755 --- a/code/bspc/l_qfiles.h +++ b/code/bspc/l_qfiles.h @@ -1,91 +1,91 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "../qcommon/unzip.h" - -#define QFILETYPE_UNKNOWN 0x8000 -#define QFILETYPE_PAK 0x0001 -#define QFILETYPE_PK3 0x0002 -#define QFILETYPE_BSP 0x0004 -#define QFILETYPE_MAP 0x0008 -#define QFILETYPE_MDL 0x0010 -#define QFILETYPE_MD2 0x0020 -#define QFILETYPE_MD3 0x0040 -#define QFILETYPE_WAL 0x0080 -#define QFILETYPE_WAV 0x0100 -#define QFILETYPE_AAS 0x4000 - -#define QFILEEXT_UNKNOWN "" -#define QFILEEXT_PAK ".PAK" -#define QFILEEXT_PK3 ".PK3" -#define QFILEEXT_SIN ".SIN" -#define QFILEEXT_BSP ".BSP" -#define QFILEEXT_MAP ".MAP" -#define QFILEEXT_MDL ".MDL" -#define QFILEEXT_MD2 ".MD2" -#define QFILEEXT_MD3 ".MD3" -#define QFILEEXT_WAL ".WAL" -#define QFILEEXT_WAV ".WAV" -#define QFILEEXT_AAS ".AAS" - -//maximum path length -#ifndef _MAX_PATH - #define _MAX_PATH 1024 -#endif - -//for Sin packs -#define MAX_PAK_FILENAME_LENGTH 120 -#define SINPAKHEADER (('K'<<24)+('A'<<16)+('P'<<8)+'S') - -typedef struct -{ - char name[MAX_PAK_FILENAME_LENGTH]; - int filepos, filelen; -} dsinpackfile_t; - -typedef struct quakefile_s -{ - char pakfile[_MAX_PATH]; - char filename[_MAX_PATH]; - char origname[_MAX_PATH]; - int zipfile; - int type; - int offset; - int length; - unz_s zipinfo; - struct quakefile_s *next; -} quakefile_t; - -//returns the file extension for the given type -char *QuakeFileTypeExtension(int type); -//returns the file type for the given extension -int QuakeFileExtensionType(char *extension); -//return the Quake file type for the given file -int QuakeFileType(char *filename); -//returns true if the filename complies to the filter -int FileFilter(char *filter, char *filename, int casesensitive); -//find Quake files using the given filter -quakefile_t *FindQuakeFiles(char *filter); -//load the given Quake file, returns the length of the file -int LoadQuakeFile(quakefile_t *qf, void **bufferptr); -//read part of a Quake file into the buffer -int ReadQuakeFile(quakefile_t *qf, void *buffer, int offset, int length); +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "../qcommon/unzip.h" + +#define QFILETYPE_UNKNOWN 0x8000 +#define QFILETYPE_PAK 0x0001 +#define QFILETYPE_PK3 0x0002 +#define QFILETYPE_BSP 0x0004 +#define QFILETYPE_MAP 0x0008 +#define QFILETYPE_MDL 0x0010 +#define QFILETYPE_MD2 0x0020 +#define QFILETYPE_MD3 0x0040 +#define QFILETYPE_WAL 0x0080 +#define QFILETYPE_WAV 0x0100 +#define QFILETYPE_AAS 0x4000 + +#define QFILEEXT_UNKNOWN "" +#define QFILEEXT_PAK ".PAK" +#define QFILEEXT_PK3 ".PK3" +#define QFILEEXT_SIN ".SIN" +#define QFILEEXT_BSP ".BSP" +#define QFILEEXT_MAP ".MAP" +#define QFILEEXT_MDL ".MDL" +#define QFILEEXT_MD2 ".MD2" +#define QFILEEXT_MD3 ".MD3" +#define QFILEEXT_WAL ".WAL" +#define QFILEEXT_WAV ".WAV" +#define QFILEEXT_AAS ".AAS" + +//maximum path length +#ifndef _MAX_PATH + #define _MAX_PATH 1024 +#endif + +//for Sin packs +#define MAX_PAK_FILENAME_LENGTH 120 +#define SINPAKHEADER (('K'<<24)+('A'<<16)+('P'<<8)+'S') + +typedef struct +{ + char name[MAX_PAK_FILENAME_LENGTH]; + int filepos, filelen; +} dsinpackfile_t; + +typedef struct quakefile_s +{ + char pakfile[_MAX_PATH]; + char filename[_MAX_PATH]; + char origname[_MAX_PATH]; + int zipfile; + int type; + int offset; + int length; + unz_s zipinfo; + struct quakefile_s *next; +} quakefile_t; + +//returns the file extension for the given type +char *QuakeFileTypeExtension(int type); +//returns the file type for the given extension +int QuakeFileExtensionType(char *extension); +//return the Quake file type for the given file +int QuakeFileType(char *filename); +//returns true if the filename complies to the filter +int FileFilter(char *filter, char *filename, int casesensitive); +//find Quake files using the given filter +quakefile_t *FindQuakeFiles(char *filter); +//load the given Quake file, returns the length of the file +int LoadQuakeFile(quakefile_t *qf, void **bufferptr); +//read part of a Quake file into the buffer +int ReadQuakeFile(quakefile_t *qf, void *buffer, int offset, int length); diff --git a/code/bspc/l_threads.c b/code/bspc/l_threads.c index bc4b998..94e641a 100755 --- a/code/bspc/l_threads.c +++ b/code/bspc/l_threads.c @@ -1,1510 +1,1510 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "l_cmd.h" -#include "l_threads.h" -#include "l_log.h" -#include "l_mem.h" - -#define MAX_THREADS 64 - -//#define THREAD_DEBUG - -int dispatch; -int workcount; -int oldf; -qboolean pacifier; -qboolean threaded; -void (*workfunction) (int); - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int GetThreadWork(void) -{ - int r; - int f; - - ThreadLock(); - - if (dispatch == workcount) - { - ThreadUnlock (); - return -1; - } - - f = 10*dispatch / workcount; - if (f != oldf) - { - oldf = f; - if (pacifier) - printf ("%i...", f); - } //end if - - r = dispatch; - dispatch++; - ThreadUnlock (); - - return r; -} //end of the function GetThreadWork -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ThreadWorkerFunction(int threadnum) -{ - int work; - - while(1) - { - work = GetThreadWork (); - if (work == -1) - break; -//printf ("thread %i, work %i\n", threadnum, work); - workfunction(work); - } //end while -} //end of the function ThreadWorkerFunction -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void RunThreadsOnIndividual (int workcnt, qboolean showpacifier, void(*func)(int)) -{ - if (numthreads == -1) - ThreadSetDefault (); - workfunction = func; - RunThreadsOn (workcnt, showpacifier, ThreadWorkerFunction); -} //end of the function RunThreadsOnIndividual - - -//=================================================================== -// -// WIN32 -// -//=================================================================== - -#if defined(WIN32) || defined(_WIN32) - -#define USED - -#include - -typedef struct thread_s -{ - HANDLE handle; - int threadid; - int id; - struct thread_s *next; -} thread_t; - -thread_t *firstthread; -thread_t *lastthread; -int currentnumthreads; -int currentthreadid; - -int numthreads = 1; -CRITICAL_SECTION crit; -HANDLE semaphore; -static int enter; -static int numwaitingthreads = 0; - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ThreadSetDefault(void) -{ - SYSTEM_INFO info; - - if (numthreads == -1) // not set manually - { - GetSystemInfo (&info); - numthreads = info.dwNumberOfProcessors; - if (numthreads < 1 || numthreads > 32) - numthreads = 1; - } //end if - qprintf ("%i threads\n", numthreads); -} //end of the function ThreadSetDefault -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ThreadLock(void) -{ - if (!threaded) - { - Error("ThreadLock: !threaded"); - return; - } //end if - EnterCriticalSection(&crit); - if (enter) - Error("Recursive ThreadLock\n"); - enter = 1; -} //end of the function ThreadLock -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ThreadUnlock (void) -{ - if (!threaded) - { - Error("ThreadUnlock: !threaded"); - return; - } //end if - if (!enter) - Error("ThreadUnlock without lock\n"); - enter = 0; - LeaveCriticalSection(&crit); -} //end of the function ThreadUnlock -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ThreadSetupLock(void) -{ - Log_Print("Win32 multi-threading\n"); - InitializeCriticalSection(&crit); - threaded = true; //Stupid me... forgot this!!! - currentnumthreads = 0; - currentthreadid = 0; -} //end of the function ThreadInitLock -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ThreadShutdownLock(void) -{ - DeleteCriticalSection(&crit); - threaded = false; //Stupid me... forgot this!!! -} //end of the function ThreadShutdownLock -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ThreadSetupSemaphore(void) -{ - semaphore = CreateSemaphore(NULL, 0, 99999999, "bspc"); -} //end of the function ThreadSetupSemaphore -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ThreadShutdownSemaphore(void) -{ -} //end of the function ThreadShutdownSemaphore -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ThreadSemaphoreWait(void) -{ - WaitForSingleObject(semaphore, INFINITE); -} //end of the function ThreadSemaphoreWait -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ThreadSemaphoreIncrease(int count) -{ - ReleaseSemaphore(semaphore, count, NULL); -} //end of the function ThreadSemaphoreIncrease -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void RunThreadsOn(int workcnt, qboolean showpacifier, void(*func)(int)) -{ - int threadid[MAX_THREADS]; - HANDLE threadhandle[MAX_THREADS]; - int i; - int start, end; - - Log_Print("Win32 multi-threading\n"); - start = I_FloatTime (); - dispatch = 0; - workcount = workcnt; - oldf = -1; - pacifier = showpacifier; - threaded = true; - - if (numthreads == -1) - ThreadSetDefault (); - - if (numthreads < 1 || numthreads > MAX_THREADS) numthreads = 1; - // - // run threads in parallel - // - InitializeCriticalSection (&crit); - - numwaitingthreads = 0; - - if (numthreads == 1) - { // use same thread - func (0); - } //end if - else - { -// printf("starting %d threads\n", numthreads); - for (i = 0; i < numthreads; i++) - { - threadhandle[i] = CreateThread( - NULL, // LPSECURITY_ATTRIBUTES lpsa, - 0, // DWORD cbStack, - (LPTHREAD_START_ROUTINE)func, // LPTHREAD_START_ROUTINE lpStartAddr, - (LPVOID)i, // LPVOID lpvThreadParm, - 0, // DWORD fdwCreate, - &threadid[i]); -// printf("started thread %d\n", i); - } //end for - - for (i = 0; i < numthreads; i++) - WaitForSingleObject (threadhandle[i], INFINITE); - } //end else - DeleteCriticalSection (&crit); - - threaded = false; - end = I_FloatTime (); - if (pacifier) printf (" (%i)\n", end-start); -} //end of the function RunThreadsOn -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AddThread(void (*func)(int)) -{ - thread_t *thread; - - if (numthreads == 1) - { - if (currentnumthreads >= numthreads) return; - currentnumthreads++; - func(-1); - currentnumthreads--; - } //end if - else - { - ThreadLock(); - if (currentnumthreads >= numthreads) - { - ThreadUnlock(); - return; - } //end if - //allocate new thread - thread = GetMemory(sizeof(thread_t)); - if (!thread) Error("can't allocate memory for thread\n"); - - // - thread->threadid = currentthreadid; - thread->handle = CreateThread( - NULL, // LPSECURITY_ATTRIBUTES lpsa, - 0, // DWORD cbStack, - (LPTHREAD_START_ROUTINE)func, // LPTHREAD_START_ROUTINE lpStartAddr, - (LPVOID) thread->threadid, // LPVOID lpvThreadParm, - 0, // DWORD fdwCreate, - &thread->id); - - //add the thread to the end of the list - thread->next = NULL; - if (lastthread) lastthread->next = thread; - else firstthread = thread; - lastthread = thread; - // -#ifdef THREAD_DEBUG - qprintf("added thread with id %d\n", thread->threadid); -#endif //THREAD_DEBUG - // - currentnumthreads++; - currentthreadid++; - // - ThreadUnlock(); - } //end else -} //end of the function AddThread -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void RemoveThread(int threadid) -{ - thread_t *thread, *last; - - //if a single thread - if (threadid == -1) return; - // - ThreadLock(); - last = NULL; - for (thread = firstthread; thread; thread = thread->next) - { - if (thread->threadid == threadid) - { - if (last) last->next = thread->next; - else firstthread = thread->next; - if (!thread->next) lastthread = last; - // - FreeMemory(thread); - currentnumthreads--; -#ifdef THREAD_DEBUG - qprintf("removed thread with id %d\n", threadid); -#endif //THREAD_DEBUG - break; - } //end if - last = thread; - } //end if - if (!thread) Error("couldn't find thread with id %d", threadid); - ThreadUnlock(); -} //end of the function RemoveThread -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void WaitForAllThreadsFinished(void) -{ - HANDLE handle; - - ThreadLock(); - while(firstthread) - { - handle = firstthread->handle; - ThreadUnlock(); - - WaitForSingleObject(handle, INFINITE); - - ThreadLock(); - } //end while - ThreadUnlock(); -} //end of the function WaitForAllThreadsFinished -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int GetNumThreads(void) -{ - return currentnumthreads; -} //end of the function GetNumThreads - -#endif - - -//=================================================================== -// -// OSF1 -// -//=================================================================== - -#if defined(__osf__) - -#define USED - -#include - -typedef struct thread_s -{ - pthread_t thread; - int threadid; - int id; - struct thread_s *next; -} thread_t; - -thread_t *firstthread; -thread_t *lastthread; -int currentnumthreads; -int currentthreadid; - -int numthreads = 1; -pthread_mutex_t my_mutex; -pthread_attr_t attrib; -static int enter; -static int numwaitingthreads = 0; - - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ThreadSetDefault(void) -{ - if (numthreads == -1) // not set manually - { - numthreads = 1; - } //end if - qprintf("%i threads\n", numthreads); -} //end of the function ThreadSetDefault -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ThreadLock(void) -{ - if (!threaded) - { - Error("ThreadLock: !threaded"); - return; - } //end if - if (my_mutex) - { - pthread_mutex_lock(my_mutex); - } //end if - if (enter) - Error("Recursive ThreadLock\n"); - enter = 1; -} //end of the function ThreadLock -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ThreadUnlock(void) -{ - if (!threaded) - { - Error("ThreadUnlock: !threaded"); - return; - } //end if - if (!enter) - Error("ThreadUnlock without lock\n"); - enter = 0; - if (my_mutex) - { - pthread_mutex_unlock(my_mutex); - } //end if -} //end of the function ThreadUnlock -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ThreadSetupLock(void) -{ - pthread_mutexattr_t mattrib; - - Log_Print("pthread multi-threading\n"); - - if (!my_mutex) - { - my_mutex = GetMemory(sizeof(*my_mutex)); - if (pthread_mutexattr_create (&mattrib) == -1) - Error ("pthread_mutex_attr_create failed"); - if (pthread_mutexattr_setkind_np (&mattrib, MUTEX_FAST_NP) == -1) - Error ("pthread_mutexattr_setkind_np failed"); - if (pthread_mutex_init (my_mutex, mattrib) == -1) - Error ("pthread_mutex_init failed"); - } - - if (pthread_attr_create (&attrib) == -1) - Error ("pthread_attr_create failed"); - if (pthread_attr_setstacksize (&attrib, 0x100000) == -1) - Error ("pthread_attr_setstacksize failed"); - - threaded = true; - currentnumthreads = 0; - currentthreadid = 0; -} //end of the function ThreadInitLock -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ThreadShutdownLock(void) -{ - threaded = false; -} //end of the function ThreadShutdownLock -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void RunThreadsOn(int workcnt, qboolean showpacifier, void(*func)(int)) -{ - int i; - pthread_t work_threads[MAX_THREADS]; - pthread_addr_t status; - pthread_attr_t attrib; - pthread_mutexattr_t mattrib; - int start, end; - - Log_Print("pthread multi-threading\n"); - - start = I_FloatTime (); - dispatch = 0; - workcount = workcnt; - oldf = -1; - pacifier = showpacifier; - threaded = true; - - if (numthreads < 1 || numthreads > MAX_THREADS) numthreads = 1; - - if (pacifier) - setbuf (stdout, NULL); - - if (!my_mutex) - { - my_mutex = GetMemory(sizeof(*my_mutex)); - if (pthread_mutexattr_create (&mattrib) == -1) - Error ("pthread_mutex_attr_create failed"); - if (pthread_mutexattr_setkind_np (&mattrib, MUTEX_FAST_NP) == -1) - Error ("pthread_mutexattr_setkind_np failed"); - if (pthread_mutex_init (my_mutex, mattrib) == -1) - Error ("pthread_mutex_init failed"); - } - - if (pthread_attr_create (&attrib) == -1) - Error ("pthread_attr_create failed"); - if (pthread_attr_setstacksize (&attrib, 0x100000) == -1) - Error ("pthread_attr_setstacksize failed"); - - for (i=0 ; i= numthreads) return; - currentnumthreads++; - func(-1); - currentnumthreads--; - } //end if - else - { - ThreadLock(); - if (currentnumthreads >= numthreads) - { - ThreadUnlock(); - return; - } //end if - //allocate new thread - thread = GetMemory(sizeof(thread_t)); - if (!thread) Error("can't allocate memory for thread\n"); - // - thread->threadid = currentthreadid; - - if (pthread_create(&thread->thread, attrib, (pthread_startroutine_t)func, (pthread_addr_t)thread->threadid) == -1) - Error ("pthread_create failed"); - - //add the thread to the end of the list - thread->next = NULL; - if (lastthread) lastthread->next = thread; - else firstthread = thread; - lastthread = thread; - // -#ifdef THREAD_DEBUG - qprintf("added thread with id %d\n", thread->threadid); -#endif //THREAD_DEBUG - // - currentnumthreads++; - currentthreadid++; - // - ThreadUnlock(); - } //end else -} //end of the function AddThread -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void RemoveThread(int threadid) -{ - thread_t *thread, *last; - - //if a single thread - if (threadid == -1) return; - // - ThreadLock(); - last = NULL; - for (thread = firstthread; thread; thread = thread->next) - { - if (thread->threadid == threadid) - { - if (last) last->next = thread->next; - else firstthread = thread->next; - if (!thread->next) lastthread = last; - // - FreeMemory(thread); - currentnumthreads--; -#ifdef THREAD_DEBUG - qprintf("removed thread with id %d\n", threadid); -#endif //THREAD_DEBUG - break; - } //end if - last = thread; - } //end if - if (!thread) Error("couldn't find thread with id %d", threadid); - ThreadUnlock(); -} //end of the function RemoveThread -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void WaitForAllThreadsFinished(void) -{ - pthread_t *thread; - pthread_addr_t status; - - ThreadLock(); - while(firstthread) - { - thread = &firstthread->thread; - ThreadUnlock(); - - if (pthread_join(*thread, &status) == -1) - Error("pthread_join failed"); - - ThreadLock(); - } //end while - ThreadUnlock(); -} //end of the function WaitForAllThreadsFinished -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int GetNumThreads(void) -{ - return currentnumthreads; -} //end of the function GetNumThreads - -#endif - -//=================================================================== -// -// LINUX -// -//=================================================================== - -#if defined(LINUX) - -#define USED - -#include -#include - -typedef struct thread_s -{ - pthread_t thread; - int threadid; - int id; - struct thread_s *next; -} thread_t; - -thread_t *firstthread; -thread_t *lastthread; -int currentnumthreads; -int currentthreadid; - -int numthreads = 1; -pthread_mutex_t my_mutex = PTHREAD_MUTEX_INITIALIZER; -pthread_attr_t attrib; -sem_t semaphore; -static int enter; -static int numwaitingthreads = 0; - - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ThreadSetDefault(void) -{ - if (numthreads == -1) // not set manually - { - numthreads = 1; - } //end if - qprintf("%i threads\n", numthreads); -} //end of the function ThreadSetDefault -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ThreadLock(void) -{ - if (!threaded) - { - Error("ThreadLock: !threaded"); - return; - } //end if - pthread_mutex_lock(&my_mutex); - if (enter) - Error("Recursive ThreadLock\n"); - enter = 1; -} //end of the function ThreadLock -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ThreadUnlock(void) -{ - if (!threaded) - { - Error("ThreadUnlock: !threaded"); - return; - } //end if - if (!enter) - Error("ThreadUnlock without lock\n"); - enter = 0; - pthread_mutex_unlock(&my_mutex); -} //end of the function ThreadUnlock -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ThreadSetupLock(void) -{ - pthread_mutexattr_t mattrib; - - Log_Print("pthread multi-threading\n"); - - threaded = true; - currentnumthreads = 0; - currentthreadid = 0; -} //end of the function ThreadInitLock -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ThreadShutdownLock(void) -{ - threaded = false; -} //end of the function ThreadShutdownLock -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ThreadSetupSemaphore(void) -{ - sem_init(&semaphore, 0, 0); -} //end of the function ThreadSetupSemaphore -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ThreadShutdownSemaphore(void) -{ - sem_destroy(&semaphore); -} //end of the function ThreadShutdownSemaphore -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ThreadSemaphoreWait(void) -{ - sem_wait(&semaphore); -} //end of the function ThreadSemaphoreWait -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ThreadSemaphoreIncrease(int count) -{ - int i; - - for (i = 0; i < count; i++) - { - sem_post(&semaphore); - } //end for -} //end of the function ThreadSemaphoreIncrease -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void RunThreadsOn(int workcnt, qboolean showpacifier, void(*func)(int)) -{ - int i; - pthread_t work_threads[MAX_THREADS]; - void *pthread_return; - pthread_attr_t attrib; - pthread_mutexattr_t mattrib; - int start, end; - - Log_Print("pthread multi-threading\n"); - - start = I_FloatTime (); - dispatch = 0; - workcount = workcnt; - oldf = -1; - pacifier = showpacifier; - threaded = true; - - if (numthreads < 1 || numthreads > MAX_THREADS) numthreads = 1; - - if (pacifier) - setbuf (stdout, NULL); - - for (i=0 ; i= numthreads) return; - currentnumthreads++; - func(-1); - currentnumthreads--; - } //end if - else - { - ThreadLock(); - if (currentnumthreads >= numthreads) - { - ThreadUnlock(); - return; - } //end if - //allocate new thread - thread = GetMemory(sizeof(thread_t)); - if (!thread) Error("can't allocate memory for thread\n"); - // - thread->threadid = currentthreadid; - - if (pthread_create(&thread->thread, NULL, (void *)func, (void *)thread->threadid) == -1) - Error ("pthread_create failed"); - - //add the thread to the end of the list - thread->next = NULL; - if (lastthread) lastthread->next = thread; - else firstthread = thread; - lastthread = thread; - // -#ifdef THREAD_DEBUG - qprintf("added thread with id %d\n", thread->threadid); -#endif //THREAD_DEBUG - // - currentnumthreads++; - currentthreadid++; - // - ThreadUnlock(); - } //end else -} //end of the function AddThread -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void RemoveThread(int threadid) -{ - thread_t *thread, *last; - - //if a single thread - if (threadid == -1) return; - // - ThreadLock(); - last = NULL; - for (thread = firstthread; thread; thread = thread->next) - { - if (thread->threadid == threadid) - { - if (last) last->next = thread->next; - else firstthread = thread->next; - if (!thread->next) lastthread = last; - // - FreeMemory(thread); - currentnumthreads--; -#ifdef THREAD_DEBUG - qprintf("removed thread with id %d\n", threadid); -#endif //THREAD_DEBUG - break; - } //end if - last = thread; - } //end if - if (!thread) Error("couldn't find thread with id %d", threadid); - ThreadUnlock(); -} //end of the function RemoveThread -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void WaitForAllThreadsFinished(void) -{ - pthread_t *thread; - void *pthread_return; - - ThreadLock(); - while(firstthread) - { - thread = &firstthread->thread; - ThreadUnlock(); - - if (pthread_join(*thread, &pthread_return) == -1) - Error("pthread_join failed"); - - ThreadLock(); - } //end while - ThreadUnlock(); -} //end of the function WaitForAllThreadsFinished -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int GetNumThreads(void) -{ - return currentnumthreads; -} //end of the function GetNumThreads - -#endif //LINUX - - -//=================================================================== -// -// IRIX -// -//=================================================================== - -#ifdef _MIPS_ISA - -#define USED - -#include -#include -#include -#include - -typedef struct thread_s -{ - int threadid; - int id; - struct thread_s *next; -} thread_t; - -thread_t *firstthread; -thread_t *lastthread; -int currentnumthreads; -int currentthreadid; - -int numthreads = 1; -static int enter; -static int numwaitingthreads = 0; - -abilock_t lck; - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ThreadSetDefault (void) -{ - if (numthreads == -1) - numthreads = prctl(PR_MAXPPROCS); - printf ("%i threads\n", numthreads); -//@@ - usconfig (CONF_INITUSERS, numthreads); -} //end of the function ThreadSetDefault -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ThreadLock (void) -{ - spin_lock (&lck); -} //end of the function ThreadLock -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ThreadUnlock (void) -{ - release_lock(&lck); -} //end of the function ThreadUnlock -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ThreadSetupLock(void) -{ - init_lock (&lck); - - Log_Print("IRIX multi-threading\n"); - - threaded = true; - currentnumthreads = 0; - currentthreadid = 0; -} //end of the function ThreadInitLock -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ThreadShutdownLock(void) -{ - threaded = false; -} //end of the function ThreadShutdownLock -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void RunThreadsOn (int workcnt, qboolean showpacifier, void(*func)(int)) -{ - int i; - int pid[MAX_THREADS]; - int start, end; - - start = I_FloatTime (); - dispatch = 0; - workcount = workcnt; - oldf = -1; - pacifier = showpacifier; - threaded = true; - - if (numthreads < 1 || numthreads > MAX_THREADS) numthreads = 1; - - if (pacifier) - setbuf (stdout, NULL); - - init_lock (&lck); - - for (i=0 ; i= numthreads) return; - currentnumthreads++; - func(-1); - currentnumthreads--; - } //end if - else - { - ThreadLock(); - if (currentnumthreads >= numthreads) - { - ThreadUnlock(); - return; - } //end if - //allocate new thread - thread = GetMemory(sizeof(thread_t)); - if (!thread) Error("can't allocate memory for thread\n"); - // - thread->threadid = currentthreadid; - - thread->id = sprocsp ( (void (*)(void *, size_t))func, PR_SALL, (void *)thread->threadid, NULL, 0x100000); - if (thread->id == -1) - { - perror ("sproc"); - Error ("sproc failed"); - } - - //add the thread to the end of the list - thread->next = NULL; - if (lastthread) lastthread->next = thread; - else firstthread = thread; - lastthread = thread; - // -#ifdef THREAD_DEBUG - qprintf("added thread with id %d\n", thread->threadid); -#endif //THREAD_DEBUG - // - currentnumthreads++; - currentthreadid++; - // - ThreadUnlock(); - } //end else -} //end of the function AddThread -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void RemoveThread(int threadid) -{ - thread_t *thread, *last; - - //if a single thread - if (threadid == -1) return; - // - ThreadLock(); - last = NULL; - for (thread = firstthread; thread; thread = thread->next) - { - if (thread->threadid == threadid) - { - if (last) last->next = thread->next; - else firstthread = thread->next; - if (!thread->next) lastthread = last; - // - FreeMemory(thread); - currentnumthreads--; -#ifdef THREAD_DEBUG - qprintf("removed thread with id %d\n", threadid); -#endif //THREAD_DEBUG - break; - } //end if - last = thread; - } //end if - if (!thread) Error("couldn't find thread with id %d", threadid); - ThreadUnlock(); -} //end of the function RemoveThread -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void WaitForAllThreadsFinished(void) -{ - ThreadLock(); - while(firstthread) - { - ThreadUnlock(); - - //wait (NULL); - - ThreadLock(); - } //end while - ThreadUnlock(); -} //end of the function WaitForAllThreadsFinished -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int GetNumThreads(void) -{ - return currentnumthreads; -} //end of the function GetNumThreads - -#endif //_MIPS_ISA - - -//======================================================================= -// -// SINGLE THREAD -// -//======================================================================= - -#ifndef USED - -int numthreads = 1; -int currentnumthreads = 0; - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ThreadSetDefault(void) -{ - numthreads = 1; -} //end of the function ThreadSetDefault -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ThreadLock(void) -{ -} //end of the function ThreadLock -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ThreadUnlock(void) -{ -} //end of the function ThreadUnlock -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ThreadSetupLock(void) -{ - Log_Print("no multi-threading\n"); -} //end of the function ThreadInitLock -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ThreadShutdownLock(void) -{ -} //end of the function ThreadShutdownLock -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ThreadSetupSemaphore(void) -{ -} //end of the function ThreadSetupSemaphore -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ThreadShutdownSemaphore(void) -{ -} //end of the function ThreadShutdownSemaphore -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ThreadSemaphoreWait(void) -{ -} //end of the function ThreadSemaphoreWait -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ThreadSemaphoreIncrease(int count) -{ -} //end of the function ThreadSemaphoreIncrease -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void RunThreadsOn(int workcnt, qboolean showpacifier, void(*func)(int)) -{ - int start, end; - - Log_Print("no multi-threading\n"); - dispatch = 0; - workcount = workcnt; - oldf = -1; - pacifier = showpacifier; - start = I_FloatTime (); -#ifdef NeXT - if (pacifier) - setbuf (stdout, NULL); -#endif - func(0); - - end = I_FloatTime (); - if (pacifier) - printf (" (%i)\n", end-start); -} //end of the function RunThreadsOn -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AddThread(void (*func)(int)) -{ - if (currentnumthreads >= numthreads) return; - currentnumthreads++; - func(-1); - currentnumthreads--; -} //end of the function AddThread -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void RemoveThread(int threadid) -{ -} //end of the function RemoveThread -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void WaitForAllThreadsFinished(void) -{ -} //end of the function WaitForAllThreadsFinished -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int GetNumThreads(void) -{ - return currentnumthreads; -} //end of the function GetNumThreads - -#endif //USED +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "l_cmd.h" +#include "l_threads.h" +#include "l_log.h" +#include "l_mem.h" + +#define MAX_THREADS 64 + +//#define THREAD_DEBUG + +int dispatch; +int workcount; +int oldf; +qboolean pacifier; +qboolean threaded; +void (*workfunction) (int); + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int GetThreadWork(void) +{ + int r; + int f; + + ThreadLock(); + + if (dispatch == workcount) + { + ThreadUnlock (); + return -1; + } + + f = 10*dispatch / workcount; + if (f != oldf) + { + oldf = f; + if (pacifier) + printf ("%i...", f); + } //end if + + r = dispatch; + dispatch++; + ThreadUnlock (); + + return r; +} //end of the function GetThreadWork +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadWorkerFunction(int threadnum) +{ + int work; + + while(1) + { + work = GetThreadWork (); + if (work == -1) + break; +//printf ("thread %i, work %i\n", threadnum, work); + workfunction(work); + } //end while +} //end of the function ThreadWorkerFunction +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void RunThreadsOnIndividual (int workcnt, qboolean showpacifier, void(*func)(int)) +{ + if (numthreads == -1) + ThreadSetDefault (); + workfunction = func; + RunThreadsOn (workcnt, showpacifier, ThreadWorkerFunction); +} //end of the function RunThreadsOnIndividual + + +//=================================================================== +// +// WIN32 +// +//=================================================================== + +#if defined(WIN32) || defined(_WIN32) + +#define USED + +#include + +typedef struct thread_s +{ + HANDLE handle; + int threadid; + int id; + struct thread_s *next; +} thread_t; + +thread_t *firstthread; +thread_t *lastthread; +int currentnumthreads; +int currentthreadid; + +int numthreads = 1; +CRITICAL_SECTION crit; +HANDLE semaphore; +static int enter; +static int numwaitingthreads = 0; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadSetDefault(void) +{ + SYSTEM_INFO info; + + if (numthreads == -1) // not set manually + { + GetSystemInfo (&info); + numthreads = info.dwNumberOfProcessors; + if (numthreads < 1 || numthreads > 32) + numthreads = 1; + } //end if + qprintf ("%i threads\n", numthreads); +} //end of the function ThreadSetDefault +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadLock(void) +{ + if (!threaded) + { + Error("ThreadLock: !threaded"); + return; + } //end if + EnterCriticalSection(&crit); + if (enter) + Error("Recursive ThreadLock\n"); + enter = 1; +} //end of the function ThreadLock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadUnlock (void) +{ + if (!threaded) + { + Error("ThreadUnlock: !threaded"); + return; + } //end if + if (!enter) + Error("ThreadUnlock without lock\n"); + enter = 0; + LeaveCriticalSection(&crit); +} //end of the function ThreadUnlock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadSetupLock(void) +{ + Log_Print("Win32 multi-threading\n"); + InitializeCriticalSection(&crit); + threaded = true; //Stupid me... forgot this!!! + currentnumthreads = 0; + currentthreadid = 0; +} //end of the function ThreadInitLock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadShutdownLock(void) +{ + DeleteCriticalSection(&crit); + threaded = false; //Stupid me... forgot this!!! +} //end of the function ThreadShutdownLock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadSetupSemaphore(void) +{ + semaphore = CreateSemaphore(NULL, 0, 99999999, "bspc"); +} //end of the function ThreadSetupSemaphore +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadShutdownSemaphore(void) +{ +} //end of the function ThreadShutdownSemaphore +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadSemaphoreWait(void) +{ + WaitForSingleObject(semaphore, INFINITE); +} //end of the function ThreadSemaphoreWait +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadSemaphoreIncrease(int count) +{ + ReleaseSemaphore(semaphore, count, NULL); +} //end of the function ThreadSemaphoreIncrease +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void RunThreadsOn(int workcnt, qboolean showpacifier, void(*func)(int)) +{ + int threadid[MAX_THREADS]; + HANDLE threadhandle[MAX_THREADS]; + int i; + int start, end; + + Log_Print("Win32 multi-threading\n"); + start = I_FloatTime (); + dispatch = 0; + workcount = workcnt; + oldf = -1; + pacifier = showpacifier; + threaded = true; + + if (numthreads == -1) + ThreadSetDefault (); + + if (numthreads < 1 || numthreads > MAX_THREADS) numthreads = 1; + // + // run threads in parallel + // + InitializeCriticalSection (&crit); + + numwaitingthreads = 0; + + if (numthreads == 1) + { // use same thread + func (0); + } //end if + else + { +// printf("starting %d threads\n", numthreads); + for (i = 0; i < numthreads; i++) + { + threadhandle[i] = CreateThread( + NULL, // LPSECURITY_ATTRIBUTES lpsa, + 0, // DWORD cbStack, + (LPTHREAD_START_ROUTINE)func, // LPTHREAD_START_ROUTINE lpStartAddr, + (LPVOID)i, // LPVOID lpvThreadParm, + 0, // DWORD fdwCreate, + &threadid[i]); +// printf("started thread %d\n", i); + } //end for + + for (i = 0; i < numthreads; i++) + WaitForSingleObject (threadhandle[i], INFINITE); + } //end else + DeleteCriticalSection (&crit); + + threaded = false; + end = I_FloatTime (); + if (pacifier) printf (" (%i)\n", end-start); +} //end of the function RunThreadsOn +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AddThread(void (*func)(int)) +{ + thread_t *thread; + + if (numthreads == 1) + { + if (currentnumthreads >= numthreads) return; + currentnumthreads++; + func(-1); + currentnumthreads--; + } //end if + else + { + ThreadLock(); + if (currentnumthreads >= numthreads) + { + ThreadUnlock(); + return; + } //end if + //allocate new thread + thread = GetMemory(sizeof(thread_t)); + if (!thread) Error("can't allocate memory for thread\n"); + + // + thread->threadid = currentthreadid; + thread->handle = CreateThread( + NULL, // LPSECURITY_ATTRIBUTES lpsa, + 0, // DWORD cbStack, + (LPTHREAD_START_ROUTINE)func, // LPTHREAD_START_ROUTINE lpStartAddr, + (LPVOID) thread->threadid, // LPVOID lpvThreadParm, + 0, // DWORD fdwCreate, + &thread->id); + + //add the thread to the end of the list + thread->next = NULL; + if (lastthread) lastthread->next = thread; + else firstthread = thread; + lastthread = thread; + // +#ifdef THREAD_DEBUG + qprintf("added thread with id %d\n", thread->threadid); +#endif //THREAD_DEBUG + // + currentnumthreads++; + currentthreadid++; + // + ThreadUnlock(); + } //end else +} //end of the function AddThread +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void RemoveThread(int threadid) +{ + thread_t *thread, *last; + + //if a single thread + if (threadid == -1) return; + // + ThreadLock(); + last = NULL; + for (thread = firstthread; thread; thread = thread->next) + { + if (thread->threadid == threadid) + { + if (last) last->next = thread->next; + else firstthread = thread->next; + if (!thread->next) lastthread = last; + // + FreeMemory(thread); + currentnumthreads--; +#ifdef THREAD_DEBUG + qprintf("removed thread with id %d\n", threadid); +#endif //THREAD_DEBUG + break; + } //end if + last = thread; + } //end if + if (!thread) Error("couldn't find thread with id %d", threadid); + ThreadUnlock(); +} //end of the function RemoveThread +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void WaitForAllThreadsFinished(void) +{ + HANDLE handle; + + ThreadLock(); + while(firstthread) + { + handle = firstthread->handle; + ThreadUnlock(); + + WaitForSingleObject(handle, INFINITE); + + ThreadLock(); + } //end while + ThreadUnlock(); +} //end of the function WaitForAllThreadsFinished +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int GetNumThreads(void) +{ + return currentnumthreads; +} //end of the function GetNumThreads + +#endif + + +//=================================================================== +// +// OSF1 +// +//=================================================================== + +#if defined(__osf__) + +#define USED + +#include + +typedef struct thread_s +{ + pthread_t thread; + int threadid; + int id; + struct thread_s *next; +} thread_t; + +thread_t *firstthread; +thread_t *lastthread; +int currentnumthreads; +int currentthreadid; + +int numthreads = 1; +pthread_mutex_t my_mutex; +pthread_attr_t attrib; +static int enter; +static int numwaitingthreads = 0; + + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadSetDefault(void) +{ + if (numthreads == -1) // not set manually + { + numthreads = 1; + } //end if + qprintf("%i threads\n", numthreads); +} //end of the function ThreadSetDefault +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadLock(void) +{ + if (!threaded) + { + Error("ThreadLock: !threaded"); + return; + } //end if + if (my_mutex) + { + pthread_mutex_lock(my_mutex); + } //end if + if (enter) + Error("Recursive ThreadLock\n"); + enter = 1; +} //end of the function ThreadLock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadUnlock(void) +{ + if (!threaded) + { + Error("ThreadUnlock: !threaded"); + return; + } //end if + if (!enter) + Error("ThreadUnlock without lock\n"); + enter = 0; + if (my_mutex) + { + pthread_mutex_unlock(my_mutex); + } //end if +} //end of the function ThreadUnlock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadSetupLock(void) +{ + pthread_mutexattr_t mattrib; + + Log_Print("pthread multi-threading\n"); + + if (!my_mutex) + { + my_mutex = GetMemory(sizeof(*my_mutex)); + if (pthread_mutexattr_create (&mattrib) == -1) + Error ("pthread_mutex_attr_create failed"); + if (pthread_mutexattr_setkind_np (&mattrib, MUTEX_FAST_NP) == -1) + Error ("pthread_mutexattr_setkind_np failed"); + if (pthread_mutex_init (my_mutex, mattrib) == -1) + Error ("pthread_mutex_init failed"); + } + + if (pthread_attr_create (&attrib) == -1) + Error ("pthread_attr_create failed"); + if (pthread_attr_setstacksize (&attrib, 0x100000) == -1) + Error ("pthread_attr_setstacksize failed"); + + threaded = true; + currentnumthreads = 0; + currentthreadid = 0; +} //end of the function ThreadInitLock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadShutdownLock(void) +{ + threaded = false; +} //end of the function ThreadShutdownLock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void RunThreadsOn(int workcnt, qboolean showpacifier, void(*func)(int)) +{ + int i; + pthread_t work_threads[MAX_THREADS]; + pthread_addr_t status; + pthread_attr_t attrib; + pthread_mutexattr_t mattrib; + int start, end; + + Log_Print("pthread multi-threading\n"); + + start = I_FloatTime (); + dispatch = 0; + workcount = workcnt; + oldf = -1; + pacifier = showpacifier; + threaded = true; + + if (numthreads < 1 || numthreads > MAX_THREADS) numthreads = 1; + + if (pacifier) + setbuf (stdout, NULL); + + if (!my_mutex) + { + my_mutex = GetMemory(sizeof(*my_mutex)); + if (pthread_mutexattr_create (&mattrib) == -1) + Error ("pthread_mutex_attr_create failed"); + if (pthread_mutexattr_setkind_np (&mattrib, MUTEX_FAST_NP) == -1) + Error ("pthread_mutexattr_setkind_np failed"); + if (pthread_mutex_init (my_mutex, mattrib) == -1) + Error ("pthread_mutex_init failed"); + } + + if (pthread_attr_create (&attrib) == -1) + Error ("pthread_attr_create failed"); + if (pthread_attr_setstacksize (&attrib, 0x100000) == -1) + Error ("pthread_attr_setstacksize failed"); + + for (i=0 ; i= numthreads) return; + currentnumthreads++; + func(-1); + currentnumthreads--; + } //end if + else + { + ThreadLock(); + if (currentnumthreads >= numthreads) + { + ThreadUnlock(); + return; + } //end if + //allocate new thread + thread = GetMemory(sizeof(thread_t)); + if (!thread) Error("can't allocate memory for thread\n"); + // + thread->threadid = currentthreadid; + + if (pthread_create(&thread->thread, attrib, (pthread_startroutine_t)func, (pthread_addr_t)thread->threadid) == -1) + Error ("pthread_create failed"); + + //add the thread to the end of the list + thread->next = NULL; + if (lastthread) lastthread->next = thread; + else firstthread = thread; + lastthread = thread; + // +#ifdef THREAD_DEBUG + qprintf("added thread with id %d\n", thread->threadid); +#endif //THREAD_DEBUG + // + currentnumthreads++; + currentthreadid++; + // + ThreadUnlock(); + } //end else +} //end of the function AddThread +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void RemoveThread(int threadid) +{ + thread_t *thread, *last; + + //if a single thread + if (threadid == -1) return; + // + ThreadLock(); + last = NULL; + for (thread = firstthread; thread; thread = thread->next) + { + if (thread->threadid == threadid) + { + if (last) last->next = thread->next; + else firstthread = thread->next; + if (!thread->next) lastthread = last; + // + FreeMemory(thread); + currentnumthreads--; +#ifdef THREAD_DEBUG + qprintf("removed thread with id %d\n", threadid); +#endif //THREAD_DEBUG + break; + } //end if + last = thread; + } //end if + if (!thread) Error("couldn't find thread with id %d", threadid); + ThreadUnlock(); +} //end of the function RemoveThread +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void WaitForAllThreadsFinished(void) +{ + pthread_t *thread; + pthread_addr_t status; + + ThreadLock(); + while(firstthread) + { + thread = &firstthread->thread; + ThreadUnlock(); + + if (pthread_join(*thread, &status) == -1) + Error("pthread_join failed"); + + ThreadLock(); + } //end while + ThreadUnlock(); +} //end of the function WaitForAllThreadsFinished +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int GetNumThreads(void) +{ + return currentnumthreads; +} //end of the function GetNumThreads + +#endif + +//=================================================================== +// +// LINUX +// +//=================================================================== + +#if defined(LINUX) + +#define USED + +#include +#include + +typedef struct thread_s +{ + pthread_t thread; + int threadid; + int id; + struct thread_s *next; +} thread_t; + +thread_t *firstthread; +thread_t *lastthread; +int currentnumthreads; +int currentthreadid; + +int numthreads = 1; +pthread_mutex_t my_mutex = PTHREAD_MUTEX_INITIALIZER; +pthread_attr_t attrib; +sem_t semaphore; +static int enter; +static int numwaitingthreads = 0; + + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadSetDefault(void) +{ + if (numthreads == -1) // not set manually + { + numthreads = 1; + } //end if + qprintf("%i threads\n", numthreads); +} //end of the function ThreadSetDefault +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadLock(void) +{ + if (!threaded) + { + Error("ThreadLock: !threaded"); + return; + } //end if + pthread_mutex_lock(&my_mutex); + if (enter) + Error("Recursive ThreadLock\n"); + enter = 1; +} //end of the function ThreadLock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadUnlock(void) +{ + if (!threaded) + { + Error("ThreadUnlock: !threaded"); + return; + } //end if + if (!enter) + Error("ThreadUnlock without lock\n"); + enter = 0; + pthread_mutex_unlock(&my_mutex); +} //end of the function ThreadUnlock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadSetupLock(void) +{ + pthread_mutexattr_t mattrib; + + Log_Print("pthread multi-threading\n"); + + threaded = true; + currentnumthreads = 0; + currentthreadid = 0; +} //end of the function ThreadInitLock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadShutdownLock(void) +{ + threaded = false; +} //end of the function ThreadShutdownLock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadSetupSemaphore(void) +{ + sem_init(&semaphore, 0, 0); +} //end of the function ThreadSetupSemaphore +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadShutdownSemaphore(void) +{ + sem_destroy(&semaphore); +} //end of the function ThreadShutdownSemaphore +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadSemaphoreWait(void) +{ + sem_wait(&semaphore); +} //end of the function ThreadSemaphoreWait +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadSemaphoreIncrease(int count) +{ + int i; + + for (i = 0; i < count; i++) + { + sem_post(&semaphore); + } //end for +} //end of the function ThreadSemaphoreIncrease +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void RunThreadsOn(int workcnt, qboolean showpacifier, void(*func)(int)) +{ + int i; + pthread_t work_threads[MAX_THREADS]; + void *pthread_return; + pthread_attr_t attrib; + pthread_mutexattr_t mattrib; + int start, end; + + Log_Print("pthread multi-threading\n"); + + start = I_FloatTime (); + dispatch = 0; + workcount = workcnt; + oldf = -1; + pacifier = showpacifier; + threaded = true; + + if (numthreads < 1 || numthreads > MAX_THREADS) numthreads = 1; + + if (pacifier) + setbuf (stdout, NULL); + + for (i=0 ; i= numthreads) return; + currentnumthreads++; + func(-1); + currentnumthreads--; + } //end if + else + { + ThreadLock(); + if (currentnumthreads >= numthreads) + { + ThreadUnlock(); + return; + } //end if + //allocate new thread + thread = GetMemory(sizeof(thread_t)); + if (!thread) Error("can't allocate memory for thread\n"); + // + thread->threadid = currentthreadid; + + if (pthread_create(&thread->thread, NULL, (void *)func, (void *)thread->threadid) == -1) + Error ("pthread_create failed"); + + //add the thread to the end of the list + thread->next = NULL; + if (lastthread) lastthread->next = thread; + else firstthread = thread; + lastthread = thread; + // +#ifdef THREAD_DEBUG + qprintf("added thread with id %d\n", thread->threadid); +#endif //THREAD_DEBUG + // + currentnumthreads++; + currentthreadid++; + // + ThreadUnlock(); + } //end else +} //end of the function AddThread +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void RemoveThread(int threadid) +{ + thread_t *thread, *last; + + //if a single thread + if (threadid == -1) return; + // + ThreadLock(); + last = NULL; + for (thread = firstthread; thread; thread = thread->next) + { + if (thread->threadid == threadid) + { + if (last) last->next = thread->next; + else firstthread = thread->next; + if (!thread->next) lastthread = last; + // + FreeMemory(thread); + currentnumthreads--; +#ifdef THREAD_DEBUG + qprintf("removed thread with id %d\n", threadid); +#endif //THREAD_DEBUG + break; + } //end if + last = thread; + } //end if + if (!thread) Error("couldn't find thread with id %d", threadid); + ThreadUnlock(); +} //end of the function RemoveThread +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void WaitForAllThreadsFinished(void) +{ + pthread_t *thread; + void *pthread_return; + + ThreadLock(); + while(firstthread) + { + thread = &firstthread->thread; + ThreadUnlock(); + + if (pthread_join(*thread, &pthread_return) == -1) + Error("pthread_join failed"); + + ThreadLock(); + } //end while + ThreadUnlock(); +} //end of the function WaitForAllThreadsFinished +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int GetNumThreads(void) +{ + return currentnumthreads; +} //end of the function GetNumThreads + +#endif //LINUX + + +//=================================================================== +// +// IRIX +// +//=================================================================== + +#ifdef _MIPS_ISA + +#define USED + +#include +#include +#include +#include + +typedef struct thread_s +{ + int threadid; + int id; + struct thread_s *next; +} thread_t; + +thread_t *firstthread; +thread_t *lastthread; +int currentnumthreads; +int currentthreadid; + +int numthreads = 1; +static int enter; +static int numwaitingthreads = 0; + +abilock_t lck; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadSetDefault (void) +{ + if (numthreads == -1) + numthreads = prctl(PR_MAXPPROCS); + printf ("%i threads\n", numthreads); +//@@ + usconfig (CONF_INITUSERS, numthreads); +} //end of the function ThreadSetDefault +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadLock (void) +{ + spin_lock (&lck); +} //end of the function ThreadLock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadUnlock (void) +{ + release_lock(&lck); +} //end of the function ThreadUnlock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadSetupLock(void) +{ + init_lock (&lck); + + Log_Print("IRIX multi-threading\n"); + + threaded = true; + currentnumthreads = 0; + currentthreadid = 0; +} //end of the function ThreadInitLock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadShutdownLock(void) +{ + threaded = false; +} //end of the function ThreadShutdownLock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void RunThreadsOn (int workcnt, qboolean showpacifier, void(*func)(int)) +{ + int i; + int pid[MAX_THREADS]; + int start, end; + + start = I_FloatTime (); + dispatch = 0; + workcount = workcnt; + oldf = -1; + pacifier = showpacifier; + threaded = true; + + if (numthreads < 1 || numthreads > MAX_THREADS) numthreads = 1; + + if (pacifier) + setbuf (stdout, NULL); + + init_lock (&lck); + + for (i=0 ; i= numthreads) return; + currentnumthreads++; + func(-1); + currentnumthreads--; + } //end if + else + { + ThreadLock(); + if (currentnumthreads >= numthreads) + { + ThreadUnlock(); + return; + } //end if + //allocate new thread + thread = GetMemory(sizeof(thread_t)); + if (!thread) Error("can't allocate memory for thread\n"); + // + thread->threadid = currentthreadid; + + thread->id = sprocsp ( (void (*)(void *, size_t))func, PR_SALL, (void *)thread->threadid, NULL, 0x100000); + if (thread->id == -1) + { + perror ("sproc"); + Error ("sproc failed"); + } + + //add the thread to the end of the list + thread->next = NULL; + if (lastthread) lastthread->next = thread; + else firstthread = thread; + lastthread = thread; + // +#ifdef THREAD_DEBUG + qprintf("added thread with id %d\n", thread->threadid); +#endif //THREAD_DEBUG + // + currentnumthreads++; + currentthreadid++; + // + ThreadUnlock(); + } //end else +} //end of the function AddThread +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void RemoveThread(int threadid) +{ + thread_t *thread, *last; + + //if a single thread + if (threadid == -1) return; + // + ThreadLock(); + last = NULL; + for (thread = firstthread; thread; thread = thread->next) + { + if (thread->threadid == threadid) + { + if (last) last->next = thread->next; + else firstthread = thread->next; + if (!thread->next) lastthread = last; + // + FreeMemory(thread); + currentnumthreads--; +#ifdef THREAD_DEBUG + qprintf("removed thread with id %d\n", threadid); +#endif //THREAD_DEBUG + break; + } //end if + last = thread; + } //end if + if (!thread) Error("couldn't find thread with id %d", threadid); + ThreadUnlock(); +} //end of the function RemoveThread +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void WaitForAllThreadsFinished(void) +{ + ThreadLock(); + while(firstthread) + { + ThreadUnlock(); + + //wait (NULL); + + ThreadLock(); + } //end while + ThreadUnlock(); +} //end of the function WaitForAllThreadsFinished +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int GetNumThreads(void) +{ + return currentnumthreads; +} //end of the function GetNumThreads + +#endif //_MIPS_ISA + + +//======================================================================= +// +// SINGLE THREAD +// +//======================================================================= + +#ifndef USED + +int numthreads = 1; +int currentnumthreads = 0; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadSetDefault(void) +{ + numthreads = 1; +} //end of the function ThreadSetDefault +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadLock(void) +{ +} //end of the function ThreadLock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadUnlock(void) +{ +} //end of the function ThreadUnlock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadSetupLock(void) +{ + Log_Print("no multi-threading\n"); +} //end of the function ThreadInitLock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadShutdownLock(void) +{ +} //end of the function ThreadShutdownLock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadSetupSemaphore(void) +{ +} //end of the function ThreadSetupSemaphore +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadShutdownSemaphore(void) +{ +} //end of the function ThreadShutdownSemaphore +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadSemaphoreWait(void) +{ +} //end of the function ThreadSemaphoreWait +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadSemaphoreIncrease(int count) +{ +} //end of the function ThreadSemaphoreIncrease +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void RunThreadsOn(int workcnt, qboolean showpacifier, void(*func)(int)) +{ + int start, end; + + Log_Print("no multi-threading\n"); + dispatch = 0; + workcount = workcnt; + oldf = -1; + pacifier = showpacifier; + start = I_FloatTime (); +#ifdef NeXT + if (pacifier) + setbuf (stdout, NULL); +#endif + func(0); + + end = I_FloatTime (); + if (pacifier) + printf (" (%i)\n", end-start); +} //end of the function RunThreadsOn +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AddThread(void (*func)(int)) +{ + if (currentnumthreads >= numthreads) return; + currentnumthreads++; + func(-1); + currentnumthreads--; +} //end of the function AddThread +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void RemoveThread(int threadid) +{ +} //end of the function RemoveThread +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void WaitForAllThreadsFinished(void) +{ +} //end of the function WaitForAllThreadsFinished +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int GetNumThreads(void) +{ + return currentnumthreads; +} //end of the function GetNumThreads + +#endif //USED diff --git a/code/bspc/l_threads.h b/code/bspc/l_threads.h index 2c6d8df..fe4df17 100755 --- a/code/bspc/l_threads.h +++ b/code/bspc/l_threads.h @@ -1,45 +1,45 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -extern int numthreads; - -void ThreadSetDefault (void); -int GetThreadWork (void); -void RunThreadsOnIndividual (int workcnt, qboolean showpacifier, void(*func)(int)); -void RunThreadsOn (int workcnt, qboolean showpacifier, void(*func)(int)); - -//mutex -void ThreadSetupLock(void); -void ThreadShutdownLock(void); -void ThreadLock (void); -void ThreadUnlock (void); -//semaphore -void ThreadSetupSemaphore(void); -void ThreadShutdownSemaphore(void); -void ThreadSemaphoreWait(void); -void ThreadSemaphoreIncrease(int count); -//add/remove threads -void AddThread(void (*func)(int)); -void RemoveThread(int threadid); -void WaitForAllThreadsFinished(void); -int GetNumThreads(void); - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +extern int numthreads; + +void ThreadSetDefault (void); +int GetThreadWork (void); +void RunThreadsOnIndividual (int workcnt, qboolean showpacifier, void(*func)(int)); +void RunThreadsOn (int workcnt, qboolean showpacifier, void(*func)(int)); + +//mutex +void ThreadSetupLock(void); +void ThreadShutdownLock(void); +void ThreadLock (void); +void ThreadUnlock (void); +//semaphore +void ThreadSetupSemaphore(void); +void ThreadShutdownSemaphore(void); +void ThreadSemaphoreWait(void); +void ThreadSemaphoreIncrease(int count); +//add/remove threads +void AddThread(void (*func)(int)); +void RemoveThread(int threadid); +void WaitForAllThreadsFinished(void); +int GetNumThreads(void); + diff --git a/code/bspc/l_utils.c b/code/bspc/l_utils.c index f9d6d5f..8b229aa 100755 --- a/code/bspc/l_utils.c +++ b/code/bspc/l_utils.c @@ -1,259 +1,259 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -//#ifndef BOTLIB -//#define BOTLIB -//#endif //BOTLIB - -#ifdef BOTLIB -#include "q_shared.h" -#include "qfiles.h" -#include "botlib.h" -#include "l_log.h" -#include "l_libvar.h" -#include "l_memory.h" -//#include "l_utils.h" -#include "be_interface.h" -#else //BOTLIB -#include "qbsp.h" -#include "l_mem.h" -#endif //BOTLIB - -#ifdef BOTLIB -//======================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//======================================================================== -void Vector2Angles(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; - } //end if - else - { - yaw = (int) (atan2(value1[1], value1[0]) * 180 / M_PI); - if (yaw < 0) yaw += 360; - - forward = sqrt (value1[0]*value1[0] + value1[1]*value1[1]); - pitch = (int) (atan2(value1[2], forward) * 180 / M_PI); - if (pitch < 0) pitch += 360; - } //end else - - angles[PITCH] = -pitch; - angles[YAW] = yaw; - angles[ROLL] = 0; -} //end of the function Vector2Angles -#endif //BOTLIB -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ConvertPath(char *path) -{ - while(*path) - { - if (*path == '/' || *path == '\\') *path = PATHSEPERATOR_CHAR; - path++; - } //end while -} //end of the function ConvertPath -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AppendPathSeperator(char *path, int length) -{ - int pathlen = strlen(path); - - if (strlen(path) && length-pathlen > 1 && path[pathlen-1] != '/' && path[pathlen-1] != '\\') - { - path[pathlen] = PATHSEPERATOR_CHAR; - path[pathlen+1] = '\0'; - } //end if -} //end of the function AppenPathSeperator - -#if 0 -//=========================================================================== -// returns pointer to file handle -// sets offset to and length of 'filename' in the pak file -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean FindFileInPak(char *pakfile, char *filename, foundfile_t *file) -{ - FILE *fp; - dpackheader_t packheader; - dpackfile_t *packfiles; - int numdirs, i; - char path[MAX_PATH]; - - //open the pak file - fp = fopen(pakfile, "rb"); - if (!fp) - { - return false; - } //end if - //read pak header, check for valid pak id and seek to the dir entries - if ((fread(&packheader, 1, sizeof(dpackheader_t), fp) != sizeof(dpackheader_t)) - || (packheader.ident != IDPAKHEADER) - || (fseek(fp, LittleLong(packheader.dirofs), SEEK_SET)) - ) - { - fclose(fp); - return false; - } //end if - //number of dir entries in the pak file - numdirs = LittleLong(packheader.dirlen) / sizeof(dpackfile_t); - packfiles = (dpackfile_t *) GetMemory(numdirs * sizeof(dpackfile_t)); - //read the dir entry - if (fread(packfiles, sizeof(dpackfile_t), numdirs, fp) != numdirs) - { - fclose(fp); - FreeMemory(packfiles); - return false; - } //end if - fclose(fp); - // - strcpy(path, filename); - ConvertPath(path); - //find the dir entry in the pak file - for (i = 0; i < numdirs; i++) - { - //convert the dir entry name - ConvertPath(packfiles[i].name); - //compare the dir entry name with the filename - if (Q_strcasecmp(packfiles[i].name, path) == 0) - { - strcpy(file->filename, pakfile); - file->offset = LittleLong(packfiles[i].filepos); - file->length = LittleLong(packfiles[i].filelen); - FreeMemory(packfiles); - return true; - } //end if - } //end for - FreeMemory(packfiles); - return false; -} //end of the function FindFileInPak -//=========================================================================== -// find a Quake2 file -// returns full path in 'filename' -// sets offset and length of the file -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean FindQuakeFile2(char *basedir, char *gamedir, char *filename, foundfile_t *file) -{ - int dir, i; - //NOTE: 3 is necessary (LCC bug???) - char gamedirs[3][MAX_PATH] = {"","",""}; - char filedir[MAX_PATH] = ""; - - // - if (gamedir) strncpy(gamedirs[0], gamedir, MAX_PATH); - strncpy(gamedirs[1], "baseq2", MAX_PATH); - // - //find the file in the two game directories - for (dir = 0; dir < 2; dir++) - { - //check if the file is in a directory - filedir[0] = 0; - if (basedir && strlen(basedir)) - { - strncpy(filedir, basedir, MAX_PATH); - AppendPathSeperator(filedir, MAX_PATH); - } //end if - if (strlen(gamedirs[dir])) - { - strncat(filedir, gamedirs[dir], MAX_PATH - strlen(filedir)); - AppendPathSeperator(filedir, MAX_PATH); - } //end if - strncat(filedir, filename, MAX_PATH - strlen(filedir)); - ConvertPath(filedir); - Log_Write("accessing %s", filedir); - if (!access(filedir, 0x04)) - { - strcpy(file->filename, filedir); - file->length = 0; - file->offset = 0; - return true; - } //end if - //check if the file is in a pak?.pak - for (i = 0; i < 10; i++) - { - filedir[0] = 0; - if (basedir && strlen(basedir)) - { - strncpy(filedir, basedir, MAX_PATH); - AppendPathSeperator(filedir, MAX_PATH); - } //end if - if (strlen(gamedirs[dir])) - { - strncat(filedir, gamedirs[dir], MAX_PATH - strlen(filedir)); - AppendPathSeperator(filedir, MAX_PATH); - } //end if - sprintf(&filedir[strlen(filedir)], "pak%d.pak\0", i); - if (!access(filedir, 0x04)) - { - Log_Write("searching %s in %s", filename, filedir); - if (FindFileInPak(filedir, filename, file)) return true; - } //end if - } //end for - } //end for - file->offset = 0; - file->length = 0; - return false; -} //end of the function FindQuakeFile2 -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -#ifdef BOTLIB -qboolean FindQuakeFile(char *filename, foundfile_t *file) -{ - return FindQuakeFile2(LibVarGetString("basedir"), - LibVarGetString("gamedir"), filename, file); -} //end of the function FindQuakeFile -#else //BOTLIB -qboolean FindQuakeFile(char *basedir, char *gamedir, char *filename, foundfile_t *file) -{ - return FindQuakeFile2(basedir, gamedir, filename, file); -} //end of the function FindQuakeFile -#endif //BOTLIB - -#endif +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +//#ifndef BOTLIB +//#define BOTLIB +//#endif //BOTLIB + +#ifdef BOTLIB +#include "q_shared.h" +#include "qfiles.h" +#include "botlib.h" +#include "l_log.h" +#include "l_libvar.h" +#include "l_memory.h" +//#include "l_utils.h" +#include "be_interface.h" +#else //BOTLIB +#include "qbsp.h" +#include "l_mem.h" +#endif //BOTLIB + +#ifdef BOTLIB +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void Vector2Angles(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; + } //end if + else + { + yaw = (int) (atan2(value1[1], value1[0]) * 180 / M_PI); + if (yaw < 0) yaw += 360; + + forward = sqrt (value1[0]*value1[0] + value1[1]*value1[1]); + pitch = (int) (atan2(value1[2], forward) * 180 / M_PI); + if (pitch < 0) pitch += 360; + } //end else + + angles[PITCH] = -pitch; + angles[YAW] = yaw; + angles[ROLL] = 0; +} //end of the function Vector2Angles +#endif //BOTLIB +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ConvertPath(char *path) +{ + while(*path) + { + if (*path == '/' || *path == '\\') *path = PATHSEPERATOR_CHAR; + path++; + } //end while +} //end of the function ConvertPath +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AppendPathSeperator(char *path, int length) +{ + int pathlen = strlen(path); + + if (strlen(path) && length-pathlen > 1 && path[pathlen-1] != '/' && path[pathlen-1] != '\\') + { + path[pathlen] = PATHSEPERATOR_CHAR; + path[pathlen+1] = '\0'; + } //end if +} //end of the function AppenPathSeperator + +#if 0 +//=========================================================================== +// returns pointer to file handle +// sets offset to and length of 'filename' in the pak file +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean FindFileInPak(char *pakfile, char *filename, foundfile_t *file) +{ + FILE *fp; + dpackheader_t packheader; + dpackfile_t *packfiles; + int numdirs, i; + char path[MAX_PATH]; + + //open the pak file + fp = fopen(pakfile, "rb"); + if (!fp) + { + return false; + } //end if + //read pak header, check for valid pak id and seek to the dir entries + if ((fread(&packheader, 1, sizeof(dpackheader_t), fp) != sizeof(dpackheader_t)) + || (packheader.ident != IDPAKHEADER) + || (fseek(fp, LittleLong(packheader.dirofs), SEEK_SET)) + ) + { + fclose(fp); + return false; + } //end if + //number of dir entries in the pak file + numdirs = LittleLong(packheader.dirlen) / sizeof(dpackfile_t); + packfiles = (dpackfile_t *) GetMemory(numdirs * sizeof(dpackfile_t)); + //read the dir entry + if (fread(packfiles, sizeof(dpackfile_t), numdirs, fp) != numdirs) + { + fclose(fp); + FreeMemory(packfiles); + return false; + } //end if + fclose(fp); + // + strcpy(path, filename); + ConvertPath(path); + //find the dir entry in the pak file + for (i = 0; i < numdirs; i++) + { + //convert the dir entry name + ConvertPath(packfiles[i].name); + //compare the dir entry name with the filename + if (Q_strcasecmp(packfiles[i].name, path) == 0) + { + strcpy(file->filename, pakfile); + file->offset = LittleLong(packfiles[i].filepos); + file->length = LittleLong(packfiles[i].filelen); + FreeMemory(packfiles); + return true; + } //end if + } //end for + FreeMemory(packfiles); + return false; +} //end of the function FindFileInPak +//=========================================================================== +// find a Quake2 file +// returns full path in 'filename' +// sets offset and length of the file +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean FindQuakeFile2(char *basedir, char *gamedir, char *filename, foundfile_t *file) +{ + int dir, i; + //NOTE: 3 is necessary (LCC bug???) + char gamedirs[3][MAX_PATH] = {"","",""}; + char filedir[MAX_PATH] = ""; + + // + if (gamedir) strncpy(gamedirs[0], gamedir, MAX_PATH); + strncpy(gamedirs[1], "baseq2", MAX_PATH); + // + //find the file in the two game directories + for (dir = 0; dir < 2; dir++) + { + //check if the file is in a directory + filedir[0] = 0; + if (basedir && strlen(basedir)) + { + strncpy(filedir, basedir, MAX_PATH); + AppendPathSeperator(filedir, MAX_PATH); + } //end if + if (strlen(gamedirs[dir])) + { + strncat(filedir, gamedirs[dir], MAX_PATH - strlen(filedir)); + AppendPathSeperator(filedir, MAX_PATH); + } //end if + strncat(filedir, filename, MAX_PATH - strlen(filedir)); + ConvertPath(filedir); + Log_Write("accessing %s", filedir); + if (!access(filedir, 0x04)) + { + strcpy(file->filename, filedir); + file->length = 0; + file->offset = 0; + return true; + } //end if + //check if the file is in a pak?.pak + for (i = 0; i < 10; i++) + { + filedir[0] = 0; + if (basedir && strlen(basedir)) + { + strncpy(filedir, basedir, MAX_PATH); + AppendPathSeperator(filedir, MAX_PATH); + } //end if + if (strlen(gamedirs[dir])) + { + strncat(filedir, gamedirs[dir], MAX_PATH - strlen(filedir)); + AppendPathSeperator(filedir, MAX_PATH); + } //end if + sprintf(&filedir[strlen(filedir)], "pak%d.pak\0", i); + if (!access(filedir, 0x04)) + { + Log_Write("searching %s in %s", filename, filedir); + if (FindFileInPak(filedir, filename, file)) return true; + } //end if + } //end for + } //end for + file->offset = 0; + file->length = 0; + return false; +} //end of the function FindQuakeFile2 +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef BOTLIB +qboolean FindQuakeFile(char *filename, foundfile_t *file) +{ + return FindQuakeFile2(LibVarGetString("basedir"), + LibVarGetString("gamedir"), filename, file); +} //end of the function FindQuakeFile +#else //BOTLIB +qboolean FindQuakeFile(char *basedir, char *gamedir, char *filename, foundfile_t *file) +{ + return FindQuakeFile2(basedir, gamedir, filename, file); +} //end of the function FindQuakeFile +#endif //BOTLIB + +#endif diff --git a/code/bspc/l_utils.h b/code/bspc/l_utils.h index 2ef9161..ebd8dbb 100755 --- a/code/bspc/l_utils.h +++ b/code/bspc/l_utils.h @@ -1,79 +1,79 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#ifndef MAX_PATH - #define MAX_PATH 64 -#endif - -#ifndef PATH_SEPERATORSTR - #if defined(WIN32)|defined(_WIN32)|defined(__NT__)|defined(__WINDOWS__)|defined(__WINDOWS_386__) - #define PATHSEPERATOR_STR "\\" - #else - #define PATHSEPERATOR_STR "/" - #endif -#endif -#ifndef PATH_SEPERATORCHAR - #if defined(WIN32)|defined(_WIN32)|defined(__NT__)|defined(__WINDOWS__)|defined(__WINDOWS_386__) - #define PATHSEPERATOR_CHAR '\\' - #else - #define PATHSEPERATOR_CHAR '/' - #endif -#endif - -//random in the range [0, 1] -#define random() ((rand () & 0x7fff) / ((float)0x7fff)) -//random in the range [-1, 1] -#define crandom() (2.0 * (random() - 0.5)) -//min and max -#define Maximum(x,y) (x > y ? x : y) -#define Minimum(x,y) (x < y ? x : y) -//absolute value -#define FloatAbs(x) (*(float *) &((* (int *) &(x)) & 0x7FFFFFFF)) -#define IntAbs(x) (~(x)) -//coordinates -#define _X 0 -#define _Y 1 -#define _Z 2 - -typedef struct foundfile_s -{ - int offset; - int length; - char filename[MAX_PATH]; //screw LCC, array must be at end of struct -} foundfile_t; - -void Vector2Angles(vec3_t value1, vec3_t angles); -//set the correct path seperators -void ConvertPath(char *path); -//append a path seperator to the given path not exceeding the length -void AppendPathSeperator(char *path, int length); -//find a file in a pak file -qboolean FindFileInPak(char *pakfile, char *filename, foundfile_t *file); -//find a quake file -#ifdef BOTLIB -qboolean FindQuakeFile(char *filename, foundfile_t *file); -#else //BOTLIB -qboolean FindQuakeFile(char *basedir, char *gamedir, char *filename, foundfile_t *file); -#endif //BOTLIB - - - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#ifndef MAX_PATH + #define MAX_PATH 64 +#endif + +#ifndef PATH_SEPERATORSTR + #if defined(WIN32)|defined(_WIN32)|defined(__NT__)|defined(__WINDOWS__)|defined(__WINDOWS_386__) + #define PATHSEPERATOR_STR "\\" + #else + #define PATHSEPERATOR_STR "/" + #endif +#endif +#ifndef PATH_SEPERATORCHAR + #if defined(WIN32)|defined(_WIN32)|defined(__NT__)|defined(__WINDOWS__)|defined(__WINDOWS_386__) + #define PATHSEPERATOR_CHAR '\\' + #else + #define PATHSEPERATOR_CHAR '/' + #endif +#endif + +//random in the range [0, 1] +#define random() ((rand () & 0x7fff) / ((float)0x7fff)) +//random in the range [-1, 1] +#define crandom() (2.0 * (random() - 0.5)) +//min and max +#define Maximum(x,y) (x > y ? x : y) +#define Minimum(x,y) (x < y ? x : y) +//absolute value +#define FloatAbs(x) (*(float *) &((* (int *) &(x)) & 0x7FFFFFFF)) +#define IntAbs(x) (~(x)) +//coordinates +#define _X 0 +#define _Y 1 +#define _Z 2 + +typedef struct foundfile_s +{ + int offset; + int length; + char filename[MAX_PATH]; //screw LCC, array must be at end of struct +} foundfile_t; + +void Vector2Angles(vec3_t value1, vec3_t angles); +//set the correct path seperators +void ConvertPath(char *path); +//append a path seperator to the given path not exceeding the length +void AppendPathSeperator(char *path, int length); +//find a file in a pak file +qboolean FindFileInPak(char *pakfile, char *filename, foundfile_t *file); +//find a quake file +#ifdef BOTLIB +qboolean FindQuakeFile(char *filename, foundfile_t *file); +#else //BOTLIB +qboolean FindQuakeFile(char *basedir, char *gamedir, char *filename, foundfile_t *file); +#endif //BOTLIB + + + diff --git a/code/bspc/lcc.mak b/code/bspc/lcc.mak index d74adce..1b99864 100755 --- a/code/bspc/lcc.mak +++ b/code/bspc/lcc.mak @@ -1,61 +1,61 @@ -# -# Makefile for the BSPC tool for the Gladiator Bot -# Intended for LCC-Win32 -# - -CC=lcc -CFLAGS=-DC_ONLY -o -OBJS= _files.obj\ - aas_areamerging.obj\ - aas_cfg.obj\ - aas_create.obj\ - aas_edgemelting.obj\ - aas_facemerging.obj\ - aas_file.obj\ - aas_gsubdiv.obj\ - aas_map.obj\ - aas_prunenodes.obj\ - aas_store.obj\ - brushbsp.obj\ - bspc.obj\ - csg.obj\ - faces.obj\ - glfile.obj\ - l_bsp_hl.obj\ - l_bsp_q1.obj\ - l_bsp_q2.obj\ - l_bsp_sin.obj\ - l_cmd.obj\ - l_log.obj\ - l_math.obj\ - l_mem.obj\ - l_poly.obj\ - l_qfiles.obj\ - l_script.obj\ - l_threads.obj\ - l_utils.obj\ - leakfile.obj\ - map.obj\ - map_hl.obj\ - map_q1.obj\ - map_q2.obj\ - map_q2_new.obj\ - map_sin.obj\ - nodraw.obj\ - portals.obj\ - prtfile.obj\ - textures.obj\ - tree.obj\ - writebsp.obj - -all: bspc.exe - -bspc.exe: $(OBJS) - lcclnk - -clean: - del *.obj bspc.exe - -%.obj: %.c - $(CC) $(CFLAGS) $< - +# +# Makefile for the BSPC tool for the Gladiator Bot +# Intended for LCC-Win32 +# + +CC=lcc +CFLAGS=-DC_ONLY -o +OBJS= _files.obj\ + aas_areamerging.obj\ + aas_cfg.obj\ + aas_create.obj\ + aas_edgemelting.obj\ + aas_facemerging.obj\ + aas_file.obj\ + aas_gsubdiv.obj\ + aas_map.obj\ + aas_prunenodes.obj\ + aas_store.obj\ + brushbsp.obj\ + bspc.obj\ + csg.obj\ + faces.obj\ + glfile.obj\ + l_bsp_hl.obj\ + l_bsp_q1.obj\ + l_bsp_q2.obj\ + l_bsp_sin.obj\ + l_cmd.obj\ + l_log.obj\ + l_math.obj\ + l_mem.obj\ + l_poly.obj\ + l_qfiles.obj\ + l_script.obj\ + l_threads.obj\ + l_utils.obj\ + leakfile.obj\ + map.obj\ + map_hl.obj\ + map_q1.obj\ + map_q2.obj\ + map_q2_new.obj\ + map_sin.obj\ + nodraw.obj\ + portals.obj\ + prtfile.obj\ + textures.obj\ + tree.obj\ + writebsp.obj + +all: bspc.exe + +bspc.exe: $(OBJS) + lcclnk + +clean: + del *.obj bspc.exe + +%.obj: %.c + $(CC) $(CFLAGS) $< + diff --git a/code/bspc/leakfile.c b/code/bspc/leakfile.c index 7118759..924b34d 100755 --- a/code/bspc/leakfile.c +++ b/code/bspc/leakfile.c @@ -1,101 +1,101 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "qbsp.h" - -/* -============================================================================== - -LEAF FILE GENERATION - -Save out name.line for qe3 to read -============================================================================== -*/ - - -/* -============= -LeakFile - -Finds the shortest possible chain of portals -that leads from the outside leaf to a specifically -occupied leaf -============= -*/ -void LeakFile (tree_t *tree) -{ - vec3_t mid; - FILE *linefile; - char filename[1024]; - node_t *node; - int count; - - if (!tree->outside_node.occupied) - return; - - qprintf ("--- LeakFile ---\n"); - - // - // write the points to the file - // - sprintf (filename, "%s.lin", source); - qprintf ("%s\n", filename); - linefile = fopen (filename, "w"); - if (!linefile) - Error ("Couldn't open %s\n", filename); - - count = 0; - node = &tree->outside_node; - while (node->occupied > 1) - { - int next; - portal_t *p, *nextportal; - node_t *nextnode; - int s; - - // find the best portal exit - next = node->occupied; - for (p=node->portals ; p ; p = p->next[!s]) - { - s = (p->nodes[0] == node); - if (p->nodes[s]->occupied - && p->nodes[s]->occupied < next) - { - nextportal = p; - nextnode = p->nodes[s]; - next = nextnode->occupied; - } - } - node = nextnode; - WindingCenter (nextportal->winding, mid); - fprintf (linefile, "%f %f %f\n", mid[0], mid[1], mid[2]); - count++; - } - // add the occupant center - GetVectorForKey (node->occupant, "origin", mid); - - fprintf (linefile, "%f %f %f\n", mid[0], mid[1], mid[2]); - qprintf ("%5i point linefile\n", count+1); - - fclose (linefile); -} - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "qbsp.h" + +/* +============================================================================== + +LEAF FILE GENERATION + +Save out name.line for qe3 to read +============================================================================== +*/ + + +/* +============= +LeakFile + +Finds the shortest possible chain of portals +that leads from the outside leaf to a specifically +occupied leaf +============= +*/ +void LeakFile (tree_t *tree) +{ + vec3_t mid; + FILE *linefile; + char filename[1024]; + node_t *node; + int count; + + if (!tree->outside_node.occupied) + return; + + qprintf ("--- LeakFile ---\n"); + + // + // write the points to the file + // + sprintf (filename, "%s.lin", source); + qprintf ("%s\n", filename); + linefile = fopen (filename, "w"); + if (!linefile) + Error ("Couldn't open %s\n", filename); + + count = 0; + node = &tree->outside_node; + while (node->occupied > 1) + { + int next; + portal_t *p, *nextportal; + node_t *nextnode; + int s; + + // find the best portal exit + next = node->occupied; + for (p=node->portals ; p ; p = p->next[!s]) + { + s = (p->nodes[0] == node); + if (p->nodes[s]->occupied + && p->nodes[s]->occupied < next) + { + nextportal = p; + nextnode = p->nodes[s]; + next = nextnode->occupied; + } + } + node = nextnode; + WindingCenter (nextportal->winding, mid); + fprintf (linefile, "%f %f %f\n", mid[0], mid[1], mid[2]); + count++; + } + // add the occupant center + GetVectorForKey (node->occupant, "origin", mid); + + fprintf (linefile, "%f %f %f\n", mid[0], mid[1], mid[2]); + qprintf ("%5i point linefile\n", count+1); + + fclose (linefile); +} + diff --git a/code/bspc/linux-i386.mak b/code/bspc/linux-i386.mak index 00a261d..dd9566d 100755 --- a/code/bspc/linux-i386.mak +++ b/code/bspc/linux-i386.mak @@ -1,109 +1,109 @@ -# -# Makefile for the BSPC tool for the Gladiator Bot -# Intended for gcc/Linux -# - -ARCH=i386 -CC=gcc -BASE_CFLAGS=-Dstricmp=strcasecmp - -#use these cflags to optimize it -CFLAGS=$(BASE_CFLAGS) -m486 -O6 -ffast-math -funroll-loops \ - -fomit-frame-pointer -fexpensive-optimizations -malign-loops=2 \ - -malign-jumps=2 -malign-functions=2 -DLINUX -DBSPC -#use these when debugging -#CFLAGS=$(BASE_CFLAGS) -g - -LDFLAGS=-ldl -lm -lpthread - -DO_CC=$(CC) $(CFLAGS) -o $@ -c $< - -############################################################################# -# SETUP AND BUILD BSPC -############################################################################# - -.c.o: - $(DO_CC) - -GAME_OBJS = \ - _files.o\ - aas_areamerging.o\ - aas_cfg.o\ - aas_create.o\ - aas_edgemelting.o\ - aas_facemerging.o\ - aas_file.o\ - aas_gsubdiv.o\ - aas_map.o\ - aas_prunenodes.o\ - aas_store.o\ - be_aas_bspc.o\ - ../botlib/be_aas_bspq3.o\ - ../botlib/be_aas_cluster.o\ - ../botlib/be_aas_move.o\ - ../botlib/be_aas_optimize.o\ - ../botlib/be_aas_reach.o\ - ../botlib/be_aas_sample.o\ - brushbsp.o\ - bspc.o\ - ../qcommon/cm_load.o\ - ../qcommon/cm_patch.o\ - ../qcommon/cm_test.o\ - ../qcommon/cm_trace.o\ - csg.o\ - glfile.o\ - l_bsp_ent.o\ - l_bsp_hl.o\ - l_bsp_q1.o\ - l_bsp_q2.o\ - l_bsp_q3.o\ - l_bsp_sin.o\ - l_cmd.o\ - ../botlib/l_libvar.o\ - l_log.o\ - l_math.o\ - l_mem.o\ - l_poly.o\ - ../botlib/l_precomp.o\ - l_qfiles.o\ - ../botlib/l_script.o\ - ../botlib/l_struct.o\ - l_threads.o\ - l_utils.o\ - leakfile.o\ - map.o\ - map_hl.o\ - map_q1.o\ - map_q2.o\ - map_q3.o\ - map_sin.o\ - ../qcommon/md4.o\ - nodraw.o\ - portals.o\ - tetrahedron.o\ - textures.o\ - tree.o\ - ../qcommon/unzip.o - -bspc$(ARCH) : $(GAME_OBJS) - $(CC) $(CFLAGS) -o $@ $(GAME_OBJS) $(LDFLAGS) - - -############################################################################# -# MISC -############################################################################# - -clean: - -rm -f $(GAME_OBJS) - -depend: - gcc -MM $(GAME_OBJS:.o=.c) - - -install: - cp bspci386 .. - -# -# From "make depend" -# - +# +# Makefile for the BSPC tool for the Gladiator Bot +# Intended for gcc/Linux +# + +ARCH=i386 +CC=gcc +BASE_CFLAGS=-Dstricmp=strcasecmp + +#use these cflags to optimize it +CFLAGS=$(BASE_CFLAGS) -m486 -O6 -ffast-math -funroll-loops \ + -fomit-frame-pointer -fexpensive-optimizations -malign-loops=2 \ + -malign-jumps=2 -malign-functions=2 -DLINUX -DBSPC +#use these when debugging +#CFLAGS=$(BASE_CFLAGS) -g + +LDFLAGS=-ldl -lm -lpthread + +DO_CC=$(CC) $(CFLAGS) -o $@ -c $< + +############################################################################# +# SETUP AND BUILD BSPC +############################################################################# + +.c.o: + $(DO_CC) + +GAME_OBJS = \ + _files.o\ + aas_areamerging.o\ + aas_cfg.o\ + aas_create.o\ + aas_edgemelting.o\ + aas_facemerging.o\ + aas_file.o\ + aas_gsubdiv.o\ + aas_map.o\ + aas_prunenodes.o\ + aas_store.o\ + be_aas_bspc.o\ + ../botlib/be_aas_bspq3.o\ + ../botlib/be_aas_cluster.o\ + ../botlib/be_aas_move.o\ + ../botlib/be_aas_optimize.o\ + ../botlib/be_aas_reach.o\ + ../botlib/be_aas_sample.o\ + brushbsp.o\ + bspc.o\ + ../qcommon/cm_load.o\ + ../qcommon/cm_patch.o\ + ../qcommon/cm_test.o\ + ../qcommon/cm_trace.o\ + csg.o\ + glfile.o\ + l_bsp_ent.o\ + l_bsp_hl.o\ + l_bsp_q1.o\ + l_bsp_q2.o\ + l_bsp_q3.o\ + l_bsp_sin.o\ + l_cmd.o\ + ../botlib/l_libvar.o\ + l_log.o\ + l_math.o\ + l_mem.o\ + l_poly.o\ + ../botlib/l_precomp.o\ + l_qfiles.o\ + ../botlib/l_script.o\ + ../botlib/l_struct.o\ + l_threads.o\ + l_utils.o\ + leakfile.o\ + map.o\ + map_hl.o\ + map_q1.o\ + map_q2.o\ + map_q3.o\ + map_sin.o\ + ../qcommon/md4.o\ + nodraw.o\ + portals.o\ + tetrahedron.o\ + textures.o\ + tree.o\ + ../qcommon/unzip.o + +bspc$(ARCH) : $(GAME_OBJS) + $(CC) $(CFLAGS) -o $@ $(GAME_OBJS) $(LDFLAGS) + + +############################################################################# +# MISC +############################################################################# + +clean: + -rm -f $(GAME_OBJS) + +depend: + gcc -MM $(GAME_OBJS:.o=.c) + + +install: + cp bspci386 .. + +# +# From "make depend" +# + diff --git a/code/bspc/map.c b/code/bspc/map.c index 613c234..e156d3a 100755 --- a/code/bspc/map.c +++ b/code/bspc/map.c @@ -1,1267 +1,1267 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "qbsp.h" -#include "l_bsp_hl.h" -#include "l_bsp_q1.h" -#include "l_bsp_q2.h" -#include "l_bsp_q3.h" -#include "l_bsp_sin.h" -#include "l_mem.h" -#include "../botlib/aasfile.h" //aas_bbox_t -#include "aas_store.h" //AAS_MAX_BBOXES -#include "aas_cfg.h" - -#define Sign(x) (x < 0 ? 1 : 0) - -int nummapbrushes; -mapbrush_t mapbrushes[MAX_MAPFILE_BRUSHES]; - -int nummapbrushsides; -side_t brushsides[MAX_MAPFILE_BRUSHSIDES]; -brush_texture_t side_brushtextures[MAX_MAPFILE_BRUSHSIDES]; - -int nummapplanes; -plane_t mapplanes[MAX_MAPFILE_PLANES]; -int mapplaneusers[MAX_MAPFILE_PLANES]; - -#define PLANE_HASHES 1024 -plane_t *planehash[PLANE_HASHES]; -vec3_t map_mins, map_maxs; - -#ifdef SIN -textureref_t side_newrefs[MAX_MAPFILE_BRUSHSIDES]; -#endif - -map_texinfo_t map_texinfo[MAX_MAPFILE_TEXINFO]; -int map_numtexinfo; -int loadedmaptype; //loaded map type - -// undefine to make plane finding use linear sort -#define USE_HASHING - -int c_boxbevels; -int c_edgebevels; -int c_areaportals; -int c_clipbrushes; -int c_squattbrushes; -int c_writtenbrushes; - -/* -============================================================================= - -PLANE FINDING - -============================================================================= -*/ - - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int PlaneSignBits(vec3_t normal) -{ - int i, signbits; - - signbits = 0; - for (i = 2; i >= 0; i--) - { - signbits = (signbits << 1) + Sign(normal[i]); - } //end for - return signbits; -} //end of the function PlaneSignBits -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int PlaneTypeForNormal(vec3_t normal) -{ - vec_t ax, ay, az; - -// NOTE: should these have an epsilon around 1.0? - if (normal[0] == 1.0 || normal[0] == -1.0) - return PLANE_X; - if (normal[1] == 1.0 || normal[1] == -1.0) - return PLANE_Y; - if (normal[2] == 1.0 || normal[2] == -1.0) - return PLANE_Z; - - ax = fabs(normal[0]); - ay = fabs(normal[1]); - az = fabs(normal[2]); - - if (ax >= ay && ax >= az) - return PLANE_ANYX; - if (ay >= ax && ay >= az) - return PLANE_ANYY; - return PLANE_ANYZ; -} //end of the function PlaneTypeForNormal -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -//ME NOTE: changed from 0.00001 -#define NORMAL_EPSILON 0.0001 -//ME NOTE: changed from 0.01 -#define DIST_EPSILON 0.02 -qboolean PlaneEqual(plane_t *p, vec3_t normal, vec_t dist) -{ -#if 1 - if ( - fabs(p->normal[0] - normal[0]) < NORMAL_EPSILON - && fabs(p->normal[1] - normal[1]) < NORMAL_EPSILON - && fabs(p->normal[2] - normal[2]) < NORMAL_EPSILON - && fabs(p->dist - dist) < DIST_EPSILON ) - return true; -#else - if (p->normal[0] == normal[0] - && p->normal[1] == normal[1] - && p->normal[2] == normal[2] - && p->dist == dist) - return true; -#endif - return false; -} //end of the function PlaneEqual -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AddPlaneToHash(plane_t *p) -{ - int hash; - - hash = (int)fabs(p->dist) / 8; - hash &= (PLANE_HASHES-1); - - p->hash_chain = planehash[hash]; - planehash[hash] = p; -} //end of the function AddPlaneToHash -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int CreateNewFloatPlane (vec3_t normal, vec_t dist) -{ - plane_t *p, temp; - - if (VectorLength(normal) < 0.5) - Error ("FloatPlane: bad normal"); - // create a new plane - if (nummapplanes+2 > MAX_MAPFILE_PLANES) - Error ("MAX_MAPFILE_PLANES"); - - p = &mapplanes[nummapplanes]; - VectorCopy (normal, p->normal); - p->dist = dist; - p->type = (p+1)->type = PlaneTypeForNormal (p->normal); - p->signbits = PlaneSignBits(p->normal); - - VectorSubtract (vec3_origin, normal, (p+1)->normal); - (p+1)->dist = -dist; - (p+1)->signbits = PlaneSignBits((p+1)->normal); - - nummapplanes += 2; - - // allways put axial planes facing positive first - if (p->type < 3) - { - if (p->normal[0] < 0 || p->normal[1] < 0 || p->normal[2] < 0) - { - // flip order - temp = *p; - *p = *(p+1); - *(p+1) = temp; - - AddPlaneToHash (p); - AddPlaneToHash (p+1); - return nummapplanes - 1; - } - } - - AddPlaneToHash (p); - AddPlaneToHash (p+1); - return nummapplanes - 2; -} //end of the function CreateNewFloatPlane -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void SnapVector(vec3_t normal) -{ - int i; - - for (i=0 ; i<3 ; i++) - { - if ( fabs(normal[i] - 1) < NORMAL_EPSILON ) - { - VectorClear (normal); - normal[i] = 1; - break; - } - if ( fabs(normal[i] - -1) < NORMAL_EPSILON ) - { - VectorClear (normal); - normal[i] = -1; - break; - } - } -} //end of the function SnapVector -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void SnapPlane(vec3_t normal, vec_t *dist) -{ - SnapVector(normal); - - if (fabs(*dist-Q_rint(*dist)) < DIST_EPSILON) - *dist = Q_rint(*dist); -} //end of the function SnapPlane -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -#ifndef USE_HASHING -int FindFloatPlane(vec3_t normal, vec_t dist) -{ - int i; - plane_t *p; - - SnapPlane(normal, &dist); - for (i = 0, p = mapplanes; i < nummapplanes; i++, p++) - { - if (PlaneEqual (p, normal, dist)) - { - mapplaneusers[i]++; - return i; - } //end if - } //end for - i = CreateNewFloatPlane (normal, dist); - mapplaneusers[i]++; - return i; -} //end of the function FindFloatPlane -#else -int FindFloatPlane (vec3_t normal, vec_t dist) -{ - int i; - plane_t *p; - int hash, h; - - SnapPlane (normal, &dist); - hash = (int)fabs(dist) / 8; - hash &= (PLANE_HASHES-1); - - // search the border bins as well - for (i = -1; i <= 1; i++) - { - h = (hash+i)&(PLANE_HASHES-1); - for (p = planehash[h]; p; p = p->hash_chain) - { - if (PlaneEqual(p, normal, dist)) - { - mapplaneusers[p-mapplanes]++; - return p - mapplanes; - } //end if - } //end for - } //end for - i = CreateNewFloatPlane (normal, dist); - mapplaneusers[i]++; - return i; -} //end of the function FindFloatPlane -#endif -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int PlaneFromPoints (int *p0, int *p1, int *p2) -{ - vec3_t t1, t2, normal; - vec_t dist; - - VectorSubtract (p0, p1, t1); - VectorSubtract (p2, p1, t2); - CrossProduct (t1, t2, normal); - VectorNormalize (normal); - - dist = DotProduct (p0, normal); - - return FindFloatPlane (normal, dist); -} //end of the function PlaneFromPoints -//=========================================================================== -// Adds any additional planes necessary to allow the brush to be expanded -// against axial bounding boxes -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AddBrushBevels (mapbrush_t *b) -{ - int axis, dir; - int i, j, k, l, order; - side_t sidetemp; - brush_texture_t tdtemp; -#ifdef SIN - textureref_t trtemp; -#endif - side_t *s, *s2; - vec3_t normal; - float dist; - winding_t *w, *w2; - vec3_t vec, vec2; - float d; - - // - // add the axial planes - // - order = 0; - for (axis=0 ; axis <3 ; axis++) - { - for (dir=-1 ; dir <= 1 ; dir+=2, order++) - { - // see if the plane is allready present - for (i=0, s=b->original_sides ; inumsides ; i++,s++) - { - if (mapplanes[s->planenum].normal[axis] == dir) - break; - } - - if (i == b->numsides) - { // add a new side - if (nummapbrushsides == MAX_MAP_BRUSHSIDES) - Error ("MAX_MAP_BRUSHSIDES"); - nummapbrushsides++; - b->numsides++; - VectorClear (normal); - normal[axis] = dir; - if (dir == 1) - dist = b->maxs[axis]; - else - dist = -b->mins[axis]; - s->planenum = FindFloatPlane (normal, dist); - s->texinfo = b->original_sides[0].texinfo; -#ifdef SIN - s->lightinfo = b->original_sides[0].lightinfo; -#endif - s->contents = b->original_sides[0].contents; - s->flags |= SFL_BEVEL; - c_boxbevels++; - } - - // if the plane is not in it canonical order, swap it - if (i != order) - { - sidetemp = b->original_sides[order]; - b->original_sides[order] = b->original_sides[i]; - b->original_sides[i] = sidetemp; - - j = b->original_sides - brushsides; - tdtemp = side_brushtextures[j+order]; - side_brushtextures[j+order] = side_brushtextures[j+i]; - side_brushtextures[j+i] = tdtemp; - -#ifdef SIN - trtemp = side_newrefs[j+order]; - side_newrefs[j+order] = side_newrefs[j+i]; - side_newrefs[j+i] = trtemp; -#endif - } - } - } - - // - // add the edge bevels - // - if (b->numsides == 6) - return; // pure axial - - // test the non-axial plane edges - for (i=6 ; inumsides ; i++) - { - s = b->original_sides + i; - w = s->winding; - if (!w) - continue; - for (j=0 ; jnumpoints ; j++) - { - k = (j+1)%w->numpoints; - VectorSubtract (w->p[j], w->p[k], vec); - if (VectorNormalize (vec) < 0.5) - continue; - 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, normal); - if (VectorNormalize (normal) < 0.5) - continue; - dist = DotProduct (w->p[j], normal); - - // if all the points on all the sides are - // behind this plane, it is a proper edge bevel - for (k=0 ; knumsides ; k++) - { - // if this plane has allready been used, skip it - if (PlaneEqual (&mapplanes[b->original_sides[k].planenum] - , normal, dist) ) - break; - - w2 = b->original_sides[k].winding; - if (!w2) - continue; - for (l=0 ; lnumpoints ; l++) - { - d = DotProduct (w2->p[l], normal) - dist; - if (d > 0.1) - break; // point in front - } - if (l != w2->numpoints) - break; - } - - if (k != b->numsides) - continue; // wasn't part of the outer hull - // add this plane - if (nummapbrushsides == MAX_MAP_BRUSHSIDES) - Error ("MAX_MAP_BRUSHSIDES"); - nummapbrushsides++; - s2 = &b->original_sides[b->numsides]; - s2->planenum = FindFloatPlane (normal, dist); - s2->texinfo = b->original_sides[0].texinfo; -#ifdef SIN - s2->lightinfo = b->original_sides[0].lightinfo; -#endif - s2->contents = b->original_sides[0].contents; - s2->flags |= SFL_BEVEL; - c_edgebevels++; - b->numsides++; - } - } - } - } -} //end of the function AddBrushBevels -//=========================================================================== -// creates windigs for sides and mins / maxs for the brush -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean MakeBrushWindings(mapbrush_t *ob) -{ - int i, j; - winding_t *w; - side_t *side; - plane_t *plane; - - ClearBounds (ob->mins, ob->maxs); - - for (i = 0; i < ob->numsides; i++) - { - plane = &mapplanes[ob->original_sides[i].planenum]; - w = BaseWindingForPlane(plane->normal, plane->dist); - for (j = 0; j numsides && w; j++) - { - if (i == j) continue; - if (ob->original_sides[j].flags & SFL_BEVEL) continue; - plane = &mapplanes[ob->original_sides[j].planenum^1]; - ChopWindingInPlace(&w, plane->normal, plane->dist, 0); //CLIP_EPSILON); - } - - side = &ob->original_sides[i]; - side->winding = w; - if (w) - { - side->flags |= SFL_VISIBLE; - for (j = 0; j < w->numpoints; j++) - AddPointToBounds (w->p[j], ob->mins, ob->maxs); - } - } - - for (i = 0; i < 3; i++) - { - //IDBUG: all the indexes into the mins and maxs were zero (not using i) - if (ob->mins[i] < -MAX_MAP_BOUNDS || ob->maxs[i] > MAX_MAP_BOUNDS) - { - Log_Print("entity %i, brush %i: bounds out of range\n", ob->entitynum, ob->brushnum); - ob->numsides = 0; //remove the brush - break; - } //end if - if (ob->mins[i] > MAX_MAP_BOUNDS || ob->maxs[i] < -MAX_MAP_BOUNDS) - { - Log_Print("entity %i, brush %i: no visible sides on brush\n", ob->entitynum, ob->brushnum); - ob->numsides = 0; //remove the brush - break; - } //end if - } //end for - return true; -} //end of the function MakeBrushWindings -//=========================================================================== -// FIXME: currently doesn't mark all bevels -// NOTE: when one brush bevel is found the remaining sides of the brush -// are bevels as well (when the brush isn't expanded for AAS :)) -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void MarkBrushBevels(mapbrush_t *brush) -{ - int i; - int we; - side_t *s; - - //check all the sides of the brush - for (i = 0; i < brush->numsides; i++) - { - s = brush->original_sides + i; - //if the side has no winding - if (!s->winding) - { - Log_Write("MarkBrushBevels: brush %d no winding", brush->brushnum); - s->flags |= SFL_BEVEL; - } //end if - //if the winding is tiny - else if (WindingIsTiny(s->winding)) - { - s->flags |= SFL_BEVEL; - Log_Write("MarkBrushBevels: brush %d tiny winding", brush->brushnum); - } //end else if - //if the winding has errors - else - { - we = WindingError(s->winding); - if (we == WE_NOTENOUGHPOINTS - || we == WE_SMALLAREA - || we == WE_POINTBOGUSRANGE -// || we == WE_NONCONVEX - ) - { - Log_Write("MarkBrushBevels: brush %d %s", brush->brushnum, WindingErrorString()); - s->flags |= SFL_BEVEL; - } //end else if - } //end else - if (s->flags & SFL_BEVEL) - { - s->flags &= ~SFL_VISIBLE; - //if the side has a valid plane - if (s->planenum > 0 && s->planenum < nummapplanes) - { - //if it is an axial plane - if (mapplanes[s->planenum].type < 3) c_boxbevels++; - else c_edgebevels++; - } //end if - } //end if - } //end for -} //end of the function MarkBrushBevels -//=========================================================================== -// returns true if the map brush already exists -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int BrushExists(mapbrush_t *brush) -{ - int i, s1, s2; - side_t *side1, *side2; - mapbrush_t *brush1, *brush2; - - for (i = 0; i < nummapbrushes; i++) - { - brush1 = brush; - brush2 = &mapbrushes[i]; - //compare the brushes - if (brush1->entitynum != brush2->entitynum) continue; - //if (brush1->contents != brush2->contents) continue; - if (brush1->numsides != brush2->numsides) continue; - for (s1 = 0; s1 < brush1->numsides; s1++) - { - side1 = brush1->original_sides + s1; - // - for (s2 = 0; s2 < brush2->numsides; s2++) - { - side2 = brush2->original_sides + s2; - // - if ((side1->planenum & ~1) == (side2->planenum & ~1) -// && side1->texinfo == side2->texinfo -// && side1->contents == side2->contents -// && side1->surf == side2->surf - ) break; - } //end if - if (s2 >= brush2->numsides) break; - } //end for - if (s1 >= brush1->numsides) return true; - } //end for - return false; -} //end of the function BrushExists -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean WriteMapBrush(FILE *fp, mapbrush_t *brush, vec3_t origin) -{ - int sn, rotate, shift[2], sv, tv, planenum, p1, i, j; - float scale[2], originshift[2], ang1, ang2, newdist; - vec3_t vecs[2], axis[2]; - map_texinfo_t *ti; - winding_t *w; - side_t *s; - plane_t *plane; - - if (noliquids) - { - if (brush->contents & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA)) - { - return true; - } //end if - } //end if - //if the brush has no contents - if (!brush->contents) return true; - //print the leading { - if (fprintf(fp, " { //brush %d\n", brush->brushnum) < 0) return false; - //write brush sides - for (sn = 0; sn < brush->numsides; sn++) - { - s = brush->original_sides + sn; - //don't write out bevels - if (!(s->flags & SFL_BEVEL)) - { - //if the entity has an origin set - if (origin[0] || origin[1] || origin[2]) - { - newdist = mapplanes[s->planenum].dist + - DotProduct(mapplanes[s->planenum].normal, origin); - planenum = FindFloatPlane(mapplanes[s->planenum].normal, newdist); - } //end if - else - { - planenum = s->planenum; - } //end else - //always take the first plane, then flip the points if necesary - plane = &mapplanes[planenum & ~1]; - w = BaseWindingForPlane(plane->normal, plane->dist); - // - for (i = 0; i < 3; i++) - { - for (j = 0; j < 3; j++) - { - if (fabs(w->p[i][j]) < 0.2) w->p[i][j] = 0; - else if (fabs((int)w->p[i][j] - w->p[i][j]) < 0.3) w->p[i][j] = (int) w->p[i][j]; - //w->p[i][j] = (int) (w->p[i][j] + 0.2); - } //end for - } //end for - //three non-colinear points to define the plane - if (planenum & 1) p1 = 1; - else p1 = 0; - if (fprintf(fp," ( %5i %5i %5i ) ", (int)w->p[p1][0], (int)w->p[p1][1], (int)w->p[p1][2]) < 0) return false; - if (fprintf(fp,"( %5i %5i %5i ) ", (int)w->p[!p1][0], (int)w->p[!p1][1], (int)w->p[!p1][2]) < 0) return false; - if (fprintf(fp,"( %5i %5i %5i ) ", (int)w->p[2][0], (int)w->p[2][1], (int)w->p[2][2]) < 0) return false; - //free the winding - FreeWinding(w); - // - if (s->texinfo == TEXINFO_NODE) - { - if (brush->contents & CONTENTS_PLAYERCLIP) - { - //player clip - if (loadedmaptype == MAPTYPE_SIN) - { - if (fprintf(fp, "generic/misc/clip 0 0 0 1 1") < 0) return false; - } //end if - else if (loadedmaptype == MAPTYPE_QUAKE2) - { //FIXME: don't always use e1u1 - if (fprintf(fp, "e1u1/clip 0 0 0 1 1") < 0) return false; - } //end else - else if (loadedmaptype == MAPTYPE_QUAKE3) - { - if (fprintf(fp, "e1u1/clip 0 0 0 1 1") < 0) return false; - } //end else if - else - { - if (fprintf(fp, "clip 0 0 0 1 1") < 0) return false; - } //end else - } //end if - else if (brush->contents == CONTENTS_MONSTERCLIP) - { - //monster clip - if (loadedmaptype == MAPTYPE_SIN) - { - if (fprintf(fp, "generic/misc/monster 0 0 0 1 1") < 0) return false; - } //end if - else if (loadedmaptype == MAPTYPE_QUAKE2) - { - if (fprintf(fp, "e1u1/clip_mon 0 0 0 1 1") < 0) return false; - } //end else - else - { - if (fprintf(fp, "clip 0 0 0 1 1") < 0) return false; - } //end else - } //end else - else - { - if (fprintf(fp, "clip 0 0 0 1 1") < 0) return false; - Log_Write("brush->contents = %d\n", brush->contents); - } //end else - } //end if - else if (loadedmaptype == MAPTYPE_SIN && s->texinfo == 0) - { - if (brush->contents & CONTENTS_DUMMYFENCE) - { - if (fprintf(fp, "generic/misc/fence 0 0 0 1 1") < 0) return false; - } //end if - else if (brush->contents & CONTENTS_MIST) - { - if (fprintf(fp, "generic/misc/volumetric_base 0 0 0 1 1") < 0) return false; - } //end if - else //unknown so far - { - if (fprintf(fp, "generic/misc/red 0 0 0 1 1") < 0) return false; - } //end else - } //end if - else if (loadedmaptype == MAPTYPE_QUAKE3) - { - //always use the same texture - if (fprintf(fp, "e2u3/floor1_2 0 0 0 1 1 1 0 0") < 0) return false; - } //end else if - else - { - //* - ti = &map_texinfo[s->texinfo]; - //the scaling of the texture - scale[0] = 1 / VectorNormalize2(ti->vecs[0], vecs[0]); - scale[1] = 1 / VectorNormalize2(ti->vecs[1], vecs[1]); - // - TextureAxisFromPlane(plane, axis[0], axis[1]); - //calculate texture shift done by entity origin - originshift[0] = DotProduct(origin, axis[0]); - originshift[1] = DotProduct(origin, axis[1]); - //the texture shift without origin shift - shift[0] = ti->vecs[0][3] - originshift[0]; - shift[1] = ti->vecs[1][3] - originshift[1]; - // - if (axis[0][0]) sv = 0; - else if (axis[0][1]) sv = 1; - else sv = 2; - if (axis[1][0]) tv = 0; - else if (axis[1][1]) tv = 1; - else tv = 2; - //calculate rotation of texture - if (vecs[0][tv] == 0) ang1 = vecs[0][sv] > 0 ? 90.0 : -90.0; - else ang1 = atan2(vecs[0][sv], vecs[0][tv]) * 180 / Q_PI; - if (ang1 < 0) ang1 += 360; - if (ang1 >= 360) ang1 -= 360; - if (axis[0][tv] == 0) ang2 = axis[0][sv] > 0 ? 90.0 : -90.0; - else ang2 = atan2(axis[0][sv], axis[0][tv]) * 180 / Q_PI; - if (ang2 < 0) ang2 += 360; - if (ang2 >= 360) ang2 -= 360; - rotate = ang2 - ang1; - if (rotate < 0) rotate += 360; - if (rotate >= 360) rotate -= 360; - //write the texture info - if (fprintf(fp, "%s %d %d %d", ti->texture, shift[0], shift[1], rotate) < 0) return false; - if (fabs(scale[0] - ((int) scale[0])) < 0.001) - { - if (fprintf(fp, " %d", (int) scale[0]) < 0) return false; - } //end if - else - { - if (fprintf(fp, " %4f", scale[0]) < 0) return false; - } //end if - if (fabs(scale[1] - ((int) scale[1])) < 0.001) - { - if (fprintf(fp, " %d", (int) scale[1]) < 0) return false; - } //end if - else - { - if (fprintf(fp, " %4f", scale[1]) < 0) return false; - } //end else - //write the extra brush side info - if (loadedmaptype == MAPTYPE_QUAKE2) - { - if (fprintf(fp, " %ld %ld %ld", s->contents, ti->flags, ti->value) < 0) return false; - } //end if - //*/ - } //end else - if (fprintf(fp, "\n") < 0) return false; - } //end if - } //end if - if (fprintf(fp, " }\n") < 0) return false; - c_writtenbrushes++; - return true; -} //end of the function WriteMapBrush -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean WriteOriginBrush(FILE *fp, vec3_t origin) -{ - vec3_t normal; - float dist; - int i, s; - winding_t *w; - - if (fprintf(fp, " {\n") < 0) return false; - // - for (i = 0; i < 3; i++) - { - for (s = -1; s <= 1; s += 2) - { - // - VectorClear(normal); - normal[i] = s; - dist = origin[i] * s + 16; - // - w = BaseWindingForPlane(normal, dist); - //three non-colinear points to define the plane - if (fprintf(fp," ( %5i %5i %5i ) ", (int)w->p[0][0], (int)w->p[0][1], (int)w->p[0][2]) < 0) return false; - if (fprintf(fp,"( %5i %5i %5i ) ", (int)w->p[1][0], (int)w->p[1][1], (int)w->p[1][2]) < 0) return false; - if (fprintf(fp,"( %5i %5i %5i ) ", (int)w->p[2][0], (int)w->p[2][1], (int)w->p[2][2]) < 0) return false; - //free the winding - FreeWinding(w); - //write origin texture: - // CONTENTS_ORIGIN = 16777216 - // SURF_NODRAW = 128 - if (loadedmaptype == MAPTYPE_SIN) - { - if (fprintf(fp, "generic/misc/origin 0 0 0 1 1") < 0) return false; - } //end if - else if (loadedmaptype == MAPTYPE_HALFLIFE) - { - if (fprintf(fp, "origin 0 0 0 1 1") < 0) return false; - } //end if - else - { - if (fprintf(fp, "e1u1/origin 0 0 0 1 1") < 0) return false; - } //end else - //Quake2 extra brush side info - if (loadedmaptype == MAPTYPE_QUAKE2) - { - //if (fprintf(fp, " 16777216 128 0") < 0) return false; - } //end if - if (fprintf(fp, "\n") < 0) return false; - } //end for - } //end for - if (fprintf(fp, " }\n") < 0) return false; - c_writtenbrushes++; - return true; -} //end of the function WriteOriginBrush -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -mapbrush_t *GetAreaPortalBrush(entity_t *mapent) -{ - int portalnum, bn; - mapbrush_t *brush; - - //the area portal number - portalnum = mapent->areaportalnum; - //find the area portal brush in the world brushes - for (bn = 0; bn < nummapbrushes && portalnum; bn++) - { - brush = &mapbrushes[bn]; - //must be in world entity - if (brush->entitynum == 0) - { - if (brush->contents & CONTENTS_AREAPORTAL) - { - portalnum--; - } //end if - } //end if - } //end for - if (bn < nummapbrushes) - { - return brush; - } //end if - else - { - Log_Print("area portal %d brush not found\n", mapent->areaportalnum); - return NULL; - } //end else -} //end of the function GetAreaPortalBrush -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean WriteMapFileSafe(FILE *fp) -{ - char key[1024], value[1024]; - int i, bn, entitybrushes; - epair_t *ep; - mapbrush_t *brush; - entity_t *mapent; - //vec3_t vec_origin = {0, 0, 0}; - - // - if (fprintf(fp,"//=====================================================\n" - "//\n" - "// map file created with BSPC "BSPC_VERSION"\n" - "//\n" - "// BSPC is designed to decompile material in which you own the copyright\n" - "// or have obtained permission to decompile from the copyright owner. Unless\n" - "// you own the copyright or have permission to decompile from the copyright\n" - "// owner, you may be violating copyright law and be subject to payment of\n" - "// damages and other remedies. If you are uncertain about your rights, contact\n" - "// your legal advisor.\n" - "//\n") < 0) return false; - if (loadedmaptype == MAPTYPE_SIN) - { - if (fprintf(fp, - "// generic/misc/red is used for unknown textures\n") < 0) return false; - } //end if - if (fprintf(fp,"//\n" - "//=====================================================\n") < 0) return false; - //write out all the entities - for (i = 0; i < num_entities; i++) - { - mapent = &entities[i]; - if (!mapent->epairs) - { - continue; - } //end if - if (fprintf(fp, "{\n") < 0) return false; - // - if (loadedmaptype == MAPTYPE_QUAKE3) - { - if (!stricmp(ValueForKey(mapent, "classname"), "light")) - { - SetKeyValue(mapent, "light", "10000"); - } //end if - } //end if - //write epairs - for (ep = mapent->epairs; ep; ep = ep->next) - { - strcpy(key, ep->key); - StripTrailing (key); - strcpy(value, ep->value); - StripTrailing(value); - // - if (loadedmaptype == MAPTYPE_QUAKE2 || - loadedmaptype == MAPTYPE_SIN) - { - //don't write an origin for BSP models - if (mapent->modelnum >= 0 && !strcmp(key, "origin")) continue; - } //end if - //don't write BSP model numbers - if (mapent->modelnum >= 0 && !strcmp(key, "model") && value[0] == '*') continue; - // - if (fprintf(fp, " \"%s\" \"%s\"\n", key, value) < 0) return false; - } //end for - // - if (ValueForKey(mapent, "origin")) GetVectorForKey(mapent, "origin", mapent->origin); - else mapent->origin[0] = mapent->origin[1] = mapent->origin[2] = 0; - //if this is an area portal entity - if (!strcmp("func_areaportal", ValueForKey(mapent, "classname"))) - { - brush = GetAreaPortalBrush(mapent); - if (!brush) return false; - if (!WriteMapBrush(fp, brush, mapent->origin)) return false; - } //end if - else - { - entitybrushes = false; - //write brushes - for (bn = 0; bn < nummapbrushes; bn++) - { - brush = &mapbrushes[bn]; - //if the brush is part of this entity - if (brush->entitynum == i) - { - //don't write out area portal brushes in the world - if (!((brush->contents & CONTENTS_AREAPORTAL) && brush->entitynum == 0)) - { - /* - if (!strcmp("func_door_rotating", ValueForKey(mapent, "classname"))) - { - AAS_PositionFuncRotatingBrush(mapent, brush); - if (!WriteMapBrush(fp, brush, vec_origin)) return false; - } //end if - else //*/ - { - if (!WriteMapBrush(fp, brush, mapent->origin)) return false; - } //end else - entitybrushes = true; - } //end if - } //end if - } //end for - //if the entity had brushes - if (entitybrushes) - { - //if the entity has an origin set - if (mapent->origin[0] || mapent->origin[1] || mapent->origin[2]) - { - if (!WriteOriginBrush(fp, mapent->origin)) return false; - } //end if - } //end if - } //end else - if (fprintf(fp, "}\n") < 0) return false; - } //end for - if (fprintf(fp, "//total of %d brushes\n", c_writtenbrushes) < 0) return false; - return true; -} //end of the function WriteMapFileSafe -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void WriteMapFile(char *filename) -{ - FILE *fp; - double start_time; - - c_writtenbrushes = 0; - //the time started - start_time = I_FloatTime(); - // - Log_Print("writing %s\n", filename); - fp = fopen(filename, "wb"); - if (!fp) - { - Log_Print("can't open %s\n", filename); - return; - } //end if - if (!WriteMapFileSafe(fp)) - { - fclose(fp); - Log_Print("error writing map file %s\n", filename); - return; - } //end if - fclose(fp); - //display creation time - Log_Print("written %d brushes\n", c_writtenbrushes); - Log_Print("map file written in %5.0f seconds\n", I_FloatTime() - start_time); -} //end of the function WriteMapFile -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void PrintMapInfo(void) -{ - Log_Print("\n"); - Log_Print("%6i brushes\n", nummapbrushes); - Log_Print("%6i brush sides\n", nummapbrushsides); -// Log_Print("%6i clipbrushes\n", c_clipbrushes); -// Log_Print("%6i total sides\n", nummapbrushsides); -// Log_Print("%6i boxbevels\n", c_boxbevels); -// Log_Print("%6i edgebevels\n", c_edgebevels); -// Log_Print("%6i entities\n", num_entities); -// Log_Print("%6i planes\n", nummapplanes); -// Log_Print("%6i areaportals\n", c_areaportals); -// Log_Print("%6i squatt brushes\n", c_squattbrushes); -// Log_Print("size: %5.0f,%5.0f,%5.0f to %5.0f,%5.0f,%5.0f\n", map_mins[0],map_mins[1],map_mins[2], -// map_maxs[0],map_maxs[1],map_maxs[2]); -} //end of the function PrintMapInfo -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void ResetMapLoading(void) -{ - int i; - epair_t *ep, *nextep; - - Q2_ResetMapLoading(); - Sin_ResetMapLoading(); - - //free all map brush side windings - for (i = 0; i < nummapbrushsides; i++) - { - if (brushsides[i].winding) - { - FreeWinding(brushsides[i].winding); - } //end for - } //end for - - //reset regular stuff - nummapbrushes = 0; - memset(mapbrushes, 0, MAX_MAPFILE_BRUSHES * sizeof(mapbrush_t)); - // - nummapbrushsides = 0; - memset(brushsides, 0, MAX_MAPFILE_BRUSHSIDES * sizeof(side_t)); - memset(side_brushtextures, 0, MAX_MAPFILE_BRUSHSIDES * sizeof(brush_texture_t)); - // - nummapplanes = 0; - memset(mapplanes, 0, MAX_MAPFILE_PLANES * sizeof(plane_t)); - // - memset(planehash, 0, PLANE_HASHES * sizeof(plane_t *)); - // - memset(map_texinfo, 0, MAX_MAPFILE_TEXINFO * sizeof(map_texinfo_t)); - map_numtexinfo = 0; - // - VectorClear(map_mins); - VectorClear(map_maxs); - // - c_boxbevels = 0; - c_edgebevels = 0; - c_areaportals = 0; - c_clipbrushes = 0; - c_writtenbrushes = 0; - //clear the entities - for (i = 0; i < num_entities; i++) - { - for (ep = entities[i].epairs; ep; ep = nextep) - { - nextep = ep->next; - FreeMemory(ep->key); - FreeMemory(ep->value); - FreeMemory(ep); - } //end for - } //end for - num_entities = 0; - memset(entities, 0, MAX_MAP_ENTITIES * sizeof(entity_t)); -} //end of the function ResetMapLoading -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -#ifndef Q1_BSPVERSION -#define Q1_BSPVERSION 29 -#endif -#ifndef HL_BSPVERSION -#define HL_BSPVERSION 30 -#endif - -#define Q2_BSPHEADER (('P'<<24)+('S'<<16)+('B'<<8)+'I') //IBSP -#define Q2_BSPVERSION 38 - -#define SINGAME_BSPHEADER (('P'<<24)+('S'<<16)+('B'<<8)+'R') //RBSP -#define SINGAME_BSPVERSION 1 - -#define SIN_BSPHEADER (('P'<<24)+('S'<<16)+('B'<<8)+'I') //IBSP -#define SIN_BSPVERSION 41 - -typedef struct -{ - int ident; - int version; -} idheader_t; - -int LoadMapFromBSP(struct quakefile_s *qf) -{ - idheader_t idheader; - - if (ReadQuakeFile(qf, &idheader, 0, sizeof(idheader_t)) != sizeof(idheader_t)) - { - return false; - } //end if - - idheader.ident = LittleLong(idheader.ident); - idheader.version = LittleLong(idheader.version); - //Quake3 BSP file - if (idheader.ident == Q3_BSP_IDENT && idheader.version == Q3_BSP_VERSION) - { - ResetMapLoading(); - Q3_LoadMapFromBSP(qf); - Q3_FreeMaxBSP(); - } //end if - //Quake2 BSP file - else if (idheader.ident == Q2_BSPHEADER && idheader.version == Q2_BSPVERSION) - { - ResetMapLoading(); - Q2_AllocMaxBSP(); - Q2_LoadMapFromBSP(qf->filename, qf->offset, qf->length); - Q2_FreeMaxBSP(); - } //endif - //Sin BSP file - else if ((idheader.ident == SIN_BSPHEADER && idheader.version == SIN_BSPVERSION) || - //the dorks gave the same format another ident and verions - (idheader.ident == SINGAME_BSPHEADER && idheader.version == SINGAME_BSPVERSION)) - { - ResetMapLoading(); - Sin_AllocMaxBSP(); - Sin_LoadMapFromBSP(qf->filename, qf->offset, qf->length); - Sin_FreeMaxBSP(); - } //end if - //the Quake1 bsp files don't have a ident only a version - else if (idheader.ident == Q1_BSPVERSION) - { - ResetMapLoading(); - Q1_AllocMaxBSP(); - Q1_LoadMapFromBSP(qf->filename, qf->offset, qf->length); - Q1_FreeMaxBSP(); - } //end if - //Half-Life also only uses a version number - else if (idheader.ident == HL_BSPVERSION) - { - ResetMapLoading(); - HL_AllocMaxBSP(); - HL_LoadMapFromBSP(qf->filename, qf->offset, qf->length); - HL_FreeMaxBSP(); - } //end if - else - { - Error("unknown BSP format %c%c%c%c, version %d\n", - (idheader.ident & 0xFF), - ((idheader.ident >> 8) & 0xFF), - ((idheader.ident >> 16) & 0xFF), - ((idheader.ident >> 24) & 0xFF), idheader.version); - return false; - } //end if - // - return true; -} //end of the function LoadMapFromBSP +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "qbsp.h" +#include "l_bsp_hl.h" +#include "l_bsp_q1.h" +#include "l_bsp_q2.h" +#include "l_bsp_q3.h" +#include "l_bsp_sin.h" +#include "l_mem.h" +#include "../botlib/aasfile.h" //aas_bbox_t +#include "aas_store.h" //AAS_MAX_BBOXES +#include "aas_cfg.h" + +#define Sign(x) (x < 0 ? 1 : 0) + +int nummapbrushes; +mapbrush_t mapbrushes[MAX_MAPFILE_BRUSHES]; + +int nummapbrushsides; +side_t brushsides[MAX_MAPFILE_BRUSHSIDES]; +brush_texture_t side_brushtextures[MAX_MAPFILE_BRUSHSIDES]; + +int nummapplanes; +plane_t mapplanes[MAX_MAPFILE_PLANES]; +int mapplaneusers[MAX_MAPFILE_PLANES]; + +#define PLANE_HASHES 1024 +plane_t *planehash[PLANE_HASHES]; +vec3_t map_mins, map_maxs; + +#ifdef SIN +textureref_t side_newrefs[MAX_MAPFILE_BRUSHSIDES]; +#endif + +map_texinfo_t map_texinfo[MAX_MAPFILE_TEXINFO]; +int map_numtexinfo; +int loadedmaptype; //loaded map type + +// undefine to make plane finding use linear sort +#define USE_HASHING + +int c_boxbevels; +int c_edgebevels; +int c_areaportals; +int c_clipbrushes; +int c_squattbrushes; +int c_writtenbrushes; + +/* +============================================================================= + +PLANE FINDING + +============================================================================= +*/ + + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int PlaneSignBits(vec3_t normal) +{ + int i, signbits; + + signbits = 0; + for (i = 2; i >= 0; i--) + { + signbits = (signbits << 1) + Sign(normal[i]); + } //end for + return signbits; +} //end of the function PlaneSignBits +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int PlaneTypeForNormal(vec3_t normal) +{ + vec_t ax, ay, az; + +// NOTE: should these have an epsilon around 1.0? + if (normal[0] == 1.0 || normal[0] == -1.0) + return PLANE_X; + if (normal[1] == 1.0 || normal[1] == -1.0) + return PLANE_Y; + if (normal[2] == 1.0 || normal[2] == -1.0) + return PLANE_Z; + + ax = fabs(normal[0]); + ay = fabs(normal[1]); + az = fabs(normal[2]); + + if (ax >= ay && ax >= az) + return PLANE_ANYX; + if (ay >= ax && ay >= az) + return PLANE_ANYY; + return PLANE_ANYZ; +} //end of the function PlaneTypeForNormal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +//ME NOTE: changed from 0.00001 +#define NORMAL_EPSILON 0.0001 +//ME NOTE: changed from 0.01 +#define DIST_EPSILON 0.02 +qboolean PlaneEqual(plane_t *p, vec3_t normal, vec_t dist) +{ +#if 1 + if ( + fabs(p->normal[0] - normal[0]) < NORMAL_EPSILON + && fabs(p->normal[1] - normal[1]) < NORMAL_EPSILON + && fabs(p->normal[2] - normal[2]) < NORMAL_EPSILON + && fabs(p->dist - dist) < DIST_EPSILON ) + return true; +#else + if (p->normal[0] == normal[0] + && p->normal[1] == normal[1] + && p->normal[2] == normal[2] + && p->dist == dist) + return true; +#endif + return false; +} //end of the function PlaneEqual +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AddPlaneToHash(plane_t *p) +{ + int hash; + + hash = (int)fabs(p->dist) / 8; + hash &= (PLANE_HASHES-1); + + p->hash_chain = planehash[hash]; + planehash[hash] = p; +} //end of the function AddPlaneToHash +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int CreateNewFloatPlane (vec3_t normal, vec_t dist) +{ + plane_t *p, temp; + + if (VectorLength(normal) < 0.5) + Error ("FloatPlane: bad normal"); + // create a new plane + if (nummapplanes+2 > MAX_MAPFILE_PLANES) + Error ("MAX_MAPFILE_PLANES"); + + p = &mapplanes[nummapplanes]; + VectorCopy (normal, p->normal); + p->dist = dist; + p->type = (p+1)->type = PlaneTypeForNormal (p->normal); + p->signbits = PlaneSignBits(p->normal); + + VectorSubtract (vec3_origin, normal, (p+1)->normal); + (p+1)->dist = -dist; + (p+1)->signbits = PlaneSignBits((p+1)->normal); + + nummapplanes += 2; + + // allways put axial planes facing positive first + if (p->type < 3) + { + if (p->normal[0] < 0 || p->normal[1] < 0 || p->normal[2] < 0) + { + // flip order + temp = *p; + *p = *(p+1); + *(p+1) = temp; + + AddPlaneToHash (p); + AddPlaneToHash (p+1); + return nummapplanes - 1; + } + } + + AddPlaneToHash (p); + AddPlaneToHash (p+1); + return nummapplanes - 2; +} //end of the function CreateNewFloatPlane +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void SnapVector(vec3_t normal) +{ + int i; + + for (i=0 ; i<3 ; i++) + { + if ( fabs(normal[i] - 1) < NORMAL_EPSILON ) + { + VectorClear (normal); + normal[i] = 1; + break; + } + if ( fabs(normal[i] - -1) < NORMAL_EPSILON ) + { + VectorClear (normal); + normal[i] = -1; + break; + } + } +} //end of the function SnapVector +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void SnapPlane(vec3_t normal, vec_t *dist) +{ + SnapVector(normal); + + if (fabs(*dist-Q_rint(*dist)) < DIST_EPSILON) + *dist = Q_rint(*dist); +} //end of the function SnapPlane +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifndef USE_HASHING +int FindFloatPlane(vec3_t normal, vec_t dist) +{ + int i; + plane_t *p; + + SnapPlane(normal, &dist); + for (i = 0, p = mapplanes; i < nummapplanes; i++, p++) + { + if (PlaneEqual (p, normal, dist)) + { + mapplaneusers[i]++; + return i; + } //end if + } //end for + i = CreateNewFloatPlane (normal, dist); + mapplaneusers[i]++; + return i; +} //end of the function FindFloatPlane +#else +int FindFloatPlane (vec3_t normal, vec_t dist) +{ + int i; + plane_t *p; + int hash, h; + + SnapPlane (normal, &dist); + hash = (int)fabs(dist) / 8; + hash &= (PLANE_HASHES-1); + + // search the border bins as well + for (i = -1; i <= 1; i++) + { + h = (hash+i)&(PLANE_HASHES-1); + for (p = planehash[h]; p; p = p->hash_chain) + { + if (PlaneEqual(p, normal, dist)) + { + mapplaneusers[p-mapplanes]++; + return p - mapplanes; + } //end if + } //end for + } //end for + i = CreateNewFloatPlane (normal, dist); + mapplaneusers[i]++; + return i; +} //end of the function FindFloatPlane +#endif +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int PlaneFromPoints (int *p0, int *p1, int *p2) +{ + vec3_t t1, t2, normal; + vec_t dist; + + VectorSubtract (p0, p1, t1); + VectorSubtract (p2, p1, t2); + CrossProduct (t1, t2, normal); + VectorNormalize (normal); + + dist = DotProduct (p0, normal); + + return FindFloatPlane (normal, dist); +} //end of the function PlaneFromPoints +//=========================================================================== +// Adds any additional planes necessary to allow the brush to be expanded +// against axial bounding boxes +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AddBrushBevels (mapbrush_t *b) +{ + int axis, dir; + int i, j, k, l, order; + side_t sidetemp; + brush_texture_t tdtemp; +#ifdef SIN + textureref_t trtemp; +#endif + side_t *s, *s2; + vec3_t normal; + float dist; + winding_t *w, *w2; + vec3_t vec, vec2; + float d; + + // + // add the axial planes + // + order = 0; + for (axis=0 ; axis <3 ; axis++) + { + for (dir=-1 ; dir <= 1 ; dir+=2, order++) + { + // see if the plane is allready present + for (i=0, s=b->original_sides ; inumsides ; i++,s++) + { + if (mapplanes[s->planenum].normal[axis] == dir) + break; + } + + if (i == b->numsides) + { // add a new side + if (nummapbrushsides == MAX_MAP_BRUSHSIDES) + Error ("MAX_MAP_BRUSHSIDES"); + nummapbrushsides++; + b->numsides++; + VectorClear (normal); + normal[axis] = dir; + if (dir == 1) + dist = b->maxs[axis]; + else + dist = -b->mins[axis]; + s->planenum = FindFloatPlane (normal, dist); + s->texinfo = b->original_sides[0].texinfo; +#ifdef SIN + s->lightinfo = b->original_sides[0].lightinfo; +#endif + s->contents = b->original_sides[0].contents; + s->flags |= SFL_BEVEL; + c_boxbevels++; + } + + // if the plane is not in it canonical order, swap it + if (i != order) + { + sidetemp = b->original_sides[order]; + b->original_sides[order] = b->original_sides[i]; + b->original_sides[i] = sidetemp; + + j = b->original_sides - brushsides; + tdtemp = side_brushtextures[j+order]; + side_brushtextures[j+order] = side_brushtextures[j+i]; + side_brushtextures[j+i] = tdtemp; + +#ifdef SIN + trtemp = side_newrefs[j+order]; + side_newrefs[j+order] = side_newrefs[j+i]; + side_newrefs[j+i] = trtemp; +#endif + } + } + } + + // + // add the edge bevels + // + if (b->numsides == 6) + return; // pure axial + + // test the non-axial plane edges + for (i=6 ; inumsides ; i++) + { + s = b->original_sides + i; + w = s->winding; + if (!w) + continue; + for (j=0 ; jnumpoints ; j++) + { + k = (j+1)%w->numpoints; + VectorSubtract (w->p[j], w->p[k], vec); + if (VectorNormalize (vec) < 0.5) + continue; + 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, normal); + if (VectorNormalize (normal) < 0.5) + continue; + dist = DotProduct (w->p[j], normal); + + // if all the points on all the sides are + // behind this plane, it is a proper edge bevel + for (k=0 ; knumsides ; k++) + { + // if this plane has allready been used, skip it + if (PlaneEqual (&mapplanes[b->original_sides[k].planenum] + , normal, dist) ) + break; + + w2 = b->original_sides[k].winding; + if (!w2) + continue; + for (l=0 ; lnumpoints ; l++) + { + d = DotProduct (w2->p[l], normal) - dist; + if (d > 0.1) + break; // point in front + } + if (l != w2->numpoints) + break; + } + + if (k != b->numsides) + continue; // wasn't part of the outer hull + // add this plane + if (nummapbrushsides == MAX_MAP_BRUSHSIDES) + Error ("MAX_MAP_BRUSHSIDES"); + nummapbrushsides++; + s2 = &b->original_sides[b->numsides]; + s2->planenum = FindFloatPlane (normal, dist); + s2->texinfo = b->original_sides[0].texinfo; +#ifdef SIN + s2->lightinfo = b->original_sides[0].lightinfo; +#endif + s2->contents = b->original_sides[0].contents; + s2->flags |= SFL_BEVEL; + c_edgebevels++; + b->numsides++; + } + } + } + } +} //end of the function AddBrushBevels +//=========================================================================== +// creates windigs for sides and mins / maxs for the brush +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean MakeBrushWindings(mapbrush_t *ob) +{ + int i, j; + winding_t *w; + side_t *side; + plane_t *plane; + + ClearBounds (ob->mins, ob->maxs); + + for (i = 0; i < ob->numsides; i++) + { + plane = &mapplanes[ob->original_sides[i].planenum]; + w = BaseWindingForPlane(plane->normal, plane->dist); + for (j = 0; j numsides && w; j++) + { + if (i == j) continue; + if (ob->original_sides[j].flags & SFL_BEVEL) continue; + plane = &mapplanes[ob->original_sides[j].planenum^1]; + ChopWindingInPlace(&w, plane->normal, plane->dist, 0); //CLIP_EPSILON); + } + + side = &ob->original_sides[i]; + side->winding = w; + if (w) + { + side->flags |= SFL_VISIBLE; + for (j = 0; j < w->numpoints; j++) + AddPointToBounds (w->p[j], ob->mins, ob->maxs); + } + } + + for (i = 0; i < 3; i++) + { + //IDBUG: all the indexes into the mins and maxs were zero (not using i) + if (ob->mins[i] < -MAX_MAP_BOUNDS || ob->maxs[i] > MAX_MAP_BOUNDS) + { + Log_Print("entity %i, brush %i: bounds out of range\n", ob->entitynum, ob->brushnum); + ob->numsides = 0; //remove the brush + break; + } //end if + if (ob->mins[i] > MAX_MAP_BOUNDS || ob->maxs[i] < -MAX_MAP_BOUNDS) + { + Log_Print("entity %i, brush %i: no visible sides on brush\n", ob->entitynum, ob->brushnum); + ob->numsides = 0; //remove the brush + break; + } //end if + } //end for + return true; +} //end of the function MakeBrushWindings +//=========================================================================== +// FIXME: currently doesn't mark all bevels +// NOTE: when one brush bevel is found the remaining sides of the brush +// are bevels as well (when the brush isn't expanded for AAS :)) +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void MarkBrushBevels(mapbrush_t *brush) +{ + int i; + int we; + side_t *s; + + //check all the sides of the brush + for (i = 0; i < brush->numsides; i++) + { + s = brush->original_sides + i; + //if the side has no winding + if (!s->winding) + { + Log_Write("MarkBrushBevels: brush %d no winding", brush->brushnum); + s->flags |= SFL_BEVEL; + } //end if + //if the winding is tiny + else if (WindingIsTiny(s->winding)) + { + s->flags |= SFL_BEVEL; + Log_Write("MarkBrushBevels: brush %d tiny winding", brush->brushnum); + } //end else if + //if the winding has errors + else + { + we = WindingError(s->winding); + if (we == WE_NOTENOUGHPOINTS + || we == WE_SMALLAREA + || we == WE_POINTBOGUSRANGE +// || we == WE_NONCONVEX + ) + { + Log_Write("MarkBrushBevels: brush %d %s", brush->brushnum, WindingErrorString()); + s->flags |= SFL_BEVEL; + } //end else if + } //end else + if (s->flags & SFL_BEVEL) + { + s->flags &= ~SFL_VISIBLE; + //if the side has a valid plane + if (s->planenum > 0 && s->planenum < nummapplanes) + { + //if it is an axial plane + if (mapplanes[s->planenum].type < 3) c_boxbevels++; + else c_edgebevels++; + } //end if + } //end if + } //end for +} //end of the function MarkBrushBevels +//=========================================================================== +// returns true if the map brush already exists +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BrushExists(mapbrush_t *brush) +{ + int i, s1, s2; + side_t *side1, *side2; + mapbrush_t *brush1, *brush2; + + for (i = 0; i < nummapbrushes; i++) + { + brush1 = brush; + brush2 = &mapbrushes[i]; + //compare the brushes + if (brush1->entitynum != brush2->entitynum) continue; + //if (brush1->contents != brush2->contents) continue; + if (brush1->numsides != brush2->numsides) continue; + for (s1 = 0; s1 < brush1->numsides; s1++) + { + side1 = brush1->original_sides + s1; + // + for (s2 = 0; s2 < brush2->numsides; s2++) + { + side2 = brush2->original_sides + s2; + // + if ((side1->planenum & ~1) == (side2->planenum & ~1) +// && side1->texinfo == side2->texinfo +// && side1->contents == side2->contents +// && side1->surf == side2->surf + ) break; + } //end if + if (s2 >= brush2->numsides) break; + } //end for + if (s1 >= brush1->numsides) return true; + } //end for + return false; +} //end of the function BrushExists +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean WriteMapBrush(FILE *fp, mapbrush_t *brush, vec3_t origin) +{ + int sn, rotate, shift[2], sv, tv, planenum, p1, i, j; + float scale[2], originshift[2], ang1, ang2, newdist; + vec3_t vecs[2], axis[2]; + map_texinfo_t *ti; + winding_t *w; + side_t *s; + plane_t *plane; + + if (noliquids) + { + if (brush->contents & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA)) + { + return true; + } //end if + } //end if + //if the brush has no contents + if (!brush->contents) return true; + //print the leading { + if (fprintf(fp, " { //brush %d\n", brush->brushnum) < 0) return false; + //write brush sides + for (sn = 0; sn < brush->numsides; sn++) + { + s = brush->original_sides + sn; + //don't write out bevels + if (!(s->flags & SFL_BEVEL)) + { + //if the entity has an origin set + if (origin[0] || origin[1] || origin[2]) + { + newdist = mapplanes[s->planenum].dist + + DotProduct(mapplanes[s->planenum].normal, origin); + planenum = FindFloatPlane(mapplanes[s->planenum].normal, newdist); + } //end if + else + { + planenum = s->planenum; + } //end else + //always take the first plane, then flip the points if necesary + plane = &mapplanes[planenum & ~1]; + w = BaseWindingForPlane(plane->normal, plane->dist); + // + for (i = 0; i < 3; i++) + { + for (j = 0; j < 3; j++) + { + if (fabs(w->p[i][j]) < 0.2) w->p[i][j] = 0; + else if (fabs((int)w->p[i][j] - w->p[i][j]) < 0.3) w->p[i][j] = (int) w->p[i][j]; + //w->p[i][j] = (int) (w->p[i][j] + 0.2); + } //end for + } //end for + //three non-colinear points to define the plane + if (planenum & 1) p1 = 1; + else p1 = 0; + if (fprintf(fp," ( %5i %5i %5i ) ", (int)w->p[p1][0], (int)w->p[p1][1], (int)w->p[p1][2]) < 0) return false; + if (fprintf(fp,"( %5i %5i %5i ) ", (int)w->p[!p1][0], (int)w->p[!p1][1], (int)w->p[!p1][2]) < 0) return false; + if (fprintf(fp,"( %5i %5i %5i ) ", (int)w->p[2][0], (int)w->p[2][1], (int)w->p[2][2]) < 0) return false; + //free the winding + FreeWinding(w); + // + if (s->texinfo == TEXINFO_NODE) + { + if (brush->contents & CONTENTS_PLAYERCLIP) + { + //player clip + if (loadedmaptype == MAPTYPE_SIN) + { + if (fprintf(fp, "generic/misc/clip 0 0 0 1 1") < 0) return false; + } //end if + else if (loadedmaptype == MAPTYPE_QUAKE2) + { //FIXME: don't always use e1u1 + if (fprintf(fp, "e1u1/clip 0 0 0 1 1") < 0) return false; + } //end else + else if (loadedmaptype == MAPTYPE_QUAKE3) + { + if (fprintf(fp, "e1u1/clip 0 0 0 1 1") < 0) return false; + } //end else if + else + { + if (fprintf(fp, "clip 0 0 0 1 1") < 0) return false; + } //end else + } //end if + else if (brush->contents == CONTENTS_MONSTERCLIP) + { + //monster clip + if (loadedmaptype == MAPTYPE_SIN) + { + if (fprintf(fp, "generic/misc/monster 0 0 0 1 1") < 0) return false; + } //end if + else if (loadedmaptype == MAPTYPE_QUAKE2) + { + if (fprintf(fp, "e1u1/clip_mon 0 0 0 1 1") < 0) return false; + } //end else + else + { + if (fprintf(fp, "clip 0 0 0 1 1") < 0) return false; + } //end else + } //end else + else + { + if (fprintf(fp, "clip 0 0 0 1 1") < 0) return false; + Log_Write("brush->contents = %d\n", brush->contents); + } //end else + } //end if + else if (loadedmaptype == MAPTYPE_SIN && s->texinfo == 0) + { + if (brush->contents & CONTENTS_DUMMYFENCE) + { + if (fprintf(fp, "generic/misc/fence 0 0 0 1 1") < 0) return false; + } //end if + else if (brush->contents & CONTENTS_MIST) + { + if (fprintf(fp, "generic/misc/volumetric_base 0 0 0 1 1") < 0) return false; + } //end if + else //unknown so far + { + if (fprintf(fp, "generic/misc/red 0 0 0 1 1") < 0) return false; + } //end else + } //end if + else if (loadedmaptype == MAPTYPE_QUAKE3) + { + //always use the same texture + if (fprintf(fp, "e2u3/floor1_2 0 0 0 1 1 1 0 0") < 0) return false; + } //end else if + else + { + //* + ti = &map_texinfo[s->texinfo]; + //the scaling of the texture + scale[0] = 1 / VectorNormalize2(ti->vecs[0], vecs[0]); + scale[1] = 1 / VectorNormalize2(ti->vecs[1], vecs[1]); + // + TextureAxisFromPlane(plane, axis[0], axis[1]); + //calculate texture shift done by entity origin + originshift[0] = DotProduct(origin, axis[0]); + originshift[1] = DotProduct(origin, axis[1]); + //the texture shift without origin shift + shift[0] = ti->vecs[0][3] - originshift[0]; + shift[1] = ti->vecs[1][3] - originshift[1]; + // + if (axis[0][0]) sv = 0; + else if (axis[0][1]) sv = 1; + else sv = 2; + if (axis[1][0]) tv = 0; + else if (axis[1][1]) tv = 1; + else tv = 2; + //calculate rotation of texture + if (vecs[0][tv] == 0) ang1 = vecs[0][sv] > 0 ? 90.0 : -90.0; + else ang1 = atan2(vecs[0][sv], vecs[0][tv]) * 180 / Q_PI; + if (ang1 < 0) ang1 += 360; + if (ang1 >= 360) ang1 -= 360; + if (axis[0][tv] == 0) ang2 = axis[0][sv] > 0 ? 90.0 : -90.0; + else ang2 = atan2(axis[0][sv], axis[0][tv]) * 180 / Q_PI; + if (ang2 < 0) ang2 += 360; + if (ang2 >= 360) ang2 -= 360; + rotate = ang2 - ang1; + if (rotate < 0) rotate += 360; + if (rotate >= 360) rotate -= 360; + //write the texture info + if (fprintf(fp, "%s %d %d %d", ti->texture, shift[0], shift[1], rotate) < 0) return false; + if (fabs(scale[0] - ((int) scale[0])) < 0.001) + { + if (fprintf(fp, " %d", (int) scale[0]) < 0) return false; + } //end if + else + { + if (fprintf(fp, " %4f", scale[0]) < 0) return false; + } //end if + if (fabs(scale[1] - ((int) scale[1])) < 0.001) + { + if (fprintf(fp, " %d", (int) scale[1]) < 0) return false; + } //end if + else + { + if (fprintf(fp, " %4f", scale[1]) < 0) return false; + } //end else + //write the extra brush side info + if (loadedmaptype == MAPTYPE_QUAKE2) + { + if (fprintf(fp, " %ld %ld %ld", s->contents, ti->flags, ti->value) < 0) return false; + } //end if + //*/ + } //end else + if (fprintf(fp, "\n") < 0) return false; + } //end if + } //end if + if (fprintf(fp, " }\n") < 0) return false; + c_writtenbrushes++; + return true; +} //end of the function WriteMapBrush +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean WriteOriginBrush(FILE *fp, vec3_t origin) +{ + vec3_t normal; + float dist; + int i, s; + winding_t *w; + + if (fprintf(fp, " {\n") < 0) return false; + // + for (i = 0; i < 3; i++) + { + for (s = -1; s <= 1; s += 2) + { + // + VectorClear(normal); + normal[i] = s; + dist = origin[i] * s + 16; + // + w = BaseWindingForPlane(normal, dist); + //three non-colinear points to define the plane + if (fprintf(fp," ( %5i %5i %5i ) ", (int)w->p[0][0], (int)w->p[0][1], (int)w->p[0][2]) < 0) return false; + if (fprintf(fp,"( %5i %5i %5i ) ", (int)w->p[1][0], (int)w->p[1][1], (int)w->p[1][2]) < 0) return false; + if (fprintf(fp,"( %5i %5i %5i ) ", (int)w->p[2][0], (int)w->p[2][1], (int)w->p[2][2]) < 0) return false; + //free the winding + FreeWinding(w); + //write origin texture: + // CONTENTS_ORIGIN = 16777216 + // SURF_NODRAW = 128 + if (loadedmaptype == MAPTYPE_SIN) + { + if (fprintf(fp, "generic/misc/origin 0 0 0 1 1") < 0) return false; + } //end if + else if (loadedmaptype == MAPTYPE_HALFLIFE) + { + if (fprintf(fp, "origin 0 0 0 1 1") < 0) return false; + } //end if + else + { + if (fprintf(fp, "e1u1/origin 0 0 0 1 1") < 0) return false; + } //end else + //Quake2 extra brush side info + if (loadedmaptype == MAPTYPE_QUAKE2) + { + //if (fprintf(fp, " 16777216 128 0") < 0) return false; + } //end if + if (fprintf(fp, "\n") < 0) return false; + } //end for + } //end for + if (fprintf(fp, " }\n") < 0) return false; + c_writtenbrushes++; + return true; +} //end of the function WriteOriginBrush +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +mapbrush_t *GetAreaPortalBrush(entity_t *mapent) +{ + int portalnum, bn; + mapbrush_t *brush; + + //the area portal number + portalnum = mapent->areaportalnum; + //find the area portal brush in the world brushes + for (bn = 0; bn < nummapbrushes && portalnum; bn++) + { + brush = &mapbrushes[bn]; + //must be in world entity + if (brush->entitynum == 0) + { + if (brush->contents & CONTENTS_AREAPORTAL) + { + portalnum--; + } //end if + } //end if + } //end for + if (bn < nummapbrushes) + { + return brush; + } //end if + else + { + Log_Print("area portal %d brush not found\n", mapent->areaportalnum); + return NULL; + } //end else +} //end of the function GetAreaPortalBrush +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean WriteMapFileSafe(FILE *fp) +{ + char key[1024], value[1024]; + int i, bn, entitybrushes; + epair_t *ep; + mapbrush_t *brush; + entity_t *mapent; + //vec3_t vec_origin = {0, 0, 0}; + + // + if (fprintf(fp,"//=====================================================\n" + "//\n" + "// map file created with BSPC "BSPC_VERSION"\n" + "//\n" + "// BSPC is designed to decompile material in which you own the copyright\n" + "// or have obtained permission to decompile from the copyright owner. Unless\n" + "// you own the copyright or have permission to decompile from the copyright\n" + "// owner, you may be violating copyright law and be subject to payment of\n" + "// damages and other remedies. If you are uncertain about your rights, contact\n" + "// your legal advisor.\n" + "//\n") < 0) return false; + if (loadedmaptype == MAPTYPE_SIN) + { + if (fprintf(fp, + "// generic/misc/red is used for unknown textures\n") < 0) return false; + } //end if + if (fprintf(fp,"//\n" + "//=====================================================\n") < 0) return false; + //write out all the entities + for (i = 0; i < num_entities; i++) + { + mapent = &entities[i]; + if (!mapent->epairs) + { + continue; + } //end if + if (fprintf(fp, "{\n") < 0) return false; + // + if (loadedmaptype == MAPTYPE_QUAKE3) + { + if (!stricmp(ValueForKey(mapent, "classname"), "light")) + { + SetKeyValue(mapent, "light", "10000"); + } //end if + } //end if + //write epairs + for (ep = mapent->epairs; ep; ep = ep->next) + { + strcpy(key, ep->key); + StripTrailing (key); + strcpy(value, ep->value); + StripTrailing(value); + // + if (loadedmaptype == MAPTYPE_QUAKE2 || + loadedmaptype == MAPTYPE_SIN) + { + //don't write an origin for BSP models + if (mapent->modelnum >= 0 && !strcmp(key, "origin")) continue; + } //end if + //don't write BSP model numbers + if (mapent->modelnum >= 0 && !strcmp(key, "model") && value[0] == '*') continue; + // + if (fprintf(fp, " \"%s\" \"%s\"\n", key, value) < 0) return false; + } //end for + // + if (ValueForKey(mapent, "origin")) GetVectorForKey(mapent, "origin", mapent->origin); + else mapent->origin[0] = mapent->origin[1] = mapent->origin[2] = 0; + //if this is an area portal entity + if (!strcmp("func_areaportal", ValueForKey(mapent, "classname"))) + { + brush = GetAreaPortalBrush(mapent); + if (!brush) return false; + if (!WriteMapBrush(fp, brush, mapent->origin)) return false; + } //end if + else + { + entitybrushes = false; + //write brushes + for (bn = 0; bn < nummapbrushes; bn++) + { + brush = &mapbrushes[bn]; + //if the brush is part of this entity + if (brush->entitynum == i) + { + //don't write out area portal brushes in the world + if (!((brush->contents & CONTENTS_AREAPORTAL) && brush->entitynum == 0)) + { + /* + if (!strcmp("func_door_rotating", ValueForKey(mapent, "classname"))) + { + AAS_PositionFuncRotatingBrush(mapent, brush); + if (!WriteMapBrush(fp, brush, vec_origin)) return false; + } //end if + else //*/ + { + if (!WriteMapBrush(fp, brush, mapent->origin)) return false; + } //end else + entitybrushes = true; + } //end if + } //end if + } //end for + //if the entity had brushes + if (entitybrushes) + { + //if the entity has an origin set + if (mapent->origin[0] || mapent->origin[1] || mapent->origin[2]) + { + if (!WriteOriginBrush(fp, mapent->origin)) return false; + } //end if + } //end if + } //end else + if (fprintf(fp, "}\n") < 0) return false; + } //end for + if (fprintf(fp, "//total of %d brushes\n", c_writtenbrushes) < 0) return false; + return true; +} //end of the function WriteMapFileSafe +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void WriteMapFile(char *filename) +{ + FILE *fp; + double start_time; + + c_writtenbrushes = 0; + //the time started + start_time = I_FloatTime(); + // + Log_Print("writing %s\n", filename); + fp = fopen(filename, "wb"); + if (!fp) + { + Log_Print("can't open %s\n", filename); + return; + } //end if + if (!WriteMapFileSafe(fp)) + { + fclose(fp); + Log_Print("error writing map file %s\n", filename); + return; + } //end if + fclose(fp); + //display creation time + Log_Print("written %d brushes\n", c_writtenbrushes); + Log_Print("map file written in %5.0f seconds\n", I_FloatTime() - start_time); +} //end of the function WriteMapFile +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void PrintMapInfo(void) +{ + Log_Print("\n"); + Log_Print("%6i brushes\n", nummapbrushes); + Log_Print("%6i brush sides\n", nummapbrushsides); +// Log_Print("%6i clipbrushes\n", c_clipbrushes); +// Log_Print("%6i total sides\n", nummapbrushsides); +// Log_Print("%6i boxbevels\n", c_boxbevels); +// Log_Print("%6i edgebevels\n", c_edgebevels); +// Log_Print("%6i entities\n", num_entities); +// Log_Print("%6i planes\n", nummapplanes); +// Log_Print("%6i areaportals\n", c_areaportals); +// Log_Print("%6i squatt brushes\n", c_squattbrushes); +// Log_Print("size: %5.0f,%5.0f,%5.0f to %5.0f,%5.0f,%5.0f\n", map_mins[0],map_mins[1],map_mins[2], +// map_maxs[0],map_maxs[1],map_maxs[2]); +} //end of the function PrintMapInfo +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ResetMapLoading(void) +{ + int i; + epair_t *ep, *nextep; + + Q2_ResetMapLoading(); + Sin_ResetMapLoading(); + + //free all map brush side windings + for (i = 0; i < nummapbrushsides; i++) + { + if (brushsides[i].winding) + { + FreeWinding(brushsides[i].winding); + } //end for + } //end for + + //reset regular stuff + nummapbrushes = 0; + memset(mapbrushes, 0, MAX_MAPFILE_BRUSHES * sizeof(mapbrush_t)); + // + nummapbrushsides = 0; + memset(brushsides, 0, MAX_MAPFILE_BRUSHSIDES * sizeof(side_t)); + memset(side_brushtextures, 0, MAX_MAPFILE_BRUSHSIDES * sizeof(brush_texture_t)); + // + nummapplanes = 0; + memset(mapplanes, 0, MAX_MAPFILE_PLANES * sizeof(plane_t)); + // + memset(planehash, 0, PLANE_HASHES * sizeof(plane_t *)); + // + memset(map_texinfo, 0, MAX_MAPFILE_TEXINFO * sizeof(map_texinfo_t)); + map_numtexinfo = 0; + // + VectorClear(map_mins); + VectorClear(map_maxs); + // + c_boxbevels = 0; + c_edgebevels = 0; + c_areaportals = 0; + c_clipbrushes = 0; + c_writtenbrushes = 0; + //clear the entities + for (i = 0; i < num_entities; i++) + { + for (ep = entities[i].epairs; ep; ep = nextep) + { + nextep = ep->next; + FreeMemory(ep->key); + FreeMemory(ep->value); + FreeMemory(ep); + } //end for + } //end for + num_entities = 0; + memset(entities, 0, MAX_MAP_ENTITIES * sizeof(entity_t)); +} //end of the function ResetMapLoading +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifndef Q1_BSPVERSION +#define Q1_BSPVERSION 29 +#endif +#ifndef HL_BSPVERSION +#define HL_BSPVERSION 30 +#endif + +#define Q2_BSPHEADER (('P'<<24)+('S'<<16)+('B'<<8)+'I') //IBSP +#define Q2_BSPVERSION 38 + +#define SINGAME_BSPHEADER (('P'<<24)+('S'<<16)+('B'<<8)+'R') //RBSP +#define SINGAME_BSPVERSION 1 + +#define SIN_BSPHEADER (('P'<<24)+('S'<<16)+('B'<<8)+'I') //IBSP +#define SIN_BSPVERSION 41 + +typedef struct +{ + int ident; + int version; +} idheader_t; + +int LoadMapFromBSP(struct quakefile_s *qf) +{ + idheader_t idheader; + + if (ReadQuakeFile(qf, &idheader, 0, sizeof(idheader_t)) != sizeof(idheader_t)) + { + return false; + } //end if + + idheader.ident = LittleLong(idheader.ident); + idheader.version = LittleLong(idheader.version); + //Quake3 BSP file + if (idheader.ident == Q3_BSP_IDENT && idheader.version == Q3_BSP_VERSION) + { + ResetMapLoading(); + Q3_LoadMapFromBSP(qf); + Q3_FreeMaxBSP(); + } //end if + //Quake2 BSP file + else if (idheader.ident == Q2_BSPHEADER && idheader.version == Q2_BSPVERSION) + { + ResetMapLoading(); + Q2_AllocMaxBSP(); + Q2_LoadMapFromBSP(qf->filename, qf->offset, qf->length); + Q2_FreeMaxBSP(); + } //endif + //Sin BSP file + else if ((idheader.ident == SIN_BSPHEADER && idheader.version == SIN_BSPVERSION) || + //the dorks gave the same format another ident and verions + (idheader.ident == SINGAME_BSPHEADER && idheader.version == SINGAME_BSPVERSION)) + { + ResetMapLoading(); + Sin_AllocMaxBSP(); + Sin_LoadMapFromBSP(qf->filename, qf->offset, qf->length); + Sin_FreeMaxBSP(); + } //end if + //the Quake1 bsp files don't have a ident only a version + else if (idheader.ident == Q1_BSPVERSION) + { + ResetMapLoading(); + Q1_AllocMaxBSP(); + Q1_LoadMapFromBSP(qf->filename, qf->offset, qf->length); + Q1_FreeMaxBSP(); + } //end if + //Half-Life also only uses a version number + else if (idheader.ident == HL_BSPVERSION) + { + ResetMapLoading(); + HL_AllocMaxBSP(); + HL_LoadMapFromBSP(qf->filename, qf->offset, qf->length); + HL_FreeMaxBSP(); + } //end if + else + { + Error("unknown BSP format %c%c%c%c, version %d\n", + (idheader.ident & 0xFF), + ((idheader.ident >> 8) & 0xFF), + ((idheader.ident >> 16) & 0xFF), + ((idheader.ident >> 24) & 0xFF), idheader.version); + return false; + } //end if + // + return true; +} //end of the function LoadMapFromBSP diff --git a/code/bspc/map_hl.c b/code/bspc/map_hl.c index 7e6037b..88e4fa7 100755 --- a/code/bspc/map_hl.c +++ b/code/bspc/map_hl.c @@ -1,1114 +1,1114 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "qbsp.h" -#include "l_bsp_hl.h" -#include "aas_map.h" //AAS_CreateMapBrushes - -int hl_numbrushes; -int hl_numclipbrushes; - -//#define HL_PRINT -#define HLCONTENTS - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int HL_TextureContents(char *name) -{ - if (!Q_strncasecmp (name, "sky",3)) - return CONTENTS_SOLID; - - if (!Q_strncasecmp(name+1,"!lava",5)) - return CONTENTS_LAVA; - - if (!Q_strncasecmp(name+1,"!slime",6)) - return CONTENTS_SLIME; - - /* - if (!Q_strncasecmp (name, "!cur_90",7)) - return CONTENTS_CURRENT_90; - if (!Q_strncasecmp (name, "!cur_0",6)) - return CONTENTS_CURRENT_0; - if (!Q_strncasecmp (name, "!cur_270",8)) - return CONTENTS_CURRENT_270; - if (!Q_strncasecmp (name, "!cur_180",8)) - return CONTENTS_CURRENT_180; - if (!Q_strncasecmp (name, "!cur_up",7)) - return CONTENTS_CURRENT_UP; - if (!Q_strncasecmp (name, "!cur_dwn",8)) - return CONTENTS_CURRENT_DOWN; - //*/ - if (name[0] == '!') - return CONTENTS_WATER; - /* - if (!Q_strncasecmp (name, "origin",6)) - return CONTENTS_ORIGIN; - if (!Q_strncasecmp (name, "clip",4)) - return CONTENTS_CLIP; - if( !Q_strncasecmp( name, "translucent", 11 ) ) - return CONTENTS_TRANSLUCENT; - if( name[0] == '@' ) - return CONTENTS_TRANSLUCENT; - //*/ - - return CONTENTS_SOLID; -} //end of the function HL_TextureContents -//=========================================================================== -// Generates two new brushes, leaving the original -// unchanged -// -// modified for Half-Life because there are quite a lot of tiny node leaves -// in the Half-Life bsps -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void HL_SplitBrush(bspbrush_t *brush, int planenum, int nodenum, - bspbrush_t **front, bspbrush_t **back) -{ - bspbrush_t *b[2]; - int i, j; - winding_t *w, *cw[2], *midwinding; - plane_t *plane, *plane2; - side_t *s, *cs; - float d, d_front, d_back; - - *front = *back = NULL; - plane = &mapplanes[planenum]; - - // check all points - d_front = d_back = 0; - for (i=0 ; inumsides ; i++) - { - w = brush->sides[i].winding; - if (!w) - continue; - for (j=0 ; jnumpoints ; j++) - { - d = DotProduct (w->p[j], plane->normal) - plane->dist; - if (d > 0 && d > d_front) - d_front = d; - if (d < 0 && d < d_back) - d_back = d; - } //end for - } //end for - - if (d_front < 0.1) // PLANESIDE_EPSILON) - { // only on back - *back = CopyBrush (brush); - Log_Print("HL_SplitBrush: only on back\n"); - return; - } //end if - if (d_back > -0.1) // PLANESIDE_EPSILON) - { // only on front - *front = CopyBrush (brush); - Log_Print("HL_SplitBrush: only on front\n"); - return; - } //end if - - // create a new winding from the split plane - - w = BaseWindingForPlane (plane->normal, plane->dist); - for (i = 0; i < brush->numsides && w; i++) - { - plane2 = &mapplanes[brush->sides[i].planenum ^ 1]; - ChopWindingInPlace(&w, plane2->normal, plane2->dist, 0); // PLANESIDE_EPSILON); - } //end for - - if (!w || WindingIsTiny(w)) - { // the brush isn't really split - int side; - - Log_Print("HL_SplitBrush: no split winding\n"); - side = BrushMostlyOnSide (brush, plane); - if (side == PSIDE_FRONT) - *front = CopyBrush (brush); - if (side == PSIDE_BACK) - *back = CopyBrush (brush); - return; - } - - if (WindingIsHuge(w)) - { - Log_Print("HL_SplitBrush: WARNING huge split winding\n"); - } //end of - - midwinding = w; - - // split it for real - - for (i = 0; i < 2; i++) - { - b[i] = AllocBrush (brush->numsides+1); - b[i]->original = brush->original; - } //end for - - // split all the current windings - - for (i=0 ; inumsides ; i++) - { - s = &brush->sides[i]; - w = s->winding; - if (!w) - continue; - ClipWindingEpsilon (w, plane->normal, plane->dist, - 0 /*PLANESIDE_EPSILON*/, &cw[0], &cw[1]); - for (j=0 ; j<2 ; j++) - { - if (!cw[j]) - continue; -#if 0 - if (WindingIsTiny (cw[j])) - { - FreeWinding (cw[j]); - continue; - } -#endif - cs = &b[j]->sides[b[j]->numsides]; - b[j]->numsides++; - *cs = *s; -// cs->planenum = s->planenum; -// cs->texinfo = s->texinfo; -// cs->visible = s->visible; -// cs->original = s->original; - cs->winding = cw[j]; - cs->flags &= ~SFL_TESTED; - } //end for - } //end for - - - // see if we have valid polygons on both sides - - for (i=0 ; i<2 ; i++) - { - BoundBrush (b[i]); - for (j=0 ; j<3 ; j++) - { - if (b[i]->mins[j] < -4096 || b[i]->maxs[j] > 4096) - { - Log_Print("HL_SplitBrush: bogus brush after clip\n"); - break; - } //end if - } //end for - - if (b[i]->numsides < 3 || j < 3) - { - FreeBrush (b[i]); - b[i] = NULL; - Log_Print("HL_SplitBrush: numsides < 3\n"); - } //end if - } //end for - - if ( !(b[0] && b[1]) ) - { - if (!b[0] && !b[1]) - Log_Print("HL_SplitBrush: split removed brush\n"); - else - Log_Print("HL_SplitBrush: split not on both sides\n"); - if (b[0]) - { - FreeBrush (b[0]); - *front = CopyBrush (brush); - } //end if - if (b[1]) - { - FreeBrush (b[1]); - *back = CopyBrush (brush); - } //end if - return; - } //end if - - // add the midwinding to both sides - for (i = 0; i < 2; i++) - { - cs = &b[i]->sides[b[i]->numsides]; - b[i]->numsides++; - - cs->planenum = planenum^i^1; - cs->texinfo = 0; - //store the node number in the surf to find the texinfo later on - cs->surf = nodenum; - // - cs->flags &= ~SFL_VISIBLE; - cs->flags &= ~SFL_TESTED; - if (i==0) - cs->winding = CopyWinding (midwinding); - else - cs->winding = midwinding; - } //end for - - -{ - vec_t v1; - int i; - - for (i=0 ; i<2 ; i++) - { - v1 = BrushVolume (b[i]); - if (v1 < 1) - { - FreeBrush (b[i]); - b[i] = NULL; - Log_Print("HL_SplitBrush: tiny volume after clip\n"); - } //end if - } //end for -} //*/ - - *front = b[0]; - *back = b[1]; -} //end of the function HL_SplitBrush -//=========================================================================== -// returns true if the tree starting at nodenum has only solid leaves -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int HL_SolidTree_r(int nodenum) -{ - if (nodenum < 0) - { - switch(hl_dleafs[(-nodenum) - 1].contents) - { - case HL_CONTENTS_EMPTY: - { - return false; - } //end case - case HL_CONTENTS_SOLID: -#ifdef HLCONTENTS - case HL_CONTENTS_CLIP: -#endif //HLCONTENTS - case HL_CONTENTS_SKY: -#ifdef HLCONTENTS - case HL_CONTENTS_TRANSLUCENT: -#endif //HLCONTENTS - { - return true; - } //end case - case HL_CONTENTS_WATER: - case HL_CONTENTS_SLIME: - case HL_CONTENTS_LAVA: -#ifdef HLCONTENTS - //these contents should not be found in the BSP - case HL_CONTENTS_ORIGIN: - case HL_CONTENTS_CURRENT_0: - case HL_CONTENTS_CURRENT_90: - case HL_CONTENTS_CURRENT_180: - case HL_CONTENTS_CURRENT_270: - case HL_CONTENTS_CURRENT_UP: - case HL_CONTENTS_CURRENT_DOWN: -#endif //HLCONTENTS - default: - { - return false; - } //end default - } //end switch - return false; - } //end if - if (!HL_SolidTree_r(hl_dnodes[nodenum].children[0])) return false; - if (!HL_SolidTree_r(hl_dnodes[nodenum].children[1])) return false; - return true; -} //end of the function HL_SolidTree_r -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bspbrush_t *HL_CreateBrushes_r(bspbrush_t *brush, int nodenum) -{ - int planenum; - bspbrush_t *front, *back; - hl_dleaf_t *leaf; - - //if it is a leaf - if (nodenum < 0) - { - leaf = &hl_dleafs[(-nodenum) - 1]; - if (leaf->contents != HL_CONTENTS_EMPTY) - { -#ifdef HL_PRINT - qprintf("\r%5i", ++hl_numbrushes); -#endif //HL_PRINT - } //end if - switch(leaf->contents) - { - case HL_CONTENTS_EMPTY: - { - FreeBrush(brush); - return NULL; - } //end case - case HL_CONTENTS_SOLID: -#ifdef HLCONTENTS - case HL_CONTENTS_CLIP: -#endif //HLCONTENTS - case HL_CONTENTS_SKY: -#ifdef HLCONTENTS - case HL_CONTENTS_TRANSLUCENT: -#endif //HLCONTENTS - { - brush->side = CONTENTS_SOLID; - return brush; - } //end case - case HL_CONTENTS_WATER: - { - brush->side = CONTENTS_WATER; - return brush; - } //end case - case HL_CONTENTS_SLIME: - { - brush->side = CONTENTS_SLIME; - return brush; - } //end case - case HL_CONTENTS_LAVA: - { - brush->side = CONTENTS_LAVA; - return brush; - } //end case -#ifdef HLCONTENTS - //these contents should not be found in the BSP - case HL_CONTENTS_ORIGIN: - case HL_CONTENTS_CURRENT_0: - case HL_CONTENTS_CURRENT_90: - case HL_CONTENTS_CURRENT_180: - case HL_CONTENTS_CURRENT_270: - case HL_CONTENTS_CURRENT_UP: - case HL_CONTENTS_CURRENT_DOWN: - { - Error("HL_CreateBrushes_r: found contents %d in Half-Life BSP", leaf->contents); - return NULL; - } //end case -#endif //HLCONTENTS - default: - { - Error("HL_CreateBrushes_r: unknown contents %d in Half-Life BSP", leaf->contents); - return NULL; - } //end default - } //end switch - return NULL; - } //end if - //if the rest of the tree is solid - /*if (HL_SolidTree_r(nodenum)) - { - brush->side = CONTENTS_SOLID; - return brush; - } //end if*/ - // - planenum = hl_dnodes[nodenum].planenum; - planenum = FindFloatPlane(hl_dplanes[planenum].normal, hl_dplanes[planenum].dist); - //split the brush with the node plane - HL_SplitBrush(brush, planenum, nodenum, &front, &back); - //free the original brush - FreeBrush(brush); - //every node must split the brush in two - if (!front || !back) - { - Log_Print("HL_CreateBrushes_r: WARNING node not splitting brush\n"); - //return NULL; - } //end if - //create brushes recursively - if (front) front = HL_CreateBrushes_r(front, hl_dnodes[nodenum].children[0]); - if (back) back = HL_CreateBrushes_r(back, hl_dnodes[nodenum].children[1]); - //link the brushes if possible and return them - if (front) - { - for (brush = front; brush->next; brush = brush->next); - brush->next = back; - return front; - } //end if - else - { - return back; - } //end else -} //end of the function HL_CreateBrushes_r -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bspbrush_t *HL_CreateBrushesFromBSP(int modelnum) -{ - bspbrush_t *brushlist; - bspbrush_t *brush; - hl_dnode_t *headnode; - vec3_t mins, maxs; - int i; - - // - headnode = &hl_dnodes[hl_dmodels[modelnum].headnode[0]]; - //get the mins and maxs of the world - VectorCopy(headnode->mins, mins); - VectorCopy(headnode->maxs, maxs); - //enlarge these mins and maxs - for (i = 0; i < 3; i++) - { - mins[i] -= 8; - maxs[i] += 8; - } //end for - //NOTE: have to add the BSP tree mins and maxs to the MAP mins and maxs - AddPointToBounds(mins, map_mins, map_maxs); - AddPointToBounds(maxs, map_mins, map_maxs); - // - if (!modelnum) - { - Log_Print("brush size: %5.0f,%5.0f,%5.0f to %5.0f,%5.0f,%5.0f\n", - map_mins[0], map_mins[1], map_mins[2], - map_maxs[0], map_maxs[1], map_maxs[2]); - } //end if - //create one huge brush containing the whole world - brush = BrushFromBounds(mins, maxs); - VectorCopy(mins, brush->mins); - VectorCopy(maxs, brush->maxs); - // -#ifdef HL_PRINT - qprintf("creating Half-Life brushes\n"); - qprintf("%5d brushes", hl_numbrushes = 0); -#endif //HL_PRINT - //create the brushes - brushlist = HL_CreateBrushes_r(brush, hl_dmodels[modelnum].headnode[0]); - // -#ifdef HL_PRINT - qprintf("\n"); -#endif //HL_PRINT - //now we've got a list with brushes! - return brushlist; -} //end of the function HL_CreateBrushesFromBSP -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bspbrush_t *HL_MergeBrushes(bspbrush_t *brushlist, int modelnum) -{ - int nummerges, merged; - bspbrush_t *b1, *b2, *tail, *newbrush, *newbrushlist; - bspbrush_t *lastb2; - - if (!brushlist) return NULL; - - if (!modelnum) qprintf("%5d brushes merged", nummerges = 0); - do - { - for (tail = brushlist; tail; tail = tail->next) - { - if (!tail->next) break; - } //end for - merged = 0; - newbrushlist = NULL; - for (b1 = brushlist; b1; b1 = brushlist) - { - lastb2 = b1; - for (b2 = b1->next; b2; b2 = b2->next) - { - //can't merge brushes with different contents - if (b1->side != b2->side) newbrush = NULL; - else newbrush = TryMergeBrushes(b1, b2); - //if a merged brush is created - if (newbrush) - { - //copy the brush contents - newbrush->side = b1->side; - //add the new brush to the end of the list - tail->next = newbrush; - //remove the second brush from the list - lastb2->next = b2->next; - //remove the first brush from the list - brushlist = brushlist->next; - //free the merged brushes - FreeBrush(b1); - FreeBrush(b2); - //get a new tail brush - for (tail = brushlist; tail; tail = tail->next) - { - if (!tail->next) break; - } //end for - merged++; - if (!modelnum) qprintf("\r%5d", nummerges++); - break; - } //end if - lastb2 = b2; - } //end for - //if b1 can't be merged with any of the other brushes - if (!b2) - { - brushlist = brushlist->next; - //keep b1 - b1->next = newbrushlist; - newbrushlist = b1; - } //end else - } //end for - brushlist = newbrushlist; - } while(merged); - if (!modelnum) qprintf("\n"); - return newbrushlist; -} //end of the function HL_MergeBrushes -//=========================================================================== -// returns the amount the face and the winding have overlap -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -float HL_FaceOnWinding(hl_dface_t *face, winding_t *winding) -{ - int i, edgenum, side; - float dist, area; - hl_dplane_t plane; - vec_t *v1, *v2; - vec3_t normal, edgevec; - winding_t *w; - - // - w = CopyWinding(winding); - memcpy(&plane, &hl_dplanes[face->planenum], sizeof(hl_dplane_t)); - //check on which side of the plane the face is - if (face->side) - { - VectorNegate(plane.normal, plane.normal); - plane.dist = -plane.dist; - } //end if - for (i = 0; i < face->numedges && w; i++) - { - //get the first and second vertex of the edge - edgenum = hl_dsurfedges[face->firstedge + i]; - side = edgenum > 0; - //if the face plane is flipped - v1 = hl_dvertexes[hl_dedges[abs(edgenum)].v[side]].point; - v2 = hl_dvertexes[hl_dedges[abs(edgenum)].v[!side]].point; - //create a plane through the edge vector, orthogonal to the face plane - //and with the normal vector pointing out of the face - VectorSubtract(v1, v2, edgevec); - CrossProduct(edgevec, plane.normal, normal); - VectorNormalize(normal); - dist = DotProduct(normal, v1); - // - ChopWindingInPlace(&w, normal, dist, 0.9); //CLIP_EPSILON - } //end for - if (w) - { - area = WindingArea(w); - FreeWinding(w); - return area; - } //end if - return 0; -} //end of the function HL_FaceOnWinding -//=========================================================================== -// returns a list with brushes created by splitting the given brush with -// planes that go through the face edges and are orthogonal to the face plane -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bspbrush_t *HL_SplitBrushWithFace(bspbrush_t *brush, hl_dface_t *face) -{ - int i, edgenum, side, planenum, splits; - float dist; - hl_dplane_t plane; - vec_t *v1, *v2; - vec3_t normal, edgevec; - bspbrush_t *front, *back, *brushlist; - - memcpy(&plane, &hl_dplanes[face->planenum], sizeof(hl_dplane_t)); - //check on which side of the plane the face is - if (face->side) - { - VectorNegate(plane.normal, plane.normal); - plane.dist = -plane.dist; - } //end if - splits = 0; - brushlist = NULL; - for (i = 0; i < face->numedges; i++) - { - //get the first and second vertex of the edge - edgenum = hl_dsurfedges[face->firstedge + i]; - side = edgenum > 0; - //if the face plane is flipped - v1 = hl_dvertexes[hl_dedges[abs(edgenum)].v[side]].point; - v2 = hl_dvertexes[hl_dedges[abs(edgenum)].v[!side]].point; - //create a plane through the edge vector, orthogonal to the face plane - //and with the normal vector pointing out of the face - VectorSubtract(v1, v2, edgevec); - CrossProduct(edgevec, plane.normal, normal); - VectorNormalize(normal); - dist = DotProduct(normal, v1); - // - planenum = FindFloatPlane(normal, dist); - //split the current brush - SplitBrush(brush, planenum, &front, &back); - //if there is a back brush just put it in the list - if (back) - { - //copy the brush contents - back->side = brush->side; - // - back->next = brushlist; - brushlist = back; - splits++; - } //end if - if (!front) - { - Log_Print("HL_SplitBrushWithFace: no new brush\n"); - FreeBrushList(brushlist); - return NULL; - } //end if - //copy the brush contents - front->side = brush->side; - //continue splitting the front brush - brush = front; - } //end for - if (!splits) - { - FreeBrush(front); - return NULL; - } //end if - front->next = brushlist; - brushlist = front; - return brushlist; -} //end of the function HL_SplitBrushWithFace -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bspbrush_t *HL_TextureBrushes(bspbrush_t *brushlist, int modelnum) -{ - float area, largestarea; - int i, n, texinfonum, sn, numbrushes, ofs; - int bestfacenum, sidenodenum; - side_t *side; - hl_dmiptexlump_t *miptexlump; - hl_miptex_t *miptex; - bspbrush_t *brush, *nextbrush, *prevbrush, *newbrushes, *brushlistend; - vec_t defaultvec[4] = {1, 0, 0, 0}; - - if (!modelnum) qprintf("texturing brushes\n"); - if (!modelnum) qprintf("%5d brushes", numbrushes = 0); - //get a pointer to the last brush in the list - for (brushlistend = brushlist; brushlistend; brushlistend = brushlistend->next) - { - if (!brushlistend->next) break; - } //end for - //there's no previous brush when at the start of the list - prevbrush = NULL; - //go over the brush list - for (brush = brushlist; brush; brush = nextbrush) - { - nextbrush = brush->next; - //find a texinfo for every brush side - for (sn = 0; sn < brush->numsides; sn++) - { - side = &brush->sides[sn]; - // - if (side->flags & SFL_TEXTURED) continue; - //number of the node that created this brush side - sidenodenum = side->surf; //see midwinding in HL_SplitBrush - //no face found yet - bestfacenum = -1; - //minimum face size - largestarea = 1; - //if optimizing the texture placement and not going for the - //least number of brushes - if (!lessbrushes) - { - for (i = 0; i < hl_numfaces; i++) - { - //the face must be in the same plane as the node plane that created - //this brush side - if (hl_dfaces[i].planenum == hl_dnodes[sidenodenum].planenum) - { - //get the area the face and the brush side overlap - area = HL_FaceOnWinding(&hl_dfaces[i], side->winding); - //if this face overlaps the brush side winding more than previous faces - if (area > largestarea) - { - //if there already was a face for texturing this brush side with - //a different texture - if (bestfacenum >= 0 && - (hl_dfaces[bestfacenum].texinfo != hl_dfaces[i].texinfo)) - { - //split the brush to fit the texture - newbrushes = HL_SplitBrushWithFace(brush, &hl_dfaces[i]); - //if new brushes where created - if (newbrushes) - { - //remove the current brush from the list - if (prevbrush) prevbrush->next = brush->next; - else brushlist = brush->next; - if (brushlistend == brush) - { - brushlistend = prevbrush; - nextbrush = newbrushes; - } //end if - //add the new brushes to the end of the list - if (brushlistend) brushlistend->next = newbrushes; - else brushlist = newbrushes; - //free the current brush - FreeBrush(brush); - //don't forget about the prevbrush pointer at the bottom of - //the outer loop - brush = prevbrush; - //find the end of the list - for (brushlistend = brushlist; brushlistend; brushlistend = brushlistend->next) - { - if (!brushlistend->next) break; - } //end for - break; - } //end if - else - { - Log_Write("brush %d: no real texture split", numbrushes); - } //end else - } //end if - else - { - //best face for texturing this brush side - bestfacenum = i; - } //end else - } //end if - } //end if - } //end for - //if the brush was split the original brush is removed - //and we just continue with the next one in the list - if (i < hl_numfaces) break; - } //end if - else - { - //find the face with the largest overlap with this brush side - //for texturing the brush side - for (i = 0; i < hl_numfaces; i++) - { - //the face must be in the same plane as the node plane that created - //this brush side - if (hl_dfaces[i].planenum == hl_dnodes[sidenodenum].planenum) - { - //get the area the face and the brush side overlap - area = HL_FaceOnWinding(&hl_dfaces[i], side->winding); - //if this face overlaps the brush side winding more than previous faces - if (area > largestarea) - { - largestarea = area; - bestfacenum = i; - } //end if - } //end if - } //end for - } //end else - //if a face was found for texturing this brush side - if (bestfacenum >= 0) - { - //set the MAP texinfo values - texinfonum = hl_dfaces[bestfacenum].texinfo; - for (n = 0; n < 4; n++) - { - map_texinfo[texinfonum].vecs[0][n] = hl_texinfo[texinfonum].vecs[0][n]; - map_texinfo[texinfonum].vecs[1][n] = hl_texinfo[texinfonum].vecs[1][n]; - } //end for - //make sure the two vectors aren't of zero length otherwise use the default - //vector to prevent a divide by zero in the map writing - if (VectorLength(map_texinfo[texinfonum].vecs[0]) < 0.01) - memcpy(map_texinfo[texinfonum].vecs[0], defaultvec, sizeof(defaultvec)); - if (VectorLength(map_texinfo[texinfonum].vecs[1]) < 0.01) - memcpy(map_texinfo[texinfonum].vecs[1], defaultvec, sizeof(defaultvec)); - // - map_texinfo[texinfonum].flags = hl_texinfo[texinfonum].flags; - map_texinfo[texinfonum].value = 0; //HL_ and HL texinfos don't have a value - //the mip texture - miptexlump = (hl_dmiptexlump_t *) hl_dtexdata; - ofs = miptexlump->dataofs[hl_texinfo[texinfonum].miptex]; - if ( ofs > hl_texdatasize ) { - ofs = miptexlump->dataofs[0]; - } - miptex = (hl_miptex_t *)((byte *)miptexlump + ofs ); - //get the mip texture name - strcpy(map_texinfo[texinfonum].texture, miptex->name); - //no animations in Quake1 and Half-Life mip textures - map_texinfo[texinfonum].nexttexinfo = -1; - //store the texinfo number - side->texinfo = texinfonum; - // - if (texinfonum > map_numtexinfo) map_numtexinfo = texinfonum; - //this side is textured - side->flags |= SFL_TEXTURED; - } //end if - else - { - //no texture for this side - side->texinfo = TEXINFO_NODE; - //this side is textured - side->flags |= SFL_TEXTURED; - } //end if - } //end for - // - if (!modelnum && prevbrush != brush) qprintf("\r%5d", ++numbrushes); - //previous brush in the list - prevbrush = brush; - } //end for - if (!modelnum) qprintf("\n"); - //return the new list with brushes - return brushlist; -} //end of the function HL_TextureBrushes -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void HL_FixContentsTextures(bspbrush_t *brushlist) -{ - int i, texinfonum; - bspbrush_t *brush; - - for (brush = brushlist; brush; brush = brush->next) - { - //only fix the textures of water, slime and lava brushes - if (brush->side != CONTENTS_WATER && - brush->side != CONTENTS_SLIME && - brush->side != CONTENTS_LAVA) continue; - // - for (i = 0; i < brush->numsides; i++) - { - texinfonum = brush->sides[i].texinfo; - if (HL_TextureContents(map_texinfo[texinfonum].texture) == brush->side) break; - } //end for - //if no specific contents texture was found - if (i >= brush->numsides) - { - texinfonum = -1; - for (i = 0; i < map_numtexinfo; i++) - { - if (HL_TextureContents(map_texinfo[i].texture) == brush->side) - { - texinfonum = i; - break; - } //end if - } //end for - } //end if - // - if (texinfonum >= 0) - { - //give all the brush sides this contents texture - for (i = 0; i < brush->numsides; i++) - { - brush->sides[i].texinfo = texinfonum; - } //end for - } //end if - else Log_Print("brush contents %d with wrong textures\n", brush->side); - // - } //end for - /* - for (brush = brushlist; brush; brush = brush->next) - { - //give all the brush sides this contents texture - for (i = 0; i < brush->numsides; i++) - { - if (HL_TextureContents(map_texinfo[texinfonum].texture) != brush->side) - { - Error("brush contents %d with wrong contents textures %s\n", brush->side, - HL_TextureContents(map_texinfo[texinfonum].texture)); - } //end if - } //end for - } //end for*/ -} //end of the function HL_FixContentsTextures -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void HL_BSPBrushToMapBrush(bspbrush_t *bspbrush, entity_t *mapent) -{ - mapbrush_t *mapbrush; - side_t *side; - int i, besttexinfo; - - if (nummapbrushes >= MAX_MAPFILE_BRUSHES) - Error ("nummapbrushes == MAX_MAPFILE_BRUSHES"); - - mapbrush = &mapbrushes[nummapbrushes]; - mapbrush->original_sides = &brushsides[nummapbrushsides]; - mapbrush->entitynum = mapent - entities; - mapbrush->brushnum = nummapbrushes - mapent->firstbrush; - mapbrush->leafnum = -1; - mapbrush->numsides = 0; - - besttexinfo = TEXINFO_NODE; - for (i = 0; i < bspbrush->numsides; i++) - { - if (!bspbrush->sides[i].winding) continue; - // - if (nummapbrushsides >= MAX_MAPFILE_BRUSHSIDES) - Error ("MAX_MAPFILE_BRUSHSIDES"); - side = &brushsides[nummapbrushsides]; - //the contents of the bsp brush is stored in the side variable - side->contents = bspbrush->side; - side->surf = 0; - side->planenum = bspbrush->sides[i].planenum; - side->texinfo = bspbrush->sides[i].texinfo; - if (side->texinfo != TEXINFO_NODE) - { - //this brush side is textured - side->flags |= SFL_TEXTURED; - besttexinfo = side->texinfo; - } //end if - // - nummapbrushsides++; - mapbrush->numsides++; - } //end for - // - if (besttexinfo == TEXINFO_NODE) - { - mapbrush->numsides = 0; - hl_numclipbrushes++; - return; - } //end if - //set the texinfo for all the brush sides without texture - for (i = 0; i < mapbrush->numsides; i++) - { - if (mapbrush->original_sides[i].texinfo == TEXINFO_NODE) - { - mapbrush->original_sides[i].texinfo = besttexinfo; - } //end if - } //end for - //contents of the brush - mapbrush->contents = bspbrush->side; - // - if (create_aas) - { - //create the AAS brushes from this brush, add brush bevels - AAS_CreateMapBrushes(mapbrush, mapent, true); - return; - } //end if - //create windings for sides and bounds for brush - MakeBrushWindings(mapbrush); - //add brush bevels - AddBrushBevels(mapbrush); - //a new brush has been created - nummapbrushes++; - mapent->numbrushes++; -} //end of the function HL_BSPBrushToMapBrush -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void HL_CreateMapBrushes(entity_t *mapent, int modelnum) -{ - bspbrush_t *brushlist, *brush, *nextbrush; - int i; - - //create brushes from the model BSP tree - brushlist = HL_CreateBrushesFromBSP(modelnum); - //texture the brushes and split them when necesary - brushlist = HL_TextureBrushes(brushlist, modelnum); - //fix the contents textures of all brushes - HL_FixContentsTextures(brushlist); - // - if (!nobrushmerge) - { - brushlist = HL_MergeBrushes(brushlist, modelnum); - //brushlist = HL_MergeBrushes(brushlist, modelnum); - } //end if - // - if (!modelnum) qprintf("converting brushes to map brushes\n"); - if (!modelnum) qprintf("%5d brushes", i = 0); - for (brush = brushlist; brush; brush = nextbrush) - { - nextbrush = brush->next; - HL_BSPBrushToMapBrush(brush, mapent); - brush->next = NULL; - FreeBrush(brush); - if (!modelnum) qprintf("\r%5d", ++i); - } //end for - if (!modelnum) qprintf("\n"); -} //end of the function HL_CreateMapBrushes -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void HL_ResetMapLoading(void) -{ -} //end of the function HL_ResetMapLoading -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void HL_LoadMapFromBSP(char *filename, int offset, int length) -{ - int i, modelnum; - char *model, *classname; - - Log_Print("-- HL_LoadMapFromBSP --\n"); - //loaded map type - loadedmaptype = MAPTYPE_HALFLIFE; - // - qprintf("loading map from %s at %d\n", filename, offset); - //load the Half-Life BSP file - HL_LoadBSPFile(filename, offset, length); - // - hl_numclipbrushes = 0; - //parse the entities from the BSP - HL_ParseEntities(); - //clear the map mins and maxs - ClearBounds(map_mins, map_maxs); - // - qprintf("creating Half-Life brushes\n"); - if (lessbrushes) qprintf("creating minimum number of brushes\n"); - else qprintf("placing textures correctly\n"); - // - for (i = 0; i < num_entities; i++) - { - entities[i].firstbrush = nummapbrushes; - entities[i].numbrushes = 0; - // - classname = ValueForKey(&entities[i], "classname"); - if (classname && !strcmp(classname, "worldspawn")) - { - modelnum = 0; - } //end if - else - { - // - model = ValueForKey(&entities[i], "model"); - if (!model || *model != '*') continue; - model++; - modelnum = atoi(model); - } //end else - //create map brushes for the entity - HL_CreateMapBrushes(&entities[i], modelnum); - } //end for - // - qprintf("%5d map brushes\n", nummapbrushes); - qprintf("%5d clip brushes\n", hl_numclipbrushes); -} //end of the function HL_LoadMapFromBSP +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "qbsp.h" +#include "l_bsp_hl.h" +#include "aas_map.h" //AAS_CreateMapBrushes + +int hl_numbrushes; +int hl_numclipbrushes; + +//#define HL_PRINT +#define HLCONTENTS + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int HL_TextureContents(char *name) +{ + if (!Q_strncasecmp (name, "sky",3)) + return CONTENTS_SOLID; + + if (!Q_strncasecmp(name+1,"!lava",5)) + return CONTENTS_LAVA; + + if (!Q_strncasecmp(name+1,"!slime",6)) + return CONTENTS_SLIME; + + /* + if (!Q_strncasecmp (name, "!cur_90",7)) + return CONTENTS_CURRENT_90; + if (!Q_strncasecmp (name, "!cur_0",6)) + return CONTENTS_CURRENT_0; + if (!Q_strncasecmp (name, "!cur_270",8)) + return CONTENTS_CURRENT_270; + if (!Q_strncasecmp (name, "!cur_180",8)) + return CONTENTS_CURRENT_180; + if (!Q_strncasecmp (name, "!cur_up",7)) + return CONTENTS_CURRENT_UP; + if (!Q_strncasecmp (name, "!cur_dwn",8)) + return CONTENTS_CURRENT_DOWN; + //*/ + if (name[0] == '!') + return CONTENTS_WATER; + /* + if (!Q_strncasecmp (name, "origin",6)) + return CONTENTS_ORIGIN; + if (!Q_strncasecmp (name, "clip",4)) + return CONTENTS_CLIP; + if( !Q_strncasecmp( name, "translucent", 11 ) ) + return CONTENTS_TRANSLUCENT; + if( name[0] == '@' ) + return CONTENTS_TRANSLUCENT; + //*/ + + return CONTENTS_SOLID; +} //end of the function HL_TextureContents +//=========================================================================== +// Generates two new brushes, leaving the original +// unchanged +// +// modified for Half-Life because there are quite a lot of tiny node leaves +// in the Half-Life bsps +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void HL_SplitBrush(bspbrush_t *brush, int planenum, int nodenum, + bspbrush_t **front, bspbrush_t **back) +{ + bspbrush_t *b[2]; + int i, j; + winding_t *w, *cw[2], *midwinding; + plane_t *plane, *plane2; + side_t *s, *cs; + float d, d_front, d_back; + + *front = *back = NULL; + plane = &mapplanes[planenum]; + + // check all points + d_front = d_back = 0; + for (i=0 ; inumsides ; i++) + { + w = brush->sides[i].winding; + if (!w) + continue; + for (j=0 ; jnumpoints ; j++) + { + d = DotProduct (w->p[j], plane->normal) - plane->dist; + if (d > 0 && d > d_front) + d_front = d; + if (d < 0 && d < d_back) + d_back = d; + } //end for + } //end for + + if (d_front < 0.1) // PLANESIDE_EPSILON) + { // only on back + *back = CopyBrush (brush); + Log_Print("HL_SplitBrush: only on back\n"); + return; + } //end if + if (d_back > -0.1) // PLANESIDE_EPSILON) + { // only on front + *front = CopyBrush (brush); + Log_Print("HL_SplitBrush: only on front\n"); + return; + } //end if + + // create a new winding from the split plane + + w = BaseWindingForPlane (plane->normal, plane->dist); + for (i = 0; i < brush->numsides && w; i++) + { + plane2 = &mapplanes[brush->sides[i].planenum ^ 1]; + ChopWindingInPlace(&w, plane2->normal, plane2->dist, 0); // PLANESIDE_EPSILON); + } //end for + + if (!w || WindingIsTiny(w)) + { // the brush isn't really split + int side; + + Log_Print("HL_SplitBrush: no split winding\n"); + side = BrushMostlyOnSide (brush, plane); + if (side == PSIDE_FRONT) + *front = CopyBrush (brush); + if (side == PSIDE_BACK) + *back = CopyBrush (brush); + return; + } + + if (WindingIsHuge(w)) + { + Log_Print("HL_SplitBrush: WARNING huge split winding\n"); + } //end of + + midwinding = w; + + // split it for real + + for (i = 0; i < 2; i++) + { + b[i] = AllocBrush (brush->numsides+1); + b[i]->original = brush->original; + } //end for + + // split all the current windings + + for (i=0 ; inumsides ; i++) + { + s = &brush->sides[i]; + w = s->winding; + if (!w) + continue; + ClipWindingEpsilon (w, plane->normal, plane->dist, + 0 /*PLANESIDE_EPSILON*/, &cw[0], &cw[1]); + for (j=0 ; j<2 ; j++) + { + if (!cw[j]) + continue; +#if 0 + if (WindingIsTiny (cw[j])) + { + FreeWinding (cw[j]); + continue; + } +#endif + cs = &b[j]->sides[b[j]->numsides]; + b[j]->numsides++; + *cs = *s; +// cs->planenum = s->planenum; +// cs->texinfo = s->texinfo; +// cs->visible = s->visible; +// cs->original = s->original; + cs->winding = cw[j]; + cs->flags &= ~SFL_TESTED; + } //end for + } //end for + + + // see if we have valid polygons on both sides + + for (i=0 ; i<2 ; i++) + { + BoundBrush (b[i]); + for (j=0 ; j<3 ; j++) + { + if (b[i]->mins[j] < -4096 || b[i]->maxs[j] > 4096) + { + Log_Print("HL_SplitBrush: bogus brush after clip\n"); + break; + } //end if + } //end for + + if (b[i]->numsides < 3 || j < 3) + { + FreeBrush (b[i]); + b[i] = NULL; + Log_Print("HL_SplitBrush: numsides < 3\n"); + } //end if + } //end for + + if ( !(b[0] && b[1]) ) + { + if (!b[0] && !b[1]) + Log_Print("HL_SplitBrush: split removed brush\n"); + else + Log_Print("HL_SplitBrush: split not on both sides\n"); + if (b[0]) + { + FreeBrush (b[0]); + *front = CopyBrush (brush); + } //end if + if (b[1]) + { + FreeBrush (b[1]); + *back = CopyBrush (brush); + } //end if + return; + } //end if + + // add the midwinding to both sides + for (i = 0; i < 2; i++) + { + cs = &b[i]->sides[b[i]->numsides]; + b[i]->numsides++; + + cs->planenum = planenum^i^1; + cs->texinfo = 0; + //store the node number in the surf to find the texinfo later on + cs->surf = nodenum; + // + cs->flags &= ~SFL_VISIBLE; + cs->flags &= ~SFL_TESTED; + if (i==0) + cs->winding = CopyWinding (midwinding); + else + cs->winding = midwinding; + } //end for + + +{ + vec_t v1; + int i; + + for (i=0 ; i<2 ; i++) + { + v1 = BrushVolume (b[i]); + if (v1 < 1) + { + FreeBrush (b[i]); + b[i] = NULL; + Log_Print("HL_SplitBrush: tiny volume after clip\n"); + } //end if + } //end for +} //*/ + + *front = b[0]; + *back = b[1]; +} //end of the function HL_SplitBrush +//=========================================================================== +// returns true if the tree starting at nodenum has only solid leaves +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int HL_SolidTree_r(int nodenum) +{ + if (nodenum < 0) + { + switch(hl_dleafs[(-nodenum) - 1].contents) + { + case HL_CONTENTS_EMPTY: + { + return false; + } //end case + case HL_CONTENTS_SOLID: +#ifdef HLCONTENTS + case HL_CONTENTS_CLIP: +#endif //HLCONTENTS + case HL_CONTENTS_SKY: +#ifdef HLCONTENTS + case HL_CONTENTS_TRANSLUCENT: +#endif //HLCONTENTS + { + return true; + } //end case + case HL_CONTENTS_WATER: + case HL_CONTENTS_SLIME: + case HL_CONTENTS_LAVA: +#ifdef HLCONTENTS + //these contents should not be found in the BSP + case HL_CONTENTS_ORIGIN: + case HL_CONTENTS_CURRENT_0: + case HL_CONTENTS_CURRENT_90: + case HL_CONTENTS_CURRENT_180: + case HL_CONTENTS_CURRENT_270: + case HL_CONTENTS_CURRENT_UP: + case HL_CONTENTS_CURRENT_DOWN: +#endif //HLCONTENTS + default: + { + return false; + } //end default + } //end switch + return false; + } //end if + if (!HL_SolidTree_r(hl_dnodes[nodenum].children[0])) return false; + if (!HL_SolidTree_r(hl_dnodes[nodenum].children[1])) return false; + return true; +} //end of the function HL_SolidTree_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *HL_CreateBrushes_r(bspbrush_t *brush, int nodenum) +{ + int planenum; + bspbrush_t *front, *back; + hl_dleaf_t *leaf; + + //if it is a leaf + if (nodenum < 0) + { + leaf = &hl_dleafs[(-nodenum) - 1]; + if (leaf->contents != HL_CONTENTS_EMPTY) + { +#ifdef HL_PRINT + qprintf("\r%5i", ++hl_numbrushes); +#endif //HL_PRINT + } //end if + switch(leaf->contents) + { + case HL_CONTENTS_EMPTY: + { + FreeBrush(brush); + return NULL; + } //end case + case HL_CONTENTS_SOLID: +#ifdef HLCONTENTS + case HL_CONTENTS_CLIP: +#endif //HLCONTENTS + case HL_CONTENTS_SKY: +#ifdef HLCONTENTS + case HL_CONTENTS_TRANSLUCENT: +#endif //HLCONTENTS + { + brush->side = CONTENTS_SOLID; + return brush; + } //end case + case HL_CONTENTS_WATER: + { + brush->side = CONTENTS_WATER; + return brush; + } //end case + case HL_CONTENTS_SLIME: + { + brush->side = CONTENTS_SLIME; + return brush; + } //end case + case HL_CONTENTS_LAVA: + { + brush->side = CONTENTS_LAVA; + return brush; + } //end case +#ifdef HLCONTENTS + //these contents should not be found in the BSP + case HL_CONTENTS_ORIGIN: + case HL_CONTENTS_CURRENT_0: + case HL_CONTENTS_CURRENT_90: + case HL_CONTENTS_CURRENT_180: + case HL_CONTENTS_CURRENT_270: + case HL_CONTENTS_CURRENT_UP: + case HL_CONTENTS_CURRENT_DOWN: + { + Error("HL_CreateBrushes_r: found contents %d in Half-Life BSP", leaf->contents); + return NULL; + } //end case +#endif //HLCONTENTS + default: + { + Error("HL_CreateBrushes_r: unknown contents %d in Half-Life BSP", leaf->contents); + return NULL; + } //end default + } //end switch + return NULL; + } //end if + //if the rest of the tree is solid + /*if (HL_SolidTree_r(nodenum)) + { + brush->side = CONTENTS_SOLID; + return brush; + } //end if*/ + // + planenum = hl_dnodes[nodenum].planenum; + planenum = FindFloatPlane(hl_dplanes[planenum].normal, hl_dplanes[planenum].dist); + //split the brush with the node plane + HL_SplitBrush(brush, planenum, nodenum, &front, &back); + //free the original brush + FreeBrush(brush); + //every node must split the brush in two + if (!front || !back) + { + Log_Print("HL_CreateBrushes_r: WARNING node not splitting brush\n"); + //return NULL; + } //end if + //create brushes recursively + if (front) front = HL_CreateBrushes_r(front, hl_dnodes[nodenum].children[0]); + if (back) back = HL_CreateBrushes_r(back, hl_dnodes[nodenum].children[1]); + //link the brushes if possible and return them + if (front) + { + for (brush = front; brush->next; brush = brush->next); + brush->next = back; + return front; + } //end if + else + { + return back; + } //end else +} //end of the function HL_CreateBrushes_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *HL_CreateBrushesFromBSP(int modelnum) +{ + bspbrush_t *brushlist; + bspbrush_t *brush; + hl_dnode_t *headnode; + vec3_t mins, maxs; + int i; + + // + headnode = &hl_dnodes[hl_dmodels[modelnum].headnode[0]]; + //get the mins and maxs of the world + VectorCopy(headnode->mins, mins); + VectorCopy(headnode->maxs, maxs); + //enlarge these mins and maxs + for (i = 0; i < 3; i++) + { + mins[i] -= 8; + maxs[i] += 8; + } //end for + //NOTE: have to add the BSP tree mins and maxs to the MAP mins and maxs + AddPointToBounds(mins, map_mins, map_maxs); + AddPointToBounds(maxs, map_mins, map_maxs); + // + if (!modelnum) + { + Log_Print("brush size: %5.0f,%5.0f,%5.0f to %5.0f,%5.0f,%5.0f\n", + map_mins[0], map_mins[1], map_mins[2], + map_maxs[0], map_maxs[1], map_maxs[2]); + } //end if + //create one huge brush containing the whole world + brush = BrushFromBounds(mins, maxs); + VectorCopy(mins, brush->mins); + VectorCopy(maxs, brush->maxs); + // +#ifdef HL_PRINT + qprintf("creating Half-Life brushes\n"); + qprintf("%5d brushes", hl_numbrushes = 0); +#endif //HL_PRINT + //create the brushes + brushlist = HL_CreateBrushes_r(brush, hl_dmodels[modelnum].headnode[0]); + // +#ifdef HL_PRINT + qprintf("\n"); +#endif //HL_PRINT + //now we've got a list with brushes! + return brushlist; +} //end of the function HL_CreateBrushesFromBSP +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *HL_MergeBrushes(bspbrush_t *brushlist, int modelnum) +{ + int nummerges, merged; + bspbrush_t *b1, *b2, *tail, *newbrush, *newbrushlist; + bspbrush_t *lastb2; + + if (!brushlist) return NULL; + + if (!modelnum) qprintf("%5d brushes merged", nummerges = 0); + do + { + for (tail = brushlist; tail; tail = tail->next) + { + if (!tail->next) break; + } //end for + merged = 0; + newbrushlist = NULL; + for (b1 = brushlist; b1; b1 = brushlist) + { + lastb2 = b1; + for (b2 = b1->next; b2; b2 = b2->next) + { + //can't merge brushes with different contents + if (b1->side != b2->side) newbrush = NULL; + else newbrush = TryMergeBrushes(b1, b2); + //if a merged brush is created + if (newbrush) + { + //copy the brush contents + newbrush->side = b1->side; + //add the new brush to the end of the list + tail->next = newbrush; + //remove the second brush from the list + lastb2->next = b2->next; + //remove the first brush from the list + brushlist = brushlist->next; + //free the merged brushes + FreeBrush(b1); + FreeBrush(b2); + //get a new tail brush + for (tail = brushlist; tail; tail = tail->next) + { + if (!tail->next) break; + } //end for + merged++; + if (!modelnum) qprintf("\r%5d", nummerges++); + break; + } //end if + lastb2 = b2; + } //end for + //if b1 can't be merged with any of the other brushes + if (!b2) + { + brushlist = brushlist->next; + //keep b1 + b1->next = newbrushlist; + newbrushlist = b1; + } //end else + } //end for + brushlist = newbrushlist; + } while(merged); + if (!modelnum) qprintf("\n"); + return newbrushlist; +} //end of the function HL_MergeBrushes +//=========================================================================== +// returns the amount the face and the winding have overlap +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float HL_FaceOnWinding(hl_dface_t *face, winding_t *winding) +{ + int i, edgenum, side; + float dist, area; + hl_dplane_t plane; + vec_t *v1, *v2; + vec3_t normal, edgevec; + winding_t *w; + + // + w = CopyWinding(winding); + memcpy(&plane, &hl_dplanes[face->planenum], sizeof(hl_dplane_t)); + //check on which side of the plane the face is + if (face->side) + { + VectorNegate(plane.normal, plane.normal); + plane.dist = -plane.dist; + } //end if + for (i = 0; i < face->numedges && w; i++) + { + //get the first and second vertex of the edge + edgenum = hl_dsurfedges[face->firstedge + i]; + side = edgenum > 0; + //if the face plane is flipped + v1 = hl_dvertexes[hl_dedges[abs(edgenum)].v[side]].point; + v2 = hl_dvertexes[hl_dedges[abs(edgenum)].v[!side]].point; + //create a plane through the edge vector, orthogonal to the face plane + //and with the normal vector pointing out of the face + VectorSubtract(v1, v2, edgevec); + CrossProduct(edgevec, plane.normal, normal); + VectorNormalize(normal); + dist = DotProduct(normal, v1); + // + ChopWindingInPlace(&w, normal, dist, 0.9); //CLIP_EPSILON + } //end for + if (w) + { + area = WindingArea(w); + FreeWinding(w); + return area; + } //end if + return 0; +} //end of the function HL_FaceOnWinding +//=========================================================================== +// returns a list with brushes created by splitting the given brush with +// planes that go through the face edges and are orthogonal to the face plane +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *HL_SplitBrushWithFace(bspbrush_t *brush, hl_dface_t *face) +{ + int i, edgenum, side, planenum, splits; + float dist; + hl_dplane_t plane; + vec_t *v1, *v2; + vec3_t normal, edgevec; + bspbrush_t *front, *back, *brushlist; + + memcpy(&plane, &hl_dplanes[face->planenum], sizeof(hl_dplane_t)); + //check on which side of the plane the face is + if (face->side) + { + VectorNegate(plane.normal, plane.normal); + plane.dist = -plane.dist; + } //end if + splits = 0; + brushlist = NULL; + for (i = 0; i < face->numedges; i++) + { + //get the first and second vertex of the edge + edgenum = hl_dsurfedges[face->firstedge + i]; + side = edgenum > 0; + //if the face plane is flipped + v1 = hl_dvertexes[hl_dedges[abs(edgenum)].v[side]].point; + v2 = hl_dvertexes[hl_dedges[abs(edgenum)].v[!side]].point; + //create a plane through the edge vector, orthogonal to the face plane + //and with the normal vector pointing out of the face + VectorSubtract(v1, v2, edgevec); + CrossProduct(edgevec, plane.normal, normal); + VectorNormalize(normal); + dist = DotProduct(normal, v1); + // + planenum = FindFloatPlane(normal, dist); + //split the current brush + SplitBrush(brush, planenum, &front, &back); + //if there is a back brush just put it in the list + if (back) + { + //copy the brush contents + back->side = brush->side; + // + back->next = brushlist; + brushlist = back; + splits++; + } //end if + if (!front) + { + Log_Print("HL_SplitBrushWithFace: no new brush\n"); + FreeBrushList(brushlist); + return NULL; + } //end if + //copy the brush contents + front->side = brush->side; + //continue splitting the front brush + brush = front; + } //end for + if (!splits) + { + FreeBrush(front); + return NULL; + } //end if + front->next = brushlist; + brushlist = front; + return brushlist; +} //end of the function HL_SplitBrushWithFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *HL_TextureBrushes(bspbrush_t *brushlist, int modelnum) +{ + float area, largestarea; + int i, n, texinfonum, sn, numbrushes, ofs; + int bestfacenum, sidenodenum; + side_t *side; + hl_dmiptexlump_t *miptexlump; + hl_miptex_t *miptex; + bspbrush_t *brush, *nextbrush, *prevbrush, *newbrushes, *brushlistend; + vec_t defaultvec[4] = {1, 0, 0, 0}; + + if (!modelnum) qprintf("texturing brushes\n"); + if (!modelnum) qprintf("%5d brushes", numbrushes = 0); + //get a pointer to the last brush in the list + for (brushlistend = brushlist; brushlistend; brushlistend = brushlistend->next) + { + if (!brushlistend->next) break; + } //end for + //there's no previous brush when at the start of the list + prevbrush = NULL; + //go over the brush list + for (brush = brushlist; brush; brush = nextbrush) + { + nextbrush = brush->next; + //find a texinfo for every brush side + for (sn = 0; sn < brush->numsides; sn++) + { + side = &brush->sides[sn]; + // + if (side->flags & SFL_TEXTURED) continue; + //number of the node that created this brush side + sidenodenum = side->surf; //see midwinding in HL_SplitBrush + //no face found yet + bestfacenum = -1; + //minimum face size + largestarea = 1; + //if optimizing the texture placement and not going for the + //least number of brushes + if (!lessbrushes) + { + for (i = 0; i < hl_numfaces; i++) + { + //the face must be in the same plane as the node plane that created + //this brush side + if (hl_dfaces[i].planenum == hl_dnodes[sidenodenum].planenum) + { + //get the area the face and the brush side overlap + area = HL_FaceOnWinding(&hl_dfaces[i], side->winding); + //if this face overlaps the brush side winding more than previous faces + if (area > largestarea) + { + //if there already was a face for texturing this brush side with + //a different texture + if (bestfacenum >= 0 && + (hl_dfaces[bestfacenum].texinfo != hl_dfaces[i].texinfo)) + { + //split the brush to fit the texture + newbrushes = HL_SplitBrushWithFace(brush, &hl_dfaces[i]); + //if new brushes where created + if (newbrushes) + { + //remove the current brush from the list + if (prevbrush) prevbrush->next = brush->next; + else brushlist = brush->next; + if (brushlistend == brush) + { + brushlistend = prevbrush; + nextbrush = newbrushes; + } //end if + //add the new brushes to the end of the list + if (brushlistend) brushlistend->next = newbrushes; + else brushlist = newbrushes; + //free the current brush + FreeBrush(brush); + //don't forget about the prevbrush pointer at the bottom of + //the outer loop + brush = prevbrush; + //find the end of the list + for (brushlistend = brushlist; brushlistend; brushlistend = brushlistend->next) + { + if (!brushlistend->next) break; + } //end for + break; + } //end if + else + { + Log_Write("brush %d: no real texture split", numbrushes); + } //end else + } //end if + else + { + //best face for texturing this brush side + bestfacenum = i; + } //end else + } //end if + } //end if + } //end for + //if the brush was split the original brush is removed + //and we just continue with the next one in the list + if (i < hl_numfaces) break; + } //end if + else + { + //find the face with the largest overlap with this brush side + //for texturing the brush side + for (i = 0; i < hl_numfaces; i++) + { + //the face must be in the same plane as the node plane that created + //this brush side + if (hl_dfaces[i].planenum == hl_dnodes[sidenodenum].planenum) + { + //get the area the face and the brush side overlap + area = HL_FaceOnWinding(&hl_dfaces[i], side->winding); + //if this face overlaps the brush side winding more than previous faces + if (area > largestarea) + { + largestarea = area; + bestfacenum = i; + } //end if + } //end if + } //end for + } //end else + //if a face was found for texturing this brush side + if (bestfacenum >= 0) + { + //set the MAP texinfo values + texinfonum = hl_dfaces[bestfacenum].texinfo; + for (n = 0; n < 4; n++) + { + map_texinfo[texinfonum].vecs[0][n] = hl_texinfo[texinfonum].vecs[0][n]; + map_texinfo[texinfonum].vecs[1][n] = hl_texinfo[texinfonum].vecs[1][n]; + } //end for + //make sure the two vectors aren't of zero length otherwise use the default + //vector to prevent a divide by zero in the map writing + if (VectorLength(map_texinfo[texinfonum].vecs[0]) < 0.01) + memcpy(map_texinfo[texinfonum].vecs[0], defaultvec, sizeof(defaultvec)); + if (VectorLength(map_texinfo[texinfonum].vecs[1]) < 0.01) + memcpy(map_texinfo[texinfonum].vecs[1], defaultvec, sizeof(defaultvec)); + // + map_texinfo[texinfonum].flags = hl_texinfo[texinfonum].flags; + map_texinfo[texinfonum].value = 0; //HL_ and HL texinfos don't have a value + //the mip texture + miptexlump = (hl_dmiptexlump_t *) hl_dtexdata; + ofs = miptexlump->dataofs[hl_texinfo[texinfonum].miptex]; + if ( ofs > hl_texdatasize ) { + ofs = miptexlump->dataofs[0]; + } + miptex = (hl_miptex_t *)((byte *)miptexlump + ofs ); + //get the mip texture name + strcpy(map_texinfo[texinfonum].texture, miptex->name); + //no animations in Quake1 and Half-Life mip textures + map_texinfo[texinfonum].nexttexinfo = -1; + //store the texinfo number + side->texinfo = texinfonum; + // + if (texinfonum > map_numtexinfo) map_numtexinfo = texinfonum; + //this side is textured + side->flags |= SFL_TEXTURED; + } //end if + else + { + //no texture for this side + side->texinfo = TEXINFO_NODE; + //this side is textured + side->flags |= SFL_TEXTURED; + } //end if + } //end for + // + if (!modelnum && prevbrush != brush) qprintf("\r%5d", ++numbrushes); + //previous brush in the list + prevbrush = brush; + } //end for + if (!modelnum) qprintf("\n"); + //return the new list with brushes + return brushlist; +} //end of the function HL_TextureBrushes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void HL_FixContentsTextures(bspbrush_t *brushlist) +{ + int i, texinfonum; + bspbrush_t *brush; + + for (brush = brushlist; brush; brush = brush->next) + { + //only fix the textures of water, slime and lava brushes + if (brush->side != CONTENTS_WATER && + brush->side != CONTENTS_SLIME && + brush->side != CONTENTS_LAVA) continue; + // + for (i = 0; i < brush->numsides; i++) + { + texinfonum = brush->sides[i].texinfo; + if (HL_TextureContents(map_texinfo[texinfonum].texture) == brush->side) break; + } //end for + //if no specific contents texture was found + if (i >= brush->numsides) + { + texinfonum = -1; + for (i = 0; i < map_numtexinfo; i++) + { + if (HL_TextureContents(map_texinfo[i].texture) == brush->side) + { + texinfonum = i; + break; + } //end if + } //end for + } //end if + // + if (texinfonum >= 0) + { + //give all the brush sides this contents texture + for (i = 0; i < brush->numsides; i++) + { + brush->sides[i].texinfo = texinfonum; + } //end for + } //end if + else Log_Print("brush contents %d with wrong textures\n", brush->side); + // + } //end for + /* + for (brush = brushlist; brush; brush = brush->next) + { + //give all the brush sides this contents texture + for (i = 0; i < brush->numsides; i++) + { + if (HL_TextureContents(map_texinfo[texinfonum].texture) != brush->side) + { + Error("brush contents %d with wrong contents textures %s\n", brush->side, + HL_TextureContents(map_texinfo[texinfonum].texture)); + } //end if + } //end for + } //end for*/ +} //end of the function HL_FixContentsTextures +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void HL_BSPBrushToMapBrush(bspbrush_t *bspbrush, entity_t *mapent) +{ + mapbrush_t *mapbrush; + side_t *side; + int i, besttexinfo; + + if (nummapbrushes >= MAX_MAPFILE_BRUSHES) + Error ("nummapbrushes == MAX_MAPFILE_BRUSHES"); + + mapbrush = &mapbrushes[nummapbrushes]; + mapbrush->original_sides = &brushsides[nummapbrushsides]; + mapbrush->entitynum = mapent - entities; + mapbrush->brushnum = nummapbrushes - mapent->firstbrush; + mapbrush->leafnum = -1; + mapbrush->numsides = 0; + + besttexinfo = TEXINFO_NODE; + for (i = 0; i < bspbrush->numsides; i++) + { + if (!bspbrush->sides[i].winding) continue; + // + if (nummapbrushsides >= MAX_MAPFILE_BRUSHSIDES) + Error ("MAX_MAPFILE_BRUSHSIDES"); + side = &brushsides[nummapbrushsides]; + //the contents of the bsp brush is stored in the side variable + side->contents = bspbrush->side; + side->surf = 0; + side->planenum = bspbrush->sides[i].planenum; + side->texinfo = bspbrush->sides[i].texinfo; + if (side->texinfo != TEXINFO_NODE) + { + //this brush side is textured + side->flags |= SFL_TEXTURED; + besttexinfo = side->texinfo; + } //end if + // + nummapbrushsides++; + mapbrush->numsides++; + } //end for + // + if (besttexinfo == TEXINFO_NODE) + { + mapbrush->numsides = 0; + hl_numclipbrushes++; + return; + } //end if + //set the texinfo for all the brush sides without texture + for (i = 0; i < mapbrush->numsides; i++) + { + if (mapbrush->original_sides[i].texinfo == TEXINFO_NODE) + { + mapbrush->original_sides[i].texinfo = besttexinfo; + } //end if + } //end for + //contents of the brush + mapbrush->contents = bspbrush->side; + // + if (create_aas) + { + //create the AAS brushes from this brush, add brush bevels + AAS_CreateMapBrushes(mapbrush, mapent, true); + return; + } //end if + //create windings for sides and bounds for brush + MakeBrushWindings(mapbrush); + //add brush bevels + AddBrushBevels(mapbrush); + //a new brush has been created + nummapbrushes++; + mapent->numbrushes++; +} //end of the function HL_BSPBrushToMapBrush +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void HL_CreateMapBrushes(entity_t *mapent, int modelnum) +{ + bspbrush_t *brushlist, *brush, *nextbrush; + int i; + + //create brushes from the model BSP tree + brushlist = HL_CreateBrushesFromBSP(modelnum); + //texture the brushes and split them when necesary + brushlist = HL_TextureBrushes(brushlist, modelnum); + //fix the contents textures of all brushes + HL_FixContentsTextures(brushlist); + // + if (!nobrushmerge) + { + brushlist = HL_MergeBrushes(brushlist, modelnum); + //brushlist = HL_MergeBrushes(brushlist, modelnum); + } //end if + // + if (!modelnum) qprintf("converting brushes to map brushes\n"); + if (!modelnum) qprintf("%5d brushes", i = 0); + for (brush = brushlist; brush; brush = nextbrush) + { + nextbrush = brush->next; + HL_BSPBrushToMapBrush(brush, mapent); + brush->next = NULL; + FreeBrush(brush); + if (!modelnum) qprintf("\r%5d", ++i); + } //end for + if (!modelnum) qprintf("\n"); +} //end of the function HL_CreateMapBrushes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void HL_ResetMapLoading(void) +{ +} //end of the function HL_ResetMapLoading +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void HL_LoadMapFromBSP(char *filename, int offset, int length) +{ + int i, modelnum; + char *model, *classname; + + Log_Print("-- HL_LoadMapFromBSP --\n"); + //loaded map type + loadedmaptype = MAPTYPE_HALFLIFE; + // + qprintf("loading map from %s at %d\n", filename, offset); + //load the Half-Life BSP file + HL_LoadBSPFile(filename, offset, length); + // + hl_numclipbrushes = 0; + //parse the entities from the BSP + HL_ParseEntities(); + //clear the map mins and maxs + ClearBounds(map_mins, map_maxs); + // + qprintf("creating Half-Life brushes\n"); + if (lessbrushes) qprintf("creating minimum number of brushes\n"); + else qprintf("placing textures correctly\n"); + // + for (i = 0; i < num_entities; i++) + { + entities[i].firstbrush = nummapbrushes; + entities[i].numbrushes = 0; + // + classname = ValueForKey(&entities[i], "classname"); + if (classname && !strcmp(classname, "worldspawn")) + { + modelnum = 0; + } //end if + else + { + // + model = ValueForKey(&entities[i], "model"); + if (!model || *model != '*') continue; + model++; + modelnum = atoi(model); + } //end else + //create map brushes for the entity + HL_CreateMapBrushes(&entities[i], modelnum); + } //end for + // + qprintf("%5d map brushes\n", nummapbrushes); + qprintf("%5d clip brushes\n", hl_numclipbrushes); +} //end of the function HL_LoadMapFromBSP diff --git a/code/bspc/map_q1.c b/code/bspc/map_q1.c index 772a358..fa9d117 100755 --- a/code/bspc/map_q1.c +++ b/code/bspc/map_q1.c @@ -1,1174 +1,1174 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "qbsp.h" -#include "l_bsp_q1.h" -#include "aas_map.h" //AAS_CreateMapBrushes - -int q1_numbrushes; -int q1_numclipbrushes; - -//#define Q1_PRINT - -//=========================================================================== -// water, slime and lava brush textures names always start with a * -// followed by the type: "slime", "lava" or otherwise water -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int Q1_TextureContents(char *name) -{ - if (!Q_strcasecmp(name, "clip")) return CONTENTS_SOLID; - if (name[0] == '*') - { - if (!Q_strncasecmp(name+1,"lava",4)) return CONTENTS_LAVA; - else if (!Q_strncasecmp(name+1,"slime",5)) return CONTENTS_SLIME; - else return CONTENTS_WATER; - } //end if - else if (!Q_strncasecmp(name, "sky", 3)) return CONTENTS_SOLID; - else return CONTENTS_SOLID; -} //end of the function Q1_TextureContents -//=========================================================================== -// Generates two new brushes, leaving the original -// unchanged -// -// modified for Half-Life because there are quite a lot of tiny node leaves -// in the Half-Life bsps -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Q1_SplitBrush(bspbrush_t *brush, int planenum, int nodenum, - bspbrush_t **front, bspbrush_t **back) -{ - bspbrush_t *b[2]; - int i, j; - winding_t *w, *cw[2], *midwinding; - plane_t *plane, *plane2; - side_t *s, *cs; - float d, d_front, d_back; - - *front = *back = NULL; - plane = &mapplanes[planenum]; - - // check all points - d_front = d_back = 0; - for (i=0 ; inumsides ; i++) - { - w = brush->sides[i].winding; - if (!w) - continue; - for (j=0 ; jnumpoints ; j++) - { - d = DotProduct (w->p[j], plane->normal) - plane->dist; - if (d > 0 && d > d_front) - d_front = d; - if (d < 0 && d < d_back) - d_back = d; - } //end for - } //end for - - if (d_front < 0.1) // PLANESIDE_EPSILON) - { // only on back - *back = CopyBrush (brush); - Log_Print("Q1_SplitBrush: only on back\n"); - return; - } //end if - if (d_back > -0.1) // PLANESIDE_EPSILON) - { // only on front - *front = CopyBrush (brush); - Log_Print("Q1_SplitBrush: only on front\n"); - return; - } //end if - - // create a new winding from the split plane - - w = BaseWindingForPlane (plane->normal, plane->dist); - for (i = 0; i < brush->numsides && w; i++) - { - plane2 = &mapplanes[brush->sides[i].planenum ^ 1]; - ChopWindingInPlace(&w, plane2->normal, plane2->dist, 0); // PLANESIDE_EPSILON); - } //end for - - if (!w || WindingIsTiny(w)) - { // the brush isn't really split - int side; - - Log_Print("Q1_SplitBrush: no split winding\n"); - side = BrushMostlyOnSide (brush, plane); - if (side == PSIDE_FRONT) - *front = CopyBrush (brush); - if (side == PSIDE_BACK) - *back = CopyBrush (brush); - return; - } - - if (WindingIsHuge(w)) - { - Log_Print("Q1_SplitBrush: WARNING huge split winding\n"); - } //end of - - midwinding = w; - - // split it for real - - for (i = 0; i < 2; i++) - { - b[i] = AllocBrush (brush->numsides+1); - b[i]->original = brush->original; - } //end for - - // split all the current windings - - for (i=0 ; inumsides ; i++) - { - s = &brush->sides[i]; - w = s->winding; - if (!w) - continue; - ClipWindingEpsilon (w, plane->normal, plane->dist, - 0 /*PLANESIDE_EPSILON*/, &cw[0], &cw[1]); - for (j=0 ; j<2 ; j++) - { - if (!cw[j]) - continue; -#if 0 - if (WindingIsTiny (cw[j])) - { - FreeWinding (cw[j]); - continue; - } -#endif - cs = &b[j]->sides[b[j]->numsides]; - b[j]->numsides++; - *cs = *s; -// cs->planenum = s->planenum; -// cs->texinfo = s->texinfo; -// cs->visible = s->visible; -// cs->original = s->original; - cs->winding = cw[j]; - cs->flags &= ~SFL_TESTED; - } //end for - } //end for - - - // see if we have valid polygons on both sides - - for (i=0 ; i<2 ; i++) - { - BoundBrush (b[i]); - for (j=0 ; j<3 ; j++) - { - if (b[i]->mins[j] < -4096 || b[i]->maxs[j] > 4096) - { - Log_Print("Q1_SplitBrush: bogus brush after clip\n"); - break; - } //end if - } //end for - - if (b[i]->numsides < 3 || j < 3) - { - FreeBrush (b[i]); - b[i] = NULL; - Log_Print("Q1_SplitBrush: numsides < 3\n"); - } //end if - } //end for - - if ( !(b[0] && b[1]) ) - { - if (!b[0] && !b[1]) - Log_Print("Q1_SplitBrush: split removed brush\n"); - else - Log_Print("Q1_SplitBrush: split not on both sides\n"); - if (b[0]) - { - FreeBrush (b[0]); - *front = CopyBrush (brush); - } //end if - if (b[1]) - { - FreeBrush (b[1]); - *back = CopyBrush (brush); - } //end if - return; - } //end if - - // add the midwinding to both sides - for (i = 0; i < 2; i++) - { - cs = &b[i]->sides[b[i]->numsides]; - b[i]->numsides++; - - cs->planenum = planenum^i^1; - cs->texinfo = 0; - //store the node number in the surf to find the texinfo later on - cs->surf = nodenum; - // - cs->flags &= ~SFL_VISIBLE; - cs->flags &= ~SFL_TESTED; - cs->flags &= ~SFL_TEXTURED; - if (i==0) - cs->winding = CopyWinding (midwinding); - else - cs->winding = midwinding; - } //end for - - -{ - vec_t v1; - int i; - - for (i=0 ; i<2 ; i++) - { - v1 = BrushVolume (b[i]); - if (v1 < 1) - { - FreeBrush (b[i]); - b[i] = NULL; - Log_Print("Q1_SplitBrush: tiny volume after clip\n"); - } //end if - } //end for -} //*/ - - *front = b[0]; - *back = b[1]; -} //end of the function Q1_SplitBrush -//=========================================================================== -// returns true if the tree starting at nodenum has only solid leaves -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int Q1_SolidTree_r(int nodenum) -{ - if (nodenum < 0) - { - switch(q1_dleafs[(-nodenum) - 1].contents) - { - case Q1_CONTENTS_EMPTY: - { - return false; - } //end case - case Q1_CONTENTS_SOLID: -#ifdef HLCONTENTS - case Q1_CONTENTS_CLIP: -#endif HLCONTENTS - case Q1_CONTENTS_SKY: -#ifdef HLCONTENTS - case Q1_CONTENTS_TRANSLUCENT: -#endif HLCONTENTS - { - return true; - } //end case - case Q1_CONTENTS_WATER: - case Q1_CONTENTS_SLIME: - case Q1_CONTENTS_LAVA: -#ifdef HLCONTENTS - //these contents should not be found in the BSP - case Q1_CONTENTS_ORIGIN: - case Q1_CONTENTS_CURRENT_0: - case Q1_CONTENTS_CURRENT_90: - case Q1_CONTENTS_CURRENT_180: - case Q1_CONTENTS_CURRENT_270: - case Q1_CONTENTS_CURRENT_UP: - case Q1_CONTENTS_CURRENT_DOWN: -#endif HLCONTENTS - default: - { - return false; - } //end default - } //end switch - return false; - } //end if - if (!Q1_SolidTree_r(q1_dnodes[nodenum].children[0])) return false; - if (!Q1_SolidTree_r(q1_dnodes[nodenum].children[1])) return false; - return true; -} //end of the function Q1_SolidTree_r -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bspbrush_t *Q1_CreateBrushes_r(bspbrush_t *brush, int nodenum) -{ - int planenum; - bspbrush_t *front, *back; - q1_dleaf_t *leaf; - - //if it is a leaf - if (nodenum < 0) - { - leaf = &q1_dleafs[(-nodenum) - 1]; - if (leaf->contents != Q1_CONTENTS_EMPTY) - { -#ifdef Q1_PRINT - qprintf("\r%5i", ++q1_numbrushes); -#endif //Q1_PRINT - } //end if - switch(leaf->contents) - { - case Q1_CONTENTS_EMPTY: - { - FreeBrush(brush); - return NULL; - } //end case - case Q1_CONTENTS_SOLID: -#ifdef HLCONTENTS - case Q1_CONTENTS_CLIP: -#endif HLCONTENTS - case Q1_CONTENTS_SKY: -#ifdef HLCONTENTS - case Q1_CONTENTS_TRANSLUCENT: -#endif HLCONTENTS - { - brush->side = CONTENTS_SOLID; - return brush; - } //end case - case Q1_CONTENTS_WATER: - { - brush->side = CONTENTS_WATER; - return brush; - } //end case - case Q1_CONTENTS_SLIME: - { - brush->side = CONTENTS_SLIME; - return brush; - } //end case - case Q1_CONTENTS_LAVA: - { - brush->side = CONTENTS_LAVA; - return brush; - } //end case -#ifdef HLCONTENTS - //these contents should not be found in the BSP - case Q1_CONTENTS_ORIGIN: - case Q1_CONTENTS_CURRENT_0: - case Q1_CONTENTS_CURRENT_90: - case Q1_CONTENTS_CURRENT_180: - case Q1_CONTENTS_CURRENT_270: - case Q1_CONTENTS_CURRENT_UP: - case Q1_CONTENTS_CURRENT_DOWN: - { - Error("Q1_CreateBrushes_r: found contents %d in Half-Life BSP", leaf->contents); - return NULL; - } //end case -#endif HLCONTENTS - default: - { - Error("Q1_CreateBrushes_r: unknown contents %d in Half-Life BSP", leaf->contents); - return NULL; - } //end default - } //end switch - return NULL; - } //end if - //if the rest of the tree is solid - /*if (Q1_SolidTree_r(nodenum)) - { - brush->side = CONTENTS_SOLID; - return brush; - } //end if*/ - // - planenum = q1_dnodes[nodenum].planenum; - planenum = FindFloatPlane(q1_dplanes[planenum].normal, q1_dplanes[planenum].dist); - //split the brush with the node plane - Q1_SplitBrush(brush, planenum, nodenum, &front, &back); - //free the original brush - FreeBrush(brush); - //every node must split the brush in two - if (!front || !back) - { - Log_Print("Q1_CreateBrushes_r: WARNING node not splitting brush\n"); - //return NULL; - } //end if - //create brushes recursively - if (front) front = Q1_CreateBrushes_r(front, q1_dnodes[nodenum].children[0]); - if (back) back = Q1_CreateBrushes_r(back, q1_dnodes[nodenum].children[1]); - //link the brushes if possible and return them - if (front) - { - for (brush = front; brush->next; brush = brush->next); - brush->next = back; - return front; - } //end if - else - { - return back; - } //end else -} //end of the function Q1_CreateBrushes_r -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bspbrush_t *Q1_CreateBrushesFromBSP(int modelnum) -{ - bspbrush_t *brushlist; - bspbrush_t *brush; - q1_dnode_t *headnode; - vec3_t mins, maxs; - int i; - - // - headnode = &q1_dnodes[q1_dmodels[modelnum].headnode[0]]; - //get the mins and maxs of the world - VectorCopy(headnode->mins, mins); - VectorCopy(headnode->maxs, maxs); - //enlarge these mins and maxs - for (i = 0; i < 3; i++) - { - mins[i] -= 8; - maxs[i] += 8; - } //end for - //NOTE: have to add the BSP tree mins and maxs to the MAP mins and maxs - AddPointToBounds(mins, map_mins, map_maxs); - AddPointToBounds(maxs, map_mins, map_maxs); - // - if (!modelnum) - { - Log_Print("brush size: %5.0f,%5.0f,%5.0f to %5.0f,%5.0f,%5.0f\n", - map_mins[0], map_mins[1], map_mins[2], - map_maxs[0], map_maxs[1], map_maxs[2]); - } //end if - //create one huge brush containing the whole world - brush = BrushFromBounds(mins, maxs); - VectorCopy(mins, brush->mins); - VectorCopy(maxs, brush->maxs); - // -#ifdef Q1_PRINT - qprintf("creating Quake brushes\n"); - qprintf("%5d brushes", q1_numbrushes = 0); -#endif //Q1_PRINT - //create the brushes - brushlist = Q1_CreateBrushes_r(brush, q1_dmodels[modelnum].headnode[0]); - // -#ifdef Q1_PRINT - qprintf("\n"); -#endif //Q1_PRINT - //now we've got a list with brushes! - return brushlist; -} //end of the function Q1_CreateBrushesFromBSP -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -q1_dleaf_t *Q1_PointInLeaf(int startnode, vec3_t point) -{ - int nodenum; - vec_t dist; - q1_dnode_t *node; - q1_dplane_t *plane; - - nodenum = startnode; - while (nodenum >= 0) - { - node = &q1_dnodes[nodenum]; - plane = &q1_dplanes[node->planenum]; - dist = DotProduct(point, plane->normal) - plane->dist; - if (dist > 0) - nodenum = node->children[0]; - else - nodenum = node->children[1]; - } //end while - - return &q1_dleafs[-nodenum - 1]; -} //end of the function Q1_PointInLeaf -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -float Q1_FaceArea(q1_dface_t *face) -{ - int i; - float total; - vec_t *v; - vec3_t d1, d2, cross; - q1_dedge_t *edge; - - edge = &q1_dedges[face->firstedge]; - v = q1_dvertexes[edge->v[0]].point; - - total = 0; - for (i = 1; i < face->numedges - 1; i++) - { - edge = &q1_dedges[face->firstedge + i]; - VectorSubtract(q1_dvertexes[edge->v[0]].point, v, d1); - VectorSubtract(q1_dvertexes[edge->v[1]].point, v, d2); - CrossProduct(d1, d2, cross); - total += 0.5 * VectorLength(cross); - } //end for - return total; -} //end of the function AAS_FaceArea -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Q1_FacePlane(q1_dface_t *face, vec3_t normal, float *dist) -{ - vec_t *v1, *v2, *v3; - vec3_t vec1, vec2; - int side, edgenum; - - edgenum = q1_dsurfedges[face->firstedge]; - side = edgenum < 0; - v1 = q1_dvertexes[q1_dedges[abs(edgenum)].v[side]].point; - v2 = q1_dvertexes[q1_dedges[abs(edgenum)].v[!side]].point; - edgenum = q1_dsurfedges[face->firstedge+1]; - side = edgenum < 0; - v3 = q1_dvertexes[q1_dedges[abs(edgenum)].v[!side]].point; - // - VectorSubtract(v2, v1, vec1); - VectorSubtract(v3, v1, vec2); - - CrossProduct(vec1, vec2, normal); - VectorNormalize(normal); - *dist = DotProduct(v1, normal); -} //end of the function Q1_FacePlane -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bspbrush_t *Q1_MergeBrushes(bspbrush_t *brushlist, int modelnum) -{ - int nummerges, merged; - bspbrush_t *b1, *b2, *tail, *newbrush, *newbrushlist; - bspbrush_t *lastb2; - - if (!brushlist) return NULL; - - if (!modelnum) qprintf("%5d brushes merged", nummerges = 0); - do - { - for (tail = brushlist; tail; tail = tail->next) - { - if (!tail->next) break; - } //end for - merged = 0; - newbrushlist = NULL; - for (b1 = brushlist; b1; b1 = brushlist) - { - lastb2 = b1; - for (b2 = b1->next; b2; b2 = b2->next) - { - //can't merge brushes with different contents - if (b1->side != b2->side) newbrush = NULL; - else newbrush = TryMergeBrushes(b1, b2); - //if a merged brush is created - if (newbrush) - { - //copy the brush contents - newbrush->side = b1->side; - //add the new brush to the end of the list - tail->next = newbrush; - //remove the second brush from the list - lastb2->next = b2->next; - //remove the first brush from the list - brushlist = brushlist->next; - //free the merged brushes - FreeBrush(b1); - FreeBrush(b2); - //get a new tail brush - for (tail = brushlist; tail; tail = tail->next) - { - if (!tail->next) break; - } //end for - merged++; - if (!modelnum) qprintf("\r%5d", nummerges++); - break; - } //end if - lastb2 = b2; - } //end for - //if b1 can't be merged with any of the other brushes - if (!b2) - { - brushlist = brushlist->next; - //keep b1 - b1->next = newbrushlist; - newbrushlist = b1; - } //end else - } //end for - brushlist = newbrushlist; - } while(merged); - if (!modelnum) qprintf("\n"); - return newbrushlist; -} //end of the function Q1_MergeBrushes -//=========================================================================== -// returns the amount the face and the winding overlap -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -float Q1_FaceOnWinding(q1_dface_t *face, winding_t *winding) -{ - int i, edgenum, side; - float dist, area; - q1_dplane_t plane; - vec_t *v1, *v2; - vec3_t normal, edgevec; - winding_t *w; - - // - w = CopyWinding(winding); - memcpy(&plane, &q1_dplanes[face->planenum], sizeof(q1_dplane_t)); - //check on which side of the plane the face is - if (face->side) - { - VectorNegate(plane.normal, plane.normal); - plane.dist = -plane.dist; - } //end if - for (i = 0; i < face->numedges && w; i++) - { - //get the first and second vertex of the edge - edgenum = q1_dsurfedges[face->firstedge + i]; - side = edgenum > 0; - //if the face plane is flipped - v1 = q1_dvertexes[q1_dedges[abs(edgenum)].v[side]].point; - v2 = q1_dvertexes[q1_dedges[abs(edgenum)].v[!side]].point; - //create a plane through the edge vector, orthogonal to the face plane - //and with the normal vector pointing out of the face - VectorSubtract(v1, v2, edgevec); - CrossProduct(edgevec, plane.normal, normal); - VectorNormalize(normal); - dist = DotProduct(normal, v1); - // - ChopWindingInPlace(&w, normal, dist, 0.9); //CLIP_EPSILON - } //end for - if (w) - { - area = WindingArea(w); - FreeWinding(w); - return area; - } //end if - return 0; -} //end of the function Q1_FaceOnWinding -//=========================================================================== -// returns a list with brushes created by splitting the given brush with -// planes that go through the face edges and are orthogonal to the face plane -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bspbrush_t *Q1_SplitBrushWithFace(bspbrush_t *brush, q1_dface_t *face) -{ - int i, edgenum, side, planenum, splits; - float dist; - q1_dplane_t plane; - vec_t *v1, *v2; - vec3_t normal, edgevec; - bspbrush_t *front, *back, *brushlist; - - memcpy(&plane, &q1_dplanes[face->planenum], sizeof(q1_dplane_t)); - //check on which side of the plane the face is - if (face->side) - { - VectorNegate(plane.normal, plane.normal); - plane.dist = -plane.dist; - } //end if - splits = 0; - brushlist = NULL; - for (i = 0; i < face->numedges; i++) - { - //get the first and second vertex of the edge - edgenum = q1_dsurfedges[face->firstedge + i]; - side = edgenum > 0; - //if the face plane is flipped - v1 = q1_dvertexes[q1_dedges[abs(edgenum)].v[side]].point; - v2 = q1_dvertexes[q1_dedges[abs(edgenum)].v[!side]].point; - //create a plane through the edge vector, orthogonal to the face plane - //and with the normal vector pointing out of the face - VectorSubtract(v1, v2, edgevec); - CrossProduct(edgevec, plane.normal, normal); - VectorNormalize(normal); - dist = DotProduct(normal, v1); - // - planenum = FindFloatPlane(normal, dist); - //split the current brush - SplitBrush(brush, planenum, &front, &back); - //if there is a back brush just put it in the list - if (back) - { - //copy the brush contents - back->side = brush->side; - // - back->next = brushlist; - brushlist = back; - splits++; - } //end if - if (!front) - { - Log_Print("Q1_SplitBrushWithFace: no new brush\n"); - FreeBrushList(brushlist); - return NULL; - } //end if - //copy the brush contents - front->side = brush->side; - //continue splitting the front brush - brush = front; - } //end for - if (!splits) - { - FreeBrush(front); - return NULL; - } //end if - front->next = brushlist; - brushlist = front; - return brushlist; -} //end of the function Q1_SplitBrushWithFace -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -bspbrush_t *Q1_TextureBrushes(bspbrush_t *brushlist, int modelnum) -{ - float area, largestarea; - int i, n, texinfonum, sn, numbrushes, ofs; - int bestfacenum, sidenodenum; - side_t *side; - q1_dmiptexlump_t *miptexlump; - q1_miptex_t *miptex; - bspbrush_t *brush, *nextbrush, *prevbrush, *newbrushes, *brushlistend; - vec_t defaultvec[4] = {1, 0, 0, 0}; - - if (!modelnum) qprintf("texturing brushes\n"); - if (!modelnum) qprintf("%5d brushes", numbrushes = 0); - //get a pointer to the last brush in the list - for (brushlistend = brushlist; brushlistend; brushlistend = brushlistend->next) - { - if (!brushlistend->next) break; - } //end for - //there's no previous brush when at the start of the list - prevbrush = NULL; - //go over the brush list - for (brush = brushlist; brush; brush = nextbrush) - { - nextbrush = brush->next; - //find a texinfo for every brush side - for (sn = 0; sn < brush->numsides; sn++) - { - side = &brush->sides[sn]; - // - if (side->flags & SFL_TEXTURED) continue; - //number of the node that created this brush side - sidenodenum = side->surf; //see midwinding in Q1_SplitBrush - //no face found yet - bestfacenum = -1; - //minimum face size - largestarea = 1; - //if optimizing the texture placement and not going for the - //least number of brushes - if (!lessbrushes) - { - for (i = 0; i < q1_numfaces; i++) - { - //the face must be in the same plane as the node plane that created - //this brush side - if (q1_dfaces[i].planenum == q1_dnodes[sidenodenum].planenum) - { - //get the area the face and the brush side overlap - area = Q1_FaceOnWinding(&q1_dfaces[i], side->winding); - //if this face overlaps the brush side winding more than previous faces - if (area > largestarea) - { - //if there already was a face for texturing this brush side with - //a different texture - if (bestfacenum >= 0 && - (q1_dfaces[bestfacenum].texinfo != q1_dfaces[i].texinfo)) - { - //split the brush to fit the texture - newbrushes = Q1_SplitBrushWithFace(brush, &q1_dfaces[i]); - //if new brushes where created - if (newbrushes) - { - //remove the current brush from the list - if (prevbrush) prevbrush->next = brush->next; - else brushlist = brush->next; - if (brushlistend == brush) - { - brushlistend = prevbrush; - nextbrush = newbrushes; - } //end if - //add the new brushes to the end of the list - if (brushlistend) brushlistend->next = newbrushes; - else brushlist = newbrushes; - //free the current brush - FreeBrush(brush); - //don't forget about the prevbrush pointer at the bottom of - //the outer loop - brush = prevbrush; - //find the end of the list - for (brushlistend = brushlist; brushlistend; brushlistend = brushlistend->next) - { - if (!brushlistend->next) break; - } //end for - break; - } //end if - else - { - Log_Write("brush %d: no real texture split", numbrushes); - } //end else - } //end if - else - { - //best face for texturing this brush side - bestfacenum = i; - } //end else - } //end if - } //end if - } //end for - //if the brush was split the original brush is removed - //and we just continue with the next one in the list - if (i < q1_numfaces) break; - } //end if - else - { - //find the face with the largest overlap with this brush side - //for texturing the brush side - for (i = 0; i < q1_numfaces; i++) - { - //the face must be in the same plane as the node plane that created - //this brush side - if (q1_dfaces[i].planenum == q1_dnodes[sidenodenum].planenum) - { - //get the area the face and the brush side overlap - area = Q1_FaceOnWinding(&q1_dfaces[i], side->winding); - //if this face overlaps the brush side winding more than previous faces - if (area > largestarea) - { - largestarea = area; - bestfacenum = i; - } //end if - } //end if - } //end for - } //end else - //if a face was found for texturing this brush side - if (bestfacenum >= 0) - { - //set the MAP texinfo values - texinfonum = q1_dfaces[bestfacenum].texinfo; - for (n = 0; n < 4; n++) - { - map_texinfo[texinfonum].vecs[0][n] = q1_texinfo[texinfonum].vecs[0][n]; - map_texinfo[texinfonum].vecs[1][n] = q1_texinfo[texinfonum].vecs[1][n]; - } //end for - //make sure the two vectors aren't of zero length otherwise use the default - //vector to prevent a divide by zero in the map writing - if (VectorLength(map_texinfo[texinfonum].vecs[0]) < 0.01) - memcpy(map_texinfo[texinfonum].vecs[0], defaultvec, sizeof(defaultvec)); - if (VectorLength(map_texinfo[texinfonum].vecs[1]) < 0.01) - memcpy(map_texinfo[texinfonum].vecs[1], defaultvec, sizeof(defaultvec)); - // - map_texinfo[texinfonum].flags = q1_texinfo[texinfonum].flags; - map_texinfo[texinfonum].value = 0; //Q1 and HL texinfos don't have a value - //the mip texture - miptexlump = (q1_dmiptexlump_t *) q1_dtexdata; - ofs = miptexlump->dataofs[q1_texinfo[texinfonum].miptex]; - if ( ofs > q1_texdatasize ) { - ofs = miptexlump->dataofs[0]; - } - miptex = (q1_miptex_t *)((byte *)miptexlump + ofs); - //get the mip texture name - strcpy(map_texinfo[texinfonum].texture, miptex->name); - //no animations in Quake1 and Half-Life mip textures - map_texinfo[texinfonum].nexttexinfo = -1; - //store the texinfo number - side->texinfo = texinfonum; - // - if (texinfonum > map_numtexinfo) map_numtexinfo = texinfonum; - //this side is textured - side->flags |= SFL_TEXTURED; - } //end if - else - { - //no texture for this side - side->texinfo = TEXINFO_NODE; - //this side is textured - side->flags |= SFL_TEXTURED; - } //end if - } //end for - // - if (!modelnum && prevbrush != brush) qprintf("\r%5d", ++numbrushes); - //previous brush in the list - prevbrush = brush; - } //end for - if (!modelnum) qprintf("\n"); - //return the new list with brushes - return brushlist; -} //end of the function Q1_TextureBrushes -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Q1_FixContentsTextures(bspbrush_t *brushlist) -{ - int i, texinfonum; - bspbrush_t *brush; - - for (brush = brushlist; brush; brush = brush->next) - { - //only fix the textures of water, slime and lava brushes - if (brush->side != CONTENTS_WATER && - brush->side != CONTENTS_SLIME && - brush->side != CONTENTS_LAVA) continue; - // - for (i = 0; i < brush->numsides; i++) - { - texinfonum = brush->sides[i].texinfo; - if (Q1_TextureContents(map_texinfo[texinfonum].texture) == brush->side) break; - } //end for - //if no specific contents texture was found - if (i >= brush->numsides) - { - texinfonum = -1; - for (i = 0; i < map_numtexinfo; i++) - { - if (Q1_TextureContents(map_texinfo[i].texture) == brush->side) - { - texinfonum = i; - break; - } //end if - } //end for - } //end if - // - if (texinfonum >= 0) - { - //give all the brush sides this contents texture - for (i = 0; i < brush->numsides; i++) - { - brush->sides[i].texinfo = texinfonum; - } //end for - } //end if - else Log_Print("brush contents %d with wrong textures\n", brush->side); - // - } //end for - /* - for (brush = brushlist; brush; brush = brush->next) - { - //give all the brush sides this contents texture - for (i = 0; i < brush->numsides; i++) - { - if (Q1_TextureContents(map_texinfo[texinfonum].texture) != brush->side) - { - Error("brush contents %d with wrong contents textures %s\n", brush->side, - Q1_TextureContents(map_texinfo[texinfonum].texture)); - } //end if - } //end for - } //end for*/ -} //end of the function Q1_FixContentsTextures -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Q1_BSPBrushToMapBrush(bspbrush_t *bspbrush, entity_t *mapent) -{ - mapbrush_t *mapbrush; - side_t *side; - int i, besttexinfo; - - CheckBSPBrush(bspbrush); - - if (nummapbrushes >= MAX_MAPFILE_BRUSHES) - Error ("nummapbrushes == MAX_MAPFILE_BRUSHES"); - - mapbrush = &mapbrushes[nummapbrushes]; - mapbrush->original_sides = &brushsides[nummapbrushsides]; - mapbrush->entitynum = mapent - entities; - mapbrush->brushnum = nummapbrushes - mapent->firstbrush; - mapbrush->leafnum = -1; - mapbrush->numsides = 0; - - besttexinfo = TEXINFO_NODE; - for (i = 0; i < bspbrush->numsides; i++) - { - if (!bspbrush->sides[i].winding) continue; - // - if (nummapbrushsides >= MAX_MAPFILE_BRUSHSIDES) - Error ("MAX_MAPFILE_BRUSHSIDES"); - side = &brushsides[nummapbrushsides]; - //the contents of the bsp brush is stored in the side variable - side->contents = bspbrush->side; - side->surf = 0; - side->planenum = bspbrush->sides[i].planenum; - side->texinfo = bspbrush->sides[i].texinfo; - if (side->texinfo != TEXINFO_NODE) - { - //this brush side is textured - side->flags |= SFL_TEXTURED; - besttexinfo = side->texinfo; - } //end if - // - nummapbrushsides++; - mapbrush->numsides++; - } //end for - // - if (besttexinfo == TEXINFO_NODE) - { - mapbrush->numsides = 0; - q1_numclipbrushes++; - return; - } //end if - //set the texinfo for all the brush sides without texture - for (i = 0; i < mapbrush->numsides; i++) - { - if (mapbrush->original_sides[i].texinfo == TEXINFO_NODE) - { - mapbrush->original_sides[i].texinfo = besttexinfo; - } //end if - } //end for - //contents of the brush - mapbrush->contents = bspbrush->side; - // - if (create_aas) - { - //create the AAS brushes from this brush, add brush bevels - AAS_CreateMapBrushes(mapbrush, mapent, true); - return; - } //end if - //create windings for sides and bounds for brush - MakeBrushWindings(mapbrush); - //add brush bevels - AddBrushBevels(mapbrush); - //a new brush has been created - nummapbrushes++; - mapent->numbrushes++; -} //end of the function Q1_BSPBrushToMapBrush -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Q1_CreateMapBrushes(entity_t *mapent, int modelnum) -{ - bspbrush_t *brushlist, *brush, *nextbrush; - int i; - - //create brushes from the model BSP tree - brushlist = Q1_CreateBrushesFromBSP(modelnum); - //texture the brushes and split them when necesary - brushlist = Q1_TextureBrushes(brushlist, modelnum); - //fix the contents textures of all brushes - Q1_FixContentsTextures(brushlist); - // - if (!nobrushmerge) - { - brushlist = Q1_MergeBrushes(brushlist, modelnum); - //brushlist = Q1_MergeBrushes(brushlist, modelnum); - } //end if - // - if (!modelnum) qprintf("converting brushes to map brushes\n"); - if (!modelnum) qprintf("%5d brushes", i = 0); - for (brush = brushlist; brush; brush = nextbrush) - { - nextbrush = brush->next; - Q1_BSPBrushToMapBrush(brush, mapent); - brush->next = NULL; - FreeBrush(brush); - if (!modelnum) qprintf("\r%5d", ++i); - } //end for - if (!modelnum) qprintf("\n"); -} //end of the function Q1_CreateMapBrushes -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Q1_ResetMapLoading(void) -{ -} //end of the function Q1_ResetMapLoading -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Q1_LoadMapFromBSP(char *filename, int offset, int length) -{ - int i, modelnum; - char *model, *classname; - - Log_Print("-- Q1_LoadMapFromBSP --\n"); - //the loaded map type - loadedmaptype = MAPTYPE_QUAKE1; - // - qprintf("loading map from %s at %d\n", filename, offset); - //load the Half-Life BSP file - Q1_LoadBSPFile(filename, offset, length); - // - q1_numclipbrushes = 0; - //CreatePath(path); - //Q1_CreateQ2WALFiles(path); - //parse the entities from the BSP - Q1_ParseEntities(); - //clear the map mins and maxs - ClearBounds(map_mins, map_maxs); - // - qprintf("creating Quake1 brushes\n"); - if (lessbrushes) qprintf("creating minimum number of brushes\n"); - else qprintf("placing textures correctly\n"); - // - for (i = 0; i < num_entities; i++) - { - entities[i].firstbrush = nummapbrushes; - entities[i].numbrushes = 0; - // - classname = ValueForKey(&entities[i], "classname"); - if (classname && !strcmp(classname, "worldspawn")) - { - modelnum = 0; - } //end if - else - { - // - model = ValueForKey(&entities[i], "model"); - if (!model || *model != '*') continue; - model++; - modelnum = atoi(model); - } //end else - //create map brushes for the entity - Q1_CreateMapBrushes(&entities[i], modelnum); - } //end for - // - qprintf("%5d map brushes\n", nummapbrushes); - qprintf("%5d clip brushes\n", q1_numclipbrushes); -} //end of the function Q1_LoadMapFromBSP +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "qbsp.h" +#include "l_bsp_q1.h" +#include "aas_map.h" //AAS_CreateMapBrushes + +int q1_numbrushes; +int q1_numclipbrushes; + +//#define Q1_PRINT + +//=========================================================================== +// water, slime and lava brush textures names always start with a * +// followed by the type: "slime", "lava" or otherwise water +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Q1_TextureContents(char *name) +{ + if (!Q_strcasecmp(name, "clip")) return CONTENTS_SOLID; + if (name[0] == '*') + { + if (!Q_strncasecmp(name+1,"lava",4)) return CONTENTS_LAVA; + else if (!Q_strncasecmp(name+1,"slime",5)) return CONTENTS_SLIME; + else return CONTENTS_WATER; + } //end if + else if (!Q_strncasecmp(name, "sky", 3)) return CONTENTS_SOLID; + else return CONTENTS_SOLID; +} //end of the function Q1_TextureContents +//=========================================================================== +// Generates two new brushes, leaving the original +// unchanged +// +// modified for Half-Life because there are quite a lot of tiny node leaves +// in the Half-Life bsps +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Q1_SplitBrush(bspbrush_t *brush, int planenum, int nodenum, + bspbrush_t **front, bspbrush_t **back) +{ + bspbrush_t *b[2]; + int i, j; + winding_t *w, *cw[2], *midwinding; + plane_t *plane, *plane2; + side_t *s, *cs; + float d, d_front, d_back; + + *front = *back = NULL; + plane = &mapplanes[planenum]; + + // check all points + d_front = d_back = 0; + for (i=0 ; inumsides ; i++) + { + w = brush->sides[i].winding; + if (!w) + continue; + for (j=0 ; jnumpoints ; j++) + { + d = DotProduct (w->p[j], plane->normal) - plane->dist; + if (d > 0 && d > d_front) + d_front = d; + if (d < 0 && d < d_back) + d_back = d; + } //end for + } //end for + + if (d_front < 0.1) // PLANESIDE_EPSILON) + { // only on back + *back = CopyBrush (brush); + Log_Print("Q1_SplitBrush: only on back\n"); + return; + } //end if + if (d_back > -0.1) // PLANESIDE_EPSILON) + { // only on front + *front = CopyBrush (brush); + Log_Print("Q1_SplitBrush: only on front\n"); + return; + } //end if + + // create a new winding from the split plane + + w = BaseWindingForPlane (plane->normal, plane->dist); + for (i = 0; i < brush->numsides && w; i++) + { + plane2 = &mapplanes[brush->sides[i].planenum ^ 1]; + ChopWindingInPlace(&w, plane2->normal, plane2->dist, 0); // PLANESIDE_EPSILON); + } //end for + + if (!w || WindingIsTiny(w)) + { // the brush isn't really split + int side; + + Log_Print("Q1_SplitBrush: no split winding\n"); + side = BrushMostlyOnSide (brush, plane); + if (side == PSIDE_FRONT) + *front = CopyBrush (brush); + if (side == PSIDE_BACK) + *back = CopyBrush (brush); + return; + } + + if (WindingIsHuge(w)) + { + Log_Print("Q1_SplitBrush: WARNING huge split winding\n"); + } //end of + + midwinding = w; + + // split it for real + + for (i = 0; i < 2; i++) + { + b[i] = AllocBrush (brush->numsides+1); + b[i]->original = brush->original; + } //end for + + // split all the current windings + + for (i=0 ; inumsides ; i++) + { + s = &brush->sides[i]; + w = s->winding; + if (!w) + continue; + ClipWindingEpsilon (w, plane->normal, plane->dist, + 0 /*PLANESIDE_EPSILON*/, &cw[0], &cw[1]); + for (j=0 ; j<2 ; j++) + { + if (!cw[j]) + continue; +#if 0 + if (WindingIsTiny (cw[j])) + { + FreeWinding (cw[j]); + continue; + } +#endif + cs = &b[j]->sides[b[j]->numsides]; + b[j]->numsides++; + *cs = *s; +// cs->planenum = s->planenum; +// cs->texinfo = s->texinfo; +// cs->visible = s->visible; +// cs->original = s->original; + cs->winding = cw[j]; + cs->flags &= ~SFL_TESTED; + } //end for + } //end for + + + // see if we have valid polygons on both sides + + for (i=0 ; i<2 ; i++) + { + BoundBrush (b[i]); + for (j=0 ; j<3 ; j++) + { + if (b[i]->mins[j] < -4096 || b[i]->maxs[j] > 4096) + { + Log_Print("Q1_SplitBrush: bogus brush after clip\n"); + break; + } //end if + } //end for + + if (b[i]->numsides < 3 || j < 3) + { + FreeBrush (b[i]); + b[i] = NULL; + Log_Print("Q1_SplitBrush: numsides < 3\n"); + } //end if + } //end for + + if ( !(b[0] && b[1]) ) + { + if (!b[0] && !b[1]) + Log_Print("Q1_SplitBrush: split removed brush\n"); + else + Log_Print("Q1_SplitBrush: split not on both sides\n"); + if (b[0]) + { + FreeBrush (b[0]); + *front = CopyBrush (brush); + } //end if + if (b[1]) + { + FreeBrush (b[1]); + *back = CopyBrush (brush); + } //end if + return; + } //end if + + // add the midwinding to both sides + for (i = 0; i < 2; i++) + { + cs = &b[i]->sides[b[i]->numsides]; + b[i]->numsides++; + + cs->planenum = planenum^i^1; + cs->texinfo = 0; + //store the node number in the surf to find the texinfo later on + cs->surf = nodenum; + // + cs->flags &= ~SFL_VISIBLE; + cs->flags &= ~SFL_TESTED; + cs->flags &= ~SFL_TEXTURED; + if (i==0) + cs->winding = CopyWinding (midwinding); + else + cs->winding = midwinding; + } //end for + + +{ + vec_t v1; + int i; + + for (i=0 ; i<2 ; i++) + { + v1 = BrushVolume (b[i]); + if (v1 < 1) + { + FreeBrush (b[i]); + b[i] = NULL; + Log_Print("Q1_SplitBrush: tiny volume after clip\n"); + } //end if + } //end for +} //*/ + + *front = b[0]; + *back = b[1]; +} //end of the function Q1_SplitBrush +//=========================================================================== +// returns true if the tree starting at nodenum has only solid leaves +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Q1_SolidTree_r(int nodenum) +{ + if (nodenum < 0) + { + switch(q1_dleafs[(-nodenum) - 1].contents) + { + case Q1_CONTENTS_EMPTY: + { + return false; + } //end case + case Q1_CONTENTS_SOLID: +#ifdef HLCONTENTS + case Q1_CONTENTS_CLIP: +#endif HLCONTENTS + case Q1_CONTENTS_SKY: +#ifdef HLCONTENTS + case Q1_CONTENTS_TRANSLUCENT: +#endif HLCONTENTS + { + return true; + } //end case + case Q1_CONTENTS_WATER: + case Q1_CONTENTS_SLIME: + case Q1_CONTENTS_LAVA: +#ifdef HLCONTENTS + //these contents should not be found in the BSP + case Q1_CONTENTS_ORIGIN: + case Q1_CONTENTS_CURRENT_0: + case Q1_CONTENTS_CURRENT_90: + case Q1_CONTENTS_CURRENT_180: + case Q1_CONTENTS_CURRENT_270: + case Q1_CONTENTS_CURRENT_UP: + case Q1_CONTENTS_CURRENT_DOWN: +#endif HLCONTENTS + default: + { + return false; + } //end default + } //end switch + return false; + } //end if + if (!Q1_SolidTree_r(q1_dnodes[nodenum].children[0])) return false; + if (!Q1_SolidTree_r(q1_dnodes[nodenum].children[1])) return false; + return true; +} //end of the function Q1_SolidTree_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *Q1_CreateBrushes_r(bspbrush_t *brush, int nodenum) +{ + int planenum; + bspbrush_t *front, *back; + q1_dleaf_t *leaf; + + //if it is a leaf + if (nodenum < 0) + { + leaf = &q1_dleafs[(-nodenum) - 1]; + if (leaf->contents != Q1_CONTENTS_EMPTY) + { +#ifdef Q1_PRINT + qprintf("\r%5i", ++q1_numbrushes); +#endif //Q1_PRINT + } //end if + switch(leaf->contents) + { + case Q1_CONTENTS_EMPTY: + { + FreeBrush(brush); + return NULL; + } //end case + case Q1_CONTENTS_SOLID: +#ifdef HLCONTENTS + case Q1_CONTENTS_CLIP: +#endif HLCONTENTS + case Q1_CONTENTS_SKY: +#ifdef HLCONTENTS + case Q1_CONTENTS_TRANSLUCENT: +#endif HLCONTENTS + { + brush->side = CONTENTS_SOLID; + return brush; + } //end case + case Q1_CONTENTS_WATER: + { + brush->side = CONTENTS_WATER; + return brush; + } //end case + case Q1_CONTENTS_SLIME: + { + brush->side = CONTENTS_SLIME; + return brush; + } //end case + case Q1_CONTENTS_LAVA: + { + brush->side = CONTENTS_LAVA; + return brush; + } //end case +#ifdef HLCONTENTS + //these contents should not be found in the BSP + case Q1_CONTENTS_ORIGIN: + case Q1_CONTENTS_CURRENT_0: + case Q1_CONTENTS_CURRENT_90: + case Q1_CONTENTS_CURRENT_180: + case Q1_CONTENTS_CURRENT_270: + case Q1_CONTENTS_CURRENT_UP: + case Q1_CONTENTS_CURRENT_DOWN: + { + Error("Q1_CreateBrushes_r: found contents %d in Half-Life BSP", leaf->contents); + return NULL; + } //end case +#endif HLCONTENTS + default: + { + Error("Q1_CreateBrushes_r: unknown contents %d in Half-Life BSP", leaf->contents); + return NULL; + } //end default + } //end switch + return NULL; + } //end if + //if the rest of the tree is solid + /*if (Q1_SolidTree_r(nodenum)) + { + brush->side = CONTENTS_SOLID; + return brush; + } //end if*/ + // + planenum = q1_dnodes[nodenum].planenum; + planenum = FindFloatPlane(q1_dplanes[planenum].normal, q1_dplanes[planenum].dist); + //split the brush with the node plane + Q1_SplitBrush(brush, planenum, nodenum, &front, &back); + //free the original brush + FreeBrush(brush); + //every node must split the brush in two + if (!front || !back) + { + Log_Print("Q1_CreateBrushes_r: WARNING node not splitting brush\n"); + //return NULL; + } //end if + //create brushes recursively + if (front) front = Q1_CreateBrushes_r(front, q1_dnodes[nodenum].children[0]); + if (back) back = Q1_CreateBrushes_r(back, q1_dnodes[nodenum].children[1]); + //link the brushes if possible and return them + if (front) + { + for (brush = front; brush->next; brush = brush->next); + brush->next = back; + return front; + } //end if + else + { + return back; + } //end else +} //end of the function Q1_CreateBrushes_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *Q1_CreateBrushesFromBSP(int modelnum) +{ + bspbrush_t *brushlist; + bspbrush_t *brush; + q1_dnode_t *headnode; + vec3_t mins, maxs; + int i; + + // + headnode = &q1_dnodes[q1_dmodels[modelnum].headnode[0]]; + //get the mins and maxs of the world + VectorCopy(headnode->mins, mins); + VectorCopy(headnode->maxs, maxs); + //enlarge these mins and maxs + for (i = 0; i < 3; i++) + { + mins[i] -= 8; + maxs[i] += 8; + } //end for + //NOTE: have to add the BSP tree mins and maxs to the MAP mins and maxs + AddPointToBounds(mins, map_mins, map_maxs); + AddPointToBounds(maxs, map_mins, map_maxs); + // + if (!modelnum) + { + Log_Print("brush size: %5.0f,%5.0f,%5.0f to %5.0f,%5.0f,%5.0f\n", + map_mins[0], map_mins[1], map_mins[2], + map_maxs[0], map_maxs[1], map_maxs[2]); + } //end if + //create one huge brush containing the whole world + brush = BrushFromBounds(mins, maxs); + VectorCopy(mins, brush->mins); + VectorCopy(maxs, brush->maxs); + // +#ifdef Q1_PRINT + qprintf("creating Quake brushes\n"); + qprintf("%5d brushes", q1_numbrushes = 0); +#endif //Q1_PRINT + //create the brushes + brushlist = Q1_CreateBrushes_r(brush, q1_dmodels[modelnum].headnode[0]); + // +#ifdef Q1_PRINT + qprintf("\n"); +#endif //Q1_PRINT + //now we've got a list with brushes! + return brushlist; +} //end of the function Q1_CreateBrushesFromBSP +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +q1_dleaf_t *Q1_PointInLeaf(int startnode, vec3_t point) +{ + int nodenum; + vec_t dist; + q1_dnode_t *node; + q1_dplane_t *plane; + + nodenum = startnode; + while (nodenum >= 0) + { + node = &q1_dnodes[nodenum]; + plane = &q1_dplanes[node->planenum]; + dist = DotProduct(point, plane->normal) - plane->dist; + if (dist > 0) + nodenum = node->children[0]; + else + nodenum = node->children[1]; + } //end while + + return &q1_dleafs[-nodenum - 1]; +} //end of the function Q1_PointInLeaf +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float Q1_FaceArea(q1_dface_t *face) +{ + int i; + float total; + vec_t *v; + vec3_t d1, d2, cross; + q1_dedge_t *edge; + + edge = &q1_dedges[face->firstedge]; + v = q1_dvertexes[edge->v[0]].point; + + total = 0; + for (i = 1; i < face->numedges - 1; i++) + { + edge = &q1_dedges[face->firstedge + i]; + VectorSubtract(q1_dvertexes[edge->v[0]].point, v, d1); + VectorSubtract(q1_dvertexes[edge->v[1]].point, v, d2); + CrossProduct(d1, d2, cross); + total += 0.5 * VectorLength(cross); + } //end for + return total; +} //end of the function AAS_FaceArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Q1_FacePlane(q1_dface_t *face, vec3_t normal, float *dist) +{ + vec_t *v1, *v2, *v3; + vec3_t vec1, vec2; + int side, edgenum; + + edgenum = q1_dsurfedges[face->firstedge]; + side = edgenum < 0; + v1 = q1_dvertexes[q1_dedges[abs(edgenum)].v[side]].point; + v2 = q1_dvertexes[q1_dedges[abs(edgenum)].v[!side]].point; + edgenum = q1_dsurfedges[face->firstedge+1]; + side = edgenum < 0; + v3 = q1_dvertexes[q1_dedges[abs(edgenum)].v[!side]].point; + // + VectorSubtract(v2, v1, vec1); + VectorSubtract(v3, v1, vec2); + + CrossProduct(vec1, vec2, normal); + VectorNormalize(normal); + *dist = DotProduct(v1, normal); +} //end of the function Q1_FacePlane +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *Q1_MergeBrushes(bspbrush_t *brushlist, int modelnum) +{ + int nummerges, merged; + bspbrush_t *b1, *b2, *tail, *newbrush, *newbrushlist; + bspbrush_t *lastb2; + + if (!brushlist) return NULL; + + if (!modelnum) qprintf("%5d brushes merged", nummerges = 0); + do + { + for (tail = brushlist; tail; tail = tail->next) + { + if (!tail->next) break; + } //end for + merged = 0; + newbrushlist = NULL; + for (b1 = brushlist; b1; b1 = brushlist) + { + lastb2 = b1; + for (b2 = b1->next; b2; b2 = b2->next) + { + //can't merge brushes with different contents + if (b1->side != b2->side) newbrush = NULL; + else newbrush = TryMergeBrushes(b1, b2); + //if a merged brush is created + if (newbrush) + { + //copy the brush contents + newbrush->side = b1->side; + //add the new brush to the end of the list + tail->next = newbrush; + //remove the second brush from the list + lastb2->next = b2->next; + //remove the first brush from the list + brushlist = brushlist->next; + //free the merged brushes + FreeBrush(b1); + FreeBrush(b2); + //get a new tail brush + for (tail = brushlist; tail; tail = tail->next) + { + if (!tail->next) break; + } //end for + merged++; + if (!modelnum) qprintf("\r%5d", nummerges++); + break; + } //end if + lastb2 = b2; + } //end for + //if b1 can't be merged with any of the other brushes + if (!b2) + { + brushlist = brushlist->next; + //keep b1 + b1->next = newbrushlist; + newbrushlist = b1; + } //end else + } //end for + brushlist = newbrushlist; + } while(merged); + if (!modelnum) qprintf("\n"); + return newbrushlist; +} //end of the function Q1_MergeBrushes +//=========================================================================== +// returns the amount the face and the winding overlap +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float Q1_FaceOnWinding(q1_dface_t *face, winding_t *winding) +{ + int i, edgenum, side; + float dist, area; + q1_dplane_t plane; + vec_t *v1, *v2; + vec3_t normal, edgevec; + winding_t *w; + + // + w = CopyWinding(winding); + memcpy(&plane, &q1_dplanes[face->planenum], sizeof(q1_dplane_t)); + //check on which side of the plane the face is + if (face->side) + { + VectorNegate(plane.normal, plane.normal); + plane.dist = -plane.dist; + } //end if + for (i = 0; i < face->numedges && w; i++) + { + //get the first and second vertex of the edge + edgenum = q1_dsurfedges[face->firstedge + i]; + side = edgenum > 0; + //if the face plane is flipped + v1 = q1_dvertexes[q1_dedges[abs(edgenum)].v[side]].point; + v2 = q1_dvertexes[q1_dedges[abs(edgenum)].v[!side]].point; + //create a plane through the edge vector, orthogonal to the face plane + //and with the normal vector pointing out of the face + VectorSubtract(v1, v2, edgevec); + CrossProduct(edgevec, plane.normal, normal); + VectorNormalize(normal); + dist = DotProduct(normal, v1); + // + ChopWindingInPlace(&w, normal, dist, 0.9); //CLIP_EPSILON + } //end for + if (w) + { + area = WindingArea(w); + FreeWinding(w); + return area; + } //end if + return 0; +} //end of the function Q1_FaceOnWinding +//=========================================================================== +// returns a list with brushes created by splitting the given brush with +// planes that go through the face edges and are orthogonal to the face plane +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *Q1_SplitBrushWithFace(bspbrush_t *brush, q1_dface_t *face) +{ + int i, edgenum, side, planenum, splits; + float dist; + q1_dplane_t plane; + vec_t *v1, *v2; + vec3_t normal, edgevec; + bspbrush_t *front, *back, *brushlist; + + memcpy(&plane, &q1_dplanes[face->planenum], sizeof(q1_dplane_t)); + //check on which side of the plane the face is + if (face->side) + { + VectorNegate(plane.normal, plane.normal); + plane.dist = -plane.dist; + } //end if + splits = 0; + brushlist = NULL; + for (i = 0; i < face->numedges; i++) + { + //get the first and second vertex of the edge + edgenum = q1_dsurfedges[face->firstedge + i]; + side = edgenum > 0; + //if the face plane is flipped + v1 = q1_dvertexes[q1_dedges[abs(edgenum)].v[side]].point; + v2 = q1_dvertexes[q1_dedges[abs(edgenum)].v[!side]].point; + //create a plane through the edge vector, orthogonal to the face plane + //and with the normal vector pointing out of the face + VectorSubtract(v1, v2, edgevec); + CrossProduct(edgevec, plane.normal, normal); + VectorNormalize(normal); + dist = DotProduct(normal, v1); + // + planenum = FindFloatPlane(normal, dist); + //split the current brush + SplitBrush(brush, planenum, &front, &back); + //if there is a back brush just put it in the list + if (back) + { + //copy the brush contents + back->side = brush->side; + // + back->next = brushlist; + brushlist = back; + splits++; + } //end if + if (!front) + { + Log_Print("Q1_SplitBrushWithFace: no new brush\n"); + FreeBrushList(brushlist); + return NULL; + } //end if + //copy the brush contents + front->side = brush->side; + //continue splitting the front brush + brush = front; + } //end for + if (!splits) + { + FreeBrush(front); + return NULL; + } //end if + front->next = brushlist; + brushlist = front; + return brushlist; +} //end of the function Q1_SplitBrushWithFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *Q1_TextureBrushes(bspbrush_t *brushlist, int modelnum) +{ + float area, largestarea; + int i, n, texinfonum, sn, numbrushes, ofs; + int bestfacenum, sidenodenum; + side_t *side; + q1_dmiptexlump_t *miptexlump; + q1_miptex_t *miptex; + bspbrush_t *brush, *nextbrush, *prevbrush, *newbrushes, *brushlistend; + vec_t defaultvec[4] = {1, 0, 0, 0}; + + if (!modelnum) qprintf("texturing brushes\n"); + if (!modelnum) qprintf("%5d brushes", numbrushes = 0); + //get a pointer to the last brush in the list + for (brushlistend = brushlist; brushlistend; brushlistend = brushlistend->next) + { + if (!brushlistend->next) break; + } //end for + //there's no previous brush when at the start of the list + prevbrush = NULL; + //go over the brush list + for (brush = brushlist; brush; brush = nextbrush) + { + nextbrush = brush->next; + //find a texinfo for every brush side + for (sn = 0; sn < brush->numsides; sn++) + { + side = &brush->sides[sn]; + // + if (side->flags & SFL_TEXTURED) continue; + //number of the node that created this brush side + sidenodenum = side->surf; //see midwinding in Q1_SplitBrush + //no face found yet + bestfacenum = -1; + //minimum face size + largestarea = 1; + //if optimizing the texture placement and not going for the + //least number of brushes + if (!lessbrushes) + { + for (i = 0; i < q1_numfaces; i++) + { + //the face must be in the same plane as the node plane that created + //this brush side + if (q1_dfaces[i].planenum == q1_dnodes[sidenodenum].planenum) + { + //get the area the face and the brush side overlap + area = Q1_FaceOnWinding(&q1_dfaces[i], side->winding); + //if this face overlaps the brush side winding more than previous faces + if (area > largestarea) + { + //if there already was a face for texturing this brush side with + //a different texture + if (bestfacenum >= 0 && + (q1_dfaces[bestfacenum].texinfo != q1_dfaces[i].texinfo)) + { + //split the brush to fit the texture + newbrushes = Q1_SplitBrushWithFace(brush, &q1_dfaces[i]); + //if new brushes where created + if (newbrushes) + { + //remove the current brush from the list + if (prevbrush) prevbrush->next = brush->next; + else brushlist = brush->next; + if (brushlistend == brush) + { + brushlistend = prevbrush; + nextbrush = newbrushes; + } //end if + //add the new brushes to the end of the list + if (brushlistend) brushlistend->next = newbrushes; + else brushlist = newbrushes; + //free the current brush + FreeBrush(brush); + //don't forget about the prevbrush pointer at the bottom of + //the outer loop + brush = prevbrush; + //find the end of the list + for (brushlistend = brushlist; brushlistend; brushlistend = brushlistend->next) + { + if (!brushlistend->next) break; + } //end for + break; + } //end if + else + { + Log_Write("brush %d: no real texture split", numbrushes); + } //end else + } //end if + else + { + //best face for texturing this brush side + bestfacenum = i; + } //end else + } //end if + } //end if + } //end for + //if the brush was split the original brush is removed + //and we just continue with the next one in the list + if (i < q1_numfaces) break; + } //end if + else + { + //find the face with the largest overlap with this brush side + //for texturing the brush side + for (i = 0; i < q1_numfaces; i++) + { + //the face must be in the same plane as the node plane that created + //this brush side + if (q1_dfaces[i].planenum == q1_dnodes[sidenodenum].planenum) + { + //get the area the face and the brush side overlap + area = Q1_FaceOnWinding(&q1_dfaces[i], side->winding); + //if this face overlaps the brush side winding more than previous faces + if (area > largestarea) + { + largestarea = area; + bestfacenum = i; + } //end if + } //end if + } //end for + } //end else + //if a face was found for texturing this brush side + if (bestfacenum >= 0) + { + //set the MAP texinfo values + texinfonum = q1_dfaces[bestfacenum].texinfo; + for (n = 0; n < 4; n++) + { + map_texinfo[texinfonum].vecs[0][n] = q1_texinfo[texinfonum].vecs[0][n]; + map_texinfo[texinfonum].vecs[1][n] = q1_texinfo[texinfonum].vecs[1][n]; + } //end for + //make sure the two vectors aren't of zero length otherwise use the default + //vector to prevent a divide by zero in the map writing + if (VectorLength(map_texinfo[texinfonum].vecs[0]) < 0.01) + memcpy(map_texinfo[texinfonum].vecs[0], defaultvec, sizeof(defaultvec)); + if (VectorLength(map_texinfo[texinfonum].vecs[1]) < 0.01) + memcpy(map_texinfo[texinfonum].vecs[1], defaultvec, sizeof(defaultvec)); + // + map_texinfo[texinfonum].flags = q1_texinfo[texinfonum].flags; + map_texinfo[texinfonum].value = 0; //Q1 and HL texinfos don't have a value + //the mip texture + miptexlump = (q1_dmiptexlump_t *) q1_dtexdata; + ofs = miptexlump->dataofs[q1_texinfo[texinfonum].miptex]; + if ( ofs > q1_texdatasize ) { + ofs = miptexlump->dataofs[0]; + } + miptex = (q1_miptex_t *)((byte *)miptexlump + ofs); + //get the mip texture name + strcpy(map_texinfo[texinfonum].texture, miptex->name); + //no animations in Quake1 and Half-Life mip textures + map_texinfo[texinfonum].nexttexinfo = -1; + //store the texinfo number + side->texinfo = texinfonum; + // + if (texinfonum > map_numtexinfo) map_numtexinfo = texinfonum; + //this side is textured + side->flags |= SFL_TEXTURED; + } //end if + else + { + //no texture for this side + side->texinfo = TEXINFO_NODE; + //this side is textured + side->flags |= SFL_TEXTURED; + } //end if + } //end for + // + if (!modelnum && prevbrush != brush) qprintf("\r%5d", ++numbrushes); + //previous brush in the list + prevbrush = brush; + } //end for + if (!modelnum) qprintf("\n"); + //return the new list with brushes + return brushlist; +} //end of the function Q1_TextureBrushes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Q1_FixContentsTextures(bspbrush_t *brushlist) +{ + int i, texinfonum; + bspbrush_t *brush; + + for (brush = brushlist; brush; brush = brush->next) + { + //only fix the textures of water, slime and lava brushes + if (brush->side != CONTENTS_WATER && + brush->side != CONTENTS_SLIME && + brush->side != CONTENTS_LAVA) continue; + // + for (i = 0; i < brush->numsides; i++) + { + texinfonum = brush->sides[i].texinfo; + if (Q1_TextureContents(map_texinfo[texinfonum].texture) == brush->side) break; + } //end for + //if no specific contents texture was found + if (i >= brush->numsides) + { + texinfonum = -1; + for (i = 0; i < map_numtexinfo; i++) + { + if (Q1_TextureContents(map_texinfo[i].texture) == brush->side) + { + texinfonum = i; + break; + } //end if + } //end for + } //end if + // + if (texinfonum >= 0) + { + //give all the brush sides this contents texture + for (i = 0; i < brush->numsides; i++) + { + brush->sides[i].texinfo = texinfonum; + } //end for + } //end if + else Log_Print("brush contents %d with wrong textures\n", brush->side); + // + } //end for + /* + for (brush = brushlist; brush; brush = brush->next) + { + //give all the brush sides this contents texture + for (i = 0; i < brush->numsides; i++) + { + if (Q1_TextureContents(map_texinfo[texinfonum].texture) != brush->side) + { + Error("brush contents %d with wrong contents textures %s\n", brush->side, + Q1_TextureContents(map_texinfo[texinfonum].texture)); + } //end if + } //end for + } //end for*/ +} //end of the function Q1_FixContentsTextures +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Q1_BSPBrushToMapBrush(bspbrush_t *bspbrush, entity_t *mapent) +{ + mapbrush_t *mapbrush; + side_t *side; + int i, besttexinfo; + + CheckBSPBrush(bspbrush); + + if (nummapbrushes >= MAX_MAPFILE_BRUSHES) + Error ("nummapbrushes == MAX_MAPFILE_BRUSHES"); + + mapbrush = &mapbrushes[nummapbrushes]; + mapbrush->original_sides = &brushsides[nummapbrushsides]; + mapbrush->entitynum = mapent - entities; + mapbrush->brushnum = nummapbrushes - mapent->firstbrush; + mapbrush->leafnum = -1; + mapbrush->numsides = 0; + + besttexinfo = TEXINFO_NODE; + for (i = 0; i < bspbrush->numsides; i++) + { + if (!bspbrush->sides[i].winding) continue; + // + if (nummapbrushsides >= MAX_MAPFILE_BRUSHSIDES) + Error ("MAX_MAPFILE_BRUSHSIDES"); + side = &brushsides[nummapbrushsides]; + //the contents of the bsp brush is stored in the side variable + side->contents = bspbrush->side; + side->surf = 0; + side->planenum = bspbrush->sides[i].planenum; + side->texinfo = bspbrush->sides[i].texinfo; + if (side->texinfo != TEXINFO_NODE) + { + //this brush side is textured + side->flags |= SFL_TEXTURED; + besttexinfo = side->texinfo; + } //end if + // + nummapbrushsides++; + mapbrush->numsides++; + } //end for + // + if (besttexinfo == TEXINFO_NODE) + { + mapbrush->numsides = 0; + q1_numclipbrushes++; + return; + } //end if + //set the texinfo for all the brush sides without texture + for (i = 0; i < mapbrush->numsides; i++) + { + if (mapbrush->original_sides[i].texinfo == TEXINFO_NODE) + { + mapbrush->original_sides[i].texinfo = besttexinfo; + } //end if + } //end for + //contents of the brush + mapbrush->contents = bspbrush->side; + // + if (create_aas) + { + //create the AAS brushes from this brush, add brush bevels + AAS_CreateMapBrushes(mapbrush, mapent, true); + return; + } //end if + //create windings for sides and bounds for brush + MakeBrushWindings(mapbrush); + //add brush bevels + AddBrushBevels(mapbrush); + //a new brush has been created + nummapbrushes++; + mapent->numbrushes++; +} //end of the function Q1_BSPBrushToMapBrush +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Q1_CreateMapBrushes(entity_t *mapent, int modelnum) +{ + bspbrush_t *brushlist, *brush, *nextbrush; + int i; + + //create brushes from the model BSP tree + brushlist = Q1_CreateBrushesFromBSP(modelnum); + //texture the brushes and split them when necesary + brushlist = Q1_TextureBrushes(brushlist, modelnum); + //fix the contents textures of all brushes + Q1_FixContentsTextures(brushlist); + // + if (!nobrushmerge) + { + brushlist = Q1_MergeBrushes(brushlist, modelnum); + //brushlist = Q1_MergeBrushes(brushlist, modelnum); + } //end if + // + if (!modelnum) qprintf("converting brushes to map brushes\n"); + if (!modelnum) qprintf("%5d brushes", i = 0); + for (brush = brushlist; brush; brush = nextbrush) + { + nextbrush = brush->next; + Q1_BSPBrushToMapBrush(brush, mapent); + brush->next = NULL; + FreeBrush(brush); + if (!modelnum) qprintf("\r%5d", ++i); + } //end for + if (!modelnum) qprintf("\n"); +} //end of the function Q1_CreateMapBrushes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Q1_ResetMapLoading(void) +{ +} //end of the function Q1_ResetMapLoading +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Q1_LoadMapFromBSP(char *filename, int offset, int length) +{ + int i, modelnum; + char *model, *classname; + + Log_Print("-- Q1_LoadMapFromBSP --\n"); + //the loaded map type + loadedmaptype = MAPTYPE_QUAKE1; + // + qprintf("loading map from %s at %d\n", filename, offset); + //load the Half-Life BSP file + Q1_LoadBSPFile(filename, offset, length); + // + q1_numclipbrushes = 0; + //CreatePath(path); + //Q1_CreateQ2WALFiles(path); + //parse the entities from the BSP + Q1_ParseEntities(); + //clear the map mins and maxs + ClearBounds(map_mins, map_maxs); + // + qprintf("creating Quake1 brushes\n"); + if (lessbrushes) qprintf("creating minimum number of brushes\n"); + else qprintf("placing textures correctly\n"); + // + for (i = 0; i < num_entities; i++) + { + entities[i].firstbrush = nummapbrushes; + entities[i].numbrushes = 0; + // + classname = ValueForKey(&entities[i], "classname"); + if (classname && !strcmp(classname, "worldspawn")) + { + modelnum = 0; + } //end if + else + { + // + model = ValueForKey(&entities[i], "model"); + if (!model || *model != '*') continue; + model++; + modelnum = atoi(model); + } //end else + //create map brushes for the entity + Q1_CreateMapBrushes(&entities[i], modelnum); + } //end for + // + qprintf("%5d map brushes\n", nummapbrushes); + qprintf("%5d clip brushes\n", q1_numclipbrushes); +} //end of the function Q1_LoadMapFromBSP diff --git a/code/bspc/map_q2.c b/code/bspc/map_q2.c index 0f15216..a91e78a 100755 --- a/code/bspc/map_q2.c +++ b/code/bspc/map_q2.c @@ -1,1162 +1,1162 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -//=========================================================================== -// ANSI, Area Navigational System Interface -// AAS, Area Awareness System -//=========================================================================== - -#include "qbsp.h" -#include "l_mem.h" -#include "../botlib/aasfile.h" //aas_bbox_t -#include "aas_store.h" //AAS_MAX_BBOXES -#include "aas_cfg.h" -#include "aas_map.h" //AAS_CreateMapBrushes -#include "l_bsp_q2.h" - - -#ifdef ME - -#define NODESTACKSIZE 1024 - -int nodestack[NODESTACKSIZE]; -int *nodestackptr; -int nodestacksize = 0; -int brushmodelnumbers[MAX_MAPFILE_BRUSHES]; -int dbrushleafnums[MAX_MAPFILE_BRUSHES]; -int dplanes2mapplanes[MAX_MAPFILE_PLANES]; - -#endif //ME - -//==================================================================== - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Q2_CreateMapTexinfo(void) -{ - int i; - - for (i = 0; i < numtexinfo; i++) - { - memcpy(map_texinfo[i].vecs, texinfo[i].vecs, sizeof(float) * 2 * 4); - map_texinfo[i].flags = texinfo[i].flags; - map_texinfo[i].value = texinfo[i].value; - strcpy(map_texinfo[i].texture, texinfo[i].texture); - map_texinfo[i].nexttexinfo = 0; - } //end for -} //end of the function Q2_CreateMapTexinfo - -/* -=========== -Q2_BrushContents -=========== -*/ -int Q2_BrushContents (mapbrush_t *b) -{ - int contents; - side_t *s; - int i; - int trans; - - s = &b->original_sides[0]; - contents = s->contents; - trans = texinfo[s->texinfo].flags; - for (i = 1; i < b->numsides; i++, s++) - { - s = &b->original_sides[i]; - trans |= texinfo[s->texinfo].flags; - if (s->contents != contents) - { - Log_Print("Entity %i, Brush %i: mixed face contents\n" - , b->entitynum, b->brushnum); - Log_Print("texture name = %s\n", texinfo[s->texinfo].texture); - break; - } - } - - // if any side is translucent, mark the contents - // and change solid to window - if ( trans & (SURF_TRANS33|SURF_TRANS66) ) - { - contents |= CONTENTS_Q2TRANSLUCENT; - if (contents & CONTENTS_SOLID) - { - contents &= ~CONTENTS_SOLID; - contents |= CONTENTS_WINDOW; - } - } - - return contents; -} - -#ifdef ME - -#define BBOX_NORMAL_EPSILON 0.0001 - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void MakeAreaPortalBrush(mapbrush_t *brush) -{ - int sn; - side_t *s; - - brush->contents = CONTENTS_AREAPORTAL; - - for (sn = 0; sn < brush->numsides; sn++) - { - s = brush->original_sides + sn; - //make sure the surfaces are not hint or skip - s->surf &= ~(SURF_HINT|SURF_SKIP); - // - s->texinfo = 0; - s->contents = CONTENTS_AREAPORTAL; - } //end for -} //end of the function MakeAreaPortalBrush -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void DPlanes2MapPlanes(void) -{ - int i; - - for (i = 0; i < numplanes; i++) - { - dplanes2mapplanes[i] = FindFloatPlane(dplanes[i].normal, dplanes[i].dist); - } //end for -} //end of the function DPlanes2MapPlanes -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void MarkVisibleBrushSides(mapbrush_t *brush) -{ - int n, i, planenum; - side_t *side; - dface_t *face; - // - for (n = 0; n < brush->numsides; n++) - { - side = brush->original_sides + n; - //if this side is a bevel or the leaf number of the brush is unknown - if ((side->flags & SFL_BEVEL) || brush->leafnum < 0) - { - //this side is a valid splitter - side->flags |= SFL_VISIBLE; - continue; - } //end if - //assum this side will not be used as a splitter - side->flags &= ~SFL_VISIBLE; - //check if the side plane is used by a visible face - for (i = 0; i < numfaces; i++) - { - face = &dfaces[i]; - planenum = dplanes2mapplanes[face->planenum]; - if ((planenum & ~1) == (side->planenum & ~1)) - { - //this side is a valid splitter - side->flags |= SFL_VISIBLE; - } //end if - } //end for - } //end for -} //end of the function MarkVisibleBrushSides - -#endif //ME - -/* -================= -Q2_ParseBrush -================= -*/ -void Q2_ParseBrush (script_t *script, entity_t *mapent) -{ - mapbrush_t *b; - int i, j, k; - int mt; - side_t *side, *s2; - int planenum; - brush_texture_t td; - int planepts[3][3]; - token_t token; - - if (nummapbrushes >= MAX_MAPFILE_BRUSHES) - Error ("nummapbrushes == MAX_MAPFILE_BRUSHES"); - - b = &mapbrushes[nummapbrushes]; - b->original_sides = &brushsides[nummapbrushsides]; - b->entitynum = num_entities-1; - b->brushnum = nummapbrushes - mapent->firstbrush; - b->leafnum = -1; - - do - { - if (!PS_ReadToken(script, &token)) - break; - if (!strcmp(token.string, "}") ) - break; - - //IDBUG: mixed use of MAX_MAPFILE_? and MAX_MAP_? this could - // lead to out of bound indexing of the arrays - if (nummapbrushsides >= MAX_MAPFILE_BRUSHSIDES) - Error ("MAX_MAPFILE_BRUSHSIDES"); - side = &brushsides[nummapbrushsides]; - - //read the three point plane definition - for (i = 0; i < 3; i++) - { - if (i != 0) PS_ExpectTokenString(script, "("); - for (j = 0; j < 3; j++) - { - PS_ExpectAnyToken(script, &token); - planepts[i][j] = atof(token.string); - } //end for - PS_ExpectTokenString(script, ")"); - } //end for - - // - //read the texturedef - // - PS_ExpectAnyToken(script, &token); - strcpy(td.name, token.string); - - PS_ExpectAnyToken(script, &token); - td.shift[0] = atol(token.string); - PS_ExpectAnyToken(script, &token); - td.shift[1] = atol(token.string); - PS_ExpectAnyToken(script, &token); - td.rotate = atol(token.string); - PS_ExpectAnyToken(script, &token); - td.scale[0] = atof(token.string); - PS_ExpectAnyToken(script, &token); - td.scale[1] = atof(token.string); - - //find default flags and values - mt = FindMiptex (td.name); - td.flags = textureref[mt].flags; - td.value = textureref[mt].value; - side->contents = textureref[mt].contents; - side->surf = td.flags = textureref[mt].flags; - - //check if there's a number available - if (PS_CheckTokenType(script, TT_NUMBER, 0, &token)) - { - side->contents = token.intvalue; - PS_ExpectTokenType(script, TT_NUMBER, 0, &token); - side->surf = td.flags = token.intvalue; - PS_ExpectTokenType(script, TT_NUMBER, 0, &token); - td.value = token.intvalue; - } - - // translucent objects are automatically classified as detail - if (side->surf & (SURF_TRANS33|SURF_TRANS66) ) - side->contents |= CONTENTS_DETAIL; - if (side->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) - side->contents |= CONTENTS_DETAIL; - if (fulldetail) - side->contents &= ~CONTENTS_DETAIL; - if (!(side->contents & ((LAST_VISIBLE_CONTENTS-1) - | CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP|CONTENTS_MIST) ) ) - side->contents |= CONTENTS_SOLID; - - // hints and skips are never detail, and have no content - if (side->surf & (SURF_HINT|SURF_SKIP) ) - { - side->contents = 0; - side->surf &= ~CONTENTS_DETAIL; - } - -#ifdef ME - //for creating AAS... this side is textured - side->flags |= SFL_TEXTURED; -#endif //ME - // - // find the plane number - // - planenum = PlaneFromPoints (planepts[0], planepts[1], planepts[2]); - if (planenum == -1) - { - Log_Print("Entity %i, Brush %i: plane with no normal\n" - , b->entitynum, b->brushnum); - continue; - } - - // - // see if the plane has been used already - // - for (k=0 ; knumsides ; k++) - { - s2 = b->original_sides + k; - if (s2->planenum == planenum) - { - Log_Print("Entity %i, Brush %i: duplicate plane\n" - , b->entitynum, b->brushnum); - break; - } - if ( s2->planenum == (planenum^1) ) - { - Log_Print("Entity %i, Brush %i: mirrored plane\n" - , b->entitynum, b->brushnum); - break; - } - } - if (k != b->numsides) - continue; // duplicated - - // - // keep this side - // - - side = b->original_sides + b->numsides; - side->planenum = planenum; - side->texinfo = TexinfoForBrushTexture (&mapplanes[planenum], - &td, vec3_origin); - - // save the td off in case there is an origin brush and we - // have to recalculate the texinfo - side_brushtextures[nummapbrushsides] = td; - - nummapbrushsides++; - b->numsides++; - } while (1); - - // get the content for the entire brush - b->contents = Q2_BrushContents (b); - -#ifdef ME - if (BrushExists(b)) - { - c_squattbrushes++; - b->numsides = 0; - return; - } //end if - - if (create_aas) - { - //create AAS brushes, and add brush bevels - AAS_CreateMapBrushes(b, mapent, true); - //NOTE: if we return here then duplicate plane errors occur for the non world entities - return; - } //end if -#endif //ME - - // allow detail brushes to be removed - if (nodetail && (b->contents & CONTENTS_DETAIL) ) - { - b->numsides = 0; - return; - } - - // allow water brushes to be removed - if (nowater && (b->contents & (CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER)) ) - { - b->numsides = 0; - return; - } - - // create windings for sides and bounds for brush - MakeBrushWindings (b); - - // brushes that will not be visible at all will never be - // used as bsp splitters - if (b->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) - { - c_clipbrushes++; - for (i=0 ; inumsides ; i++) - b->original_sides[i].texinfo = TEXINFO_NODE; - } - - // - // origin brushes are removed, but they set - // the rotation origin for the rest of the brushes - // in the entity. After the entire entity is parsed, - // the planenums and texinfos will be adjusted for - // the origin brush - // - if (b->contents & CONTENTS_ORIGIN) - { - char string[32]; - vec3_t origin; - - if (num_entities == 1) - { - Error ("Entity %i, Brush %i: origin brushes not allowed in world" - , b->entitynum, b->brushnum); - return; - } - - VectorAdd (b->mins, b->maxs, origin); - VectorScale (origin, 0.5, origin); - - sprintf (string, "%i %i %i", (int)origin[0], (int)origin[1], (int)origin[2]); - SetKeyValue (&entities[b->entitynum], "origin", string); - - VectorCopy (origin, entities[b->entitynum].origin); - - // don't keep this brush - b->numsides = 0; - - return; - } - - AddBrushBevels(b); - - nummapbrushes++; - mapent->numbrushes++; -} - -/* -================ -Q2_MoveBrushesToWorld - -Takes all of the brushes from the current entity and -adds them to the world's brush list. - -Used by func_group and func_areaportal -================ -*/ -void Q2_MoveBrushesToWorld (entity_t *mapent) -{ - int newbrushes; - int worldbrushes; - mapbrush_t *temp; - int i; - - // this is pretty gross, because the brushes are expected to be - // in linear order for each entity - - newbrushes = mapent->numbrushes; - worldbrushes = entities[0].numbrushes; - - temp = GetMemory(newbrushes*sizeof(mapbrush_t)); - memcpy (temp, mapbrushes + mapent->firstbrush, newbrushes*sizeof(mapbrush_t)); - -#if 0 // let them keep their original brush numbers - for (i=0 ; inumbrushes = 0; -} - -/* -================ -Q2_ParseMapEntity -================ -*/ -qboolean Q2_ParseMapEntity(script_t *script) -{ - entity_t *mapent; - epair_t *e; - side_t *s; - int i, j; - int startbrush, startsides; - vec_t newdist; - mapbrush_t *b; - token_t token; - - if (!PS_ReadToken(script, &token)) return false; - - if (strcmp(token.string, "{") ) - Error ("ParseEntity: { not found"); - - if (num_entities == MAX_MAP_ENTITIES) - Error ("num_entities == MAX_MAP_ENTITIES"); - - startbrush = nummapbrushes; - startsides = nummapbrushsides; - - mapent = &entities[num_entities]; - num_entities++; - memset (mapent, 0, sizeof(*mapent)); - mapent->firstbrush = nummapbrushes; - mapent->numbrushes = 0; -// mapent->portalareas[0] = -1; -// mapent->portalareas[1] = -1; - - do - { - if (!PS_ReadToken(script, &token)) - { - Error("ParseEntity: EOF without closing brace"); - } //end if - if (!strcmp(token.string, "}")) break; - if (!strcmp(token.string, "{")) - { - Q2_ParseBrush(script, mapent); - } //end if - else - { - PS_UnreadLastToken(script); - e = ParseEpair(script); - e->next = mapent->epairs; - mapent->epairs = e; - } //end else - } while(1); - - GetVectorForKey(mapent, "origin", mapent->origin); - - // - // if there was an origin brush, offset all of the planes and texinfo - // - if (mapent->origin[0] || mapent->origin[1] || mapent->origin[2]) - { - for (i=0 ; inumbrushes ; i++) - { - b = &mapbrushes[mapent->firstbrush + i]; - for (j=0 ; jnumsides ; j++) - { - s = &b->original_sides[j]; - newdist = mapplanes[s->planenum].dist - - DotProduct (mapplanes[s->planenum].normal, mapent->origin); - s->planenum = FindFloatPlane (mapplanes[s->planenum].normal, newdist); - s->texinfo = TexinfoForBrushTexture (&mapplanes[s->planenum], - &side_brushtextures[s-brushsides], mapent->origin); - } - MakeBrushWindings (b); - } - } - - // group entities are just for editor convenience - // toss all brushes into the world entity - if (!strcmp ("func_group", ValueForKey (mapent, "classname"))) - { - Q2_MoveBrushesToWorld (mapent); - mapent->numbrushes = 0; - return true; - } - - // areaportal entities move their brushes, but don't eliminate - // the entity - if (!strcmp ("func_areaportal", ValueForKey (mapent, "classname"))) - { - char str[128]; - - if (mapent->numbrushes != 1) - Error ("Entity %i: func_areaportal can only be a single brush", num_entities-1); - - b = &mapbrushes[nummapbrushes-1]; - b->contents = CONTENTS_AREAPORTAL; - c_areaportals++; - mapent->areaportalnum = c_areaportals; - // set the portal number as "style" - sprintf (str, "%i", c_areaportals); - SetKeyValue (mapent, "style", str); - Q2_MoveBrushesToWorld (mapent); - return true; - } - - return true; -} - -//=================================================================== - -/* -================ -LoadMapFile -================ -*/ -void Q2_LoadMapFile(char *filename) -{ - int i; - script_t *script; - - Log_Print("-- Q2_LoadMapFile --\n"); -#ifdef ME - //loaded map type - loadedmaptype = MAPTYPE_QUAKE2; - //reset the map loading - ResetMapLoading(); -#endif //ME - - script = LoadScriptFile(filename); - if (!script) - { - Log_Print("couldn't open %s\n", filename); - return; - } //end if - //white spaces and escape characters inside a string are not allowed - SetScriptFlags(script, SCFL_NOSTRINGWHITESPACES | - SCFL_NOSTRINGESCAPECHARS | - SCFL_PRIMITIVE); - - nummapbrushsides = 0; - num_entities = 0; - - while (Q2_ParseMapEntity(script)) - { - } - - ClearBounds (map_mins, map_maxs); - for (i=0 ; i 4096) - continue; // no valid points - AddPointToBounds (mapbrushes[i].mins, map_mins, map_maxs); - AddPointToBounds (mapbrushes[i].maxs, map_mins, map_maxs); - } //end for - - PrintMapInfo(); - - //free the script - FreeScript(script); -// TestExpandBrushes (); - // - Q2_CreateMapTexinfo(); -} //end of the function Q2_LoadMapFile - -#ifdef ME //Begin MAP loading from BSP file -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Q2_SetLeafBrushesModelNumbers(int leafnum, int modelnum) -{ - int i, brushnum; - dleaf_t *leaf; - - leaf = &dleafs[leafnum]; - for (i = 0; i < leaf->numleafbrushes; i++) - { - brushnum = dleafbrushes[leaf->firstleafbrush + i]; - brushmodelnumbers[brushnum] = modelnum; - dbrushleafnums[brushnum] = leafnum; - } //end for -} //end of the function Q2_SetLeafBrushesModelNumbers -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Q2_InitNodeStack(void) -{ - nodestackptr = nodestack; - nodestacksize = 0; -} //end of the function Q2_InitNodeStack -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Q2_PushNodeStack(int num) -{ - *nodestackptr = num; - nodestackptr++; - nodestacksize++; - // - if (nodestackptr >= &nodestack[NODESTACKSIZE]) - { - Error("Q2_PushNodeStack: stack overflow\n"); - } //end if -} //end of the function Q2_PushNodeStack -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int Q2_PopNodeStack(void) -{ - //if the stack is empty - if (nodestackptr <= nodestack) return -1; - //decrease stack pointer - nodestackptr--; - nodestacksize--; - //return the top value from the stack - return *nodestackptr; -} //end of the function Q2_PopNodeStack -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Q2_SetBrushModelNumbers(entity_t *mapent) -{ - int n, pn; - int leafnum; - - // - Q2_InitNodeStack(); - //head node (root) of the bsp tree - n = dmodels[mapent->modelnum].headnode; - pn = 0; - - do - { - //if we are in a leaf (negative node number) - if (n < 0) - { - //number of the leaf - leafnum = (-n) - 1; - //set the brush numbers - Q2_SetLeafBrushesModelNumbers(leafnum, mapent->modelnum); - //walk back into the tree to find a second child to continue with - for (pn = Q2_PopNodeStack(); pn >= 0; n = pn, pn = Q2_PopNodeStack()) - { - //if we took the first child at the parent node - if (dnodes[pn].children[0] == n) break; - } //end for - //if the stack wasn't empty (if not processed whole tree) - if (pn >= 0) - { - //push the parent node again - Q2_PushNodeStack(pn); - //we proceed with the second child of the parent node - n = dnodes[pn].children[1]; - } //end if - } //end if - else - { - //push the current node onto the stack - Q2_PushNodeStack(n); - //walk forward into the tree to the first child - n = dnodes[n].children[0]; - } //end else - } while(pn >= 0); -} //end of the function Q2_SetBrushModelNumbers -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Q2_BSPBrushToMapBrush(dbrush_t *bspbrush, entity_t *mapent) -{ - mapbrush_t *b; - int i, k, n; - side_t *side, *s2; - int planenum; - dbrushside_t *bspbrushside; - dplane_t *bspplane; - - if (nummapbrushes >= MAX_MAPFILE_BRUSHES) - Error ("nummapbrushes >= MAX_MAPFILE_BRUSHES"); - - b = &mapbrushes[nummapbrushes]; - b->original_sides = &brushsides[nummapbrushsides]; - b->entitynum = mapent-entities; - b->brushnum = nummapbrushes - mapent->firstbrush; - b->leafnum = dbrushleafnums[bspbrush - dbrushes]; - - for (n = 0; n < bspbrush->numsides; n++) - { - //pointer to the bsp brush side - bspbrushside = &dbrushsides[bspbrush->firstside + n]; - - if (nummapbrushsides >= MAX_MAPFILE_BRUSHSIDES) - { - Error ("MAX_MAPFILE_BRUSHSIDES"); - } //end if - //pointer to the map brush side - side = &brushsides[nummapbrushsides]; - //if the BSP brush side is textured - if (brushsidetextured[bspbrush->firstside + n]) side->flags |= SFL_TEXTURED; - else side->flags &= ~SFL_TEXTURED; - //ME: can get side contents and surf directly from BSP file - side->contents = bspbrush->contents; - //if the texinfo is TEXINFO_NODE - if (bspbrushside->texinfo < 0) side->surf = 0; - else side->surf = texinfo[bspbrushside->texinfo].flags; - - // translucent objects are automatically classified as detail - if (side->surf & (SURF_TRANS33|SURF_TRANS66) ) - side->contents |= CONTENTS_DETAIL; - if (side->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) - side->contents |= CONTENTS_DETAIL; - if (fulldetail) - side->contents &= ~CONTENTS_DETAIL; - if (!(side->contents & ((LAST_VISIBLE_CONTENTS-1) - | CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP|CONTENTS_MIST) ) ) - side->contents |= CONTENTS_SOLID; - - // hints and skips are never detail, and have no content - if (side->surf & (SURF_HINT|SURF_SKIP) ) - { - side->contents = 0; - side->surf &= ~CONTENTS_DETAIL; - } - - //ME: get a plane for this side - bspplane = &dplanes[bspbrushside->planenum]; - planenum = FindFloatPlane(bspplane->normal, bspplane->dist); - // - // see if the plane has been used already - // - //ME: this really shouldn't happen!!! - //ME: otherwise the bsp file is corrupted?? - //ME: still it seems to happen, maybe Johny Boy's - //ME: brush bevel adding is crappy ? - for (k = 0; k < b->numsides; k++) - { - s2 = b->original_sides + k; -// if (DotProduct (mapplanes[s2->planenum].normal, mapplanes[planenum].normal) > 0.999 -// && fabs(mapplanes[s2->planenum].dist - mapplanes[planenum].dist) < 0.01 ) - - if (s2->planenum == planenum) - { - Log_Print("Entity %i, Brush %i: duplicate plane\n" - , b->entitynum, b->brushnum); - break; - } - if ( s2->planenum == (planenum^1) ) - { - Log_Print("Entity %i, Brush %i: mirrored plane\n" - , b->entitynum, b->brushnum); - break; - } - } - if (k != b->numsides) - continue; // duplicated - - // - // keep this side - // - //ME: reset pointer to side, why? hell I dunno (pointer is set above already) - side = b->original_sides + b->numsides; - //ME: store the plane number - side->planenum = planenum; - //ME: texinfo is already stored when bsp is loaded - //NOTE: check for TEXINFO_NODE, otherwise crash in Q2_BrushContents - if (bspbrushside->texinfo < 0) side->texinfo = 0; - else side->texinfo = bspbrushside->texinfo; - - // save the td off in case there is an origin brush and we - // have to recalculate the texinfo - // ME: don't need to recalculate because it's already done - // (for non-world entities) in the BSP file -// side_brushtextures[nummapbrushsides] = td; - - nummapbrushsides++; - b->numsides++; - } //end for - - // get the content for the entire brush - b->contents = bspbrush->contents; - Q2_BrushContents(b); - - if (BrushExists(b)) - { - c_squattbrushes++; - b->numsides = 0; - return; - } //end if - - //if we're creating AAS - if (create_aas) - { - //create the AAS brushes from this brush, don't add brush bevels - AAS_CreateMapBrushes(b, mapent, false); - return; - } //end if - - // allow detail brushes to be removed - if (nodetail && (b->contents & CONTENTS_DETAIL) ) - { - b->numsides = 0; - return; - } //end if - - // allow water brushes to be removed - if (nowater && (b->contents & (CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER)) ) - { - b->numsides = 0; - return; - } //end if - - // create windings for sides and bounds for brush - MakeBrushWindings(b); - - //mark brushes without winding or with a tiny window as bevels - MarkBrushBevels(b); - - // brushes that will not be visible at all will never be - // used as bsp splitters - if (b->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) - { - c_clipbrushes++; - for (i = 0; i < b->numsides; i++) - b->original_sides[i].texinfo = TEXINFO_NODE; - } //end for - - // - // origin brushes are removed, but they set - // the rotation origin for the rest of the brushes - // in the entity. After the entire entity is parsed, - // the planenums and texinfos will be adjusted for - // the origin brush - // - //ME: not needed because the entities in the BSP file already - // have an origin set -// if (b->contents & CONTENTS_ORIGIN) -// { -// char string[32]; -// vec3_t origin; -// -// if (num_entities == 1) -// { -// Error ("Entity %i, Brush %i: origin brushes not allowed in world" -// , b->entitynum, b->brushnum); -// return; -// } -// -// VectorAdd (b->mins, b->maxs, origin); -// VectorScale (origin, 0.5, origin); -// -// sprintf (string, "%i %i %i", (int)origin[0], (int)origin[1], (int)origin[2]); -// SetKeyValue (&entities[b->entitynum], "origin", string); -// -// VectorCopy (origin, entities[b->entitynum].origin); -// -// // don't keep this brush -// b->numsides = 0; -// -// return; -// } - - //ME: the bsp brushes already have bevels, so we won't try to - // add them again (especially since Johny Boy's bevel adding might - // be crappy) -// AddBrushBevels(b); - - nummapbrushes++; - mapent->numbrushes++; -} //end of the function Q2_BSPBrushToMapBrush -//=========================================================================== -//=========================================================================== -void Q2_ParseBSPBrushes(entity_t *mapent) -{ - int i; - - //give all the brushes that belong to this entity the number of the - //BSP model used by this entity - Q2_SetBrushModelNumbers(mapent); - //now parse all the brushes with the correct mapent->modelnum - for (i = 0; i < numbrushes; i++) - { - if (brushmodelnumbers[i] == mapent->modelnum) - { - Q2_BSPBrushToMapBrush(&dbrushes[i], mapent); - } //end if - } //end for -} //end of the function Q2_ParseBSPBrushes -//=========================================================================== -//=========================================================================== -qboolean Q2_ParseBSPEntity(int entnum) -{ - entity_t *mapent; - char *model; - int startbrush, startsides; - - startbrush = nummapbrushes; - startsides = nummapbrushsides; - - mapent = &entities[entnum];//num_entities]; - mapent->firstbrush = nummapbrushes; - mapent->numbrushes = 0; - mapent->modelnum = -1; //-1 = no model - - model = ValueForKey(mapent, "model"); - if (model && strlen(model)) - { - if (*model != '*') - { - Error("Q2_ParseBSPEntity: model number without leading *"); - } //end if - //get the model number of this entity (skip the leading *) - mapent->modelnum = atoi(&model[1]); - } //end if - - GetVectorForKey(mapent, "origin", mapent->origin); - - //if this is the world entity it has model number zero - //the world entity has no model key - if (!strcmp("worldspawn", ValueForKey(mapent, "classname"))) - { - mapent->modelnum = 0; - } //end if - //if the map entity has a BSP model (a modelnum of -1 is used for - //entities that aren't using a BSP model) - if (mapent->modelnum >= 0) - { - //parse the bsp brushes - Q2_ParseBSPBrushes(mapent); - } //end if - // - //the origin of the entity is already taken into account - // - //func_group entities can't be in the bsp file - // - //check out the func_areaportal entities - if (!strcmp ("func_areaportal", ValueForKey (mapent, "classname"))) - { - c_areaportals++; - mapent->areaportalnum = c_areaportals; - return true; - } //end if - return true; -} //end of the function Q2_ParseBSPEntity -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Q2_LoadMapFromBSP(char *filename, int offset, int length) -{ - int i; - - Log_Print("-- Q2_LoadMapFromBSP --\n"); - //loaded map type - loadedmaptype = MAPTYPE_QUAKE2; - - Log_Print("Loading map from %s...\n", filename); - //load the bsp file - Q2_LoadBSPFile(filename, offset, length); - - //create an index from bsp planes to map planes - //DPlanes2MapPlanes(); - //clear brush model numbers - for (i = 0; i < MAX_MAPFILE_BRUSHES; i++) - brushmodelnumbers[i] = -1; - - nummapbrushsides = 0; - num_entities = 0; - - Q2_ParseEntities(); - // - for (i = 0; i < num_entities; i++) - { - Q2_ParseBSPEntity(i); - } //end for - - //get the map mins and maxs from the world model - ClearBounds(map_mins, map_maxs); - for (i = 0; i < entities[0].numbrushes; i++) - { - if (mapbrushes[i].mins[0] > 4096) - continue; //no valid points - AddPointToBounds (mapbrushes[i].mins, map_mins, map_maxs); - AddPointToBounds (mapbrushes[i].maxs, map_mins, map_maxs); - } //end for - - PrintMapInfo(); - // - Q2_CreateMapTexinfo(); -} //end of the function Q2_LoadMapFromBSP - -void Q2_ResetMapLoading(void) -{ - //reset for map loading from bsp - memset(nodestack, 0, NODESTACKSIZE * sizeof(int)); - nodestackptr = NULL; - nodestacksize = 0; - memset(brushmodelnumbers, 0, MAX_MAPFILE_BRUSHES * sizeof(int)); -} //end of the function Q2_ResetMapLoading - -//End MAP loading from BSP file -#endif //ME - -//==================================================================== - -/* -================ -TestExpandBrushes - -Expands all the brush planes and saves a new map out -================ -*/ -void TestExpandBrushes (void) -{ - FILE *f; - side_t *s; - int i, j, bn; - winding_t *w; - char *name = "expanded.map"; - mapbrush_t *brush; - vec_t dist; - - Log_Print("writing %s\n", name); - f = fopen (name, "wb"); - if (!f) - Error ("Can't write %s\n", name); - - fprintf (f, "{\n\"classname\" \"worldspawn\"\n"); - - for (bn=0 ; bnnumsides ; i++) - { - s = brush->original_sides + i; - dist = mapplanes[s->planenum].dist; - for (j=0 ; j<3 ; j++) - dist += fabs( 16 * mapplanes[s->planenum].normal[j] ); - - w = BaseWindingForPlane (mapplanes[s->planenum].normal, dist); - - fprintf (f,"( %i %i %i ) ", (int)w->p[0][0], (int)w->p[0][1], (int)w->p[0][2]); - fprintf (f,"( %i %i %i ) ", (int)w->p[1][0], (int)w->p[1][1], (int)w->p[1][2]); - fprintf (f,"( %i %i %i ) ", (int)w->p[2][0], (int)w->p[2][1], (int)w->p[2][2]); - - fprintf (f, "%s 0 0 0 1 1\n", texinfo[s->texinfo].texture); - FreeWinding (w); - } - fprintf (f, "}\n"); - } - fprintf (f, "}\n"); - - fclose (f); - - Error ("can't proceed after expanding brushes"); -} //end of the function TestExpandBrushes - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +//=========================================================================== +// ANSI, Area Navigational System Interface +// AAS, Area Awareness System +//=========================================================================== + +#include "qbsp.h" +#include "l_mem.h" +#include "../botlib/aasfile.h" //aas_bbox_t +#include "aas_store.h" //AAS_MAX_BBOXES +#include "aas_cfg.h" +#include "aas_map.h" //AAS_CreateMapBrushes +#include "l_bsp_q2.h" + + +#ifdef ME + +#define NODESTACKSIZE 1024 + +int nodestack[NODESTACKSIZE]; +int *nodestackptr; +int nodestacksize = 0; +int brushmodelnumbers[MAX_MAPFILE_BRUSHES]; +int dbrushleafnums[MAX_MAPFILE_BRUSHES]; +int dplanes2mapplanes[MAX_MAPFILE_PLANES]; + +#endif //ME + +//==================================================================== + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Q2_CreateMapTexinfo(void) +{ + int i; + + for (i = 0; i < numtexinfo; i++) + { + memcpy(map_texinfo[i].vecs, texinfo[i].vecs, sizeof(float) * 2 * 4); + map_texinfo[i].flags = texinfo[i].flags; + map_texinfo[i].value = texinfo[i].value; + strcpy(map_texinfo[i].texture, texinfo[i].texture); + map_texinfo[i].nexttexinfo = 0; + } //end for +} //end of the function Q2_CreateMapTexinfo + +/* +=========== +Q2_BrushContents +=========== +*/ +int Q2_BrushContents (mapbrush_t *b) +{ + int contents; + side_t *s; + int i; + int trans; + + s = &b->original_sides[0]; + contents = s->contents; + trans = texinfo[s->texinfo].flags; + for (i = 1; i < b->numsides; i++, s++) + { + s = &b->original_sides[i]; + trans |= texinfo[s->texinfo].flags; + if (s->contents != contents) + { + Log_Print("Entity %i, Brush %i: mixed face contents\n" + , b->entitynum, b->brushnum); + Log_Print("texture name = %s\n", texinfo[s->texinfo].texture); + break; + } + } + + // if any side is translucent, mark the contents + // and change solid to window + if ( trans & (SURF_TRANS33|SURF_TRANS66) ) + { + contents |= CONTENTS_Q2TRANSLUCENT; + if (contents & CONTENTS_SOLID) + { + contents &= ~CONTENTS_SOLID; + contents |= CONTENTS_WINDOW; + } + } + + return contents; +} + +#ifdef ME + +#define BBOX_NORMAL_EPSILON 0.0001 + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void MakeAreaPortalBrush(mapbrush_t *brush) +{ + int sn; + side_t *s; + + brush->contents = CONTENTS_AREAPORTAL; + + for (sn = 0; sn < brush->numsides; sn++) + { + s = brush->original_sides + sn; + //make sure the surfaces are not hint or skip + s->surf &= ~(SURF_HINT|SURF_SKIP); + // + s->texinfo = 0; + s->contents = CONTENTS_AREAPORTAL; + } //end for +} //end of the function MakeAreaPortalBrush +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void DPlanes2MapPlanes(void) +{ + int i; + + for (i = 0; i < numplanes; i++) + { + dplanes2mapplanes[i] = FindFloatPlane(dplanes[i].normal, dplanes[i].dist); + } //end for +} //end of the function DPlanes2MapPlanes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void MarkVisibleBrushSides(mapbrush_t *brush) +{ + int n, i, planenum; + side_t *side; + dface_t *face; + // + for (n = 0; n < brush->numsides; n++) + { + side = brush->original_sides + n; + //if this side is a bevel or the leaf number of the brush is unknown + if ((side->flags & SFL_BEVEL) || brush->leafnum < 0) + { + //this side is a valid splitter + side->flags |= SFL_VISIBLE; + continue; + } //end if + //assum this side will not be used as a splitter + side->flags &= ~SFL_VISIBLE; + //check if the side plane is used by a visible face + for (i = 0; i < numfaces; i++) + { + face = &dfaces[i]; + planenum = dplanes2mapplanes[face->planenum]; + if ((planenum & ~1) == (side->planenum & ~1)) + { + //this side is a valid splitter + side->flags |= SFL_VISIBLE; + } //end if + } //end for + } //end for +} //end of the function MarkVisibleBrushSides + +#endif //ME + +/* +================= +Q2_ParseBrush +================= +*/ +void Q2_ParseBrush (script_t *script, entity_t *mapent) +{ + mapbrush_t *b; + int i, j, k; + int mt; + side_t *side, *s2; + int planenum; + brush_texture_t td; + int planepts[3][3]; + token_t token; + + if (nummapbrushes >= MAX_MAPFILE_BRUSHES) + Error ("nummapbrushes == MAX_MAPFILE_BRUSHES"); + + b = &mapbrushes[nummapbrushes]; + b->original_sides = &brushsides[nummapbrushsides]; + b->entitynum = num_entities-1; + b->brushnum = nummapbrushes - mapent->firstbrush; + b->leafnum = -1; + + do + { + if (!PS_ReadToken(script, &token)) + break; + if (!strcmp(token.string, "}") ) + break; + + //IDBUG: mixed use of MAX_MAPFILE_? and MAX_MAP_? this could + // lead to out of bound indexing of the arrays + if (nummapbrushsides >= MAX_MAPFILE_BRUSHSIDES) + Error ("MAX_MAPFILE_BRUSHSIDES"); + side = &brushsides[nummapbrushsides]; + + //read the three point plane definition + for (i = 0; i < 3; i++) + { + if (i != 0) PS_ExpectTokenString(script, "("); + for (j = 0; j < 3; j++) + { + PS_ExpectAnyToken(script, &token); + planepts[i][j] = atof(token.string); + } //end for + PS_ExpectTokenString(script, ")"); + } //end for + + // + //read the texturedef + // + PS_ExpectAnyToken(script, &token); + strcpy(td.name, token.string); + + PS_ExpectAnyToken(script, &token); + td.shift[0] = atol(token.string); + PS_ExpectAnyToken(script, &token); + td.shift[1] = atol(token.string); + PS_ExpectAnyToken(script, &token); + td.rotate = atol(token.string); + PS_ExpectAnyToken(script, &token); + td.scale[0] = atof(token.string); + PS_ExpectAnyToken(script, &token); + td.scale[1] = atof(token.string); + + //find default flags and values + mt = FindMiptex (td.name); + td.flags = textureref[mt].flags; + td.value = textureref[mt].value; + side->contents = textureref[mt].contents; + side->surf = td.flags = textureref[mt].flags; + + //check if there's a number available + if (PS_CheckTokenType(script, TT_NUMBER, 0, &token)) + { + side->contents = token.intvalue; + PS_ExpectTokenType(script, TT_NUMBER, 0, &token); + side->surf = td.flags = token.intvalue; + PS_ExpectTokenType(script, TT_NUMBER, 0, &token); + td.value = token.intvalue; + } + + // translucent objects are automatically classified as detail + if (side->surf & (SURF_TRANS33|SURF_TRANS66) ) + side->contents |= CONTENTS_DETAIL; + if (side->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) + side->contents |= CONTENTS_DETAIL; + if (fulldetail) + side->contents &= ~CONTENTS_DETAIL; + if (!(side->contents & ((LAST_VISIBLE_CONTENTS-1) + | CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP|CONTENTS_MIST) ) ) + side->contents |= CONTENTS_SOLID; + + // hints and skips are never detail, and have no content + if (side->surf & (SURF_HINT|SURF_SKIP) ) + { + side->contents = 0; + side->surf &= ~CONTENTS_DETAIL; + } + +#ifdef ME + //for creating AAS... this side is textured + side->flags |= SFL_TEXTURED; +#endif //ME + // + // find the plane number + // + planenum = PlaneFromPoints (planepts[0], planepts[1], planepts[2]); + if (planenum == -1) + { + Log_Print("Entity %i, Brush %i: plane with no normal\n" + , b->entitynum, b->brushnum); + continue; + } + + // + // see if the plane has been used already + // + for (k=0 ; knumsides ; k++) + { + s2 = b->original_sides + k; + if (s2->planenum == planenum) + { + Log_Print("Entity %i, Brush %i: duplicate plane\n" + , b->entitynum, b->brushnum); + break; + } + if ( s2->planenum == (planenum^1) ) + { + Log_Print("Entity %i, Brush %i: mirrored plane\n" + , b->entitynum, b->brushnum); + break; + } + } + if (k != b->numsides) + continue; // duplicated + + // + // keep this side + // + + side = b->original_sides + b->numsides; + side->planenum = planenum; + side->texinfo = TexinfoForBrushTexture (&mapplanes[planenum], + &td, vec3_origin); + + // save the td off in case there is an origin brush and we + // have to recalculate the texinfo + side_brushtextures[nummapbrushsides] = td; + + nummapbrushsides++; + b->numsides++; + } while (1); + + // get the content for the entire brush + b->contents = Q2_BrushContents (b); + +#ifdef ME + if (BrushExists(b)) + { + c_squattbrushes++; + b->numsides = 0; + return; + } //end if + + if (create_aas) + { + //create AAS brushes, and add brush bevels + AAS_CreateMapBrushes(b, mapent, true); + //NOTE: if we return here then duplicate plane errors occur for the non world entities + return; + } //end if +#endif //ME + + // allow detail brushes to be removed + if (nodetail && (b->contents & CONTENTS_DETAIL) ) + { + b->numsides = 0; + return; + } + + // allow water brushes to be removed + if (nowater && (b->contents & (CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER)) ) + { + b->numsides = 0; + return; + } + + // create windings for sides and bounds for brush + MakeBrushWindings (b); + + // brushes that will not be visible at all will never be + // used as bsp splitters + if (b->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) + { + c_clipbrushes++; + for (i=0 ; inumsides ; i++) + b->original_sides[i].texinfo = TEXINFO_NODE; + } + + // + // origin brushes are removed, but they set + // the rotation origin for the rest of the brushes + // in the entity. After the entire entity is parsed, + // the planenums and texinfos will be adjusted for + // the origin brush + // + if (b->contents & CONTENTS_ORIGIN) + { + char string[32]; + vec3_t origin; + + if (num_entities == 1) + { + Error ("Entity %i, Brush %i: origin brushes not allowed in world" + , b->entitynum, b->brushnum); + return; + } + + VectorAdd (b->mins, b->maxs, origin); + VectorScale (origin, 0.5, origin); + + sprintf (string, "%i %i %i", (int)origin[0], (int)origin[1], (int)origin[2]); + SetKeyValue (&entities[b->entitynum], "origin", string); + + VectorCopy (origin, entities[b->entitynum].origin); + + // don't keep this brush + b->numsides = 0; + + return; + } + + AddBrushBevels(b); + + nummapbrushes++; + mapent->numbrushes++; +} + +/* +================ +Q2_MoveBrushesToWorld + +Takes all of the brushes from the current entity and +adds them to the world's brush list. + +Used by func_group and func_areaportal +================ +*/ +void Q2_MoveBrushesToWorld (entity_t *mapent) +{ + int newbrushes; + int worldbrushes; + mapbrush_t *temp; + int i; + + // this is pretty gross, because the brushes are expected to be + // in linear order for each entity + + newbrushes = mapent->numbrushes; + worldbrushes = entities[0].numbrushes; + + temp = GetMemory(newbrushes*sizeof(mapbrush_t)); + memcpy (temp, mapbrushes + mapent->firstbrush, newbrushes*sizeof(mapbrush_t)); + +#if 0 // let them keep their original brush numbers + for (i=0 ; inumbrushes = 0; +} + +/* +================ +Q2_ParseMapEntity +================ +*/ +qboolean Q2_ParseMapEntity(script_t *script) +{ + entity_t *mapent; + epair_t *e; + side_t *s; + int i, j; + int startbrush, startsides; + vec_t newdist; + mapbrush_t *b; + token_t token; + + if (!PS_ReadToken(script, &token)) return false; + + if (strcmp(token.string, "{") ) + Error ("ParseEntity: { not found"); + + if (num_entities == MAX_MAP_ENTITIES) + Error ("num_entities == MAX_MAP_ENTITIES"); + + startbrush = nummapbrushes; + startsides = nummapbrushsides; + + mapent = &entities[num_entities]; + num_entities++; + memset (mapent, 0, sizeof(*mapent)); + mapent->firstbrush = nummapbrushes; + mapent->numbrushes = 0; +// mapent->portalareas[0] = -1; +// mapent->portalareas[1] = -1; + + do + { + if (!PS_ReadToken(script, &token)) + { + Error("ParseEntity: EOF without closing brace"); + } //end if + if (!strcmp(token.string, "}")) break; + if (!strcmp(token.string, "{")) + { + Q2_ParseBrush(script, mapent); + } //end if + else + { + PS_UnreadLastToken(script); + e = ParseEpair(script); + e->next = mapent->epairs; + mapent->epairs = e; + } //end else + } while(1); + + GetVectorForKey(mapent, "origin", mapent->origin); + + // + // if there was an origin brush, offset all of the planes and texinfo + // + if (mapent->origin[0] || mapent->origin[1] || mapent->origin[2]) + { + for (i=0 ; inumbrushes ; i++) + { + b = &mapbrushes[mapent->firstbrush + i]; + for (j=0 ; jnumsides ; j++) + { + s = &b->original_sides[j]; + newdist = mapplanes[s->planenum].dist - + DotProduct (mapplanes[s->planenum].normal, mapent->origin); + s->planenum = FindFloatPlane (mapplanes[s->planenum].normal, newdist); + s->texinfo = TexinfoForBrushTexture (&mapplanes[s->planenum], + &side_brushtextures[s-brushsides], mapent->origin); + } + MakeBrushWindings (b); + } + } + + // group entities are just for editor convenience + // toss all brushes into the world entity + if (!strcmp ("func_group", ValueForKey (mapent, "classname"))) + { + Q2_MoveBrushesToWorld (mapent); + mapent->numbrushes = 0; + return true; + } + + // areaportal entities move their brushes, but don't eliminate + // the entity + if (!strcmp ("func_areaportal", ValueForKey (mapent, "classname"))) + { + char str[128]; + + if (mapent->numbrushes != 1) + Error ("Entity %i: func_areaportal can only be a single brush", num_entities-1); + + b = &mapbrushes[nummapbrushes-1]; + b->contents = CONTENTS_AREAPORTAL; + c_areaportals++; + mapent->areaportalnum = c_areaportals; + // set the portal number as "style" + sprintf (str, "%i", c_areaportals); + SetKeyValue (mapent, "style", str); + Q2_MoveBrushesToWorld (mapent); + return true; + } + + return true; +} + +//=================================================================== + +/* +================ +LoadMapFile +================ +*/ +void Q2_LoadMapFile(char *filename) +{ + int i; + script_t *script; + + Log_Print("-- Q2_LoadMapFile --\n"); +#ifdef ME + //loaded map type + loadedmaptype = MAPTYPE_QUAKE2; + //reset the map loading + ResetMapLoading(); +#endif //ME + + script = LoadScriptFile(filename); + if (!script) + { + Log_Print("couldn't open %s\n", filename); + return; + } //end if + //white spaces and escape characters inside a string are not allowed + SetScriptFlags(script, SCFL_NOSTRINGWHITESPACES | + SCFL_NOSTRINGESCAPECHARS | + SCFL_PRIMITIVE); + + nummapbrushsides = 0; + num_entities = 0; + + while (Q2_ParseMapEntity(script)) + { + } + + ClearBounds (map_mins, map_maxs); + for (i=0 ; i 4096) + continue; // no valid points + AddPointToBounds (mapbrushes[i].mins, map_mins, map_maxs); + AddPointToBounds (mapbrushes[i].maxs, map_mins, map_maxs); + } //end for + + PrintMapInfo(); + + //free the script + FreeScript(script); +// TestExpandBrushes (); + // + Q2_CreateMapTexinfo(); +} //end of the function Q2_LoadMapFile + +#ifdef ME //Begin MAP loading from BSP file +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Q2_SetLeafBrushesModelNumbers(int leafnum, int modelnum) +{ + int i, brushnum; + dleaf_t *leaf; + + leaf = &dleafs[leafnum]; + for (i = 0; i < leaf->numleafbrushes; i++) + { + brushnum = dleafbrushes[leaf->firstleafbrush + i]; + brushmodelnumbers[brushnum] = modelnum; + dbrushleafnums[brushnum] = leafnum; + } //end for +} //end of the function Q2_SetLeafBrushesModelNumbers +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Q2_InitNodeStack(void) +{ + nodestackptr = nodestack; + nodestacksize = 0; +} //end of the function Q2_InitNodeStack +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Q2_PushNodeStack(int num) +{ + *nodestackptr = num; + nodestackptr++; + nodestacksize++; + // + if (nodestackptr >= &nodestack[NODESTACKSIZE]) + { + Error("Q2_PushNodeStack: stack overflow\n"); + } //end if +} //end of the function Q2_PushNodeStack +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Q2_PopNodeStack(void) +{ + //if the stack is empty + if (nodestackptr <= nodestack) return -1; + //decrease stack pointer + nodestackptr--; + nodestacksize--; + //return the top value from the stack + return *nodestackptr; +} //end of the function Q2_PopNodeStack +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Q2_SetBrushModelNumbers(entity_t *mapent) +{ + int n, pn; + int leafnum; + + // + Q2_InitNodeStack(); + //head node (root) of the bsp tree + n = dmodels[mapent->modelnum].headnode; + pn = 0; + + do + { + //if we are in a leaf (negative node number) + if (n < 0) + { + //number of the leaf + leafnum = (-n) - 1; + //set the brush numbers + Q2_SetLeafBrushesModelNumbers(leafnum, mapent->modelnum); + //walk back into the tree to find a second child to continue with + for (pn = Q2_PopNodeStack(); pn >= 0; n = pn, pn = Q2_PopNodeStack()) + { + //if we took the first child at the parent node + if (dnodes[pn].children[0] == n) break; + } //end for + //if the stack wasn't empty (if not processed whole tree) + if (pn >= 0) + { + //push the parent node again + Q2_PushNodeStack(pn); + //we proceed with the second child of the parent node + n = dnodes[pn].children[1]; + } //end if + } //end if + else + { + //push the current node onto the stack + Q2_PushNodeStack(n); + //walk forward into the tree to the first child + n = dnodes[n].children[0]; + } //end else + } while(pn >= 0); +} //end of the function Q2_SetBrushModelNumbers +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Q2_BSPBrushToMapBrush(dbrush_t *bspbrush, entity_t *mapent) +{ + mapbrush_t *b; + int i, k, n; + side_t *side, *s2; + int planenum; + dbrushside_t *bspbrushside; + dplane_t *bspplane; + + if (nummapbrushes >= MAX_MAPFILE_BRUSHES) + Error ("nummapbrushes >= MAX_MAPFILE_BRUSHES"); + + b = &mapbrushes[nummapbrushes]; + b->original_sides = &brushsides[nummapbrushsides]; + b->entitynum = mapent-entities; + b->brushnum = nummapbrushes - mapent->firstbrush; + b->leafnum = dbrushleafnums[bspbrush - dbrushes]; + + for (n = 0; n < bspbrush->numsides; n++) + { + //pointer to the bsp brush side + bspbrushside = &dbrushsides[bspbrush->firstside + n]; + + if (nummapbrushsides >= MAX_MAPFILE_BRUSHSIDES) + { + Error ("MAX_MAPFILE_BRUSHSIDES"); + } //end if + //pointer to the map brush side + side = &brushsides[nummapbrushsides]; + //if the BSP brush side is textured + if (brushsidetextured[bspbrush->firstside + n]) side->flags |= SFL_TEXTURED; + else side->flags &= ~SFL_TEXTURED; + //ME: can get side contents and surf directly from BSP file + side->contents = bspbrush->contents; + //if the texinfo is TEXINFO_NODE + if (bspbrushside->texinfo < 0) side->surf = 0; + else side->surf = texinfo[bspbrushside->texinfo].flags; + + // translucent objects are automatically classified as detail + if (side->surf & (SURF_TRANS33|SURF_TRANS66) ) + side->contents |= CONTENTS_DETAIL; + if (side->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) + side->contents |= CONTENTS_DETAIL; + if (fulldetail) + side->contents &= ~CONTENTS_DETAIL; + if (!(side->contents & ((LAST_VISIBLE_CONTENTS-1) + | CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP|CONTENTS_MIST) ) ) + side->contents |= CONTENTS_SOLID; + + // hints and skips are never detail, and have no content + if (side->surf & (SURF_HINT|SURF_SKIP) ) + { + side->contents = 0; + side->surf &= ~CONTENTS_DETAIL; + } + + //ME: get a plane for this side + bspplane = &dplanes[bspbrushside->planenum]; + planenum = FindFloatPlane(bspplane->normal, bspplane->dist); + // + // see if the plane has been used already + // + //ME: this really shouldn't happen!!! + //ME: otherwise the bsp file is corrupted?? + //ME: still it seems to happen, maybe Johny Boy's + //ME: brush bevel adding is crappy ? + for (k = 0; k < b->numsides; k++) + { + s2 = b->original_sides + k; +// if (DotProduct (mapplanes[s2->planenum].normal, mapplanes[planenum].normal) > 0.999 +// && fabs(mapplanes[s2->planenum].dist - mapplanes[planenum].dist) < 0.01 ) + + if (s2->planenum == planenum) + { + Log_Print("Entity %i, Brush %i: duplicate plane\n" + , b->entitynum, b->brushnum); + break; + } + if ( s2->planenum == (planenum^1) ) + { + Log_Print("Entity %i, Brush %i: mirrored plane\n" + , b->entitynum, b->brushnum); + break; + } + } + if (k != b->numsides) + continue; // duplicated + + // + // keep this side + // + //ME: reset pointer to side, why? hell I dunno (pointer is set above already) + side = b->original_sides + b->numsides; + //ME: store the plane number + side->planenum = planenum; + //ME: texinfo is already stored when bsp is loaded + //NOTE: check for TEXINFO_NODE, otherwise crash in Q2_BrushContents + if (bspbrushside->texinfo < 0) side->texinfo = 0; + else side->texinfo = bspbrushside->texinfo; + + // save the td off in case there is an origin brush and we + // have to recalculate the texinfo + // ME: don't need to recalculate because it's already done + // (for non-world entities) in the BSP file +// side_brushtextures[nummapbrushsides] = td; + + nummapbrushsides++; + b->numsides++; + } //end for + + // get the content for the entire brush + b->contents = bspbrush->contents; + Q2_BrushContents(b); + + if (BrushExists(b)) + { + c_squattbrushes++; + b->numsides = 0; + return; + } //end if + + //if we're creating AAS + if (create_aas) + { + //create the AAS brushes from this brush, don't add brush bevels + AAS_CreateMapBrushes(b, mapent, false); + return; + } //end if + + // allow detail brushes to be removed + if (nodetail && (b->contents & CONTENTS_DETAIL) ) + { + b->numsides = 0; + return; + } //end if + + // allow water brushes to be removed + if (nowater && (b->contents & (CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER)) ) + { + b->numsides = 0; + return; + } //end if + + // create windings for sides and bounds for brush + MakeBrushWindings(b); + + //mark brushes without winding or with a tiny window as bevels + MarkBrushBevels(b); + + // brushes that will not be visible at all will never be + // used as bsp splitters + if (b->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) + { + c_clipbrushes++; + for (i = 0; i < b->numsides; i++) + b->original_sides[i].texinfo = TEXINFO_NODE; + } //end for + + // + // origin brushes are removed, but they set + // the rotation origin for the rest of the brushes + // in the entity. After the entire entity is parsed, + // the planenums and texinfos will be adjusted for + // the origin brush + // + //ME: not needed because the entities in the BSP file already + // have an origin set +// if (b->contents & CONTENTS_ORIGIN) +// { +// char string[32]; +// vec3_t origin; +// +// if (num_entities == 1) +// { +// Error ("Entity %i, Brush %i: origin brushes not allowed in world" +// , b->entitynum, b->brushnum); +// return; +// } +// +// VectorAdd (b->mins, b->maxs, origin); +// VectorScale (origin, 0.5, origin); +// +// sprintf (string, "%i %i %i", (int)origin[0], (int)origin[1], (int)origin[2]); +// SetKeyValue (&entities[b->entitynum], "origin", string); +// +// VectorCopy (origin, entities[b->entitynum].origin); +// +// // don't keep this brush +// b->numsides = 0; +// +// return; +// } + + //ME: the bsp brushes already have bevels, so we won't try to + // add them again (especially since Johny Boy's bevel adding might + // be crappy) +// AddBrushBevels(b); + + nummapbrushes++; + mapent->numbrushes++; +} //end of the function Q2_BSPBrushToMapBrush +//=========================================================================== +//=========================================================================== +void Q2_ParseBSPBrushes(entity_t *mapent) +{ + int i; + + //give all the brushes that belong to this entity the number of the + //BSP model used by this entity + Q2_SetBrushModelNumbers(mapent); + //now parse all the brushes with the correct mapent->modelnum + for (i = 0; i < numbrushes; i++) + { + if (brushmodelnumbers[i] == mapent->modelnum) + { + Q2_BSPBrushToMapBrush(&dbrushes[i], mapent); + } //end if + } //end for +} //end of the function Q2_ParseBSPBrushes +//=========================================================================== +//=========================================================================== +qboolean Q2_ParseBSPEntity(int entnum) +{ + entity_t *mapent; + char *model; + int startbrush, startsides; + + startbrush = nummapbrushes; + startsides = nummapbrushsides; + + mapent = &entities[entnum];//num_entities]; + mapent->firstbrush = nummapbrushes; + mapent->numbrushes = 0; + mapent->modelnum = -1; //-1 = no model + + model = ValueForKey(mapent, "model"); + if (model && strlen(model)) + { + if (*model != '*') + { + Error("Q2_ParseBSPEntity: model number without leading *"); + } //end if + //get the model number of this entity (skip the leading *) + mapent->modelnum = atoi(&model[1]); + } //end if + + GetVectorForKey(mapent, "origin", mapent->origin); + + //if this is the world entity it has model number zero + //the world entity has no model key + if (!strcmp("worldspawn", ValueForKey(mapent, "classname"))) + { + mapent->modelnum = 0; + } //end if + //if the map entity has a BSP model (a modelnum of -1 is used for + //entities that aren't using a BSP model) + if (mapent->modelnum >= 0) + { + //parse the bsp brushes + Q2_ParseBSPBrushes(mapent); + } //end if + // + //the origin of the entity is already taken into account + // + //func_group entities can't be in the bsp file + // + //check out the func_areaportal entities + if (!strcmp ("func_areaportal", ValueForKey (mapent, "classname"))) + { + c_areaportals++; + mapent->areaportalnum = c_areaportals; + return true; + } //end if + return true; +} //end of the function Q2_ParseBSPEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Q2_LoadMapFromBSP(char *filename, int offset, int length) +{ + int i; + + Log_Print("-- Q2_LoadMapFromBSP --\n"); + //loaded map type + loadedmaptype = MAPTYPE_QUAKE2; + + Log_Print("Loading map from %s...\n", filename); + //load the bsp file + Q2_LoadBSPFile(filename, offset, length); + + //create an index from bsp planes to map planes + //DPlanes2MapPlanes(); + //clear brush model numbers + for (i = 0; i < MAX_MAPFILE_BRUSHES; i++) + brushmodelnumbers[i] = -1; + + nummapbrushsides = 0; + num_entities = 0; + + Q2_ParseEntities(); + // + for (i = 0; i < num_entities; i++) + { + Q2_ParseBSPEntity(i); + } //end for + + //get the map mins and maxs from the world model + ClearBounds(map_mins, map_maxs); + for (i = 0; i < entities[0].numbrushes; i++) + { + if (mapbrushes[i].mins[0] > 4096) + continue; //no valid points + AddPointToBounds (mapbrushes[i].mins, map_mins, map_maxs); + AddPointToBounds (mapbrushes[i].maxs, map_mins, map_maxs); + } //end for + + PrintMapInfo(); + // + Q2_CreateMapTexinfo(); +} //end of the function Q2_LoadMapFromBSP + +void Q2_ResetMapLoading(void) +{ + //reset for map loading from bsp + memset(nodestack, 0, NODESTACKSIZE * sizeof(int)); + nodestackptr = NULL; + nodestacksize = 0; + memset(brushmodelnumbers, 0, MAX_MAPFILE_BRUSHES * sizeof(int)); +} //end of the function Q2_ResetMapLoading + +//End MAP loading from BSP file +#endif //ME + +//==================================================================== + +/* +================ +TestExpandBrushes + +Expands all the brush planes and saves a new map out +================ +*/ +void TestExpandBrushes (void) +{ + FILE *f; + side_t *s; + int i, j, bn; + winding_t *w; + char *name = "expanded.map"; + mapbrush_t *brush; + vec_t dist; + + Log_Print("writing %s\n", name); + f = fopen (name, "wb"); + if (!f) + Error ("Can't write %s\n", name); + + fprintf (f, "{\n\"classname\" \"worldspawn\"\n"); + + for (bn=0 ; bnnumsides ; i++) + { + s = brush->original_sides + i; + dist = mapplanes[s->planenum].dist; + for (j=0 ; j<3 ; j++) + dist += fabs( 16 * mapplanes[s->planenum].normal[j] ); + + w = BaseWindingForPlane (mapplanes[s->planenum].normal, dist); + + fprintf (f,"( %i %i %i ) ", (int)w->p[0][0], (int)w->p[0][1], (int)w->p[0][2]); + fprintf (f,"( %i %i %i ) ", (int)w->p[1][0], (int)w->p[1][1], (int)w->p[1][2]); + fprintf (f,"( %i %i %i ) ", (int)w->p[2][0], (int)w->p[2][1], (int)w->p[2][2]); + + fprintf (f, "%s 0 0 0 1 1\n", texinfo[s->texinfo].texture); + FreeWinding (w); + } + fprintf (f, "}\n"); + } + fprintf (f, "}\n"); + + fclose (f); + + Error ("can't proceed after expanding brushes"); +} //end of the function TestExpandBrushes + diff --git a/code/bspc/map_q3.c b/code/bspc/map_q3.c index 750b304..375100c 100755 --- a/code/bspc/map_q3.c +++ b/code/bspc/map_q3.c @@ -1,681 +1,681 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "qbsp.h" -#include "l_mem.h" -#include "../botlib/aasfile.h" //aas_bbox_t -#include "aas_store.h" //AAS_MAX_BBOXES -#include "aas_cfg.h" -#include "aas_map.h" //AAS_CreateMapBrushes -#include "l_bsp_q3.h" -#include "../qcommon/cm_patch.h" -#include "../game/surfaceflags.h" - -#define NODESTACKSIZE 1024 - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void PrintContents(int contents); - -int Q3_BrushContents(mapbrush_t *b) -{ - int contents, i, mixed, hint; - side_t *s; - - s = &b->original_sides[0]; - contents = s->contents; - // - mixed = false; - hint = false; - for (i = 1; i < b->numsides; i++) - { - s = &b->original_sides[i]; - if (s->contents != contents) mixed = true; - if (s->surf & (SURF_HINT|SURF_SKIP)) hint = true; - contents |= s->contents; - } //end for - // - if (hint) - { - if (contents) - { - Log_Write("WARNING: hint brush with contents: "); - PrintContents(contents); - Log_Write("\r\n"); - // - Log_Write("brush contents is: "); - PrintContents(b->contents); - Log_Write("\r\n"); - } //end if - return 0; - } //end if - //Log_Write("brush %d contents ", nummapbrushes); - //PrintContents(contents); - //Log_Write("\r\n"); - //remove ladder and fog contents - contents &= ~(CONTENTS_LADDER|CONTENTS_FOG); - // - if (mixed) - { - Log_Write("Entity %i, Brush %i: mixed face contents " - , b->entitynum, b->brushnum); - PrintContents(contents); - Log_Write("\r\n"); - // - Log_Write("brush contents is: "); - PrintContents(b->contents); - Log_Write("\r\n"); - // - if (contents & CONTENTS_DONOTENTER) return CONTENTS_DONOTENTER;//Log_Print("mixed contents with donotenter\n"); - /* - Log_Print("contents:"); PrintContents(contents); - Log_Print("\ncontents:"); PrintContents(s->contents); - Log_Print("\n"); - Log_Print("texture name = %s\n", texinfo[s->texinfo].texture); - */ - //if liquid brush - if (contents & (CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER)) - { - return (contents & (CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER)); - } //end if - if (contents & CONTENTS_PLAYERCLIP) return (contents & CONTENTS_PLAYERCLIP); - return (contents & CONTENTS_SOLID); - } //end if - /* - if (contents & CONTENTS_AREAPORTAL) - { - static int num; - Log_Write("Entity %i, Brush %i: area portal %d\r\n", b->entitynum, b->brushnum, num++); - } //end if*/ - if (contents == (contents & CONTENTS_STRUCTURAL)) - { - //Log_Print("brush %i is only structural\n", b->brushnum); - contents = 0; - } //end if - if (contents & CONTENTS_DONOTENTER) - { - Log_Print("brush %i is a donotenter brush, c = %X\n", b->brushnum, contents); - } //end if - return contents; -} //end of the function Q3_BrushContents -#define BBOX_NORMAL_EPSILON 0.0001 -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Q3_DPlanes2MapPlanes(void) -{ - int i; - - for (i = 0; i < q3_numplanes; i++) - { - dplanes2mapplanes[i] = FindFloatPlane(q3_dplanes[i].normal, q3_dplanes[i].dist); - } //end for -} //end of the function Q3_DPlanes2MapPlanes -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Q3_BSPBrushToMapBrush(q3_dbrush_t *bspbrush, entity_t *mapent) -{ - mapbrush_t *b; - int i, k, n; - side_t *side, *s2; - int planenum; - q3_dbrushside_t *bspbrushside; - q3_dplane_t *bspplane; - - if (nummapbrushes >= MAX_MAPFILE_BRUSHES) - Error ("nummapbrushes >= MAX_MAPFILE_BRUSHES"); - - b = &mapbrushes[nummapbrushes]; - b->original_sides = &brushsides[nummapbrushsides]; - b->entitynum = mapent-entities; - b->brushnum = nummapbrushes - mapent->firstbrush; - b->leafnum = dbrushleafnums[bspbrush - q3_dbrushes]; - - for (n = 0; n < bspbrush->numSides; n++) - { - //pointer to the bsp brush side - bspbrushside = &q3_dbrushsides[bspbrush->firstSide + n]; - - if (nummapbrushsides >= MAX_MAPFILE_BRUSHSIDES) - { - Error ("MAX_MAPFILE_BRUSHSIDES"); - } //end if - //pointer to the map brush side - side = &brushsides[nummapbrushsides]; - //if the BSP brush side is textured - if (q3_dbrushsidetextured[bspbrush->firstSide + n]) side->flags |= SFL_TEXTURED|SFL_VISIBLE; - else side->flags &= ~SFL_TEXTURED; - //NOTE: all Quake3 sides are assumed textured - //side->flags |= SFL_TEXTURED|SFL_VISIBLE; - // - if (bspbrushside->shaderNum < 0) - { - side->contents = 0; - side->surf = 0; - } //end if - else - { - side->contents = q3_dshaders[bspbrushside->shaderNum].contentFlags; - side->surf = q3_dshaders[bspbrushside->shaderNum].surfaceFlags; - if (strstr(q3_dshaders[bspbrushside->shaderNum].shader, "common/hint")) - { - //Log_Print("found hint side\n"); - side->surf |= SURF_HINT; - } //end if - } //end else - // - if (side->surf & SURF_NODRAW) - { - side->flags |= SFL_TEXTURED|SFL_VISIBLE; - } //end if - /* - if (side->contents & (CONTENTS_TRANSLUCENT|CONTENTS_STRUCTURAL)) - { - side->flags |= SFL_TEXTURED|SFL_VISIBLE; - } //end if*/ - - // hints and skips are never detail, and have no content - if (side->surf & (SURF_HINT|SURF_SKIP) ) - { - side->contents = 0; - //Log_Print("found hint brush side\n"); - } - /* - if ((side->surf & SURF_NODRAW) && (side->surf & SURF_NOIMPACT)) - { - side->contents = 0; - side->surf &= ~CONTENTS_DETAIL; - Log_Print("probably found hint brush in a BSP without hints being used\n"); - } //end if*/ - - //ME: get a plane for this side - bspplane = &q3_dplanes[bspbrushside->planeNum]; - planenum = FindFloatPlane(bspplane->normal, bspplane->dist); - // - // see if the plane has been used already - // - //ME: this really shouldn't happen!!! - //ME: otherwise the bsp file is corrupted?? - //ME: still it seems to happen, maybe Johny Boy's - //ME: brush bevel adding is crappy ? - for (k = 0; k < b->numsides; k++) - { - s2 = b->original_sides + k; -// if (DotProduct (mapplanes[s2->planenum].normal, mapplanes[planenum].normal) > 0.999 -// && fabs(mapplanes[s2->planenum].dist - mapplanes[planenum].dist) < 0.01 ) - - if (s2->planenum == planenum) - { - Log_Print("Entity %i, Brush %i: duplicate plane\n" - , b->entitynum, b->brushnum); - break; - } - if ( s2->planenum == (planenum^1) ) - { - Log_Print("Entity %i, Brush %i: mirrored plane\n" - , b->entitynum, b->brushnum); - break; - } - } - if (k != b->numsides) - continue; // duplicated - - // - // keep this side - // - //ME: reset pointer to side, why? hell I dunno (pointer is set above already) - side = b->original_sides + b->numsides; - //ME: store the plane number - side->planenum = planenum; - //ME: texinfo is already stored when bsp is loaded - //NOTE: check for TEXINFO_NODE, otherwise crash in Q3_BrushContents - //if (bspbrushside->texinfo < 0) side->texinfo = 0; - //else side->texinfo = bspbrushside->texinfo; - - // save the td off in case there is an origin brush and we - // have to recalculate the texinfo - // ME: don't need to recalculate because it's already done - // (for non-world entities) in the BSP file -// side_brushtextures[nummapbrushsides] = td; - - nummapbrushsides++; - b->numsides++; - } //end for - - // get the content for the entire brush - b->contents = q3_dshaders[bspbrush->shaderNum].contentFlags; - b->contents &= ~(CONTENTS_LADDER|CONTENTS_FOG|CONTENTS_STRUCTURAL); -// b->contents = Q3_BrushContents(b); - // - - if (BrushExists(b)) - { - c_squattbrushes++; - b->numsides = 0; - return; - } //end if - - //if we're creating AAS - if (create_aas) - { - //create the AAS brushes from this brush, don't add brush bevels - AAS_CreateMapBrushes(b, mapent, false); - return; - } //end if - - // allow detail brushes to be removed - if (nodetail && (b->contents & CONTENTS_DETAIL) ) - { - b->numsides = 0; - return; - } //end if - - // allow water brushes to be removed - if (nowater && (b->contents & (CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER)) ) - { - b->numsides = 0; - return; - } //end if - - // create windings for sides and bounds for brush - MakeBrushWindings(b); - - //mark brushes without winding or with a tiny window as bevels - MarkBrushBevels(b); - - // brushes that will not be visible at all will never be - // used as bsp splitters - if (b->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) - { - c_clipbrushes++; - for (i = 0; i < b->numsides; i++) - b->original_sides[i].texinfo = TEXINFO_NODE; - } //end for - - // - // origin brushes are removed, but they set - // the rotation origin for the rest of the brushes - // in the entity. After the entire entity is parsed, - // the planenums and texinfos will be adjusted for - // the origin brush - // - //ME: not needed because the entities in the BSP file already - // have an origin set -// if (b->contents & CONTENTS_ORIGIN) -// { -// char string[32]; -// vec3_t origin; -// -// if (num_entities == 1) -// { -// Error ("Entity %i, Brush %i: origin brushes not allowed in world" -// , b->entitynum, b->brushnum); -// return; -// } -// -// VectorAdd (b->mins, b->maxs, origin); -// VectorScale (origin, 0.5, origin); -// -// sprintf (string, "%i %i %i", (int)origin[0], (int)origin[1], (int)origin[2]); -// SetKeyValue (&entities[b->entitynum], "origin", string); -// -// VectorCopy (origin, entities[b->entitynum].origin); -// -// // don't keep this brush -// b->numsides = 0; -// -// return; -// } - - //ME: the bsp brushes already have bevels, so we won't try to - // add them again (especially since Johny Boy's bevel adding might - // be crappy) -// AddBrushBevels(b); - - nummapbrushes++; - mapent->numbrushes++; -} //end of the function Q3_BSPBrushToMapBrush -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Q3_ParseBSPBrushes(entity_t *mapent) -{ - int i; - - for (i = 0; i < q3_dmodels[mapent->modelnum].numBrushes; i++) - { - Q3_BSPBrushToMapBrush(&q3_dbrushes[q3_dmodels[mapent->modelnum].firstBrush + i], mapent); - } //end for -} //end of the function Q3_ParseBSPBrushes -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean Q3_ParseBSPEntity(int entnum) -{ - entity_t *mapent; - char *model; - int startbrush, startsides; - - startbrush = nummapbrushes; - startsides = nummapbrushsides; - - mapent = &entities[entnum];//num_entities]; - mapent->firstbrush = nummapbrushes; - mapent->numbrushes = 0; - mapent->modelnum = -1; //-1 = no BSP model - - model = ValueForKey(mapent, "model"); - if (model && strlen(model)) - { - if (*model == '*') - { - //get the model number of this entity (skip the leading *) - mapent->modelnum = atoi(&model[1]); - } //end if - } //end if - - GetVectorForKey(mapent, "origin", mapent->origin); - - //if this is the world entity it has model number zero - //the world entity has no model key - if (!strcmp("worldspawn", ValueForKey(mapent, "classname"))) - { - mapent->modelnum = 0; - } //end if - //if the map entity has a BSP model (a modelnum of -1 is used for - //entities that aren't using a BSP model) - if (mapent->modelnum >= 0) - { - //parse the bsp brushes - Q3_ParseBSPBrushes(mapent); - } //end if - // - //the origin of the entity is already taken into account - // - //func_group entities can't be in the bsp file - // - //check out the func_areaportal entities - if (!strcmp ("func_areaportal", ValueForKey (mapent, "classname"))) - { - c_areaportals++; - mapent->areaportalnum = c_areaportals; - return true; - } //end if - return true; -} //end of the function Q3_ParseBSPEntity -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -#define MAX_PATCH_VERTS 1024 - -void AAS_CreateCurveBrushes(void) -{ - int i, j, n, planenum, numcurvebrushes = 0; - q3_dsurface_t *surface; - q3_drawVert_t *dv_p; - vec3_t points[MAX_PATCH_VERTS]; - int width, height, c; - patchCollide_t *pc; - facet_t *facet; - mapbrush_t *brush; - side_t *side; - entity_t *mapent; - winding_t *winding; - - qprintf("nummapbrushsides = %d\n", nummapbrushsides); - mapent = &entities[0]; - for (i = 0; i < q3_numDrawSurfaces; i++) - { - surface = &q3_drawSurfaces[i]; - if ( ! surface->patchWidth ) continue; - // if the curve is not solid - if (!(q3_dshaders[surface->shaderNum].contentFlags & (CONTENTS_SOLID|CONTENTS_PLAYERCLIP))) - { - //Log_Print("skipped non-solid curve\n"); - continue; - } //end if - // if this curve should not be used for AAS - if ( q3_dshaders[surface->shaderNum].contentFlags & CONTENTS_NOBOTCLIP ) { - continue; - } - // - width = surface->patchWidth; - height = surface->patchHeight; - c = width * height; - if (c > MAX_PATCH_VERTS) - { - Error("ParseMesh: MAX_PATCH_VERTS"); - } //end if - - dv_p = q3_drawVerts + surface->firstVert; - 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]; - } //end for - // create the internal facet structure - pc = CM_GeneratePatchCollide(width, height, points); - // - for (j = 0; j < pc->numFacets; j++) - { - facet = &pc->facets[j]; - // - brush = &mapbrushes[nummapbrushes]; - brush->original_sides = &brushsides[nummapbrushsides]; - brush->entitynum = 0; - brush->brushnum = nummapbrushes - mapent->firstbrush; - // - brush->numsides = facet->numBorders + 2; - nummapbrushsides += brush->numsides; - brush->contents = CONTENTS_SOLID; - // - //qprintf("\r%6d curve brushes", nummapbrushsides);//++numcurvebrushes); - qprintf("\r%6d curve brushes", ++numcurvebrushes); - // - planenum = FindFloatPlane(pc->planes[facet->surfacePlane].plane, pc->planes[facet->surfacePlane].plane[3]); - // - side = &brush->original_sides[0]; - side->planenum = planenum; - side->contents = CONTENTS_SOLID; - side->flags |= SFL_TEXTURED|SFL_VISIBLE|SFL_CURVE; - side->surf = 0; - // - side = &brush->original_sides[1]; - if (create_aas) - { - //the plane is expanded later so it's not a problem that - //these first two opposite sides are coplanar - side->planenum = planenum ^ 1; - } //end if - else - { - side->planenum = FindFloatPlane(mapplanes[planenum^1].normal, mapplanes[planenum^1].dist + 1); - side->flags |= SFL_TEXTURED|SFL_VISIBLE; - } //end else - side->contents = CONTENTS_SOLID; - side->flags |= SFL_CURVE; - side->surf = 0; - // - winding = BaseWindingForPlane(mapplanes[side->planenum].normal, mapplanes[side->planenum].dist); - for (n = 0; n < facet->numBorders; n++) - { - //never use the surface plane as a border - if (facet->borderPlanes[n] == facet->surfacePlane) continue; - // - side = &brush->original_sides[2 + n]; - side->planenum = FindFloatPlane(pc->planes[facet->borderPlanes[n]].plane, pc->planes[facet->borderPlanes[n]].plane[3]); - if (facet->borderInward[n]) side->planenum ^= 1; - side->contents = CONTENTS_SOLID; - side->flags |= SFL_TEXTURED|SFL_CURVE; - side->surf = 0; - //chop the winding in place - if (winding) ChopWindingInPlace(&winding, mapplanes[side->planenum^1].normal, mapplanes[side->planenum^1].dist, 0.1); //CLIP_EPSILON); - } //end for - //VectorCopy(pc->bounds[0], brush->mins); - //VectorCopy(pc->bounds[1], brush->maxs); - if (!winding) - { - Log_Print("WARNING: AAS_CreateCurveBrushes: no winding\n"); - brush->numsides = 0; - continue; - } //end if - brush->original_sides[0].winding = winding; - WindingBounds(winding, brush->mins, brush->maxs); - for (n = 0; n < 3; n++) - { - //IDBUG: all the indexes into the mins and maxs were zero (not using i) - if (brush->mins[n] < -MAX_MAP_BOUNDS || brush->maxs[n] > MAX_MAP_BOUNDS) - { - Log_Print("entity %i, brush %i: bounds out of range\n", brush->entitynum, brush->brushnum); - Log_Print("brush->mins[%d] = %f, brush->maxs[%d] = %f\n", n, brush->mins[n], n, brush->maxs[n]); - brush->numsides = 0; //remove the brush - break; - } //end if - if (brush->mins[n] > MAX_MAP_BOUNDS || brush->maxs[n] < -MAX_MAP_BOUNDS) - { - Log_Print("entity %i, brush %i: no visible sides on brush\n", brush->entitynum, brush->brushnum); - Log_Print("brush->mins[%d] = %f, brush->maxs[%d] = %f\n", n, brush->mins[n], n, brush->maxs[n]); - brush->numsides = 0; //remove the brush - break; - } //end if - } //end for - if (create_aas) - { - //NOTE: brush bevels now already added - //AddBrushBevels(brush); - AAS_CreateMapBrushes(brush, mapent, false); - } //end if - else - { - // create windings for sides and bounds for brush - MakeBrushWindings(brush); - AddBrushBevels(brush); - nummapbrushes++; - mapent->numbrushes++; - } //end else - } //end for - } //end for - //qprintf("\r%6d curve brushes", nummapbrushsides);//++numcurvebrushes); - qprintf("\r%6d curve brushes\n", numcurvebrushes); -} //end of the function AAS_CreateCurveBrushes -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AAS_ExpandMapBrush(mapbrush_t *brush, vec3_t mins, vec3_t maxs); - -void Q3_LoadMapFromBSP(struct quakefile_s *qf) -{ - int i; - vec3_t mins = {-1,-1,-1}, maxs = {1, 1, 1}; - - Log_Print("-- Q3_LoadMapFromBSP --\n"); - //loaded map type - loadedmaptype = MAPTYPE_QUAKE3; - - Log_Print("Loading map from %s...\n", qf->filename); - //load the bsp file - Q3_LoadBSPFile(qf); - - //create an index from bsp planes to map planes - //DPlanes2MapPlanes(); - //clear brush model numbers - for (i = 0; i < MAX_MAPFILE_BRUSHES; i++) - brushmodelnumbers[i] = -1; - - nummapbrushsides = 0; - num_entities = 0; - - Q3_ParseEntities(); - // - for (i = 0; i < num_entities; i++) - { - Q3_ParseBSPEntity(i); - } //end for - - AAS_CreateCurveBrushes(); - //get the map mins and maxs from the world model - ClearBounds(map_mins, map_maxs); - for (i = 0; i < entities[0].numbrushes; i++) - { - if (mapbrushes[i].numsides <= 0) - continue; - AddPointToBounds (mapbrushes[i].mins, map_mins, map_maxs); - AddPointToBounds (mapbrushes[i].maxs, map_mins, map_maxs); - } //end for - /*/ - for (i = 0; i < nummapbrushes; i++) - { - //if (!mapbrushes[i].original_sides) continue; - //AddBrushBevels(&mapbrushes[i]); - //AAS_ExpandMapBrush(&mapbrushes[i], mins, maxs); - } //end for*/ - /* - for (i = 0; i < nummapbrushsides; i++) - { - Log_Write("side %d flags = %d", i, brushsides[i].flags); - } //end for - for (i = 0; i < nummapbrushes; i++) - { - Log_Write("brush contents: "); - PrintContents(mapbrushes[i].contents); - Log_Print("\n"); - } //end for*/ -} //end of the function Q3_LoadMapFromBSP -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Q3_ResetMapLoading(void) -{ - //reset for map loading from bsp - memset(nodestack, 0, NODESTACKSIZE * sizeof(int)); - nodestackptr = NULL; - nodestacksize = 0; - memset(brushmodelnumbers, 0, MAX_MAPFILE_BRUSHES * sizeof(int)); -} //end of the function Q3_ResetMapLoading - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "qbsp.h" +#include "l_mem.h" +#include "../botlib/aasfile.h" //aas_bbox_t +#include "aas_store.h" //AAS_MAX_BBOXES +#include "aas_cfg.h" +#include "aas_map.h" //AAS_CreateMapBrushes +#include "l_bsp_q3.h" +#include "../qcommon/cm_patch.h" +#include "../game/surfaceflags.h" + +#define NODESTACKSIZE 1024 + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void PrintContents(int contents); + +int Q3_BrushContents(mapbrush_t *b) +{ + int contents, i, mixed, hint; + side_t *s; + + s = &b->original_sides[0]; + contents = s->contents; + // + mixed = false; + hint = false; + for (i = 1; i < b->numsides; i++) + { + s = &b->original_sides[i]; + if (s->contents != contents) mixed = true; + if (s->surf & (SURF_HINT|SURF_SKIP)) hint = true; + contents |= s->contents; + } //end for + // + if (hint) + { + if (contents) + { + Log_Write("WARNING: hint brush with contents: "); + PrintContents(contents); + Log_Write("\r\n"); + // + Log_Write("brush contents is: "); + PrintContents(b->contents); + Log_Write("\r\n"); + } //end if + return 0; + } //end if + //Log_Write("brush %d contents ", nummapbrushes); + //PrintContents(contents); + //Log_Write("\r\n"); + //remove ladder and fog contents + contents &= ~(CONTENTS_LADDER|CONTENTS_FOG); + // + if (mixed) + { + Log_Write("Entity %i, Brush %i: mixed face contents " + , b->entitynum, b->brushnum); + PrintContents(contents); + Log_Write("\r\n"); + // + Log_Write("brush contents is: "); + PrintContents(b->contents); + Log_Write("\r\n"); + // + if (contents & CONTENTS_DONOTENTER) return CONTENTS_DONOTENTER;//Log_Print("mixed contents with donotenter\n"); + /* + Log_Print("contents:"); PrintContents(contents); + Log_Print("\ncontents:"); PrintContents(s->contents); + Log_Print("\n"); + Log_Print("texture name = %s\n", texinfo[s->texinfo].texture); + */ + //if liquid brush + if (contents & (CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER)) + { + return (contents & (CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER)); + } //end if + if (contents & CONTENTS_PLAYERCLIP) return (contents & CONTENTS_PLAYERCLIP); + return (contents & CONTENTS_SOLID); + } //end if + /* + if (contents & CONTENTS_AREAPORTAL) + { + static int num; + Log_Write("Entity %i, Brush %i: area portal %d\r\n", b->entitynum, b->brushnum, num++); + } //end if*/ + if (contents == (contents & CONTENTS_STRUCTURAL)) + { + //Log_Print("brush %i is only structural\n", b->brushnum); + contents = 0; + } //end if + if (contents & CONTENTS_DONOTENTER) + { + Log_Print("brush %i is a donotenter brush, c = %X\n", b->brushnum, contents); + } //end if + return contents; +} //end of the function Q3_BrushContents +#define BBOX_NORMAL_EPSILON 0.0001 +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Q3_DPlanes2MapPlanes(void) +{ + int i; + + for (i = 0; i < q3_numplanes; i++) + { + dplanes2mapplanes[i] = FindFloatPlane(q3_dplanes[i].normal, q3_dplanes[i].dist); + } //end for +} //end of the function Q3_DPlanes2MapPlanes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Q3_BSPBrushToMapBrush(q3_dbrush_t *bspbrush, entity_t *mapent) +{ + mapbrush_t *b; + int i, k, n; + side_t *side, *s2; + int planenum; + q3_dbrushside_t *bspbrushside; + q3_dplane_t *bspplane; + + if (nummapbrushes >= MAX_MAPFILE_BRUSHES) + Error ("nummapbrushes >= MAX_MAPFILE_BRUSHES"); + + b = &mapbrushes[nummapbrushes]; + b->original_sides = &brushsides[nummapbrushsides]; + b->entitynum = mapent-entities; + b->brushnum = nummapbrushes - mapent->firstbrush; + b->leafnum = dbrushleafnums[bspbrush - q3_dbrushes]; + + for (n = 0; n < bspbrush->numSides; n++) + { + //pointer to the bsp brush side + bspbrushside = &q3_dbrushsides[bspbrush->firstSide + n]; + + if (nummapbrushsides >= MAX_MAPFILE_BRUSHSIDES) + { + Error ("MAX_MAPFILE_BRUSHSIDES"); + } //end if + //pointer to the map brush side + side = &brushsides[nummapbrushsides]; + //if the BSP brush side is textured + if (q3_dbrushsidetextured[bspbrush->firstSide + n]) side->flags |= SFL_TEXTURED|SFL_VISIBLE; + else side->flags &= ~SFL_TEXTURED; + //NOTE: all Quake3 sides are assumed textured + //side->flags |= SFL_TEXTURED|SFL_VISIBLE; + // + if (bspbrushside->shaderNum < 0) + { + side->contents = 0; + side->surf = 0; + } //end if + else + { + side->contents = q3_dshaders[bspbrushside->shaderNum].contentFlags; + side->surf = q3_dshaders[bspbrushside->shaderNum].surfaceFlags; + if (strstr(q3_dshaders[bspbrushside->shaderNum].shader, "common/hint")) + { + //Log_Print("found hint side\n"); + side->surf |= SURF_HINT; + } //end if + } //end else + // + if (side->surf & SURF_NODRAW) + { + side->flags |= SFL_TEXTURED|SFL_VISIBLE; + } //end if + /* + if (side->contents & (CONTENTS_TRANSLUCENT|CONTENTS_STRUCTURAL)) + { + side->flags |= SFL_TEXTURED|SFL_VISIBLE; + } //end if*/ + + // hints and skips are never detail, and have no content + if (side->surf & (SURF_HINT|SURF_SKIP) ) + { + side->contents = 0; + //Log_Print("found hint brush side\n"); + } + /* + if ((side->surf & SURF_NODRAW) && (side->surf & SURF_NOIMPACT)) + { + side->contents = 0; + side->surf &= ~CONTENTS_DETAIL; + Log_Print("probably found hint brush in a BSP without hints being used\n"); + } //end if*/ + + //ME: get a plane for this side + bspplane = &q3_dplanes[bspbrushside->planeNum]; + planenum = FindFloatPlane(bspplane->normal, bspplane->dist); + // + // see if the plane has been used already + // + //ME: this really shouldn't happen!!! + //ME: otherwise the bsp file is corrupted?? + //ME: still it seems to happen, maybe Johny Boy's + //ME: brush bevel adding is crappy ? + for (k = 0; k < b->numsides; k++) + { + s2 = b->original_sides + k; +// if (DotProduct (mapplanes[s2->planenum].normal, mapplanes[planenum].normal) > 0.999 +// && fabs(mapplanes[s2->planenum].dist - mapplanes[planenum].dist) < 0.01 ) + + if (s2->planenum == planenum) + { + Log_Print("Entity %i, Brush %i: duplicate plane\n" + , b->entitynum, b->brushnum); + break; + } + if ( s2->planenum == (planenum^1) ) + { + Log_Print("Entity %i, Brush %i: mirrored plane\n" + , b->entitynum, b->brushnum); + break; + } + } + if (k != b->numsides) + continue; // duplicated + + // + // keep this side + // + //ME: reset pointer to side, why? hell I dunno (pointer is set above already) + side = b->original_sides + b->numsides; + //ME: store the plane number + side->planenum = planenum; + //ME: texinfo is already stored when bsp is loaded + //NOTE: check for TEXINFO_NODE, otherwise crash in Q3_BrushContents + //if (bspbrushside->texinfo < 0) side->texinfo = 0; + //else side->texinfo = bspbrushside->texinfo; + + // save the td off in case there is an origin brush and we + // have to recalculate the texinfo + // ME: don't need to recalculate because it's already done + // (for non-world entities) in the BSP file +// side_brushtextures[nummapbrushsides] = td; + + nummapbrushsides++; + b->numsides++; + } //end for + + // get the content for the entire brush + b->contents = q3_dshaders[bspbrush->shaderNum].contentFlags; + b->contents &= ~(CONTENTS_LADDER|CONTENTS_FOG|CONTENTS_STRUCTURAL); +// b->contents = Q3_BrushContents(b); + // + + if (BrushExists(b)) + { + c_squattbrushes++; + b->numsides = 0; + return; + } //end if + + //if we're creating AAS + if (create_aas) + { + //create the AAS brushes from this brush, don't add brush bevels + AAS_CreateMapBrushes(b, mapent, false); + return; + } //end if + + // allow detail brushes to be removed + if (nodetail && (b->contents & CONTENTS_DETAIL) ) + { + b->numsides = 0; + return; + } //end if + + // allow water brushes to be removed + if (nowater && (b->contents & (CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER)) ) + { + b->numsides = 0; + return; + } //end if + + // create windings for sides and bounds for brush + MakeBrushWindings(b); + + //mark brushes without winding or with a tiny window as bevels + MarkBrushBevels(b); + + // brushes that will not be visible at all will never be + // used as bsp splitters + if (b->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) + { + c_clipbrushes++; + for (i = 0; i < b->numsides; i++) + b->original_sides[i].texinfo = TEXINFO_NODE; + } //end for + + // + // origin brushes are removed, but they set + // the rotation origin for the rest of the brushes + // in the entity. After the entire entity is parsed, + // the planenums and texinfos will be adjusted for + // the origin brush + // + //ME: not needed because the entities in the BSP file already + // have an origin set +// if (b->contents & CONTENTS_ORIGIN) +// { +// char string[32]; +// vec3_t origin; +// +// if (num_entities == 1) +// { +// Error ("Entity %i, Brush %i: origin brushes not allowed in world" +// , b->entitynum, b->brushnum); +// return; +// } +// +// VectorAdd (b->mins, b->maxs, origin); +// VectorScale (origin, 0.5, origin); +// +// sprintf (string, "%i %i %i", (int)origin[0], (int)origin[1], (int)origin[2]); +// SetKeyValue (&entities[b->entitynum], "origin", string); +// +// VectorCopy (origin, entities[b->entitynum].origin); +// +// // don't keep this brush +// b->numsides = 0; +// +// return; +// } + + //ME: the bsp brushes already have bevels, so we won't try to + // add them again (especially since Johny Boy's bevel adding might + // be crappy) +// AddBrushBevels(b); + + nummapbrushes++; + mapent->numbrushes++; +} //end of the function Q3_BSPBrushToMapBrush +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Q3_ParseBSPBrushes(entity_t *mapent) +{ + int i; + + for (i = 0; i < q3_dmodels[mapent->modelnum].numBrushes; i++) + { + Q3_BSPBrushToMapBrush(&q3_dbrushes[q3_dmodels[mapent->modelnum].firstBrush + i], mapent); + } //end for +} //end of the function Q3_ParseBSPBrushes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean Q3_ParseBSPEntity(int entnum) +{ + entity_t *mapent; + char *model; + int startbrush, startsides; + + startbrush = nummapbrushes; + startsides = nummapbrushsides; + + mapent = &entities[entnum];//num_entities]; + mapent->firstbrush = nummapbrushes; + mapent->numbrushes = 0; + mapent->modelnum = -1; //-1 = no BSP model + + model = ValueForKey(mapent, "model"); + if (model && strlen(model)) + { + if (*model == '*') + { + //get the model number of this entity (skip the leading *) + mapent->modelnum = atoi(&model[1]); + } //end if + } //end if + + GetVectorForKey(mapent, "origin", mapent->origin); + + //if this is the world entity it has model number zero + //the world entity has no model key + if (!strcmp("worldspawn", ValueForKey(mapent, "classname"))) + { + mapent->modelnum = 0; + } //end if + //if the map entity has a BSP model (a modelnum of -1 is used for + //entities that aren't using a BSP model) + if (mapent->modelnum >= 0) + { + //parse the bsp brushes + Q3_ParseBSPBrushes(mapent); + } //end if + // + //the origin of the entity is already taken into account + // + //func_group entities can't be in the bsp file + // + //check out the func_areaportal entities + if (!strcmp ("func_areaportal", ValueForKey (mapent, "classname"))) + { + c_areaportals++; + mapent->areaportalnum = c_areaportals; + return true; + } //end if + return true; +} //end of the function Q3_ParseBSPEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#define MAX_PATCH_VERTS 1024 + +void AAS_CreateCurveBrushes(void) +{ + int i, j, n, planenum, numcurvebrushes = 0; + q3_dsurface_t *surface; + q3_drawVert_t *dv_p; + vec3_t points[MAX_PATCH_VERTS]; + int width, height, c; + patchCollide_t *pc; + facet_t *facet; + mapbrush_t *brush; + side_t *side; + entity_t *mapent; + winding_t *winding; + + qprintf("nummapbrushsides = %d\n", nummapbrushsides); + mapent = &entities[0]; + for (i = 0; i < q3_numDrawSurfaces; i++) + { + surface = &q3_drawSurfaces[i]; + if ( ! surface->patchWidth ) continue; + // if the curve is not solid + if (!(q3_dshaders[surface->shaderNum].contentFlags & (CONTENTS_SOLID|CONTENTS_PLAYERCLIP))) + { + //Log_Print("skipped non-solid curve\n"); + continue; + } //end if + // if this curve should not be used for AAS + if ( q3_dshaders[surface->shaderNum].contentFlags & CONTENTS_NOBOTCLIP ) { + continue; + } + // + width = surface->patchWidth; + height = surface->patchHeight; + c = width * height; + if (c > MAX_PATCH_VERTS) + { + Error("ParseMesh: MAX_PATCH_VERTS"); + } //end if + + dv_p = q3_drawVerts + surface->firstVert; + 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]; + } //end for + // create the internal facet structure + pc = CM_GeneratePatchCollide(width, height, points); + // + for (j = 0; j < pc->numFacets; j++) + { + facet = &pc->facets[j]; + // + brush = &mapbrushes[nummapbrushes]; + brush->original_sides = &brushsides[nummapbrushsides]; + brush->entitynum = 0; + brush->brushnum = nummapbrushes - mapent->firstbrush; + // + brush->numsides = facet->numBorders + 2; + nummapbrushsides += brush->numsides; + brush->contents = CONTENTS_SOLID; + // + //qprintf("\r%6d curve brushes", nummapbrushsides);//++numcurvebrushes); + qprintf("\r%6d curve brushes", ++numcurvebrushes); + // + planenum = FindFloatPlane(pc->planes[facet->surfacePlane].plane, pc->planes[facet->surfacePlane].plane[3]); + // + side = &brush->original_sides[0]; + side->planenum = planenum; + side->contents = CONTENTS_SOLID; + side->flags |= SFL_TEXTURED|SFL_VISIBLE|SFL_CURVE; + side->surf = 0; + // + side = &brush->original_sides[1]; + if (create_aas) + { + //the plane is expanded later so it's not a problem that + //these first two opposite sides are coplanar + side->planenum = planenum ^ 1; + } //end if + else + { + side->planenum = FindFloatPlane(mapplanes[planenum^1].normal, mapplanes[planenum^1].dist + 1); + side->flags |= SFL_TEXTURED|SFL_VISIBLE; + } //end else + side->contents = CONTENTS_SOLID; + side->flags |= SFL_CURVE; + side->surf = 0; + // + winding = BaseWindingForPlane(mapplanes[side->planenum].normal, mapplanes[side->planenum].dist); + for (n = 0; n < facet->numBorders; n++) + { + //never use the surface plane as a border + if (facet->borderPlanes[n] == facet->surfacePlane) continue; + // + side = &brush->original_sides[2 + n]; + side->planenum = FindFloatPlane(pc->planes[facet->borderPlanes[n]].plane, pc->planes[facet->borderPlanes[n]].plane[3]); + if (facet->borderInward[n]) side->planenum ^= 1; + side->contents = CONTENTS_SOLID; + side->flags |= SFL_TEXTURED|SFL_CURVE; + side->surf = 0; + //chop the winding in place + if (winding) ChopWindingInPlace(&winding, mapplanes[side->planenum^1].normal, mapplanes[side->planenum^1].dist, 0.1); //CLIP_EPSILON); + } //end for + //VectorCopy(pc->bounds[0], brush->mins); + //VectorCopy(pc->bounds[1], brush->maxs); + if (!winding) + { + Log_Print("WARNING: AAS_CreateCurveBrushes: no winding\n"); + brush->numsides = 0; + continue; + } //end if + brush->original_sides[0].winding = winding; + WindingBounds(winding, brush->mins, brush->maxs); + for (n = 0; n < 3; n++) + { + //IDBUG: all the indexes into the mins and maxs were zero (not using i) + if (brush->mins[n] < -MAX_MAP_BOUNDS || brush->maxs[n] > MAX_MAP_BOUNDS) + { + Log_Print("entity %i, brush %i: bounds out of range\n", brush->entitynum, brush->brushnum); + Log_Print("brush->mins[%d] = %f, brush->maxs[%d] = %f\n", n, brush->mins[n], n, brush->maxs[n]); + brush->numsides = 0; //remove the brush + break; + } //end if + if (brush->mins[n] > MAX_MAP_BOUNDS || brush->maxs[n] < -MAX_MAP_BOUNDS) + { + Log_Print("entity %i, brush %i: no visible sides on brush\n", brush->entitynum, brush->brushnum); + Log_Print("brush->mins[%d] = %f, brush->maxs[%d] = %f\n", n, brush->mins[n], n, brush->maxs[n]); + brush->numsides = 0; //remove the brush + break; + } //end if + } //end for + if (create_aas) + { + //NOTE: brush bevels now already added + //AddBrushBevels(brush); + AAS_CreateMapBrushes(brush, mapent, false); + } //end if + else + { + // create windings for sides and bounds for brush + MakeBrushWindings(brush); + AddBrushBevels(brush); + nummapbrushes++; + mapent->numbrushes++; + } //end else + } //end for + } //end for + //qprintf("\r%6d curve brushes", nummapbrushsides);//++numcurvebrushes); + qprintf("\r%6d curve brushes\n", numcurvebrushes); +} //end of the function AAS_CreateCurveBrushes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ExpandMapBrush(mapbrush_t *brush, vec3_t mins, vec3_t maxs); + +void Q3_LoadMapFromBSP(struct quakefile_s *qf) +{ + int i; + vec3_t mins = {-1,-1,-1}, maxs = {1, 1, 1}; + + Log_Print("-- Q3_LoadMapFromBSP --\n"); + //loaded map type + loadedmaptype = MAPTYPE_QUAKE3; + + Log_Print("Loading map from %s...\n", qf->filename); + //load the bsp file + Q3_LoadBSPFile(qf); + + //create an index from bsp planes to map planes + //DPlanes2MapPlanes(); + //clear brush model numbers + for (i = 0; i < MAX_MAPFILE_BRUSHES; i++) + brushmodelnumbers[i] = -1; + + nummapbrushsides = 0; + num_entities = 0; + + Q3_ParseEntities(); + // + for (i = 0; i < num_entities; i++) + { + Q3_ParseBSPEntity(i); + } //end for + + AAS_CreateCurveBrushes(); + //get the map mins and maxs from the world model + ClearBounds(map_mins, map_maxs); + for (i = 0; i < entities[0].numbrushes; i++) + { + if (mapbrushes[i].numsides <= 0) + continue; + AddPointToBounds (mapbrushes[i].mins, map_mins, map_maxs); + AddPointToBounds (mapbrushes[i].maxs, map_mins, map_maxs); + } //end for + /*/ + for (i = 0; i < nummapbrushes; i++) + { + //if (!mapbrushes[i].original_sides) continue; + //AddBrushBevels(&mapbrushes[i]); + //AAS_ExpandMapBrush(&mapbrushes[i], mins, maxs); + } //end for*/ + /* + for (i = 0; i < nummapbrushsides; i++) + { + Log_Write("side %d flags = %d", i, brushsides[i].flags); + } //end for + for (i = 0; i < nummapbrushes; i++) + { + Log_Write("brush contents: "); + PrintContents(mapbrushes[i].contents); + Log_Print("\n"); + } //end for*/ +} //end of the function Q3_LoadMapFromBSP +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Q3_ResetMapLoading(void) +{ + //reset for map loading from bsp + memset(nodestack, 0, NODESTACKSIZE * sizeof(int)); + nodestackptr = NULL; + nodestacksize = 0; + memset(brushmodelnumbers, 0, MAX_MAPFILE_BRUSHES * sizeof(int)); +} //end of the function Q3_ResetMapLoading + diff --git a/code/bspc/map_sin.c b/code/bspc/map_sin.c index 7794b8c..b6e67ff 100755 --- a/code/bspc/map_sin.c +++ b/code/bspc/map_sin.c @@ -1,1211 +1,1211 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -//----------------------------------------------------------------------------- -// -// $Logfile:: /MissionPack/code/bspc/map_sin.c $ - -#include "qbsp.h" -#include "l_bsp_sin.h" -#include "aas_map.h" //AAS_CreateMapBrushes - - -//==================================================================== - - -/* -=========== -Sin_BrushContents -=========== -*/ - -int Sin_BrushContents(mapbrush_t *b) -{ - int contents; - side_t *s; - int i; -#ifdef SIN - float trans = 0; -#else - int trans; -#endif - - s = &b->original_sides[0]; - contents = s->contents; - -#ifdef SIN - trans = sin_texinfo[s->texinfo].translucence; -#else - trans = texinfo[s->texinfo].flags; -#endif - for (i=1 ; inumsides ; i++, s++) - { - s = &b->original_sides[i]; -#ifdef SIN - trans += sin_texinfo[s->texinfo].translucence; -#else - trans |= texinfo[s->texinfo].flags; -#endif - if (s->contents != contents) - { -#ifdef SIN - if ( - ( s->contents & CONTENTS_DETAIL && !(contents & CONTENTS_DETAIL) ) || - ( !(s->contents & CONTENTS_DETAIL) && contents & CONTENTS_DETAIL ) - ) - { - s->contents |= CONTENTS_DETAIL; - contents |= CONTENTS_DETAIL; - continue; - } -#endif - printf ("Entity %i, Brush %i: mixed face contents\n" - , b->entitynum, b->brushnum); - break; - } - } - - -#ifdef SIN - if (contents & CONTENTS_FENCE) - { -// contents |= CONTENTS_TRANSLUCENT; - contents |= CONTENTS_DETAIL; - contents |= CONTENTS_DUMMYFENCE; - contents &= ~CONTENTS_SOLID; - contents &= ~CONTENTS_FENCE; - contents |= CONTENTS_WINDOW; - } -#endif - - // if any side is translucent, mark the contents - // and change solid to window -#ifdef SIN - if ( trans > 0 ) -#else - if ( trans & (SURF_TRANS33|SURF_TRANS66) ) -#endif - { - contents |= CONTENTS_Q2TRANSLUCENT; - if (contents & CONTENTS_SOLID) - { - contents &= ~CONTENTS_SOLID; - contents |= CONTENTS_WINDOW; - } - } - - return contents; -} //*/ - - -//============================================================================ - - - -/* -================= -ParseBrush -================= -* / -void ParseBrush (entity_t *mapent) -{ - mapbrush_t *b; - int i,j, k; - int mt; - side_t *side, *s2; - int planenum; - brush_texture_t td; -#ifdef SIN - textureref_t newref; -#endif - int planepts[3][3]; - - if (nummapbrushes == MAX_MAP_BRUSHES) - Error ("nummapbrushes == MAX_MAP_BRUSHES"); - - b = &mapbrushes[nummapbrushes]; - b->original_sides = &brushsides[nummapbrushsides]; - b->entitynum = num_entities-1; - b->brushnum = nummapbrushes - mapent->firstbrush; - - do - { - if (!GetToken (true)) - break; - if (!strcmp (token, "}") ) - break; - - if (nummapbrushsides == MAX_MAP_BRUSHSIDES) - Error ("MAX_MAP_BRUSHSIDES"); - side = &brushsides[nummapbrushsides]; - - // read the three point plane definition - for (i=0 ; i<3 ; i++) - { - if (i != 0) - GetToken (true); - if (strcmp (token, "(") ) - Error ("parsing brush"); - - for (j=0 ; j<3 ; j++) - { - GetToken (false); - planepts[i][j] = atoi(token); - } - - GetToken (false); - if (strcmp (token, ")") ) - Error ("parsing brush"); - - } - - - // - // read the texturedef - // - GetToken (false); - strcpy (td.name, token); - - GetToken (false); - td.shift[0] = atoi(token); - GetToken (false); - td.shift[1] = atoi(token); - GetToken (false); -#ifdef SIN - td.rotate = atof(token); -#else - td.rotate = atoi(token); -#endif - GetToken (false); - td.scale[0] = atof(token); - GetToken (false); - td.scale[1] = atof(token); - - // find default flags and values - mt = FindMiptex (td.name); -#ifdef SIN - // clear out the masks on newref - memset(&newref,0,sizeof(newref)); - // copy over the name - strcpy( newref.name, td.name ); - - ParseSurfaceInfo( &newref ); - MergeRefs( &bsp_textureref[mt], &newref, &td.tref ); - side->contents = td.tref.contents; - side->surf = td.tref.flags; -#else - td.flags = textureref[mt].flags; - td.value = textureref[mt].value; - side->contents = textureref[mt].contents; - side->surf = td.flags = textureref[mt].flags; - - if (TokenAvailable()) - { - GetToken (false); - side->contents = atoi(token); - GetToken (false); - side->surf = td.flags = atoi(token); - GetToken (false); - td.value = atoi(token); - } -#endif - - // translucent objects are automatically classified as detail -#ifdef SIN - if ( td.tref.translucence > 0 ) -#else - if (side->surf & (SURF_TRANS33|SURF_TRANS66) ) -#endif - side->contents |= CONTENTS_DETAIL; - if (side->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) - side->contents |= CONTENTS_DETAIL; - if (fulldetail) - side->contents &= ~CONTENTS_DETAIL; - if (!(side->contents & ((LAST_VISIBLE_CONTENTS-1) - | CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP|CONTENTS_MIST) ) ) - side->contents |= CONTENTS_SOLID; - - // hints and skips are never detail, and have no content - if (side->surf & (SURF_HINT|SURF_SKIP) ) - { - side->contents = 0; -#ifndef SIN // I think this is a bug of some kind - side->surf &= ~CONTENTS_DETAIL; -#endif - } - - // - // find the plane number - // - planenum = PlaneFromPoints (planepts[0], planepts[1], planepts[2]); - if (planenum == -1) - { - printf ("Entity %i, Brush %i: plane with no normal\n" - , b->entitynum, b->brushnum); - continue; - } - - // - // see if the plane has been used already - // - for (k=0 ; knumsides ; k++) - { - s2 = b->original_sides + k; - if (s2->planenum == planenum) - { - printf ("Entity %i, Brush %i: duplicate plane\n" - , b->entitynum, b->brushnum); - break; - } - if ( s2->planenum == (planenum^1) ) - { - printf ("Entity %i, Brush %i: mirrored plane\n" - , b->entitynum, b->brushnum); - break; - } - } - if (k != b->numsides) - continue; // duplicated - - // - // keep this side - // - - side = b->original_sides + b->numsides; - side->planenum = planenum; -#ifdef SIN - side->texinfo = TexinfoForBrushTexture (&mapplanes[planenum], - &td, vec3_origin, &newref); - // - // save off lightinfo - // - side->lightinfo = LightinfoForBrushTexture ( &td ); -#else - side->texinfo = TexinfoForBrushTexture (&mapplanes[planenum], - &td, vec3_origin); - -#endif - - // save the td off in case there is an origin brush and we - // have to recalculate the texinfo - side_brushtextures[nummapbrushsides] = td; -#ifdef SIN - // save off the merged tref for animating textures - side_newrefs[nummapbrushsides] = newref; -#endif - - nummapbrushsides++; - b->numsides++; - } while (1); - - // get the content for the entire brush - b->contents = Sin_BrushContents (b); - - // allow detail brushes to be removed - if (nodetail && (b->contents & CONTENTS_DETAIL) ) - { - b->numsides = 0; - return; - } - - // allow water brushes to be removed - if (nowater && (b->contents & (CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER)) ) - { - b->numsides = 0; - return; - } - - // create windings for sides and bounds for brush - MakeBrushWindings (b); - - // brushes that will not be visible at all will never be - // used as bsp splitters - if (b->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) - { - c_clipbrushes++; - for (i=0 ; inumsides ; i++) - b->original_sides[i].texinfo = TEXINFO_NODE; - } - - // - // origin brushes are removed, but they set - // the rotation origin for the rest of the brushes - // in the entity. After the entire entity is parsed, - // the planenums and texinfos will be adjusted for - // the origin brush - // - if (b->contents & CONTENTS_ORIGIN) - { - char string[32]; - vec3_t origin; - - if (num_entities == 1) - { - Error ("Entity %i, Brush %i: origin brushes not allowed in world" - , b->entitynum, b->brushnum); - return; - } - - VectorAdd (b->mins, b->maxs, origin); - VectorScale (origin, 0.5, origin); - - sprintf (string, "%i %i %i", (int)origin[0], (int)origin[1], (int)origin[2]); - SetKeyValue (&entities[b->entitynum], "origin", string); - - VectorCopy (origin, entities[b->entitynum].origin); - - // don't keep this brush - b->numsides = 0; - - return; - } - - AddBrushBevels (b); - - nummapbrushes++; - mapent->numbrushes++; -} //*/ - -/* -================ -MoveBrushesToWorld - -Takes all of the brushes from the current entity and -adds them to the world's brush list. - -Used by func_group and func_areaportal -================ -* / -void MoveBrushesToWorld (entity_t *mapent) -{ - int newbrushes; - int worldbrushes; - mapbrush_t *temp; - int i; - - // this is pretty gross, because the brushes are expected to be - // in linear order for each entity - - newbrushes = mapent->numbrushes; - worldbrushes = entities[0].numbrushes; - - temp = malloc(newbrushes*sizeof(mapbrush_t)); - memcpy (temp, mapbrushes + mapent->firstbrush, newbrushes*sizeof(mapbrush_t)); - -#if 0 // let them keep their original brush numbers - for (i=0 ; inumbrushes = 0; -} //*/ - -/* -================ -ParseMapEntity -================ -* / -qboolean Sin_ParseMapEntity (void) -{ - entity_t *mapent; - epair_t *e; - side_t *s; - int i, j; - int startbrush, startsides; - vec_t newdist; - mapbrush_t *b; - - if (!GetToken (true)) - return false; - - if (strcmp (token, "{") ) - Error ("ParseEntity: { not found"); - - if (num_entities == MAX_MAP_ENTITIES) - Error ("num_entities == MAX_MAP_ENTITIES"); - - startbrush = nummapbrushes; - startsides = nummapbrushsides; - - mapent = &entities[num_entities]; - num_entities++; - memset (mapent, 0, sizeof(*mapent)); - mapent->firstbrush = nummapbrushes; - mapent->numbrushes = 0; -// mapent->portalareas[0] = -1; -// mapent->portalareas[1] = -1; - - do - { - if (!GetToken (true)) - Error ("ParseEntity: EOF without closing brace"); - if (!strcmp (token, "}") ) - break; - if (!strcmp (token, "{") ) - ParseBrush (mapent); - else - { - e = ParseEpair (); -#ifdef SIN - //HACK HACK HACK - // MED Gotta do this here - if ( !stricmp(e->key, "surfacefile") ) - { - if (!surfacefile[0]) - { - strcpy( surfacefile, e->value ); - } - printf ("--- ParseSurfaceFile ---\n"); - printf ("Surface script: %s\n", surfacefile); - if (!ParseSurfaceFile(surfacefile)) - { - Error ("Script file not found: %s\n", surfacefile); - } - } -#endif - e->next = mapent->epairs; - mapent->epairs = e; - } - } while (1); - -#ifdef SIN - if (!(strlen(ValueForKey(mapent, "origin"))) && ((num_entities-1) != 0)) - { - mapbrush_t *brush; - vec3_t origin; - char string[32]; - vec3_t mins, maxs; - int start, end; - // Calculate bounds - - start = mapent->firstbrush; - end = start + mapent->numbrushes; - ClearBounds (mins, maxs); - - for (j=start ; jnumsides) - continue; // not a real brush (origin brush) - shouldn't happen - AddPointToBounds (brush->mins, mins, maxs); - AddPointToBounds (brush->maxs, mins, maxs); - } - - // Set the origin to be the centroid of the entity. - VectorAdd ( mins, maxs, origin); - VectorScale( origin, 0.5f, origin ); - - sprintf (string, "%i %i %i", (int)origin[0], (int)origin[1], (int)origin[2]); - SetKeyValue ( mapent, "origin", string); -// qprintf("Setting origin to %s\n",string); - } -#endif - - GetVectorForKey (mapent, "origin", mapent->origin); - -#ifdef SIN - if ( - (!strcmp ("func_areaportal", ValueForKey (mapent, "classname"))) || - (!strcmp ("func_group", ValueForKey (mapent, "classname"))) || - (!strcmp ("detail", ValueForKey (mapent, "classname")) && !entitydetails) - ) - { - VectorClear( mapent->origin ); - } -#endif - - // - // if there was an origin brush, offset all of the planes and texinfo - // - if (mapent->origin[0] || mapent->origin[1] || mapent->origin[2]) - { - for (i=0 ; inumbrushes ; i++) - { - b = &mapbrushes[mapent->firstbrush + i]; - for (j=0 ; jnumsides ; j++) - { - s = &b->original_sides[j]; - newdist = mapplanes[s->planenum].dist - - DotProduct (mapplanes[s->planenum].normal, mapent->origin); - s->planenum = FindFloatPlane (mapplanes[s->planenum].normal, newdist); -#ifdef SIN - s->texinfo = TexinfoForBrushTexture (&mapplanes[s->planenum], - &side_brushtextures[s-brushsides], mapent->origin, &side_newrefs[s-brushsides]); - // - // save off lightinfo - // - s->lightinfo = LightinfoForBrushTexture ( &side_brushtextures[s-brushsides] ); -#else - s->texinfo = TexinfoForBrushTexture (&mapplanes[s->planenum], - &side_brushtextures[s-brushsides], mapent->origin); -#endif - } - MakeBrushWindings (b); - } - } - - // group entities are just for editor convenience - // toss all brushes into the world entity - if (!strcmp ("func_group", ValueForKey (mapent, "classname"))) - { - MoveBrushesToWorld (mapent); - mapent->numbrushes = 0; - mapent->wasdetail = true; - FreeValueKeys( mapent ); - return true; - } -#ifdef SIN - // detail entities are just for editor convenience - // toss all brushes into the world entity as detail brushes - if (!strcmp ("detail", ValueForKey (mapent, "classname")) && !entitydetails) - { - for (i=0 ; inumbrushes ; i++) - { - int j; - side_t * s; - b = &mapbrushes[mapent->firstbrush + i]; - if (nodetail) - { - b->numsides = 0; - continue; - } - if (!fulldetail) - { - // set the contents for the entire brush - b->contents |= CONTENTS_DETAIL; - // set the contents in the sides as well - for (j=0, s=b->original_sides ; jnumsides ; j++,s++) - { - s->contents |= CONTENTS_DETAIL; - } - } - else - { - // set the contents for the entire brush - b->contents |= CONTENTS_SOLID; - // set the contents in the sides as well - for (j=0, s=b->original_sides ; jnumsides ; j++,s++) - { - s->contents |= CONTENTS_SOLID; - } - } - } - MoveBrushesToWorld (mapent); - mapent->wasdetail = true; - FreeValueKeys( mapent ); - // kill off the entity - // num_entities--; - return true; - } -#endif - - // areaportal entities move their brushes, but don't eliminate - // the entity - if (!strcmp ("func_areaportal", ValueForKey (mapent, "classname"))) - { - char str[128]; - - if (mapent->numbrushes != 1) - Error ("Entity %i: func_areaportal can only be a single brush", num_entities-1); - - b = &mapbrushes[nummapbrushes-1]; - b->contents = CONTENTS_AREAPORTAL; - c_areaportals++; - mapent->areaportalnum = c_areaportals; - // set the portal number as "style" - sprintf (str, "%i", c_areaportals); - SetKeyValue (mapent, "style", str); - MoveBrushesToWorld (mapent); - return true; - } - - return true; -} //end of the function Sin_ParseMapEntity */ - -//=================================================================== - -/* -================ -LoadMapFile -================ -* / -void Sin_LoadMapFile (char *filename) -{ - int i; -#ifdef SIN - int num_detailsides=0; - int num_detailbrushes=0; - int num_worldsides=0; - int num_worldbrushes=0; - int j,k; -#endif - - qprintf ("--- LoadMapFile ---\n"); - - LoadScriptFile (filename); - - nummapbrushsides = 0; - num_entities = 0; - - while (ParseMapEntity ()) - { - } - - ClearBounds (map_mins, map_maxs); - for (i=0 ; i 4096) - continue; // no valid points - AddPointToBounds (mapbrushes[i].mins, map_mins, map_maxs); - AddPointToBounds (mapbrushes[i].maxs, map_mins, map_maxs); - } -#ifdef SIN - for (j=0; jnumsides && b->contents & CONTENTS_DETAIL) - num_detailbrushes++; - else if (b->numsides) - num_worldbrushes++; - for (k=0, s=b->original_sides ; knumsides ; k++,s++) - { - if (s->contents & CONTENTS_DETAIL) - num_detailsides++; - else - num_worldsides++; - } - } - } -#endif - - qprintf ("%5i brushes\n", nummapbrushes); - qprintf ("%5i clipbrushes\n", c_clipbrushes); - qprintf ("%5i total sides\n", nummapbrushsides); - qprintf ("%5i boxbevels\n", c_boxbevels); - qprintf ("%5i edgebevels\n", c_edgebevels); - qprintf ("%5i entities\n", num_entities); - qprintf ("%5i planes\n", nummapplanes); - qprintf ("%5i areaportals\n", c_areaportals); - qprintf ("size: %5.0f,%5.0f,%5.0f to %5.0f,%5.0f,%5.0f\n", map_mins[0],map_mins[1],map_mins[2], - map_maxs[0],map_maxs[1],map_maxs[2]); -#ifdef SIN - qprintf ("%5i detailbrushes\n", num_detailbrushes); - qprintf ("%5i worldbrushes\n", num_worldbrushes); - qprintf ("%5i detailsides\n", num_detailsides); - qprintf ("%5i worldsides\n", num_worldsides); -#endif - -} //end of the function Sin_LoadMap */ - - -#ifdef ME //Begin MAP loading from BSP file -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Sin_CreateMapTexinfo(void) -{ - int i; - vec_t defaultvec[4] = {1, 0, 0, 0}; - - memcpy(map_texinfo[0].vecs[0], defaultvec, sizeof(defaultvec)); - memcpy(map_texinfo[0].vecs[1], defaultvec, sizeof(defaultvec)); - map_texinfo[0].flags = 0; - map_texinfo[0].value = 0; - strcpy(map_texinfo[0].texture, "generic/misc/red"); //no texture - map_texinfo[0].nexttexinfo = -1; - for (i = 1; i < sin_numtexinfo; i++) - { - memcpy(map_texinfo[i].vecs, sin_texinfo[i].vecs, sizeof(float) * 2 * 4); - map_texinfo[i].flags = sin_texinfo[i].flags; - map_texinfo[i].value = 0; - strcpy(map_texinfo[i].texture, sin_texinfo[i].texture); - map_texinfo[i].nexttexinfo = -1; - } //end for -} //end of the function Sin_CreateMapTexinfo -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Sin_SetLeafBrushesModelNumbers(int leafnum, int modelnum) -{ - int i, brushnum; - sin_dleaf_t *leaf; - - leaf = &sin_dleafs[leafnum]; - for (i = 0; i < leaf->numleafbrushes; i++) - { - brushnum = sin_dleafbrushes[leaf->firstleafbrush + i]; - brushmodelnumbers[brushnum] = modelnum; - dbrushleafnums[brushnum] = leafnum; - } //end for -} //end of the function Sin_SetLeafBrushesModelNumbers -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Sin_InitNodeStack(void) -{ - nodestackptr = nodestack; - nodestacksize = 0; -} //end of the function Sin_InitNodeStack -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Sin_PushNodeStack(int num) -{ - *nodestackptr = num; - nodestackptr++; - nodestacksize++; - // - if (nodestackptr >= &nodestack[NODESTACKSIZE]) - { - Error("Sin_PushNodeStack: stack overflow\n"); - } //end if -} //end of the function Sin_PushNodeStack -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int Sin_PopNodeStack(void) -{ - //if the stack is empty - if (nodestackptr <= nodestack) return -1; - //decrease stack pointer - nodestackptr--; - nodestacksize--; - //return the top value from the stack - return *nodestackptr; -} //end of the function Sin_PopNodeStack -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Sin_SetBrushModelNumbers(entity_t *mapent) -{ - int n, pn; - int leafnum; - - // - Sin_InitNodeStack(); - //head node (root) of the bsp tree - n = sin_dmodels[mapent->modelnum].headnode; - pn = 0; - - do - { - //if we are in a leaf (negative node number) - if (n < 0) - { - //number of the leaf - leafnum = (-n) - 1; - //set the brush numbers - Sin_SetLeafBrushesModelNumbers(leafnum, mapent->modelnum); - //walk back into the tree to find a second child to continue with - for (pn = Sin_PopNodeStack(); pn >= 0; n = pn, pn = Sin_PopNodeStack()) - { - //if we took the first child at the parent node - if (sin_dnodes[pn].children[0] == n) break; - } //end for - //if the stack wasn't empty (if not processed whole tree) - if (pn >= 0) - { - //push the parent node again - Sin_PushNodeStack(pn); - //we proceed with the second child of the parent node - n = sin_dnodes[pn].children[1]; - } //end if - } //end if - else - { - //push the current node onto the stack - Sin_PushNodeStack(n); - //walk forward into the tree to the first child - n = sin_dnodes[n].children[0]; - } //end else - } while(pn >= 0); -} //end of the function Sin_SetBrushModelNumbers -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Sin_BSPBrushToMapBrush(sin_dbrush_t *bspbrush, entity_t *mapent) -{ - mapbrush_t *b; - int i, k, n; - side_t *side, *s2; - int planenum; - sin_dbrushside_t *bspbrushside; - sin_dplane_t *bspplane; - - if (nummapbrushes >= MAX_MAPFILE_BRUSHES) - Error ("nummapbrushes >= MAX_MAPFILE_BRUSHES"); - - b = &mapbrushes[nummapbrushes]; - b->original_sides = &brushsides[nummapbrushsides]; - b->entitynum = mapent-entities; - b->brushnum = nummapbrushes - mapent->firstbrush; - b->leafnum = dbrushleafnums[bspbrush - sin_dbrushes]; - - for (n = 0; n < bspbrush->numsides; n++) - { - //pointer to the bsp brush side - bspbrushside = &sin_dbrushsides[bspbrush->firstside + n]; - - if (nummapbrushsides >= MAX_MAPFILE_BRUSHSIDES) - { - Error ("MAX_MAPFILE_BRUSHSIDES"); - } //end if - //pointer to the map brush side - side = &brushsides[nummapbrushsides]; - //if the BSP brush side is textured - if (sin_dbrushsidetextured[bspbrush->firstside + n]) side->flags |= SFL_TEXTURED; - else side->flags &= ~SFL_TEXTURED; - //ME: can get side contents and surf directly from BSP file - side->contents = bspbrush->contents; - //if the texinfo is TEXINFO_NODE - if (bspbrushside->texinfo < 0) side->surf = 0; - else side->surf = sin_texinfo[bspbrushside->texinfo].flags; - - // translucent objects are automatically classified as detail - if (side->surf & (SURF_TRANS33|SURF_TRANS66) ) - side->contents |= CONTENTS_DETAIL; - if (side->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) - side->contents |= CONTENTS_DETAIL; - if (fulldetail) - side->contents &= ~CONTENTS_DETAIL; - if (!(side->contents & ((LAST_VISIBLE_CONTENTS-1) - | CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP|CONTENTS_MIST) ) ) - side->contents |= CONTENTS_SOLID; - - // hints and skips are never detail, and have no content - if (side->surf & (SURF_HINT|SURF_SKIP) ) - { - side->contents = 0; - side->surf &= ~CONTENTS_DETAIL; - } - - //ME: get a plane for this side - bspplane = &sin_dplanes[bspbrushside->planenum]; - planenum = FindFloatPlane(bspplane->normal, bspplane->dist); - // - // see if the plane has been used already - // - //ME: this really shouldn't happen!!! - //ME: otherwise the bsp file is corrupted?? - //ME: still it seems to happen, maybe Johny Boy's - //ME: brush bevel adding is crappy ? - for (k = 0; k < b->numsides; k++) - { - s2 = b->original_sides + k; - if (s2->planenum == planenum) - { - Log_Print("Entity %i, Brush %i: duplicate plane\n" - , b->entitynum, b->brushnum); - break; - } - if ( s2->planenum == (planenum^1) ) - { - Log_Print("Entity %i, Brush %i: mirrored plane\n" - , b->entitynum, b->brushnum); - break; - } - } - if (k != b->numsides) - continue; // duplicated - - // - // keep this side - // - //ME: reset pointer to side, why? hell I dunno (pointer is set above already) - side = b->original_sides + b->numsides; - //ME: store the plane number - side->planenum = planenum; - //ME: texinfo is already stored when bsp is loaded - //NOTE: check for TEXINFO_NODE, otherwise crash in Sin_BrushContents - if (bspbrushside->texinfo < 0) side->texinfo = 0; - else side->texinfo = bspbrushside->texinfo; - - // save the td off in case there is an origin brush and we - // have to recalculate the texinfo - // ME: don't need to recalculate because it's already done - // (for non-world entities) in the BSP file -// side_brushtextures[nummapbrushsides] = td; - - nummapbrushsides++; - b->numsides++; - } //end for - - // get the content for the entire brush - b->contents = bspbrush->contents; - Sin_BrushContents(b); - - if (BrushExists(b)) - { - c_squattbrushes++; - b->numsides = 0; - return; - } //end if - - //if we're creating AAS - if (create_aas) - { - //create the AAS brushes from this brush, don't add brush bevels - AAS_CreateMapBrushes(b, mapent, false); - return; - } //end if - - // allow detail brushes to be removed - if (nodetail && (b->contents & CONTENTS_DETAIL) ) - { - b->numsides = 0; - return; - } //end if - - // allow water brushes to be removed - if (nowater && (b->contents & (CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER)) ) - { - b->numsides = 0; - return; - } //end if - - // create windings for sides and bounds for brush - MakeBrushWindings(b); - - //mark brushes without winding or with a tiny window as bevels - MarkBrushBevels(b); - - // brushes that will not be visible at all will never be - // used as bsp splitters - if (b->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) - { - c_clipbrushes++; - for (i = 0; i < b->numsides; i++) - b->original_sides[i].texinfo = TEXINFO_NODE; - } //end for - - // - // origin brushes are removed, but they set - // the rotation origin for the rest of the brushes - // in the entity. After the entire entity is parsed, - // the planenums and texinfos will be adjusted for - // the origin brush - // - //ME: not needed because the entities in the BSP file already - // have an origin set -// if (b->contents & CONTENTS_ORIGIN) -// { -// char string[32]; -// vec3_t origin; -// -// if (num_entities == 1) -// { -// Error ("Entity %i, Brush %i: origin brushes not allowed in world" -// , b->entitynum, b->brushnum); -// return; -// } -// -// VectorAdd (b->mins, b->maxs, origin); -// VectorScale (origin, 0.5, origin); -// -// sprintf (string, "%i %i %i", (int)origin[0], (int)origin[1], (int)origin[2]); -// SetKeyValue (&entities[b->entitynum], "origin", string); -// -// VectorCopy (origin, entities[b->entitynum].origin); -// -// // don't keep this brush -// b->numsides = 0; -// -// return; -// } - - //ME: the bsp brushes already have bevels, so we won't try to - // add them again (especially since Johny Boy's bevel adding might - // be crappy) -// AddBrushBevels(b); - - nummapbrushes++; - mapent->numbrushes++; -} //end of the function Sin_BSPBrushToMapBrush -//=========================================================================== -//=========================================================================== -void Sin_ParseBSPBrushes(entity_t *mapent) -{ - int i, testnum = 0; - - //give all the brushes that belong to this entity the number of the - //BSP model used by this entity - Sin_SetBrushModelNumbers(mapent); - //now parse all the brushes with the correct mapent->modelnum - for (i = 0; i < sin_numbrushes; i++) - { - if (brushmodelnumbers[i] == mapent->modelnum) - { - testnum++; - Sin_BSPBrushToMapBrush(&sin_dbrushes[i], mapent); - } //end if - } //end for -} //end of the function Sin_ParseBSPBrushes -//=========================================================================== -//=========================================================================== -qboolean Sin_ParseBSPEntity(int entnum) -{ - entity_t *mapent; - char *model; - int startbrush, startsides; - - startbrush = nummapbrushes; - startsides = nummapbrushsides; - - mapent = &entities[entnum];//num_entities]; - mapent->firstbrush = nummapbrushes; - mapent->numbrushes = 0; - mapent->modelnum = -1; //-1 = no model - - model = ValueForKey(mapent, "model"); - if (model && *model == '*') - { - mapent->modelnum = atoi(&model[1]); - //Log_Print("model = %s\n", model); - //Log_Print("mapent->modelnum = %d\n", mapent->modelnum); - } //end if - - GetVectorForKey(mapent, "origin", mapent->origin); - - //if this is the world entity it has model number zero - //the world entity has no model key - if (!strcmp("worldspawn", ValueForKey(mapent, "classname"))) - { - mapent->modelnum = 0; - } //end if - //if the map entity has a BSP model (a modelnum of -1 is used for - //entities that aren't using a BSP model) - if (mapent->modelnum >= 0) - { - //parse the bsp brushes - Sin_ParseBSPBrushes(mapent); - } //end if - // - //the origin of the entity is already taken into account - // - //func_group entities can't be in the bsp file - // - //check out the func_areaportal entities - if (!strcmp ("func_areaportal", ValueForKey (mapent, "classname"))) - { - c_areaportals++; - mapent->areaportalnum = c_areaportals; - return true; - } //end if - return true; -} //end of the function Sin_ParseBSPEntity -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Sin_LoadMapFromBSP(char *filename, int offset, int length) -{ - int i; - - Log_Print("-- Sin_LoadMapFromBSP --\n"); - //loaded map type - loadedmaptype = MAPTYPE_SIN; - - Log_Print("Loading map from %s...\n", filename); - //load the bsp file - Sin_LoadBSPFile(filename, offset, length); - - //create an index from bsp planes to map planes - //DPlanes2MapPlanes(); - //clear brush model numbers - for (i = 0; i < MAX_MAPFILE_BRUSHES; i++) - brushmodelnumbers[i] = -1; - - nummapbrushsides = 0; - num_entities = 0; - - Sin_ParseEntities(); - // - for (i = 0; i < num_entities; i++) - { - Sin_ParseBSPEntity(i); - } //end for - - //get the map mins and maxs from the world model - ClearBounds(map_mins, map_maxs); - for (i = 0; i < entities[0].numbrushes; i++) - { - if (mapbrushes[i].mins[0] > 4096) - continue; //no valid points - AddPointToBounds (mapbrushes[i].mins, map_mins, map_maxs); - AddPointToBounds (mapbrushes[i].maxs, map_mins, map_maxs); - } //end for - // - Sin_CreateMapTexinfo(); -} //end of the function Sin_LoadMapFromBSP - -void Sin_ResetMapLoading(void) -{ - //reset for map loading from bsp - memset(nodestack, 0, NODESTACKSIZE * sizeof(int)); - nodestackptr = NULL; - nodestacksize = 0; - memset(brushmodelnumbers, 0, MAX_MAPFILE_BRUSHES * sizeof(int)); -} //end of the function Sin_ResetMapLoading - -//End MAP loading from BSP file - -#endif //ME +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +//----------------------------------------------------------------------------- +// +// $Logfile:: /MissionPack/code/bspc/map_sin.c $ + +#include "qbsp.h" +#include "l_bsp_sin.h" +#include "aas_map.h" //AAS_CreateMapBrushes + + +//==================================================================== + + +/* +=========== +Sin_BrushContents +=========== +*/ + +int Sin_BrushContents(mapbrush_t *b) +{ + int contents; + side_t *s; + int i; +#ifdef SIN + float trans = 0; +#else + int trans; +#endif + + s = &b->original_sides[0]; + contents = s->contents; + +#ifdef SIN + trans = sin_texinfo[s->texinfo].translucence; +#else + trans = texinfo[s->texinfo].flags; +#endif + for (i=1 ; inumsides ; i++, s++) + { + s = &b->original_sides[i]; +#ifdef SIN + trans += sin_texinfo[s->texinfo].translucence; +#else + trans |= texinfo[s->texinfo].flags; +#endif + if (s->contents != contents) + { +#ifdef SIN + if ( + ( s->contents & CONTENTS_DETAIL && !(contents & CONTENTS_DETAIL) ) || + ( !(s->contents & CONTENTS_DETAIL) && contents & CONTENTS_DETAIL ) + ) + { + s->contents |= CONTENTS_DETAIL; + contents |= CONTENTS_DETAIL; + continue; + } +#endif + printf ("Entity %i, Brush %i: mixed face contents\n" + , b->entitynum, b->brushnum); + break; + } + } + + +#ifdef SIN + if (contents & CONTENTS_FENCE) + { +// contents |= CONTENTS_TRANSLUCENT; + contents |= CONTENTS_DETAIL; + contents |= CONTENTS_DUMMYFENCE; + contents &= ~CONTENTS_SOLID; + contents &= ~CONTENTS_FENCE; + contents |= CONTENTS_WINDOW; + } +#endif + + // if any side is translucent, mark the contents + // and change solid to window +#ifdef SIN + if ( trans > 0 ) +#else + if ( trans & (SURF_TRANS33|SURF_TRANS66) ) +#endif + { + contents |= CONTENTS_Q2TRANSLUCENT; + if (contents & CONTENTS_SOLID) + { + contents &= ~CONTENTS_SOLID; + contents |= CONTENTS_WINDOW; + } + } + + return contents; +} //*/ + + +//============================================================================ + + + +/* +================= +ParseBrush +================= +* / +void ParseBrush (entity_t *mapent) +{ + mapbrush_t *b; + int i,j, k; + int mt; + side_t *side, *s2; + int planenum; + brush_texture_t td; +#ifdef SIN + textureref_t newref; +#endif + int planepts[3][3]; + + if (nummapbrushes == MAX_MAP_BRUSHES) + Error ("nummapbrushes == MAX_MAP_BRUSHES"); + + b = &mapbrushes[nummapbrushes]; + b->original_sides = &brushsides[nummapbrushsides]; + b->entitynum = num_entities-1; + b->brushnum = nummapbrushes - mapent->firstbrush; + + do + { + if (!GetToken (true)) + break; + if (!strcmp (token, "}") ) + break; + + if (nummapbrushsides == MAX_MAP_BRUSHSIDES) + Error ("MAX_MAP_BRUSHSIDES"); + side = &brushsides[nummapbrushsides]; + + // read the three point plane definition + for (i=0 ; i<3 ; i++) + { + if (i != 0) + GetToken (true); + if (strcmp (token, "(") ) + Error ("parsing brush"); + + for (j=0 ; j<3 ; j++) + { + GetToken (false); + planepts[i][j] = atoi(token); + } + + GetToken (false); + if (strcmp (token, ")") ) + Error ("parsing brush"); + + } + + + // + // read the texturedef + // + GetToken (false); + strcpy (td.name, token); + + GetToken (false); + td.shift[0] = atoi(token); + GetToken (false); + td.shift[1] = atoi(token); + GetToken (false); +#ifdef SIN + td.rotate = atof(token); +#else + td.rotate = atoi(token); +#endif + GetToken (false); + td.scale[0] = atof(token); + GetToken (false); + td.scale[1] = atof(token); + + // find default flags and values + mt = FindMiptex (td.name); +#ifdef SIN + // clear out the masks on newref + memset(&newref,0,sizeof(newref)); + // copy over the name + strcpy( newref.name, td.name ); + + ParseSurfaceInfo( &newref ); + MergeRefs( &bsp_textureref[mt], &newref, &td.tref ); + side->contents = td.tref.contents; + side->surf = td.tref.flags; +#else + td.flags = textureref[mt].flags; + td.value = textureref[mt].value; + side->contents = textureref[mt].contents; + side->surf = td.flags = textureref[mt].flags; + + if (TokenAvailable()) + { + GetToken (false); + side->contents = atoi(token); + GetToken (false); + side->surf = td.flags = atoi(token); + GetToken (false); + td.value = atoi(token); + } +#endif + + // translucent objects are automatically classified as detail +#ifdef SIN + if ( td.tref.translucence > 0 ) +#else + if (side->surf & (SURF_TRANS33|SURF_TRANS66) ) +#endif + side->contents |= CONTENTS_DETAIL; + if (side->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) + side->contents |= CONTENTS_DETAIL; + if (fulldetail) + side->contents &= ~CONTENTS_DETAIL; + if (!(side->contents & ((LAST_VISIBLE_CONTENTS-1) + | CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP|CONTENTS_MIST) ) ) + side->contents |= CONTENTS_SOLID; + + // hints and skips are never detail, and have no content + if (side->surf & (SURF_HINT|SURF_SKIP) ) + { + side->contents = 0; +#ifndef SIN // I think this is a bug of some kind + side->surf &= ~CONTENTS_DETAIL; +#endif + } + + // + // find the plane number + // + planenum = PlaneFromPoints (planepts[0], planepts[1], planepts[2]); + if (planenum == -1) + { + printf ("Entity %i, Brush %i: plane with no normal\n" + , b->entitynum, b->brushnum); + continue; + } + + // + // see if the plane has been used already + // + for (k=0 ; knumsides ; k++) + { + s2 = b->original_sides + k; + if (s2->planenum == planenum) + { + printf ("Entity %i, Brush %i: duplicate plane\n" + , b->entitynum, b->brushnum); + break; + } + if ( s2->planenum == (planenum^1) ) + { + printf ("Entity %i, Brush %i: mirrored plane\n" + , b->entitynum, b->brushnum); + break; + } + } + if (k != b->numsides) + continue; // duplicated + + // + // keep this side + // + + side = b->original_sides + b->numsides; + side->planenum = planenum; +#ifdef SIN + side->texinfo = TexinfoForBrushTexture (&mapplanes[planenum], + &td, vec3_origin, &newref); + // + // save off lightinfo + // + side->lightinfo = LightinfoForBrushTexture ( &td ); +#else + side->texinfo = TexinfoForBrushTexture (&mapplanes[planenum], + &td, vec3_origin); + +#endif + + // save the td off in case there is an origin brush and we + // have to recalculate the texinfo + side_brushtextures[nummapbrushsides] = td; +#ifdef SIN + // save off the merged tref for animating textures + side_newrefs[nummapbrushsides] = newref; +#endif + + nummapbrushsides++; + b->numsides++; + } while (1); + + // get the content for the entire brush + b->contents = Sin_BrushContents (b); + + // allow detail brushes to be removed + if (nodetail && (b->contents & CONTENTS_DETAIL) ) + { + b->numsides = 0; + return; + } + + // allow water brushes to be removed + if (nowater && (b->contents & (CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER)) ) + { + b->numsides = 0; + return; + } + + // create windings for sides and bounds for brush + MakeBrushWindings (b); + + // brushes that will not be visible at all will never be + // used as bsp splitters + if (b->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) + { + c_clipbrushes++; + for (i=0 ; inumsides ; i++) + b->original_sides[i].texinfo = TEXINFO_NODE; + } + + // + // origin brushes are removed, but they set + // the rotation origin for the rest of the brushes + // in the entity. After the entire entity is parsed, + // the planenums and texinfos will be adjusted for + // the origin brush + // + if (b->contents & CONTENTS_ORIGIN) + { + char string[32]; + vec3_t origin; + + if (num_entities == 1) + { + Error ("Entity %i, Brush %i: origin brushes not allowed in world" + , b->entitynum, b->brushnum); + return; + } + + VectorAdd (b->mins, b->maxs, origin); + VectorScale (origin, 0.5, origin); + + sprintf (string, "%i %i %i", (int)origin[0], (int)origin[1], (int)origin[2]); + SetKeyValue (&entities[b->entitynum], "origin", string); + + VectorCopy (origin, entities[b->entitynum].origin); + + // don't keep this brush + b->numsides = 0; + + return; + } + + AddBrushBevels (b); + + nummapbrushes++; + mapent->numbrushes++; +} //*/ + +/* +================ +MoveBrushesToWorld + +Takes all of the brushes from the current entity and +adds them to the world's brush list. + +Used by func_group and func_areaportal +================ +* / +void MoveBrushesToWorld (entity_t *mapent) +{ + int newbrushes; + int worldbrushes; + mapbrush_t *temp; + int i; + + // this is pretty gross, because the brushes are expected to be + // in linear order for each entity + + newbrushes = mapent->numbrushes; + worldbrushes = entities[0].numbrushes; + + temp = malloc(newbrushes*sizeof(mapbrush_t)); + memcpy (temp, mapbrushes + mapent->firstbrush, newbrushes*sizeof(mapbrush_t)); + +#if 0 // let them keep their original brush numbers + for (i=0 ; inumbrushes = 0; +} //*/ + +/* +================ +ParseMapEntity +================ +* / +qboolean Sin_ParseMapEntity (void) +{ + entity_t *mapent; + epair_t *e; + side_t *s; + int i, j; + int startbrush, startsides; + vec_t newdist; + mapbrush_t *b; + + if (!GetToken (true)) + return false; + + if (strcmp (token, "{") ) + Error ("ParseEntity: { not found"); + + if (num_entities == MAX_MAP_ENTITIES) + Error ("num_entities == MAX_MAP_ENTITIES"); + + startbrush = nummapbrushes; + startsides = nummapbrushsides; + + mapent = &entities[num_entities]; + num_entities++; + memset (mapent, 0, sizeof(*mapent)); + mapent->firstbrush = nummapbrushes; + mapent->numbrushes = 0; +// mapent->portalareas[0] = -1; +// mapent->portalareas[1] = -1; + + do + { + if (!GetToken (true)) + Error ("ParseEntity: EOF without closing brace"); + if (!strcmp (token, "}") ) + break; + if (!strcmp (token, "{") ) + ParseBrush (mapent); + else + { + e = ParseEpair (); +#ifdef SIN + //HACK HACK HACK + // MED Gotta do this here + if ( !stricmp(e->key, "surfacefile") ) + { + if (!surfacefile[0]) + { + strcpy( surfacefile, e->value ); + } + printf ("--- ParseSurfaceFile ---\n"); + printf ("Surface script: %s\n", surfacefile); + if (!ParseSurfaceFile(surfacefile)) + { + Error ("Script file not found: %s\n", surfacefile); + } + } +#endif + e->next = mapent->epairs; + mapent->epairs = e; + } + } while (1); + +#ifdef SIN + if (!(strlen(ValueForKey(mapent, "origin"))) && ((num_entities-1) != 0)) + { + mapbrush_t *brush; + vec3_t origin; + char string[32]; + vec3_t mins, maxs; + int start, end; + // Calculate bounds + + start = mapent->firstbrush; + end = start + mapent->numbrushes; + ClearBounds (mins, maxs); + + for (j=start ; jnumsides) + continue; // not a real brush (origin brush) - shouldn't happen + AddPointToBounds (brush->mins, mins, maxs); + AddPointToBounds (brush->maxs, mins, maxs); + } + + // Set the origin to be the centroid of the entity. + VectorAdd ( mins, maxs, origin); + VectorScale( origin, 0.5f, origin ); + + sprintf (string, "%i %i %i", (int)origin[0], (int)origin[1], (int)origin[2]); + SetKeyValue ( mapent, "origin", string); +// qprintf("Setting origin to %s\n",string); + } +#endif + + GetVectorForKey (mapent, "origin", mapent->origin); + +#ifdef SIN + if ( + (!strcmp ("func_areaportal", ValueForKey (mapent, "classname"))) || + (!strcmp ("func_group", ValueForKey (mapent, "classname"))) || + (!strcmp ("detail", ValueForKey (mapent, "classname")) && !entitydetails) + ) + { + VectorClear( mapent->origin ); + } +#endif + + // + // if there was an origin brush, offset all of the planes and texinfo + // + if (mapent->origin[0] || mapent->origin[1] || mapent->origin[2]) + { + for (i=0 ; inumbrushes ; i++) + { + b = &mapbrushes[mapent->firstbrush + i]; + for (j=0 ; jnumsides ; j++) + { + s = &b->original_sides[j]; + newdist = mapplanes[s->planenum].dist - + DotProduct (mapplanes[s->planenum].normal, mapent->origin); + s->planenum = FindFloatPlane (mapplanes[s->planenum].normal, newdist); +#ifdef SIN + s->texinfo = TexinfoForBrushTexture (&mapplanes[s->planenum], + &side_brushtextures[s-brushsides], mapent->origin, &side_newrefs[s-brushsides]); + // + // save off lightinfo + // + s->lightinfo = LightinfoForBrushTexture ( &side_brushtextures[s-brushsides] ); +#else + s->texinfo = TexinfoForBrushTexture (&mapplanes[s->planenum], + &side_brushtextures[s-brushsides], mapent->origin); +#endif + } + MakeBrushWindings (b); + } + } + + // group entities are just for editor convenience + // toss all brushes into the world entity + if (!strcmp ("func_group", ValueForKey (mapent, "classname"))) + { + MoveBrushesToWorld (mapent); + mapent->numbrushes = 0; + mapent->wasdetail = true; + FreeValueKeys( mapent ); + return true; + } +#ifdef SIN + // detail entities are just for editor convenience + // toss all brushes into the world entity as detail brushes + if (!strcmp ("detail", ValueForKey (mapent, "classname")) && !entitydetails) + { + for (i=0 ; inumbrushes ; i++) + { + int j; + side_t * s; + b = &mapbrushes[mapent->firstbrush + i]; + if (nodetail) + { + b->numsides = 0; + continue; + } + if (!fulldetail) + { + // set the contents for the entire brush + b->contents |= CONTENTS_DETAIL; + // set the contents in the sides as well + for (j=0, s=b->original_sides ; jnumsides ; j++,s++) + { + s->contents |= CONTENTS_DETAIL; + } + } + else + { + // set the contents for the entire brush + b->contents |= CONTENTS_SOLID; + // set the contents in the sides as well + for (j=0, s=b->original_sides ; jnumsides ; j++,s++) + { + s->contents |= CONTENTS_SOLID; + } + } + } + MoveBrushesToWorld (mapent); + mapent->wasdetail = true; + FreeValueKeys( mapent ); + // kill off the entity + // num_entities--; + return true; + } +#endif + + // areaportal entities move their brushes, but don't eliminate + // the entity + if (!strcmp ("func_areaportal", ValueForKey (mapent, "classname"))) + { + char str[128]; + + if (mapent->numbrushes != 1) + Error ("Entity %i: func_areaportal can only be a single brush", num_entities-1); + + b = &mapbrushes[nummapbrushes-1]; + b->contents = CONTENTS_AREAPORTAL; + c_areaportals++; + mapent->areaportalnum = c_areaportals; + // set the portal number as "style" + sprintf (str, "%i", c_areaportals); + SetKeyValue (mapent, "style", str); + MoveBrushesToWorld (mapent); + return true; + } + + return true; +} //end of the function Sin_ParseMapEntity */ + +//=================================================================== + +/* +================ +LoadMapFile +================ +* / +void Sin_LoadMapFile (char *filename) +{ + int i; +#ifdef SIN + int num_detailsides=0; + int num_detailbrushes=0; + int num_worldsides=0; + int num_worldbrushes=0; + int j,k; +#endif + + qprintf ("--- LoadMapFile ---\n"); + + LoadScriptFile (filename); + + nummapbrushsides = 0; + num_entities = 0; + + while (ParseMapEntity ()) + { + } + + ClearBounds (map_mins, map_maxs); + for (i=0 ; i 4096) + continue; // no valid points + AddPointToBounds (mapbrushes[i].mins, map_mins, map_maxs); + AddPointToBounds (mapbrushes[i].maxs, map_mins, map_maxs); + } +#ifdef SIN + for (j=0; jnumsides && b->contents & CONTENTS_DETAIL) + num_detailbrushes++; + else if (b->numsides) + num_worldbrushes++; + for (k=0, s=b->original_sides ; knumsides ; k++,s++) + { + if (s->contents & CONTENTS_DETAIL) + num_detailsides++; + else + num_worldsides++; + } + } + } +#endif + + qprintf ("%5i brushes\n", nummapbrushes); + qprintf ("%5i clipbrushes\n", c_clipbrushes); + qprintf ("%5i total sides\n", nummapbrushsides); + qprintf ("%5i boxbevels\n", c_boxbevels); + qprintf ("%5i edgebevels\n", c_edgebevels); + qprintf ("%5i entities\n", num_entities); + qprintf ("%5i planes\n", nummapplanes); + qprintf ("%5i areaportals\n", c_areaportals); + qprintf ("size: %5.0f,%5.0f,%5.0f to %5.0f,%5.0f,%5.0f\n", map_mins[0],map_mins[1],map_mins[2], + map_maxs[0],map_maxs[1],map_maxs[2]); +#ifdef SIN + qprintf ("%5i detailbrushes\n", num_detailbrushes); + qprintf ("%5i worldbrushes\n", num_worldbrushes); + qprintf ("%5i detailsides\n", num_detailsides); + qprintf ("%5i worldsides\n", num_worldsides); +#endif + +} //end of the function Sin_LoadMap */ + + +#ifdef ME //Begin MAP loading from BSP file +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Sin_CreateMapTexinfo(void) +{ + int i; + vec_t defaultvec[4] = {1, 0, 0, 0}; + + memcpy(map_texinfo[0].vecs[0], defaultvec, sizeof(defaultvec)); + memcpy(map_texinfo[0].vecs[1], defaultvec, sizeof(defaultvec)); + map_texinfo[0].flags = 0; + map_texinfo[0].value = 0; + strcpy(map_texinfo[0].texture, "generic/misc/red"); //no texture + map_texinfo[0].nexttexinfo = -1; + for (i = 1; i < sin_numtexinfo; i++) + { + memcpy(map_texinfo[i].vecs, sin_texinfo[i].vecs, sizeof(float) * 2 * 4); + map_texinfo[i].flags = sin_texinfo[i].flags; + map_texinfo[i].value = 0; + strcpy(map_texinfo[i].texture, sin_texinfo[i].texture); + map_texinfo[i].nexttexinfo = -1; + } //end for +} //end of the function Sin_CreateMapTexinfo +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Sin_SetLeafBrushesModelNumbers(int leafnum, int modelnum) +{ + int i, brushnum; + sin_dleaf_t *leaf; + + leaf = &sin_dleafs[leafnum]; + for (i = 0; i < leaf->numleafbrushes; i++) + { + brushnum = sin_dleafbrushes[leaf->firstleafbrush + i]; + brushmodelnumbers[brushnum] = modelnum; + dbrushleafnums[brushnum] = leafnum; + } //end for +} //end of the function Sin_SetLeafBrushesModelNumbers +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Sin_InitNodeStack(void) +{ + nodestackptr = nodestack; + nodestacksize = 0; +} //end of the function Sin_InitNodeStack +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Sin_PushNodeStack(int num) +{ + *nodestackptr = num; + nodestackptr++; + nodestacksize++; + // + if (nodestackptr >= &nodestack[NODESTACKSIZE]) + { + Error("Sin_PushNodeStack: stack overflow\n"); + } //end if +} //end of the function Sin_PushNodeStack +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Sin_PopNodeStack(void) +{ + //if the stack is empty + if (nodestackptr <= nodestack) return -1; + //decrease stack pointer + nodestackptr--; + nodestacksize--; + //return the top value from the stack + return *nodestackptr; +} //end of the function Sin_PopNodeStack +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Sin_SetBrushModelNumbers(entity_t *mapent) +{ + int n, pn; + int leafnum; + + // + Sin_InitNodeStack(); + //head node (root) of the bsp tree + n = sin_dmodels[mapent->modelnum].headnode; + pn = 0; + + do + { + //if we are in a leaf (negative node number) + if (n < 0) + { + //number of the leaf + leafnum = (-n) - 1; + //set the brush numbers + Sin_SetLeafBrushesModelNumbers(leafnum, mapent->modelnum); + //walk back into the tree to find a second child to continue with + for (pn = Sin_PopNodeStack(); pn >= 0; n = pn, pn = Sin_PopNodeStack()) + { + //if we took the first child at the parent node + if (sin_dnodes[pn].children[0] == n) break; + } //end for + //if the stack wasn't empty (if not processed whole tree) + if (pn >= 0) + { + //push the parent node again + Sin_PushNodeStack(pn); + //we proceed with the second child of the parent node + n = sin_dnodes[pn].children[1]; + } //end if + } //end if + else + { + //push the current node onto the stack + Sin_PushNodeStack(n); + //walk forward into the tree to the first child + n = sin_dnodes[n].children[0]; + } //end else + } while(pn >= 0); +} //end of the function Sin_SetBrushModelNumbers +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Sin_BSPBrushToMapBrush(sin_dbrush_t *bspbrush, entity_t *mapent) +{ + mapbrush_t *b; + int i, k, n; + side_t *side, *s2; + int planenum; + sin_dbrushside_t *bspbrushside; + sin_dplane_t *bspplane; + + if (nummapbrushes >= MAX_MAPFILE_BRUSHES) + Error ("nummapbrushes >= MAX_MAPFILE_BRUSHES"); + + b = &mapbrushes[nummapbrushes]; + b->original_sides = &brushsides[nummapbrushsides]; + b->entitynum = mapent-entities; + b->brushnum = nummapbrushes - mapent->firstbrush; + b->leafnum = dbrushleafnums[bspbrush - sin_dbrushes]; + + for (n = 0; n < bspbrush->numsides; n++) + { + //pointer to the bsp brush side + bspbrushside = &sin_dbrushsides[bspbrush->firstside + n]; + + if (nummapbrushsides >= MAX_MAPFILE_BRUSHSIDES) + { + Error ("MAX_MAPFILE_BRUSHSIDES"); + } //end if + //pointer to the map brush side + side = &brushsides[nummapbrushsides]; + //if the BSP brush side is textured + if (sin_dbrushsidetextured[bspbrush->firstside + n]) side->flags |= SFL_TEXTURED; + else side->flags &= ~SFL_TEXTURED; + //ME: can get side contents and surf directly from BSP file + side->contents = bspbrush->contents; + //if the texinfo is TEXINFO_NODE + if (bspbrushside->texinfo < 0) side->surf = 0; + else side->surf = sin_texinfo[bspbrushside->texinfo].flags; + + // translucent objects are automatically classified as detail + if (side->surf & (SURF_TRANS33|SURF_TRANS66) ) + side->contents |= CONTENTS_DETAIL; + if (side->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) + side->contents |= CONTENTS_DETAIL; + if (fulldetail) + side->contents &= ~CONTENTS_DETAIL; + if (!(side->contents & ((LAST_VISIBLE_CONTENTS-1) + | CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP|CONTENTS_MIST) ) ) + side->contents |= CONTENTS_SOLID; + + // hints and skips are never detail, and have no content + if (side->surf & (SURF_HINT|SURF_SKIP) ) + { + side->contents = 0; + side->surf &= ~CONTENTS_DETAIL; + } + + //ME: get a plane for this side + bspplane = &sin_dplanes[bspbrushside->planenum]; + planenum = FindFloatPlane(bspplane->normal, bspplane->dist); + // + // see if the plane has been used already + // + //ME: this really shouldn't happen!!! + //ME: otherwise the bsp file is corrupted?? + //ME: still it seems to happen, maybe Johny Boy's + //ME: brush bevel adding is crappy ? + for (k = 0; k < b->numsides; k++) + { + s2 = b->original_sides + k; + if (s2->planenum == planenum) + { + Log_Print("Entity %i, Brush %i: duplicate plane\n" + , b->entitynum, b->brushnum); + break; + } + if ( s2->planenum == (planenum^1) ) + { + Log_Print("Entity %i, Brush %i: mirrored plane\n" + , b->entitynum, b->brushnum); + break; + } + } + if (k != b->numsides) + continue; // duplicated + + // + // keep this side + // + //ME: reset pointer to side, why? hell I dunno (pointer is set above already) + side = b->original_sides + b->numsides; + //ME: store the plane number + side->planenum = planenum; + //ME: texinfo is already stored when bsp is loaded + //NOTE: check for TEXINFO_NODE, otherwise crash in Sin_BrushContents + if (bspbrushside->texinfo < 0) side->texinfo = 0; + else side->texinfo = bspbrushside->texinfo; + + // save the td off in case there is an origin brush and we + // have to recalculate the texinfo + // ME: don't need to recalculate because it's already done + // (for non-world entities) in the BSP file +// side_brushtextures[nummapbrushsides] = td; + + nummapbrushsides++; + b->numsides++; + } //end for + + // get the content for the entire brush + b->contents = bspbrush->contents; + Sin_BrushContents(b); + + if (BrushExists(b)) + { + c_squattbrushes++; + b->numsides = 0; + return; + } //end if + + //if we're creating AAS + if (create_aas) + { + //create the AAS brushes from this brush, don't add brush bevels + AAS_CreateMapBrushes(b, mapent, false); + return; + } //end if + + // allow detail brushes to be removed + if (nodetail && (b->contents & CONTENTS_DETAIL) ) + { + b->numsides = 0; + return; + } //end if + + // allow water brushes to be removed + if (nowater && (b->contents & (CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER)) ) + { + b->numsides = 0; + return; + } //end if + + // create windings for sides and bounds for brush + MakeBrushWindings(b); + + //mark brushes without winding or with a tiny window as bevels + MarkBrushBevels(b); + + // brushes that will not be visible at all will never be + // used as bsp splitters + if (b->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) + { + c_clipbrushes++; + for (i = 0; i < b->numsides; i++) + b->original_sides[i].texinfo = TEXINFO_NODE; + } //end for + + // + // origin brushes are removed, but they set + // the rotation origin for the rest of the brushes + // in the entity. After the entire entity is parsed, + // the planenums and texinfos will be adjusted for + // the origin brush + // + //ME: not needed because the entities in the BSP file already + // have an origin set +// if (b->contents & CONTENTS_ORIGIN) +// { +// char string[32]; +// vec3_t origin; +// +// if (num_entities == 1) +// { +// Error ("Entity %i, Brush %i: origin brushes not allowed in world" +// , b->entitynum, b->brushnum); +// return; +// } +// +// VectorAdd (b->mins, b->maxs, origin); +// VectorScale (origin, 0.5, origin); +// +// sprintf (string, "%i %i %i", (int)origin[0], (int)origin[1], (int)origin[2]); +// SetKeyValue (&entities[b->entitynum], "origin", string); +// +// VectorCopy (origin, entities[b->entitynum].origin); +// +// // don't keep this brush +// b->numsides = 0; +// +// return; +// } + + //ME: the bsp brushes already have bevels, so we won't try to + // add them again (especially since Johny Boy's bevel adding might + // be crappy) +// AddBrushBevels(b); + + nummapbrushes++; + mapent->numbrushes++; +} //end of the function Sin_BSPBrushToMapBrush +//=========================================================================== +//=========================================================================== +void Sin_ParseBSPBrushes(entity_t *mapent) +{ + int i, testnum = 0; + + //give all the brushes that belong to this entity the number of the + //BSP model used by this entity + Sin_SetBrushModelNumbers(mapent); + //now parse all the brushes with the correct mapent->modelnum + for (i = 0; i < sin_numbrushes; i++) + { + if (brushmodelnumbers[i] == mapent->modelnum) + { + testnum++; + Sin_BSPBrushToMapBrush(&sin_dbrushes[i], mapent); + } //end if + } //end for +} //end of the function Sin_ParseBSPBrushes +//=========================================================================== +//=========================================================================== +qboolean Sin_ParseBSPEntity(int entnum) +{ + entity_t *mapent; + char *model; + int startbrush, startsides; + + startbrush = nummapbrushes; + startsides = nummapbrushsides; + + mapent = &entities[entnum];//num_entities]; + mapent->firstbrush = nummapbrushes; + mapent->numbrushes = 0; + mapent->modelnum = -1; //-1 = no model + + model = ValueForKey(mapent, "model"); + if (model && *model == '*') + { + mapent->modelnum = atoi(&model[1]); + //Log_Print("model = %s\n", model); + //Log_Print("mapent->modelnum = %d\n", mapent->modelnum); + } //end if + + GetVectorForKey(mapent, "origin", mapent->origin); + + //if this is the world entity it has model number zero + //the world entity has no model key + if (!strcmp("worldspawn", ValueForKey(mapent, "classname"))) + { + mapent->modelnum = 0; + } //end if + //if the map entity has a BSP model (a modelnum of -1 is used for + //entities that aren't using a BSP model) + if (mapent->modelnum >= 0) + { + //parse the bsp brushes + Sin_ParseBSPBrushes(mapent); + } //end if + // + //the origin of the entity is already taken into account + // + //func_group entities can't be in the bsp file + // + //check out the func_areaportal entities + if (!strcmp ("func_areaportal", ValueForKey (mapent, "classname"))) + { + c_areaportals++; + mapent->areaportalnum = c_areaportals; + return true; + } //end if + return true; +} //end of the function Sin_ParseBSPEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Sin_LoadMapFromBSP(char *filename, int offset, int length) +{ + int i; + + Log_Print("-- Sin_LoadMapFromBSP --\n"); + //loaded map type + loadedmaptype = MAPTYPE_SIN; + + Log_Print("Loading map from %s...\n", filename); + //load the bsp file + Sin_LoadBSPFile(filename, offset, length); + + //create an index from bsp planes to map planes + //DPlanes2MapPlanes(); + //clear brush model numbers + for (i = 0; i < MAX_MAPFILE_BRUSHES; i++) + brushmodelnumbers[i] = -1; + + nummapbrushsides = 0; + num_entities = 0; + + Sin_ParseEntities(); + // + for (i = 0; i < num_entities; i++) + { + Sin_ParseBSPEntity(i); + } //end for + + //get the map mins and maxs from the world model + ClearBounds(map_mins, map_maxs); + for (i = 0; i < entities[0].numbrushes; i++) + { + if (mapbrushes[i].mins[0] > 4096) + continue; //no valid points + AddPointToBounds (mapbrushes[i].mins, map_mins, map_maxs); + AddPointToBounds (mapbrushes[i].maxs, map_mins, map_maxs); + } //end for + // + Sin_CreateMapTexinfo(); +} //end of the function Sin_LoadMapFromBSP + +void Sin_ResetMapLoading(void) +{ + //reset for map loading from bsp + memset(nodestack, 0, NODESTACKSIZE * sizeof(int)); + nodestackptr = NULL; + nodestacksize = 0; + memset(brushmodelnumbers, 0, MAX_MAPFILE_BRUSHES * sizeof(int)); +} //end of the function Sin_ResetMapLoading + +//End MAP loading from BSP file + +#endif //ME diff --git a/code/bspc/nodraw.c b/code/bspc/nodraw.c index b5442dc..7503a48 100755 --- a/code/bspc/nodraw.c +++ b/code/bspc/nodraw.c @@ -1,47 +1,47 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "qbsp.h" - -vec3_t draw_mins, draw_maxs; -qboolean drawflag; - -void Draw_ClearWindow (void) -{ -} - -//============================================================ - -#define GLSERV_PORT 25001 - - -void GLS_BeginScene (void) -{ -} - -void GLS_Winding (winding_t *w, int code) -{ -} - -void GLS_EndScene (void) -{ -} +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "qbsp.h" + +vec3_t draw_mins, draw_maxs; +qboolean drawflag; + +void Draw_ClearWindow (void) +{ +} + +//============================================================ + +#define GLSERV_PORT 25001 + + +void GLS_BeginScene (void) +{ +} + +void GLS_Winding (winding_t *w, int code) +{ +} + +void GLS_EndScene (void) +{ +} diff --git a/code/bspc/portals.c b/code/bspc/portals.c index 3d3389d..cde4f60 100755 --- a/code/bspc/portals.c +++ b/code/bspc/portals.c @@ -1,1297 +1,1297 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "qbsp.h" -#include "l_mem.h" - -int c_active_portals; -int c_peak_portals; -int c_boundary; -int c_boundary_sides; -int c_portalmemory; - -//portal_t *portallist = NULL; -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -portal_t *AllocPortal (void) -{ - portal_t *p; - - p = GetMemory(sizeof(portal_t)); - memset (p, 0, sizeof(portal_t)); - - if (numthreads == 1) - { - c_active_portals++; - if (c_active_portals > c_peak_portals) - { - c_peak_portals = c_active_portals; - } //end if - c_portalmemory += MemorySize(p); - } //end if - -// p->nextportal = portallist; -// portallist = p; - - return p; -} //end of the function AllocPortal -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void FreePortal (portal_t *p) -{ - if (p->winding) FreeWinding(p->winding); - if (numthreads == 1) - { - c_active_portals--; - c_portalmemory -= MemorySize(p); - } //end if - FreeMemory(p); -} //end of the function FreePortal -//=========================================================================== -// Returns the single content bit of the -// strongest visible content present -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int VisibleContents (int contents) -{ - int i; - - for (i=1 ; i<=LAST_VISIBLE_CONTENTS ; i<<=1) - if (contents & i ) - return i; - - return 0; -} //end of the function VisibleContents -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int ClusterContents (node_t *node) -{ - int c1, c2, c; - - if (node->planenum == PLANENUM_LEAF) - return node->contents; - - c1 = ClusterContents(node->children[0]); - c2 = ClusterContents(node->children[1]); - c = c1|c2; - - // a cluster may include some solid detail areas, but - // still be seen into - if ( ! (c1&CONTENTS_SOLID) || ! (c2&CONTENTS_SOLID) ) - c &= ~CONTENTS_SOLID; - return c; -} //end of the function ClusterContents - -//=========================================================================== -// Returns true if the portal is empty or translucent, allowing -// the PVS calculation to see through it. -// The nodes on either side of the portal may actually be clusters, -// not leaves, so all contents should be ored together -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean Portal_VisFlood (portal_t *p) -{ - int c1, c2; - - if (!p->onnode) - return false; // to global outsideleaf - - c1 = ClusterContents(p->nodes[0]); - c2 = ClusterContents(p->nodes[1]); - - if (!VisibleContents (c1^c2)) - return true; - - if (c1 & (CONTENTS_Q2TRANSLUCENT|CONTENTS_DETAIL)) - c1 = 0; - if (c2 & (CONTENTS_Q2TRANSLUCENT|CONTENTS_DETAIL)) - c2 = 0; - - if ( (c1|c2) & CONTENTS_SOLID ) - return false; // can't see through solid - - if (! (c1 ^ c2)) - return true; // identical on both sides - - if (!VisibleContents (c1^c2)) - return true; - return false; -} //end of the function Portal_VisFlood -//=========================================================================== -// The entity flood determines which areas are -// "outside" on the map, which are then filled in. -// Flowing from side s to side !s -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean Portal_EntityFlood (portal_t *p, int s) -{ - if (p->nodes[0]->planenum != PLANENUM_LEAF - || p->nodes[1]->planenum != PLANENUM_LEAF) - Error ("Portal_EntityFlood: not a leaf"); - - // can never cross to a solid - if ( (p->nodes[0]->contents & CONTENTS_SOLID) - || (p->nodes[1]->contents & CONTENTS_SOLID) ) - return false; - - // can flood through everything else - return true; -} - - -//============================================================================= - -int c_tinyportals; - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void AddPortalToNodes (portal_t *p, node_t *front, node_t *back) -{ - if (p->nodes[0] || p->nodes[1]) - Error ("AddPortalToNode: allready included"); - - p->nodes[0] = front; - p->next[0] = front->portals; - front->portals = p; - - p->nodes[1] = back; - p->next[1] = back->portals; - back->portals = p; -} //end of the function AddPortalToNodes -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void RemovePortalFromNode (portal_t *portal, node_t *l) -{ - portal_t **pp, *t; - - int s, i, n; - portal_t *p; - portal_t *portals[4096]; - -// remove reference to the current portal - pp = &l->portals; - while (1) - { - t = *pp; - if (!t) - Error ("RemovePortalFromNode: portal not in leaf"); - - if ( t == portal ) - break; - - if (t->nodes[0] == l) - pp = &t->next[0]; - else if (t->nodes[1] == l) - pp = &t->next[1]; - else - Error ("RemovePortalFromNode: portal not bounding leaf"); - } - - if (portal->nodes[0] == l) - { - *pp = portal->next[0]; - portal->nodes[0] = NULL; - } //end if - else if (portal->nodes[1] == l) - { - *pp = portal->next[1]; - portal->nodes[1] = NULL; - } //end else if - else - { - Error("RemovePortalFromNode: mislinked portal"); - } //end else -//#ifdef ME - n = 0; - for (p = l->portals; p; p = p->next[s]) - { - for (i = 0; i < n; i++) - { - if (p == portals[i]) Error("RemovePortalFromNode: circular linked\n"); - } //end for - if (p->nodes[0] != l && p->nodes[1] != l) - { - Error("RemovePortalFromNodes: portal does not belong to node\n"); - } //end if - portals[n] = p; - s = (p->nodes[1] == l); -// if (++n >= 4096) Error("RemovePortalFromNode: more than 4096 portals\n"); - } //end for -//#endif -} //end of the function RemovePortalFromNode -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void PrintPortal (portal_t *p) -{ - int i; - winding_t *w; - - w = p->winding; - for (i=0 ; inumpoints ; i++) - printf ("(%5.0f,%5.0f,%5.0f)\n",w->p[i][0] - , w->p[i][1], w->p[i][2]); -} //end of the function PrintPortal -//=========================================================================== -// The created portals will face the global outside_node -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -#define SIDESPACE 8 - -void MakeHeadnodePortals (tree_t *tree) -{ - vec3_t bounds[2]; - int i, j, n; - portal_t *p, *portals[6]; - plane_t bplanes[6], *pl; - node_t *node; - - node = tree->headnode; - -// pad with some space so there will never be null volume leaves - for (i=0 ; i<3 ; i++) - { - bounds[0][i] = tree->mins[i] - SIDESPACE; - bounds[1][i] = tree->maxs[i] + SIDESPACE; - if ( bounds[0][i] > bounds[1][i] ) { - Error("empty BSP tree"); - } - } - - tree->outside_node.planenum = PLANENUM_LEAF; - tree->outside_node.brushlist = NULL; - tree->outside_node.portals = NULL; - tree->outside_node.contents = 0; - - for (i=0 ; i<3 ; i++) - for (j=0 ; j<2 ; j++) - { - n = j*3 + i; - - p = AllocPortal (); - portals[n] = p; - - pl = &bplanes[n]; - memset (pl, 0, sizeof(*pl)); - if (j) - { - pl->normal[i] = -1; - pl->dist = -bounds[j][i]; - } - else - { - pl->normal[i] = 1; - pl->dist = bounds[j][i]; - } - p->plane = *pl; - p->winding = BaseWindingForPlane (pl->normal, pl->dist); - AddPortalToNodes (p, node, &tree->outside_node); - } - -// clip the basewindings by all the other planes - for (i=0 ; i<6 ; i++) - { - for (j=0 ; j<6 ; j++) - { - if (j == i) continue; - ChopWindingInPlace (&portals[i]->winding, bplanes[j].normal, bplanes[j].dist, ON_EPSILON); - } //end for - } //end for -} //end of the function MakeHeadNodePortals -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -#define BASE_WINDING_EPSILON 0.001 -#define SPLIT_WINDING_EPSILON 0.001 - -winding_t *BaseWindingForNode (node_t *node) -{ - winding_t *w; - node_t *n; - plane_t *plane; - vec3_t normal; - vec_t dist; - - w = BaseWindingForPlane (mapplanes[node->planenum].normal - , mapplanes[node->planenum].dist); - - // clip by all the parents - for (n=node->parent ; n && w ; ) - { - plane = &mapplanes[n->planenum]; - - if (n->children[0] == node) - { // take front - ChopWindingInPlace (&w, plane->normal, plane->dist, BASE_WINDING_EPSILON); - } - else - { // take back - VectorSubtract (vec3_origin, plane->normal, normal); - dist = -plane->dist; - ChopWindingInPlace (&w, normal, dist, BASE_WINDING_EPSILON); - } - node = n; - n = n->parent; - } - - return w; -} //end of the function BaseWindingForNode -//=========================================================================== -// create the new portal by taking the full plane winding for the cutting -// plane and clipping it by all of parents of this node -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean WindingIsTiny (winding_t *w); - -void MakeNodePortal (node_t *node) -{ - portal_t *new_portal, *p; - winding_t *w; - vec3_t normal; - float dist; - int side; - - w = BaseWindingForNode (node); - - // clip the portal by all the other portals in the node - for (p = node->portals; p && w; p = p->next[side]) - { - if (p->nodes[0] == node) - { - side = 0; - VectorCopy (p->plane.normal, normal); - dist = p->plane.dist; - } //end if - else if (p->nodes[1] == node) - { - side = 1; - VectorSubtract (vec3_origin, p->plane.normal, normal); - dist = -p->plane.dist; - } //end else if - else - { - Error ("MakeNodePortal: mislinked portal"); - } //end else - ChopWindingInPlace (&w, normal, dist, 0.1); - } //end for - - if (!w) - { - return; - } //end if - - if (WindingIsTiny (w)) - { - c_tinyportals++; - FreeWinding(w); - return; - } //end if - -#ifdef DEBUG -/* //NOTE: don't use this winding ok check - // all the invalid windings only have a degenerate edge - if (WindingError(w)) - { - Log_Print("MakeNodePortal: %s\n", WindingErrorString()); - FreeWinding(w); - return; - } //end if*/ -#endif //DEBUG - - - new_portal = AllocPortal(); - new_portal->plane = mapplanes[node->planenum]; - -#ifdef ME - new_portal->planenum = node->planenum; -#endif //ME - - new_portal->onnode = node; - new_portal->winding = w; - AddPortalToNodes (new_portal, node->children[0], node->children[1]); -} //end of the function MakeNodePortal -//=========================================================================== -// Move or split the portals that bound node so that the node's -// children have portals instead of node. -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void SplitNodePortals (node_t *node) -{ - portal_t *p, *next_portal, *new_portal; - node_t *f, *b, *other_node; - int side; - plane_t *plane; - winding_t *frontwinding, *backwinding; - - plane = &mapplanes[node->planenum]; - f = node->children[0]; - b = node->children[1]; - - for (p = node->portals ; p ; p = next_portal) - { - if (p->nodes[0] == node) side = 0; - else if (p->nodes[1] == node) side = 1; - else Error ("SplitNodePortals: mislinked portal"); - next_portal = p->next[side]; - - other_node = p->nodes[!side]; - RemovePortalFromNode (p, p->nodes[0]); - RemovePortalFromNode (p, p->nodes[1]); - -// -// cut the portal into two portals, one on each side of the cut plane -// - ClipWindingEpsilon (p->winding, plane->normal, plane->dist, - SPLIT_WINDING_EPSILON, &frontwinding, &backwinding); - - if (frontwinding && WindingIsTiny(frontwinding)) - { - FreeWinding (frontwinding); - frontwinding = NULL; - c_tinyportals++; - } //end if - - if (backwinding && WindingIsTiny(backwinding)) - { - FreeWinding (backwinding); - backwinding = NULL; - c_tinyportals++; - } //end if - -#ifdef DEBUG -/* //NOTE: don't use this winding ok check - // all the invalid windings only have a degenerate edge - if (frontwinding && WindingError(frontwinding)) - { - Log_Print("SplitNodePortals: front %s\n", WindingErrorString()); - FreeWinding(frontwinding); - frontwinding = NULL; - } //end if - if (backwinding && WindingError(backwinding)) - { - Log_Print("SplitNodePortals: back %s\n", WindingErrorString()); - FreeWinding(backwinding); - backwinding = NULL; - } //end if*/ -#endif //DEBUG - - if (!frontwinding && !backwinding) - { // tiny windings on both sides - continue; - } - - if (!frontwinding) - { - FreeWinding (backwinding); - if (side == 0) AddPortalToNodes (p, b, other_node); - else AddPortalToNodes (p, other_node, b); - continue; - } - if (!backwinding) - { - FreeWinding (frontwinding); - if (side == 0) AddPortalToNodes (p, f, other_node); - else AddPortalToNodes (p, other_node, f); - continue; - } - - // the winding is split - new_portal = AllocPortal(); - *new_portal = *p; - new_portal->winding = backwinding; - FreeWinding (p->winding); - p->winding = frontwinding; - - if (side == 0) - { - AddPortalToNodes (p, f, other_node); - AddPortalToNodes (new_portal, b, other_node); - } //end if - else - { - AddPortalToNodes (p, other_node, f); - AddPortalToNodes (new_portal, other_node, b); - } //end else - } - - node->portals = NULL; -} //end of the function SplitNodePortals -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void CalcNodeBounds (node_t *node) -{ - portal_t *p; - int s; - int i; - - // calc mins/maxs for both leaves and nodes - ClearBounds (node->mins, node->maxs); - for (p = node->portals ; p ; p = p->next[s]) - { - s = (p->nodes[1] == node); - for (i=0 ; iwinding->numpoints ; i++) - AddPointToBounds (p->winding->p[i], node->mins, node->maxs); - } -} //end of the function CalcNodeBounds -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int c_numportalizednodes; - -void MakeTreePortals_r (node_t *node) -{ - int i; - -#ifdef ME - qprintf("\r%6d", ++c_numportalizednodes); - if (cancelconversion) return; -#endif //ME - - CalcNodeBounds (node); - if (node->mins[0] >= node->maxs[0]) - { - Log_Print("WARNING: node without a volume\n"); - } - - for (i=0 ; i<3 ; i++) - { - if (node->mins[i] < -MAX_MAP_BOUNDS || node->maxs[i] > MAX_MAP_BOUNDS) - { - Log_Print("WARNING: node with unbounded volume\n"); - break; - } - } - if (node->planenum == PLANENUM_LEAF) - return; - - MakeNodePortal (node); - SplitNodePortals (node); - - MakeTreePortals_r (node->children[0]); - MakeTreePortals_r (node->children[1]); -} //end of the function MakeTreePortals_r -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void MakeTreePortals(tree_t *tree) -{ - -#ifdef ME - Log_Print("---- Node Portalization ----\n"); - c_numportalizednodes = 0; - c_portalmemory = 0; - qprintf("%6d nodes portalized", c_numportalizednodes); -#endif //ME - - MakeHeadnodePortals(tree); - MakeTreePortals_r(tree->headnode); - -#ifdef ME - qprintf("\n"); - Log_Write("%6d nodes portalized\r\n", c_numportalizednodes); - Log_Print("%6d tiny portals\r\n", c_tinyportals); - Log_Print("%6d KB of portal memory\r\n", c_portalmemory >> 10); - Log_Print("%6i KB of winding memory\r\n", WindingMemory() >> 10); -#endif //ME -} //end of the function MakeTreePortals - -/* -========================================================= - -FLOOD ENTITIES - -========================================================= -*/ -//#define P_NODESTACK - -node_t *p_firstnode; -node_t *p_lastnode; - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -#ifdef P_NODESTACK -void P_AddNodeToList(node_t *node) -{ - node->next = p_firstnode; - p_firstnode = node; - if (!p_lastnode) p_lastnode = node; -} //end of the function P_AddNodeToList -#else //it's a node queue -//add the node to the end of the node list -void P_AddNodeToList(node_t *node) -{ - node->next = NULL; - if (p_lastnode) p_lastnode->next = node; - else p_firstnode = node; - p_lastnode = node; -} //end of the function P_AddNodeToList -#endif //P_NODESTACK -//=========================================================================== -// get the first node from the front of the node list -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -node_t *P_NextNodeFromList(void) -{ - node_t *node; - - node = p_firstnode; - if (p_firstnode) p_firstnode = p_firstnode->next; - if (!p_firstnode) p_lastnode = NULL; - return node; -} //end of the function P_NextNodeFromList -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void FloodPortals(node_t *firstnode) -{ - node_t *node; - portal_t *p; - int s; - - firstnode->occupied = 1; - P_AddNodeToList(firstnode); - - for (node = P_NextNodeFromList(); node; node = P_NextNodeFromList()) - { - for (p = node->portals; p; p = p->next[s]) - { - s = (p->nodes[1] == node); - //if the node at the other side of the portal is occupied already - if (p->nodes[!s]->occupied) continue; - //if it isn't possible to flood through this portal - if (!Portal_EntityFlood(p, s)) continue; - // - p->nodes[!s]->occupied = node->occupied + 1; - // - P_AddNodeToList(p->nodes[!s]); - } //end for - } //end for -} //end of the function FloodPortals -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int numrec; - -void FloodPortals_r (node_t *node, int dist) -{ - portal_t *p; - int s; -// int i; - - Log_Print("\r%6d", ++numrec); - - if (node->occupied) Error("FloodPortals_r: node already occupied\n"); - if (!node) - { - Error("FloodPortals_r: NULL node\n"); - } //end if*/ - node->occupied = dist; - - for (p = node->portals; p; p = p->next[s]) - { - s = (p->nodes[1] == node); - //if the node at the other side of the portal is occupied already - if (p->nodes[!s]->occupied) continue; - //if it isn't possible to flood through this portal - if (!Portal_EntityFlood(p, s)) continue; - //flood recursively through the current portal - FloodPortals_r(p->nodes[!s], dist+1); - } //end for - Log_Print("\r%6d", --numrec); -} //end of the function FloodPortals_r -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean PlaceOccupant (node_t *headnode, vec3_t origin, entity_t *occupant) -{ - node_t *node; - vec_t d; - plane_t *plane; - - //find the leaf to start in - node = headnode; - while(node->planenum != PLANENUM_LEAF) - { - if (node->planenum < 0 || node->planenum > nummapplanes) - { - Error("PlaceOccupant: invalid node->planenum\n"); - } //end if - plane = &mapplanes[node->planenum]; - d = DotProduct(origin, plane->normal) - plane->dist; - if (d >= 0) node = node->children[0]; - else node = node->children[1]; - if (!node) - { - Error("PlaceOccupant: invalid child %d\n", d < 0); - } //end if - } //end while - //don't start in solid -// if (node->contents == CONTENTS_SOLID) - //ME: replaced because in LeafNode in brushbsp.c - // some nodes have contents solid with other contents - if (node->contents & CONTENTS_SOLID) return false; - //if the node is already occupied - if (node->occupied) return false; - - //place the occupant in the first leaf - node->occupant = occupant; - - numrec = 0; -// Log_Print("%6d recurses", numrec); -// FloodPortals_r(node, 1); -// Log_Print("\n"); - FloodPortals(node); - - return true; -} //end of the function PlaceOccupant -//=========================================================================== -// Marks all nodes that can be reached by entites -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean FloodEntities (tree_t *tree) -{ - int i; - int x, y; - vec3_t origin; - char *cl; - qboolean inside; - node_t *headnode; - - headnode = tree->headnode; - Log_Print("------ FloodEntities -------\n"); - inside = false; - tree->outside_node.occupied = 0; - - //start at entity 1 not the world ( = 0) - for (i = 1; i < num_entities; i++) - { - GetVectorForKey(&entities[i], "origin", origin); - if (VectorCompare(origin, vec3_origin)) continue; - - cl = ValueForKey(&entities[i], "classname"); - origin[2] += 1; //so objects on floor are ok - -// Log_Print("flooding from entity %d: %s\n", i, cl); - //nudge playerstart around if needed so clipping hulls allways - //have a valid point - if (!strcmp(cl, "info_player_start")) - { - for (x = -16; x <= 16; x += 16) - { - for (y = -16; y <= 16; y += 16) - { - origin[0] += x; - origin[1] += y; - if (PlaceOccupant(headnode, origin, &entities[i])) - { - inside = true; - x = 999; //stop for this info_player_start - break; - } //end if - origin[0] -= x; - origin[1] -= y; - } //end for - } //end for - } //end if - else - { - if (PlaceOccupant(headnode, origin, &entities[i])) - { - inside = true; - } //end if - } //end else - } //end for - - if (!inside) - { - Log_Print("WARNING: no entities inside\n"); - } //end if - else if (tree->outside_node.occupied) - { - Log_Print("WARNING: entity reached from outside\n"); - } //end else if - - return (qboolean)(inside && !tree->outside_node.occupied); -} //end of the function FloodEntities - -/* -========================================================= - -FILL OUTSIDE - -========================================================= -*/ - -int c_outside; -int c_inside; -int c_solid; - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void FillOutside_r (node_t *node) -{ - if (node->planenum != PLANENUM_LEAF) - { - FillOutside_r (node->children[0]); - FillOutside_r (node->children[1]); - return; - } //end if - // anything not reachable by an entity - // can be filled away (by setting solid contents) - if (!node->occupied) - { - if (!(node->contents & CONTENTS_SOLID)) - { - c_outside++; - node->contents |= CONTENTS_SOLID; - } //end if - else - { - c_solid++; - } //end else - } //end if - else - { - c_inside++; - } //end else -} //end of the function FillOutside_r -//=========================================================================== -// Fill all nodes that can't be reached by entities -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void FillOutside (node_t *headnode) -{ - c_outside = 0; - c_inside = 0; - c_solid = 0; - Log_Print("------- FillOutside --------\n"); - FillOutside_r (headnode); - Log_Print("%5i solid leaves\n", c_solid); - Log_Print("%5i leaves filled\n", c_outside); - Log_Print("%5i inside leaves\n", c_inside); -} //end of the function FillOutside - -/* -========================================================= - -FLOOD AREAS - -========================================================= -*/ - -int c_areas; - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void FloodAreas_r (node_t *node) -{ - portal_t *p; - int s; - bspbrush_t *b; - entity_t *e; - - if (node->contents == CONTENTS_AREAPORTAL) - { - // this node is part of an area portal - b = node->brushlist; - e = &entities[b->original->entitynum]; - - // if the current area has allready touched this - // portal, we are done - if (e->portalareas[0] == c_areas || e->portalareas[1] == c_areas) - return; - - // note the current area as bounding the portal - if (e->portalareas[1]) - { - Log_Print("WARNING: areaportal entity %i touches > 2 areas\n", b->original->entitynum); - return; - } - if (e->portalareas[0]) - e->portalareas[1] = c_areas; - else - e->portalareas[0] = c_areas; - - return; - } //end if - - if (node->area) - return; // allready got it - node->area = c_areas; - - for (p=node->portals ; p ; p = p->next[s]) - { - s = (p->nodes[1] == node); -#if 0 - if (p->nodes[!s]->occupied) - continue; -#endif - if (!Portal_EntityFlood (p, s)) - continue; - - FloodAreas_r (p->nodes[!s]); - } //end for -} //end of the function FloodAreas_r -//=========================================================================== -// Just decend the tree, and for each node that hasn't had an -// area set, flood fill out from there -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void FindAreas_r (node_t *node) -{ - if (node->planenum != PLANENUM_LEAF) - { - FindAreas_r (node->children[0]); - FindAreas_r (node->children[1]); - return; - } - - if (node->area) - return; // allready got it - - if (node->contents & CONTENTS_SOLID) - return; - - if (!node->occupied) - return; // not reachable by entities - - // area portals are allways only flooded into, never - // out of - if (node->contents == CONTENTS_AREAPORTAL) - return; - - c_areas++; - FloodAreas_r (node); -} //end of the function FindAreas_r -//=========================================================================== -// Just decend the tree, and for each node that hasn't had an -// area set, flood fill out from there -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void SetAreaPortalAreas_r (node_t *node) -{ - bspbrush_t *b; - entity_t *e; - - if (node->planenum != PLANENUM_LEAF) - { - SetAreaPortalAreas_r (node->children[0]); - SetAreaPortalAreas_r (node->children[1]); - return; - } //end if - - if (node->contents == CONTENTS_AREAPORTAL) - { - if (node->area) - return; // allready set - - b = node->brushlist; - e = &entities[b->original->entitynum]; - node->area = e->portalareas[0]; - if (!e->portalareas[1]) - { - Log_Print("WARNING: areaportal entity %i doesn't touch two areas\n", b->original->entitynum); - return; - } //end if - } //end if -} //end of the function SetAreaPortalAreas_r -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -/* -void EmitAreaPortals(node_t *headnode) -{ - int i, j; - entity_t *e; - dareaportal_t *dp; - - if (c_areas > MAX_MAP_AREAS) - Error ("MAX_MAP_AREAS"); - numareas = c_areas+1; - numareaportals = 1; // leave 0 as an error - - for (i=1 ; i<=c_areas ; i++) - { - dareas[i].firstareaportal = numareaportals; - for (j=0 ; jareaportalnum) - continue; - dp = &dareaportals[numareaportals]; - if (e->portalareas[0] == i) - { - dp->portalnum = e->areaportalnum; - dp->otherarea = e->portalareas[1]; - numareaportals++; - } //end if - else if (e->portalareas[1] == i) - { - dp->portalnum = e->areaportalnum; - dp->otherarea = e->portalareas[0]; - numareaportals++; - } //end else if - } //end for - dareas[i].numareaportals = numareaportals - dareas[i].firstareaportal; - } //end for - - Log_Print("%5i numareas\n", numareas); - Log_Print("%5i numareaportals\n", numareaportals); -} //end of the function EmitAreaPortals -*/ -//=========================================================================== -// Mark each leaf with an area, bounded by CONTENTS_AREAPORTAL -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void FloodAreas (tree_t *tree) -{ - Log_Print("--- FloodAreas ---\n"); - FindAreas_r (tree->headnode); - SetAreaPortalAreas_r (tree->headnode); - Log_Print("%5i areas\n", c_areas); -} //end of the function FloodAreas -//=========================================================================== -// Finds a brush side to use for texturing the given portal -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void FindPortalSide (portal_t *p) -{ - int viscontents; - bspbrush_t *bb; - mapbrush_t *brush; - node_t *n; - int i,j; - int planenum; - side_t *side, *bestside; - float dot, bestdot; - plane_t *p1, *p2; - - // decide which content change is strongest - // solid > lava > water, etc - viscontents = VisibleContents (p->nodes[0]->contents ^ p->nodes[1]->contents); - if (!viscontents) - return; - - planenum = p->onnode->planenum; - bestside = NULL; - bestdot = 0; - - for (j=0 ; j<2 ; j++) - { - n = p->nodes[j]; - p1 = &mapplanes[p->onnode->planenum]; - for (bb=n->brushlist ; bb ; bb=bb->next) - { - brush = bb->original; - if ( !(brush->contents & viscontents) ) - continue; - for (i=0 ; inumsides ; i++) - { - side = &brush->original_sides[i]; - if (side->flags & SFL_BEVEL) - continue; - if (side->texinfo == TEXINFO_NODE) - continue; // non-visible - if ((side->planenum&~1) == planenum) - { // exact match - bestside = &brush->original_sides[i]; - goto gotit; - } //end if - // see how close the match is - p2 = &mapplanes[side->planenum&~1]; - dot = DotProduct (p1->normal, p2->normal); - if (dot > bestdot) - { - bestdot = dot; - bestside = side; - } //end if - } //end for - } //end for - } //end for - -gotit: - if (!bestside) - Log_Print("WARNING: side not found for portal\n"); - - p->sidefound = true; - p->side = bestside; -} //end of the function FindPortalSide -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void MarkVisibleSides_r (node_t *node) -{ - portal_t *p; - int s; - - if (node->planenum != PLANENUM_LEAF) - { - MarkVisibleSides_r (node->children[0]); - MarkVisibleSides_r (node->children[1]); - return; - } //end if - - // empty leaves are never boundary leaves - if (!node->contents) return; - - // see if there is a visible face - for (p=node->portals ; p ; p = p->next[!s]) - { - s = (p->nodes[0] == node); - if (!p->onnode) - continue; // edge of world - if (!p->sidefound) - FindPortalSide (p); - if (p->side) - p->side->flags |= SFL_VISIBLE; - } //end for -} //end of the function MarkVisibleSides_r -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void MarkVisibleSides(tree_t *tree, int startbrush, int endbrush) -{ - int i, j; - mapbrush_t *mb; - int numsides; - - Log_Print("--- MarkVisibleSides ---\n"); - - // clear all the visible flags - for (i=startbrush ; inumsides; - for (j=0 ; joriginal_sides[j].flags &= ~SFL_VISIBLE; - } - - // set visible flags on the sides that are used by portals - MarkVisibleSides_r (tree->headnode); -} //end of the function MarkVisibleSides - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "qbsp.h" +#include "l_mem.h" + +int c_active_portals; +int c_peak_portals; +int c_boundary; +int c_boundary_sides; +int c_portalmemory; + +//portal_t *portallist = NULL; +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +portal_t *AllocPortal (void) +{ + portal_t *p; + + p = GetMemory(sizeof(portal_t)); + memset (p, 0, sizeof(portal_t)); + + if (numthreads == 1) + { + c_active_portals++; + if (c_active_portals > c_peak_portals) + { + c_peak_portals = c_active_portals; + } //end if + c_portalmemory += MemorySize(p); + } //end if + +// p->nextportal = portallist; +// portallist = p; + + return p; +} //end of the function AllocPortal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreePortal (portal_t *p) +{ + if (p->winding) FreeWinding(p->winding); + if (numthreads == 1) + { + c_active_portals--; + c_portalmemory -= MemorySize(p); + } //end if + FreeMemory(p); +} //end of the function FreePortal +//=========================================================================== +// Returns the single content bit of the +// strongest visible content present +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int VisibleContents (int contents) +{ + int i; + + for (i=1 ; i<=LAST_VISIBLE_CONTENTS ; i<<=1) + if (contents & i ) + return i; + + return 0; +} //end of the function VisibleContents +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int ClusterContents (node_t *node) +{ + int c1, c2, c; + + if (node->planenum == PLANENUM_LEAF) + return node->contents; + + c1 = ClusterContents(node->children[0]); + c2 = ClusterContents(node->children[1]); + c = c1|c2; + + // a cluster may include some solid detail areas, but + // still be seen into + if ( ! (c1&CONTENTS_SOLID) || ! (c2&CONTENTS_SOLID) ) + c &= ~CONTENTS_SOLID; + return c; +} //end of the function ClusterContents + +//=========================================================================== +// Returns true if the portal is empty or translucent, allowing +// the PVS calculation to see through it. +// The nodes on either side of the portal may actually be clusters, +// not leaves, so all contents should be ored together +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean Portal_VisFlood (portal_t *p) +{ + int c1, c2; + + if (!p->onnode) + return false; // to global outsideleaf + + c1 = ClusterContents(p->nodes[0]); + c2 = ClusterContents(p->nodes[1]); + + if (!VisibleContents (c1^c2)) + return true; + + if (c1 & (CONTENTS_Q2TRANSLUCENT|CONTENTS_DETAIL)) + c1 = 0; + if (c2 & (CONTENTS_Q2TRANSLUCENT|CONTENTS_DETAIL)) + c2 = 0; + + if ( (c1|c2) & CONTENTS_SOLID ) + return false; // can't see through solid + + if (! (c1 ^ c2)) + return true; // identical on both sides + + if (!VisibleContents (c1^c2)) + return true; + return false; +} //end of the function Portal_VisFlood +//=========================================================================== +// The entity flood determines which areas are +// "outside" on the map, which are then filled in. +// Flowing from side s to side !s +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean Portal_EntityFlood (portal_t *p, int s) +{ + if (p->nodes[0]->planenum != PLANENUM_LEAF + || p->nodes[1]->planenum != PLANENUM_LEAF) + Error ("Portal_EntityFlood: not a leaf"); + + // can never cross to a solid + if ( (p->nodes[0]->contents & CONTENTS_SOLID) + || (p->nodes[1]->contents & CONTENTS_SOLID) ) + return false; + + // can flood through everything else + return true; +} + + +//============================================================================= + +int c_tinyportals; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AddPortalToNodes (portal_t *p, node_t *front, node_t *back) +{ + if (p->nodes[0] || p->nodes[1]) + Error ("AddPortalToNode: allready included"); + + p->nodes[0] = front; + p->next[0] = front->portals; + front->portals = p; + + p->nodes[1] = back; + p->next[1] = back->portals; + back->portals = p; +} //end of the function AddPortalToNodes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void RemovePortalFromNode (portal_t *portal, node_t *l) +{ + portal_t **pp, *t; + + int s, i, n; + portal_t *p; + portal_t *portals[4096]; + +// remove reference to the current portal + pp = &l->portals; + while (1) + { + t = *pp; + if (!t) + Error ("RemovePortalFromNode: portal not in leaf"); + + if ( t == portal ) + break; + + if (t->nodes[0] == l) + pp = &t->next[0]; + else if (t->nodes[1] == l) + pp = &t->next[1]; + else + Error ("RemovePortalFromNode: portal not bounding leaf"); + } + + if (portal->nodes[0] == l) + { + *pp = portal->next[0]; + portal->nodes[0] = NULL; + } //end if + else if (portal->nodes[1] == l) + { + *pp = portal->next[1]; + portal->nodes[1] = NULL; + } //end else if + else + { + Error("RemovePortalFromNode: mislinked portal"); + } //end else +//#ifdef ME + n = 0; + for (p = l->portals; p; p = p->next[s]) + { + for (i = 0; i < n; i++) + { + if (p == portals[i]) Error("RemovePortalFromNode: circular linked\n"); + } //end for + if (p->nodes[0] != l && p->nodes[1] != l) + { + Error("RemovePortalFromNodes: portal does not belong to node\n"); + } //end if + portals[n] = p; + s = (p->nodes[1] == l); +// if (++n >= 4096) Error("RemovePortalFromNode: more than 4096 portals\n"); + } //end for +//#endif +} //end of the function RemovePortalFromNode +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void PrintPortal (portal_t *p) +{ + int i; + winding_t *w; + + w = p->winding; + for (i=0 ; inumpoints ; i++) + printf ("(%5.0f,%5.0f,%5.0f)\n",w->p[i][0] + , w->p[i][1], w->p[i][2]); +} //end of the function PrintPortal +//=========================================================================== +// The created portals will face the global outside_node +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#define SIDESPACE 8 + +void MakeHeadnodePortals (tree_t *tree) +{ + vec3_t bounds[2]; + int i, j, n; + portal_t *p, *portals[6]; + plane_t bplanes[6], *pl; + node_t *node; + + node = tree->headnode; + +// pad with some space so there will never be null volume leaves + for (i=0 ; i<3 ; i++) + { + bounds[0][i] = tree->mins[i] - SIDESPACE; + bounds[1][i] = tree->maxs[i] + SIDESPACE; + if ( bounds[0][i] > bounds[1][i] ) { + Error("empty BSP tree"); + } + } + + tree->outside_node.planenum = PLANENUM_LEAF; + tree->outside_node.brushlist = NULL; + tree->outside_node.portals = NULL; + tree->outside_node.contents = 0; + + for (i=0 ; i<3 ; i++) + for (j=0 ; j<2 ; j++) + { + n = j*3 + i; + + p = AllocPortal (); + portals[n] = p; + + pl = &bplanes[n]; + memset (pl, 0, sizeof(*pl)); + if (j) + { + pl->normal[i] = -1; + pl->dist = -bounds[j][i]; + } + else + { + pl->normal[i] = 1; + pl->dist = bounds[j][i]; + } + p->plane = *pl; + p->winding = BaseWindingForPlane (pl->normal, pl->dist); + AddPortalToNodes (p, node, &tree->outside_node); + } + +// clip the basewindings by all the other planes + for (i=0 ; i<6 ; i++) + { + for (j=0 ; j<6 ; j++) + { + if (j == i) continue; + ChopWindingInPlace (&portals[i]->winding, bplanes[j].normal, bplanes[j].dist, ON_EPSILON); + } //end for + } //end for +} //end of the function MakeHeadNodePortals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#define BASE_WINDING_EPSILON 0.001 +#define SPLIT_WINDING_EPSILON 0.001 + +winding_t *BaseWindingForNode (node_t *node) +{ + winding_t *w; + node_t *n; + plane_t *plane; + vec3_t normal; + vec_t dist; + + w = BaseWindingForPlane (mapplanes[node->planenum].normal + , mapplanes[node->planenum].dist); + + // clip by all the parents + for (n=node->parent ; n && w ; ) + { + plane = &mapplanes[n->planenum]; + + if (n->children[0] == node) + { // take front + ChopWindingInPlace (&w, plane->normal, plane->dist, BASE_WINDING_EPSILON); + } + else + { // take back + VectorSubtract (vec3_origin, plane->normal, normal); + dist = -plane->dist; + ChopWindingInPlace (&w, normal, dist, BASE_WINDING_EPSILON); + } + node = n; + n = n->parent; + } + + return w; +} //end of the function BaseWindingForNode +//=========================================================================== +// create the new portal by taking the full plane winding for the cutting +// plane and clipping it by all of parents of this node +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean WindingIsTiny (winding_t *w); + +void MakeNodePortal (node_t *node) +{ + portal_t *new_portal, *p; + winding_t *w; + vec3_t normal; + float dist; + int side; + + w = BaseWindingForNode (node); + + // clip the portal by all the other portals in the node + for (p = node->portals; p && w; p = p->next[side]) + { + if (p->nodes[0] == node) + { + side = 0; + VectorCopy (p->plane.normal, normal); + dist = p->plane.dist; + } //end if + else if (p->nodes[1] == node) + { + side = 1; + VectorSubtract (vec3_origin, p->plane.normal, normal); + dist = -p->plane.dist; + } //end else if + else + { + Error ("MakeNodePortal: mislinked portal"); + } //end else + ChopWindingInPlace (&w, normal, dist, 0.1); + } //end for + + if (!w) + { + return; + } //end if + + if (WindingIsTiny (w)) + { + c_tinyportals++; + FreeWinding(w); + return; + } //end if + +#ifdef DEBUG +/* //NOTE: don't use this winding ok check + // all the invalid windings only have a degenerate edge + if (WindingError(w)) + { + Log_Print("MakeNodePortal: %s\n", WindingErrorString()); + FreeWinding(w); + return; + } //end if*/ +#endif //DEBUG + + + new_portal = AllocPortal(); + new_portal->plane = mapplanes[node->planenum]; + +#ifdef ME + new_portal->planenum = node->planenum; +#endif //ME + + new_portal->onnode = node; + new_portal->winding = w; + AddPortalToNodes (new_portal, node->children[0], node->children[1]); +} //end of the function MakeNodePortal +//=========================================================================== +// Move or split the portals that bound node so that the node's +// children have portals instead of node. +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void SplitNodePortals (node_t *node) +{ + portal_t *p, *next_portal, *new_portal; + node_t *f, *b, *other_node; + int side; + plane_t *plane; + winding_t *frontwinding, *backwinding; + + plane = &mapplanes[node->planenum]; + f = node->children[0]; + b = node->children[1]; + + for (p = node->portals ; p ; p = next_portal) + { + if (p->nodes[0] == node) side = 0; + else if (p->nodes[1] == node) side = 1; + else Error ("SplitNodePortals: mislinked portal"); + next_portal = p->next[side]; + + other_node = p->nodes[!side]; + RemovePortalFromNode (p, p->nodes[0]); + RemovePortalFromNode (p, p->nodes[1]); + +// +// cut the portal into two portals, one on each side of the cut plane +// + ClipWindingEpsilon (p->winding, plane->normal, plane->dist, + SPLIT_WINDING_EPSILON, &frontwinding, &backwinding); + + if (frontwinding && WindingIsTiny(frontwinding)) + { + FreeWinding (frontwinding); + frontwinding = NULL; + c_tinyportals++; + } //end if + + if (backwinding && WindingIsTiny(backwinding)) + { + FreeWinding (backwinding); + backwinding = NULL; + c_tinyportals++; + } //end if + +#ifdef DEBUG +/* //NOTE: don't use this winding ok check + // all the invalid windings only have a degenerate edge + if (frontwinding && WindingError(frontwinding)) + { + Log_Print("SplitNodePortals: front %s\n", WindingErrorString()); + FreeWinding(frontwinding); + frontwinding = NULL; + } //end if + if (backwinding && WindingError(backwinding)) + { + Log_Print("SplitNodePortals: back %s\n", WindingErrorString()); + FreeWinding(backwinding); + backwinding = NULL; + } //end if*/ +#endif //DEBUG + + if (!frontwinding && !backwinding) + { // tiny windings on both sides + continue; + } + + if (!frontwinding) + { + FreeWinding (backwinding); + if (side == 0) AddPortalToNodes (p, b, other_node); + else AddPortalToNodes (p, other_node, b); + continue; + } + if (!backwinding) + { + FreeWinding (frontwinding); + if (side == 0) AddPortalToNodes (p, f, other_node); + else AddPortalToNodes (p, other_node, f); + continue; + } + + // the winding is split + new_portal = AllocPortal(); + *new_portal = *p; + new_portal->winding = backwinding; + FreeWinding (p->winding); + p->winding = frontwinding; + + if (side == 0) + { + AddPortalToNodes (p, f, other_node); + AddPortalToNodes (new_portal, b, other_node); + } //end if + else + { + AddPortalToNodes (p, other_node, f); + AddPortalToNodes (new_portal, other_node, b); + } //end else + } + + node->portals = NULL; +} //end of the function SplitNodePortals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void CalcNodeBounds (node_t *node) +{ + portal_t *p; + int s; + int i; + + // calc mins/maxs for both leaves and nodes + ClearBounds (node->mins, node->maxs); + for (p = node->portals ; p ; p = p->next[s]) + { + s = (p->nodes[1] == node); + for (i=0 ; iwinding->numpoints ; i++) + AddPointToBounds (p->winding->p[i], node->mins, node->maxs); + } +} //end of the function CalcNodeBounds +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int c_numportalizednodes; + +void MakeTreePortals_r (node_t *node) +{ + int i; + +#ifdef ME + qprintf("\r%6d", ++c_numportalizednodes); + if (cancelconversion) return; +#endif //ME + + CalcNodeBounds (node); + if (node->mins[0] >= node->maxs[0]) + { + Log_Print("WARNING: node without a volume\n"); + } + + for (i=0 ; i<3 ; i++) + { + if (node->mins[i] < -MAX_MAP_BOUNDS || node->maxs[i] > MAX_MAP_BOUNDS) + { + Log_Print("WARNING: node with unbounded volume\n"); + break; + } + } + if (node->planenum == PLANENUM_LEAF) + return; + + MakeNodePortal (node); + SplitNodePortals (node); + + MakeTreePortals_r (node->children[0]); + MakeTreePortals_r (node->children[1]); +} //end of the function MakeTreePortals_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void MakeTreePortals(tree_t *tree) +{ + +#ifdef ME + Log_Print("---- Node Portalization ----\n"); + c_numportalizednodes = 0; + c_portalmemory = 0; + qprintf("%6d nodes portalized", c_numportalizednodes); +#endif //ME + + MakeHeadnodePortals(tree); + MakeTreePortals_r(tree->headnode); + +#ifdef ME + qprintf("\n"); + Log_Write("%6d nodes portalized\r\n", c_numportalizednodes); + Log_Print("%6d tiny portals\r\n", c_tinyportals); + Log_Print("%6d KB of portal memory\r\n", c_portalmemory >> 10); + Log_Print("%6i KB of winding memory\r\n", WindingMemory() >> 10); +#endif //ME +} //end of the function MakeTreePortals + +/* +========================================================= + +FLOOD ENTITIES + +========================================================= +*/ +//#define P_NODESTACK + +node_t *p_firstnode; +node_t *p_lastnode; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef P_NODESTACK +void P_AddNodeToList(node_t *node) +{ + node->next = p_firstnode; + p_firstnode = node; + if (!p_lastnode) p_lastnode = node; +} //end of the function P_AddNodeToList +#else //it's a node queue +//add the node to the end of the node list +void P_AddNodeToList(node_t *node) +{ + node->next = NULL; + if (p_lastnode) p_lastnode->next = node; + else p_firstnode = node; + p_lastnode = node; +} //end of the function P_AddNodeToList +#endif //P_NODESTACK +//=========================================================================== +// get the first node from the front of the node list +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +node_t *P_NextNodeFromList(void) +{ + node_t *node; + + node = p_firstnode; + if (p_firstnode) p_firstnode = p_firstnode->next; + if (!p_firstnode) p_lastnode = NULL; + return node; +} //end of the function P_NextNodeFromList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FloodPortals(node_t *firstnode) +{ + node_t *node; + portal_t *p; + int s; + + firstnode->occupied = 1; + P_AddNodeToList(firstnode); + + for (node = P_NextNodeFromList(); node; node = P_NextNodeFromList()) + { + for (p = node->portals; p; p = p->next[s]) + { + s = (p->nodes[1] == node); + //if the node at the other side of the portal is occupied already + if (p->nodes[!s]->occupied) continue; + //if it isn't possible to flood through this portal + if (!Portal_EntityFlood(p, s)) continue; + // + p->nodes[!s]->occupied = node->occupied + 1; + // + P_AddNodeToList(p->nodes[!s]); + } //end for + } //end for +} //end of the function FloodPortals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int numrec; + +void FloodPortals_r (node_t *node, int dist) +{ + portal_t *p; + int s; +// int i; + + Log_Print("\r%6d", ++numrec); + + if (node->occupied) Error("FloodPortals_r: node already occupied\n"); + if (!node) + { + Error("FloodPortals_r: NULL node\n"); + } //end if*/ + node->occupied = dist; + + for (p = node->portals; p; p = p->next[s]) + { + s = (p->nodes[1] == node); + //if the node at the other side of the portal is occupied already + if (p->nodes[!s]->occupied) continue; + //if it isn't possible to flood through this portal + if (!Portal_EntityFlood(p, s)) continue; + //flood recursively through the current portal + FloodPortals_r(p->nodes[!s], dist+1); + } //end for + Log_Print("\r%6d", --numrec); +} //end of the function FloodPortals_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean PlaceOccupant (node_t *headnode, vec3_t origin, entity_t *occupant) +{ + node_t *node; + vec_t d; + plane_t *plane; + + //find the leaf to start in + node = headnode; + while(node->planenum != PLANENUM_LEAF) + { + if (node->planenum < 0 || node->planenum > nummapplanes) + { + Error("PlaceOccupant: invalid node->planenum\n"); + } //end if + plane = &mapplanes[node->planenum]; + d = DotProduct(origin, plane->normal) - plane->dist; + if (d >= 0) node = node->children[0]; + else node = node->children[1]; + if (!node) + { + Error("PlaceOccupant: invalid child %d\n", d < 0); + } //end if + } //end while + //don't start in solid +// if (node->contents == CONTENTS_SOLID) + //ME: replaced because in LeafNode in brushbsp.c + // some nodes have contents solid with other contents + if (node->contents & CONTENTS_SOLID) return false; + //if the node is already occupied + if (node->occupied) return false; + + //place the occupant in the first leaf + node->occupant = occupant; + + numrec = 0; +// Log_Print("%6d recurses", numrec); +// FloodPortals_r(node, 1); +// Log_Print("\n"); + FloodPortals(node); + + return true; +} //end of the function PlaceOccupant +//=========================================================================== +// Marks all nodes that can be reached by entites +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean FloodEntities (tree_t *tree) +{ + int i; + int x, y; + vec3_t origin; + char *cl; + qboolean inside; + node_t *headnode; + + headnode = tree->headnode; + Log_Print("------ FloodEntities -------\n"); + inside = false; + tree->outside_node.occupied = 0; + + //start at entity 1 not the world ( = 0) + for (i = 1; i < num_entities; i++) + { + GetVectorForKey(&entities[i], "origin", origin); + if (VectorCompare(origin, vec3_origin)) continue; + + cl = ValueForKey(&entities[i], "classname"); + origin[2] += 1; //so objects on floor are ok + +// Log_Print("flooding from entity %d: %s\n", i, cl); + //nudge playerstart around if needed so clipping hulls allways + //have a valid point + if (!strcmp(cl, "info_player_start")) + { + for (x = -16; x <= 16; x += 16) + { + for (y = -16; y <= 16; y += 16) + { + origin[0] += x; + origin[1] += y; + if (PlaceOccupant(headnode, origin, &entities[i])) + { + inside = true; + x = 999; //stop for this info_player_start + break; + } //end if + origin[0] -= x; + origin[1] -= y; + } //end for + } //end for + } //end if + else + { + if (PlaceOccupant(headnode, origin, &entities[i])) + { + inside = true; + } //end if + } //end else + } //end for + + if (!inside) + { + Log_Print("WARNING: no entities inside\n"); + } //end if + else if (tree->outside_node.occupied) + { + Log_Print("WARNING: entity reached from outside\n"); + } //end else if + + return (qboolean)(inside && !tree->outside_node.occupied); +} //end of the function FloodEntities + +/* +========================================================= + +FILL OUTSIDE + +========================================================= +*/ + +int c_outside; +int c_inside; +int c_solid; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FillOutside_r (node_t *node) +{ + if (node->planenum != PLANENUM_LEAF) + { + FillOutside_r (node->children[0]); + FillOutside_r (node->children[1]); + return; + } //end if + // anything not reachable by an entity + // can be filled away (by setting solid contents) + if (!node->occupied) + { + if (!(node->contents & CONTENTS_SOLID)) + { + c_outside++; + node->contents |= CONTENTS_SOLID; + } //end if + else + { + c_solid++; + } //end else + } //end if + else + { + c_inside++; + } //end else +} //end of the function FillOutside_r +//=========================================================================== +// Fill all nodes that can't be reached by entities +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FillOutside (node_t *headnode) +{ + c_outside = 0; + c_inside = 0; + c_solid = 0; + Log_Print("------- FillOutside --------\n"); + FillOutside_r (headnode); + Log_Print("%5i solid leaves\n", c_solid); + Log_Print("%5i leaves filled\n", c_outside); + Log_Print("%5i inside leaves\n", c_inside); +} //end of the function FillOutside + +/* +========================================================= + +FLOOD AREAS + +========================================================= +*/ + +int c_areas; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FloodAreas_r (node_t *node) +{ + portal_t *p; + int s; + bspbrush_t *b; + entity_t *e; + + if (node->contents == CONTENTS_AREAPORTAL) + { + // this node is part of an area portal + b = node->brushlist; + e = &entities[b->original->entitynum]; + + // if the current area has allready touched this + // portal, we are done + if (e->portalareas[0] == c_areas || e->portalareas[1] == c_areas) + return; + + // note the current area as bounding the portal + if (e->portalareas[1]) + { + Log_Print("WARNING: areaportal entity %i touches > 2 areas\n", b->original->entitynum); + return; + } + if (e->portalareas[0]) + e->portalareas[1] = c_areas; + else + e->portalareas[0] = c_areas; + + return; + } //end if + + if (node->area) + return; // allready got it + node->area = c_areas; + + for (p=node->portals ; p ; p = p->next[s]) + { + s = (p->nodes[1] == node); +#if 0 + if (p->nodes[!s]->occupied) + continue; +#endif + if (!Portal_EntityFlood (p, s)) + continue; + + FloodAreas_r (p->nodes[!s]); + } //end for +} //end of the function FloodAreas_r +//=========================================================================== +// Just decend the tree, and for each node that hasn't had an +// area set, flood fill out from there +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FindAreas_r (node_t *node) +{ + if (node->planenum != PLANENUM_LEAF) + { + FindAreas_r (node->children[0]); + FindAreas_r (node->children[1]); + return; + } + + if (node->area) + return; // allready got it + + if (node->contents & CONTENTS_SOLID) + return; + + if (!node->occupied) + return; // not reachable by entities + + // area portals are allways only flooded into, never + // out of + if (node->contents == CONTENTS_AREAPORTAL) + return; + + c_areas++; + FloodAreas_r (node); +} //end of the function FindAreas_r +//=========================================================================== +// Just decend the tree, and for each node that hasn't had an +// area set, flood fill out from there +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void SetAreaPortalAreas_r (node_t *node) +{ + bspbrush_t *b; + entity_t *e; + + if (node->planenum != PLANENUM_LEAF) + { + SetAreaPortalAreas_r (node->children[0]); + SetAreaPortalAreas_r (node->children[1]); + return; + } //end if + + if (node->contents == CONTENTS_AREAPORTAL) + { + if (node->area) + return; // allready set + + b = node->brushlist; + e = &entities[b->original->entitynum]; + node->area = e->portalareas[0]; + if (!e->portalareas[1]) + { + Log_Print("WARNING: areaportal entity %i doesn't touch two areas\n", b->original->entitynum); + return; + } //end if + } //end if +} //end of the function SetAreaPortalAreas_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +/* +void EmitAreaPortals(node_t *headnode) +{ + int i, j; + entity_t *e; + dareaportal_t *dp; + + if (c_areas > MAX_MAP_AREAS) + Error ("MAX_MAP_AREAS"); + numareas = c_areas+1; + numareaportals = 1; // leave 0 as an error + + for (i=1 ; i<=c_areas ; i++) + { + dareas[i].firstareaportal = numareaportals; + for (j=0 ; jareaportalnum) + continue; + dp = &dareaportals[numareaportals]; + if (e->portalareas[0] == i) + { + dp->portalnum = e->areaportalnum; + dp->otherarea = e->portalareas[1]; + numareaportals++; + } //end if + else if (e->portalareas[1] == i) + { + dp->portalnum = e->areaportalnum; + dp->otherarea = e->portalareas[0]; + numareaportals++; + } //end else if + } //end for + dareas[i].numareaportals = numareaportals - dareas[i].firstareaportal; + } //end for + + Log_Print("%5i numareas\n", numareas); + Log_Print("%5i numareaportals\n", numareaportals); +} //end of the function EmitAreaPortals +*/ +//=========================================================================== +// Mark each leaf with an area, bounded by CONTENTS_AREAPORTAL +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FloodAreas (tree_t *tree) +{ + Log_Print("--- FloodAreas ---\n"); + FindAreas_r (tree->headnode); + SetAreaPortalAreas_r (tree->headnode); + Log_Print("%5i areas\n", c_areas); +} //end of the function FloodAreas +//=========================================================================== +// Finds a brush side to use for texturing the given portal +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FindPortalSide (portal_t *p) +{ + int viscontents; + bspbrush_t *bb; + mapbrush_t *brush; + node_t *n; + int i,j; + int planenum; + side_t *side, *bestside; + float dot, bestdot; + plane_t *p1, *p2; + + // decide which content change is strongest + // solid > lava > water, etc + viscontents = VisibleContents (p->nodes[0]->contents ^ p->nodes[1]->contents); + if (!viscontents) + return; + + planenum = p->onnode->planenum; + bestside = NULL; + bestdot = 0; + + for (j=0 ; j<2 ; j++) + { + n = p->nodes[j]; + p1 = &mapplanes[p->onnode->planenum]; + for (bb=n->brushlist ; bb ; bb=bb->next) + { + brush = bb->original; + if ( !(brush->contents & viscontents) ) + continue; + for (i=0 ; inumsides ; i++) + { + side = &brush->original_sides[i]; + if (side->flags & SFL_BEVEL) + continue; + if (side->texinfo == TEXINFO_NODE) + continue; // non-visible + if ((side->planenum&~1) == planenum) + { // exact match + bestside = &brush->original_sides[i]; + goto gotit; + } //end if + // see how close the match is + p2 = &mapplanes[side->planenum&~1]; + dot = DotProduct (p1->normal, p2->normal); + if (dot > bestdot) + { + bestdot = dot; + bestside = side; + } //end if + } //end for + } //end for + } //end for + +gotit: + if (!bestside) + Log_Print("WARNING: side not found for portal\n"); + + p->sidefound = true; + p->side = bestside; +} //end of the function FindPortalSide +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void MarkVisibleSides_r (node_t *node) +{ + portal_t *p; + int s; + + if (node->planenum != PLANENUM_LEAF) + { + MarkVisibleSides_r (node->children[0]); + MarkVisibleSides_r (node->children[1]); + return; + } //end if + + // empty leaves are never boundary leaves + if (!node->contents) return; + + // see if there is a visible face + for (p=node->portals ; p ; p = p->next[!s]) + { + s = (p->nodes[0] == node); + if (!p->onnode) + continue; // edge of world + if (!p->sidefound) + FindPortalSide (p); + if (p->side) + p->side->flags |= SFL_VISIBLE; + } //end for +} //end of the function MarkVisibleSides_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void MarkVisibleSides(tree_t *tree, int startbrush, int endbrush) +{ + int i, j; + mapbrush_t *mb; + int numsides; + + Log_Print("--- MarkVisibleSides ---\n"); + + // clear all the visible flags + for (i=startbrush ; inumsides; + for (j=0 ; joriginal_sides[j].flags &= ~SFL_VISIBLE; + } + + // set visible flags on the sides that are used by portals + MarkVisibleSides_r (tree->headnode); +} //end of the function MarkVisibleSides + diff --git a/code/bspc/prtfile.c b/code/bspc/prtfile.c index 69834aa..58d1206 100755 --- a/code/bspc/prtfile.c +++ b/code/bspc/prtfile.c @@ -1,287 +1,287 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "qbsp.h" - -/* -============================================================================== - -PORTAL FILE GENERATION - -Save out name.prt for qvis to read -============================================================================== -*/ - - -#define PORTALFILE "PRT1" - -FILE *pf; -int num_visclusters; // clusters the player can be in -int num_visportals; - -void WriteFloat2 (FILE *f, vec_t v) -{ - if ( fabs(v - Q_rint(v)) < 0.001 ) - fprintf (f,"%i ",(int)Q_rint(v)); - else - fprintf (f,"%f ",v); -} - -/* -================= -WritePortalFile_r -================= -*/ -void WritePortalFile_r (node_t *node) -{ - int i, s; - portal_t *p; - winding_t *w; - vec3_t normal; - vec_t dist; - - // decision node - if (node->planenum != PLANENUM_LEAF && !node->detail_seperator) - { - WritePortalFile_r (node->children[0]); - WritePortalFile_r (node->children[1]); - return; - } - - if (node->contents & CONTENTS_SOLID) - return; - - for (p = node->portals ; p ; p=p->next[s]) - { - w = p->winding; - s = (p->nodes[1] == node); - if (w && p->nodes[0] == node) - { - if (!Portal_VisFlood (p)) - continue; - // write out to the file - - // sometimes planes get turned around when they are very near - // the changeover point between different axis. interpret the - // plane the same way vis will, and flip the side orders if needed - // FIXME: is this still relevent? - WindingPlane (w, normal, &dist); - if ( DotProduct (p->plane.normal, normal) < 0.99 ) - { // backwards... - fprintf (pf,"%i %i %i ",w->numpoints, p->nodes[1]->cluster, p->nodes[0]->cluster); - } - else - fprintf (pf,"%i %i %i ",w->numpoints, p->nodes[0]->cluster, p->nodes[1]->cluster); - for (i=0 ; inumpoints ; i++) - { - fprintf (pf,"("); - WriteFloat2 (pf, w->p[i][0]); - WriteFloat2 (pf, w->p[i][1]); - WriteFloat2 (pf, w->p[i][2]); - fprintf (pf,") "); - } - fprintf (pf,"\n"); - } - } - -} - -/* -================ -FillLeafNumbers_r - -All of the leafs under node will have the same cluster -================ -*/ -void FillLeafNumbers_r (node_t *node, int num) -{ - if (node->planenum == PLANENUM_LEAF) - { - if (node->contents & CONTENTS_SOLID) - node->cluster = -1; - else - node->cluster = num; - return; - } - node->cluster = num; - FillLeafNumbers_r (node->children[0], num); - FillLeafNumbers_r (node->children[1], num); -} - -/* -================ -NumberLeafs_r -================ -*/ -void NumberLeafs_r (node_t *node) -{ - portal_t *p; - - if (node->planenum != PLANENUM_LEAF && !node->detail_seperator) - { // decision node - node->cluster = -99; - NumberLeafs_r (node->children[0]); - NumberLeafs_r (node->children[1]); - return; - } - - // either a leaf or a detail cluster - - if ( node->contents & CONTENTS_SOLID ) - { // solid block, viewpoint never inside - node->cluster = -1; - return; - } - - FillLeafNumbers_r (node, num_visclusters); - num_visclusters++; - - // count the portals - for (p = node->portals ; p ; ) - { - if (p->nodes[0] == node) // only write out from first leaf - { - if (Portal_VisFlood (p)) - num_visportals++; - p = p->next[0]; - } - else - p = p->next[1]; - } - -} - - -/* -================ -CreateVisPortals_r -================ -*/ -void CreateVisPortals_r (node_t *node) -{ - // stop as soon as we get to a detail_seperator, which - // means that everything below is in a single cluster - if (node->planenum == PLANENUM_LEAF || node->detail_seperator ) - return; - - MakeNodePortal (node); - SplitNodePortals (node); - - CreateVisPortals_r (node->children[0]); - CreateVisPortals_r (node->children[1]); -} - -/* -================ -FinishVisPortals_r -================ -*/ -void FinishVisPortals2_r (node_t *node) -{ - if (node->planenum == PLANENUM_LEAF) - return; - - MakeNodePortal (node); - SplitNodePortals (node); - - FinishVisPortals2_r (node->children[0]); - FinishVisPortals2_r (node->children[1]); -} - -void FinishVisPortals_r (node_t *node) -{ - if (node->planenum == PLANENUM_LEAF) - return; - - if (node->detail_seperator) - { - FinishVisPortals2_r (node); - return; - } - - FinishVisPortals_r (node->children[0]); - FinishVisPortals_r (node->children[1]); -} - - -int clusterleaf; -void SaveClusters_r (node_t *node) -{ - if (node->planenum == PLANENUM_LEAF) - { - dleafs[clusterleaf++].cluster = node->cluster; - return; - } - SaveClusters_r (node->children[0]); - SaveClusters_r (node->children[1]); -} - -/* -================ -WritePortalFile -================ -*/ -void WritePortalFile (tree_t *tree) -{ - char filename[1024]; - node_t *headnode; - - qprintf ("--- WritePortalFile ---\n"); - - headnode = tree->headnode; - num_visclusters = 0; - num_visportals = 0; - - Tree_FreePortals_r (headnode); - - MakeHeadnodePortals (tree); - - CreateVisPortals_r (headnode); - -// set the cluster field in every leaf and count the total number of portals - - NumberLeafs_r (headnode); - -// write the file - sprintf (filename, "%s.prt", source); - printf ("writing %s\n", filename); - pf = fopen (filename, "w"); - if (!pf) - Error ("Error opening %s", filename); - - fprintf (pf, "%s\n", PORTALFILE); - fprintf (pf, "%i\n", num_visclusters); - fprintf (pf, "%i\n", num_visportals); - - qprintf ("%5i visclusters\n", num_visclusters); - qprintf ("%5i visportals\n", num_visportals); - - WritePortalFile_r (headnode); - - fclose (pf); - - // we need to store the clusters out now because ordering - // issues made us do this after writebsp... - clusterleaf = 1; - SaveClusters_r (headnode); -} - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "qbsp.h" + +/* +============================================================================== + +PORTAL FILE GENERATION + +Save out name.prt for qvis to read +============================================================================== +*/ + + +#define PORTALFILE "PRT1" + +FILE *pf; +int num_visclusters; // clusters the player can be in +int num_visportals; + +void WriteFloat2 (FILE *f, vec_t v) +{ + if ( fabs(v - Q_rint(v)) < 0.001 ) + fprintf (f,"%i ",(int)Q_rint(v)); + else + fprintf (f,"%f ",v); +} + +/* +================= +WritePortalFile_r +================= +*/ +void WritePortalFile_r (node_t *node) +{ + int i, s; + portal_t *p; + winding_t *w; + vec3_t normal; + vec_t dist; + + // decision node + if (node->planenum != PLANENUM_LEAF && !node->detail_seperator) + { + WritePortalFile_r (node->children[0]); + WritePortalFile_r (node->children[1]); + return; + } + + if (node->contents & CONTENTS_SOLID) + return; + + for (p = node->portals ; p ; p=p->next[s]) + { + w = p->winding; + s = (p->nodes[1] == node); + if (w && p->nodes[0] == node) + { + if (!Portal_VisFlood (p)) + continue; + // write out to the file + + // sometimes planes get turned around when they are very near + // the changeover point between different axis. interpret the + // plane the same way vis will, and flip the side orders if needed + // FIXME: is this still relevent? + WindingPlane (w, normal, &dist); + if ( DotProduct (p->plane.normal, normal) < 0.99 ) + { // backwards... + fprintf (pf,"%i %i %i ",w->numpoints, p->nodes[1]->cluster, p->nodes[0]->cluster); + } + else + fprintf (pf,"%i %i %i ",w->numpoints, p->nodes[0]->cluster, p->nodes[1]->cluster); + for (i=0 ; inumpoints ; i++) + { + fprintf (pf,"("); + WriteFloat2 (pf, w->p[i][0]); + WriteFloat2 (pf, w->p[i][1]); + WriteFloat2 (pf, w->p[i][2]); + fprintf (pf,") "); + } + fprintf (pf,"\n"); + } + } + +} + +/* +================ +FillLeafNumbers_r + +All of the leafs under node will have the same cluster +================ +*/ +void FillLeafNumbers_r (node_t *node, int num) +{ + if (node->planenum == PLANENUM_LEAF) + { + if (node->contents & CONTENTS_SOLID) + node->cluster = -1; + else + node->cluster = num; + return; + } + node->cluster = num; + FillLeafNumbers_r (node->children[0], num); + FillLeafNumbers_r (node->children[1], num); +} + +/* +================ +NumberLeafs_r +================ +*/ +void NumberLeafs_r (node_t *node) +{ + portal_t *p; + + if (node->planenum != PLANENUM_LEAF && !node->detail_seperator) + { // decision node + node->cluster = -99; + NumberLeafs_r (node->children[0]); + NumberLeafs_r (node->children[1]); + return; + } + + // either a leaf or a detail cluster + + if ( node->contents & CONTENTS_SOLID ) + { // solid block, viewpoint never inside + node->cluster = -1; + return; + } + + FillLeafNumbers_r (node, num_visclusters); + num_visclusters++; + + // count the portals + for (p = node->portals ; p ; ) + { + if (p->nodes[0] == node) // only write out from first leaf + { + if (Portal_VisFlood (p)) + num_visportals++; + p = p->next[0]; + } + else + p = p->next[1]; + } + +} + + +/* +================ +CreateVisPortals_r +================ +*/ +void CreateVisPortals_r (node_t *node) +{ + // stop as soon as we get to a detail_seperator, which + // means that everything below is in a single cluster + if (node->planenum == PLANENUM_LEAF || node->detail_seperator ) + return; + + MakeNodePortal (node); + SplitNodePortals (node); + + CreateVisPortals_r (node->children[0]); + CreateVisPortals_r (node->children[1]); +} + +/* +================ +FinishVisPortals_r +================ +*/ +void FinishVisPortals2_r (node_t *node) +{ + if (node->planenum == PLANENUM_LEAF) + return; + + MakeNodePortal (node); + SplitNodePortals (node); + + FinishVisPortals2_r (node->children[0]); + FinishVisPortals2_r (node->children[1]); +} + +void FinishVisPortals_r (node_t *node) +{ + if (node->planenum == PLANENUM_LEAF) + return; + + if (node->detail_seperator) + { + FinishVisPortals2_r (node); + return; + } + + FinishVisPortals_r (node->children[0]); + FinishVisPortals_r (node->children[1]); +} + + +int clusterleaf; +void SaveClusters_r (node_t *node) +{ + if (node->planenum == PLANENUM_LEAF) + { + dleafs[clusterleaf++].cluster = node->cluster; + return; + } + SaveClusters_r (node->children[0]); + SaveClusters_r (node->children[1]); +} + +/* +================ +WritePortalFile +================ +*/ +void WritePortalFile (tree_t *tree) +{ + char filename[1024]; + node_t *headnode; + + qprintf ("--- WritePortalFile ---\n"); + + headnode = tree->headnode; + num_visclusters = 0; + num_visportals = 0; + + Tree_FreePortals_r (headnode); + + MakeHeadnodePortals (tree); + + CreateVisPortals_r (headnode); + +// set the cluster field in every leaf and count the total number of portals + + NumberLeafs_r (headnode); + +// write the file + sprintf (filename, "%s.prt", source); + printf ("writing %s\n", filename); + pf = fopen (filename, "w"); + if (!pf) + Error ("Error opening %s", filename); + + fprintf (pf, "%s\n", PORTALFILE); + fprintf (pf, "%i\n", num_visclusters); + fprintf (pf, "%i\n", num_visportals); + + qprintf ("%5i visclusters\n", num_visclusters); + qprintf ("%5i visportals\n", num_visportals); + + WritePortalFile_r (headnode); + + fclose (pf); + + // we need to store the clusters out now because ordering + // issues made us do this after writebsp... + clusterleaf = 1; + SaveClusters_r (headnode); +} + diff --git a/code/bspc/q2files.h b/code/bspc/q2files.h index 5ffea6e..317e47c 100755 --- a/code/bspc/q2files.h +++ b/code/bspc/q2files.h @@ -1,487 +1,487 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -// -// qfiles.h: quake file formats -// This file must be identical in the quake and utils directories -// - -/* -======================================================================== - -The .pak files are just a linear collapse of a directory tree - -======================================================================== -*/ - -#define IDPAKHEADER (('K'<<24)+('C'<<16)+('A'<<8)+'P') - -typedef struct -{ - char name[56]; - int filepos, filelen; -} dpackfile_t; - -typedef struct -{ - int ident; // == IDPAKHEADER - int dirofs; - int dirlen; -} dpackheader_t; - -#define MAX_FILES_IN_PACK 4096 - - -/* -======================================================================== - -PCX files are used for as many images as possible - -======================================================================== -*/ - -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; - - -/* -======================================================================== - -.MD2 triangle model file format - -======================================================================== -*/ - -#define IDALIASHEADER (('2'<<24)+('P'<<16)+('D'<<8)+'I') -#define ALIAS_VERSION 8 - -#define MAX_TRIANGLES 4096 -#define MAX_VERTS 2048 -#define MAX_FRAMES 512 -#define MAX_MD2SKINS 32 -#define MAX_SKINNAME 64 - -typedef struct -{ - short s; - short t; -} dstvert_t; - -typedef struct -{ - short index_xyz[3]; - short index_st[3]; -} dtriangle_t; - -typedef struct -{ - byte v[3]; // scaled byte to fit in frame mins/maxs - byte lightnormalindex; -} dtrivertx_t; - -#define DTRIVERTX_V0 0 -#define DTRIVERTX_V1 1 -#define DTRIVERTX_V2 2 -#define DTRIVERTX_LNI 3 -#define DTRIVERTX_SIZE 4 - -typedef struct -{ - float scale[3]; // multiply byte verts by this - float translate[3]; // then add this - char name[16]; // frame name from grabbing - dtrivertx_t verts[1]; // variable sized -} daliasframe_t; - - -// the glcmd format: -// a positive integer starts a tristrip command, followed by that many -// vertex structures. -// a negative integer starts a trifan command, followed by -x vertexes -// a zero indicates the end of the command list. -// a vertex consists of a floating point s, a floating point t, -// and an integer vertex index. - - -typedef struct -{ - int ident; - int version; - - int skinwidth; - int skinheight; - int framesize; // byte size of each frame - - int num_skins; - int num_xyz; - int num_st; // greater than num_xyz for seams - int num_tris; - int num_glcmds; // dwords in strip/fan command list - int num_frames; - - int ofs_skins; // each skin is a MAX_SKINNAME string - int ofs_st; // byte offset from start for stverts - int ofs_tris; // offset for dtriangles - int ofs_frames; // offset for first frame - int ofs_glcmds; - int ofs_end; // end of file - -} dmdl_t; - -/* -======================================================================== - -.SP2 sprite file format - -======================================================================== -*/ - -#define IDSPRITEHEADER (('2'<<24)+('S'<<16)+('D'<<8)+'I') - // little-endian "IDS2" -#define SPRITE_VERSION 2 - -typedef struct -{ - int width, height; - int origin_x, origin_y; // raster coordinates inside pic - char name[MAX_SKINNAME]; // name of pcx file -} dsprframe_t; - -typedef struct { - int ident; - int version; - int numframes; - dsprframe_t frames[1]; // variable sized -} dsprite_t; - -/* -============================================================================== - - .WAL texture file format - -============================================================================== -*/ - - -#define MIPLEVELS 4 -typedef struct miptex_s -{ - char name[32]; - unsigned width, height; - unsigned offsets[MIPLEVELS]; // four mip maps stored - char animname[32]; // next frame in animation chain - int flags; - int contents; - int value; -} miptex_t; - - - -/* -============================================================================== - - .BSP file format - -============================================================================== -*/ - -#define IDBSPHEADER (('P'<<24)+('S'<<16)+('B'<<8)+'I') - // little-endian "IBSP" - -#define BSPVERSION 38 - - -// upper design bounds -// leaffaces, leafbrushes, planes, and verts are still bounded by -// 16 bit short limits -#define MAX_MAP_MODELS 1024 -#define MAX_MAP_BRUSHES 8192 -#define MAX_MAP_ENTITIES 2048 -#define MAX_MAP_ENTSTRING 0x40000 -#define MAX_MAP_TEXINFO 8192 - -#define MAX_MAP_AREAS 256 -#define MAX_MAP_AREAPORTALS 1024 -#define MAX_MAP_PLANES 65536 -#define MAX_MAP_NODES 65536 -#define MAX_MAP_BRUSHSIDES 65536 -#define MAX_MAP_LEAFS 65536 -#define MAX_MAP_VERTS 65536 -#define MAX_MAP_FACES 65536 -#define MAX_MAP_LEAFFACES 65536 -#define MAX_MAP_LEAFBRUSHES 65536 -#define MAX_MAP_PORTALS 65536 -#define MAX_MAP_EDGES 128000 -#define MAX_MAP_SURFEDGES 256000 -#define MAX_MAP_LIGHTING 0x320000 -#define MAX_MAP_VISIBILITY 0x280000 - -// key / value pair sizes - -#define MAX_KEY 32 -#define MAX_VALUE 1024 - -//============================================================================= - -typedef struct -{ - int fileofs, filelen; -} lump_t; - -#define LUMP_ENTITIES 0 -#define LUMP_PLANES 1 -#define LUMP_VERTEXES 2 -#define LUMP_VISIBILITY 3 -#define LUMP_NODES 4 -#define LUMP_TEXINFO 5 -#define LUMP_FACES 6 -#define LUMP_LIGHTING 7 -#define LUMP_LEAFS 8 -#define LUMP_LEAFFACES 9 -#define LUMP_LEAFBRUSHES 10 -#define LUMP_EDGES 11 -#define LUMP_SURFEDGES 12 -#define LUMP_MODELS 13 -#define LUMP_BRUSHES 14 -#define LUMP_BRUSHSIDES 15 -#define LUMP_POP 16 -#define LUMP_AREAS 17 -#define LUMP_AREAPORTALS 18 -#define HEADER_LUMPS 19 - -typedef struct -{ - int ident; - int version; - lump_t lumps[HEADER_LUMPS]; -} dheader_t; - -typedef struct -{ - float mins[3], maxs[3]; - float origin[3]; // for sounds or lights - int headnode; - int firstface, numfaces; // submodels just draw faces - // without walking the bsp tree -} dmodel_t; - - -typedef struct -{ - float point[3]; -} dvertex_t; - - -// 0-2 are axial planes -#define PLANE_X 0 -#define PLANE_Y 1 -#define PLANE_Z 2 - -// 3-5 are non-axial planes snapped to the nearest -#define PLANE_ANYX 3 -#define PLANE_ANYY 4 -#define PLANE_ANYZ 5 - -// planes (x&~1) and (x&~1)+1 are allways opposites - -typedef struct -{ - float normal[3]; - float dist; - int type; // PLANE_X - PLANE_ANYZ ?remove? trivial to regenerate -} dplane_t; - - -// contents flags are seperate bits -// a given brush can contribute multiple content bits -// multiple brushes can be in a single leaf - -// these definitions also need to be in q_shared.h! - -// lower bits are stronger, and will eat weaker brushes completely -#define CONTENTS_SOLID 1 // an eye is never valid in a solid -#define CONTENTS_WINDOW 2 // translucent, but not watery -#define CONTENTS_AUX 4 -#define CONTENTS_LAVA 8 -#define CONTENTS_SLIME 16 -#define CONTENTS_WATER 32 -#define CONTENTS_MIST 64 -#define LAST_VISIBLE_CONTENTS 64 - -// remaining contents are non-visible, and don't eat brushes - -#define CONTENTS_AREAPORTAL 0x8000 - -#define CONTENTS_PLAYERCLIP 0x10000 -#define CONTENTS_MONSTERCLIP 0x20000 - -// currents can be added to any other contents, and may be mixed -#define CONTENTS_CURRENT_0 0x40000 -#define CONTENTS_CURRENT_90 0x80000 -#define CONTENTS_CURRENT_180 0x100000 -#define CONTENTS_CURRENT_270 0x200000 -#define CONTENTS_CURRENT_UP 0x400000 -#define CONTENTS_CURRENT_DOWN 0x800000 - -#define CONTENTS_ORIGIN 0x1000000 // removed before bsping an entity - -#define CONTENTS_MONSTER 0x2000000 // should never be on a brush, only in game -#define CONTENTS_DEADMONSTER 0x4000000 -#define CONTENTS_DETAIL 0x8000000 // brushes to be added after vis leafs -//renamed because it's in conflict with the Q3A translucent contents -#define CONTENTS_Q2TRANSLUCENT 0x10000000 // auto set if any surface has trans -#define CONTENTS_LADDER 0x20000000 - - - -#define SURF_LIGHT 0x1 // value will hold the light strength - -#define SURF_SLICK 0x2 // effects game physics - -#define SURF_SKY 0x4 // don't draw, but add to skybox -#define SURF_WARP 0x8 // turbulent water warp -#define SURF_TRANS33 0x10 -#define SURF_TRANS66 0x20 -#define SURF_FLOWING 0x40 // scroll towards angle -#define SURF_NODRAW 0x80 // don't bother referencing the texture - -#define SURF_HINT 0x100 // make a primary bsp splitter -#define SURF_SKIP 0x200 // completely ignore, allowing non-closed brushes - - - -typedef struct -{ - int planenum; - int children[2]; // negative numbers are -(leafs+1), not nodes - short mins[3]; // for frustom culling - short maxs[3]; - unsigned short firstface; - unsigned short numfaces; // counting both sides -} dnode_t; - - -typedef struct texinfo_s -{ - float vecs[2][4]; // [s/t][xyz offset] - int flags; // miptex flags + overrides - int value; // light emission, etc - char texture[32]; // texture name (textures/*.wal) - int nexttexinfo; // for animations, -1 = end of chain -} texinfo_t; - - -// note that edge 0 is never used, because negative edge nums are used for -// counterclockwise use of the edge in a face -typedef struct -{ - unsigned short v[2]; // vertex numbers -} dedge_t; - -#define MAXLIGHTMAPS 4 -typedef struct -{ - unsigned short planenum; - short side; - - int firstedge; // we must support > 64k edges - short numedges; - short texinfo; - -// lighting info - byte styles[MAXLIGHTMAPS]; - int lightofs; // start of [numstyles*surfsize] samples -} dface_t; - -typedef struct -{ - int contents; // OR of all brushes (not needed?) - - short cluster; - short area; - - short mins[3]; // for frustum culling - short maxs[3]; - - unsigned short firstleafface; - unsigned short numleaffaces; - - unsigned short firstleafbrush; - unsigned short numleafbrushes; -} dleaf_t; - -typedef struct -{ - unsigned short planenum; // facing out of the leaf - short texinfo; -} dbrushside_t; - -typedef struct -{ - int firstside; - int numsides; - int contents; -} dbrush_t; - -#define ANGLE_UP -1 -#define ANGLE_DOWN -2 - - -// the visibility lump consists of a header with a count, then -// byte offsets for the PVS and PHS of each cluster, then the raw -// compressed bit vectors -#define DVIS_PVS 0 -#define DVIS_PHS 1 -typedef struct -{ - int numclusters; - int bitofs[8][2]; // bitofs[numclusters][2] -} dvis_t; - -// each area has a list of portals that lead into other areas -// when portals are closed, other areas may not be visible or -// hearable even if the vis info says that it should be -typedef struct -{ - int portalnum; - int otherarea; -} dareaportal_t; - -typedef struct -{ - int numareaportals; - int firstareaportal; -} darea_t; +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// +// qfiles.h: quake file formats +// This file must be identical in the quake and utils directories +// + +/* +======================================================================== + +The .pak files are just a linear collapse of a directory tree + +======================================================================== +*/ + +#define IDPAKHEADER (('K'<<24)+('C'<<16)+('A'<<8)+'P') + +typedef struct +{ + char name[56]; + int filepos, filelen; +} dpackfile_t; + +typedef struct +{ + int ident; // == IDPAKHEADER + int dirofs; + int dirlen; +} dpackheader_t; + +#define MAX_FILES_IN_PACK 4096 + + +/* +======================================================================== + +PCX files are used for as many images as possible + +======================================================================== +*/ + +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; + + +/* +======================================================================== + +.MD2 triangle model file format + +======================================================================== +*/ + +#define IDALIASHEADER (('2'<<24)+('P'<<16)+('D'<<8)+'I') +#define ALIAS_VERSION 8 + +#define MAX_TRIANGLES 4096 +#define MAX_VERTS 2048 +#define MAX_FRAMES 512 +#define MAX_MD2SKINS 32 +#define MAX_SKINNAME 64 + +typedef struct +{ + short s; + short t; +} dstvert_t; + +typedef struct +{ + short index_xyz[3]; + short index_st[3]; +} dtriangle_t; + +typedef struct +{ + byte v[3]; // scaled byte to fit in frame mins/maxs + byte lightnormalindex; +} dtrivertx_t; + +#define DTRIVERTX_V0 0 +#define DTRIVERTX_V1 1 +#define DTRIVERTX_V2 2 +#define DTRIVERTX_LNI 3 +#define DTRIVERTX_SIZE 4 + +typedef struct +{ + float scale[3]; // multiply byte verts by this + float translate[3]; // then add this + char name[16]; // frame name from grabbing + dtrivertx_t verts[1]; // variable sized +} daliasframe_t; + + +// the glcmd format: +// a positive integer starts a tristrip command, followed by that many +// vertex structures. +// a negative integer starts a trifan command, followed by -x vertexes +// a zero indicates the end of the command list. +// a vertex consists of a floating point s, a floating point t, +// and an integer vertex index. + + +typedef struct +{ + int ident; + int version; + + int skinwidth; + int skinheight; + int framesize; // byte size of each frame + + int num_skins; + int num_xyz; + int num_st; // greater than num_xyz for seams + int num_tris; + int num_glcmds; // dwords in strip/fan command list + int num_frames; + + int ofs_skins; // each skin is a MAX_SKINNAME string + int ofs_st; // byte offset from start for stverts + int ofs_tris; // offset for dtriangles + int ofs_frames; // offset for first frame + int ofs_glcmds; + int ofs_end; // end of file + +} dmdl_t; + +/* +======================================================================== + +.SP2 sprite file format + +======================================================================== +*/ + +#define IDSPRITEHEADER (('2'<<24)+('S'<<16)+('D'<<8)+'I') + // little-endian "IDS2" +#define SPRITE_VERSION 2 + +typedef struct +{ + int width, height; + int origin_x, origin_y; // raster coordinates inside pic + char name[MAX_SKINNAME]; // name of pcx file +} dsprframe_t; + +typedef struct { + int ident; + int version; + int numframes; + dsprframe_t frames[1]; // variable sized +} dsprite_t; + +/* +============================================================================== + + .WAL texture file format + +============================================================================== +*/ + + +#define MIPLEVELS 4 +typedef struct miptex_s +{ + char name[32]; + unsigned width, height; + unsigned offsets[MIPLEVELS]; // four mip maps stored + char animname[32]; // next frame in animation chain + int flags; + int contents; + int value; +} miptex_t; + + + +/* +============================================================================== + + .BSP file format + +============================================================================== +*/ + +#define IDBSPHEADER (('P'<<24)+('S'<<16)+('B'<<8)+'I') + // little-endian "IBSP" + +#define BSPVERSION 38 + + +// upper design bounds +// leaffaces, leafbrushes, planes, and verts are still bounded by +// 16 bit short limits +#define MAX_MAP_MODELS 1024 +#define MAX_MAP_BRUSHES 8192 +#define MAX_MAP_ENTITIES 2048 +#define MAX_MAP_ENTSTRING 0x40000 +#define MAX_MAP_TEXINFO 8192 + +#define MAX_MAP_AREAS 256 +#define MAX_MAP_AREAPORTALS 1024 +#define MAX_MAP_PLANES 65536 +#define MAX_MAP_NODES 65536 +#define MAX_MAP_BRUSHSIDES 65536 +#define MAX_MAP_LEAFS 65536 +#define MAX_MAP_VERTS 65536 +#define MAX_MAP_FACES 65536 +#define MAX_MAP_LEAFFACES 65536 +#define MAX_MAP_LEAFBRUSHES 65536 +#define MAX_MAP_PORTALS 65536 +#define MAX_MAP_EDGES 128000 +#define MAX_MAP_SURFEDGES 256000 +#define MAX_MAP_LIGHTING 0x320000 +#define MAX_MAP_VISIBILITY 0x280000 + +// key / value pair sizes + +#define MAX_KEY 32 +#define MAX_VALUE 1024 + +//============================================================================= + +typedef struct +{ + int fileofs, filelen; +} lump_t; + +#define LUMP_ENTITIES 0 +#define LUMP_PLANES 1 +#define LUMP_VERTEXES 2 +#define LUMP_VISIBILITY 3 +#define LUMP_NODES 4 +#define LUMP_TEXINFO 5 +#define LUMP_FACES 6 +#define LUMP_LIGHTING 7 +#define LUMP_LEAFS 8 +#define LUMP_LEAFFACES 9 +#define LUMP_LEAFBRUSHES 10 +#define LUMP_EDGES 11 +#define LUMP_SURFEDGES 12 +#define LUMP_MODELS 13 +#define LUMP_BRUSHES 14 +#define LUMP_BRUSHSIDES 15 +#define LUMP_POP 16 +#define LUMP_AREAS 17 +#define LUMP_AREAPORTALS 18 +#define HEADER_LUMPS 19 + +typedef struct +{ + int ident; + int version; + lump_t lumps[HEADER_LUMPS]; +} dheader_t; + +typedef struct +{ + float mins[3], maxs[3]; + float origin[3]; // for sounds or lights + int headnode; + int firstface, numfaces; // submodels just draw faces + // without walking the bsp tree +} dmodel_t; + + +typedef struct +{ + float point[3]; +} dvertex_t; + + +// 0-2 are axial planes +#define PLANE_X 0 +#define PLANE_Y 1 +#define PLANE_Z 2 + +// 3-5 are non-axial planes snapped to the nearest +#define PLANE_ANYX 3 +#define PLANE_ANYY 4 +#define PLANE_ANYZ 5 + +// planes (x&~1) and (x&~1)+1 are allways opposites + +typedef struct +{ + float normal[3]; + float dist; + int type; // PLANE_X - PLANE_ANYZ ?remove? trivial to regenerate +} dplane_t; + + +// contents flags are seperate bits +// a given brush can contribute multiple content bits +// multiple brushes can be in a single leaf + +// these definitions also need to be in q_shared.h! + +// lower bits are stronger, and will eat weaker brushes completely +#define CONTENTS_SOLID 1 // an eye is never valid in a solid +#define CONTENTS_WINDOW 2 // translucent, but not watery +#define CONTENTS_AUX 4 +#define CONTENTS_LAVA 8 +#define CONTENTS_SLIME 16 +#define CONTENTS_WATER 32 +#define CONTENTS_MIST 64 +#define LAST_VISIBLE_CONTENTS 64 + +// remaining contents are non-visible, and don't eat brushes + +#define CONTENTS_AREAPORTAL 0x8000 + +#define CONTENTS_PLAYERCLIP 0x10000 +#define CONTENTS_MONSTERCLIP 0x20000 + +// currents can be added to any other contents, and may be mixed +#define CONTENTS_CURRENT_0 0x40000 +#define CONTENTS_CURRENT_90 0x80000 +#define CONTENTS_CURRENT_180 0x100000 +#define CONTENTS_CURRENT_270 0x200000 +#define CONTENTS_CURRENT_UP 0x400000 +#define CONTENTS_CURRENT_DOWN 0x800000 + +#define CONTENTS_ORIGIN 0x1000000 // removed before bsping an entity + +#define CONTENTS_MONSTER 0x2000000 // should never be on a brush, only in game +#define CONTENTS_DEADMONSTER 0x4000000 +#define CONTENTS_DETAIL 0x8000000 // brushes to be added after vis leafs +//renamed because it's in conflict with the Q3A translucent contents +#define CONTENTS_Q2TRANSLUCENT 0x10000000 // auto set if any surface has trans +#define CONTENTS_LADDER 0x20000000 + + + +#define SURF_LIGHT 0x1 // value will hold the light strength + +#define SURF_SLICK 0x2 // effects game physics + +#define SURF_SKY 0x4 // don't draw, but add to skybox +#define SURF_WARP 0x8 // turbulent water warp +#define SURF_TRANS33 0x10 +#define SURF_TRANS66 0x20 +#define SURF_FLOWING 0x40 // scroll towards angle +#define SURF_NODRAW 0x80 // don't bother referencing the texture + +#define SURF_HINT 0x100 // make a primary bsp splitter +#define SURF_SKIP 0x200 // completely ignore, allowing non-closed brushes + + + +typedef struct +{ + int planenum; + int children[2]; // negative numbers are -(leafs+1), not nodes + short mins[3]; // for frustom culling + short maxs[3]; + unsigned short firstface; + unsigned short numfaces; // counting both sides +} dnode_t; + + +typedef struct texinfo_s +{ + float vecs[2][4]; // [s/t][xyz offset] + int flags; // miptex flags + overrides + int value; // light emission, etc + char texture[32]; // texture name (textures/*.wal) + int nexttexinfo; // for animations, -1 = end of chain +} texinfo_t; + + +// note that edge 0 is never used, because negative edge nums are used for +// counterclockwise use of the edge in a face +typedef struct +{ + unsigned short v[2]; // vertex numbers +} dedge_t; + +#define MAXLIGHTMAPS 4 +typedef struct +{ + unsigned short planenum; + short side; + + int firstedge; // we must support > 64k edges + short numedges; + short texinfo; + +// lighting info + byte styles[MAXLIGHTMAPS]; + int lightofs; // start of [numstyles*surfsize] samples +} dface_t; + +typedef struct +{ + int contents; // OR of all brushes (not needed?) + + short cluster; + short area; + + short mins[3]; // for frustum culling + short maxs[3]; + + unsigned short firstleafface; + unsigned short numleaffaces; + + unsigned short firstleafbrush; + unsigned short numleafbrushes; +} dleaf_t; + +typedef struct +{ + unsigned short planenum; // facing out of the leaf + short texinfo; +} dbrushside_t; + +typedef struct +{ + int firstside; + int numsides; + int contents; +} dbrush_t; + +#define ANGLE_UP -1 +#define ANGLE_DOWN -2 + + +// the visibility lump consists of a header with a count, then +// byte offsets for the PVS and PHS of each cluster, then the raw +// compressed bit vectors +#define DVIS_PVS 0 +#define DVIS_PHS 1 +typedef struct +{ + int numclusters; + int bitofs[8][2]; // bitofs[numclusters][2] +} dvis_t; + +// each area has a list of portals that lead into other areas +// when portals are closed, other areas may not be visible or +// hearable even if the vis info says that it should be +typedef struct +{ + int portalnum; + int otherarea; +} dareaportal_t; + +typedef struct +{ + int numareaportals; + int firstareaportal; +} darea_t; diff --git a/code/bspc/q3files.h b/code/bspc/q3files.h index b251cc0..5ed267d 100755 --- a/code/bspc/q3files.h +++ b/code/bspc/q3files.h @@ -1,374 +1,374 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -#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 - - -/* -======================================================================== - -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 4 -#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 // 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 Q3_BSP_IDENT (('P'<<24)+('S'<<16)+('B'<<8)+'I') - // little-endian "IBSP" - -#define Q3_BSP_VERSION 46 - - -// there shouldn't be any problem with increasing these values at the -// expense of more memory allocation in the utilities -#define Q3_MAX_MAP_MODELS 0x400 -#define Q3_MAX_MAP_BRUSHES 0x8000 -#define Q3_MAX_MAP_ENTITIES 0x800 -#define Q3_MAX_MAP_ENTSTRING 0x10000 -#define Q3_MAX_MAP_SHADERS 0x400 - -#define Q3_MAX_MAP_AREAS 0x100 // MAX_MAP_AREA_BYTES in q_shared must match! -#define Q3_MAX_MAP_FOGS 0x100 -#define Q3_MAX_MAP_PLANES 0x10000 -#define Q3_MAX_MAP_NODES 0x10000 -#define Q3_MAX_MAP_BRUSHSIDES 0x10000 -#define Q3_MAX_MAP_LEAFS 0x10000 -#define Q3_MAX_MAP_LEAFFACES 0x10000 -#define Q3_MAX_MAP_LEAFBRUSHES 0x10000 -#define Q3_MAX_MAP_PORTALS 0x10000 -#define Q3_MAX_MAP_LIGHTING 0x400000 -#define Q3_MAX_MAP_LIGHTGRID 0x400000 -#define Q3_MAX_MAP_VISIBILITY 0x200000 - -#define Q3_MAX_MAP_DRAW_SURFS 0x20000 -#define Q3_MAX_MAP_DRAW_VERTS 0x80000 -#define Q3_MAX_MAP_DRAW_INDEXES 0x80000 - - -// key / value pair sizes in the entities lump -#define Q3_MAX_KEY 32 -#define Q3_MAX_VALUE 1024 - -// the editor uses these predefined yaw angles to orient entities up or down -#define ANGLE_UP -1 -#define ANGLE_DOWN -2 - -#define LIGHTMAP_WIDTH 128 -#define LIGHTMAP_HEIGHT 128 - - -//============================================================================= - - -typedef struct { - int fileofs, filelen; -} q3_lump_t; - -#define Q3_LUMP_ENTITIES 0 -#define Q3_LUMP_SHADERS 1 -#define Q3_LUMP_PLANES 2 -#define Q3_LUMP_NODES 3 -#define Q3_LUMP_LEAFS 4 -#define Q3_LUMP_LEAFSURFACES 5 -#define Q3_LUMP_LEAFBRUSHES 6 -#define Q3_LUMP_MODELS 7 -#define Q3_LUMP_BRUSHES 8 -#define Q3_LUMP_BRUSHSIDES 9 -#define Q3_LUMP_DRAWVERTS 10 -#define Q3_LUMP_DRAWINDEXES 11 -#define Q3_LUMP_FOGS 12 -#define Q3_LUMP_SURFACES 13 -#define Q3_LUMP_LIGHTMAPS 14 -#define Q3_LUMP_LIGHTGRID 15 -#define Q3_LUMP_VISIBILITY 16 -#define Q3_HEADER_LUMPS 17 - -typedef struct { - int ident; - int version; - - q3_lump_t lumps[Q3_HEADER_LUMPS]; -} q3_dheader_t; - -typedef struct { - float mins[3], maxs[3]; - int firstSurface, numSurfaces; - int firstBrush, numBrushes; -} q3_dmodel_t; - -typedef struct { - char shader[MAX_QPATH]; - int surfaceFlags; - int contentFlags; -} q3_dshader_t; - -// planes (x&~1) and (x&~1)+1 are allways opposites - -typedef struct { - float normal[3]; - float dist; -} q3_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]; -} q3_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; -} q3_dleaf_t; - -typedef struct { - int planeNum; // positive plane side faces out of the leaf - int shaderNum; -} q3_dbrushside_t; - -typedef struct { - int firstSide; - int numSides; - int shaderNum; // the shader that determines the contents flags -} q3_dbrush_t; - -typedef struct { - char shader[MAX_QPATH]; - int brushNum; - int visibleSide; // the brush side that ray tests need to clip against (-1 == none) -} q3_dfog_t; - -typedef struct { - vec3_t xyz; - float st[2]; - float lightmap[2]; - vec3_t normal; - byte color[4]; -} q3_drawVert_t; - -typedef enum { - MST_BAD, - MST_PLANAR, - MST_PATCH, - MST_TRIANGLE_SOUP, - MST_FLARE -} q3_mapSurfaceType_t; - -typedef struct { - int shaderNum; - int fogNum; - int surfaceType; - - int firstVert; - int numVerts; - - int firstIndex; - int numIndexes; - - int lightmapNum; - int lightmapX, lightmapY; - int lightmapWidth, lightmapHeight; - - vec3_t lightmapOrigin; - vec3_t lightmapVecs[3]; // for patches, [0] and [1] are lodbounds - - int patchWidth; - int patchHeight; -} q3_dsurface_t; - - -#endif +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +#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 + + +/* +======================================================================== + +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 4 +#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 // 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 Q3_BSP_IDENT (('P'<<24)+('S'<<16)+('B'<<8)+'I') + // little-endian "IBSP" + +#define Q3_BSP_VERSION 46 + + +// there shouldn't be any problem with increasing these values at the +// expense of more memory allocation in the utilities +#define Q3_MAX_MAP_MODELS 0x400 +#define Q3_MAX_MAP_BRUSHES 0x8000 +#define Q3_MAX_MAP_ENTITIES 0x800 +#define Q3_MAX_MAP_ENTSTRING 0x10000 +#define Q3_MAX_MAP_SHADERS 0x400 + +#define Q3_MAX_MAP_AREAS 0x100 // MAX_MAP_AREA_BYTES in q_shared must match! +#define Q3_MAX_MAP_FOGS 0x100 +#define Q3_MAX_MAP_PLANES 0x10000 +#define Q3_MAX_MAP_NODES 0x10000 +#define Q3_MAX_MAP_BRUSHSIDES 0x10000 +#define Q3_MAX_MAP_LEAFS 0x10000 +#define Q3_MAX_MAP_LEAFFACES 0x10000 +#define Q3_MAX_MAP_LEAFBRUSHES 0x10000 +#define Q3_MAX_MAP_PORTALS 0x10000 +#define Q3_MAX_MAP_LIGHTING 0x400000 +#define Q3_MAX_MAP_LIGHTGRID 0x400000 +#define Q3_MAX_MAP_VISIBILITY 0x200000 + +#define Q3_MAX_MAP_DRAW_SURFS 0x20000 +#define Q3_MAX_MAP_DRAW_VERTS 0x80000 +#define Q3_MAX_MAP_DRAW_INDEXES 0x80000 + + +// key / value pair sizes in the entities lump +#define Q3_MAX_KEY 32 +#define Q3_MAX_VALUE 1024 + +// the editor uses these predefined yaw angles to orient entities up or down +#define ANGLE_UP -1 +#define ANGLE_DOWN -2 + +#define LIGHTMAP_WIDTH 128 +#define LIGHTMAP_HEIGHT 128 + + +//============================================================================= + + +typedef struct { + int fileofs, filelen; +} q3_lump_t; + +#define Q3_LUMP_ENTITIES 0 +#define Q3_LUMP_SHADERS 1 +#define Q3_LUMP_PLANES 2 +#define Q3_LUMP_NODES 3 +#define Q3_LUMP_LEAFS 4 +#define Q3_LUMP_LEAFSURFACES 5 +#define Q3_LUMP_LEAFBRUSHES 6 +#define Q3_LUMP_MODELS 7 +#define Q3_LUMP_BRUSHES 8 +#define Q3_LUMP_BRUSHSIDES 9 +#define Q3_LUMP_DRAWVERTS 10 +#define Q3_LUMP_DRAWINDEXES 11 +#define Q3_LUMP_FOGS 12 +#define Q3_LUMP_SURFACES 13 +#define Q3_LUMP_LIGHTMAPS 14 +#define Q3_LUMP_LIGHTGRID 15 +#define Q3_LUMP_VISIBILITY 16 +#define Q3_HEADER_LUMPS 17 + +typedef struct { + int ident; + int version; + + q3_lump_t lumps[Q3_HEADER_LUMPS]; +} q3_dheader_t; + +typedef struct { + float mins[3], maxs[3]; + int firstSurface, numSurfaces; + int firstBrush, numBrushes; +} q3_dmodel_t; + +typedef struct { + char shader[MAX_QPATH]; + int surfaceFlags; + int contentFlags; +} q3_dshader_t; + +// planes (x&~1) and (x&~1)+1 are allways opposites + +typedef struct { + float normal[3]; + float dist; +} q3_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]; +} q3_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; +} q3_dleaf_t; + +typedef struct { + int planeNum; // positive plane side faces out of the leaf + int shaderNum; +} q3_dbrushside_t; + +typedef struct { + int firstSide; + int numSides; + int shaderNum; // the shader that determines the contents flags +} q3_dbrush_t; + +typedef struct { + char shader[MAX_QPATH]; + int brushNum; + int visibleSide; // the brush side that ray tests need to clip against (-1 == none) +} q3_dfog_t; + +typedef struct { + vec3_t xyz; + float st[2]; + float lightmap[2]; + vec3_t normal; + byte color[4]; +} q3_drawVert_t; + +typedef enum { + MST_BAD, + MST_PLANAR, + MST_PATCH, + MST_TRIANGLE_SOUP, + MST_FLARE +} q3_mapSurfaceType_t; + +typedef struct { + int shaderNum; + int fogNum; + int surfaceType; + + int firstVert; + int numVerts; + + int firstIndex; + int numIndexes; + + int lightmapNum; + int lightmapX, lightmapY; + int lightmapWidth, lightmapHeight; + + vec3_t lightmapOrigin; + vec3_t lightmapVecs[3]; // for patches, [0] and [1] are lodbounds + + int patchWidth; + int patchHeight; +} q3_dsurface_t; + + +#endif diff --git a/code/bspc/qbsp.h b/code/bspc/qbsp.h index 7ebf9d4..dd57200 100755 --- a/code/bspc/qbsp.h +++ b/code/bspc/qbsp.h @@ -1,477 +1,477 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - - -#if defined(WIN32) || defined(_WIN32) -#include -#endif -#include -#include "l_cmd.h" -#include "l_math.h" -#include "l_poly.h" -#include "l_threads.h" -#include "../botlib/l_script.h" -#include "l_bsp_ent.h" -#include "q2files.h" -#include "l_mem.h" -#include "l_utils.h" -#include "l_log.h" -#include "l_qfiles.h" - -#define BSPC_VERSION "2.1h" - -#define ME -#define DEBUG -#define NODELIST -#define SIN - -#define MAX_BRUSH_SIDES 128 //maximum number of sides per brush -#define CLIP_EPSILON 0.1 -#define MAX_MAP_BOUNDS 65535 -#define BOGUS_RANGE (MAX_MAP_BOUNDS+128) //somewhere outside the map -#define TEXINFO_NODE -1 //side is allready on a node -#define PLANENUM_LEAF -1 //used for leaf nodes -#define MAXEDGES 20 //maximum number of face edges -#define MAX_NODE_BRUSHES 8 //maximum brushes in a node -//side flags -#define SFL_TESTED 1 -#define SFL_VISIBLE 2 -#define SFL_BEVEL 4 -#define SFL_TEXTURED 8 -#define SFL_CURVE 16 - -//map plane -typedef struct plane_s -{ - vec3_t normal; - vec_t dist; - int type; - int signbits; - struct plane_s *hash_chain; -} plane_t; -//brush texture -typedef struct -{ - vec_t shift[2]; - vec_t rotate; - vec_t scale[2]; - char name[32]; - int flags; - int value; -} brush_texture_t; -//brush side -typedef struct side_s -{ - int planenum; // map plane this side is in - int texinfo; // texture reference - winding_t *winding; // winding of this side - struct side_s *original; // bspbrush_t sides will reference the mapbrush_t sides - int lightinfo; // for SIN only - int contents; // from miptex - int surf; // from miptex - unsigned short flags; // side flags -} side_t; //sizeof(side_t) = 36 -//map brush -typedef struct mapbrush_s -{ - int entitynum; - int brushnum; - - int contents; -#ifdef ME - int expansionbbox; //bbox used for expansion of the brush - int leafnum; - int modelnum; -#endif - - vec3_t mins, maxs; - - int numsides; - side_t *original_sides; -} mapbrush_t; -//bsp face -typedef struct face_s -{ - struct face_s *next; // on node - - // the chain of faces off of a node can be merged or split, - // but each face_t along the way will remain in the chain - // until the entire tree is freed - struct face_s *merged; // if set, this face isn't valid anymore - struct face_s *split[2]; // if set, this face isn't valid anymore - - struct portal_s *portal; - int texinfo; -#ifdef SIN - int lightinfo; -#endif - int planenum; - int contents; // faces in different contents can't merge - int outputnumber; - winding_t *w; - int numpoints; - qboolean badstartvert; // tjunctions cannot be fixed without a midpoint vertex - int vertexnums[MAXEDGES]; -} face_t; -//bsp brush -typedef struct bspbrush_s -{ - struct bspbrush_s *next; - vec3_t mins, maxs; - int side, testside; // side of node during construction - mapbrush_t *original; - int numsides; - side_t sides[6]; // variably sized -} bspbrush_t; //sizeof(bspbrush_t) = 44 + numsides * sizeof(side_t) -//bsp node -typedef struct node_s -{ - //both leafs and nodes - int planenum; // -1 = leaf node - struct node_s *parent; - vec3_t mins, maxs; // valid after portalization - bspbrush_t *volume; // one for each leaf/node - - // nodes only - qboolean detail_seperator; // a detail brush caused the split - side_t *side; // the side that created the node - struct node_s *children[2]; - face_t *faces; - - // leafs only - bspbrush_t *brushlist; // fragments of all brushes in this leaf - int contents; // OR of all brush contents - int occupied; // 1 or greater can reach entity - entity_t *occupant; // for leak file testing - int cluster; // for portalfile writing - int area; // for areaportals - struct portal_s *portals; // also on nodes during construction -#ifdef NODELIST - struct node_s *next; //next node in the nodelist -#endif -#ifdef ME - int expansionbboxes; //OR of all bboxes used for expansion of the brushes - int modelnum; -#endif -} node_t; //sizeof(node_t) = 80 bytes -//bsp portal -typedef struct portal_s -{ - plane_t plane; - node_t *onnode; // NULL = outside box - node_t *nodes[2]; // [0] = front side of plane - struct portal_s *next[2]; - winding_t *winding; - - qboolean sidefound; // false if ->side hasn't been checked - side_t *side; // NULL = non-visible - face_t *face[2]; // output face in bsp file -#ifdef ME - struct tmp_face_s *tmpface; //pointer to the tmpface created for this portal - int planenum; //number of the map plane used by the portal -#endif -} portal_t; -//bsp tree -typedef struct -{ - node_t *headnode; - node_t outside_node; - vec3_t mins, maxs; -} tree_t; - -//============================================================================= -// bspc.c -//============================================================================= - -extern qboolean noprune; -extern qboolean nodetail; -extern qboolean fulldetail; -extern qboolean nomerge; -extern qboolean nosubdiv; -extern qboolean nowater; -extern qboolean noweld; -extern qboolean noshare; -extern qboolean notjunc; -extern qboolean onlyents; -#ifdef ME -extern qboolean nocsg; -extern qboolean create_aas; -extern qboolean freetree; -extern qboolean lessbrushes; -extern qboolean nobrushmerge; -extern qboolean cancelconversion; -extern qboolean noliquids; -extern qboolean capsule_collision; -#endif //ME - -extern float subdivide_size; -extern vec_t microvolume; - -extern char outbase[32]; -extern char source[1024]; - -//============================================================================= -// map.c -//============================================================================= - -#define MAX_MAPFILE_PLANES 256000 -#define MAX_MAPFILE_BRUSHES 65535 -#define MAX_MAPFILE_BRUSHSIDES (MAX_MAPFILE_BRUSHES*8) -#define MAX_MAPFILE_TEXINFO 8192 - -extern int entity_num; - -extern plane_t mapplanes[MAX_MAPFILE_PLANES]; -extern int nummapplanes; -extern int mapplaneusers[MAX_MAPFILE_PLANES]; - -extern int nummapbrushes; -extern mapbrush_t mapbrushes[MAX_MAPFILE_BRUSHES]; - -extern vec3_t map_mins, map_maxs; - -extern int nummapbrushsides; -extern side_t brushsides[MAX_MAPFILE_BRUSHSIDES]; -extern brush_texture_t side_brushtextures[MAX_MAPFILE_BRUSHSIDES]; - -#ifdef ME - -typedef struct -{ - float vecs[2][4]; // [s/t][xyz offset] - int flags; // miptex flags + overrides - int value; - char texture[64]; // texture name (textures/*.wal) - int nexttexinfo; // for animations, -1 = end of chain -} map_texinfo_t; - -extern map_texinfo_t map_texinfo[MAX_MAPFILE_TEXINFO]; -extern int map_numtexinfo; -#define NODESTACKSIZE 1024 - -#define MAPTYPE_QUAKE1 1 -#define MAPTYPE_QUAKE2 2 -#define MAPTYPE_QUAKE3 3 -#define MAPTYPE_HALFLIFE 4 -#define MAPTYPE_SIN 5 - -extern int nodestack[NODESTACKSIZE]; -extern int *nodestackptr; -extern int nodestacksize; -extern int brushmodelnumbers[MAX_MAPFILE_BRUSHES]; -extern int dbrushleafnums[MAX_MAPFILE_BRUSHES]; -extern int dplanes2mapplanes[MAX_MAPFILE_PLANES]; - -extern int loadedmaptype; -#endif //ME - -extern int c_boxbevels; -extern int c_edgebevels; -extern int c_areaportals; -extern int c_clipbrushes; -extern int c_squattbrushes; - -//finds a float plane for the given normal and distance -int FindFloatPlane(vec3_t normal, vec_t dist); -//returns the plane type for the given normal -int PlaneTypeForNormal(vec3_t normal); -//returns the plane defined by the three given points -int PlaneFromPoints(int *p0, int *p1, int *p2); -//add bevels to the map brush -void AddBrushBevels(mapbrush_t *b); -//makes brush side windings for the brush -qboolean MakeBrushWindings(mapbrush_t *ob); -//marks brush bevels of the brush as bevel -void MarkBrushBevels(mapbrush_t *brush); -//returns true if the map brush already exists -int BrushExists(mapbrush_t *brush); -//loads a map from a bsp file -int LoadMapFromBSP(struct quakefile_s *qf); -//resets map loading -void ResetMapLoading(void); -//print some map info -void PrintMapInfo(void); -//writes a map file (type depending on loaded map type) -void WriteMapFile(char *filename); - -//============================================================================= -// map_q2.c -//============================================================================= - -void Q2_ResetMapLoading(void); -//loads a Quake2 map file -void Q2_LoadMapFile(char *filename); -//loads a map from a Quake2 bsp file -void Q2_LoadMapFromBSP(char *filename, int offset, int length); - -//============================================================================= -// map_q1.c -//============================================================================= - -void Q1_ResetMapLoading(void); -//loads a Quake2 map file -void Q1_LoadMapFile(char *filename); -//loads a map from a Quake1 bsp file -void Q1_LoadMapFromBSP(char *filename, int offset, int length); - -//============================================================================= -// map_q3.c -//============================================================================= -void Q3_ResetMapLoading(void); -//loads a map from a Quake3 bsp file -void Q3_LoadMapFromBSP(struct quakefile_s *qf); - -//============================================================================= -// map_sin.c -//============================================================================= - -void Sin_ResetMapLoading(void); -//loads a Sin map file -void Sin_LoadMapFile(char *filename); -//loads a map from a Sin bsp file -void Sin_LoadMapFromBSP(char *filename, int offset, int length); - -//============================================================================= -// map_hl.c -//============================================================================= - -void HL_ResetMapLoading(void); -//loads a Half-Life map file -void HL_LoadMapFile(char *filename); -//loads a map from a Half-Life bsp file -void HL_LoadMapFromBSP(char *filename, int offset, int length); - -//============================================================================= -// textures.c -//============================================================================= - -typedef struct -{ - char name[64]; - int flags; - int value; - int contents; - char animname[64]; -} textureref_t; - -#define MAX_MAP_TEXTURES 1024 - -extern textureref_t textureref[MAX_MAP_TEXTURES]; - -int FindMiptex(char *name); -int TexinfoForBrushTexture(plane_t *plane, brush_texture_t *bt, vec3_t origin); -void TextureAxisFromPlane(plane_t *pln, vec3_t xv, vec3_t yv); - -//============================================================================= -// csg -//============================================================================= - -bspbrush_t *MakeBspBrushList(int startbrush, int endbrush, vec3_t clipmins, vec3_t clipmaxs); -bspbrush_t *ChopBrushes(bspbrush_t *head); -bspbrush_t *InitialBrushList(bspbrush_t *list); -bspbrush_t *OptimizedBrushList(bspbrush_t *list); -void WriteBrushMap(char *name, bspbrush_t *list); -void CheckBSPBrush(bspbrush_t *brush); -void BSPBrushWindings(bspbrush_t *brush); -bspbrush_t *TryMergeBrushes(bspbrush_t *brush1, bspbrush_t *brush2); -tree_t *ProcessWorldBrushes(int brush_start, int brush_end); - -//============================================================================= -// brushbsp -//============================================================================= - -#define PSIDE_FRONT 1 -#define PSIDE_BACK 2 -#define PSIDE_BOTH (PSIDE_FRONT|PSIDE_BACK) -#define PSIDE_FACING 4 - -void WriteBrushList(char *name, bspbrush_t *brush, qboolean onlyvis); -bspbrush_t *CopyBrush(bspbrush_t *brush); -void SplitBrush(bspbrush_t *brush, int planenum, bspbrush_t **front, bspbrush_t **back); -node_t *AllocNode(void); -bspbrush_t *AllocBrush(int numsides); -int CountBrushList(bspbrush_t *brushes); -void FreeBrush(bspbrush_t *brushes); -vec_t BrushVolume(bspbrush_t *brush); -void BoundBrush(bspbrush_t *brush); -void FreeBrushList(bspbrush_t *brushes); -tree_t *BrushBSP(bspbrush_t *brushlist, vec3_t mins, vec3_t maxs); -bspbrush_t *BrushFromBounds(vec3_t mins, vec3_t maxs); -int BrushMostlyOnSide(bspbrush_t *brush, plane_t *plane); -qboolean WindingIsHuge(winding_t *w); -qboolean WindingIsTiny(winding_t *w); -void ResetBrushBSP(void); - -//============================================================================= -// portals.c -//============================================================================= - -int VisibleContents (int contents); -void MakeHeadnodePortals (tree_t *tree); -void MakeNodePortal (node_t *node); -void SplitNodePortals (node_t *node); -qboolean Portal_VisFlood (portal_t *p); -qboolean FloodEntities (tree_t *tree); -void FillOutside (node_t *headnode); -void FloodAreas (tree_t *tree); -void MarkVisibleSides (tree_t *tree, int start, int end); -void FreePortal (portal_t *p); -void EmitAreaPortals (node_t *headnode); -void MakeTreePortals (tree_t *tree); - -//============================================================================= -// glfile.c -//============================================================================= - -void OutputWinding(winding_t *w, FILE *glview); -void WriteGLView(tree_t *tree, char *source); - -//============================================================================= -// gldraw.c -//============================================================================= - -extern vec3_t draw_mins, draw_maxs; -extern qboolean drawflag; - -void Draw_ClearWindow (void); -void DrawWinding (winding_t *w); -void GLS_BeginScene (void); -void GLS_Winding (winding_t *w, int code); -void GLS_EndScene (void); - -//============================================================================= -// leakfile.c -//============================================================================= - -void LeakFile (tree_t *tree); - -//============================================================================= -// tree.c -//============================================================================= - -tree_t *Tree_Alloc(void); -void Tree_Free(tree_t *tree); -void Tree_Free_r(node_t *node); -void Tree_Print_r(node_t *node, int depth); -void Tree_FreePortals_r(node_t *node); -void Tree_PruneNodes_r(node_t *node); -void Tree_PruneNodes(node_t *node); +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + + +#if defined(WIN32) || defined(_WIN32) +#include +#endif +#include +#include "l_cmd.h" +#include "l_math.h" +#include "l_poly.h" +#include "l_threads.h" +#include "../botlib/l_script.h" +#include "l_bsp_ent.h" +#include "q2files.h" +#include "l_mem.h" +#include "l_utils.h" +#include "l_log.h" +#include "l_qfiles.h" + +#define BSPC_VERSION "2.1h" + +#define ME +#define DEBUG +#define NODELIST +#define SIN + +#define MAX_BRUSH_SIDES 128 //maximum number of sides per brush +#define CLIP_EPSILON 0.1 +#define MAX_MAP_BOUNDS 65535 +#define BOGUS_RANGE (MAX_MAP_BOUNDS+128) //somewhere outside the map +#define TEXINFO_NODE -1 //side is allready on a node +#define PLANENUM_LEAF -1 //used for leaf nodes +#define MAXEDGES 20 //maximum number of face edges +#define MAX_NODE_BRUSHES 8 //maximum brushes in a node +//side flags +#define SFL_TESTED 1 +#define SFL_VISIBLE 2 +#define SFL_BEVEL 4 +#define SFL_TEXTURED 8 +#define SFL_CURVE 16 + +//map plane +typedef struct plane_s +{ + vec3_t normal; + vec_t dist; + int type; + int signbits; + struct plane_s *hash_chain; +} plane_t; +//brush texture +typedef struct +{ + vec_t shift[2]; + vec_t rotate; + vec_t scale[2]; + char name[32]; + int flags; + int value; +} brush_texture_t; +//brush side +typedef struct side_s +{ + int planenum; // map plane this side is in + int texinfo; // texture reference + winding_t *winding; // winding of this side + struct side_s *original; // bspbrush_t sides will reference the mapbrush_t sides + int lightinfo; // for SIN only + int contents; // from miptex + int surf; // from miptex + unsigned short flags; // side flags +} side_t; //sizeof(side_t) = 36 +//map brush +typedef struct mapbrush_s +{ + int entitynum; + int brushnum; + + int contents; +#ifdef ME + int expansionbbox; //bbox used for expansion of the brush + int leafnum; + int modelnum; +#endif + + vec3_t mins, maxs; + + int numsides; + side_t *original_sides; +} mapbrush_t; +//bsp face +typedef struct face_s +{ + struct face_s *next; // on node + + // the chain of faces off of a node can be merged or split, + // but each face_t along the way will remain in the chain + // until the entire tree is freed + struct face_s *merged; // if set, this face isn't valid anymore + struct face_s *split[2]; // if set, this face isn't valid anymore + + struct portal_s *portal; + int texinfo; +#ifdef SIN + int lightinfo; +#endif + int planenum; + int contents; // faces in different contents can't merge + int outputnumber; + winding_t *w; + int numpoints; + qboolean badstartvert; // tjunctions cannot be fixed without a midpoint vertex + int vertexnums[MAXEDGES]; +} face_t; +//bsp brush +typedef struct bspbrush_s +{ + struct bspbrush_s *next; + vec3_t mins, maxs; + int side, testside; // side of node during construction + mapbrush_t *original; + int numsides; + side_t sides[6]; // variably sized +} bspbrush_t; //sizeof(bspbrush_t) = 44 + numsides * sizeof(side_t) +//bsp node +typedef struct node_s +{ + //both leafs and nodes + int planenum; // -1 = leaf node + struct node_s *parent; + vec3_t mins, maxs; // valid after portalization + bspbrush_t *volume; // one for each leaf/node + + // nodes only + qboolean detail_seperator; // a detail brush caused the split + side_t *side; // the side that created the node + struct node_s *children[2]; + face_t *faces; + + // leafs only + bspbrush_t *brushlist; // fragments of all brushes in this leaf + int contents; // OR of all brush contents + int occupied; // 1 or greater can reach entity + entity_t *occupant; // for leak file testing + int cluster; // for portalfile writing + int area; // for areaportals + struct portal_s *portals; // also on nodes during construction +#ifdef NODELIST + struct node_s *next; //next node in the nodelist +#endif +#ifdef ME + int expansionbboxes; //OR of all bboxes used for expansion of the brushes + int modelnum; +#endif +} node_t; //sizeof(node_t) = 80 bytes +//bsp portal +typedef struct portal_s +{ + plane_t plane; + node_t *onnode; // NULL = outside box + node_t *nodes[2]; // [0] = front side of plane + struct portal_s *next[2]; + winding_t *winding; + + qboolean sidefound; // false if ->side hasn't been checked + side_t *side; // NULL = non-visible + face_t *face[2]; // output face in bsp file +#ifdef ME + struct tmp_face_s *tmpface; //pointer to the tmpface created for this portal + int planenum; //number of the map plane used by the portal +#endif +} portal_t; +//bsp tree +typedef struct +{ + node_t *headnode; + node_t outside_node; + vec3_t mins, maxs; +} tree_t; + +//============================================================================= +// bspc.c +//============================================================================= + +extern qboolean noprune; +extern qboolean nodetail; +extern qboolean fulldetail; +extern qboolean nomerge; +extern qboolean nosubdiv; +extern qboolean nowater; +extern qboolean noweld; +extern qboolean noshare; +extern qboolean notjunc; +extern qboolean onlyents; +#ifdef ME +extern qboolean nocsg; +extern qboolean create_aas; +extern qboolean freetree; +extern qboolean lessbrushes; +extern qboolean nobrushmerge; +extern qboolean cancelconversion; +extern qboolean noliquids; +extern qboolean capsule_collision; +#endif //ME + +extern float subdivide_size; +extern vec_t microvolume; + +extern char outbase[32]; +extern char source[1024]; + +//============================================================================= +// map.c +//============================================================================= + +#define MAX_MAPFILE_PLANES 256000 +#define MAX_MAPFILE_BRUSHES 65535 +#define MAX_MAPFILE_BRUSHSIDES (MAX_MAPFILE_BRUSHES*8) +#define MAX_MAPFILE_TEXINFO 8192 + +extern int entity_num; + +extern plane_t mapplanes[MAX_MAPFILE_PLANES]; +extern int nummapplanes; +extern int mapplaneusers[MAX_MAPFILE_PLANES]; + +extern int nummapbrushes; +extern mapbrush_t mapbrushes[MAX_MAPFILE_BRUSHES]; + +extern vec3_t map_mins, map_maxs; + +extern int nummapbrushsides; +extern side_t brushsides[MAX_MAPFILE_BRUSHSIDES]; +extern brush_texture_t side_brushtextures[MAX_MAPFILE_BRUSHSIDES]; + +#ifdef ME + +typedef struct +{ + float vecs[2][4]; // [s/t][xyz offset] + int flags; // miptex flags + overrides + int value; + char texture[64]; // texture name (textures/*.wal) + int nexttexinfo; // for animations, -1 = end of chain +} map_texinfo_t; + +extern map_texinfo_t map_texinfo[MAX_MAPFILE_TEXINFO]; +extern int map_numtexinfo; +#define NODESTACKSIZE 1024 + +#define MAPTYPE_QUAKE1 1 +#define MAPTYPE_QUAKE2 2 +#define MAPTYPE_QUAKE3 3 +#define MAPTYPE_HALFLIFE 4 +#define MAPTYPE_SIN 5 + +extern int nodestack[NODESTACKSIZE]; +extern int *nodestackptr; +extern int nodestacksize; +extern int brushmodelnumbers[MAX_MAPFILE_BRUSHES]; +extern int dbrushleafnums[MAX_MAPFILE_BRUSHES]; +extern int dplanes2mapplanes[MAX_MAPFILE_PLANES]; + +extern int loadedmaptype; +#endif //ME + +extern int c_boxbevels; +extern int c_edgebevels; +extern int c_areaportals; +extern int c_clipbrushes; +extern int c_squattbrushes; + +//finds a float plane for the given normal and distance +int FindFloatPlane(vec3_t normal, vec_t dist); +//returns the plane type for the given normal +int PlaneTypeForNormal(vec3_t normal); +//returns the plane defined by the three given points +int PlaneFromPoints(int *p0, int *p1, int *p2); +//add bevels to the map brush +void AddBrushBevels(mapbrush_t *b); +//makes brush side windings for the brush +qboolean MakeBrushWindings(mapbrush_t *ob); +//marks brush bevels of the brush as bevel +void MarkBrushBevels(mapbrush_t *brush); +//returns true if the map brush already exists +int BrushExists(mapbrush_t *brush); +//loads a map from a bsp file +int LoadMapFromBSP(struct quakefile_s *qf); +//resets map loading +void ResetMapLoading(void); +//print some map info +void PrintMapInfo(void); +//writes a map file (type depending on loaded map type) +void WriteMapFile(char *filename); + +//============================================================================= +// map_q2.c +//============================================================================= + +void Q2_ResetMapLoading(void); +//loads a Quake2 map file +void Q2_LoadMapFile(char *filename); +//loads a map from a Quake2 bsp file +void Q2_LoadMapFromBSP(char *filename, int offset, int length); + +//============================================================================= +// map_q1.c +//============================================================================= + +void Q1_ResetMapLoading(void); +//loads a Quake2 map file +void Q1_LoadMapFile(char *filename); +//loads a map from a Quake1 bsp file +void Q1_LoadMapFromBSP(char *filename, int offset, int length); + +//============================================================================= +// map_q3.c +//============================================================================= +void Q3_ResetMapLoading(void); +//loads a map from a Quake3 bsp file +void Q3_LoadMapFromBSP(struct quakefile_s *qf); + +//============================================================================= +// map_sin.c +//============================================================================= + +void Sin_ResetMapLoading(void); +//loads a Sin map file +void Sin_LoadMapFile(char *filename); +//loads a map from a Sin bsp file +void Sin_LoadMapFromBSP(char *filename, int offset, int length); + +//============================================================================= +// map_hl.c +//============================================================================= + +void HL_ResetMapLoading(void); +//loads a Half-Life map file +void HL_LoadMapFile(char *filename); +//loads a map from a Half-Life bsp file +void HL_LoadMapFromBSP(char *filename, int offset, int length); + +//============================================================================= +// textures.c +//============================================================================= + +typedef struct +{ + char name[64]; + int flags; + int value; + int contents; + char animname[64]; +} textureref_t; + +#define MAX_MAP_TEXTURES 1024 + +extern textureref_t textureref[MAX_MAP_TEXTURES]; + +int FindMiptex(char *name); +int TexinfoForBrushTexture(plane_t *plane, brush_texture_t *bt, vec3_t origin); +void TextureAxisFromPlane(plane_t *pln, vec3_t xv, vec3_t yv); + +//============================================================================= +// csg +//============================================================================= + +bspbrush_t *MakeBspBrushList(int startbrush, int endbrush, vec3_t clipmins, vec3_t clipmaxs); +bspbrush_t *ChopBrushes(bspbrush_t *head); +bspbrush_t *InitialBrushList(bspbrush_t *list); +bspbrush_t *OptimizedBrushList(bspbrush_t *list); +void WriteBrushMap(char *name, bspbrush_t *list); +void CheckBSPBrush(bspbrush_t *brush); +void BSPBrushWindings(bspbrush_t *brush); +bspbrush_t *TryMergeBrushes(bspbrush_t *brush1, bspbrush_t *brush2); +tree_t *ProcessWorldBrushes(int brush_start, int brush_end); + +//============================================================================= +// brushbsp +//============================================================================= + +#define PSIDE_FRONT 1 +#define PSIDE_BACK 2 +#define PSIDE_BOTH (PSIDE_FRONT|PSIDE_BACK) +#define PSIDE_FACING 4 + +void WriteBrushList(char *name, bspbrush_t *brush, qboolean onlyvis); +bspbrush_t *CopyBrush(bspbrush_t *brush); +void SplitBrush(bspbrush_t *brush, int planenum, bspbrush_t **front, bspbrush_t **back); +node_t *AllocNode(void); +bspbrush_t *AllocBrush(int numsides); +int CountBrushList(bspbrush_t *brushes); +void FreeBrush(bspbrush_t *brushes); +vec_t BrushVolume(bspbrush_t *brush); +void BoundBrush(bspbrush_t *brush); +void FreeBrushList(bspbrush_t *brushes); +tree_t *BrushBSP(bspbrush_t *brushlist, vec3_t mins, vec3_t maxs); +bspbrush_t *BrushFromBounds(vec3_t mins, vec3_t maxs); +int BrushMostlyOnSide(bspbrush_t *brush, plane_t *plane); +qboolean WindingIsHuge(winding_t *w); +qboolean WindingIsTiny(winding_t *w); +void ResetBrushBSP(void); + +//============================================================================= +// portals.c +//============================================================================= + +int VisibleContents (int contents); +void MakeHeadnodePortals (tree_t *tree); +void MakeNodePortal (node_t *node); +void SplitNodePortals (node_t *node); +qboolean Portal_VisFlood (portal_t *p); +qboolean FloodEntities (tree_t *tree); +void FillOutside (node_t *headnode); +void FloodAreas (tree_t *tree); +void MarkVisibleSides (tree_t *tree, int start, int end); +void FreePortal (portal_t *p); +void EmitAreaPortals (node_t *headnode); +void MakeTreePortals (tree_t *tree); + +//============================================================================= +// glfile.c +//============================================================================= + +void OutputWinding(winding_t *w, FILE *glview); +void WriteGLView(tree_t *tree, char *source); + +//============================================================================= +// gldraw.c +//============================================================================= + +extern vec3_t draw_mins, draw_maxs; +extern qboolean drawflag; + +void Draw_ClearWindow (void); +void DrawWinding (winding_t *w); +void GLS_BeginScene (void); +void GLS_Winding (winding_t *w, int code); +void GLS_EndScene (void); + +//============================================================================= +// leakfile.c +//============================================================================= + +void LeakFile (tree_t *tree); + +//============================================================================= +// tree.c +//============================================================================= + +tree_t *Tree_Alloc(void); +void Tree_Free(tree_t *tree); +void Tree_Free_r(node_t *node); +void Tree_Print_r(node_t *node, int depth); +void Tree_FreePortals_r(node_t *node); +void Tree_PruneNodes_r(node_t *node); +void Tree_PruneNodes(node_t *node); diff --git a/code/bspc/qfiles.h b/code/bspc/qfiles.h index 5ffea6e..317e47c 100755 --- a/code/bspc/qfiles.h +++ b/code/bspc/qfiles.h @@ -1,487 +1,487 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -// -// qfiles.h: quake file formats -// This file must be identical in the quake and utils directories -// - -/* -======================================================================== - -The .pak files are just a linear collapse of a directory tree - -======================================================================== -*/ - -#define IDPAKHEADER (('K'<<24)+('C'<<16)+('A'<<8)+'P') - -typedef struct -{ - char name[56]; - int filepos, filelen; -} dpackfile_t; - -typedef struct -{ - int ident; // == IDPAKHEADER - int dirofs; - int dirlen; -} dpackheader_t; - -#define MAX_FILES_IN_PACK 4096 - - -/* -======================================================================== - -PCX files are used for as many images as possible - -======================================================================== -*/ - -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; - - -/* -======================================================================== - -.MD2 triangle model file format - -======================================================================== -*/ - -#define IDALIASHEADER (('2'<<24)+('P'<<16)+('D'<<8)+'I') -#define ALIAS_VERSION 8 - -#define MAX_TRIANGLES 4096 -#define MAX_VERTS 2048 -#define MAX_FRAMES 512 -#define MAX_MD2SKINS 32 -#define MAX_SKINNAME 64 - -typedef struct -{ - short s; - short t; -} dstvert_t; - -typedef struct -{ - short index_xyz[3]; - short index_st[3]; -} dtriangle_t; - -typedef struct -{ - byte v[3]; // scaled byte to fit in frame mins/maxs - byte lightnormalindex; -} dtrivertx_t; - -#define DTRIVERTX_V0 0 -#define DTRIVERTX_V1 1 -#define DTRIVERTX_V2 2 -#define DTRIVERTX_LNI 3 -#define DTRIVERTX_SIZE 4 - -typedef struct -{ - float scale[3]; // multiply byte verts by this - float translate[3]; // then add this - char name[16]; // frame name from grabbing - dtrivertx_t verts[1]; // variable sized -} daliasframe_t; - - -// the glcmd format: -// a positive integer starts a tristrip command, followed by that many -// vertex structures. -// a negative integer starts a trifan command, followed by -x vertexes -// a zero indicates the end of the command list. -// a vertex consists of a floating point s, a floating point t, -// and an integer vertex index. - - -typedef struct -{ - int ident; - int version; - - int skinwidth; - int skinheight; - int framesize; // byte size of each frame - - int num_skins; - int num_xyz; - int num_st; // greater than num_xyz for seams - int num_tris; - int num_glcmds; // dwords in strip/fan command list - int num_frames; - - int ofs_skins; // each skin is a MAX_SKINNAME string - int ofs_st; // byte offset from start for stverts - int ofs_tris; // offset for dtriangles - int ofs_frames; // offset for first frame - int ofs_glcmds; - int ofs_end; // end of file - -} dmdl_t; - -/* -======================================================================== - -.SP2 sprite file format - -======================================================================== -*/ - -#define IDSPRITEHEADER (('2'<<24)+('S'<<16)+('D'<<8)+'I') - // little-endian "IDS2" -#define SPRITE_VERSION 2 - -typedef struct -{ - int width, height; - int origin_x, origin_y; // raster coordinates inside pic - char name[MAX_SKINNAME]; // name of pcx file -} dsprframe_t; - -typedef struct { - int ident; - int version; - int numframes; - dsprframe_t frames[1]; // variable sized -} dsprite_t; - -/* -============================================================================== - - .WAL texture file format - -============================================================================== -*/ - - -#define MIPLEVELS 4 -typedef struct miptex_s -{ - char name[32]; - unsigned width, height; - unsigned offsets[MIPLEVELS]; // four mip maps stored - char animname[32]; // next frame in animation chain - int flags; - int contents; - int value; -} miptex_t; - - - -/* -============================================================================== - - .BSP file format - -============================================================================== -*/ - -#define IDBSPHEADER (('P'<<24)+('S'<<16)+('B'<<8)+'I') - // little-endian "IBSP" - -#define BSPVERSION 38 - - -// upper design bounds -// leaffaces, leafbrushes, planes, and verts are still bounded by -// 16 bit short limits -#define MAX_MAP_MODELS 1024 -#define MAX_MAP_BRUSHES 8192 -#define MAX_MAP_ENTITIES 2048 -#define MAX_MAP_ENTSTRING 0x40000 -#define MAX_MAP_TEXINFO 8192 - -#define MAX_MAP_AREAS 256 -#define MAX_MAP_AREAPORTALS 1024 -#define MAX_MAP_PLANES 65536 -#define MAX_MAP_NODES 65536 -#define MAX_MAP_BRUSHSIDES 65536 -#define MAX_MAP_LEAFS 65536 -#define MAX_MAP_VERTS 65536 -#define MAX_MAP_FACES 65536 -#define MAX_MAP_LEAFFACES 65536 -#define MAX_MAP_LEAFBRUSHES 65536 -#define MAX_MAP_PORTALS 65536 -#define MAX_MAP_EDGES 128000 -#define MAX_MAP_SURFEDGES 256000 -#define MAX_MAP_LIGHTING 0x320000 -#define MAX_MAP_VISIBILITY 0x280000 - -// key / value pair sizes - -#define MAX_KEY 32 -#define MAX_VALUE 1024 - -//============================================================================= - -typedef struct -{ - int fileofs, filelen; -} lump_t; - -#define LUMP_ENTITIES 0 -#define LUMP_PLANES 1 -#define LUMP_VERTEXES 2 -#define LUMP_VISIBILITY 3 -#define LUMP_NODES 4 -#define LUMP_TEXINFO 5 -#define LUMP_FACES 6 -#define LUMP_LIGHTING 7 -#define LUMP_LEAFS 8 -#define LUMP_LEAFFACES 9 -#define LUMP_LEAFBRUSHES 10 -#define LUMP_EDGES 11 -#define LUMP_SURFEDGES 12 -#define LUMP_MODELS 13 -#define LUMP_BRUSHES 14 -#define LUMP_BRUSHSIDES 15 -#define LUMP_POP 16 -#define LUMP_AREAS 17 -#define LUMP_AREAPORTALS 18 -#define HEADER_LUMPS 19 - -typedef struct -{ - int ident; - int version; - lump_t lumps[HEADER_LUMPS]; -} dheader_t; - -typedef struct -{ - float mins[3], maxs[3]; - float origin[3]; // for sounds or lights - int headnode; - int firstface, numfaces; // submodels just draw faces - // without walking the bsp tree -} dmodel_t; - - -typedef struct -{ - float point[3]; -} dvertex_t; - - -// 0-2 are axial planes -#define PLANE_X 0 -#define PLANE_Y 1 -#define PLANE_Z 2 - -// 3-5 are non-axial planes snapped to the nearest -#define PLANE_ANYX 3 -#define PLANE_ANYY 4 -#define PLANE_ANYZ 5 - -// planes (x&~1) and (x&~1)+1 are allways opposites - -typedef struct -{ - float normal[3]; - float dist; - int type; // PLANE_X - PLANE_ANYZ ?remove? trivial to regenerate -} dplane_t; - - -// contents flags are seperate bits -// a given brush can contribute multiple content bits -// multiple brushes can be in a single leaf - -// these definitions also need to be in q_shared.h! - -// lower bits are stronger, and will eat weaker brushes completely -#define CONTENTS_SOLID 1 // an eye is never valid in a solid -#define CONTENTS_WINDOW 2 // translucent, but not watery -#define CONTENTS_AUX 4 -#define CONTENTS_LAVA 8 -#define CONTENTS_SLIME 16 -#define CONTENTS_WATER 32 -#define CONTENTS_MIST 64 -#define LAST_VISIBLE_CONTENTS 64 - -// remaining contents are non-visible, and don't eat brushes - -#define CONTENTS_AREAPORTAL 0x8000 - -#define CONTENTS_PLAYERCLIP 0x10000 -#define CONTENTS_MONSTERCLIP 0x20000 - -// currents can be added to any other contents, and may be mixed -#define CONTENTS_CURRENT_0 0x40000 -#define CONTENTS_CURRENT_90 0x80000 -#define CONTENTS_CURRENT_180 0x100000 -#define CONTENTS_CURRENT_270 0x200000 -#define CONTENTS_CURRENT_UP 0x400000 -#define CONTENTS_CURRENT_DOWN 0x800000 - -#define CONTENTS_ORIGIN 0x1000000 // removed before bsping an entity - -#define CONTENTS_MONSTER 0x2000000 // should never be on a brush, only in game -#define CONTENTS_DEADMONSTER 0x4000000 -#define CONTENTS_DETAIL 0x8000000 // brushes to be added after vis leafs -//renamed because it's in conflict with the Q3A translucent contents -#define CONTENTS_Q2TRANSLUCENT 0x10000000 // auto set if any surface has trans -#define CONTENTS_LADDER 0x20000000 - - - -#define SURF_LIGHT 0x1 // value will hold the light strength - -#define SURF_SLICK 0x2 // effects game physics - -#define SURF_SKY 0x4 // don't draw, but add to skybox -#define SURF_WARP 0x8 // turbulent water warp -#define SURF_TRANS33 0x10 -#define SURF_TRANS66 0x20 -#define SURF_FLOWING 0x40 // scroll towards angle -#define SURF_NODRAW 0x80 // don't bother referencing the texture - -#define SURF_HINT 0x100 // make a primary bsp splitter -#define SURF_SKIP 0x200 // completely ignore, allowing non-closed brushes - - - -typedef struct -{ - int planenum; - int children[2]; // negative numbers are -(leafs+1), not nodes - short mins[3]; // for frustom culling - short maxs[3]; - unsigned short firstface; - unsigned short numfaces; // counting both sides -} dnode_t; - - -typedef struct texinfo_s -{ - float vecs[2][4]; // [s/t][xyz offset] - int flags; // miptex flags + overrides - int value; // light emission, etc - char texture[32]; // texture name (textures/*.wal) - int nexttexinfo; // for animations, -1 = end of chain -} texinfo_t; - - -// note that edge 0 is never used, because negative edge nums are used for -// counterclockwise use of the edge in a face -typedef struct -{ - unsigned short v[2]; // vertex numbers -} dedge_t; - -#define MAXLIGHTMAPS 4 -typedef struct -{ - unsigned short planenum; - short side; - - int firstedge; // we must support > 64k edges - short numedges; - short texinfo; - -// lighting info - byte styles[MAXLIGHTMAPS]; - int lightofs; // start of [numstyles*surfsize] samples -} dface_t; - -typedef struct -{ - int contents; // OR of all brushes (not needed?) - - short cluster; - short area; - - short mins[3]; // for frustum culling - short maxs[3]; - - unsigned short firstleafface; - unsigned short numleaffaces; - - unsigned short firstleafbrush; - unsigned short numleafbrushes; -} dleaf_t; - -typedef struct -{ - unsigned short planenum; // facing out of the leaf - short texinfo; -} dbrushside_t; - -typedef struct -{ - int firstside; - int numsides; - int contents; -} dbrush_t; - -#define ANGLE_UP -1 -#define ANGLE_DOWN -2 - - -// the visibility lump consists of a header with a count, then -// byte offsets for the PVS and PHS of each cluster, then the raw -// compressed bit vectors -#define DVIS_PVS 0 -#define DVIS_PHS 1 -typedef struct -{ - int numclusters; - int bitofs[8][2]; // bitofs[numclusters][2] -} dvis_t; - -// each area has a list of portals that lead into other areas -// when portals are closed, other areas may not be visible or -// hearable even if the vis info says that it should be -typedef struct -{ - int portalnum; - int otherarea; -} dareaportal_t; - -typedef struct -{ - int numareaportals; - int firstareaportal; -} darea_t; +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// +// qfiles.h: quake file formats +// This file must be identical in the quake and utils directories +// + +/* +======================================================================== + +The .pak files are just a linear collapse of a directory tree + +======================================================================== +*/ + +#define IDPAKHEADER (('K'<<24)+('C'<<16)+('A'<<8)+'P') + +typedef struct +{ + char name[56]; + int filepos, filelen; +} dpackfile_t; + +typedef struct +{ + int ident; // == IDPAKHEADER + int dirofs; + int dirlen; +} dpackheader_t; + +#define MAX_FILES_IN_PACK 4096 + + +/* +======================================================================== + +PCX files are used for as many images as possible + +======================================================================== +*/ + +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; + + +/* +======================================================================== + +.MD2 triangle model file format + +======================================================================== +*/ + +#define IDALIASHEADER (('2'<<24)+('P'<<16)+('D'<<8)+'I') +#define ALIAS_VERSION 8 + +#define MAX_TRIANGLES 4096 +#define MAX_VERTS 2048 +#define MAX_FRAMES 512 +#define MAX_MD2SKINS 32 +#define MAX_SKINNAME 64 + +typedef struct +{ + short s; + short t; +} dstvert_t; + +typedef struct +{ + short index_xyz[3]; + short index_st[3]; +} dtriangle_t; + +typedef struct +{ + byte v[3]; // scaled byte to fit in frame mins/maxs + byte lightnormalindex; +} dtrivertx_t; + +#define DTRIVERTX_V0 0 +#define DTRIVERTX_V1 1 +#define DTRIVERTX_V2 2 +#define DTRIVERTX_LNI 3 +#define DTRIVERTX_SIZE 4 + +typedef struct +{ + float scale[3]; // multiply byte verts by this + float translate[3]; // then add this + char name[16]; // frame name from grabbing + dtrivertx_t verts[1]; // variable sized +} daliasframe_t; + + +// the glcmd format: +// a positive integer starts a tristrip command, followed by that many +// vertex structures. +// a negative integer starts a trifan command, followed by -x vertexes +// a zero indicates the end of the command list. +// a vertex consists of a floating point s, a floating point t, +// and an integer vertex index. + + +typedef struct +{ + int ident; + int version; + + int skinwidth; + int skinheight; + int framesize; // byte size of each frame + + int num_skins; + int num_xyz; + int num_st; // greater than num_xyz for seams + int num_tris; + int num_glcmds; // dwords in strip/fan command list + int num_frames; + + int ofs_skins; // each skin is a MAX_SKINNAME string + int ofs_st; // byte offset from start for stverts + int ofs_tris; // offset for dtriangles + int ofs_frames; // offset for first frame + int ofs_glcmds; + int ofs_end; // end of file + +} dmdl_t; + +/* +======================================================================== + +.SP2 sprite file format + +======================================================================== +*/ + +#define IDSPRITEHEADER (('2'<<24)+('S'<<16)+('D'<<8)+'I') + // little-endian "IDS2" +#define SPRITE_VERSION 2 + +typedef struct +{ + int width, height; + int origin_x, origin_y; // raster coordinates inside pic + char name[MAX_SKINNAME]; // name of pcx file +} dsprframe_t; + +typedef struct { + int ident; + int version; + int numframes; + dsprframe_t frames[1]; // variable sized +} dsprite_t; + +/* +============================================================================== + + .WAL texture file format + +============================================================================== +*/ + + +#define MIPLEVELS 4 +typedef struct miptex_s +{ + char name[32]; + unsigned width, height; + unsigned offsets[MIPLEVELS]; // four mip maps stored + char animname[32]; // next frame in animation chain + int flags; + int contents; + int value; +} miptex_t; + + + +/* +============================================================================== + + .BSP file format + +============================================================================== +*/ + +#define IDBSPHEADER (('P'<<24)+('S'<<16)+('B'<<8)+'I') + // little-endian "IBSP" + +#define BSPVERSION 38 + + +// upper design bounds +// leaffaces, leafbrushes, planes, and verts are still bounded by +// 16 bit short limits +#define MAX_MAP_MODELS 1024 +#define MAX_MAP_BRUSHES 8192 +#define MAX_MAP_ENTITIES 2048 +#define MAX_MAP_ENTSTRING 0x40000 +#define MAX_MAP_TEXINFO 8192 + +#define MAX_MAP_AREAS 256 +#define MAX_MAP_AREAPORTALS 1024 +#define MAX_MAP_PLANES 65536 +#define MAX_MAP_NODES 65536 +#define MAX_MAP_BRUSHSIDES 65536 +#define MAX_MAP_LEAFS 65536 +#define MAX_MAP_VERTS 65536 +#define MAX_MAP_FACES 65536 +#define MAX_MAP_LEAFFACES 65536 +#define MAX_MAP_LEAFBRUSHES 65536 +#define MAX_MAP_PORTALS 65536 +#define MAX_MAP_EDGES 128000 +#define MAX_MAP_SURFEDGES 256000 +#define MAX_MAP_LIGHTING 0x320000 +#define MAX_MAP_VISIBILITY 0x280000 + +// key / value pair sizes + +#define MAX_KEY 32 +#define MAX_VALUE 1024 + +//============================================================================= + +typedef struct +{ + int fileofs, filelen; +} lump_t; + +#define LUMP_ENTITIES 0 +#define LUMP_PLANES 1 +#define LUMP_VERTEXES 2 +#define LUMP_VISIBILITY 3 +#define LUMP_NODES 4 +#define LUMP_TEXINFO 5 +#define LUMP_FACES 6 +#define LUMP_LIGHTING 7 +#define LUMP_LEAFS 8 +#define LUMP_LEAFFACES 9 +#define LUMP_LEAFBRUSHES 10 +#define LUMP_EDGES 11 +#define LUMP_SURFEDGES 12 +#define LUMP_MODELS 13 +#define LUMP_BRUSHES 14 +#define LUMP_BRUSHSIDES 15 +#define LUMP_POP 16 +#define LUMP_AREAS 17 +#define LUMP_AREAPORTALS 18 +#define HEADER_LUMPS 19 + +typedef struct +{ + int ident; + int version; + lump_t lumps[HEADER_LUMPS]; +} dheader_t; + +typedef struct +{ + float mins[3], maxs[3]; + float origin[3]; // for sounds or lights + int headnode; + int firstface, numfaces; // submodels just draw faces + // without walking the bsp tree +} dmodel_t; + + +typedef struct +{ + float point[3]; +} dvertex_t; + + +// 0-2 are axial planes +#define PLANE_X 0 +#define PLANE_Y 1 +#define PLANE_Z 2 + +// 3-5 are non-axial planes snapped to the nearest +#define PLANE_ANYX 3 +#define PLANE_ANYY 4 +#define PLANE_ANYZ 5 + +// planes (x&~1) and (x&~1)+1 are allways opposites + +typedef struct +{ + float normal[3]; + float dist; + int type; // PLANE_X - PLANE_ANYZ ?remove? trivial to regenerate +} dplane_t; + + +// contents flags are seperate bits +// a given brush can contribute multiple content bits +// multiple brushes can be in a single leaf + +// these definitions also need to be in q_shared.h! + +// lower bits are stronger, and will eat weaker brushes completely +#define CONTENTS_SOLID 1 // an eye is never valid in a solid +#define CONTENTS_WINDOW 2 // translucent, but not watery +#define CONTENTS_AUX 4 +#define CONTENTS_LAVA 8 +#define CONTENTS_SLIME 16 +#define CONTENTS_WATER 32 +#define CONTENTS_MIST 64 +#define LAST_VISIBLE_CONTENTS 64 + +// remaining contents are non-visible, and don't eat brushes + +#define CONTENTS_AREAPORTAL 0x8000 + +#define CONTENTS_PLAYERCLIP 0x10000 +#define CONTENTS_MONSTERCLIP 0x20000 + +// currents can be added to any other contents, and may be mixed +#define CONTENTS_CURRENT_0 0x40000 +#define CONTENTS_CURRENT_90 0x80000 +#define CONTENTS_CURRENT_180 0x100000 +#define CONTENTS_CURRENT_270 0x200000 +#define CONTENTS_CURRENT_UP 0x400000 +#define CONTENTS_CURRENT_DOWN 0x800000 + +#define CONTENTS_ORIGIN 0x1000000 // removed before bsping an entity + +#define CONTENTS_MONSTER 0x2000000 // should never be on a brush, only in game +#define CONTENTS_DEADMONSTER 0x4000000 +#define CONTENTS_DETAIL 0x8000000 // brushes to be added after vis leafs +//renamed because it's in conflict with the Q3A translucent contents +#define CONTENTS_Q2TRANSLUCENT 0x10000000 // auto set if any surface has trans +#define CONTENTS_LADDER 0x20000000 + + + +#define SURF_LIGHT 0x1 // value will hold the light strength + +#define SURF_SLICK 0x2 // effects game physics + +#define SURF_SKY 0x4 // don't draw, but add to skybox +#define SURF_WARP 0x8 // turbulent water warp +#define SURF_TRANS33 0x10 +#define SURF_TRANS66 0x20 +#define SURF_FLOWING 0x40 // scroll towards angle +#define SURF_NODRAW 0x80 // don't bother referencing the texture + +#define SURF_HINT 0x100 // make a primary bsp splitter +#define SURF_SKIP 0x200 // completely ignore, allowing non-closed brushes + + + +typedef struct +{ + int planenum; + int children[2]; // negative numbers are -(leafs+1), not nodes + short mins[3]; // for frustom culling + short maxs[3]; + unsigned short firstface; + unsigned short numfaces; // counting both sides +} dnode_t; + + +typedef struct texinfo_s +{ + float vecs[2][4]; // [s/t][xyz offset] + int flags; // miptex flags + overrides + int value; // light emission, etc + char texture[32]; // texture name (textures/*.wal) + int nexttexinfo; // for animations, -1 = end of chain +} texinfo_t; + + +// note that edge 0 is never used, because negative edge nums are used for +// counterclockwise use of the edge in a face +typedef struct +{ + unsigned short v[2]; // vertex numbers +} dedge_t; + +#define MAXLIGHTMAPS 4 +typedef struct +{ + unsigned short planenum; + short side; + + int firstedge; // we must support > 64k edges + short numedges; + short texinfo; + +// lighting info + byte styles[MAXLIGHTMAPS]; + int lightofs; // start of [numstyles*surfsize] samples +} dface_t; + +typedef struct +{ + int contents; // OR of all brushes (not needed?) + + short cluster; + short area; + + short mins[3]; // for frustum culling + short maxs[3]; + + unsigned short firstleafface; + unsigned short numleaffaces; + + unsigned short firstleafbrush; + unsigned short numleafbrushes; +} dleaf_t; + +typedef struct +{ + unsigned short planenum; // facing out of the leaf + short texinfo; +} dbrushside_t; + +typedef struct +{ + int firstside; + int numsides; + int contents; +} dbrush_t; + +#define ANGLE_UP -1 +#define ANGLE_DOWN -2 + + +// the visibility lump consists of a header with a count, then +// byte offsets for the PVS and PHS of each cluster, then the raw +// compressed bit vectors +#define DVIS_PVS 0 +#define DVIS_PHS 1 +typedef struct +{ + int numclusters; + int bitofs[8][2]; // bitofs[numclusters][2] +} dvis_t; + +// each area has a list of portals that lead into other areas +// when portals are closed, other areas may not be visible or +// hearable even if the vis info says that it should be +typedef struct +{ + int portalnum; + int otherarea; +} dareaportal_t; + +typedef struct +{ + int numareaportals; + int firstareaportal; +} darea_t; diff --git a/code/bspc/sinfiles.h b/code/bspc/sinfiles.h index 31091c5..bcb5ee6 100755 --- a/code/bspc/sinfiles.h +++ b/code/bspc/sinfiles.h @@ -1,365 +1,365 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/* -============================================================================== - - .BSP file format - -============================================================================== -*/ - -#define SIN - -#define SINBSPVERSION 41 - -// upper design bounds -// leaffaces, leafbrushes, planes, and verts are still bounded by -// 16 bit short limits -#define SIN_MAX_MAP_MODELS 1024 -#define SIN_MAX_MAP_BRUSHES 8192 -#define SIN_MAX_MAP_ENTITIES 2048 -#define SIN_MAX_MAP_ENTSTRING 0x40000 -#define SIN_MAX_MAP_TEXINFO 8192 - -#define SIN_MAX_MAP_AREAS 256 -#define SIN_MAX_MAP_AREAPORTALS 1024 -#define SIN_MAX_MAP_PLANES 65536 -#define SIN_MAX_MAP_NODES 65536 -#define SIN_MAX_MAP_BRUSHSIDES 65536 -#define SIN_MAX_MAP_LEAFS 65536 -#define SIN_MAX_MAP_VERTS 65536 -#define SIN_MAX_MAP_FACES 65536 -#define SIN_MAX_MAP_LEAFFACES 65536 -#define SIN_MAX_MAP_LEAFBRUSHES 65536 -#define SIN_MAX_MAP_PORTALS 65536 -#define SIN_MAX_MAP_EDGES 128000 -#define SIN_MAX_MAP_SURFEDGES 256000 -#define SIN_MAX_MAP_LIGHTING 0x320000 -#define SIN_MAX_MAP_VISIBILITY 0x280000 - -#ifdef SIN -#define SIN_MAX_MAP_LIGHTINFO 8192 -#endif - -#ifdef SIN -#undef SIN_MAX_MAP_LIGHTING //undef the Quake2 bsp version -#define SIN_MAX_MAP_LIGHTING 0x300000 -#endif - -#ifdef SIN -#undef SIN_MAX_MAP_VISIBILITY //undef the Quake2 bsp version -#define SIN_MAX_MAP_VISIBILITY 0x280000 -#endif - -//============================================================================= - -typedef struct -{ - int fileofs, filelen; -} sin_lump_t; - -#define SIN_LUMP_ENTITIES 0 -#define SIN_LUMP_PLANES 1 -#define SIN_LUMP_VERTEXES 2 -#define SIN_LUMP_VISIBILITY 3 -#define SIN_LUMP_NODES 4 -#define SIN_LUMP_TEXINFO 5 -#define SIN_LUMP_FACES 6 -#define SIN_LUMP_LIGHTING 7 -#define SIN_LUMP_LEAFS 8 -#define SIN_LUMP_LEAFFACES 9 -#define SIN_LUMP_LEAFBRUSHES 10 -#define SIN_LUMP_EDGES 11 -#define SIN_LUMP_SURFEDGES 12 -#define SIN_LUMP_MODELS 13 -#define SIN_LUMP_BRUSHES 14 -#define SIN_LUMP_BRUSHSIDES 15 -#define SIN_LUMP_POP 16 -#define SIN_LUMP_AREAS 17 -#define SIN_LUMP_AREAPORTALS 18 - -#ifdef SIN -#define SIN_LUMP_LIGHTINFO 19 -#define SINHEADER_LUMPS 20 -#endif - -typedef struct -{ - int ident; - int version; - sin_lump_t lumps[SINHEADER_LUMPS]; -} sin_dheader_t; - -typedef struct -{ - float mins[3], maxs[3]; - float origin[3]; // for sounds or lights - int headnode; - int firstface, numfaces; // submodels just draw faces - // without walking the bsp tree -} sin_dmodel_t; - -typedef struct -{ - float point[3]; -} sin_dvertex_t; - - -// 0-2 are axial planes -#define PLANE_X 0 -#define PLANE_Y 1 -#define PLANE_Z 2 - -// 3-5 are non-axial planes snapped to the nearest -#define PLANE_ANYX 3 -#define PLANE_ANYY 4 -#define PLANE_ANYZ 5 - -// planes (x&~1) and (x&~1)+1 are allways opposites - -typedef struct -{ - float normal[3]; - float dist; - int type; // PLANE_X - PLANE_ANYZ ?remove? trivial to regenerate -} sin_dplane_t; - - -// contents flags are seperate bits -// a given brush can contribute multiple content bits -// multiple brushes can be in a single leaf - -// these definitions also need to be in q_shared.h! - -// lower bits are stronger, and will eat weaker brushes completely -#ifdef SIN -#define CONTENTS_FENCE 4 -#endif -// remaining contents are non-visible, and don't eat brushes - -#ifdef SIN -#define CONTENTS_DUMMYFENCE 0x1000 -#endif - -#ifdef SIN -#define SURF_MASKED 0x2 // surface texture is masked -#endif - -#define SURF_SKY 0x4 // don't draw, but add to skybox -#define SURF_WARP 0x8 // turbulent water warp - -#ifdef SIN -#define SURF_NONLIT 0x10 // surface is not lit -#define SURF_NOFILTER 0x20 // surface is not bi-linear filtered -#endif - -#define SURF_FLOWING 0x40 // scroll towards angle -#define SURF_NODRAW 0x80 // don't bother referencing the texture - -#define SURF_HINT 0x100 // make a primary bsp splitter -#define SURF_SKIP 0x200 // completely ignore, allowing non-closed brushes - -#ifdef SIN -#define SURF_CONVEYOR 0x40 // surface is not lit -#endif - -#ifdef SIN -#define SURF_WAVY 0x400 // surface has waves -#define SURF_RICOCHET 0x800 // projectiles bounce literally bounce off this surface -#define SURF_PRELIT 0x1000 // surface has intensity information for pre-lighting -#define SURF_MIRROR 0x2000 // surface is a mirror -#define SURF_CONSOLE 0x4000 // surface is a console -#define SURF_USECOLOR 0x8000 // surface is lit with non-lit * color -#define SURF_HARDWAREONLY 0x10000 // surface has been damaged -#define SURF_DAMAGE 0x20000 // surface can be damaged -#define SURF_WEAK 0x40000 // surface has weak hit points -#define SURF_NORMAL 0x80000 // surface has normal hit points -#define SURF_ADD 0x100000 // surface will be additive -#define SURF_ENVMAPPED 0x200000 // surface is envmapped -#define SURF_RANDOMANIMATE 0x400000 // surface start animating on a random frame -#define SURF_ANIMATE 0x800000 // surface animates -#define SURF_RNDTIME 0x1000000 // time between animations is random -#define SURF_TRANSLATE 0x2000000 // surface translates -#define SURF_NOMERGE 0x4000000 // surface is not merged in csg phase -#define SURF_TYPE_BIT0 0x8000000 // 0 bit of surface type -#define SURF_TYPE_BIT1 0x10000000 // 1 bit of surface type -#define SURF_TYPE_BIT2 0x20000000 // 2 bit of surface type -#define SURF_TYPE_BIT3 0x40000000 // 3 bit of surface type - -#define SURF_START_BIT 27 -#define SURFACETYPE_FROM_FLAGS( x ) ( ( x >> (SURF_START_BIT) ) & 0xf ) - - -#define SURF_TYPE_SHIFT(x) ( (x) << (SURF_START_BIT) ) // macro for getting proper bit mask - -#define SURF_TYPE_NONE SURF_TYPE_SHIFT(0) -#define SURF_TYPE_WOOD SURF_TYPE_SHIFT(1) -#define SURF_TYPE_METAL SURF_TYPE_SHIFT(2) -#define SURF_TYPE_STONE SURF_TYPE_SHIFT(3) -#define SURF_TYPE_CONCRETE SURF_TYPE_SHIFT(4) -#define SURF_TYPE_DIRT SURF_TYPE_SHIFT(5) -#define SURF_TYPE_FLESH SURF_TYPE_SHIFT(6) -#define SURF_TYPE_GRILL SURF_TYPE_SHIFT(7) -#define SURF_TYPE_GLASS SURF_TYPE_SHIFT(8) -#define SURF_TYPE_FABRIC SURF_TYPE_SHIFT(9) -#define SURF_TYPE_MONITOR SURF_TYPE_SHIFT(10) -#define SURF_TYPE_GRAVEL SURF_TYPE_SHIFT(11) -#define SURF_TYPE_VEGETATION SURF_TYPE_SHIFT(12) -#define SURF_TYPE_PAPER SURF_TYPE_SHIFT(13) -#define SURF_TYPE_DUCT SURF_TYPE_SHIFT(14) -#define SURF_TYPE_WATER SURF_TYPE_SHIFT(15) -#endif - - -typedef struct -{ - int planenum; - int children[2]; // negative numbers are -(leafs+1), not nodes - short mins[3]; // for frustom culling - short maxs[3]; - unsigned short firstface; - unsigned short numfaces; // counting both sides -} sin_dnode_t; - -#ifdef SIN - -typedef struct sin_lightvalue_s -{ - int value; // light emission, etc - vec3_t color; - float direct; - float directangle; - float directstyle; - char directstylename[32]; -} sin_lightvalue_t; - -typedef struct sin_texinfo_s -{ - float vecs[2][4]; // [s/t][xyz offset] - int flags; // miptex flags + overrides - char texture[64]; // texture name (textures/*.wal) - int nexttexinfo; // for animations, -1 = end of chain - float trans_mag; - int trans_angle; - int base_angle; - float animtime; - float nonlit; - float translucence; - float friction; - float restitution; - vec3_t color; - char groupname[32]; -} sin_texinfo_t; - -#endif //SIN - -// note that edge 0 is never used, because negative edge nums are used for -// counterclockwise use of the edge in a face -typedef struct -{ - unsigned short v[2]; // vertex numbers -} sin_dedge_t; - -#ifdef MAXLIGHTMAPS -#undef MAXLIGHTMAPS -#endif -#define MAXLIGHTMAPS 16 -typedef struct -{ - unsigned short planenum; - short side; - - int firstedge; // we must support > 64k edges - short numedges; - short texinfo; - -// lighting info - byte styles[MAXLIGHTMAPS]; - int lightofs; // start of [numstyles*surfsize] samples -#ifdef SIN - int lightinfo; -#endif -} sin_dface_t; - -typedef struct -{ - int contents; // OR of all brushes (not needed?) - - short cluster; - short area; - - short mins[3]; // for frustum culling - short maxs[3]; - - unsigned short firstleafface; - unsigned short numleaffaces; - - unsigned short firstleafbrush; - unsigned short numleafbrushes; -} sin_dleaf_t; - -typedef struct -{ - unsigned short planenum; // facing out of the leaf - short texinfo; -#ifdef SIN - int lightinfo; -#endif -} sin_dbrushside_t; - -typedef struct -{ - int firstside; - int numsides; - int contents; -} sin_dbrush_t; - -#define ANGLE_UP -1 -#define ANGLE_DOWN -2 - - -// the visibility lump consists of a header with a count, then -// byte offsets for the PVS and PHS of each cluster, then the raw -// compressed bit vectors -#define DVIS_PVS 0 -#define DVIS_PHS 1 -typedef struct -{ - int numclusters; - int bitofs[8][2]; // bitofs[numclusters][2] -} sin_dvis_t; - -// each area has a list of portals that lead into other areas -// when portals are closed, other areas may not be visible or -// hearable even if the vis info says that it should be -typedef struct -{ - int portalnum; - int otherarea; -} sin_dareaportal_t; - -typedef struct -{ - int numareaportals; - int firstareaportal; -} sin_darea_t; +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/* +============================================================================== + + .BSP file format + +============================================================================== +*/ + +#define SIN + +#define SINBSPVERSION 41 + +// upper design bounds +// leaffaces, leafbrushes, planes, and verts are still bounded by +// 16 bit short limits +#define SIN_MAX_MAP_MODELS 1024 +#define SIN_MAX_MAP_BRUSHES 8192 +#define SIN_MAX_MAP_ENTITIES 2048 +#define SIN_MAX_MAP_ENTSTRING 0x40000 +#define SIN_MAX_MAP_TEXINFO 8192 + +#define SIN_MAX_MAP_AREAS 256 +#define SIN_MAX_MAP_AREAPORTALS 1024 +#define SIN_MAX_MAP_PLANES 65536 +#define SIN_MAX_MAP_NODES 65536 +#define SIN_MAX_MAP_BRUSHSIDES 65536 +#define SIN_MAX_MAP_LEAFS 65536 +#define SIN_MAX_MAP_VERTS 65536 +#define SIN_MAX_MAP_FACES 65536 +#define SIN_MAX_MAP_LEAFFACES 65536 +#define SIN_MAX_MAP_LEAFBRUSHES 65536 +#define SIN_MAX_MAP_PORTALS 65536 +#define SIN_MAX_MAP_EDGES 128000 +#define SIN_MAX_MAP_SURFEDGES 256000 +#define SIN_MAX_MAP_LIGHTING 0x320000 +#define SIN_MAX_MAP_VISIBILITY 0x280000 + +#ifdef SIN +#define SIN_MAX_MAP_LIGHTINFO 8192 +#endif + +#ifdef SIN +#undef SIN_MAX_MAP_LIGHTING //undef the Quake2 bsp version +#define SIN_MAX_MAP_LIGHTING 0x300000 +#endif + +#ifdef SIN +#undef SIN_MAX_MAP_VISIBILITY //undef the Quake2 bsp version +#define SIN_MAX_MAP_VISIBILITY 0x280000 +#endif + +//============================================================================= + +typedef struct +{ + int fileofs, filelen; +} sin_lump_t; + +#define SIN_LUMP_ENTITIES 0 +#define SIN_LUMP_PLANES 1 +#define SIN_LUMP_VERTEXES 2 +#define SIN_LUMP_VISIBILITY 3 +#define SIN_LUMP_NODES 4 +#define SIN_LUMP_TEXINFO 5 +#define SIN_LUMP_FACES 6 +#define SIN_LUMP_LIGHTING 7 +#define SIN_LUMP_LEAFS 8 +#define SIN_LUMP_LEAFFACES 9 +#define SIN_LUMP_LEAFBRUSHES 10 +#define SIN_LUMP_EDGES 11 +#define SIN_LUMP_SURFEDGES 12 +#define SIN_LUMP_MODELS 13 +#define SIN_LUMP_BRUSHES 14 +#define SIN_LUMP_BRUSHSIDES 15 +#define SIN_LUMP_POP 16 +#define SIN_LUMP_AREAS 17 +#define SIN_LUMP_AREAPORTALS 18 + +#ifdef SIN +#define SIN_LUMP_LIGHTINFO 19 +#define SINHEADER_LUMPS 20 +#endif + +typedef struct +{ + int ident; + int version; + sin_lump_t lumps[SINHEADER_LUMPS]; +} sin_dheader_t; + +typedef struct +{ + float mins[3], maxs[3]; + float origin[3]; // for sounds or lights + int headnode; + int firstface, numfaces; // submodels just draw faces + // without walking the bsp tree +} sin_dmodel_t; + +typedef struct +{ + float point[3]; +} sin_dvertex_t; + + +// 0-2 are axial planes +#define PLANE_X 0 +#define PLANE_Y 1 +#define PLANE_Z 2 + +// 3-5 are non-axial planes snapped to the nearest +#define PLANE_ANYX 3 +#define PLANE_ANYY 4 +#define PLANE_ANYZ 5 + +// planes (x&~1) and (x&~1)+1 are allways opposites + +typedef struct +{ + float normal[3]; + float dist; + int type; // PLANE_X - PLANE_ANYZ ?remove? trivial to regenerate +} sin_dplane_t; + + +// contents flags are seperate bits +// a given brush can contribute multiple content bits +// multiple brushes can be in a single leaf + +// these definitions also need to be in q_shared.h! + +// lower bits are stronger, and will eat weaker brushes completely +#ifdef SIN +#define CONTENTS_FENCE 4 +#endif +// remaining contents are non-visible, and don't eat brushes + +#ifdef SIN +#define CONTENTS_DUMMYFENCE 0x1000 +#endif + +#ifdef SIN +#define SURF_MASKED 0x2 // surface texture is masked +#endif + +#define SURF_SKY 0x4 // don't draw, but add to skybox +#define SURF_WARP 0x8 // turbulent water warp + +#ifdef SIN +#define SURF_NONLIT 0x10 // surface is not lit +#define SURF_NOFILTER 0x20 // surface is not bi-linear filtered +#endif + +#define SURF_FLOWING 0x40 // scroll towards angle +#define SURF_NODRAW 0x80 // don't bother referencing the texture + +#define SURF_HINT 0x100 // make a primary bsp splitter +#define SURF_SKIP 0x200 // completely ignore, allowing non-closed brushes + +#ifdef SIN +#define SURF_CONVEYOR 0x40 // surface is not lit +#endif + +#ifdef SIN +#define SURF_WAVY 0x400 // surface has waves +#define SURF_RICOCHET 0x800 // projectiles bounce literally bounce off this surface +#define SURF_PRELIT 0x1000 // surface has intensity information for pre-lighting +#define SURF_MIRROR 0x2000 // surface is a mirror +#define SURF_CONSOLE 0x4000 // surface is a console +#define SURF_USECOLOR 0x8000 // surface is lit with non-lit * color +#define SURF_HARDWAREONLY 0x10000 // surface has been damaged +#define SURF_DAMAGE 0x20000 // surface can be damaged +#define SURF_WEAK 0x40000 // surface has weak hit points +#define SURF_NORMAL 0x80000 // surface has normal hit points +#define SURF_ADD 0x100000 // surface will be additive +#define SURF_ENVMAPPED 0x200000 // surface is envmapped +#define SURF_RANDOMANIMATE 0x400000 // surface start animating on a random frame +#define SURF_ANIMATE 0x800000 // surface animates +#define SURF_RNDTIME 0x1000000 // time between animations is random +#define SURF_TRANSLATE 0x2000000 // surface translates +#define SURF_NOMERGE 0x4000000 // surface is not merged in csg phase +#define SURF_TYPE_BIT0 0x8000000 // 0 bit of surface type +#define SURF_TYPE_BIT1 0x10000000 // 1 bit of surface type +#define SURF_TYPE_BIT2 0x20000000 // 2 bit of surface type +#define SURF_TYPE_BIT3 0x40000000 // 3 bit of surface type + +#define SURF_START_BIT 27 +#define SURFACETYPE_FROM_FLAGS( x ) ( ( x >> (SURF_START_BIT) ) & 0xf ) + + +#define SURF_TYPE_SHIFT(x) ( (x) << (SURF_START_BIT) ) // macro for getting proper bit mask + +#define SURF_TYPE_NONE SURF_TYPE_SHIFT(0) +#define SURF_TYPE_WOOD SURF_TYPE_SHIFT(1) +#define SURF_TYPE_METAL SURF_TYPE_SHIFT(2) +#define SURF_TYPE_STONE SURF_TYPE_SHIFT(3) +#define SURF_TYPE_CONCRETE SURF_TYPE_SHIFT(4) +#define SURF_TYPE_DIRT SURF_TYPE_SHIFT(5) +#define SURF_TYPE_FLESH SURF_TYPE_SHIFT(6) +#define SURF_TYPE_GRILL SURF_TYPE_SHIFT(7) +#define SURF_TYPE_GLASS SURF_TYPE_SHIFT(8) +#define SURF_TYPE_FABRIC SURF_TYPE_SHIFT(9) +#define SURF_TYPE_MONITOR SURF_TYPE_SHIFT(10) +#define SURF_TYPE_GRAVEL SURF_TYPE_SHIFT(11) +#define SURF_TYPE_VEGETATION SURF_TYPE_SHIFT(12) +#define SURF_TYPE_PAPER SURF_TYPE_SHIFT(13) +#define SURF_TYPE_DUCT SURF_TYPE_SHIFT(14) +#define SURF_TYPE_WATER SURF_TYPE_SHIFT(15) +#endif + + +typedef struct +{ + int planenum; + int children[2]; // negative numbers are -(leafs+1), not nodes + short mins[3]; // for frustom culling + short maxs[3]; + unsigned short firstface; + unsigned short numfaces; // counting both sides +} sin_dnode_t; + +#ifdef SIN + +typedef struct sin_lightvalue_s +{ + int value; // light emission, etc + vec3_t color; + float direct; + float directangle; + float directstyle; + char directstylename[32]; +} sin_lightvalue_t; + +typedef struct sin_texinfo_s +{ + float vecs[2][4]; // [s/t][xyz offset] + int flags; // miptex flags + overrides + char texture[64]; // texture name (textures/*.wal) + int nexttexinfo; // for animations, -1 = end of chain + float trans_mag; + int trans_angle; + int base_angle; + float animtime; + float nonlit; + float translucence; + float friction; + float restitution; + vec3_t color; + char groupname[32]; +} sin_texinfo_t; + +#endif //SIN + +// note that edge 0 is never used, because negative edge nums are used for +// counterclockwise use of the edge in a face +typedef struct +{ + unsigned short v[2]; // vertex numbers +} sin_dedge_t; + +#ifdef MAXLIGHTMAPS +#undef MAXLIGHTMAPS +#endif +#define MAXLIGHTMAPS 16 +typedef struct +{ + unsigned short planenum; + short side; + + int firstedge; // we must support > 64k edges + short numedges; + short texinfo; + +// lighting info + byte styles[MAXLIGHTMAPS]; + int lightofs; // start of [numstyles*surfsize] samples +#ifdef SIN + int lightinfo; +#endif +} sin_dface_t; + +typedef struct +{ + int contents; // OR of all brushes (not needed?) + + short cluster; + short area; + + short mins[3]; // for frustum culling + short maxs[3]; + + unsigned short firstleafface; + unsigned short numleaffaces; + + unsigned short firstleafbrush; + unsigned short numleafbrushes; +} sin_dleaf_t; + +typedef struct +{ + unsigned short planenum; // facing out of the leaf + short texinfo; +#ifdef SIN + int lightinfo; +#endif +} sin_dbrushside_t; + +typedef struct +{ + int firstside; + int numsides; + int contents; +} sin_dbrush_t; + +#define ANGLE_UP -1 +#define ANGLE_DOWN -2 + + +// the visibility lump consists of a header with a count, then +// byte offsets for the PVS and PHS of each cluster, then the raw +// compressed bit vectors +#define DVIS_PVS 0 +#define DVIS_PHS 1 +typedef struct +{ + int numclusters; + int bitofs[8][2]; // bitofs[numclusters][2] +} sin_dvis_t; + +// each area has a list of portals that lead into other areas +// when portals are closed, other areas may not be visible or +// hearable even if the vis info says that it should be +typedef struct +{ + int portalnum; + int otherarea; +} sin_dareaportal_t; + +typedef struct +{ + int numareaportals; + int firstareaportal; +} sin_darea_t; diff --git a/code/bspc/tetrahedron.c b/code/bspc/tetrahedron.c index 36c10af..4c4d251 100755 --- a/code/bspc/tetrahedron.c +++ b/code/bspc/tetrahedron.c @@ -1,1389 +1,1389 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "qbsp.h" -#include "l_mem.h" -#include "../botlib/aasfile.h" -#include "aas_store.h" -#include "aas_cfg.h" -#include "aas_file.h" - -// -// creating tetrahedrons from a arbitrary world bounded by triangles -// -// a triangle has 3 corners and 3 edges -// a tetrahedron is build out of 4 triangles -// a tetrahedron has 6 edges -// we start with a world bounded by triangles, a side of a triangle facing -// towards the oudside of the world is marked as part of tetrahedron -1 -// -// a tetrahedron is defined by two non-coplanar triangles with a shared edge -// -// a tetrahedron is defined by one triangle and a vertex not in the triangle plane -// -// if all triangles using a specific vertex have tetrahedrons -// at both sides then this vertex will never be part of a new tetrahedron -// -// if all triangles using a specific edge have tetrahedrons -// at both sides then this vertex will never be part of a new tetrahedron -// -// each triangle can only be shared by two tetrahedrons -// when all triangles have tetrahedrons at both sides then we're done -// -// if we cannot create any new tetrahedrons and there is at least one triangle -// which has a tetrahedron only at one side then the world leaks -// - -#define Sign(x) (x < 0 ? 1 : 0) - -#define MAX_TH_VERTEXES 128000 -#define MAX_TH_PLANES 128000 -#define MAX_TH_EDGES 512000 -#define MAX_TH_TRIANGLES 51200 -#define MAX_TH_TETRAHEDRONS 12800 - -#define PLANEHASH_SIZE 1024 -#define EDGEHASH_SIZE 1024 -#define TRIANGLEHASH_SIZE 1024 -#define VERTEXHASH_SHIFT 7 -#define VERTEXHASH_SIZE ((MAX_MAP_BOUNDS>>(VERTEXHASH_SHIFT-1))+1) //was 64 - -#define NORMAL_EPSILON 0.0001 -#define DIST_EPSILON 0.1 -#define VERTEX_EPSILON 0.01 -#define INTEGRAL_EPSILON 0.01 - - -//plane -typedef struct th_plane_s -{ - vec3_t normal; - float dist; - int type; - int signbits; - struct th_plane_s *hashnext; //next plane in hash -} th_plane_t; - -//vertex -typedef struct th_vertex_s -{ - vec3_t v; - int usercount; //2x the number of to be processed - //triangles using this vertex - struct th_vertex_s *hashnext; //next vertex in hash -} th_vertex_t; - -//edge -typedef struct th_edge_s -{ - int v[2]; //vertex indexes - int usercount; //number of to be processed - //triangles using this edge - struct th_edge_s *hashnext; //next edge in hash -} th_edge_t; - -//triangle -typedef struct th_triangle_s -{ - int edges[3]; //negative if edge is flipped - th_plane_t planes[3]; //triangle bounding planes - int planenum; //plane the triangle is in - int front; //tetrahedron at the front - int back; //tetrahedron at the back - vec3_t mins, maxs; //triangle bounding box - struct th_triangle_s *prev, *next; //links in linked triangle lists - struct th_triangle_s *hashnext; //next triangle in hash -} th_triangle_t; - -//tetrahedron -typedef struct th_tetrahedron_s -{ - int triangles[4]; //negative if at backside of triangle - float volume; //tetrahedron volume -} th_tetrahedron_t; - -typedef struct th_s -{ - //vertexes - int numvertexes; - th_vertex_t *vertexes; - th_vertex_t *vertexhash[VERTEXHASH_SIZE * VERTEXHASH_SIZE]; - //planes - int numplanes; - th_plane_t *planes; - th_plane_t *planehash[PLANEHASH_SIZE]; - //edges - int numedges; - th_edge_t *edges; - th_edge_t *edgehash[EDGEHASH_SIZE]; - //triangles - int numtriangles; - th_triangle_t *triangles; - th_triangle_t *trianglehash[TRIANGLEHASH_SIZE]; - //tetrahedrons - int numtetrahedrons; - th_tetrahedron_t *tetrahedrons; -} th_t; - -th_t thworld; - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void TH_InitMaxTH(void) -{ - //get memory for the tetrahedron data - thworld.vertexes = (th_vertex_t *) GetClearedMemory(MAX_TH_VERTEXES * sizeof(th_vertex_t)); - thworld.planes = (th_plane_t *) GetClearedMemory(MAX_TH_PLANES * sizeof(th_plane_t)); - thworld.edges = (th_edge_t *) GetClearedMemory(MAX_TH_EDGES * sizeof(th_edge_t)); - thworld.triangles = (th_triangle_t *) GetClearedMemory(MAX_TH_TRIANGLES * sizeof(th_triangle_t)); - thworld.tetrahedrons = (th_tetrahedron_t *) GetClearedMemory(MAX_TH_TETRAHEDRONS * sizeof(th_tetrahedron_t)); - //reset the hash tables - memset(thworld.vertexhash, 0, VERTEXHASH_SIZE * sizeof(th_vertex_t *)); - memset(thworld.planehash, 0, PLANEHASH_SIZE * sizeof(th_plane_t *)); - memset(thworld.edgehash, 0, EDGEHASH_SIZE * sizeof(th_edge_t *)); - memset(thworld.trianglehash, 0, TRIANGLEHASH_SIZE * sizeof(th_triangle_t *)); -} //end of the function TH_InitMaxTH -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void TH_FreeMaxTH(void) -{ - if (thworld.vertexes) FreeMemory(thworld.vertexes); - thworld.vertexes = NULL; - thworld.numvertexes = 0; - if (thworld.planes) FreeMemory(thworld.planes); - thworld.planes = NULL; - thworld.numplanes = 0; - if (thworld.edges) FreeMemory(thworld.edges); - thworld.edges = NULL; - thworld.numedges = 0; - if (thworld.triangles) FreeMemory(thworld.triangles); - thworld.triangles = NULL; - thworld.numtriangles = 0; - if (thworld.tetrahedrons) FreeMemory(thworld.tetrahedrons); - thworld.tetrahedrons = NULL; - thworld.numtetrahedrons = 0; -} //end of the function TH_FreeMaxTH -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -float TH_TriangleArea(th_triangle_t *tri) -{ - return 0; -} //end of the function TH_TriangleArea -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -float TH_TetrahedronVolume(th_tetrahedron_t *tetrahedron) -{ - int edgenum, verts[3], i, j, v2; - float volume, d; - th_triangle_t *tri, *tri2; - th_plane_t *plane; - - tri = &thworld.triangles[abs(tetrahedron->triangles[0])]; - for (i = 0; i < 3; i++) - { - edgenum = tri->edges[i]; - if (edgenum < 0) verts[i] = thworld.edges[abs(edgenum)].v[1]; - else verts[i] = thworld.edges[edgenum].v[0]; - } //end for - // - tri2 = &thworld.triangles[abs(tetrahedron->triangles[1])]; - for (j = 0; j < 3; j++) - { - edgenum = tri2->edges[i]; - if (edgenum < 0) v2 = thworld.edges[abs(edgenum)].v[1]; - else v2 = thworld.edges[edgenum].v[0]; - if (v2 != verts[0] && - v2 != verts[1] && - v2 != verts[2]) break; - } //end for - - plane = &thworld.planes[tri->planenum]; - d = -(DotProduct (thworld.vertexes[v2].v, plane->normal) - plane->dist); - volume = TH_TriangleArea(tri) * d / 3; - return volume; -} //end of the function TH_TetrahedronVolume -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int TH_PlaneSignBits(vec3_t normal) -{ - int i, signbits; - - signbits = 0; - for (i = 2; i >= 0; i--) - { - signbits = (signbits << 1) + Sign(normal[i]); - } //end for - return signbits; -} //end of the function TH_PlaneSignBits -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int TH_PlaneTypeForNormal(vec3_t normal) -{ - vec_t ax, ay, az; - -// NOTE: should these have an epsilon around 1.0? - if (normal[0] == 1.0 || normal[0] == -1.0) - return PLANE_X; - if (normal[1] == 1.0 || normal[1] == -1.0) - return PLANE_Y; - if (normal[2] == 1.0 || normal[2] == -1.0) - return PLANE_Z; - - ax = fabs(normal[0]); - ay = fabs(normal[1]); - az = fabs(normal[2]); - - if (ax >= ay && ax >= az) - return PLANE_ANYX; - if (ay >= ax && ay >= az) - return PLANE_ANYY; - return PLANE_ANYZ; -} //end of the function TH_PlaneTypeForNormal -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -qboolean TH_PlaneEqual(th_plane_t *p, vec3_t normal, vec_t dist) -{ - if ( - fabs(p->normal[0] - normal[0]) < NORMAL_EPSILON - && fabs(p->normal[1] - normal[1]) < NORMAL_EPSILON - && fabs(p->normal[2] - normal[2]) < NORMAL_EPSILON - && fabs(p->dist - dist) < DIST_EPSILON ) - return true; - return false; -} //end of the function TH_PlaneEqual -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void TH_AddPlaneToHash(th_plane_t *p) -{ - int hash; - - hash = (int)fabs(p->dist) / 8; - hash &= (PLANEHASH_SIZE-1); - - p->hashnext = thworld.planehash[hash]; - thworld.planehash[hash] = p; -} //end of the function TH_AddPlaneToHash -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int TH_CreateFloatPlane(vec3_t normal, vec_t dist) -{ - th_plane_t *p, temp; - - if (VectorLength(normal) < 0.5) - Error ("FloatPlane: bad normal"); - // create a new plane - if (thworld.numplanes+2 > MAX_TH_PLANES) - Error ("MAX_TH_PLANES"); - - p = &thworld.planes[thworld.numplanes]; - VectorCopy (normal, p->normal); - p->dist = dist; - p->type = (p+1)->type = TH_PlaneTypeForNormal (p->normal); - p->signbits = TH_PlaneSignBits(p->normal); - - VectorSubtract (vec3_origin, normal, (p+1)->normal); - (p+1)->dist = -dist; - (p+1)->signbits = TH_PlaneSignBits((p+1)->normal); - - thworld.numplanes += 2; - - // allways put axial planes facing positive first - if (p->type < 3) - { - if (p->normal[0] < 0 || p->normal[1] < 0 || p->normal[2] < 0) - { - // flip order - temp = *p; - *p = *(p+1); - *(p+1) = temp; - - TH_AddPlaneToHash(p); - TH_AddPlaneToHash(p+1); - return thworld.numplanes - 1; - } //end if - } //end if - - TH_AddPlaneToHash(p); - TH_AddPlaneToHash(p+1); - return thworld.numplanes - 2; -} //end of the function TH_CreateFloatPlane -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void TH_SnapVector(vec3_t normal) -{ - int i; - - for (i = 0; i < 3; i++) - { - if ( fabs(normal[i] - 1) < NORMAL_EPSILON ) - { - VectorClear (normal); - normal[i] = 1; - break; - } //end if - if ( fabs(normal[i] - -1) < NORMAL_EPSILON ) - { - VectorClear (normal); - normal[i] = -1; - break; - } //end if - } //end for -} //end of the function TH_SnapVector -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void TH_SnapPlane(vec3_t normal, vec_t *dist) -{ - TH_SnapVector(normal); - - if (fabs(*dist-Q_rint(*dist)) < DIST_EPSILON) - *dist = Q_rint(*dist); -} //end of the function TH_SnapPlane -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int TH_FindFloatPlane(vec3_t normal, vec_t dist) -{ - int i; - th_plane_t *p; - int hash, h; - - TH_SnapPlane (normal, &dist); - hash = (int)fabs(dist) / 8; - hash &= (PLANEHASH_SIZE-1); - - // search the border bins as well - for (i = -1; i <= 1; i++) - { - h = (hash+i)&(PLANEHASH_SIZE-1); - for (p = thworld.planehash[h]; p; p = p->hashnext) - { - if (TH_PlaneEqual(p, normal, dist)) - { - return p - thworld.planes; - } //end if - } //end for - } //end for - return TH_CreateFloatPlane(normal, dist); -} //end of the function TH_FindFloatPlane -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int TH_PlaneFromPoints(int v1, int v2, int v3) -{ - vec3_t t1, t2, normal; - vec_t dist; - float *p0, *p1, *p2; - - p0 = thworld.vertexes[v1].v; - p1 = thworld.vertexes[v2].v; - p2 = thworld.vertexes[v3].v; - - VectorSubtract(p0, p1, t1); - VectorSubtract(p2, p1, t2); - CrossProduct(t1, t2, normal); - VectorNormalize(normal); - - dist = DotProduct(p0, normal); - - return TH_FindFloatPlane(normal, dist); -} //end of the function TH_PlaneFromPoints -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void TH_AddEdgeUser(int edgenum) -{ - th_edge_t *edge; - - edge = &thworld.edges[abs(edgenum)]; - //increase edge user count - edge->usercount++; - //increase vertex user count as well - thworld.vertexes[edge->v[0]].usercount++; - thworld.vertexes[edge->v[1]].usercount++; -} //end of the function TH_AddEdgeUser -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void TH_RemoveEdgeUser(int edgenum) -{ - th_edge_t *edge; - - edge = &thworld.edges[abs(edgenum)]; - //decrease edge user count - edge->usercount--; - //decrease vertex user count as well - thworld.vertexes[edge->v[0]].usercount--; - thworld.vertexes[edge->v[1]].usercount--; -} //end of the function TH_RemoveEdgeUser -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void TH_FreeTriangleEdges(th_triangle_t *tri) -{ - int i; - - for (i = 0; i < 3; i++) - { - TH_RemoveEdgeUser(abs(tri->edges[i])); - } //end for -} //end of the function TH_FreeTriangleEdges -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -unsigned TH_HashVec(vec3_t vec) -{ - int x, y; - - x = (MAX_MAP_BOUNDS + (int)(vec[0]+0.5)) >> VERTEXHASH_SHIFT; - y = (MAX_MAP_BOUNDS + (int)(vec[1]+0.5)) >> VERTEXHASH_SHIFT; - - if (x < 0 || x >= VERTEXHASH_SIZE || y < 0 || y >= VERTEXHASH_SIZE) - Error("HashVec: point %f %f %f outside valid range", vec[0], vec[1], vec[2]); - - return y*VERTEXHASH_SIZE + x; -} //end of the function TH_HashVec -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int TH_FindVertex(vec3_t v) -{ - int i, h; - th_vertex_t *vertex; - vec3_t vert; - - for (i = 0; i < 3; i++) - { - if ( fabs(v[i] - Q_rint(v[i])) < INTEGRAL_EPSILON) - vert[i] = Q_rint(v[i]); - else - vert[i] = v[i]; - } //end for - - h = TH_HashVec(vert); - - for (vertex = thworld.vertexhash[h]; vertex; vertex = vertex->hashnext) - { - if (fabs(vertex->v[0] - vert[0]) < VERTEX_EPSILON && - fabs(vertex->v[1] - vert[1]) < VERTEX_EPSILON && - fabs(vertex->v[2] - vert[2]) < VERTEX_EPSILON) - { - return vertex - thworld.vertexes; - } //end if - } //end for - return 0; -} //end of the function TH_FindVertex -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void TH_AddVertexToHash(th_vertex_t *vertex) -{ - int hashvalue; - - hashvalue = TH_HashVec(vertex->v); - vertex->hashnext = thworld.vertexhash[hashvalue]; - thworld.vertexhash[hashvalue] = vertex; -} //end of the function TH_AddVertexToHash -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int TH_CreateVertex(vec3_t v) -{ - if (thworld.numvertexes == 0) thworld.numvertexes = 1; - if (thworld.numvertexes >= MAX_TH_VERTEXES) - Error("MAX_TH_VERTEXES"); - VectorCopy(v, thworld.vertexes[thworld.numvertexes].v); - thworld.vertexes[thworld.numvertexes].usercount = 0; - TH_AddVertexToHash(&thworld.vertexes[thworld.numvertexes]); - thworld.numvertexes++; - return thworld.numvertexes-1; -} //end of the function TH_CreateVertex -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int TH_FindOrCreateVertex(vec3_t v) -{ - int vertexnum; - - vertexnum = TH_FindVertex(v); - if (!vertexnum) vertexnum = TH_CreateVertex(v); - return vertexnum; -} //end of the function TH_FindOrCreateVertex -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int TH_FindEdge(int v1, int v2) -{ - int hashvalue; - th_edge_t *edge; - - hashvalue = (v1 + v2) & (EDGEHASH_SIZE-1); - - for (edge = thworld.edgehash[hashvalue]; edge; edge = edge->hashnext) - { - if (edge->v[0] == v1 && edge->v[1] == v2) return edge - thworld.edges; - if (edge->v[1] == v1 && edge->v[0] == v2) return -(edge - thworld.edges); - } //end for - return 0; -} //end of the function TH_FindEdge -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void TH_AddEdgeToHash(th_edge_t *edge) -{ - int hashvalue; - - hashvalue = (edge->v[0] + edge->v[1]) & (EDGEHASH_SIZE-1); - edge->hashnext = thworld.edgehash[hashvalue]; - thworld.edgehash[hashvalue] = edge; -} //end of the function TH_AddEdgeToHash -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int TH_CreateEdge(int v1, int v2) -{ - th_edge_t *edge; - - if (thworld.numedges == 0) thworld.numedges = 1; - if (thworld.numedges >= MAX_TH_EDGES) - Error("MAX_TH_EDGES"); - edge = &thworld.edges[thworld.numedges++]; - edge->v[0] = v1; - edge->v[1] = v2; - TH_AddEdgeToHash(edge); - return thworld.numedges-1; -} //end of the function TH_CreateEdge -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int TH_FindOrCreateEdge(int v1, int v2) -{ - int edgenum; - - edgenum = TH_FindEdge(v1, v2); - if (!edgenum) edgenum = TH_CreateEdge(v1, v2); - return edgenum; -} //end of the function TH_FindOrCreateEdge -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int TH_FindTriangle(int verts[3]) -{ - int i, hashvalue, edges[3]; - th_triangle_t *tri; - - for (i = 0; i < 3; i++) - { - edges[i] = TH_FindEdge(verts[i], verts[(i+1)%3]); - if (!edges[i]) return false; - } //end for - hashvalue = (abs(edges[0]) + abs(edges[1]) + abs(edges[2])) & (TRIANGLEHASH_SIZE-1); - for (tri = thworld.trianglehash[hashvalue]; tri; tri = tri->next) - { - for (i = 0; i < 3; i++) - { - if (abs(tri->edges[i]) != abs(edges[0]) && - abs(tri->edges[i]) != abs(edges[1]) && - abs(tri->edges[i]) != abs(edges[2])) break; - } //end for - if (i >= 3) return tri - thworld.triangles; - } //end for - return 0; -} //end of the function TH_FindTriangle -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void TH_AddTriangleToHash(th_triangle_t *tri) -{ - int hashvalue; - - hashvalue = (abs(tri->edges[0]) + abs(tri->edges[1]) + abs(tri->edges[2])) & (TRIANGLEHASH_SIZE-1); - tri->hashnext = thworld.trianglehash[hashvalue]; - thworld.trianglehash[hashvalue] = tri; -} //end of the function TH_AddTriangleToHash -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void TH_CreateTrianglePlanes(int verts[3], th_plane_t *triplane, th_plane_t *planes) -{ - int i; - vec3_t dir; - - for (i = 0; i < 3; i++) - { - VectorSubtract(thworld.vertexes[verts[(i+1)%3]].v, thworld.vertexes[verts[i]].v, dir); - CrossProduct(dir, triplane->normal, planes[i].normal); - VectorNormalize(planes[i].normal); - planes[i].dist = DotProduct(thworld.vertexes[verts[i]].v, planes[i].normal); - } //end for -} //end of the function TH_CreateTrianglePlanes -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int TH_CreateTriangle(int verts[3]) -{ - th_triangle_t *tri; - int i; - - if (thworld.numtriangles == 0) thworld.numtriangles = 1; - if (thworld.numtriangles >= MAX_TH_TRIANGLES) - Error("MAX_TH_TRIANGLES"); - tri = &thworld.triangles[thworld.numtriangles++]; - for (i = 0; i < 3; i++) - { - tri->edges[i] = TH_FindOrCreateEdge(verts[i], verts[(i+1)%3]); - TH_AddEdgeUser(abs(tri->edges[i])); - } //end for - tri->front = 0; - tri->back = 0; - tri->planenum = TH_PlaneFromPoints(verts[0], verts[1], verts[2]); - tri->prev = NULL; - tri->next = NULL; - tri->hashnext = NULL; - TH_CreateTrianglePlanes(verts, &thworld.planes[tri->planenum], tri->planes); - TH_AddTriangleToHash(tri); - ClearBounds(tri->mins, tri->maxs); - for (i = 0; i < 3; i++) - { - AddPointToBounds(thworld.vertexes[verts[i]].v, tri->mins, tri->maxs); - } //end for - return thworld.numtriangles-1; -} //end of the function TH_CreateTriangle -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int TH_CreateTetrahedron(int triangles[4]) -{ - th_tetrahedron_t *tetrahedron; - int i; - - if (thworld.numtetrahedrons == 0) thworld.numtetrahedrons = 1; - if (thworld.numtetrahedrons >= MAX_TH_TETRAHEDRONS) - Error("MAX_TH_TETRAHEDRONS"); - tetrahedron = &thworld.tetrahedrons[thworld.numtetrahedrons++]; - for (i = 0; i < 4; i++) - { - tetrahedron->triangles[i] = triangles[i]; - if (thworld.triangles[abs(triangles[i])].front) - { - thworld.triangles[abs(triangles[i])].back = thworld.numtetrahedrons-1; - } //end if - else - { - thworld.triangles[abs(triangles[i])].front = thworld.numtetrahedrons-1; - } //end else - } //end for - tetrahedron->volume = 0; - return thworld.numtetrahedrons-1; -} //end of the function TH_CreateTetrahedron -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int TH_IntersectTrianglePlanes(int v1, int v2, th_plane_t *triplane, th_plane_t *planes) -{ - float *p1, *p2, front, back, frac, d; - int i, side, lastside; - vec3_t mid; - - p1 = thworld.vertexes[v1].v; - p2 = thworld.vertexes[v2].v; - - front = DotProduct(p1, triplane->normal) - triplane->dist; - back = DotProduct(p2, triplane->normal) - triplane->dist; - //if both points at the same side of the plane - if (front < 0.1 && back < 0.1) return false; - if (front > -0.1 && back > -0.1) return false; - // - frac = front/(front-back); - mid[0] = p1[0] + (p2[0] - p1[0]) * frac; - mid[1] = p1[1] + (p2[1] - p1[1]) * frac; - mid[2] = p1[2] + (p2[2] - p1[2]) * frac; - //if the mid point is at the same side of all the tri bounding planes - lastside = 0; - for (i = 0; i < 3; i++) - { - d = DotProduct(mid, planes[i].normal) - planes[i].dist; - side = d < 0; - if (i && side != lastside) return false; - lastside = side; - } //end for - return true; -} //end of the function TH_IntersectTrianglePlanes -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int TH_OutsideBoundingBox(int v1, int v2, vec3_t mins, vec3_t maxs) -{ - float *p1, *p2; - int i; - - p1 = thworld.vertexes[v1].v; - p2 = thworld.vertexes[v2].v; - //if both points are at the outer side of one of the bounding box planes - for (i = 0; i < 3; i++) - { - if (p1[i] < mins[i] && p2[i] < mins[i]) return true; - if (p1[i] > maxs[i] && p2[i] > maxs[i]) return true; - } //end for - return false; -} //end of the function TH_OutsideBoundingBox -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int TH_TryEdge(int v1, int v2) -{ - int i, j, v; - th_plane_t *plane; - th_triangle_t *tri; - - //if the edge already exists it must be valid - if (TH_FindEdge(v1, v2)) return true; - //test the edge with all existing triangles - for (i = 1; i < thworld.numtriangles; i++) - { - tri = &thworld.triangles[i]; - //if triangle is enclosed by two tetrahedrons we don't have to test it - //because the edge always has to go through another triangle of those - //tetrahedrons first to reach the enclosed triangle - if (tri->front && tri->back) continue; - //if the edges is totally outside the triangle bounding box - if (TH_OutsideBoundingBox(v1, v2, tri->mins, tri->maxs)) continue; - //if one of the edge vertexes is used by this triangle - for (j = 0; j < 3; j++) - { - v = thworld.edges[abs(tri->edges[j])].v[tri->edges[j] < 0]; - if (v == v1 || v == v2) break; - } //end for - if (j < 3) continue; - //get the triangle plane - plane = &thworld.planes[tri->planenum]; - //if the edge intersects with a triangle then it's not valid - if (TH_IntersectTrianglePlanes(v1, v2, plane, tri->planes)) return false; - } //end for - return true; -} //end of the function TH_TryEdge -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int TH_TryTriangle(int verts[3]) -{ - th_plane_t planes[3], triplane; - vec3_t t1, t2; - float *p0, *p1, *p2; - int i, j; - - p0 = thworld.vertexes[verts[0]].v; - p1 = thworld.vertexes[verts[1]].v; - p2 = thworld.vertexes[verts[2]].v; - - VectorSubtract(p0, p1, t1); - VectorSubtract(p2, p1, t2); - CrossProduct(t1, t2, triplane.normal); - VectorNormalize(triplane.normal); - triplane.dist = DotProduct(p0, triplane.normal); - // - TH_CreateTrianglePlanes(verts, &triplane, planes); - //test if any existing edge intersects with this triangle - for (i = 1; i < thworld.numedges; i++) - { - //if the edge is only used by triangles with tetrahedrons at both sides - if (!thworld.edges[i].usercount) continue; - //if one of the triangle vertexes is used by this edge - for (j = 0; j < 3; j++) - { - if (verts[j] == thworld.edges[j].v[0] || - verts[j] == thworld.edges[j].v[1]) break; - } //end for - if (j < 3) continue; - //if this edge intersects with the triangle - if (TH_IntersectTrianglePlanes(thworld.edges[i].v[0], thworld.edges[i].v[1], &triplane, planes)) return false; - } //end for - return true; -} //end of the function TH_TryTriangle -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void TH_AddTriangleToList(th_triangle_t **trianglelist, th_triangle_t *tri) -{ - tri->prev = NULL; - tri->next = *trianglelist; - if (*trianglelist) (*trianglelist)->prev = tri; - *trianglelist = tri; -} //end of the function TH_AddTriangleToList -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void TH_RemoveTriangleFromList(th_triangle_t **trianglelist, th_triangle_t *tri) -{ - if (tri->next) tri->next->prev = tri->prev; - if (tri->prev) tri->prev->next = tri->next; - else *trianglelist = tri->next; -} //end of the function TH_RemoveTriangleFromList -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int TH_FindTetrahedron1(th_triangle_t *tri, int *triangles) -{ - int i, j, edgenum, side, v1, v2, v3, v4; - int verts1[3], verts2[3]; - th_triangle_t *tri2; - - //find another triangle with a shared edge - for (tri2 = tri->next; tri2; tri2 = tri2->next) - { - //if the triangles are in the same plane - if ((tri->planenum & ~1) == (tri2->planenum & ~1)) continue; - //try to find a shared edge - for (i = 0; i < 3; i++) - { - edgenum = abs(tri->edges[i]); - for (j = 0; j < 3; j++) - { - if (edgenum == abs(tri2->edges[j])) break; - } //end for - if (j < 3) break; - } //end for - //if the triangles have a shared edge - if (i < 3) - { - edgenum = tri->edges[(i+1)%3]; - if (edgenum < 0) v1 = thworld.edges[abs(edgenum)].v[0]; - else v1 = thworld.edges[edgenum].v[1]; - edgenum = tri2->edges[(j+1)%3]; - if (edgenum < 0) v2 = thworld.edges[abs(edgenum)].v[0]; - else v2 = thworld.edges[edgenum].v[1]; - //try the new edge - if (TH_TryEdge(v1, v2)) - { - edgenum = tri->edges[i]; - side = edgenum < 0; - //get the vertexes of the shared edge - v3 = thworld.edges[abs(edgenum)].v[side]; - v4 = thworld.edges[abs(edgenum)].v[!side]; - //try the two new triangles - verts1[0] = v1; - verts1[1] = v2; - verts1[2] = v3; - triangles[2] = TH_FindTriangle(verts1); - if (triangles[2] || TH_TryTriangle(verts1)) - { - verts2[0] = v2; - verts2[1] = v1; - verts2[2] = v4; - triangles[3] = TH_FindTriangle(verts2); - if (triangles[3] || TH_TryTriangle(verts2)) - { - triangles[0] = tri - thworld.triangles; - triangles[1] = tri2 - thworld.triangles; - if (!triangles[2]) triangles[2] = TH_CreateTriangle(verts1); - if (!triangles[3]) triangles[3] = TH_CreateTriangle(verts2); - return true; - } //end if - } //end if - } //end if - } //end if - } //end for - return false; -} //end of the function TH_FindTetrahedron -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int TH_FindTetrahedron2(th_triangle_t *tri, int *triangles) -{ - int i, edgenum, v1, verts[3], triverts[3]; - float d; - th_plane_t *plane; - - //get the verts of this triangle - for (i = 0; i < 3; i++) - { - edgenum = tri->edges[i]; - if (edgenum < 0) verts[i] = thworld.edges[abs(edgenum)].v[1]; - else verts[i] = thworld.edges[edgenum].v[0]; - } //end for - // - plane = &thworld.planes[tri->planenum]; - for (v1 = 0; v1 < thworld.numvertexes; v1++) - { - //if the vertex is only used by triangles with tetrahedrons at both sides - if (!thworld.vertexes[v1].usercount) continue; - //check if the vertex is not coplanar with the triangle - d = DotProduct(thworld.vertexes[v1].v, plane->normal) - plane->dist; - if (fabs(d) < 1) continue; - //check if we can create edges from the triangle towards this new vertex - for (i = 0; i < 3; i++) - { - if (v1 == verts[i]) break; - if (!TH_TryEdge(v1, verts[i])) break; - } //end for - if (i < 3) continue; - //check if the triangles are valid - for (i = 0; i < 3; i++) - { - triverts[0] = v1; - triverts[1] = verts[i]; - triverts[2] = verts[(i+1)%3]; - //if the triangle already exists then it is valid - triangles[i] = TH_FindTriangle(triverts); - if (!triangles[i]) - { - if (!TH_TryTriangle(triverts)) break; - } //end if - } //end for - if (i < 3) continue; - //create the tetrahedron triangles using the new vertex - for (i = 0; i < 3; i++) - { - if (!triangles[i]) - { - triverts[0] = v1; - triverts[1] = verts[i]; - triverts[2] = verts[(i+1)%3]; - triangles[i] = TH_CreateTriangle(triverts); - } //end if - } //end for - //add the existing triangle - triangles[3] = tri - thworld.triangles; - // - return true; - } //end for - return false; -} //end of the function TH_FindTetrahedron2 -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void TH_TetrahedralDecomposition(th_triangle_t *triangles) -{ - int i, thtriangles[4], numtriangles; - th_triangle_t *donetriangles, *tri; - - donetriangles = NULL; - - /* - numtriangles = 0; - qprintf("%6d triangles", numtriangles); - for (tri = triangles; tri; tri = triangles) - { - qprintf("\r%6d", numtriangles++); - if (!TH_FindTetrahedron1(tri, thtriangles)) - { -// if (!TH_FindTetrahedron2(tri, thtriangles)) - { -// Error("triangle without tetrahedron"); - TH_RemoveTriangleFromList(&triangles, tri); - continue; - } //end if - } //end if - //create a tetrahedron from the triangles - TH_CreateTetrahedron(thtriangles); - // - for (i = 0; i < 4; i++) - { - if (thworld.triangles[abs(thtriangles[i])].front && - thworld.triangles[abs(thtriangles[i])].back) - { - TH_RemoveTriangleFromList(&triangles, &thworld.triangles[abs(thtriangles[i])]); - TH_AddTriangleToList(&donetriangles, &thworld.triangles[abs(thtriangles[i])]); - TH_FreeTriangleEdges(&thworld.triangles[abs(thtriangles[i])]); - } //end if - else - { - TH_AddTriangleToList(&triangles, &thworld.triangles[abs(thtriangles[i])]); - } //end else - } //end for - } //end for*/ - qprintf("%6d tetrahedrons", thworld.numtetrahedrons); - do - { - do - { - numtriangles = 0; - for (i = 1; i < thworld.numtriangles; i++) - { - tri = &thworld.triangles[i]; - if (tri->front && tri->back) continue; - //qprintf("\r%6d", numtriangles++); - if (!TH_FindTetrahedron1(tri, thtriangles)) - { -// if (!TH_FindTetrahedron2(tri, thtriangles)) - { - continue; - } //end if - } //end if - numtriangles++; - //create a tetrahedron from the triangles - TH_CreateTetrahedron(thtriangles); - qprintf("\r%6d", thworld.numtetrahedrons); - } //end for - } while(numtriangles); - for (i = 1; i < thworld.numtriangles; i++) - { - tri = &thworld.triangles[i]; - if (tri->front && tri->back) continue; - //qprintf("\r%6d", numtriangles++); -// if (!TH_FindTetrahedron1(tri, thtriangles)) - { - if (!TH_FindTetrahedron2(tri, thtriangles)) - { - continue; - } //end if - } //end if - numtriangles++; - //create a tetrahedron from the triangles - TH_CreateTetrahedron(thtriangles); - qprintf("\r%6d", thworld.numtetrahedrons); - } //end for - } while(numtriangles); - // - numtriangles = 0; - for (i = 1; i < thworld.numtriangles; i++) - { - tri = &thworld.triangles[i]; - if (!tri->front && !tri->back) numtriangles++; - } //end for - Log_Print("\r%6d triangles with front only\n", numtriangles); - Log_Print("\r%6d tetrahedrons\n", thworld.numtetrahedrons-1); -} //end of the function TH_TetrahedralDecomposition -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void TH_AASFaceVertex(aas_face_t *face, int index, vec3_t vertex) -{ - int edgenum, side; - - edgenum = aasworld.edgeindex[face->firstedge + index]; - side = edgenum < 0; - VectorCopy(aasworld.vertexes[aasworld.edges[abs(edgenum)].v[side]], vertex); -} //end of the function TH_AASFaceVertex -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int TH_Colinear(float *v0, float *v1, float *v2) -{ - vec3_t t1, t2, vcross; - float d; - - VectorSubtract(v1, v0, t1); - VectorSubtract(v2, v0, t2); - CrossProduct (t1, t2, vcross); - d = VectorLength( vcross ); - - // if cross product is zero point is colinear - if (d < 10) - { - return true; - } //end if - return false; -} //end of the function TH_Colinear -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void TH_FaceCenter(aas_face_t *face, vec3_t center) -{ - int i, edgenum, side; - aas_edge_t *edge; - - VectorClear(center); - for (i = 0; i < face->numedges; i++) - { - edgenum = abs(aasworld.edgeindex[face->firstedge + i]); - side = edgenum < 0; - edge = &aasworld.edges[abs(edgenum)]; - VectorAdd(aasworld.vertexes[edge->v[side]], center, center); - } //end for - VectorScale(center, 1.0 / face->numedges, center); -} //end of the function TH_FaceCenter -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -th_triangle_t *TH_CreateAASFaceTriangles(aas_face_t *face) -{ - int i, first, verts[3], trinum; - vec3_t p0, p1, p2, p3, p4, center; - th_triangle_t *tri, *triangles; - - triangles = NULL; - //find three points that are not colinear - for (i = 0; i < face->numedges; i++) - { - TH_AASFaceVertex(face, (face->numedges + i-2)%face->numedges, p0); - TH_AASFaceVertex(face, (face->numedges + i-1)%face->numedges, p1); - TH_AASFaceVertex(face, (i )%face->numedges, p2); - if (TH_Colinear(p2, p0, p1)) continue; - TH_AASFaceVertex(face, (i+1)%face->numedges, p3); - TH_AASFaceVertex(face, (i+2)%face->numedges, p4); - if (TH_Colinear(p2, p3, p4)) continue; - break; - } //end for - //if there are three points that are not colinear - if (i < face->numedges) - { - //normal triangulation - first = i; //left and right most point of three non-colinear points - TH_AASFaceVertex(face, first, p0); - verts[0] = TH_FindOrCreateVertex(p0); - for (i = 1; i < face->numedges-1; i++) - { - TH_AASFaceVertex(face, (first+i )%face->numedges, p1); - TH_AASFaceVertex(face, (first+i+1)%face->numedges, p2); - verts[1] = TH_FindOrCreateVertex(p1); - verts[2] = TH_FindOrCreateVertex(p2); - trinum = TH_CreateTriangle(verts); - tri = &thworld.triangles[trinum]; - tri->front = -1; - TH_AddTriangleToList(&triangles, tri); - } //end for - } //end if - else - { - //fan triangulation - TH_FaceCenter(face, center); - // - verts[0] = TH_FindOrCreateVertex(center); - for (i = 0; i < face->numedges; i++) - { - TH_AASFaceVertex(face, (i )%face->numedges, p1); - TH_AASFaceVertex(face, (i+1)%face->numedges, p2); - if (TH_Colinear(center, p1, p2)) continue; - verts[1] = TH_FindOrCreateVertex(p1); - verts[2] = TH_FindOrCreateVertex(p2); - trinum = TH_CreateTriangle(verts); - tri = &thworld.triangles[trinum]; - tri->front = -1; - TH_AddTriangleToList(&triangles, tri); - } //end for - } //end else - return triangles; -} //end of the function TH_CreateAASFaceTriangles -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -th_triangle_t *TH_AASToTriangleMesh(void) -{ - int i, j, facenum, otherareanum; - aas_face_t *face; - th_triangle_t *tri, *nexttri, *triangles; - - triangles = NULL; - for (i = 1; i < aasworld.numareas; i++) - { - //if (!(aasworld.areasettings[i].presencetype & PRESENCE_NORMAL)) continue; - for (j = 0; j < aasworld.areas[i].numfaces; j++) - { - facenum = abs(aasworld.faceindex[aasworld.areas[i].firstface + j]); - face = &aasworld.faces[facenum]; - //only convert solid faces into triangles - if (!(face->faceflags & FACE_SOLID)) - { - /* - if (face->frontarea == i) otherareanum = face->backarea; - else otherareanum = face->frontarea; - if (aasworld.areasettings[otherareanum].presencetype & PRESENCE_NORMAL) continue; - */ - continue; - } //end if - // - tri = TH_CreateAASFaceTriangles(face); - for (; tri; tri = nexttri) - { - nexttri = tri->next; - TH_AddTriangleToList(&triangles, tri); - } //end for - } //end if - } //end for - return triangles; -} //end of the function TH_AASToTriangleMesh -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void TH_AASToTetrahedrons(char *filename) -{ - th_triangle_t *triangles, *tri, *lasttri; - int cnt; - - if (!AAS_LoadAASFile(filename, 0, 0)) - Error("couldn't load %s\n", filename); - - // - TH_InitMaxTH(); - //create a triangle mesh from the solid faces in the AAS file - triangles = TH_AASToTriangleMesh(); - // - cnt = 0; - lasttri = NULL; - for (tri = triangles; tri; tri = tri->next) - { - cnt++; - if (tri->prev != lasttri) Log_Print("BAH\n"); - lasttri = tri; - } //end for - Log_Print("%6d triangles\n", cnt); - //create a tetrahedral decomposition of the world bounded by triangles - TH_TetrahedralDecomposition(triangles); - // - TH_FreeMaxTH(); -} //end of the function TH_AASToTetrahedrons +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "qbsp.h" +#include "l_mem.h" +#include "../botlib/aasfile.h" +#include "aas_store.h" +#include "aas_cfg.h" +#include "aas_file.h" + +// +// creating tetrahedrons from a arbitrary world bounded by triangles +// +// a triangle has 3 corners and 3 edges +// a tetrahedron is build out of 4 triangles +// a tetrahedron has 6 edges +// we start with a world bounded by triangles, a side of a triangle facing +// towards the oudside of the world is marked as part of tetrahedron -1 +// +// a tetrahedron is defined by two non-coplanar triangles with a shared edge +// +// a tetrahedron is defined by one triangle and a vertex not in the triangle plane +// +// if all triangles using a specific vertex have tetrahedrons +// at both sides then this vertex will never be part of a new tetrahedron +// +// if all triangles using a specific edge have tetrahedrons +// at both sides then this vertex will never be part of a new tetrahedron +// +// each triangle can only be shared by two tetrahedrons +// when all triangles have tetrahedrons at both sides then we're done +// +// if we cannot create any new tetrahedrons and there is at least one triangle +// which has a tetrahedron only at one side then the world leaks +// + +#define Sign(x) (x < 0 ? 1 : 0) + +#define MAX_TH_VERTEXES 128000 +#define MAX_TH_PLANES 128000 +#define MAX_TH_EDGES 512000 +#define MAX_TH_TRIANGLES 51200 +#define MAX_TH_TETRAHEDRONS 12800 + +#define PLANEHASH_SIZE 1024 +#define EDGEHASH_SIZE 1024 +#define TRIANGLEHASH_SIZE 1024 +#define VERTEXHASH_SHIFT 7 +#define VERTEXHASH_SIZE ((MAX_MAP_BOUNDS>>(VERTEXHASH_SHIFT-1))+1) //was 64 + +#define NORMAL_EPSILON 0.0001 +#define DIST_EPSILON 0.1 +#define VERTEX_EPSILON 0.01 +#define INTEGRAL_EPSILON 0.01 + + +//plane +typedef struct th_plane_s +{ + vec3_t normal; + float dist; + int type; + int signbits; + struct th_plane_s *hashnext; //next plane in hash +} th_plane_t; + +//vertex +typedef struct th_vertex_s +{ + vec3_t v; + int usercount; //2x the number of to be processed + //triangles using this vertex + struct th_vertex_s *hashnext; //next vertex in hash +} th_vertex_t; + +//edge +typedef struct th_edge_s +{ + int v[2]; //vertex indexes + int usercount; //number of to be processed + //triangles using this edge + struct th_edge_s *hashnext; //next edge in hash +} th_edge_t; + +//triangle +typedef struct th_triangle_s +{ + int edges[3]; //negative if edge is flipped + th_plane_t planes[3]; //triangle bounding planes + int planenum; //plane the triangle is in + int front; //tetrahedron at the front + int back; //tetrahedron at the back + vec3_t mins, maxs; //triangle bounding box + struct th_triangle_s *prev, *next; //links in linked triangle lists + struct th_triangle_s *hashnext; //next triangle in hash +} th_triangle_t; + +//tetrahedron +typedef struct th_tetrahedron_s +{ + int triangles[4]; //negative if at backside of triangle + float volume; //tetrahedron volume +} th_tetrahedron_t; + +typedef struct th_s +{ + //vertexes + int numvertexes; + th_vertex_t *vertexes; + th_vertex_t *vertexhash[VERTEXHASH_SIZE * VERTEXHASH_SIZE]; + //planes + int numplanes; + th_plane_t *planes; + th_plane_t *planehash[PLANEHASH_SIZE]; + //edges + int numedges; + th_edge_t *edges; + th_edge_t *edgehash[EDGEHASH_SIZE]; + //triangles + int numtriangles; + th_triangle_t *triangles; + th_triangle_t *trianglehash[TRIANGLEHASH_SIZE]; + //tetrahedrons + int numtetrahedrons; + th_tetrahedron_t *tetrahedrons; +} th_t; + +th_t thworld; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void TH_InitMaxTH(void) +{ + //get memory for the tetrahedron data + thworld.vertexes = (th_vertex_t *) GetClearedMemory(MAX_TH_VERTEXES * sizeof(th_vertex_t)); + thworld.planes = (th_plane_t *) GetClearedMemory(MAX_TH_PLANES * sizeof(th_plane_t)); + thworld.edges = (th_edge_t *) GetClearedMemory(MAX_TH_EDGES * sizeof(th_edge_t)); + thworld.triangles = (th_triangle_t *) GetClearedMemory(MAX_TH_TRIANGLES * sizeof(th_triangle_t)); + thworld.tetrahedrons = (th_tetrahedron_t *) GetClearedMemory(MAX_TH_TETRAHEDRONS * sizeof(th_tetrahedron_t)); + //reset the hash tables + memset(thworld.vertexhash, 0, VERTEXHASH_SIZE * sizeof(th_vertex_t *)); + memset(thworld.planehash, 0, PLANEHASH_SIZE * sizeof(th_plane_t *)); + memset(thworld.edgehash, 0, EDGEHASH_SIZE * sizeof(th_edge_t *)); + memset(thworld.trianglehash, 0, TRIANGLEHASH_SIZE * sizeof(th_triangle_t *)); +} //end of the function TH_InitMaxTH +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void TH_FreeMaxTH(void) +{ + if (thworld.vertexes) FreeMemory(thworld.vertexes); + thworld.vertexes = NULL; + thworld.numvertexes = 0; + if (thworld.planes) FreeMemory(thworld.planes); + thworld.planes = NULL; + thworld.numplanes = 0; + if (thworld.edges) FreeMemory(thworld.edges); + thworld.edges = NULL; + thworld.numedges = 0; + if (thworld.triangles) FreeMemory(thworld.triangles); + thworld.triangles = NULL; + thworld.numtriangles = 0; + if (thworld.tetrahedrons) FreeMemory(thworld.tetrahedrons); + thworld.tetrahedrons = NULL; + thworld.numtetrahedrons = 0; +} //end of the function TH_FreeMaxTH +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float TH_TriangleArea(th_triangle_t *tri) +{ + return 0; +} //end of the function TH_TriangleArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float TH_TetrahedronVolume(th_tetrahedron_t *tetrahedron) +{ + int edgenum, verts[3], i, j, v2; + float volume, d; + th_triangle_t *tri, *tri2; + th_plane_t *plane; + + tri = &thworld.triangles[abs(tetrahedron->triangles[0])]; + for (i = 0; i < 3; i++) + { + edgenum = tri->edges[i]; + if (edgenum < 0) verts[i] = thworld.edges[abs(edgenum)].v[1]; + else verts[i] = thworld.edges[edgenum].v[0]; + } //end for + // + tri2 = &thworld.triangles[abs(tetrahedron->triangles[1])]; + for (j = 0; j < 3; j++) + { + edgenum = tri2->edges[i]; + if (edgenum < 0) v2 = thworld.edges[abs(edgenum)].v[1]; + else v2 = thworld.edges[edgenum].v[0]; + if (v2 != verts[0] && + v2 != verts[1] && + v2 != verts[2]) break; + } //end for + + plane = &thworld.planes[tri->planenum]; + d = -(DotProduct (thworld.vertexes[v2].v, plane->normal) - plane->dist); + volume = TH_TriangleArea(tri) * d / 3; + return volume; +} //end of the function TH_TetrahedronVolume +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int TH_PlaneSignBits(vec3_t normal) +{ + int i, signbits; + + signbits = 0; + for (i = 2; i >= 0; i--) + { + signbits = (signbits << 1) + Sign(normal[i]); + } //end for + return signbits; +} //end of the function TH_PlaneSignBits +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int TH_PlaneTypeForNormal(vec3_t normal) +{ + vec_t ax, ay, az; + +// NOTE: should these have an epsilon around 1.0? + if (normal[0] == 1.0 || normal[0] == -1.0) + return PLANE_X; + if (normal[1] == 1.0 || normal[1] == -1.0) + return PLANE_Y; + if (normal[2] == 1.0 || normal[2] == -1.0) + return PLANE_Z; + + ax = fabs(normal[0]); + ay = fabs(normal[1]); + az = fabs(normal[2]); + + if (ax >= ay && ax >= az) + return PLANE_ANYX; + if (ay >= ax && ay >= az) + return PLANE_ANYY; + return PLANE_ANYZ; +} //end of the function TH_PlaneTypeForNormal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean TH_PlaneEqual(th_plane_t *p, vec3_t normal, vec_t dist) +{ + if ( + fabs(p->normal[0] - normal[0]) < NORMAL_EPSILON + && fabs(p->normal[1] - normal[1]) < NORMAL_EPSILON + && fabs(p->normal[2] - normal[2]) < NORMAL_EPSILON + && fabs(p->dist - dist) < DIST_EPSILON ) + return true; + return false; +} //end of the function TH_PlaneEqual +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void TH_AddPlaneToHash(th_plane_t *p) +{ + int hash; + + hash = (int)fabs(p->dist) / 8; + hash &= (PLANEHASH_SIZE-1); + + p->hashnext = thworld.planehash[hash]; + thworld.planehash[hash] = p; +} //end of the function TH_AddPlaneToHash +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int TH_CreateFloatPlane(vec3_t normal, vec_t dist) +{ + th_plane_t *p, temp; + + if (VectorLength(normal) < 0.5) + Error ("FloatPlane: bad normal"); + // create a new plane + if (thworld.numplanes+2 > MAX_TH_PLANES) + Error ("MAX_TH_PLANES"); + + p = &thworld.planes[thworld.numplanes]; + VectorCopy (normal, p->normal); + p->dist = dist; + p->type = (p+1)->type = TH_PlaneTypeForNormal (p->normal); + p->signbits = TH_PlaneSignBits(p->normal); + + VectorSubtract (vec3_origin, normal, (p+1)->normal); + (p+1)->dist = -dist; + (p+1)->signbits = TH_PlaneSignBits((p+1)->normal); + + thworld.numplanes += 2; + + // allways put axial planes facing positive first + if (p->type < 3) + { + if (p->normal[0] < 0 || p->normal[1] < 0 || p->normal[2] < 0) + { + // flip order + temp = *p; + *p = *(p+1); + *(p+1) = temp; + + TH_AddPlaneToHash(p); + TH_AddPlaneToHash(p+1); + return thworld.numplanes - 1; + } //end if + } //end if + + TH_AddPlaneToHash(p); + TH_AddPlaneToHash(p+1); + return thworld.numplanes - 2; +} //end of the function TH_CreateFloatPlane +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void TH_SnapVector(vec3_t normal) +{ + int i; + + for (i = 0; i < 3; i++) + { + if ( fabs(normal[i] - 1) < NORMAL_EPSILON ) + { + VectorClear (normal); + normal[i] = 1; + break; + } //end if + if ( fabs(normal[i] - -1) < NORMAL_EPSILON ) + { + VectorClear (normal); + normal[i] = -1; + break; + } //end if + } //end for +} //end of the function TH_SnapVector +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void TH_SnapPlane(vec3_t normal, vec_t *dist) +{ + TH_SnapVector(normal); + + if (fabs(*dist-Q_rint(*dist)) < DIST_EPSILON) + *dist = Q_rint(*dist); +} //end of the function TH_SnapPlane +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int TH_FindFloatPlane(vec3_t normal, vec_t dist) +{ + int i; + th_plane_t *p; + int hash, h; + + TH_SnapPlane (normal, &dist); + hash = (int)fabs(dist) / 8; + hash &= (PLANEHASH_SIZE-1); + + // search the border bins as well + for (i = -1; i <= 1; i++) + { + h = (hash+i)&(PLANEHASH_SIZE-1); + for (p = thworld.planehash[h]; p; p = p->hashnext) + { + if (TH_PlaneEqual(p, normal, dist)) + { + return p - thworld.planes; + } //end if + } //end for + } //end for + return TH_CreateFloatPlane(normal, dist); +} //end of the function TH_FindFloatPlane +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int TH_PlaneFromPoints(int v1, int v2, int v3) +{ + vec3_t t1, t2, normal; + vec_t dist; + float *p0, *p1, *p2; + + p0 = thworld.vertexes[v1].v; + p1 = thworld.vertexes[v2].v; + p2 = thworld.vertexes[v3].v; + + VectorSubtract(p0, p1, t1); + VectorSubtract(p2, p1, t2); + CrossProduct(t1, t2, normal); + VectorNormalize(normal); + + dist = DotProduct(p0, normal); + + return TH_FindFloatPlane(normal, dist); +} //end of the function TH_PlaneFromPoints +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void TH_AddEdgeUser(int edgenum) +{ + th_edge_t *edge; + + edge = &thworld.edges[abs(edgenum)]; + //increase edge user count + edge->usercount++; + //increase vertex user count as well + thworld.vertexes[edge->v[0]].usercount++; + thworld.vertexes[edge->v[1]].usercount++; +} //end of the function TH_AddEdgeUser +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void TH_RemoveEdgeUser(int edgenum) +{ + th_edge_t *edge; + + edge = &thworld.edges[abs(edgenum)]; + //decrease edge user count + edge->usercount--; + //decrease vertex user count as well + thworld.vertexes[edge->v[0]].usercount--; + thworld.vertexes[edge->v[1]].usercount--; +} //end of the function TH_RemoveEdgeUser +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void TH_FreeTriangleEdges(th_triangle_t *tri) +{ + int i; + + for (i = 0; i < 3; i++) + { + TH_RemoveEdgeUser(abs(tri->edges[i])); + } //end for +} //end of the function TH_FreeTriangleEdges +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +unsigned TH_HashVec(vec3_t vec) +{ + int x, y; + + x = (MAX_MAP_BOUNDS + (int)(vec[0]+0.5)) >> VERTEXHASH_SHIFT; + y = (MAX_MAP_BOUNDS + (int)(vec[1]+0.5)) >> VERTEXHASH_SHIFT; + + if (x < 0 || x >= VERTEXHASH_SIZE || y < 0 || y >= VERTEXHASH_SIZE) + Error("HashVec: point %f %f %f outside valid range", vec[0], vec[1], vec[2]); + + return y*VERTEXHASH_SIZE + x; +} //end of the function TH_HashVec +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int TH_FindVertex(vec3_t v) +{ + int i, h; + th_vertex_t *vertex; + vec3_t vert; + + for (i = 0; i < 3; i++) + { + if ( fabs(v[i] - Q_rint(v[i])) < INTEGRAL_EPSILON) + vert[i] = Q_rint(v[i]); + else + vert[i] = v[i]; + } //end for + + h = TH_HashVec(vert); + + for (vertex = thworld.vertexhash[h]; vertex; vertex = vertex->hashnext) + { + if (fabs(vertex->v[0] - vert[0]) < VERTEX_EPSILON && + fabs(vertex->v[1] - vert[1]) < VERTEX_EPSILON && + fabs(vertex->v[2] - vert[2]) < VERTEX_EPSILON) + { + return vertex - thworld.vertexes; + } //end if + } //end for + return 0; +} //end of the function TH_FindVertex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void TH_AddVertexToHash(th_vertex_t *vertex) +{ + int hashvalue; + + hashvalue = TH_HashVec(vertex->v); + vertex->hashnext = thworld.vertexhash[hashvalue]; + thworld.vertexhash[hashvalue] = vertex; +} //end of the function TH_AddVertexToHash +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int TH_CreateVertex(vec3_t v) +{ + if (thworld.numvertexes == 0) thworld.numvertexes = 1; + if (thworld.numvertexes >= MAX_TH_VERTEXES) + Error("MAX_TH_VERTEXES"); + VectorCopy(v, thworld.vertexes[thworld.numvertexes].v); + thworld.vertexes[thworld.numvertexes].usercount = 0; + TH_AddVertexToHash(&thworld.vertexes[thworld.numvertexes]); + thworld.numvertexes++; + return thworld.numvertexes-1; +} //end of the function TH_CreateVertex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int TH_FindOrCreateVertex(vec3_t v) +{ + int vertexnum; + + vertexnum = TH_FindVertex(v); + if (!vertexnum) vertexnum = TH_CreateVertex(v); + return vertexnum; +} //end of the function TH_FindOrCreateVertex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int TH_FindEdge(int v1, int v2) +{ + int hashvalue; + th_edge_t *edge; + + hashvalue = (v1 + v2) & (EDGEHASH_SIZE-1); + + for (edge = thworld.edgehash[hashvalue]; edge; edge = edge->hashnext) + { + if (edge->v[0] == v1 && edge->v[1] == v2) return edge - thworld.edges; + if (edge->v[1] == v1 && edge->v[0] == v2) return -(edge - thworld.edges); + } //end for + return 0; +} //end of the function TH_FindEdge +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void TH_AddEdgeToHash(th_edge_t *edge) +{ + int hashvalue; + + hashvalue = (edge->v[0] + edge->v[1]) & (EDGEHASH_SIZE-1); + edge->hashnext = thworld.edgehash[hashvalue]; + thworld.edgehash[hashvalue] = edge; +} //end of the function TH_AddEdgeToHash +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int TH_CreateEdge(int v1, int v2) +{ + th_edge_t *edge; + + if (thworld.numedges == 0) thworld.numedges = 1; + if (thworld.numedges >= MAX_TH_EDGES) + Error("MAX_TH_EDGES"); + edge = &thworld.edges[thworld.numedges++]; + edge->v[0] = v1; + edge->v[1] = v2; + TH_AddEdgeToHash(edge); + return thworld.numedges-1; +} //end of the function TH_CreateEdge +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int TH_FindOrCreateEdge(int v1, int v2) +{ + int edgenum; + + edgenum = TH_FindEdge(v1, v2); + if (!edgenum) edgenum = TH_CreateEdge(v1, v2); + return edgenum; +} //end of the function TH_FindOrCreateEdge +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int TH_FindTriangle(int verts[3]) +{ + int i, hashvalue, edges[3]; + th_triangle_t *tri; + + for (i = 0; i < 3; i++) + { + edges[i] = TH_FindEdge(verts[i], verts[(i+1)%3]); + if (!edges[i]) return false; + } //end for + hashvalue = (abs(edges[0]) + abs(edges[1]) + abs(edges[2])) & (TRIANGLEHASH_SIZE-1); + for (tri = thworld.trianglehash[hashvalue]; tri; tri = tri->next) + { + for (i = 0; i < 3; i++) + { + if (abs(tri->edges[i]) != abs(edges[0]) && + abs(tri->edges[i]) != abs(edges[1]) && + abs(tri->edges[i]) != abs(edges[2])) break; + } //end for + if (i >= 3) return tri - thworld.triangles; + } //end for + return 0; +} //end of the function TH_FindTriangle +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void TH_AddTriangleToHash(th_triangle_t *tri) +{ + int hashvalue; + + hashvalue = (abs(tri->edges[0]) + abs(tri->edges[1]) + abs(tri->edges[2])) & (TRIANGLEHASH_SIZE-1); + tri->hashnext = thworld.trianglehash[hashvalue]; + thworld.trianglehash[hashvalue] = tri; +} //end of the function TH_AddTriangleToHash +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void TH_CreateTrianglePlanes(int verts[3], th_plane_t *triplane, th_plane_t *planes) +{ + int i; + vec3_t dir; + + for (i = 0; i < 3; i++) + { + VectorSubtract(thworld.vertexes[verts[(i+1)%3]].v, thworld.vertexes[verts[i]].v, dir); + CrossProduct(dir, triplane->normal, planes[i].normal); + VectorNormalize(planes[i].normal); + planes[i].dist = DotProduct(thworld.vertexes[verts[i]].v, planes[i].normal); + } //end for +} //end of the function TH_CreateTrianglePlanes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int TH_CreateTriangle(int verts[3]) +{ + th_triangle_t *tri; + int i; + + if (thworld.numtriangles == 0) thworld.numtriangles = 1; + if (thworld.numtriangles >= MAX_TH_TRIANGLES) + Error("MAX_TH_TRIANGLES"); + tri = &thworld.triangles[thworld.numtriangles++]; + for (i = 0; i < 3; i++) + { + tri->edges[i] = TH_FindOrCreateEdge(verts[i], verts[(i+1)%3]); + TH_AddEdgeUser(abs(tri->edges[i])); + } //end for + tri->front = 0; + tri->back = 0; + tri->planenum = TH_PlaneFromPoints(verts[0], verts[1], verts[2]); + tri->prev = NULL; + tri->next = NULL; + tri->hashnext = NULL; + TH_CreateTrianglePlanes(verts, &thworld.planes[tri->planenum], tri->planes); + TH_AddTriangleToHash(tri); + ClearBounds(tri->mins, tri->maxs); + for (i = 0; i < 3; i++) + { + AddPointToBounds(thworld.vertexes[verts[i]].v, tri->mins, tri->maxs); + } //end for + return thworld.numtriangles-1; +} //end of the function TH_CreateTriangle +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int TH_CreateTetrahedron(int triangles[4]) +{ + th_tetrahedron_t *tetrahedron; + int i; + + if (thworld.numtetrahedrons == 0) thworld.numtetrahedrons = 1; + if (thworld.numtetrahedrons >= MAX_TH_TETRAHEDRONS) + Error("MAX_TH_TETRAHEDRONS"); + tetrahedron = &thworld.tetrahedrons[thworld.numtetrahedrons++]; + for (i = 0; i < 4; i++) + { + tetrahedron->triangles[i] = triangles[i]; + if (thworld.triangles[abs(triangles[i])].front) + { + thworld.triangles[abs(triangles[i])].back = thworld.numtetrahedrons-1; + } //end if + else + { + thworld.triangles[abs(triangles[i])].front = thworld.numtetrahedrons-1; + } //end else + } //end for + tetrahedron->volume = 0; + return thworld.numtetrahedrons-1; +} //end of the function TH_CreateTetrahedron +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int TH_IntersectTrianglePlanes(int v1, int v2, th_plane_t *triplane, th_plane_t *planes) +{ + float *p1, *p2, front, back, frac, d; + int i, side, lastside; + vec3_t mid; + + p1 = thworld.vertexes[v1].v; + p2 = thworld.vertexes[v2].v; + + front = DotProduct(p1, triplane->normal) - triplane->dist; + back = DotProduct(p2, triplane->normal) - triplane->dist; + //if both points at the same side of the plane + if (front < 0.1 && back < 0.1) return false; + if (front > -0.1 && back > -0.1) return false; + // + frac = front/(front-back); + mid[0] = p1[0] + (p2[0] - p1[0]) * frac; + mid[1] = p1[1] + (p2[1] - p1[1]) * frac; + mid[2] = p1[2] + (p2[2] - p1[2]) * frac; + //if the mid point is at the same side of all the tri bounding planes + lastside = 0; + for (i = 0; i < 3; i++) + { + d = DotProduct(mid, planes[i].normal) - planes[i].dist; + side = d < 0; + if (i && side != lastside) return false; + lastside = side; + } //end for + return true; +} //end of the function TH_IntersectTrianglePlanes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int TH_OutsideBoundingBox(int v1, int v2, vec3_t mins, vec3_t maxs) +{ + float *p1, *p2; + int i; + + p1 = thworld.vertexes[v1].v; + p2 = thworld.vertexes[v2].v; + //if both points are at the outer side of one of the bounding box planes + for (i = 0; i < 3; i++) + { + if (p1[i] < mins[i] && p2[i] < mins[i]) return true; + if (p1[i] > maxs[i] && p2[i] > maxs[i]) return true; + } //end for + return false; +} //end of the function TH_OutsideBoundingBox +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int TH_TryEdge(int v1, int v2) +{ + int i, j, v; + th_plane_t *plane; + th_triangle_t *tri; + + //if the edge already exists it must be valid + if (TH_FindEdge(v1, v2)) return true; + //test the edge with all existing triangles + for (i = 1; i < thworld.numtriangles; i++) + { + tri = &thworld.triangles[i]; + //if triangle is enclosed by two tetrahedrons we don't have to test it + //because the edge always has to go through another triangle of those + //tetrahedrons first to reach the enclosed triangle + if (tri->front && tri->back) continue; + //if the edges is totally outside the triangle bounding box + if (TH_OutsideBoundingBox(v1, v2, tri->mins, tri->maxs)) continue; + //if one of the edge vertexes is used by this triangle + for (j = 0; j < 3; j++) + { + v = thworld.edges[abs(tri->edges[j])].v[tri->edges[j] < 0]; + if (v == v1 || v == v2) break; + } //end for + if (j < 3) continue; + //get the triangle plane + plane = &thworld.planes[tri->planenum]; + //if the edge intersects with a triangle then it's not valid + if (TH_IntersectTrianglePlanes(v1, v2, plane, tri->planes)) return false; + } //end for + return true; +} //end of the function TH_TryEdge +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int TH_TryTriangle(int verts[3]) +{ + th_plane_t planes[3], triplane; + vec3_t t1, t2; + float *p0, *p1, *p2; + int i, j; + + p0 = thworld.vertexes[verts[0]].v; + p1 = thworld.vertexes[verts[1]].v; + p2 = thworld.vertexes[verts[2]].v; + + VectorSubtract(p0, p1, t1); + VectorSubtract(p2, p1, t2); + CrossProduct(t1, t2, triplane.normal); + VectorNormalize(triplane.normal); + triplane.dist = DotProduct(p0, triplane.normal); + // + TH_CreateTrianglePlanes(verts, &triplane, planes); + //test if any existing edge intersects with this triangle + for (i = 1; i < thworld.numedges; i++) + { + //if the edge is only used by triangles with tetrahedrons at both sides + if (!thworld.edges[i].usercount) continue; + //if one of the triangle vertexes is used by this edge + for (j = 0; j < 3; j++) + { + if (verts[j] == thworld.edges[j].v[0] || + verts[j] == thworld.edges[j].v[1]) break; + } //end for + if (j < 3) continue; + //if this edge intersects with the triangle + if (TH_IntersectTrianglePlanes(thworld.edges[i].v[0], thworld.edges[i].v[1], &triplane, planes)) return false; + } //end for + return true; +} //end of the function TH_TryTriangle +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void TH_AddTriangleToList(th_triangle_t **trianglelist, th_triangle_t *tri) +{ + tri->prev = NULL; + tri->next = *trianglelist; + if (*trianglelist) (*trianglelist)->prev = tri; + *trianglelist = tri; +} //end of the function TH_AddTriangleToList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void TH_RemoveTriangleFromList(th_triangle_t **trianglelist, th_triangle_t *tri) +{ + if (tri->next) tri->next->prev = tri->prev; + if (tri->prev) tri->prev->next = tri->next; + else *trianglelist = tri->next; +} //end of the function TH_RemoveTriangleFromList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int TH_FindTetrahedron1(th_triangle_t *tri, int *triangles) +{ + int i, j, edgenum, side, v1, v2, v3, v4; + int verts1[3], verts2[3]; + th_triangle_t *tri2; + + //find another triangle with a shared edge + for (tri2 = tri->next; tri2; tri2 = tri2->next) + { + //if the triangles are in the same plane + if ((tri->planenum & ~1) == (tri2->planenum & ~1)) continue; + //try to find a shared edge + for (i = 0; i < 3; i++) + { + edgenum = abs(tri->edges[i]); + for (j = 0; j < 3; j++) + { + if (edgenum == abs(tri2->edges[j])) break; + } //end for + if (j < 3) break; + } //end for + //if the triangles have a shared edge + if (i < 3) + { + edgenum = tri->edges[(i+1)%3]; + if (edgenum < 0) v1 = thworld.edges[abs(edgenum)].v[0]; + else v1 = thworld.edges[edgenum].v[1]; + edgenum = tri2->edges[(j+1)%3]; + if (edgenum < 0) v2 = thworld.edges[abs(edgenum)].v[0]; + else v2 = thworld.edges[edgenum].v[1]; + //try the new edge + if (TH_TryEdge(v1, v2)) + { + edgenum = tri->edges[i]; + side = edgenum < 0; + //get the vertexes of the shared edge + v3 = thworld.edges[abs(edgenum)].v[side]; + v4 = thworld.edges[abs(edgenum)].v[!side]; + //try the two new triangles + verts1[0] = v1; + verts1[1] = v2; + verts1[2] = v3; + triangles[2] = TH_FindTriangle(verts1); + if (triangles[2] || TH_TryTriangle(verts1)) + { + verts2[0] = v2; + verts2[1] = v1; + verts2[2] = v4; + triangles[3] = TH_FindTriangle(verts2); + if (triangles[3] || TH_TryTriangle(verts2)) + { + triangles[0] = tri - thworld.triangles; + triangles[1] = tri2 - thworld.triangles; + if (!triangles[2]) triangles[2] = TH_CreateTriangle(verts1); + if (!triangles[3]) triangles[3] = TH_CreateTriangle(verts2); + return true; + } //end if + } //end if + } //end if + } //end if + } //end for + return false; +} //end of the function TH_FindTetrahedron +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int TH_FindTetrahedron2(th_triangle_t *tri, int *triangles) +{ + int i, edgenum, v1, verts[3], triverts[3]; + float d; + th_plane_t *plane; + + //get the verts of this triangle + for (i = 0; i < 3; i++) + { + edgenum = tri->edges[i]; + if (edgenum < 0) verts[i] = thworld.edges[abs(edgenum)].v[1]; + else verts[i] = thworld.edges[edgenum].v[0]; + } //end for + // + plane = &thworld.planes[tri->planenum]; + for (v1 = 0; v1 < thworld.numvertexes; v1++) + { + //if the vertex is only used by triangles with tetrahedrons at both sides + if (!thworld.vertexes[v1].usercount) continue; + //check if the vertex is not coplanar with the triangle + d = DotProduct(thworld.vertexes[v1].v, plane->normal) - plane->dist; + if (fabs(d) < 1) continue; + //check if we can create edges from the triangle towards this new vertex + for (i = 0; i < 3; i++) + { + if (v1 == verts[i]) break; + if (!TH_TryEdge(v1, verts[i])) break; + } //end for + if (i < 3) continue; + //check if the triangles are valid + for (i = 0; i < 3; i++) + { + triverts[0] = v1; + triverts[1] = verts[i]; + triverts[2] = verts[(i+1)%3]; + //if the triangle already exists then it is valid + triangles[i] = TH_FindTriangle(triverts); + if (!triangles[i]) + { + if (!TH_TryTriangle(triverts)) break; + } //end if + } //end for + if (i < 3) continue; + //create the tetrahedron triangles using the new vertex + for (i = 0; i < 3; i++) + { + if (!triangles[i]) + { + triverts[0] = v1; + triverts[1] = verts[i]; + triverts[2] = verts[(i+1)%3]; + triangles[i] = TH_CreateTriangle(triverts); + } //end if + } //end for + //add the existing triangle + triangles[3] = tri - thworld.triangles; + // + return true; + } //end for + return false; +} //end of the function TH_FindTetrahedron2 +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void TH_TetrahedralDecomposition(th_triangle_t *triangles) +{ + int i, thtriangles[4], numtriangles; + th_triangle_t *donetriangles, *tri; + + donetriangles = NULL; + + /* + numtriangles = 0; + qprintf("%6d triangles", numtriangles); + for (tri = triangles; tri; tri = triangles) + { + qprintf("\r%6d", numtriangles++); + if (!TH_FindTetrahedron1(tri, thtriangles)) + { +// if (!TH_FindTetrahedron2(tri, thtriangles)) + { +// Error("triangle without tetrahedron"); + TH_RemoveTriangleFromList(&triangles, tri); + continue; + } //end if + } //end if + //create a tetrahedron from the triangles + TH_CreateTetrahedron(thtriangles); + // + for (i = 0; i < 4; i++) + { + if (thworld.triangles[abs(thtriangles[i])].front && + thworld.triangles[abs(thtriangles[i])].back) + { + TH_RemoveTriangleFromList(&triangles, &thworld.triangles[abs(thtriangles[i])]); + TH_AddTriangleToList(&donetriangles, &thworld.triangles[abs(thtriangles[i])]); + TH_FreeTriangleEdges(&thworld.triangles[abs(thtriangles[i])]); + } //end if + else + { + TH_AddTriangleToList(&triangles, &thworld.triangles[abs(thtriangles[i])]); + } //end else + } //end for + } //end for*/ + qprintf("%6d tetrahedrons", thworld.numtetrahedrons); + do + { + do + { + numtriangles = 0; + for (i = 1; i < thworld.numtriangles; i++) + { + tri = &thworld.triangles[i]; + if (tri->front && tri->back) continue; + //qprintf("\r%6d", numtriangles++); + if (!TH_FindTetrahedron1(tri, thtriangles)) + { +// if (!TH_FindTetrahedron2(tri, thtriangles)) + { + continue; + } //end if + } //end if + numtriangles++; + //create a tetrahedron from the triangles + TH_CreateTetrahedron(thtriangles); + qprintf("\r%6d", thworld.numtetrahedrons); + } //end for + } while(numtriangles); + for (i = 1; i < thworld.numtriangles; i++) + { + tri = &thworld.triangles[i]; + if (tri->front && tri->back) continue; + //qprintf("\r%6d", numtriangles++); +// if (!TH_FindTetrahedron1(tri, thtriangles)) + { + if (!TH_FindTetrahedron2(tri, thtriangles)) + { + continue; + } //end if + } //end if + numtriangles++; + //create a tetrahedron from the triangles + TH_CreateTetrahedron(thtriangles); + qprintf("\r%6d", thworld.numtetrahedrons); + } //end for + } while(numtriangles); + // + numtriangles = 0; + for (i = 1; i < thworld.numtriangles; i++) + { + tri = &thworld.triangles[i]; + if (!tri->front && !tri->back) numtriangles++; + } //end for + Log_Print("\r%6d triangles with front only\n", numtriangles); + Log_Print("\r%6d tetrahedrons\n", thworld.numtetrahedrons-1); +} //end of the function TH_TetrahedralDecomposition +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void TH_AASFaceVertex(aas_face_t *face, int index, vec3_t vertex) +{ + int edgenum, side; + + edgenum = aasworld.edgeindex[face->firstedge + index]; + side = edgenum < 0; + VectorCopy(aasworld.vertexes[aasworld.edges[abs(edgenum)].v[side]], vertex); +} //end of the function TH_AASFaceVertex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int TH_Colinear(float *v0, float *v1, float *v2) +{ + vec3_t t1, t2, vcross; + float d; + + VectorSubtract(v1, v0, t1); + VectorSubtract(v2, v0, t2); + CrossProduct (t1, t2, vcross); + d = VectorLength( vcross ); + + // if cross product is zero point is colinear + if (d < 10) + { + return true; + } //end if + return false; +} //end of the function TH_Colinear +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void TH_FaceCenter(aas_face_t *face, vec3_t center) +{ + int i, edgenum, side; + aas_edge_t *edge; + + VectorClear(center); + for (i = 0; i < face->numedges; i++) + { + edgenum = abs(aasworld.edgeindex[face->firstedge + i]); + side = edgenum < 0; + edge = &aasworld.edges[abs(edgenum)]; + VectorAdd(aasworld.vertexes[edge->v[side]], center, center); + } //end for + VectorScale(center, 1.0 / face->numedges, center); +} //end of the function TH_FaceCenter +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +th_triangle_t *TH_CreateAASFaceTriangles(aas_face_t *face) +{ + int i, first, verts[3], trinum; + vec3_t p0, p1, p2, p3, p4, center; + th_triangle_t *tri, *triangles; + + triangles = NULL; + //find three points that are not colinear + for (i = 0; i < face->numedges; i++) + { + TH_AASFaceVertex(face, (face->numedges + i-2)%face->numedges, p0); + TH_AASFaceVertex(face, (face->numedges + i-1)%face->numedges, p1); + TH_AASFaceVertex(face, (i )%face->numedges, p2); + if (TH_Colinear(p2, p0, p1)) continue; + TH_AASFaceVertex(face, (i+1)%face->numedges, p3); + TH_AASFaceVertex(face, (i+2)%face->numedges, p4); + if (TH_Colinear(p2, p3, p4)) continue; + break; + } //end for + //if there are three points that are not colinear + if (i < face->numedges) + { + //normal triangulation + first = i; //left and right most point of three non-colinear points + TH_AASFaceVertex(face, first, p0); + verts[0] = TH_FindOrCreateVertex(p0); + for (i = 1; i < face->numedges-1; i++) + { + TH_AASFaceVertex(face, (first+i )%face->numedges, p1); + TH_AASFaceVertex(face, (first+i+1)%face->numedges, p2); + verts[1] = TH_FindOrCreateVertex(p1); + verts[2] = TH_FindOrCreateVertex(p2); + trinum = TH_CreateTriangle(verts); + tri = &thworld.triangles[trinum]; + tri->front = -1; + TH_AddTriangleToList(&triangles, tri); + } //end for + } //end if + else + { + //fan triangulation + TH_FaceCenter(face, center); + // + verts[0] = TH_FindOrCreateVertex(center); + for (i = 0; i < face->numedges; i++) + { + TH_AASFaceVertex(face, (i )%face->numedges, p1); + TH_AASFaceVertex(face, (i+1)%face->numedges, p2); + if (TH_Colinear(center, p1, p2)) continue; + verts[1] = TH_FindOrCreateVertex(p1); + verts[2] = TH_FindOrCreateVertex(p2); + trinum = TH_CreateTriangle(verts); + tri = &thworld.triangles[trinum]; + tri->front = -1; + TH_AddTriangleToList(&triangles, tri); + } //end for + } //end else + return triangles; +} //end of the function TH_CreateAASFaceTriangles +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +th_triangle_t *TH_AASToTriangleMesh(void) +{ + int i, j, facenum, otherareanum; + aas_face_t *face; + th_triangle_t *tri, *nexttri, *triangles; + + triangles = NULL; + for (i = 1; i < aasworld.numareas; i++) + { + //if (!(aasworld.areasettings[i].presencetype & PRESENCE_NORMAL)) continue; + for (j = 0; j < aasworld.areas[i].numfaces; j++) + { + facenum = abs(aasworld.faceindex[aasworld.areas[i].firstface + j]); + face = &aasworld.faces[facenum]; + //only convert solid faces into triangles + if (!(face->faceflags & FACE_SOLID)) + { + /* + if (face->frontarea == i) otherareanum = face->backarea; + else otherareanum = face->frontarea; + if (aasworld.areasettings[otherareanum].presencetype & PRESENCE_NORMAL) continue; + */ + continue; + } //end if + // + tri = TH_CreateAASFaceTriangles(face); + for (; tri; tri = nexttri) + { + nexttri = tri->next; + TH_AddTriangleToList(&triangles, tri); + } //end for + } //end if + } //end for + return triangles; +} //end of the function TH_AASToTriangleMesh +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void TH_AASToTetrahedrons(char *filename) +{ + th_triangle_t *triangles, *tri, *lasttri; + int cnt; + + if (!AAS_LoadAASFile(filename, 0, 0)) + Error("couldn't load %s\n", filename); + + // + TH_InitMaxTH(); + //create a triangle mesh from the solid faces in the AAS file + triangles = TH_AASToTriangleMesh(); + // + cnt = 0; + lasttri = NULL; + for (tri = triangles; tri; tri = tri->next) + { + cnt++; + if (tri->prev != lasttri) Log_Print("BAH\n"); + lasttri = tri; + } //end for + Log_Print("%6d triangles\n", cnt); + //create a tetrahedral decomposition of the world bounded by triangles + TH_TetrahedralDecomposition(triangles); + // + TH_FreeMaxTH(); +} //end of the function TH_AASToTetrahedrons diff --git a/code/bspc/tetrahedron.h b/code/bspc/tetrahedron.h index 89003f1..2c6293d 100755 --- a/code/bspc/tetrahedron.h +++ b/code/bspc/tetrahedron.h @@ -1,24 +1,24 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -void TH_AASToTetrahedrons(char *filename); - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +void TH_AASToTetrahedrons(char *filename); + diff --git a/code/bspc/textures.c b/code/bspc/textures.c index ad6a8f5..76cc0fd 100755 --- a/code/bspc/textures.c +++ b/code/bspc/textures.c @@ -1,228 +1,228 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "qbsp.h" -#include "l_bsp_q2.h" - -int nummiptex; -textureref_t textureref[MAX_MAP_TEXTURES]; - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int FindMiptex (char *name) -{ - int i; - char path[1024]; - miptex_t *mt; - - for (i = 0; i < nummiptex; i++) - { - if (!strcmp (name, textureref[i].name)) - { - return i; - } //end if - } //end for - if (nummiptex == MAX_MAP_TEXTURES) - Error ("MAX_MAP_TEXTURES"); - strcpy (textureref[i].name, name); - - // load the miptex to get the flags and values - sprintf (path, "%stextures/%s.wal", gamedir, name); - if (TryLoadFile (path, (void **)&mt) != -1) - { - textureref[i].value = LittleLong (mt->value); - textureref[i].flags = LittleLong (mt->flags); - textureref[i].contents = LittleLong (mt->contents); - strcpy (textureref[i].animname, mt->animname); - FreeMemory(mt); - } //end if - nummiptex++; - - if (textureref[i].animname[0]) - FindMiptex (textureref[i].animname); - - return i; -} //end of the function FindMipTex -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -vec3_t baseaxis[18] = -{ -{0,0,1}, {1,0,0}, {0,-1,0}, // floor -{0,0,-1}, {1,0,0}, {0,-1,0}, // ceiling -{1,0,0}, {0,1,0}, {0,0,-1}, // west wall -{-1,0,0}, {0,1,0}, {0,0,-1}, // east wall -{0,1,0}, {1,0,0}, {0,0,-1}, // south wall -{0,-1,0}, {1,0,0}, {0,0,-1} // north wall -}; - -void TextureAxisFromPlane(plane_t *pln, vec3_t xv, vec3_t yv) -{ - int bestaxis; - vec_t dot,best; - int i; - - best = 0; - bestaxis = 0; - - for (i=0 ; i<6 ; i++) - { - dot = DotProduct (pln->normal, baseaxis[i*3]); - if (dot > best) - { - best = dot; - bestaxis = i; - } - } - - VectorCopy (baseaxis[bestaxis*3+1], xv); - VectorCopy (baseaxis[bestaxis*3+2], yv); -} //end of the function TextureAxisFromPlane -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -int TexinfoForBrushTexture(plane_t *plane, brush_texture_t *bt, vec3_t origin) -{ - vec3_t vecs[2]; - int sv, tv; - vec_t ang, sinv, cosv; - vec_t ns, nt; - texinfo_t tx, *tc; - int i, j, k; - float shift[2]; - brush_texture_t anim; - int mt; - - if (!bt->name[0]) - return 0; - - memset (&tx, 0, sizeof(tx)); - strcpy (tx.texture, bt->name); - - TextureAxisFromPlane(plane, vecs[0], vecs[1]); - - shift[0] = DotProduct (origin, vecs[0]); - shift[1] = DotProduct (origin, vecs[1]); - - if (!bt->scale[0]) - bt->scale[0] = 1; - if (!bt->scale[1]) - bt->scale[1] = 1; - - -// rotate axis - if (bt->rotate == 0) - { sinv = 0 ; cosv = 1; } - else if (bt->rotate == 90) - { sinv = 1 ; cosv = 0; } - else if (bt->rotate == 180) - { sinv = 0 ; cosv = -1; } - else if (bt->rotate == 270) - { sinv = -1 ; cosv = 0; } - else - { - ang = bt->rotate / 180 * Q_PI; - sinv = sin(ang); - cosv = cos(ang); - } - - if (vecs[0][0]) - sv = 0; - else if (vecs[0][1]) - sv = 1; - else - sv = 2; - - if (vecs[1][0]) - tv = 0; - else if (vecs[1][1]) - tv = 1; - else - tv = 2; - - for (i=0 ; i<2 ; i++) - { - ns = cosv * vecs[i][sv] - sinv * vecs[i][tv]; - nt = sinv * vecs[i][sv] + cosv * vecs[i][tv]; - vecs[i][sv] = ns; - vecs[i][tv] = nt; - } - - for (i=0 ; i<2 ; i++) - for (j=0 ; j<3 ; j++) - tx.vecs[i][j] = vecs[i][j] / bt->scale[i]; - - tx.vecs[0][3] = bt->shift[0] + shift[0]; - tx.vecs[1][3] = bt->shift[1] + shift[1]; - tx.flags = bt->flags; - tx.value = bt->value; - - // - // find the texinfo - // - tc = texinfo; - for (i=0 ; iflags != tx.flags) - continue; - if (tc->value != tx.value) - continue; - for (j=0 ; j<2 ; j++) - { - if (strcmp (tc->texture, tx.texture)) - goto skip; - for (k=0 ; k<4 ; k++) - { - if (tc->vecs[j][k] != tx.vecs[j][k]) - goto skip; - } - } - return i; -skip:; - } - *tc = tx; - numtexinfo++; - - // load the next animation - mt = FindMiptex (bt->name); - if (textureref[mt].animname[0]) - { - anim = *bt; - strcpy (anim.name, textureref[mt].animname); - tc->nexttexinfo = TexinfoForBrushTexture (plane, &anim, origin); - } - else - tc->nexttexinfo = -1; - - - return i; -} //end of the function TexinfoForBrushTexture +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "qbsp.h" +#include "l_bsp_q2.h" + +int nummiptex; +textureref_t textureref[MAX_MAP_TEXTURES]; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int FindMiptex (char *name) +{ + int i; + char path[1024]; + miptex_t *mt; + + for (i = 0; i < nummiptex; i++) + { + if (!strcmp (name, textureref[i].name)) + { + return i; + } //end if + } //end for + if (nummiptex == MAX_MAP_TEXTURES) + Error ("MAX_MAP_TEXTURES"); + strcpy (textureref[i].name, name); + + // load the miptex to get the flags and values + sprintf (path, "%stextures/%s.wal", gamedir, name); + if (TryLoadFile (path, (void **)&mt) != -1) + { + textureref[i].value = LittleLong (mt->value); + textureref[i].flags = LittleLong (mt->flags); + textureref[i].contents = LittleLong (mt->contents); + strcpy (textureref[i].animname, mt->animname); + FreeMemory(mt); + } //end if + nummiptex++; + + if (textureref[i].animname[0]) + FindMiptex (textureref[i].animname); + + return i; +} //end of the function FindMipTex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +vec3_t baseaxis[18] = +{ +{0,0,1}, {1,0,0}, {0,-1,0}, // floor +{0,0,-1}, {1,0,0}, {0,-1,0}, // ceiling +{1,0,0}, {0,1,0}, {0,0,-1}, // west wall +{-1,0,0}, {0,1,0}, {0,0,-1}, // east wall +{0,1,0}, {1,0,0}, {0,0,-1}, // south wall +{0,-1,0}, {1,0,0}, {0,0,-1} // north wall +}; + +void TextureAxisFromPlane(plane_t *pln, vec3_t xv, vec3_t yv) +{ + int bestaxis; + vec_t dot,best; + int i; + + best = 0; + bestaxis = 0; + + for (i=0 ; i<6 ; i++) + { + dot = DotProduct (pln->normal, baseaxis[i*3]); + if (dot > best) + { + best = dot; + bestaxis = i; + } + } + + VectorCopy (baseaxis[bestaxis*3+1], xv); + VectorCopy (baseaxis[bestaxis*3+2], yv); +} //end of the function TextureAxisFromPlane +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int TexinfoForBrushTexture(plane_t *plane, brush_texture_t *bt, vec3_t origin) +{ + vec3_t vecs[2]; + int sv, tv; + vec_t ang, sinv, cosv; + vec_t ns, nt; + texinfo_t tx, *tc; + int i, j, k; + float shift[2]; + brush_texture_t anim; + int mt; + + if (!bt->name[0]) + return 0; + + memset (&tx, 0, sizeof(tx)); + strcpy (tx.texture, bt->name); + + TextureAxisFromPlane(plane, vecs[0], vecs[1]); + + shift[0] = DotProduct (origin, vecs[0]); + shift[1] = DotProduct (origin, vecs[1]); + + if (!bt->scale[0]) + bt->scale[0] = 1; + if (!bt->scale[1]) + bt->scale[1] = 1; + + +// rotate axis + if (bt->rotate == 0) + { sinv = 0 ; cosv = 1; } + else if (bt->rotate == 90) + { sinv = 1 ; cosv = 0; } + else if (bt->rotate == 180) + { sinv = 0 ; cosv = -1; } + else if (bt->rotate == 270) + { sinv = -1 ; cosv = 0; } + else + { + ang = bt->rotate / 180 * Q_PI; + sinv = sin(ang); + cosv = cos(ang); + } + + if (vecs[0][0]) + sv = 0; + else if (vecs[0][1]) + sv = 1; + else + sv = 2; + + if (vecs[1][0]) + tv = 0; + else if (vecs[1][1]) + tv = 1; + else + tv = 2; + + for (i=0 ; i<2 ; i++) + { + ns = cosv * vecs[i][sv] - sinv * vecs[i][tv]; + nt = sinv * vecs[i][sv] + cosv * vecs[i][tv]; + vecs[i][sv] = ns; + vecs[i][tv] = nt; + } + + for (i=0 ; i<2 ; i++) + for (j=0 ; j<3 ; j++) + tx.vecs[i][j] = vecs[i][j] / bt->scale[i]; + + tx.vecs[0][3] = bt->shift[0] + shift[0]; + tx.vecs[1][3] = bt->shift[1] + shift[1]; + tx.flags = bt->flags; + tx.value = bt->value; + + // + // find the texinfo + // + tc = texinfo; + for (i=0 ; iflags != tx.flags) + continue; + if (tc->value != tx.value) + continue; + for (j=0 ; j<2 ; j++) + { + if (strcmp (tc->texture, tx.texture)) + goto skip; + for (k=0 ; k<4 ; k++) + { + if (tc->vecs[j][k] != tx.vecs[j][k]) + goto skip; + } + } + return i; +skip:; + } + *tc = tx; + numtexinfo++; + + // load the next animation + mt = FindMiptex (bt->name); + if (textureref[mt].animname[0]) + { + anim = *bt; + strcpy (anim.name, textureref[mt].animname); + tc->nexttexinfo = TexinfoForBrushTexture (plane, &anim, origin); + } + else + tc->nexttexinfo = -1; + + + return i; +} //end of the function TexinfoForBrushTexture diff --git a/code/bspc/tree.c b/code/bspc/tree.c index 3d2ee9c..bc3cb8e 100755 --- a/code/bspc/tree.c +++ b/code/bspc/tree.c @@ -1,283 +1,283 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "qbsp.h" - -extern int c_nodes; -int c_pruned; -int freedtreemem = 0; - -void RemovePortalFromNode (portal_t *portal, node_t *l); - -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -node_t *NodeForPoint (node_t *node, vec3_t origin) -{ - plane_t *plane; - vec_t d; - - while (node->planenum != PLANENUM_LEAF) - { - plane = &mapplanes[node->planenum]; - d = DotProduct (origin, plane->normal) - plane->dist; - if (d >= 0) - node = node->children[0]; - else - node = node->children[1]; - } - return node; -} //end of the function NodeForPoint -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Tree_FreePortals_r (node_t *node) -{ - portal_t *p, *nextp; - int s; - - // free children - if (node->planenum != PLANENUM_LEAF) - { - Tree_FreePortals_r(node->children[0]); - Tree_FreePortals_r(node->children[1]); - } - - // free portals - for (p = node->portals; p; p = nextp) - { - s = (p->nodes[1] == node); - nextp = p->next[s]; - - RemovePortalFromNode (p, p->nodes[!s]); -#ifdef ME - if (p->winding) freedtreemem += MemorySize(p->winding); - freedtreemem += MemorySize(p); -#endif //ME - FreePortal(p); - } - node->portals = NULL; -} //end of the function Tree_FreePortals_r -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Tree_Free_r (node_t *node) -{ -// face_t *f, *nextf; - bspbrush_t *brush, *nextbrush; - - //free children - if (node->planenum != PLANENUM_LEAF) - { - Tree_Free_r (node->children[0]); - Tree_Free_r (node->children[1]); - } //end if - //free bspbrushes -// FreeBrushList (node->brushlist); - for (brush = node->brushlist; brush; brush = nextbrush) - { - nextbrush = brush->next; -#ifdef ME - freedtreemem += MemorySize(brush); -#endif //ME - FreeBrush(brush); - } //end for - node->brushlist = NULL; - - /* - NOTE: only used when creating Q2 bsp - // free faces - for (f = node->faces; f; f = nextf) - { - nextf = f->next; -#ifdef ME - if (f->w) freedtreemem += MemorySize(f->w); - freedtreemem += sizeof(face_t); -#endif //ME - FreeFace(f); - } //end for - */ - - // free the node - if (node->volume) - { -#ifdef ME - freedtreemem += MemorySize(node->volume); -#endif //ME - FreeBrush (node->volume); - } //end if - - if (numthreads == 1) c_nodes--; -#ifdef ME - freedtreemem += MemorySize(node); -#endif //ME - FreeMemory(node); -} //end of the function Tree_Free_r -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Tree_Free(tree_t *tree) -{ - //if no tree just return - if (!tree) return; - // - freedtreemem = 0; - // - Tree_FreePortals_r(tree->headnode); - Tree_Free_r(tree->headnode); -#ifdef ME - freedtreemem += MemorySize(tree); -#endif //ME - FreeMemory(tree); -#ifdef ME - Log_Print("freed "); - PrintMemorySize(freedtreemem); - Log_Print(" of tree memory\n"); -#endif //ME -} //end of the function Tree_Free -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -tree_t *Tree_Alloc(void) -{ - tree_t *tree; - - tree = GetMemory(sizeof(*tree)); - memset (tree, 0, sizeof(*tree)); - ClearBounds (tree->mins, tree->maxs); - - return tree; -} //end of the function Tree_Alloc -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Tree_Print_r (node_t *node, int depth) -{ - int i; - plane_t *plane; - bspbrush_t *bb; - - for (i=0 ; iplanenum == PLANENUM_LEAF) - { - if (!node->brushlist) - printf ("NULL\n"); - else - { - for (bb=node->brushlist ; bb ; bb=bb->next) - printf ("%i ", bb->original->brushnum); - printf ("\n"); - } - return; - } - - plane = &mapplanes[node->planenum]; - printf ("#%i (%5.2f %5.2f %5.2f):%5.2f\n", node->planenum, - plane->normal[0], plane->normal[1], plane->normal[2], - plane->dist); - Tree_Print_r (node->children[0], depth+1); - Tree_Print_r (node->children[1], depth+1); -} //end of the function Tree_Print_r -//=========================================================================== -// NODES THAT DON'T SEPERATE DIFFERENT CONTENTS CAN BE PRUNED -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Tree_PruneNodes_r (node_t *node) -{ - bspbrush_t *b, *next; - - if (node->planenum == PLANENUM_LEAF) return; - - Tree_PruneNodes_r (node->children[0]); - Tree_PruneNodes_r (node->children[1]); - - if (create_aas) - { - if ((node->children[0]->contents & CONTENTS_LADDER) || - (node->children[1]->contents & CONTENTS_LADDER)) return; - } - - if ((node->children[0]->contents & CONTENTS_SOLID) - && (node->children[1]->contents & CONTENTS_SOLID)) - { - if (node->faces) - Error ("node->faces seperating CONTENTS_SOLID"); - if (node->children[0]->faces || node->children[1]->faces) - Error ("!node->faces with children"); - // FIXME: free stuff - node->planenum = PLANENUM_LEAF; - node->contents = CONTENTS_SOLID; - node->detail_seperator = false; - - if (node->brushlist) - Error ("PruneNodes: node->brushlist"); - // combine brush lists - node->brushlist = node->children[1]->brushlist; - - for (b = node->children[0]->brushlist; b; b = next) - { - next = b->next; - b->next = node->brushlist; - node->brushlist = b; - } //end for - //free the child nodes - FreeMemory(node->children[0]); - FreeMemory(node->children[1]); - //two nodes are cut away - c_pruned += 2; - } //end if -} //end of the function Tree_PruneNodes_r -//=========================================================================== -// -// Parameter: - -// Returns: - -// Changes Globals: - -//=========================================================================== -void Tree_PruneNodes(node_t *node) -{ - Log_Print("------- Prune Nodes --------\n"); - c_pruned = 0; - Tree_PruneNodes_r(node); - Log_Print("%5i pruned nodes\n", c_pruned); -} //end of the function Tree_PruneNodes +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "qbsp.h" + +extern int c_nodes; +int c_pruned; +int freedtreemem = 0; + +void RemovePortalFromNode (portal_t *portal, node_t *l); + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +node_t *NodeForPoint (node_t *node, vec3_t origin) +{ + plane_t *plane; + vec_t d; + + while (node->planenum != PLANENUM_LEAF) + { + plane = &mapplanes[node->planenum]; + d = DotProduct (origin, plane->normal) - plane->dist; + if (d >= 0) + node = node->children[0]; + else + node = node->children[1]; + } + return node; +} //end of the function NodeForPoint +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Tree_FreePortals_r (node_t *node) +{ + portal_t *p, *nextp; + int s; + + // free children + if (node->planenum != PLANENUM_LEAF) + { + Tree_FreePortals_r(node->children[0]); + Tree_FreePortals_r(node->children[1]); + } + + // free portals + for (p = node->portals; p; p = nextp) + { + s = (p->nodes[1] == node); + nextp = p->next[s]; + + RemovePortalFromNode (p, p->nodes[!s]); +#ifdef ME + if (p->winding) freedtreemem += MemorySize(p->winding); + freedtreemem += MemorySize(p); +#endif //ME + FreePortal(p); + } + node->portals = NULL; +} //end of the function Tree_FreePortals_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Tree_Free_r (node_t *node) +{ +// face_t *f, *nextf; + bspbrush_t *brush, *nextbrush; + + //free children + if (node->planenum != PLANENUM_LEAF) + { + Tree_Free_r (node->children[0]); + Tree_Free_r (node->children[1]); + } //end if + //free bspbrushes +// FreeBrushList (node->brushlist); + for (brush = node->brushlist; brush; brush = nextbrush) + { + nextbrush = brush->next; +#ifdef ME + freedtreemem += MemorySize(brush); +#endif //ME + FreeBrush(brush); + } //end for + node->brushlist = NULL; + + /* + NOTE: only used when creating Q2 bsp + // free faces + for (f = node->faces; f; f = nextf) + { + nextf = f->next; +#ifdef ME + if (f->w) freedtreemem += MemorySize(f->w); + freedtreemem += sizeof(face_t); +#endif //ME + FreeFace(f); + } //end for + */ + + // free the node + if (node->volume) + { +#ifdef ME + freedtreemem += MemorySize(node->volume); +#endif //ME + FreeBrush (node->volume); + } //end if + + if (numthreads == 1) c_nodes--; +#ifdef ME + freedtreemem += MemorySize(node); +#endif //ME + FreeMemory(node); +} //end of the function Tree_Free_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Tree_Free(tree_t *tree) +{ + //if no tree just return + if (!tree) return; + // + freedtreemem = 0; + // + Tree_FreePortals_r(tree->headnode); + Tree_Free_r(tree->headnode); +#ifdef ME + freedtreemem += MemorySize(tree); +#endif //ME + FreeMemory(tree); +#ifdef ME + Log_Print("freed "); + PrintMemorySize(freedtreemem); + Log_Print(" of tree memory\n"); +#endif //ME +} //end of the function Tree_Free +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +tree_t *Tree_Alloc(void) +{ + tree_t *tree; + + tree = GetMemory(sizeof(*tree)); + memset (tree, 0, sizeof(*tree)); + ClearBounds (tree->mins, tree->maxs); + + return tree; +} //end of the function Tree_Alloc +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Tree_Print_r (node_t *node, int depth) +{ + int i; + plane_t *plane; + bspbrush_t *bb; + + for (i=0 ; iplanenum == PLANENUM_LEAF) + { + if (!node->brushlist) + printf ("NULL\n"); + else + { + for (bb=node->brushlist ; bb ; bb=bb->next) + printf ("%i ", bb->original->brushnum); + printf ("\n"); + } + return; + } + + plane = &mapplanes[node->planenum]; + printf ("#%i (%5.2f %5.2f %5.2f):%5.2f\n", node->planenum, + plane->normal[0], plane->normal[1], plane->normal[2], + plane->dist); + Tree_Print_r (node->children[0], depth+1); + Tree_Print_r (node->children[1], depth+1); +} //end of the function Tree_Print_r +//=========================================================================== +// NODES THAT DON'T SEPERATE DIFFERENT CONTENTS CAN BE PRUNED +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Tree_PruneNodes_r (node_t *node) +{ + bspbrush_t *b, *next; + + if (node->planenum == PLANENUM_LEAF) return; + + Tree_PruneNodes_r (node->children[0]); + Tree_PruneNodes_r (node->children[1]); + + if (create_aas) + { + if ((node->children[0]->contents & CONTENTS_LADDER) || + (node->children[1]->contents & CONTENTS_LADDER)) return; + } + + if ((node->children[0]->contents & CONTENTS_SOLID) + && (node->children[1]->contents & CONTENTS_SOLID)) + { + if (node->faces) + Error ("node->faces seperating CONTENTS_SOLID"); + if (node->children[0]->faces || node->children[1]->faces) + Error ("!node->faces with children"); + // FIXME: free stuff + node->planenum = PLANENUM_LEAF; + node->contents = CONTENTS_SOLID; + node->detail_seperator = false; + + if (node->brushlist) + Error ("PruneNodes: node->brushlist"); + // combine brush lists + node->brushlist = node->children[1]->brushlist; + + for (b = node->children[0]->brushlist; b; b = next) + { + next = b->next; + b->next = node->brushlist; + node->brushlist = b; + } //end for + //free the child nodes + FreeMemory(node->children[0]); + FreeMemory(node->children[1]); + //two nodes are cut away + c_pruned += 2; + } //end if +} //end of the function Tree_PruneNodes_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Tree_PruneNodes(node_t *node) +{ + Log_Print("------- Prune Nodes --------\n"); + c_pruned = 0; + Tree_PruneNodes_r(node); + Log_Print("%5i pruned nodes\n", c_pruned); +} //end of the function Tree_PruneNodes diff --git a/code/bspc/writebsp.c b/code/bspc/writebsp.c index 38e9980..b6cf4e4 100755 --- a/code/bspc/writebsp.c +++ b/code/bspc/writebsp.c @@ -1,595 +1,595 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -#include "qbsp.h" - -int c_nofaces; -int c_facenodes; - - -/* -========================================================= - -ONLY SAVE OUT PLANES THAT ARE ACTUALLY USED AS NODES - -========================================================= -*/ - -int planeused[MAX_MAP_PLANES]; - -/* -============ -EmitPlanes - -There is no oportunity to discard planes, because all of the original -brushes will be saved in the map. -============ -*/ -void EmitPlanes (void) -{ - int i; - dplane_t *dp; - plane_t *mp; - //ME: this causes a crash?? -// int planetranslate[MAX_MAP_PLANES]; - - mp = mapplanes; - for (i=0 ; inormal, dp->normal); - dp->dist = mp->dist; - dp->type = mp->type; - numplanes++; - if (numplanes >= MAX_MAP_PLANES) - Error("MAX_MAP_PLANES"); - } -} - - -//======================================================== - -void EmitMarkFace (dleaf_t *leaf_p, face_t *f) -{ - int i; - int facenum; - - while (f->merged) - f = f->merged; - - if (f->split[0]) - { - EmitMarkFace (leaf_p, f->split[0]); - EmitMarkFace (leaf_p, f->split[1]); - return; - } - - facenum = f->outputnumber; - if (facenum == -1) - return; // degenerate face - - if (facenum < 0 || facenum >= numfaces) - Error ("Bad leafface"); - for (i=leaf_p->firstleafface ; i= MAX_MAP_LEAFFACES) - Error ("MAX_MAP_LEAFFACES"); - - dleaffaces[numleaffaces] = facenum; - numleaffaces++; - } - -} - - -/* -================== -EmitLeaf -================== -*/ -void EmitLeaf (node_t *node) -{ - dleaf_t *leaf_p; - portal_t *p; - int s; - face_t *f; - bspbrush_t *b; - int i; - int brushnum; - - // emit a leaf - if (numleafs >= MAX_MAP_LEAFS) - Error ("MAX_MAP_LEAFS"); - - leaf_p = &dleafs[numleafs]; - numleafs++; - - leaf_p->contents = node->contents; - leaf_p->cluster = node->cluster; - leaf_p->area = node->area; - - // - // write bounding box info - // - VectorCopy (node->mins, leaf_p->mins); - VectorCopy (node->maxs, leaf_p->maxs); - - // - // write the leafbrushes - // - leaf_p->firstleafbrush = numleafbrushes; - for (b=node->brushlist ; b ; b=b->next) - { - if (numleafbrushes >= MAX_MAP_LEAFBRUSHES) - Error ("MAX_MAP_LEAFBRUSHES"); - - brushnum = b->original - mapbrushes; - for (i=leaf_p->firstleafbrush ; inumleafbrushes = numleafbrushes - leaf_p->firstleafbrush; - - // - // write the leaffaces - // - if (leaf_p->contents & CONTENTS_SOLID) - return; // no leaffaces in solids - - leaf_p->firstleafface = numleaffaces; - - for (p = node->portals ; p ; p = p->next[s]) - { - s = (p->nodes[1] == node); - f = p->face[s]; - if (!f) - continue; // not a visible portal - - EmitMarkFace (leaf_p, f); - } - - leaf_p->numleaffaces = numleaffaces - leaf_p->firstleafface; -} - - -/* -================== -EmitFace -================== -*/ -void EmitFace (face_t *f) -{ - dface_t *df; - int i; - int e; - - f->outputnumber = -1; - - if (f->numpoints < 3) - { - return; // degenerated - } - if (f->merged || f->split[0] || f->split[1]) - { - return; // not a final face - } - - // save output number so leaffaces can use - f->outputnumber = numfaces; - - if (numfaces >= MAX_MAP_FACES) - Error ("numfaces == MAX_MAP_FACES"); - df = &dfaces[numfaces]; - numfaces++; - - // planenum is used by qlight, but not quake - df->planenum = f->planenum & (~1); - df->side = f->planenum & 1; - - df->firstedge = numsurfedges; - df->numedges = f->numpoints; - df->texinfo = f->texinfo; - for (i=0 ; inumpoints ; i++) - { -// e = GetEdge (f->pts[i], f->pts[(i+1)%f->numpoints], f); - e = GetEdge2 (f->vertexnums[i], f->vertexnums[(i+1)%f->numpoints], f); - if (numsurfedges >= MAX_MAP_SURFEDGES) - Error ("numsurfedges == MAX_MAP_SURFEDGES"); - dsurfedges[numsurfedges] = e; - numsurfedges++; - } -} - -/* -============ -EmitDrawingNode_r -============ -*/ -int EmitDrawNode_r (node_t *node) -{ - dnode_t *n; - face_t *f; - int i; - - if (node->planenum == PLANENUM_LEAF) - { - EmitLeaf (node); - return -numleafs; - } - - // emit a node - if (numnodes == MAX_MAP_NODES) - Error ("MAX_MAP_NODES"); - n = &dnodes[numnodes]; - numnodes++; - - VectorCopy (node->mins, n->mins); - VectorCopy (node->maxs, n->maxs); - - planeused[node->planenum]++; - planeused[node->planenum^1]++; - - if (node->planenum & 1) - Error ("WriteDrawNodes_r: odd planenum"); - n->planenum = node->planenum; - n->firstface = numfaces; - - if (!node->faces) - c_nofaces++; - else - c_facenodes++; - - for (f=node->faces ; f ; f=f->next) - EmitFace (f); - - n->numfaces = numfaces - n->firstface; - - - // - // recursively output the other nodes - // - for (i=0 ; i<2 ; i++) - { - if (node->children[i]->planenum == PLANENUM_LEAF) - { - n->children[i] = -(numleafs + 1); - EmitLeaf (node->children[i]); - } - else - { - n->children[i] = numnodes; - EmitDrawNode_r (node->children[i]); - } - } - - return n - dnodes; -} - -//========================================================= - - -/* -============ -WriteBSP -============ -*/ -void WriteBSP (node_t *headnode) -{ - int oldfaces; - - c_nofaces = 0; - c_facenodes = 0; - - qprintf ("--- WriteBSP ---\n"); - - oldfaces = numfaces; - dmodels[nummodels].headnode = EmitDrawNode_r (headnode); - EmitAreaPortals (headnode); - - qprintf ("%5i nodes with faces\n", c_facenodes); - qprintf ("%5i nodes without faces\n", c_nofaces); - qprintf ("%5i faces\n", numfaces-oldfaces); -} - -//=========================================================== - -/* -============ -SetModelNumbers -============ -*/ -void SetModelNumbers (void) -{ - int i; - int models; - char value[10]; - - models = 1; - for (i=1 ; icontents = b->contents; - db->firstside = numbrushsides; - db->numsides = b->numsides; - for (j=0 ; jnumsides ; j++) - { - if (numbrushsides == MAX_MAP_BRUSHSIDES) - Error ("MAX_MAP_BRUSHSIDES"); - cp = &dbrushsides[numbrushsides]; - numbrushsides++; - cp->planenum = b->original_sides[j].planenum; - cp->texinfo = b->original_sides[j].texinfo; - } - -#ifdef ME - //for collision detection, bounding boxes are axial :) - //brushes are convex so just add dot or line touching planes on the sides of - //the brush parallell to the axis planes -#endif - // add any axis planes not contained in the brush to bevel off corners - for (x=0 ; x<3 ; x++) - for (s=-1 ; s<=1 ; s+=2) - { - // add the plane - VectorCopy (vec3_origin, normal); - normal[x] = s; - if (s == -1) - dist = -b->mins[x]; - else - dist = b->maxs[x]; - planenum = FindFloatPlane (normal, dist); - for (i=0 ; inumsides ; i++) - if (b->original_sides[i].planenum == planenum) - break; - if (i == b->numsides) - { - if (numbrushsides >= MAX_MAP_BRUSHSIDES) - Error ("MAX_MAP_BRUSHSIDES"); - - dbrushsides[numbrushsides].planenum = planenum; - dbrushsides[numbrushsides].texinfo = - dbrushsides[numbrushsides-1].texinfo; - numbrushsides++; - db->numsides++; - } - } - - } - -} - -//=========================================================== - -/* -================== -BeginBSPFile -================== -*/ -void BeginBSPFile (void) -{ - // these values may actually be initialized - // if the file existed when loaded, so clear them explicitly - nummodels = 0; - numfaces = 0; - numnodes = 0; - numbrushsides = 0; - numvertexes = 0; - numleaffaces = 0; - numleafbrushes = 0; - numsurfedges = 0; - - // edge 0 is not used, because 0 can't be negated - numedges = 1; - - // leave vertex 0 as an error - numvertexes = 1; - - // leave leaf 0 as an error - numleafs = 1; - dleafs[0].contents = CONTENTS_SOLID; -} - - -/* -============ -EndBSPFile -============ -*/ -void EndBSPFile (void) -{ -#if 0 - char path[1024]; - int len; - byte *buf; -#endif - - - EmitBrushes (); - EmitPlanes (); - Q2_UnparseEntities (); - - // load the pop -#if 0 - sprintf (path, "%s/pics/pop.lmp", gamedir); - len = LoadFile (path, &buf); - memcpy (dpop, buf, sizeof(dpop)); - FreeMemory(buf); -#endif -} - - -/* -================== -BeginModel -================== -*/ -int firstmodleaf; -extern int firstmodeledge; -extern int firstmodelface; -void BeginModel (void) -{ - dmodel_t *mod; - int start, end; - mapbrush_t *b; - int j; - entity_t *e; - vec3_t mins, maxs; - - if (nummodels == MAX_MAP_MODELS) - Error ("MAX_MAP_MODELS"); - mod = &dmodels[nummodels]; - - mod->firstface = numfaces; - - firstmodleaf = numleafs; - firstmodeledge = numedges; - firstmodelface = numfaces; - - // - // bound the brushes - // - e = &entities[entity_num]; - - start = e->firstbrush; - end = start + e->numbrushes; - ClearBounds (mins, maxs); - - for (j=start ; jnumsides) - continue; // not a real brush (origin brush) - AddPointToBounds (b->mins, mins, maxs); - AddPointToBounds (b->maxs, mins, maxs); - } - - VectorCopy (mins, mod->mins); - VectorCopy (maxs, mod->maxs); -} - - -/* -================== -EndModel -================== -*/ -void EndModel (void) -{ - dmodel_t *mod; - - mod = &dmodels[nummodels]; - - mod->numfaces = numfaces - mod->firstface; - - nummodels++; -} - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +#include "qbsp.h" + +int c_nofaces; +int c_facenodes; + + +/* +========================================================= + +ONLY SAVE OUT PLANES THAT ARE ACTUALLY USED AS NODES + +========================================================= +*/ + +int planeused[MAX_MAP_PLANES]; + +/* +============ +EmitPlanes + +There is no oportunity to discard planes, because all of the original +brushes will be saved in the map. +============ +*/ +void EmitPlanes (void) +{ + int i; + dplane_t *dp; + plane_t *mp; + //ME: this causes a crash?? +// int planetranslate[MAX_MAP_PLANES]; + + mp = mapplanes; + for (i=0 ; inormal, dp->normal); + dp->dist = mp->dist; + dp->type = mp->type; + numplanes++; + if (numplanes >= MAX_MAP_PLANES) + Error("MAX_MAP_PLANES"); + } +} + + +//======================================================== + +void EmitMarkFace (dleaf_t *leaf_p, face_t *f) +{ + int i; + int facenum; + + while (f->merged) + f = f->merged; + + if (f->split[0]) + { + EmitMarkFace (leaf_p, f->split[0]); + EmitMarkFace (leaf_p, f->split[1]); + return; + } + + facenum = f->outputnumber; + if (facenum == -1) + return; // degenerate face + + if (facenum < 0 || facenum >= numfaces) + Error ("Bad leafface"); + for (i=leaf_p->firstleafface ; i= MAX_MAP_LEAFFACES) + Error ("MAX_MAP_LEAFFACES"); + + dleaffaces[numleaffaces] = facenum; + numleaffaces++; + } + +} + + +/* +================== +EmitLeaf +================== +*/ +void EmitLeaf (node_t *node) +{ + dleaf_t *leaf_p; + portal_t *p; + int s; + face_t *f; + bspbrush_t *b; + int i; + int brushnum; + + // emit a leaf + if (numleafs >= MAX_MAP_LEAFS) + Error ("MAX_MAP_LEAFS"); + + leaf_p = &dleafs[numleafs]; + numleafs++; + + leaf_p->contents = node->contents; + leaf_p->cluster = node->cluster; + leaf_p->area = node->area; + + // + // write bounding box info + // + VectorCopy (node->mins, leaf_p->mins); + VectorCopy (node->maxs, leaf_p->maxs); + + // + // write the leafbrushes + // + leaf_p->firstleafbrush = numleafbrushes; + for (b=node->brushlist ; b ; b=b->next) + { + if (numleafbrushes >= MAX_MAP_LEAFBRUSHES) + Error ("MAX_MAP_LEAFBRUSHES"); + + brushnum = b->original - mapbrushes; + for (i=leaf_p->firstleafbrush ; inumleafbrushes = numleafbrushes - leaf_p->firstleafbrush; + + // + // write the leaffaces + // + if (leaf_p->contents & CONTENTS_SOLID) + return; // no leaffaces in solids + + leaf_p->firstleafface = numleaffaces; + + for (p = node->portals ; p ; p = p->next[s]) + { + s = (p->nodes[1] == node); + f = p->face[s]; + if (!f) + continue; // not a visible portal + + EmitMarkFace (leaf_p, f); + } + + leaf_p->numleaffaces = numleaffaces - leaf_p->firstleafface; +} + + +/* +================== +EmitFace +================== +*/ +void EmitFace (face_t *f) +{ + dface_t *df; + int i; + int e; + + f->outputnumber = -1; + + if (f->numpoints < 3) + { + return; // degenerated + } + if (f->merged || f->split[0] || f->split[1]) + { + return; // not a final face + } + + // save output number so leaffaces can use + f->outputnumber = numfaces; + + if (numfaces >= MAX_MAP_FACES) + Error ("numfaces == MAX_MAP_FACES"); + df = &dfaces[numfaces]; + numfaces++; + + // planenum is used by qlight, but not quake + df->planenum = f->planenum & (~1); + df->side = f->planenum & 1; + + df->firstedge = numsurfedges; + df->numedges = f->numpoints; + df->texinfo = f->texinfo; + for (i=0 ; inumpoints ; i++) + { +// e = GetEdge (f->pts[i], f->pts[(i+1)%f->numpoints], f); + e = GetEdge2 (f->vertexnums[i], f->vertexnums[(i+1)%f->numpoints], f); + if (numsurfedges >= MAX_MAP_SURFEDGES) + Error ("numsurfedges == MAX_MAP_SURFEDGES"); + dsurfedges[numsurfedges] = e; + numsurfedges++; + } +} + +/* +============ +EmitDrawingNode_r +============ +*/ +int EmitDrawNode_r (node_t *node) +{ + dnode_t *n; + face_t *f; + int i; + + if (node->planenum == PLANENUM_LEAF) + { + EmitLeaf (node); + return -numleafs; + } + + // emit a node + if (numnodes == MAX_MAP_NODES) + Error ("MAX_MAP_NODES"); + n = &dnodes[numnodes]; + numnodes++; + + VectorCopy (node->mins, n->mins); + VectorCopy (node->maxs, n->maxs); + + planeused[node->planenum]++; + planeused[node->planenum^1]++; + + if (node->planenum & 1) + Error ("WriteDrawNodes_r: odd planenum"); + n->planenum = node->planenum; + n->firstface = numfaces; + + if (!node->faces) + c_nofaces++; + else + c_facenodes++; + + for (f=node->faces ; f ; f=f->next) + EmitFace (f); + + n->numfaces = numfaces - n->firstface; + + + // + // recursively output the other nodes + // + for (i=0 ; i<2 ; i++) + { + if (node->children[i]->planenum == PLANENUM_LEAF) + { + n->children[i] = -(numleafs + 1); + EmitLeaf (node->children[i]); + } + else + { + n->children[i] = numnodes; + EmitDrawNode_r (node->children[i]); + } + } + + return n - dnodes; +} + +//========================================================= + + +/* +============ +WriteBSP +============ +*/ +void WriteBSP (node_t *headnode) +{ + int oldfaces; + + c_nofaces = 0; + c_facenodes = 0; + + qprintf ("--- WriteBSP ---\n"); + + oldfaces = numfaces; + dmodels[nummodels].headnode = EmitDrawNode_r (headnode); + EmitAreaPortals (headnode); + + qprintf ("%5i nodes with faces\n", c_facenodes); + qprintf ("%5i nodes without faces\n", c_nofaces); + qprintf ("%5i faces\n", numfaces-oldfaces); +} + +//=========================================================== + +/* +============ +SetModelNumbers +============ +*/ +void SetModelNumbers (void) +{ + int i; + int models; + char value[10]; + + models = 1; + for (i=1 ; icontents = b->contents; + db->firstside = numbrushsides; + db->numsides = b->numsides; + for (j=0 ; jnumsides ; j++) + { + if (numbrushsides == MAX_MAP_BRUSHSIDES) + Error ("MAX_MAP_BRUSHSIDES"); + cp = &dbrushsides[numbrushsides]; + numbrushsides++; + cp->planenum = b->original_sides[j].planenum; + cp->texinfo = b->original_sides[j].texinfo; + } + +#ifdef ME + //for collision detection, bounding boxes are axial :) + //brushes are convex so just add dot or line touching planes on the sides of + //the brush parallell to the axis planes +#endif + // add any axis planes not contained in the brush to bevel off corners + for (x=0 ; x<3 ; x++) + for (s=-1 ; s<=1 ; s+=2) + { + // add the plane + VectorCopy (vec3_origin, normal); + normal[x] = s; + if (s == -1) + dist = -b->mins[x]; + else + dist = b->maxs[x]; + planenum = FindFloatPlane (normal, dist); + for (i=0 ; inumsides ; i++) + if (b->original_sides[i].planenum == planenum) + break; + if (i == b->numsides) + { + if (numbrushsides >= MAX_MAP_BRUSHSIDES) + Error ("MAX_MAP_BRUSHSIDES"); + + dbrushsides[numbrushsides].planenum = planenum; + dbrushsides[numbrushsides].texinfo = + dbrushsides[numbrushsides-1].texinfo; + numbrushsides++; + db->numsides++; + } + } + + } + +} + +//=========================================================== + +/* +================== +BeginBSPFile +================== +*/ +void BeginBSPFile (void) +{ + // these values may actually be initialized + // if the file existed when loaded, so clear them explicitly + nummodels = 0; + numfaces = 0; + numnodes = 0; + numbrushsides = 0; + numvertexes = 0; + numleaffaces = 0; + numleafbrushes = 0; + numsurfedges = 0; + + // edge 0 is not used, because 0 can't be negated + numedges = 1; + + // leave vertex 0 as an error + numvertexes = 1; + + // leave leaf 0 as an error + numleafs = 1; + dleafs[0].contents = CONTENTS_SOLID; +} + + +/* +============ +EndBSPFile +============ +*/ +void EndBSPFile (void) +{ +#if 0 + char path[1024]; + int len; + byte *buf; +#endif + + + EmitBrushes (); + EmitPlanes (); + Q2_UnparseEntities (); + + // load the pop +#if 0 + sprintf (path, "%s/pics/pop.lmp", gamedir); + len = LoadFile (path, &buf); + memcpy (dpop, buf, sizeof(dpop)); + FreeMemory(buf); +#endif +} + + +/* +================== +BeginModel +================== +*/ +int firstmodleaf; +extern int firstmodeledge; +extern int firstmodelface; +void BeginModel (void) +{ + dmodel_t *mod; + int start, end; + mapbrush_t *b; + int j; + entity_t *e; + vec3_t mins, maxs; + + if (nummodels == MAX_MAP_MODELS) + Error ("MAX_MAP_MODELS"); + mod = &dmodels[nummodels]; + + mod->firstface = numfaces; + + firstmodleaf = numleafs; + firstmodeledge = numedges; + firstmodelface = numfaces; + + // + // bound the brushes + // + e = &entities[entity_num]; + + start = e->firstbrush; + end = start + e->numbrushes; + ClearBounds (mins, maxs); + + for (j=start ; jnumsides) + continue; // not a real brush (origin brush) + AddPointToBounds (b->mins, mins, maxs); + AddPointToBounds (b->maxs, mins, maxs); + } + + VectorCopy (mins, mod->mins); + VectorCopy (maxs, mod->maxs); +} + + +/* +================== +EndModel +================== +*/ +void EndModel (void) +{ + dmodel_t *mod; + + mod = &dmodels[nummodels]; + + mod->numfaces = numfaces - mod->firstface; + + nummodels++; +} + diff --git a/code/cgame.lnt b/code/cgame.lnt index e1801aa..713f962 100755 --- a/code/cgame.lnt +++ b/code/cgame.lnt @@ -1,25 +1,25 @@ -opts.lnt - -game\bg_misc.c -game\bg_pmove.c -game\q_math.c -game\q_shared.c -cgame\cg_consolecmds.c -cgame\cg_draw.c -cgame\cg_drawtools.c -cgame\cg_effects.c -cgame\cg_ents.c -cgame\cg_event.c -cgame\cg_info.c -cgame\cg_localents.c -cgame\cg_main.c -cgame\cg_marks.c -cgame\cg_players.c -cgame\cg_playerstate.c -cgame\cg_predict.c -cgame\cg_scoreboard.c -cgame\cg_servercmds.c -cgame\cg_snapshot.c -cgame\cg_syscalls.c -cgame\cg_view.c -cgame\cg_weapons.c +opts.lnt + +game\bg_misc.c +game\bg_pmove.c +game\q_math.c +game\q_shared.c +cgame\cg_consolecmds.c +cgame\cg_draw.c +cgame\cg_drawtools.c +cgame\cg_effects.c +cgame\cg_ents.c +cgame\cg_event.c +cgame\cg_info.c +cgame\cg_localents.c +cgame\cg_main.c +cgame\cg_marks.c +cgame\cg_players.c +cgame\cg_playerstate.c +cgame\cg_predict.c +cgame\cg_scoreboard.c +cgame\cg_servercmds.c +cgame\cg_snapshot.c +cgame\cg_syscalls.c +cgame\cg_view.c +cgame\cg_weapons.c diff --git a/code/cgame/Conscript b/code/cgame/Conscript index 887a2a1..0cac8ff 100755 --- a/code/cgame/Conscript +++ b/code/cgame/Conscript @@ -1,138 +1,138 @@ -# cgame building -# builds the cgame for vanilla Q3 and TA - -# there are slight differences between Q3 and TA build: -# -DMISSIONPACK -# TA has cg_newdraw.c and ../ui/ui_shared.c -# the config is passed in the imported variable TARGET_DIR - -# qvm building against native: -# only native has cg_syscalls.c -# only qvm has ../game/bg_lib.c -# qvm uses a custom cg_syscalls.asm with equ stubs - -Import qw( BASE_CFLAGS TARGET_DIR INSTALL_DIR NO_VM NO_SO CC CXX LINK ); - -$env = new cons( - # the code has the very bad habit of doing things like #include "../ui/ui_shared.h" - # this seems to confuse the dependency analysis, explicit toplevel includes seem to fix - CPPPATH => '#cgame:#game:#q3_ui', - CC => $CC, - CXX => $CXX, - LINK => $LINK, - ENV => { PATH => $ENV{PATH}, HOME => $ENV{HOME} }, - CFLAGS => $BASE_CFLAGS . '-fPIC', - LDFLAGS => '-shared -ldl -lm' -); - -# for TA, use -DMISSIONPACK -%ta_env_hash = $env->copy( - CPPPATH => '#cgame:#game:#ui' - ); -$ta_env_hash{CFLAGS} = '-DMISSIONPACK ' . $ta_env_hash{CFLAGS}; -$ta_env = new cons(%ta_env_hash); - -# qvm building -# we heavily customize the cons environment -$vm_env = new cons( - # the code has the very bad habit of doing things like #include "../ui/ui_shared.h" - # this seems to confuse the dependency analysis, explicit toplevel includes seem to fix - CPPPATH => '#cgame:#game:#q3_ui', - CC => 'q3lcc', - CCCOM => '%CC %CFLAGS %_IFLAGS -c %< -o %>', - SUFOBJ => '.asm', - LINK => 'q3asm', - CFLAGS => '-DQ3_VM -DCGAME -S -Wf-target=bytecode -Wf-g', - # need to know where to find the compiler tools - ENV => { PATH => $ENV{PATH} . ":./qvmtools", }, -); - -# TA qvm building -%vm_ta_env_hash = $vm_env->copy( - CPPPATH => '#cgame:#game:#ui' - ); -$vm_ta_env_hash{CFLAGS} = '-DMISSIONPACK ' . $vm_ta_env_hash{CFLAGS}; -$vm_ta_env = new cons(%vm_ta_env_hash); - -# the file with vmMain function MUST be the first one of the list -@FILES = qw( - cg_main.c - ../game/bg_misc.c - ../game/bg_pmove.c - ../game/bg_slidemove.c - ../game/q_math.c - ../game/q_shared.c - cg_consolecmds.c - cg_draw.c - cg_drawtools.c - cg_effects.c - cg_ents.c - cg_event.c - cg_info.c - cg_localents.c - cg_marks.c - cg_players.c - cg_playerstate.c - cg_predict.c - cg_scoreboard.c - cg_servercmds.c - cg_snapshot.c - cg_view.c - cg_weapons.c - ); -$FILESREF = \@FILES; - -# only in .so -# (VM uses a custom .asm with equ stubs) -@SO_FILES = qw( - cg_syscalls.c - ); -$SO_FILESREF = \@SO_FILES; - -# only for VM -@VM_FILES = qw( - ../game/bg_lib.c - cg_syscalls.asm - ); -$VM_FILESREF = \@VM_FILES; - -# common additionals for TA -@TA_FILES = qw( - cg_newdraw.c - ../ui/ui_shared.c - ); -$TA_FILESREF = \@TA_FILES; - -# FIXME CPU string -if ($TARGET_DIR eq 'Q3') -{ - if ($NO_SO eq 0) - { - Program $env 'cgamei386.so', @$FILESREF, @$SO_FILESREF; - Install $env $INSTALL_DIR, 'cgamei386.so'; - } - if ($NO_VM eq 0) - { - Depends $vm_env 'cgame.qvm', '#qvmtools/q3lcc'; - Depends $vm_env 'cgame.qvm', '#qvmtools/q3asm'; - Program $vm_env 'cgame.qvm', @$FILESREF, @$VM_FILESREF; - Install $vm_env $INSTALL_DIR . '/vm', 'cgame.qvm'; - } -} -else -{ - if ($NO_SO eq 0) - { - Program $ta_env 'cgamei386.so', - @$FILESREF, @$SO_FILESREF, @$TA_FILESREF; - Install $ta_env $INSTALL_DIR, 'cgamei386.so'; - } - if ($NO_VM eq 0) - { - Depends $vm_env 'cgame.qvm', '#qvmtools/q3lcc'; - Depends $vm_env 'cgame.qvm', '#qvmtools/q3asm'; - Program $vm_ta_env 'cgame.qvm', - @$FILESREF, @$VM_FILESREF, @$TA_FILESREF; - Install $vm_ta_env $INSTALL_DIR . '/vm', 'cgame.qvm'; - } -} +# cgame building +# builds the cgame for vanilla Q3 and TA + +# there are slight differences between Q3 and TA build: +# -DMISSIONPACK +# TA has cg_newdraw.c and ../ui/ui_shared.c +# the config is passed in the imported variable TARGET_DIR + +# qvm building against native: +# only native has cg_syscalls.c +# only qvm has ../game/bg_lib.c +# qvm uses a custom cg_syscalls.asm with equ stubs + +Import qw( BASE_CFLAGS TARGET_DIR INSTALL_DIR NO_VM NO_SO CC CXX LINK ); + +$env = new cons( + # the code has the very bad habit of doing things like #include "../ui/ui_shared.h" + # this seems to confuse the dependency analysis, explicit toplevel includes seem to fix + CPPPATH => '#cgame:#game:#q3_ui', + CC => $CC, + CXX => $CXX, + LINK => $LINK, + ENV => { PATH => $ENV{PATH}, HOME => $ENV{HOME} }, + CFLAGS => $BASE_CFLAGS . '-fPIC', + LDFLAGS => '-shared -ldl -lm' +); + +# for TA, use -DMISSIONPACK +%ta_env_hash = $env->copy( + CPPPATH => '#cgame:#game:#ui' + ); +$ta_env_hash{CFLAGS} = '-DMISSIONPACK ' . $ta_env_hash{CFLAGS}; +$ta_env = new cons(%ta_env_hash); + +# qvm building +# we heavily customize the cons environment +$vm_env = new cons( + # the code has the very bad habit of doing things like #include "../ui/ui_shared.h" + # this seems to confuse the dependency analysis, explicit toplevel includes seem to fix + CPPPATH => '#cgame:#game:#q3_ui', + CC => 'q3lcc', + CCCOM => '%CC %CFLAGS %_IFLAGS -c %< -o %>', + SUFOBJ => '.asm', + LINK => 'q3asm', + CFLAGS => '-DQ3_VM -DCGAME -S -Wf-target=bytecode -Wf-g', + # need to know where to find the compiler tools + ENV => { PATH => $ENV{PATH} . ":./qvmtools", }, +); + +# TA qvm building +%vm_ta_env_hash = $vm_env->copy( + CPPPATH => '#cgame:#game:#ui' + ); +$vm_ta_env_hash{CFLAGS} = '-DMISSIONPACK ' . $vm_ta_env_hash{CFLAGS}; +$vm_ta_env = new cons(%vm_ta_env_hash); + +# the file with vmMain function MUST be the first one of the list +@FILES = qw( + cg_main.c + ../game/bg_misc.c + ../game/bg_pmove.c + ../game/bg_slidemove.c + ../game/q_math.c + ../game/q_shared.c + cg_consolecmds.c + cg_draw.c + cg_drawtools.c + cg_effects.c + cg_ents.c + cg_event.c + cg_info.c + cg_localents.c + cg_marks.c + cg_players.c + cg_playerstate.c + cg_predict.c + cg_scoreboard.c + cg_servercmds.c + cg_snapshot.c + cg_view.c + cg_weapons.c + ); +$FILESREF = \@FILES; + +# only in .so +# (VM uses a custom .asm with equ stubs) +@SO_FILES = qw( + cg_syscalls.c + ); +$SO_FILESREF = \@SO_FILES; + +# only for VM +@VM_FILES = qw( + ../game/bg_lib.c + cg_syscalls.asm + ); +$VM_FILESREF = \@VM_FILES; + +# common additionals for TA +@TA_FILES = qw( + cg_newdraw.c + ../ui/ui_shared.c + ); +$TA_FILESREF = \@TA_FILES; + +# FIXME CPU string +if ($TARGET_DIR eq 'Q3') +{ + if ($NO_SO eq 0) + { + Program $env 'cgamei386.so', @$FILESREF, @$SO_FILESREF; + Install $env $INSTALL_DIR, 'cgamei386.so'; + } + if ($NO_VM eq 0) + { + Depends $vm_env 'cgame.qvm', '#qvmtools/q3lcc'; + Depends $vm_env 'cgame.qvm', '#qvmtools/q3asm'; + Program $vm_env 'cgame.qvm', @$FILESREF, @$VM_FILESREF; + Install $vm_env $INSTALL_DIR . '/vm', 'cgame.qvm'; + } +} +else +{ + if ($NO_SO eq 0) + { + Program $ta_env 'cgamei386.so', + @$FILESREF, @$SO_FILESREF, @$TA_FILESREF; + Install $ta_env $INSTALL_DIR, 'cgamei386.so'; + } + if ($NO_VM eq 0) + { + Depends $vm_env 'cgame.qvm', '#qvmtools/q3lcc'; + Depends $vm_env 'cgame.qvm', '#qvmtools/q3asm'; + Program $vm_ta_env 'cgame.qvm', + @$FILESREF, @$VM_FILESREF, @$TA_FILESREF; + Install $vm_ta_env $INSTALL_DIR . '/vm', 'cgame.qvm'; + } +} diff --git a/code/cgame/cg_consolecmds.c b/code/cgame/cg_consolecmds.c index 6f13a22..584ebd4 100755 --- a/code/cgame/cg_consolecmds.c +++ b/code/cgame/cg_consolecmds.c @@ -1,578 +1,578 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// cg_consolecmds.c -- text commands typed in at the local console, or -// executed by a key binding - -#include "cg_local.h" -#include "../ui/ui_shared.h" -#ifdef MISSIONPACK -extern menuDef_t *menuScoreboard; -#endif - - - -void CG_TargetCommand_f( void ) { - int targetNum; - char test[4]; - - targetNum = CG_CrosshairPlayer(); - if (!targetNum ) { - return; - } - - trap_Argv( 1, test, 4 ); - trap_SendConsoleCommand( va( "gc %i %i", targetNum, atoi( test ) ) ); -} - - - -/* -================= -CG_SizeUp_f - -Keybinding command -================= -*/ -static void CG_SizeUp_f (void) { - trap_Cvar_Set("cg_viewsize", va("%i",(int)(cg_viewsize.integer+10))); -} - - -/* -================= -CG_SizeDown_f - -Keybinding command -================= -*/ -static void CG_SizeDown_f (void) { - trap_Cvar_Set("cg_viewsize", va("%i",(int)(cg_viewsize.integer-10))); -} - - -/* -============= -CG_Viewpos_f - -Debugging command to print the current position -============= -*/ -static void CG_Viewpos_f (void) { - CG_Printf ("(%i %i %i) : %i\n", (int)cg.refdef.vieworg[0], - (int)cg.refdef.vieworg[1], (int)cg.refdef.vieworg[2], - (int)cg.refdefViewAngles[YAW]); -} - - -static void CG_ScoresDown_f( void ) { - -#ifdef MISSIONPACK - CG_BuildSpectatorString(); -#endif - if ( cg.scoresRequestTime + 2000 < cg.time ) { - // the scores are more than two seconds out of data, - // so request new ones - cg.scoresRequestTime = cg.time; - trap_SendClientCommand( "score" ); - - // leave the current scores up if they were already - // displayed, but if this is the first hit, clear them out - if ( !cg.showScores ) { - cg.showScores = qtrue; - cg.numScores = 0; - } - } else { - // show the cached contents even if they just pressed if it - // is within two seconds - cg.showScores = qtrue; - } -} - -static void CG_ScoresUp_f( void ) { - if ( cg.showScores ) { - cg.showScores = qfalse; - cg.scoreFadeTime = cg.time; - } -} - -#ifdef MISSIONPACK -extern menuDef_t *menuScoreboard; -void Menu_Reset(); // FIXME: add to right include file - -static void CG_LoadHud_f( void) { - char buff[1024]; - const char *hudSet; - memset(buff, 0, sizeof(buff)); - - String_Init(); - Menu_Reset(); - - trap_Cvar_VariableStringBuffer("cg_hudFiles", buff, sizeof(buff)); - hudSet = buff; - if (hudSet[0] == '\0') { - hudSet = "ui/hud.txt"; - } - - CG_LoadMenus(hudSet); - menuScoreboard = NULL; -} - - -static void CG_scrollScoresDown_f( void) { - if (menuScoreboard && cg.scoreBoardShowing) { - Menu_ScrollFeeder(menuScoreboard, FEEDER_SCOREBOARD, qtrue); - Menu_ScrollFeeder(menuScoreboard, FEEDER_REDTEAM_LIST, qtrue); - Menu_ScrollFeeder(menuScoreboard, FEEDER_BLUETEAM_LIST, qtrue); - } -} - - -static void CG_scrollScoresUp_f( void) { - if (menuScoreboard && cg.scoreBoardShowing) { - Menu_ScrollFeeder(menuScoreboard, FEEDER_SCOREBOARD, qfalse); - Menu_ScrollFeeder(menuScoreboard, FEEDER_REDTEAM_LIST, qfalse); - Menu_ScrollFeeder(menuScoreboard, FEEDER_BLUETEAM_LIST, qfalse); - } -} - - -static void CG_spWin_f( void) { - trap_Cvar_Set("cg_cameraOrbit", "2"); - trap_Cvar_Set("cg_cameraOrbitDelay", "35"); - trap_Cvar_Set("cg_thirdPerson", "1"); - trap_Cvar_Set("cg_thirdPersonAngle", "0"); - trap_Cvar_Set("cg_thirdPersonRange", "100"); - CG_AddBufferedSound(cgs.media.winnerSound); - //trap_S_StartLocalSound(cgs.media.winnerSound, CHAN_ANNOUNCER); - CG_CenterPrint("YOU WIN!", SCREEN_HEIGHT * .30, 0); -} - -static void CG_spLose_f( void) { - trap_Cvar_Set("cg_cameraOrbit", "2"); - trap_Cvar_Set("cg_cameraOrbitDelay", "35"); - trap_Cvar_Set("cg_thirdPerson", "1"); - trap_Cvar_Set("cg_thirdPersonAngle", "0"); - trap_Cvar_Set("cg_thirdPersonRange", "100"); - CG_AddBufferedSound(cgs.media.loserSound); - //trap_S_StartLocalSound(cgs.media.loserSound, CHAN_ANNOUNCER); - CG_CenterPrint("YOU LOSE...", SCREEN_HEIGHT * .30, 0); -} - -#endif - -static void CG_TellTarget_f( void ) { - int clientNum; - char command[128]; - char message[128]; - - clientNum = CG_CrosshairPlayer(); - if ( clientNum == -1 ) { - return; - } - - trap_Args( message, 128 ); - Com_sprintf( command, 128, "tell %i %s", clientNum, message ); - trap_SendClientCommand( command ); -} - -static void CG_TellAttacker_f( void ) { - int clientNum; - char command[128]; - char message[128]; - - clientNum = CG_LastAttacker(); - if ( clientNum == -1 ) { - return; - } - - trap_Args( message, 128 ); - Com_sprintf( command, 128, "tell %i %s", clientNum, message ); - trap_SendClientCommand( command ); -} - -static void CG_VoiceTellTarget_f( void ) { - int clientNum; - char command[128]; - char message[128]; - - clientNum = CG_CrosshairPlayer(); - if ( clientNum == -1 ) { - return; - } - - trap_Args( message, 128 ); - Com_sprintf( command, 128, "vtell %i %s", clientNum, message ); - trap_SendClientCommand( command ); -} - -static void CG_VoiceTellAttacker_f( void ) { - int clientNum; - char command[128]; - char message[128]; - - clientNum = CG_LastAttacker(); - if ( clientNum == -1 ) { - return; - } - - trap_Args( message, 128 ); - Com_sprintf( command, 128, "vtell %i %s", clientNum, message ); - trap_SendClientCommand( command ); -} - -#ifdef MISSIONPACK -static void CG_NextTeamMember_f( void ) { - CG_SelectNextPlayer(); -} - -static void CG_PrevTeamMember_f( void ) { - CG_SelectPrevPlayer(); -} - -// ASS U ME's enumeration order as far as task specific orders, OFFENSE is zero, CAMP is last -// -static void CG_NextOrder_f( void ) { - clientInfo_t *ci = cgs.clientinfo + cg.snap->ps.clientNum; - if (ci) { - if (!ci->teamLeader && sortedTeamPlayers[cg_currentSelectedPlayer.integer] != cg.snap->ps.clientNum) { - return; - } - } - if (cgs.currentOrder < TEAMTASK_CAMP) { - cgs.currentOrder++; - - if (cgs.currentOrder == TEAMTASK_RETRIEVE) { - if (!CG_OtherTeamHasFlag()) { - cgs.currentOrder++; - } - } - - if (cgs.currentOrder == TEAMTASK_ESCORT) { - if (!CG_YourTeamHasFlag()) { - cgs.currentOrder++; - } - } - - } else { - cgs.currentOrder = TEAMTASK_OFFENSE; - } - cgs.orderPending = qtrue; - cgs.orderTime = cg.time + 3000; -} - - -static void CG_ConfirmOrder_f (void ) { - trap_SendConsoleCommand(va("cmd vtell %d %s\n", cgs.acceptLeader, VOICECHAT_YES)); - trap_SendConsoleCommand("+button5; wait; -button5"); - if (cg.time < cgs.acceptOrderTime) { - trap_SendClientCommand(va("teamtask %d\n", cgs.acceptTask)); - cgs.acceptOrderTime = 0; - } -} - -static void CG_DenyOrder_f (void ) { - trap_SendConsoleCommand(va("cmd vtell %d %s\n", cgs.acceptLeader, VOICECHAT_NO)); - trap_SendConsoleCommand("+button6; wait; -button6"); - if (cg.time < cgs.acceptOrderTime) { - cgs.acceptOrderTime = 0; - } -} - -static void CG_TaskOffense_f (void ) { - if (cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF) { - trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONGETFLAG)); - } else { - trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONOFFENSE)); - } - trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_OFFENSE)); -} - -static void CG_TaskDefense_f (void ) { - trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONDEFENSE)); - trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_DEFENSE)); -} - -static void CG_TaskPatrol_f (void ) { - trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONPATROL)); - trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_PATROL)); -} - -static void CG_TaskCamp_f (void ) { - trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONCAMPING)); - trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_CAMP)); -} - -static void CG_TaskFollow_f (void ) { - trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONFOLLOW)); - trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_FOLLOW)); -} - -static void CG_TaskRetrieve_f (void ) { - trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONRETURNFLAG)); - trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_RETRIEVE)); -} - -static void CG_TaskEscort_f (void ) { - trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONFOLLOWCARRIER)); - trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_ESCORT)); -} - -static void CG_TaskOwnFlag_f (void ) { - trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_IHAVEFLAG)); -} - -static void CG_TauntKillInsult_f (void ) { - trap_SendConsoleCommand("cmd vsay kill_insult\n"); -} - -static void CG_TauntPraise_f (void ) { - trap_SendConsoleCommand("cmd vsay praise\n"); -} - -static void CG_TauntTaunt_f (void ) { - trap_SendConsoleCommand("cmd vtaunt\n"); -} - -static void CG_TauntDeathInsult_f (void ) { - trap_SendConsoleCommand("cmd vsay death_insult\n"); -} - -static void CG_TauntGauntlet_f (void ) { - trap_SendConsoleCommand("cmd vsay kill_guantlet\n"); -} - -static void CG_TaskSuicide_f (void ) { - int clientNum; - char command[128]; - - clientNum = CG_CrosshairPlayer(); - if ( clientNum == -1 ) { - return; - } - - Com_sprintf( command, 128, "tell %i suicide", clientNum ); - trap_SendClientCommand( command ); -} - - - -/* -================== -CG_TeamMenu_f -================== -*/ -/* -static void CG_TeamMenu_f( void ) { - if (trap_Key_GetCatcher() & KEYCATCH_CGAME) { - CG_EventHandling(CGAME_EVENT_NONE); - trap_Key_SetCatcher(0); - } else { - CG_EventHandling(CGAME_EVENT_TEAMMENU); - //trap_Key_SetCatcher(KEYCATCH_CGAME); - } -} -*/ - -/* -================== -CG_EditHud_f -================== -*/ -/* -static void CG_EditHud_f( void ) { - //cls.keyCatchers ^= KEYCATCH_CGAME; - //VM_Call (cgvm, CG_EVENT_HANDLING, (cls.keyCatchers & KEYCATCH_CGAME) ? CGAME_EVENT_EDITHUD : CGAME_EVENT_NONE); -} -*/ - -#endif - -/* -================== -CG_StartOrbit_f -================== -*/ - -static void CG_StartOrbit_f( void ) { - char var[MAX_TOKEN_CHARS]; - - trap_Cvar_VariableStringBuffer( "developer", var, sizeof( var ) ); - if ( !atoi(var) ) { - return; - } - if (cg_cameraOrbit.value != 0) { - trap_Cvar_Set ("cg_cameraOrbit", "0"); - trap_Cvar_Set("cg_thirdPerson", "0"); - } else { - trap_Cvar_Set("cg_cameraOrbit", "5"); - trap_Cvar_Set("cg_thirdPerson", "1"); - trap_Cvar_Set("cg_thirdPersonAngle", "0"); - trap_Cvar_Set("cg_thirdPersonRange", "100"); - } -} - -/* -static void CG_Camera_f( void ) { - char name[1024]; - trap_Argv( 1, name, sizeof(name)); - if (trap_loadCamera(name)) { - cg.cameraMode = qtrue; - trap_startCamera(cg.time); - } else { - CG_Printf ("Unable to load camera %s\n",name); - } -} -*/ - - -typedef struct { - char *cmd; - void (*function)(void); -} consoleCommand_t; - -static consoleCommand_t commands[] = { - { "testgun", CG_TestGun_f }, - { "testmodel", CG_TestModel_f }, - { "nextframe", CG_TestModelNextFrame_f }, - { "prevframe", CG_TestModelPrevFrame_f }, - { "nextskin", CG_TestModelNextSkin_f }, - { "prevskin", CG_TestModelPrevSkin_f }, - { "viewpos", CG_Viewpos_f }, - { "+scores", CG_ScoresDown_f }, - { "-scores", CG_ScoresUp_f }, - { "+zoom", CG_ZoomDown_f }, - { "-zoom", CG_ZoomUp_f }, - { "sizeup", CG_SizeUp_f }, - { "sizedown", CG_SizeDown_f }, - { "weapnext", CG_NextWeapon_f }, - { "weapprev", CG_PrevWeapon_f }, - { "weapon", CG_Weapon_f }, - { "tell_target", CG_TellTarget_f }, - { "tell_attacker", CG_TellAttacker_f }, - { "vtell_target", CG_VoiceTellTarget_f }, - { "vtell_attacker", CG_VoiceTellAttacker_f }, - { "tcmd", CG_TargetCommand_f }, -#ifdef MISSIONPACK - { "loadhud", CG_LoadHud_f }, - { "nextTeamMember", CG_NextTeamMember_f }, - { "prevTeamMember", CG_PrevTeamMember_f }, - { "nextOrder", CG_NextOrder_f }, - { "confirmOrder", CG_ConfirmOrder_f }, - { "denyOrder", CG_DenyOrder_f }, - { "taskOffense", CG_TaskOffense_f }, - { "taskDefense", CG_TaskDefense_f }, - { "taskPatrol", CG_TaskPatrol_f }, - { "taskCamp", CG_TaskCamp_f }, - { "taskFollow", CG_TaskFollow_f }, - { "taskRetrieve", CG_TaskRetrieve_f }, - { "taskEscort", CG_TaskEscort_f }, - { "taskSuicide", CG_TaskSuicide_f }, - { "taskOwnFlag", CG_TaskOwnFlag_f }, - { "tauntKillInsult", CG_TauntKillInsult_f }, - { "tauntPraise", CG_TauntPraise_f }, - { "tauntTaunt", CG_TauntTaunt_f }, - { "tauntDeathInsult", CG_TauntDeathInsult_f }, - { "tauntGauntlet", CG_TauntGauntlet_f }, - { "spWin", CG_spWin_f }, - { "spLose", CG_spLose_f }, - { "scoresDown", CG_scrollScoresDown_f }, - { "scoresUp", CG_scrollScoresUp_f }, -#endif - { "startOrbit", CG_StartOrbit_f }, - //{ "camera", CG_Camera_f }, - { "loaddeferred", CG_LoadDeferredPlayers } -}; - - -/* -================= -CG_ConsoleCommand - -The string has been tokenized and can be retrieved with -Cmd_Argc() / Cmd_Argv() -================= -*/ -qboolean CG_ConsoleCommand( void ) { - const char *cmd; - int i; - - cmd = CG_Argv(0); - - for ( i = 0 ; i < sizeof( commands ) / sizeof( commands[0] ) ; i++ ) { - if ( !Q_stricmp( cmd, commands[i].cmd ) ) { - commands[i].function(); - return qtrue; - } - } - - return qfalse; -} - - -/* -================= -CG_InitConsoleCommands - -Let the client system know about all of our commands -so it can perform tab completion -================= -*/ -void CG_InitConsoleCommands( void ) { - int i; - - for ( i = 0 ; i < sizeof( commands ) / sizeof( commands[0] ) ; i++ ) { - trap_AddCommand( commands[i].cmd ); - } - - // - // the game server will interpret these commands, which will be automatically - // forwarded to the server after they are not recognized locally - // - trap_AddCommand ("kill"); - trap_AddCommand ("say"); - trap_AddCommand ("say_team"); - trap_AddCommand ("tell"); - trap_AddCommand ("vsay"); - trap_AddCommand ("vsay_team"); - trap_AddCommand ("vtell"); - trap_AddCommand ("vtaunt"); - trap_AddCommand ("vosay"); - trap_AddCommand ("vosay_team"); - trap_AddCommand ("votell"); - trap_AddCommand ("give"); - trap_AddCommand ("god"); - trap_AddCommand ("notarget"); - trap_AddCommand ("noclip"); - trap_AddCommand ("team"); - trap_AddCommand ("follow"); - trap_AddCommand ("levelshot"); - trap_AddCommand ("addbot"); - trap_AddCommand ("setviewpos"); - trap_AddCommand ("callvote"); - trap_AddCommand ("vote"); - trap_AddCommand ("callteamvote"); - trap_AddCommand ("teamvote"); - trap_AddCommand ("stats"); - trap_AddCommand ("teamtask"); - trap_AddCommand ("loaddefered"); // spelled wrong, but not changing for demo -} +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// cg_consolecmds.c -- text commands typed in at the local console, or +// executed by a key binding + +#include "cg_local.h" +#include "../ui/ui_shared.h" +#ifdef MISSIONPACK +extern menuDef_t *menuScoreboard; +#endif + + + +void CG_TargetCommand_f( void ) { + int targetNum; + char test[4]; + + targetNum = CG_CrosshairPlayer(); + if (!targetNum ) { + return; + } + + trap_Argv( 1, test, 4 ); + trap_SendConsoleCommand( va( "gc %i %i", targetNum, atoi( test ) ) ); +} + + + +/* +================= +CG_SizeUp_f + +Keybinding command +================= +*/ +static void CG_SizeUp_f (void) { + trap_Cvar_Set("cg_viewsize", va("%i",(int)(cg_viewsize.integer+10))); +} + + +/* +================= +CG_SizeDown_f + +Keybinding command +================= +*/ +static void CG_SizeDown_f (void) { + trap_Cvar_Set("cg_viewsize", va("%i",(int)(cg_viewsize.integer-10))); +} + + +/* +============= +CG_Viewpos_f + +Debugging command to print the current position +============= +*/ +static void CG_Viewpos_f (void) { + CG_Printf ("(%i %i %i) : %i\n", (int)cg.refdef.vieworg[0], + (int)cg.refdef.vieworg[1], (int)cg.refdef.vieworg[2], + (int)cg.refdefViewAngles[YAW]); +} + + +static void CG_ScoresDown_f( void ) { + +#ifdef MISSIONPACK + CG_BuildSpectatorString(); +#endif + if ( cg.scoresRequestTime + 2000 < cg.time ) { + // the scores are more than two seconds out of data, + // so request new ones + cg.scoresRequestTime = cg.time; + trap_SendClientCommand( "score" ); + + // leave the current scores up if they were already + // displayed, but if this is the first hit, clear them out + if ( !cg.showScores ) { + cg.showScores = qtrue; + cg.numScores = 0; + } + } else { + // show the cached contents even if they just pressed if it + // is within two seconds + cg.showScores = qtrue; + } +} + +static void CG_ScoresUp_f( void ) { + if ( cg.showScores ) { + cg.showScores = qfalse; + cg.scoreFadeTime = cg.time; + } +} + +#ifdef MISSIONPACK +extern menuDef_t *menuScoreboard; +void Menu_Reset(); // FIXME: add to right include file + +static void CG_LoadHud_f( void) { + char buff[1024]; + const char *hudSet; + memset(buff, 0, sizeof(buff)); + + String_Init(); + Menu_Reset(); + + trap_Cvar_VariableStringBuffer("cg_hudFiles", buff, sizeof(buff)); + hudSet = buff; + if (hudSet[0] == '\0') { + hudSet = "ui/hud.txt"; + } + + CG_LoadMenus(hudSet); + menuScoreboard = NULL; +} + + +static void CG_scrollScoresDown_f( void) { + if (menuScoreboard && cg.scoreBoardShowing) { + Menu_ScrollFeeder(menuScoreboard, FEEDER_SCOREBOARD, qtrue); + Menu_ScrollFeeder(menuScoreboard, FEEDER_REDTEAM_LIST, qtrue); + Menu_ScrollFeeder(menuScoreboard, FEEDER_BLUETEAM_LIST, qtrue); + } +} + + +static void CG_scrollScoresUp_f( void) { + if (menuScoreboard && cg.scoreBoardShowing) { + Menu_ScrollFeeder(menuScoreboard, FEEDER_SCOREBOARD, qfalse); + Menu_ScrollFeeder(menuScoreboard, FEEDER_REDTEAM_LIST, qfalse); + Menu_ScrollFeeder(menuScoreboard, FEEDER_BLUETEAM_LIST, qfalse); + } +} + + +static void CG_spWin_f( void) { + trap_Cvar_Set("cg_cameraOrbit", "2"); + trap_Cvar_Set("cg_cameraOrbitDelay", "35"); + trap_Cvar_Set("cg_thirdPerson", "1"); + trap_Cvar_Set("cg_thirdPersonAngle", "0"); + trap_Cvar_Set("cg_thirdPersonRange", "100"); + CG_AddBufferedSound(cgs.media.winnerSound); + //trap_S_StartLocalSound(cgs.media.winnerSound, CHAN_ANNOUNCER); + CG_CenterPrint("YOU WIN!", SCREEN_HEIGHT * .30, 0); +} + +static void CG_spLose_f( void) { + trap_Cvar_Set("cg_cameraOrbit", "2"); + trap_Cvar_Set("cg_cameraOrbitDelay", "35"); + trap_Cvar_Set("cg_thirdPerson", "1"); + trap_Cvar_Set("cg_thirdPersonAngle", "0"); + trap_Cvar_Set("cg_thirdPersonRange", "100"); + CG_AddBufferedSound(cgs.media.loserSound); + //trap_S_StartLocalSound(cgs.media.loserSound, CHAN_ANNOUNCER); + CG_CenterPrint("YOU LOSE...", SCREEN_HEIGHT * .30, 0); +} + +#endif + +static void CG_TellTarget_f( void ) { + int clientNum; + char command[128]; + char message[128]; + + clientNum = CG_CrosshairPlayer(); + if ( clientNum == -1 ) { + return; + } + + trap_Args( message, 128 ); + Com_sprintf( command, 128, "tell %i %s", clientNum, message ); + trap_SendClientCommand( command ); +} + +static void CG_TellAttacker_f( void ) { + int clientNum; + char command[128]; + char message[128]; + + clientNum = CG_LastAttacker(); + if ( clientNum == -1 ) { + return; + } + + trap_Args( message, 128 ); + Com_sprintf( command, 128, "tell %i %s", clientNum, message ); + trap_SendClientCommand( command ); +} + +static void CG_VoiceTellTarget_f( void ) { + int clientNum; + char command[128]; + char message[128]; + + clientNum = CG_CrosshairPlayer(); + if ( clientNum == -1 ) { + return; + } + + trap_Args( message, 128 ); + Com_sprintf( command, 128, "vtell %i %s", clientNum, message ); + trap_SendClientCommand( command ); +} + +static void CG_VoiceTellAttacker_f( void ) { + int clientNum; + char command[128]; + char message[128]; + + clientNum = CG_LastAttacker(); + if ( clientNum == -1 ) { + return; + } + + trap_Args( message, 128 ); + Com_sprintf( command, 128, "vtell %i %s", clientNum, message ); + trap_SendClientCommand( command ); +} + +#ifdef MISSIONPACK +static void CG_NextTeamMember_f( void ) { + CG_SelectNextPlayer(); +} + +static void CG_PrevTeamMember_f( void ) { + CG_SelectPrevPlayer(); +} + +// ASS U ME's enumeration order as far as task specific orders, OFFENSE is zero, CAMP is last +// +static void CG_NextOrder_f( void ) { + clientInfo_t *ci = cgs.clientinfo + cg.snap->ps.clientNum; + if (ci) { + if (!ci->teamLeader && sortedTeamPlayers[cg_currentSelectedPlayer.integer] != cg.snap->ps.clientNum) { + return; + } + } + if (cgs.currentOrder < TEAMTASK_CAMP) { + cgs.currentOrder++; + + if (cgs.currentOrder == TEAMTASK_RETRIEVE) { + if (!CG_OtherTeamHasFlag()) { + cgs.currentOrder++; + } + } + + if (cgs.currentOrder == TEAMTASK_ESCORT) { + if (!CG_YourTeamHasFlag()) { + cgs.currentOrder++; + } + } + + } else { + cgs.currentOrder = TEAMTASK_OFFENSE; + } + cgs.orderPending = qtrue; + cgs.orderTime = cg.time + 3000; +} + + +static void CG_ConfirmOrder_f (void ) { + trap_SendConsoleCommand(va("cmd vtell %d %s\n", cgs.acceptLeader, VOICECHAT_YES)); + trap_SendConsoleCommand("+button5; wait; -button5"); + if (cg.time < cgs.acceptOrderTime) { + trap_SendClientCommand(va("teamtask %d\n", cgs.acceptTask)); + cgs.acceptOrderTime = 0; + } +} + +static void CG_DenyOrder_f (void ) { + trap_SendConsoleCommand(va("cmd vtell %d %s\n", cgs.acceptLeader, VOICECHAT_NO)); + trap_SendConsoleCommand("+button6; wait; -button6"); + if (cg.time < cgs.acceptOrderTime) { + cgs.acceptOrderTime = 0; + } +} + +static void CG_TaskOffense_f (void ) { + if (cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF) { + trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONGETFLAG)); + } else { + trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONOFFENSE)); + } + trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_OFFENSE)); +} + +static void CG_TaskDefense_f (void ) { + trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONDEFENSE)); + trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_DEFENSE)); +} + +static void CG_TaskPatrol_f (void ) { + trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONPATROL)); + trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_PATROL)); +} + +static void CG_TaskCamp_f (void ) { + trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONCAMPING)); + trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_CAMP)); +} + +static void CG_TaskFollow_f (void ) { + trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONFOLLOW)); + trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_FOLLOW)); +} + +static void CG_TaskRetrieve_f (void ) { + trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONRETURNFLAG)); + trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_RETRIEVE)); +} + +static void CG_TaskEscort_f (void ) { + trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONFOLLOWCARRIER)); + trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_ESCORT)); +} + +static void CG_TaskOwnFlag_f (void ) { + trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_IHAVEFLAG)); +} + +static void CG_TauntKillInsult_f (void ) { + trap_SendConsoleCommand("cmd vsay kill_insult\n"); +} + +static void CG_TauntPraise_f (void ) { + trap_SendConsoleCommand("cmd vsay praise\n"); +} + +static void CG_TauntTaunt_f (void ) { + trap_SendConsoleCommand("cmd vtaunt\n"); +} + +static void CG_TauntDeathInsult_f (void ) { + trap_SendConsoleCommand("cmd vsay death_insult\n"); +} + +static void CG_TauntGauntlet_f (void ) { + trap_SendConsoleCommand("cmd vsay kill_guantlet\n"); +} + +static void CG_TaskSuicide_f (void ) { + int clientNum; + char command[128]; + + clientNum = CG_CrosshairPlayer(); + if ( clientNum == -1 ) { + return; + } + + Com_sprintf( command, 128, "tell %i suicide", clientNum ); + trap_SendClientCommand( command ); +} + + + +/* +================== +CG_TeamMenu_f +================== +*/ +/* +static void CG_TeamMenu_f( void ) { + if (trap_Key_GetCatcher() & KEYCATCH_CGAME) { + CG_EventHandling(CGAME_EVENT_NONE); + trap_Key_SetCatcher(0); + } else { + CG_EventHandling(CGAME_EVENT_TEAMMENU); + //trap_Key_SetCatcher(KEYCATCH_CGAME); + } +} +*/ + +/* +================== +CG_EditHud_f +================== +*/ +/* +static void CG_EditHud_f( void ) { + //cls.keyCatchers ^= KEYCATCH_CGAME; + //VM_Call (cgvm, CG_EVENT_HANDLING, (cls.keyCatchers & KEYCATCH_CGAME) ? CGAME_EVENT_EDITHUD : CGAME_EVENT_NONE); +} +*/ + +#endif + +/* +================== +CG_StartOrbit_f +================== +*/ + +static void CG_StartOrbit_f( void ) { + char var[MAX_TOKEN_CHARS]; + + trap_Cvar_VariableStringBuffer( "developer", var, sizeof( var ) ); + if ( !atoi(var) ) { + return; + } + if (cg_cameraOrbit.value != 0) { + trap_Cvar_Set ("cg_cameraOrbit", "0"); + trap_Cvar_Set("cg_thirdPerson", "0"); + } else { + trap_Cvar_Set("cg_cameraOrbit", "5"); + trap_Cvar_Set("cg_thirdPerson", "1"); + trap_Cvar_Set("cg_thirdPersonAngle", "0"); + trap_Cvar_Set("cg_thirdPersonRange", "100"); + } +} + +/* +static void CG_Camera_f( void ) { + char name[1024]; + trap_Argv( 1, name, sizeof(name)); + if (trap_loadCamera(name)) { + cg.cameraMode = qtrue; + trap_startCamera(cg.time); + } else { + CG_Printf ("Unable to load camera %s\n",name); + } +} +*/ + + +typedef struct { + char *cmd; + void (*function)(void); +} consoleCommand_t; + +static consoleCommand_t commands[] = { + { "testgun", CG_TestGun_f }, + { "testmodel", CG_TestModel_f }, + { "nextframe", CG_TestModelNextFrame_f }, + { "prevframe", CG_TestModelPrevFrame_f }, + { "nextskin", CG_TestModelNextSkin_f }, + { "prevskin", CG_TestModelPrevSkin_f }, + { "viewpos", CG_Viewpos_f }, + { "+scores", CG_ScoresDown_f }, + { "-scores", CG_ScoresUp_f }, + { "+zoom", CG_ZoomDown_f }, + { "-zoom", CG_ZoomUp_f }, + { "sizeup", CG_SizeUp_f }, + { "sizedown", CG_SizeDown_f }, + { "weapnext", CG_NextWeapon_f }, + { "weapprev", CG_PrevWeapon_f }, + { "weapon", CG_Weapon_f }, + { "tell_target", CG_TellTarget_f }, + { "tell_attacker", CG_TellAttacker_f }, + { "vtell_target", CG_VoiceTellTarget_f }, + { "vtell_attacker", CG_VoiceTellAttacker_f }, + { "tcmd", CG_TargetCommand_f }, +#ifdef MISSIONPACK + { "loadhud", CG_LoadHud_f }, + { "nextTeamMember", CG_NextTeamMember_f }, + { "prevTeamMember", CG_PrevTeamMember_f }, + { "nextOrder", CG_NextOrder_f }, + { "confirmOrder", CG_ConfirmOrder_f }, + { "denyOrder", CG_DenyOrder_f }, + { "taskOffense", CG_TaskOffense_f }, + { "taskDefense", CG_TaskDefense_f }, + { "taskPatrol", CG_TaskPatrol_f }, + { "taskCamp", CG_TaskCamp_f }, + { "taskFollow", CG_TaskFollow_f }, + { "taskRetrieve", CG_TaskRetrieve_f }, + { "taskEscort", CG_TaskEscort_f }, + { "taskSuicide", CG_TaskSuicide_f }, + { "taskOwnFlag", CG_TaskOwnFlag_f }, + { "tauntKillInsult", CG_TauntKillInsult_f }, + { "tauntPraise", CG_TauntPraise_f }, + { "tauntTaunt", CG_TauntTaunt_f }, + { "tauntDeathInsult", CG_TauntDeathInsult_f }, + { "tauntGauntlet", CG_TauntGauntlet_f }, + { "spWin", CG_spWin_f }, + { "spLose", CG_spLose_f }, + { "scoresDown", CG_scrollScoresDown_f }, + { "scoresUp", CG_scrollScoresUp_f }, +#endif + { "startOrbit", CG_StartOrbit_f }, + //{ "camera", CG_Camera_f }, + { "loaddeferred", CG_LoadDeferredPlayers } +}; + + +/* +================= +CG_ConsoleCommand + +The string has been tokenized and can be retrieved with +Cmd_Argc() / Cmd_Argv() +================= +*/ +qboolean CG_ConsoleCommand( void ) { + const char *cmd; + int i; + + cmd = CG_Argv(0); + + for ( i = 0 ; i < sizeof( commands ) / sizeof( commands[0] ) ; i++ ) { + if ( !Q_stricmp( cmd, commands[i].cmd ) ) { + commands[i].function(); + return qtrue; + } + } + + return qfalse; +} + + +/* +================= +CG_InitConsoleCommands + +Let the client system know about all of our commands +so it can perform tab completion +================= +*/ +void CG_InitConsoleCommands( void ) { + int i; + + for ( i = 0 ; i < sizeof( commands ) / sizeof( commands[0] ) ; i++ ) { + trap_AddCommand( commands[i].cmd ); + } + + // + // the game server will interpret these commands, which will be automatically + // forwarded to the server after they are not recognized locally + // + trap_AddCommand ("kill"); + trap_AddCommand ("say"); + trap_AddCommand ("say_team"); + trap_AddCommand ("tell"); + trap_AddCommand ("vsay"); + trap_AddCommand ("vsay_team"); + trap_AddCommand ("vtell"); + trap_AddCommand ("vtaunt"); + trap_AddCommand ("vosay"); + trap_AddCommand ("vosay_team"); + trap_AddCommand ("votell"); + trap_AddCommand ("give"); + trap_AddCommand ("god"); + trap_AddCommand ("notarget"); + trap_AddCommand ("noclip"); + trap_AddCommand ("team"); + trap_AddCommand ("follow"); + trap_AddCommand ("levelshot"); + trap_AddCommand ("addbot"); + trap_AddCommand ("setviewpos"); + trap_AddCommand ("callvote"); + trap_AddCommand ("vote"); + trap_AddCommand ("callteamvote"); + trap_AddCommand ("teamvote"); + trap_AddCommand ("stats"); + trap_AddCommand ("teamtask"); + trap_AddCommand ("loaddefered"); // spelled wrong, but not changing for demo +} diff --git a/code/cgame/cg_draw.c b/code/cgame/cg_draw.c index e29c10f..5575aaf 100755 --- a/code/cgame/cg_draw.c +++ b/code/cgame/cg_draw.c @@ -1,2659 +1,2659 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// cg_draw.c -- draw all of the graphical elements during -// active (after loading) gameplay - -#include "cg_local.h" - -#ifdef MISSIONPACK -#include "../ui/ui_shared.h" - -// used for scoreboard -extern displayContextDef_t cgDC; -menuDef_t *menuScoreboard = NULL; -#else -int drawTeamOverlayModificationCount = -1; -#endif - -int sortedTeamPlayers[TEAM_MAXOVERLAY]; -int numSortedTeamPlayers; - -char systemChat[256]; -char teamChat1[256]; -char teamChat2[256]; - -#ifdef MISSIONPACK - -int CG_Text_Width(const char *text, float scale, int limit) { - int count,len; - float out; - glyphInfo_t *glyph; - float useScale; -// FIXME: see ui_main.c, same problem -// const unsigned char *s = text; - const char *s = text; - fontInfo_t *font = &cgDC.Assets.textFont; - if (scale <= cg_smallFont.value) { - font = &cgDC.Assets.smallFont; - } else if (scale > cg_bigFont.value) { - font = &cgDC.Assets.bigFont; - } - useScale = scale * font->glyphScale; - out = 0; - if (text) { - len = strlen(text); - if (limit > 0 && len > limit) { - len = limit; - } - count = 0; - while (s && *s && count < len) { - if ( Q_IsColorString(s) ) { - s += 2; - continue; - } else { - glyph = &font->glyphs[(int)*s]; // TTimo: FIXME: getting nasty warnings without the cast, hopefully this doesn't break the VM build - out += glyph->xSkip; - s++; - count++; - } - } - } - return out * useScale; -} - -int CG_Text_Height(const char *text, float scale, int limit) { - int len, count; - float max; - glyphInfo_t *glyph; - float useScale; -// TTimo: FIXME -// const unsigned char *s = text; - const char *s = text; - fontInfo_t *font = &cgDC.Assets.textFont; - if (scale <= cg_smallFont.value) { - font = &cgDC.Assets.smallFont; - } else if (scale > cg_bigFont.value) { - font = &cgDC.Assets.bigFont; - } - useScale = scale * font->glyphScale; - max = 0; - if (text) { - len = strlen(text); - if (limit > 0 && len > limit) { - len = limit; - } - count = 0; - while (s && *s && count < len) { - if ( Q_IsColorString(s) ) { - s += 2; - continue; - } else { - glyph = &font->glyphs[(int)*s]; // TTimo: FIXME: getting nasty warnings without the cast, hopefully this doesn't break the VM build - if (max < glyph->height) { - max = glyph->height; - } - s++; - count++; - } - } - } - return max * useScale; -} - -void CG_Text_PaintChar(float x, float y, float width, float height, float scale, float s, float t, float s2, float t2, qhandle_t hShader) { - float w, h; - w = width * scale; - h = height * scale; - CG_AdjustFrom640( &x, &y, &w, &h ); - trap_R_DrawStretchPic( x, y, w, h, s, t, s2, t2, hShader ); -} - -void CG_Text_Paint(float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit, int style) { - int len, count; - vec4_t newColor; - glyphInfo_t *glyph; - float useScale; - fontInfo_t *font = &cgDC.Assets.textFont; - if (scale <= cg_smallFont.value) { - font = &cgDC.Assets.smallFont; - } else if (scale > cg_bigFont.value) { - font = &cgDC.Assets.bigFont; - } - useScale = scale * font->glyphScale; - if (text) { -// TTimo: FIXME -// const unsigned char *s = text; - const char *s = text; - trap_R_SetColor( color ); - memcpy(&newColor[0], &color[0], sizeof(vec4_t)); - len = strlen(text); - if (limit > 0 && len > limit) { - len = limit; - } - count = 0; - while (s && *s && count < len) { - glyph = &font->glyphs[(int)*s]; // TTimo: FIXME: getting nasty warnings without the cast, hopefully this doesn't break the VM build - //int yadj = Assets.textFont.glyphs[text[i]].bottom + Assets.textFont.glyphs[text[i]].top; - //float yadj = scale * (Assets.textFont.glyphs[text[i]].imageHeight - Assets.textFont.glyphs[text[i]].height); - if ( Q_IsColorString( s ) ) { - memcpy( newColor, g_color_table[ColorIndex(*(s+1))], sizeof( newColor ) ); - newColor[3] = color[3]; - trap_R_SetColor( newColor ); - s += 2; - continue; - } else { - float yadj = useScale * glyph->top; - if (style == ITEM_TEXTSTYLE_SHADOWED || style == ITEM_TEXTSTYLE_SHADOWEDMORE) { - int ofs = style == ITEM_TEXTSTYLE_SHADOWED ? 1 : 2; - colorBlack[3] = newColor[3]; - trap_R_SetColor( colorBlack ); - CG_Text_PaintChar(x + ofs, y - yadj + ofs, - glyph->imageWidth, - glyph->imageHeight, - useScale, - glyph->s, - glyph->t, - glyph->s2, - glyph->t2, - glyph->glyph); - colorBlack[3] = 1.0; - trap_R_SetColor( newColor ); - } - CG_Text_PaintChar(x, y - yadj, - glyph->imageWidth, - glyph->imageHeight, - useScale, - glyph->s, - glyph->t, - glyph->s2, - glyph->t2, - glyph->glyph); - // CG_DrawPic(x, y - yadj, scale * cgDC.Assets.textFont.glyphs[text[i]].imageWidth, scale * cgDC.Assets.textFont.glyphs[text[i]].imageHeight, cgDC.Assets.textFont.glyphs[text[i]].glyph); - x += (glyph->xSkip * useScale) + adjust; - s++; - count++; - } - } - trap_R_SetColor( NULL ); - } -} - - -#endif - -/* -============== -CG_DrawField - -Draws large numbers for status bar and powerups -============== -*/ -#ifndef MISSIONPACK -static void CG_DrawField (int x, int y, int width, int value) { - char num[16], *ptr; - int l; - int frame; - - if ( width < 1 ) { - return; - } - - // draw number string - if ( width > 5 ) { - width = 5; - } - - switch ( width ) { - case 1: - value = value > 9 ? 9 : value; - value = value < 0 ? 0 : value; - break; - case 2: - value = value > 99 ? 99 : value; - value = value < -9 ? -9 : value; - break; - case 3: - value = value > 999 ? 999 : value; - value = value < -99 ? -99 : value; - break; - case 4: - value = value > 9999 ? 9999 : value; - value = value < -999 ? -999 : value; - break; - } - - Com_sprintf (num, sizeof(num), "%i", value); - l = strlen(num); - if (l > width) - l = width; - x += 2 + CHAR_WIDTH*(width - l); - - ptr = num; - while (*ptr && l) - { - if (*ptr == '-') - frame = STAT_MINUS; - else - frame = *ptr -'0'; - - CG_DrawPic( x,y, CHAR_WIDTH, CHAR_HEIGHT, cgs.media.numberShaders[frame] ); - x += CHAR_WIDTH; - ptr++; - l--; - } -} -#endif // MISSIONPACK - -/* -================ -CG_Draw3DModel - -================ -*/ -void CG_Draw3DModel( float x, float y, float w, float h, qhandle_t model, qhandle_t skin, vec3_t origin, vec3_t angles ) { - refdef_t refdef; - refEntity_t ent; - - if ( !cg_draw3dIcons.integer || !cg_drawIcons.integer ) { - return; - } - - CG_AdjustFrom640( &x, &y, &w, &h ); - - memset( &refdef, 0, sizeof( refdef ) ); - - memset( &ent, 0, sizeof( ent ) ); - AnglesToAxis( angles, ent.axis ); - VectorCopy( origin, ent.origin ); - ent.hModel = model; - ent.customSkin = skin; - ent.renderfx = RF_NOSHADOW; // no stencil shadows - - refdef.rdflags = RDF_NOWORLDMODEL; - - AxisClear( refdef.viewaxis ); - - refdef.fov_x = 30; - refdef.fov_y = 30; - - refdef.x = x; - refdef.y = y; - refdef.width = w; - refdef.height = h; - - refdef.time = cg.time; - - trap_R_ClearScene(); - trap_R_AddRefEntityToScene( &ent ); - trap_R_RenderScene( &refdef ); -} - -/* -================ -CG_DrawHead - -Used for both the status bar and the scoreboard -================ -*/ -void CG_DrawHead( float x, float y, float w, float h, int clientNum, vec3_t headAngles ) { - clipHandle_t cm; - clientInfo_t *ci; - float len; - vec3_t origin; - vec3_t mins, maxs; - - ci = &cgs.clientinfo[ clientNum ]; - - if ( cg_draw3dIcons.integer ) { - cm = ci->headModel; - if ( !cm ) { - return; - } - - // offset the origin y and z to center the head - trap_R_ModelBounds( cm, mins, maxs ); - - origin[2] = -0.5 * ( mins[2] + maxs[2] ); - origin[1] = 0.5 * ( mins[1] + maxs[1] ); - - // calculate distance so the head nearly fills the box - // assume heads are taller than wide - len = 0.7 * ( maxs[2] - mins[2] ); - origin[0] = len / 0.268; // len / tan( fov/2 ) - - // allow per-model tweaking - VectorAdd( origin, ci->headOffset, origin ); - - CG_Draw3DModel( x, y, w, h, ci->headModel, ci->headSkin, origin, headAngles ); - } else if ( cg_drawIcons.integer ) { - CG_DrawPic( x, y, w, h, ci->modelIcon ); - } - - // if they are deferred, draw a cross out - if ( ci->deferred ) { - CG_DrawPic( x, y, w, h, cgs.media.deferShader ); - } -} - -/* -================ -CG_DrawFlagModel - -Used for both the status bar and the scoreboard -================ -*/ -void CG_DrawFlagModel( float x, float y, float w, float h, int team, qboolean force2D ) { - qhandle_t cm; - float len; - vec3_t origin, angles; - vec3_t mins, maxs; - qhandle_t handle; - - if ( !force2D && cg_draw3dIcons.integer ) { - - VectorClear( angles ); - - cm = cgs.media.redFlagModel; - - // offset the origin y and z to center the flag - trap_R_ModelBounds( cm, mins, maxs ); - - origin[2] = -0.5 * ( mins[2] + maxs[2] ); - origin[1] = 0.5 * ( mins[1] + maxs[1] ); - - // calculate distance so the flag nearly fills the box - // assume heads are taller than wide - len = 0.5 * ( maxs[2] - mins[2] ); - origin[0] = len / 0.268; // len / tan( fov/2 ) - - angles[YAW] = 60 * sin( cg.time / 2000.0 );; - - if( team == TEAM_RED ) { - handle = cgs.media.redFlagModel; - } else if( team == TEAM_BLUE ) { - handle = cgs.media.blueFlagModel; - } else if( team == TEAM_FREE ) { - handle = cgs.media.neutralFlagModel; - } else { - return; - } - CG_Draw3DModel( x, y, w, h, handle, 0, origin, angles ); - } else if ( cg_drawIcons.integer ) { - gitem_t *item; - - if( team == TEAM_RED ) { - item = BG_FindItemForPowerup( PW_REDFLAG ); - } else if( team == TEAM_BLUE ) { - item = BG_FindItemForPowerup( PW_BLUEFLAG ); - } else if( team == TEAM_FREE ) { - item = BG_FindItemForPowerup( PW_NEUTRALFLAG ); - } else { - return; - } - if (item) { - CG_DrawPic( x, y, w, h, cg_items[ ITEM_INDEX(item) ].icon ); - } - } -} - -/* -================ -CG_DrawStatusBarHead - -================ -*/ -#ifndef MISSIONPACK - -static void CG_DrawStatusBarHead( float x ) { - vec3_t angles; - float size, stretch; - float frac; - - VectorClear( angles ); - - if ( cg.damageTime && cg.time - cg.damageTime < DAMAGE_TIME ) { - frac = (float)(cg.time - cg.damageTime ) / DAMAGE_TIME; - size = ICON_SIZE * 1.25 * ( 1.5 - frac * 0.5 ); - - stretch = size - ICON_SIZE * 1.25; - // kick in the direction of damage - x -= stretch * 0.5 + cg.damageX * stretch * 0.5; - - cg.headStartYaw = 180 + cg.damageX * 45; - - cg.headEndYaw = 180 + 20 * cos( crandom()*M_PI ); - cg.headEndPitch = 5 * cos( crandom()*M_PI ); - - cg.headStartTime = cg.time; - cg.headEndTime = cg.time + 100 + random() * 2000; - } else { - if ( cg.time >= cg.headEndTime ) { - // select a new head angle - cg.headStartYaw = cg.headEndYaw; - cg.headStartPitch = cg.headEndPitch; - cg.headStartTime = cg.headEndTime; - cg.headEndTime = cg.time + 100 + random() * 2000; - - cg.headEndYaw = 180 + 20 * cos( crandom()*M_PI ); - cg.headEndPitch = 5 * cos( crandom()*M_PI ); - } - - size = ICON_SIZE * 1.25; - } - - // if the server was frozen for a while we may have a bad head start time - if ( cg.headStartTime > cg.time ) { - cg.headStartTime = cg.time; - } - - frac = ( cg.time - cg.headStartTime ) / (float)( cg.headEndTime - cg.headStartTime ); - frac = frac * frac * ( 3 - 2 * frac ); - angles[YAW] = cg.headStartYaw + ( cg.headEndYaw - cg.headStartYaw ) * frac; - angles[PITCH] = cg.headStartPitch + ( cg.headEndPitch - cg.headStartPitch ) * frac; - - CG_DrawHead( x, 480 - size, size, size, - cg.snap->ps.clientNum, angles ); -} -#endif // MISSIONPACK - -/* -================ -CG_DrawStatusBarFlag - -================ -*/ -#ifndef MISSIONPACK -static void CG_DrawStatusBarFlag( float x, int team ) { - CG_DrawFlagModel( x, 480 - ICON_SIZE, ICON_SIZE, ICON_SIZE, team, qfalse ); -} -#endif // MISSIONPACK - -/* -================ -CG_DrawTeamBackground - -================ -*/ -void CG_DrawTeamBackground( int x, int y, int w, int h, float alpha, int team ) -{ - vec4_t hcolor; - - hcolor[3] = alpha; - if ( team == TEAM_RED ) { - hcolor[0] = 1; - hcolor[1] = 0; - hcolor[2] = 0; - } else if ( team == TEAM_BLUE ) { - hcolor[0] = 0; - hcolor[1] = 0; - hcolor[2] = 1; - } else { - return; - } - trap_R_SetColor( hcolor ); - CG_DrawPic( x, y, w, h, cgs.media.teamStatusBar ); - trap_R_SetColor( NULL ); -} - -/* -================ -CG_DrawStatusBar - -================ -*/ -#ifndef MISSIONPACK -static void CG_DrawStatusBar( void ) { - int color; - centity_t *cent; - playerState_t *ps; - int value; - vec4_t hcolor; - vec3_t angles; - vec3_t origin; -#ifdef MISSIONPACK - qhandle_t handle; -#endif - static float colors[4][4] = { -// { 0.2, 1.0, 0.2, 1.0 } , { 1.0, 0.2, 0.2, 1.0 }, {0.5, 0.5, 0.5, 1} }; - { 1.0f, 0.69f, 0.0f, 1.0f }, // normal - { 1.0f, 0.2f, 0.2f, 1.0f }, // low health - { 0.5f, 0.5f, 0.5f, 1.0f }, // weapon firing - { 1.0f, 1.0f, 1.0f, 1.0f } }; // health > 100 - - if ( cg_drawStatus.integer == 0 ) { - return; - } - - // draw the team background - CG_DrawTeamBackground( 0, 420, 640, 60, 0.33f, cg.snap->ps.persistant[PERS_TEAM] ); - - cent = &cg_entities[cg.snap->ps.clientNum]; - ps = &cg.snap->ps; - - VectorClear( angles ); - - // draw any 3D icons first, so the changes back to 2D are minimized - if ( cent->currentState.weapon && cg_weapons[ cent->currentState.weapon ].ammoModel ) { - origin[0] = 70; - origin[1] = 0; - origin[2] = 0; - angles[YAW] = 90 + 20 * sin( cg.time / 1000.0 ); - CG_Draw3DModel( CHAR_WIDTH*3 + TEXT_ICON_SPACE, 432, ICON_SIZE, ICON_SIZE, - cg_weapons[ cent->currentState.weapon ].ammoModel, 0, origin, angles ); - } - - CG_DrawStatusBarHead( 185 + CHAR_WIDTH*3 + TEXT_ICON_SPACE ); - - if( cg.predictedPlayerState.powerups[PW_REDFLAG] ) { - CG_DrawStatusBarFlag( 185 + CHAR_WIDTH*3 + TEXT_ICON_SPACE + ICON_SIZE, TEAM_RED ); - } else if( cg.predictedPlayerState.powerups[PW_BLUEFLAG] ) { - CG_DrawStatusBarFlag( 185 + CHAR_WIDTH*3 + TEXT_ICON_SPACE + ICON_SIZE, TEAM_BLUE ); - } else if( cg.predictedPlayerState.powerups[PW_NEUTRALFLAG] ) { - CG_DrawStatusBarFlag( 185 + CHAR_WIDTH*3 + TEXT_ICON_SPACE + ICON_SIZE, TEAM_FREE ); - } - - if ( ps->stats[ STAT_ARMOR ] ) { - origin[0] = 90; - origin[1] = 0; - origin[2] = -10; - angles[YAW] = ( cg.time & 2047 ) * 360 / 2048.0; - CG_Draw3DModel( 370 + CHAR_WIDTH*3 + TEXT_ICON_SPACE, 432, ICON_SIZE, ICON_SIZE, - cgs.media.armorModel, 0, origin, angles ); - } -#ifdef MISSIONPACK - if( cgs.gametype == GT_HARVESTER ) { - origin[0] = 90; - origin[1] = 0; - origin[2] = -10; - angles[YAW] = ( cg.time & 2047 ) * 360 / 2048.0; - if( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { - handle = cgs.media.redCubeModel; - } else { - handle = cgs.media.blueCubeModel; - } - CG_Draw3DModel( 640 - (TEXT_ICON_SPACE + ICON_SIZE), 416, ICON_SIZE, ICON_SIZE, handle, 0, origin, angles ); - } -#endif - // - // ammo - // - if ( cent->currentState.weapon ) { - value = ps->ammo[cent->currentState.weapon]; - if ( value > -1 ) { - if ( cg.predictedPlayerState.weaponstate == WEAPON_FIRING - && cg.predictedPlayerState.weaponTime > 100 ) { - // draw as dark grey when reloading - color = 2; // dark grey - } else { - if ( value >= 0 ) { - color = 0; // green - } else { - color = 1; // red - } - } - trap_R_SetColor( colors[color] ); - - CG_DrawField (0, 432, 3, value); - trap_R_SetColor( NULL ); - - // if we didn't draw a 3D icon, draw a 2D icon for ammo - if ( !cg_draw3dIcons.integer && cg_drawIcons.integer ) { - qhandle_t icon; - - icon = cg_weapons[ cg.predictedPlayerState.weapon ].ammoIcon; - if ( icon ) { - CG_DrawPic( CHAR_WIDTH*3 + TEXT_ICON_SPACE, 432, ICON_SIZE, ICON_SIZE, icon ); - } - } - } - } - - // - // health - // - value = ps->stats[STAT_HEALTH]; - if ( value > 100 ) { - trap_R_SetColor( colors[3] ); // white - } else if (value > 25) { - trap_R_SetColor( colors[0] ); // green - } else if (value > 0) { - color = (cg.time >> 8) & 1; // flash - trap_R_SetColor( colors[color] ); - } else { - trap_R_SetColor( colors[1] ); // red - } - - // stretch the health up when taking damage - CG_DrawField ( 185, 432, 3, value); - CG_ColorForHealth( hcolor ); - trap_R_SetColor( hcolor ); - - - // - // armor - // - value = ps->stats[STAT_ARMOR]; - if (value > 0 ) { - trap_R_SetColor( colors[0] ); - CG_DrawField (370, 432, 3, value); - trap_R_SetColor( NULL ); - // if we didn't draw a 3D icon, draw a 2D icon for armor - if ( !cg_draw3dIcons.integer && cg_drawIcons.integer ) { - CG_DrawPic( 370 + CHAR_WIDTH*3 + TEXT_ICON_SPACE, 432, ICON_SIZE, ICON_SIZE, cgs.media.armorIcon ); - } - - } -#ifdef MISSIONPACK - // - // cubes - // - if( cgs.gametype == GT_HARVESTER ) { - value = ps->generic1; - if( value > 99 ) { - value = 99; - } - trap_R_SetColor( colors[0] ); - CG_DrawField (640 - (CHAR_WIDTH*2 + TEXT_ICON_SPACE + ICON_SIZE), 432, 2, value); - trap_R_SetColor( NULL ); - // if we didn't draw a 3D icon, draw a 2D icon for armor - if ( !cg_draw3dIcons.integer && cg_drawIcons.integer ) { - if( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { - handle = cgs.media.redCubeIcon; - } else { - handle = cgs.media.blueCubeIcon; - } - CG_DrawPic( 640 - (TEXT_ICON_SPACE + ICON_SIZE), 432, ICON_SIZE, ICON_SIZE, handle ); - } - } -#endif -} -#endif - -/* -=========================================================================================== - - UPPER RIGHT CORNER - -=========================================================================================== -*/ - -/* -================ -CG_DrawAttacker - -================ -*/ -static float CG_DrawAttacker( float y ) { - int t; - float size; - vec3_t angles; - const char *info; - const char *name; - int clientNum; - - if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) { - return y; - } - - if ( !cg.attackerTime ) { - return y; - } - - clientNum = cg.predictedPlayerState.persistant[PERS_ATTACKER]; - if ( clientNum < 0 || clientNum >= MAX_CLIENTS || clientNum == cg.snap->ps.clientNum ) { - return y; - } - - t = cg.time - cg.attackerTime; - if ( t > ATTACKER_HEAD_TIME ) { - cg.attackerTime = 0; - return y; - } - - size = ICON_SIZE * 1.25; - - angles[PITCH] = 0; - angles[YAW] = 180; - angles[ROLL] = 0; - CG_DrawHead( 640 - size, y, size, size, clientNum, angles ); - - info = CG_ConfigString( CS_PLAYERS + clientNum ); - name = Info_ValueForKey( info, "n" ); - y += size; - CG_DrawBigString( 640 - ( Q_PrintStrlen( name ) * BIGCHAR_WIDTH), y, name, 0.5 ); - - return y + BIGCHAR_HEIGHT + 2; -} - -/* -================== -CG_DrawSnapshot -================== -*/ -static float CG_DrawSnapshot( float y ) { - char *s; - int w; - - s = va( "time:%i snap:%i cmd:%i", cg.snap->serverTime, - cg.latestSnapshotNum, cgs.serverCommandSequence ); - w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; - - CG_DrawBigString( 635 - w, y + 2, s, 1.0F); - - return y + BIGCHAR_HEIGHT + 4; -} - -/* -================== -CG_DrawFPS -================== -*/ -#define FPS_FRAMES 4 -static float CG_DrawFPS( float y ) { - char *s; - int w; - static int previousTimes[FPS_FRAMES]; - static int index; - int i, total; - int fps; - static int previous; - int t, frameTime; - - // don't use serverTime, because that will be drifting to - // correct for internet lag changes, timescales, timedemos, etc - t = trap_Milliseconds(); - frameTime = t - previous; - previous = t; - - previousTimes[index % FPS_FRAMES] = frameTime; - index++; - if ( index > FPS_FRAMES ) { - // average multiple frames together to smooth changes out a bit - total = 0; - for ( i = 0 ; i < FPS_FRAMES ; i++ ) { - total += previousTimes[i]; - } - if ( !total ) { - total = 1; - } - fps = 1000 * FPS_FRAMES / total; - - s = va( "%ifps", fps ); - w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; - - CG_DrawBigString( 635 - w, y + 2, s, 1.0F); - } - - return y + BIGCHAR_HEIGHT + 4; -} - -/* -================= -CG_DrawTimer -================= -*/ -static float CG_DrawTimer( float y ) { - char *s; - int w; - int mins, seconds, tens; - int msec; - - msec = cg.time - cgs.levelStartTime; - - seconds = msec / 1000; - mins = seconds / 60; - seconds -= mins * 60; - tens = seconds / 10; - seconds -= tens * 10; - - s = va( "%i:%i%i", mins, tens, seconds ); - w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; - - CG_DrawBigString( 635 - w, y + 2, s, 1.0F); - - return y + BIGCHAR_HEIGHT + 4; -} - - -/* -================= -CG_DrawTeamOverlay -================= -*/ - -static float CG_DrawTeamOverlay( float y, qboolean right, qboolean upper ) { - int x, w, h, xx; - int i, j, len; - const char *p; - vec4_t hcolor; - int pwidth, lwidth; - int plyrs; - char st[16]; - clientInfo_t *ci; - gitem_t *item; - int ret_y, count; - - if ( !cg_drawTeamOverlay.integer ) { - return y; - } - - if ( cg.snap->ps.persistant[PERS_TEAM] != TEAM_RED && cg.snap->ps.persistant[PERS_TEAM] != TEAM_BLUE ) { - return y; // Not on any team - } - - plyrs = 0; - - // max player name width - pwidth = 0; - count = (numSortedTeamPlayers > 8) ? 8 : numSortedTeamPlayers; - for (i = 0; i < count; i++) { - ci = cgs.clientinfo + sortedTeamPlayers[i]; - if ( ci->infoValid && ci->team == cg.snap->ps.persistant[PERS_TEAM]) { - plyrs++; - len = CG_DrawStrlen(ci->name); - if (len > pwidth) - pwidth = len; - } - } - - if (!plyrs) - return y; - - if (pwidth > TEAM_OVERLAY_MAXNAME_WIDTH) - pwidth = TEAM_OVERLAY_MAXNAME_WIDTH; - - // max location name width - lwidth = 0; - for (i = 1; i < MAX_LOCATIONS; i++) { - p = CG_ConfigString(CS_LOCATIONS + i); - if (p && *p) { - len = CG_DrawStrlen(p); - if (len > lwidth) - lwidth = len; - } - } - - if (lwidth > TEAM_OVERLAY_MAXLOCATION_WIDTH) - lwidth = TEAM_OVERLAY_MAXLOCATION_WIDTH; - - w = (pwidth + lwidth + 4 + 7) * TINYCHAR_WIDTH; - - if ( right ) - x = 640 - w; - else - x = 0; - - h = plyrs * TINYCHAR_HEIGHT; - - if ( upper ) { - ret_y = y + h; - } else { - y -= h; - ret_y = y; - } - - if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED ) { - hcolor[0] = 1.0f; - hcolor[1] = 0.0f; - hcolor[2] = 0.0f; - hcolor[3] = 0.33f; - } else { // if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) - hcolor[0] = 0.0f; - hcolor[1] = 0.0f; - hcolor[2] = 1.0f; - hcolor[3] = 0.33f; - } - trap_R_SetColor( hcolor ); - CG_DrawPic( x, y, w, h, cgs.media.teamStatusBar ); - trap_R_SetColor( NULL ); - - for (i = 0; i < count; i++) { - ci = cgs.clientinfo + sortedTeamPlayers[i]; - if ( ci->infoValid && ci->team == cg.snap->ps.persistant[PERS_TEAM]) { - - hcolor[0] = hcolor[1] = hcolor[2] = hcolor[3] = 1.0; - - xx = x + TINYCHAR_WIDTH; - - CG_DrawStringExt( xx, y, - ci->name, hcolor, qfalse, qfalse, - TINYCHAR_WIDTH, TINYCHAR_HEIGHT, TEAM_OVERLAY_MAXNAME_WIDTH); - - if (lwidth) { - p = CG_ConfigString(CS_LOCATIONS + ci->location); - if (!p || !*p) - p = "unknown"; - len = CG_DrawStrlen(p); - if (len > lwidth) - len = lwidth; - -// xx = x + TINYCHAR_WIDTH * 2 + TINYCHAR_WIDTH * pwidth + -// ((lwidth/2 - len/2) * TINYCHAR_WIDTH); - xx = x + TINYCHAR_WIDTH * 2 + TINYCHAR_WIDTH * pwidth; - CG_DrawStringExt( xx, y, - p, hcolor, qfalse, qfalse, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, - TEAM_OVERLAY_MAXLOCATION_WIDTH); - } - - CG_GetColorForHealth( ci->health, ci->armor, hcolor ); - - Com_sprintf (st, sizeof(st), "%3i %3i", ci->health, ci->armor); - - xx = x + TINYCHAR_WIDTH * 3 + - TINYCHAR_WIDTH * pwidth + TINYCHAR_WIDTH * lwidth; - - CG_DrawStringExt( xx, y, - st, hcolor, qfalse, qfalse, - TINYCHAR_WIDTH, TINYCHAR_HEIGHT, 0 ); - - // draw weapon icon - xx += TINYCHAR_WIDTH * 3; - - if ( cg_weapons[ci->curWeapon].weaponIcon ) { - CG_DrawPic( xx, y, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, - cg_weapons[ci->curWeapon].weaponIcon ); - } else { - CG_DrawPic( xx, y, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, - cgs.media.deferShader ); - } - - // Draw powerup icons - if (right) { - xx = x; - } else { - xx = x + w - TINYCHAR_WIDTH; - } - for (j = 0; j <= PW_NUM_POWERUPS; j++) { - if (ci->powerups & (1 << j)) { - - item = BG_FindItemForPowerup( j ); - - if (item) { - CG_DrawPic( xx, y, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, - trap_R_RegisterShader( item->icon ) ); - if (right) { - xx -= TINYCHAR_WIDTH; - } else { - xx += TINYCHAR_WIDTH; - } - } - } - } - - y += TINYCHAR_HEIGHT; - } - } - - return ret_y; -//#endif -} - - -/* -===================== -CG_DrawUpperRight - -===================== -*/ -static void CG_DrawUpperRight( void ) { - float y; - - y = 0; - - if ( cgs.gametype >= GT_TEAM && cg_drawTeamOverlay.integer == 1 ) { - y = CG_DrawTeamOverlay( y, qtrue, qtrue ); - } - if ( cg_drawSnapshot.integer ) { - y = CG_DrawSnapshot( y ); - } - if ( cg_drawFPS.integer ) { - y = CG_DrawFPS( y ); - } - if ( cg_drawTimer.integer ) { - y = CG_DrawTimer( y ); - } - if ( cg_drawAttacker.integer ) { - y = CG_DrawAttacker( y ); - } - -} - -/* -=========================================================================================== - - LOWER RIGHT CORNER - -=========================================================================================== -*/ - -/* -================= -CG_DrawScores - -Draw the small two score display -================= -*/ -#ifndef MISSIONPACK -static float CG_DrawScores( float y ) { - const char *s; - int s1, s2, score; - int x, w; - int v; - vec4_t color; - float y1; - gitem_t *item; - - s1 = cgs.scores1; - s2 = cgs.scores2; - - y -= BIGCHAR_HEIGHT + 8; - - y1 = y; - - // draw from the right side to left - if ( cgs.gametype >= GT_TEAM ) { - x = 640; - color[0] = 0.0f; - color[1] = 0.0f; - color[2] = 1.0f; - color[3] = 0.33f; - s = va( "%2i", s2 ); - w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; - x -= w; - CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); - if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { - CG_DrawPic( x, y-4, w, BIGCHAR_HEIGHT+8, cgs.media.selectShader ); - } - CG_DrawBigString( x + 4, y, s, 1.0F); - - if ( cgs.gametype == GT_CTF ) { - // Display flag status - item = BG_FindItemForPowerup( PW_BLUEFLAG ); - - if (item) { - y1 = y - BIGCHAR_HEIGHT - 8; - if( cgs.blueflag >= 0 && cgs.blueflag <= 2 ) { - CG_DrawPic( x, y1-4, w, BIGCHAR_HEIGHT+8, cgs.media.blueFlagShader[cgs.blueflag] ); - } - } - } - color[0] = 1.0f; - color[1] = 0.0f; - color[2] = 0.0f; - color[3] = 0.33f; - s = va( "%2i", s1 ); - w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; - x -= w; - CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); - if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED ) { - CG_DrawPic( x, y-4, w, BIGCHAR_HEIGHT+8, cgs.media.selectShader ); - } - CG_DrawBigString( x + 4, y, s, 1.0F); - - if ( cgs.gametype == GT_CTF ) { - // Display flag status - item = BG_FindItemForPowerup( PW_REDFLAG ); - - if (item) { - y1 = y - BIGCHAR_HEIGHT - 8; - if( cgs.redflag >= 0 && cgs.redflag <= 2 ) { - CG_DrawPic( x, y1-4, w, BIGCHAR_HEIGHT+8, cgs.media.redFlagShader[cgs.redflag] ); - } - } - } - -#ifdef MISSIONPACK - if ( cgs.gametype == GT_1FCTF ) { - // Display flag status - item = BG_FindItemForPowerup( PW_NEUTRALFLAG ); - - if (item) { - y1 = y - BIGCHAR_HEIGHT - 8; - if( cgs.flagStatus >= 0 && cgs.flagStatus <= 3 ) { - CG_DrawPic( x, y1-4, w, BIGCHAR_HEIGHT+8, cgs.media.flagShader[cgs.flagStatus] ); - } - } - } -#endif - if ( cgs.gametype >= GT_CTF ) { - v = cgs.capturelimit; - } else { - v = cgs.fraglimit; - } - if ( v ) { - s = va( "%2i", v ); - w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; - x -= w; - CG_DrawBigString( x + 4, y, s, 1.0F); - } - - } else { - qboolean spectator; - - x = 640; - score = cg.snap->ps.persistant[PERS_SCORE]; - spectator = ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR ); - - // always show your score in the second box if not in first place - if ( s1 != score ) { - s2 = score; - } - if ( s2 != SCORE_NOT_PRESENT ) { - s = va( "%2i", s2 ); - w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; - x -= w; - if ( !spectator && score == s2 && score != s1 ) { - color[0] = 1.0f; - color[1] = 0.0f; - color[2] = 0.0f; - color[3] = 0.33f; - CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); - CG_DrawPic( x, y-4, w, BIGCHAR_HEIGHT+8, cgs.media.selectShader ); - } else { - color[0] = 0.5f; - color[1] = 0.5f; - color[2] = 0.5f; - color[3] = 0.33f; - CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); - } - CG_DrawBigString( x + 4, y, s, 1.0F); - } - - // first place - if ( s1 != SCORE_NOT_PRESENT ) { - s = va( "%2i", s1 ); - w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; - x -= w; - if ( !spectator && score == s1 ) { - color[0] = 0.0f; - color[1] = 0.0f; - color[2] = 1.0f; - color[3] = 0.33f; - CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); - CG_DrawPic( x, y-4, w, BIGCHAR_HEIGHT+8, cgs.media.selectShader ); - } else { - color[0] = 0.5f; - color[1] = 0.5f; - color[2] = 0.5f; - color[3] = 0.33f; - CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); - } - CG_DrawBigString( x + 4, y, s, 1.0F); - } - - if ( cgs.fraglimit ) { - s = va( "%2i", cgs.fraglimit ); - w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; - x -= w; - CG_DrawBigString( x + 4, y, s, 1.0F); - } - - } - - return y1 - 8; -} -#endif // MISSIONPACK - -/* -================ -CG_DrawPowerups -================ -*/ -#ifndef MISSIONPACK -static float CG_DrawPowerups( float y ) { - int sorted[MAX_POWERUPS]; - int sortedTime[MAX_POWERUPS]; - int i, j, k; - int active; - playerState_t *ps; - int t; - gitem_t *item; - int x; - int color; - float size; - float f; - static float colors[2][4] = { - { 0.2f, 1.0f, 0.2f, 1.0f } , - { 1.0f, 0.2f, 0.2f, 1.0f } - }; - - ps = &cg.snap->ps; - - if ( ps->stats[STAT_HEALTH] <= 0 ) { - return y; - } - - // sort the list by time remaining - active = 0; - for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { - if ( !ps->powerups[ i ] ) { - continue; - } - t = ps->powerups[ i ] - cg.time; - // ZOID--don't draw if the power up has unlimited time (999 seconds) - // This is true of the CTF flags - if ( t < 0 || t > 999000) { - continue; - } - - // insert into the list - for ( j = 0 ; j < active ; j++ ) { - if ( sortedTime[j] >= t ) { - for ( k = active - 1 ; k >= j ; k-- ) { - sorted[k+1] = sorted[k]; - sortedTime[k+1] = sortedTime[k]; - } - break; - } - } - sorted[j] = i; - sortedTime[j] = t; - active++; - } - - // draw the icons and timers - x = 640 - ICON_SIZE - CHAR_WIDTH * 2; - for ( i = 0 ; i < active ; i++ ) { - item = BG_FindItemForPowerup( sorted[i] ); - - if (item) { - - color = 1; - - y -= ICON_SIZE; - - trap_R_SetColor( colors[color] ); - CG_DrawField( x, y, 2, sortedTime[ i ] / 1000 ); - - t = ps->powerups[ sorted[i] ]; - if ( t - cg.time >= POWERUP_BLINKS * POWERUP_BLINK_TIME ) { - trap_R_SetColor( NULL ); - } else { - vec4_t modulate; - - f = (float)( t - cg.time ) / POWERUP_BLINK_TIME; - f -= (int)f; - modulate[0] = modulate[1] = modulate[2] = modulate[3] = f; - trap_R_SetColor( modulate ); - } - - if ( cg.powerupActive == sorted[i] && - cg.time - cg.powerupTime < PULSE_TIME ) { - f = 1.0 - ( ( (float)cg.time - cg.powerupTime ) / PULSE_TIME ); - size = ICON_SIZE * ( 1.0 + ( PULSE_SCALE - 1.0 ) * f ); - } else { - size = ICON_SIZE; - } - - CG_DrawPic( 640 - size, y + ICON_SIZE / 2 - size / 2, - size, size, trap_R_RegisterShader( item->icon ) ); - } - } - trap_R_SetColor( NULL ); - - return y; -} -#endif // MISSIONPACK - -/* -===================== -CG_DrawLowerRight - -===================== -*/ -#ifndef MISSIONPACK -static void CG_DrawLowerRight( void ) { - float y; - - y = 480 - ICON_SIZE; - - if ( cgs.gametype >= GT_TEAM && cg_drawTeamOverlay.integer == 2 ) { - y = CG_DrawTeamOverlay( y, qtrue, qfalse ); - } - - y = CG_DrawScores( y ); - y = CG_DrawPowerups( y ); -} -#endif // MISSIONPACK - -/* -=================== -CG_DrawPickupItem -=================== -*/ -#ifndef MISSIONPACK -static int CG_DrawPickupItem( int y ) { - int value; - float *fadeColor; - - if ( cg.snap->ps.stats[STAT_HEALTH] <= 0 ) { - return y; - } - - y -= ICON_SIZE; - - value = cg.itemPickup; - if ( value ) { - fadeColor = CG_FadeColor( cg.itemPickupTime, 3000 ); - if ( fadeColor ) { - CG_RegisterItemVisuals( value ); - trap_R_SetColor( fadeColor ); - CG_DrawPic( 8, y, ICON_SIZE, ICON_SIZE, cg_items[ value ].icon ); - CG_DrawBigString( ICON_SIZE + 16, y + (ICON_SIZE/2 - BIGCHAR_HEIGHT/2), bg_itemlist[ value ].pickup_name, fadeColor[0] ); - trap_R_SetColor( NULL ); - } - } - - return y; -} -#endif // MISSIONPACK - -/* -===================== -CG_DrawLowerLeft - -===================== -*/ -#ifndef MISSIONPACK -static void CG_DrawLowerLeft( void ) { - float y; - - y = 480 - ICON_SIZE; - - if ( cgs.gametype >= GT_TEAM && cg_drawTeamOverlay.integer == 3 ) { - y = CG_DrawTeamOverlay( y, qfalse, qfalse ); - } - - - y = CG_DrawPickupItem( y ); -} -#endif // MISSIONPACK - - -//=========================================================================================== - -/* -================= -CG_DrawTeamInfo -================= -*/ -#ifndef MISSIONPACK -static void CG_DrawTeamInfo( void ) { - int w, h; - int i, len; - vec4_t hcolor; - int chatHeight; - -#define CHATLOC_Y 420 // bottom end -#define CHATLOC_X 0 - - if (cg_teamChatHeight.integer < TEAMCHAT_HEIGHT) - chatHeight = cg_teamChatHeight.integer; - else - chatHeight = TEAMCHAT_HEIGHT; - if (chatHeight <= 0) - return; // disabled - - if (cgs.teamLastChatPos != cgs.teamChatPos) { - if (cg.time - cgs.teamChatMsgTimes[cgs.teamLastChatPos % chatHeight] > cg_teamChatTime.integer) { - cgs.teamLastChatPos++; - } - - h = (cgs.teamChatPos - cgs.teamLastChatPos) * TINYCHAR_HEIGHT; - - w = 0; - - for (i = cgs.teamLastChatPos; i < cgs.teamChatPos; i++) { - len = CG_DrawStrlen(cgs.teamChatMsgs[i % chatHeight]); - if (len > w) - w = len; - } - w *= TINYCHAR_WIDTH; - w += TINYCHAR_WIDTH * 2; - - if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED ) { - hcolor[0] = 1.0f; - hcolor[1] = 0.0f; - hcolor[2] = 0.0f; - hcolor[3] = 0.33f; - } else if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { - hcolor[0] = 0.0f; - hcolor[1] = 0.0f; - hcolor[2] = 1.0f; - hcolor[3] = 0.33f; - } else { - hcolor[0] = 0.0f; - hcolor[1] = 1.0f; - hcolor[2] = 0.0f; - hcolor[3] = 0.33f; - } - - trap_R_SetColor( hcolor ); - CG_DrawPic( CHATLOC_X, CHATLOC_Y - h, 640, h, cgs.media.teamStatusBar ); - trap_R_SetColor( NULL ); - - hcolor[0] = hcolor[1] = hcolor[2] = 1.0f; - hcolor[3] = 1.0f; - - for (i = cgs.teamChatPos - 1; i >= cgs.teamLastChatPos; i--) { - CG_DrawStringExt( CHATLOC_X + TINYCHAR_WIDTH, - CHATLOC_Y - (cgs.teamChatPos - i)*TINYCHAR_HEIGHT, - cgs.teamChatMsgs[i % chatHeight], hcolor, qfalse, qfalse, - TINYCHAR_WIDTH, TINYCHAR_HEIGHT, 0 ); - } - } -} -#endif // MISSIONPACK - -/* -=================== -CG_DrawHoldableItem -=================== -*/ -#ifndef MISSIONPACK -static void CG_DrawHoldableItem( void ) { - int value; - - value = cg.snap->ps.stats[STAT_HOLDABLE_ITEM]; - if ( value ) { - CG_RegisterItemVisuals( value ); - CG_DrawPic( 640-ICON_SIZE, (SCREEN_HEIGHT-ICON_SIZE)/2, ICON_SIZE, ICON_SIZE, cg_items[ value ].icon ); - } - -} -#endif // MISSIONPACK - -#ifdef MISSIONPACK -/* -=================== -CG_DrawPersistantPowerup -=================== -*/ -#if 0 // sos001208 - DEAD -static void CG_DrawPersistantPowerup( void ) { - int value; - - value = cg.snap->ps.stats[STAT_PERSISTANT_POWERUP]; - if ( value ) { - CG_RegisterItemVisuals( value ); - CG_DrawPic( 640-ICON_SIZE, (SCREEN_HEIGHT-ICON_SIZE)/2 - ICON_SIZE, ICON_SIZE, ICON_SIZE, cg_items[ value ].icon ); - } -} -#endif -#endif // MISSIONPACK - - -/* -=================== -CG_DrawReward -=================== -*/ -static void CG_DrawReward( void ) { - float *color; - int i, count; - float x, y; - char buf[32]; - - if ( !cg_drawRewards.integer ) { - return; - } - - color = CG_FadeColor( cg.rewardTime, REWARD_TIME ); - if ( !color ) { - if (cg.rewardStack > 0) { - for(i = 0; i < cg.rewardStack; i++) { - cg.rewardSound[i] = cg.rewardSound[i+1]; - cg.rewardShader[i] = cg.rewardShader[i+1]; - cg.rewardCount[i] = cg.rewardCount[i+1]; - } - cg.rewardTime = cg.time; - cg.rewardStack--; - color = CG_FadeColor( cg.rewardTime, REWARD_TIME ); - trap_S_StartLocalSound(cg.rewardSound[0], CHAN_ANNOUNCER); - } else { - return; - } - } - - trap_R_SetColor( color ); - - /* - count = cg.rewardCount[0]/10; // number of big rewards to draw - - if (count) { - y = 4; - x = 320 - count * ICON_SIZE; - for ( i = 0 ; i < count ; i++ ) { - CG_DrawPic( x, y, (ICON_SIZE*2)-4, (ICON_SIZE*2)-4, cg.rewardShader[0] ); - x += (ICON_SIZE*2); - } - } - - count = cg.rewardCount[0] - count*10; // number of small rewards to draw - */ - - if ( cg.rewardCount[0] >= 10 ) { - y = 56; - x = 320 - ICON_SIZE/2; - CG_DrawPic( x, y, ICON_SIZE-4, ICON_SIZE-4, cg.rewardShader[0] ); - Com_sprintf(buf, sizeof(buf), "%d", cg.rewardCount[0]); - x = ( SCREEN_WIDTH - SMALLCHAR_WIDTH * CG_DrawStrlen( buf ) ) / 2; - CG_DrawStringExt( x, y+ICON_SIZE, buf, color, qfalse, qtrue, - SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, 0 ); - } - else { - - count = cg.rewardCount[0]; - - y = 56; - x = 320 - count * ICON_SIZE/2; - for ( i = 0 ; i < count ; i++ ) { - CG_DrawPic( x, y, ICON_SIZE-4, ICON_SIZE-4, cg.rewardShader[0] ); - x += ICON_SIZE; - } - } - trap_R_SetColor( NULL ); -} - - -/* -=============================================================================== - -LAGOMETER - -=============================================================================== -*/ - -#define LAG_SAMPLES 128 - - -typedef struct { - int frameSamples[LAG_SAMPLES]; - int frameCount; - int snapshotFlags[LAG_SAMPLES]; - int snapshotSamples[LAG_SAMPLES]; - int snapshotCount; -} lagometer_t; - -lagometer_t lagometer; - -/* -============== -CG_AddLagometerFrameInfo - -Adds the current interpolate / extrapolate bar for this frame -============== -*/ -void CG_AddLagometerFrameInfo( void ) { - int offset; - - offset = cg.time - cg.latestSnapshotTime; - lagometer.frameSamples[ lagometer.frameCount & ( LAG_SAMPLES - 1) ] = offset; - lagometer.frameCount++; -} - -/* -============== -CG_AddLagometerSnapshotInfo - -Each time a snapshot is received, log its ping time and -the number of snapshots that were dropped before it. - -Pass NULL for a dropped packet. -============== -*/ -void CG_AddLagometerSnapshotInfo( snapshot_t *snap ) { - // dropped packet - if ( !snap ) { - lagometer.snapshotSamples[ lagometer.snapshotCount & ( LAG_SAMPLES - 1) ] = -1; - lagometer.snapshotCount++; - return; - } - - // add this snapshot's info - lagometer.snapshotSamples[ lagometer.snapshotCount & ( LAG_SAMPLES - 1) ] = snap->ping; - lagometer.snapshotFlags[ lagometer.snapshotCount & ( LAG_SAMPLES - 1) ] = snap->snapFlags; - lagometer.snapshotCount++; -} - -/* -============== -CG_DrawDisconnect - -Should we draw something differnet for long lag vs no packets? -============== -*/ -static void CG_DrawDisconnect( void ) { - float x, y; - int cmdNum; - usercmd_t cmd; - const char *s; - int w; // bk010215 - FIXME char message[1024]; - - // draw the phone jack if we are completely past our buffers - cmdNum = trap_GetCurrentCmdNumber() - CMD_BACKUP + 1; - trap_GetUserCmd( cmdNum, &cmd ); - if ( cmd.serverTime <= cg.snap->ps.commandTime - || cmd.serverTime > cg.time ) { // special check for map_restart // bk 0102165 - FIXME - return; - } - - // also add text in center of screen - s = "Connection Interrupted"; // bk 010215 - FIXME - w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; - CG_DrawBigString( 320 - w/2, 100, s, 1.0F); - - // blink the icon - if ( ( cg.time >> 9 ) & 1 ) { - return; - } - - x = 640 - 48; - y = 480 - 48; - - CG_DrawPic( x, y, 48, 48, trap_R_RegisterShader("gfx/2d/net.tga" ) ); -} - - -#define MAX_LAGOMETER_PING 900 -#define MAX_LAGOMETER_RANGE 300 - -/* -============== -CG_DrawLagometer -============== -*/ -static void CG_DrawLagometer( void ) { - int a, x, y, i; - float v; - float ax, ay, aw, ah, mid, range; - int color; - float vscale; - - if ( !cg_lagometer.integer || cgs.localServer ) { - CG_DrawDisconnect(); - return; - } - - // - // draw the graph - // -#ifdef MISSIONPACK - x = 640 - 48; - y = 480 - 144; -#else - x = 640 - 48; - y = 480 - 48; -#endif - - trap_R_SetColor( NULL ); - CG_DrawPic( x, y, 48, 48, cgs.media.lagometerShader ); - - ax = x; - ay = y; - aw = 48; - ah = 48; - CG_AdjustFrom640( &ax, &ay, &aw, &ah ); - - color = -1; - range = ah / 3; - mid = ay + range; - - vscale = range / MAX_LAGOMETER_RANGE; - - // draw the frame interpoalte / extrapolate graph - for ( a = 0 ; a < aw ; a++ ) { - i = ( lagometer.frameCount - 1 - a ) & (LAG_SAMPLES - 1); - v = lagometer.frameSamples[i]; - v *= vscale; - if ( v > 0 ) { - if ( color != 1 ) { - color = 1; - trap_R_SetColor( g_color_table[ColorIndex(COLOR_YELLOW)] ); - } - if ( v > range ) { - v = range; - } - trap_R_DrawStretchPic ( ax + aw - a, mid - v, 1, v, 0, 0, 0, 0, cgs.media.whiteShader ); - } else if ( v < 0 ) { - if ( color != 2 ) { - color = 2; - trap_R_SetColor( g_color_table[ColorIndex(COLOR_BLUE)] ); - } - v = -v; - if ( v > range ) { - v = range; - } - trap_R_DrawStretchPic( ax + aw - a, mid, 1, v, 0, 0, 0, 0, cgs.media.whiteShader ); - } - } - - // draw the snapshot latency / drop graph - range = ah / 2; - vscale = range / MAX_LAGOMETER_PING; - - for ( a = 0 ; a < aw ; a++ ) { - i = ( lagometer.snapshotCount - 1 - a ) & (LAG_SAMPLES - 1); - v = lagometer.snapshotSamples[i]; - if ( v > 0 ) { - if ( lagometer.snapshotFlags[i] & SNAPFLAG_RATE_DELAYED ) { - if ( color != 5 ) { - color = 5; // YELLOW for rate delay - trap_R_SetColor( g_color_table[ColorIndex(COLOR_YELLOW)] ); - } - } else { - if ( color != 3 ) { - color = 3; - trap_R_SetColor( g_color_table[ColorIndex(COLOR_GREEN)] ); - } - } - v = v * vscale; - if ( v > range ) { - v = range; - } - trap_R_DrawStretchPic( ax + aw - a, ay + ah - v, 1, v, 0, 0, 0, 0, cgs.media.whiteShader ); - } else if ( v < 0 ) { - if ( color != 4 ) { - color = 4; // RED for dropped snapshots - trap_R_SetColor( g_color_table[ColorIndex(COLOR_RED)] ); - } - trap_R_DrawStretchPic( ax + aw - a, ay + ah - range, 1, range, 0, 0, 0, 0, cgs.media.whiteShader ); - } - } - - trap_R_SetColor( NULL ); - - if ( cg_nopredict.integer || cg_synchronousClients.integer ) { - CG_DrawBigString( ax, ay, "snc", 1.0 ); - } - - CG_DrawDisconnect(); -} - - - -/* -=============================================================================== - -CENTER PRINTING - -=============================================================================== -*/ - - -/* -============== -CG_CenterPrint - -Called for important messages that should stay in the center of the screen -for a few moments -============== -*/ -void CG_CenterPrint( const char *str, int y, int charWidth ) { - char *s; - - Q_strncpyz( cg.centerPrint, str, sizeof(cg.centerPrint) ); - - cg.centerPrintTime = cg.time; - cg.centerPrintY = y; - cg.centerPrintCharWidth = charWidth; - - // count the number of lines for centering - cg.centerPrintLines = 1; - s = cg.centerPrint; - while( *s ) { - if (*s == '\n') - cg.centerPrintLines++; - s++; - } -} - - -/* -=================== -CG_DrawCenterString -=================== -*/ -static void CG_DrawCenterString( void ) { - char *start; - int l; - int x, y, w; -#ifdef MISSIONPACK // bk010221 - unused else - int h; -#endif - float *color; - - if ( !cg.centerPrintTime ) { - return; - } - - color = CG_FadeColor( cg.centerPrintTime, 1000 * cg_centertime.value ); - if ( !color ) { - return; - } - - trap_R_SetColor( color ); - - start = cg.centerPrint; - - y = cg.centerPrintY - cg.centerPrintLines * BIGCHAR_HEIGHT / 2; - - while ( 1 ) { - char linebuffer[1024]; - - for ( l = 0; l < 50; l++ ) { - if ( !start[l] || start[l] == '\n' ) { - break; - } - linebuffer[l] = start[l]; - } - linebuffer[l] = 0; - -#ifdef MISSIONPACK - w = CG_Text_Width(linebuffer, 0.5, 0); - h = CG_Text_Height(linebuffer, 0.5, 0); - x = (SCREEN_WIDTH - w) / 2; - CG_Text_Paint(x, y + h, 0.5, color, linebuffer, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE); - y += h + 6; -#else - w = cg.centerPrintCharWidth * CG_DrawStrlen( linebuffer ); - - x = ( SCREEN_WIDTH - w ) / 2; - - CG_DrawStringExt( x, y, linebuffer, color, qfalse, qtrue, - cg.centerPrintCharWidth, (int)(cg.centerPrintCharWidth * 1.5), 0 ); - - y += cg.centerPrintCharWidth * 1.5; -#endif - while ( *start && ( *start != '\n' ) ) { - start++; - } - if ( !*start ) { - break; - } - start++; - } - - trap_R_SetColor( NULL ); -} - - - -/* -================================================================================ - -CROSSHAIR - -================================================================================ -*/ - - -/* -================= -CG_DrawCrosshair -================= -*/ -static void CG_DrawCrosshair(void) { - float w, h; - qhandle_t hShader; - float f; - float x, y; - int ca; - - if ( !cg_drawCrosshair.integer ) { - return; - } - - if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR) { - return; - } - - if ( cg.renderingThirdPerson ) { - return; - } - - // set color based on health - if ( cg_crosshairHealth.integer ) { - vec4_t hcolor; - - CG_ColorForHealth( hcolor ); - trap_R_SetColor( hcolor ); - } else { - trap_R_SetColor( NULL ); - } - - w = h = cg_crosshairSize.value; - - // pulse the size of the crosshair when picking up items - f = cg.time - cg.itemPickupBlendTime; - if ( f > 0 && f < ITEM_BLOB_TIME ) { - f /= ITEM_BLOB_TIME; - w *= ( 1 + f ); - h *= ( 1 + f ); - } - - x = cg_crosshairX.integer; - y = cg_crosshairY.integer; - CG_AdjustFrom640( &x, &y, &w, &h ); - - ca = cg_drawCrosshair.integer; - if (ca < 0) { - ca = 0; - } - hShader = cgs.media.crosshairShader[ ca % NUM_CROSSHAIRS ]; - - trap_R_DrawStretchPic( x + cg.refdef.x + 0.5 * (cg.refdef.width - w), - y + cg.refdef.y + 0.5 * (cg.refdef.height - h), - w, h, 0, 0, 1, 1, hShader ); -} - - - -/* -================= -CG_ScanForCrosshairEntity -================= -*/ -static void CG_ScanForCrosshairEntity( void ) { - trace_t trace; - vec3_t start, end; - int content; - - VectorCopy( cg.refdef.vieworg, start ); - VectorMA( start, 131072, cg.refdef.viewaxis[0], end ); - - CG_Trace( &trace, start, vec3_origin, vec3_origin, end, - cg.snap->ps.clientNum, CONTENTS_SOLID|CONTENTS_BODY ); - if ( trace.entityNum >= MAX_CLIENTS ) { - return; - } - - // if the player is in fog, don't show it - content = trap_CM_PointContents( trace.endpos, 0 ); - if ( content & CONTENTS_FOG ) { - return; - } - - // if the player is invisible, don't show it - if ( cg_entities[ trace.entityNum ].currentState.powerups & ( 1 << PW_INVIS ) ) { - return; - } - - // update the fade timer - cg.crosshairClientNum = trace.entityNum; - cg.crosshairClientTime = cg.time; -} - - -/* -===================== -CG_DrawCrosshairNames -===================== -*/ -static void CG_DrawCrosshairNames( void ) { - float *color; - char *name; - float w; - - if ( !cg_drawCrosshair.integer ) { - return; - } - if ( !cg_drawCrosshairNames.integer ) { - return; - } - if ( cg.renderingThirdPerson ) { - return; - } - - // scan the known entities to see if the crosshair is sighted on one - CG_ScanForCrosshairEntity(); - - // draw the name of the player being looked at - color = CG_FadeColor( cg.crosshairClientTime, 1000 ); - if ( !color ) { - trap_R_SetColor( NULL ); - return; - } - - name = cgs.clientinfo[ cg.crosshairClientNum ].name; -#ifdef MISSIONPACK - color[3] *= 0.5f; - w = CG_Text_Width(name, 0.3f, 0); - CG_Text_Paint( 320 - w / 2, 190, 0.3f, color, name, 0, 0, ITEM_TEXTSTYLE_SHADOWED); -#else - w = CG_DrawStrlen( name ) * BIGCHAR_WIDTH; - CG_DrawBigString( 320 - w / 2, 170, name, color[3] * 0.5f ); -#endif - trap_R_SetColor( NULL ); -} - - -//============================================================================== - -/* -================= -CG_DrawSpectator -================= -*/ -static void CG_DrawSpectator(void) { - CG_DrawBigString(320 - 9 * 8, 440, "SPECTATOR", 1.0F); - if ( cgs.gametype == GT_TOURNAMENT ) { - CG_DrawBigString(320 - 15 * 8, 460, "waiting to play", 1.0F); - } - else if ( cgs.gametype >= GT_TEAM ) { - CG_DrawBigString(320 - 39 * 8, 460, "press ESC and use the JOIN menu to play", 1.0F); - } -} - -/* -================= -CG_DrawVote -================= -*/ -static void CG_DrawVote(void) { - char *s; - int sec; - - if ( !cgs.voteTime ) { - return; - } - - // play a talk beep whenever it is modified - if ( cgs.voteModified ) { - cgs.voteModified = qfalse; - trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); - } - - sec = ( VOTE_TIME - ( cg.time - cgs.voteTime ) ) / 1000; - if ( sec < 0 ) { - sec = 0; - } -#ifdef MISSIONPACK - s = va("VOTE(%i):%s yes:%i no:%i", sec, cgs.voteString, cgs.voteYes, cgs.voteNo); - CG_DrawSmallString( 0, 58, s, 1.0F ); - s = "or press ESC then click Vote"; - CG_DrawSmallString( 0, 58 + SMALLCHAR_HEIGHT + 2, s, 1.0F ); -#else - s = va("VOTE(%i):%s yes:%i no:%i", sec, cgs.voteString, cgs.voteYes, cgs.voteNo ); - CG_DrawSmallString( 0, 58, s, 1.0F ); -#endif -} - -/* -================= -CG_DrawTeamVote -================= -*/ -static void CG_DrawTeamVote(void) { - char *s; - int sec, cs_offset; - - if ( cgs.clientinfo->team == TEAM_RED ) - cs_offset = 0; - else if ( cgs.clientinfo->team == TEAM_BLUE ) - cs_offset = 1; - else - return; - - if ( !cgs.teamVoteTime[cs_offset] ) { - return; - } - - // play a talk beep whenever it is modified - if ( cgs.teamVoteModified[cs_offset] ) { - cgs.teamVoteModified[cs_offset] = qfalse; - trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); - } - - sec = ( VOTE_TIME - ( cg.time - cgs.teamVoteTime[cs_offset] ) ) / 1000; - if ( sec < 0 ) { - sec = 0; - } - s = va("TEAMVOTE(%i):%s yes:%i no:%i", sec, cgs.teamVoteString[cs_offset], - cgs.teamVoteYes[cs_offset], cgs.teamVoteNo[cs_offset] ); - CG_DrawSmallString( 0, 90, s, 1.0F ); -} - - -static qboolean CG_DrawScoreboard() { -#ifdef MISSIONPACK - static qboolean firstTime = qtrue; - float fade, *fadeColor; - - if (menuScoreboard) { - menuScoreboard->window.flags &= ~WINDOW_FORCED; - } - if (cg_paused.integer) { - cg.deferredPlayerLoading = 0; - firstTime = qtrue; - return qfalse; - } - - // should never happen in Team Arena - if (cgs.gametype == GT_SINGLE_PLAYER && cg.predictedPlayerState.pm_type == PM_INTERMISSION ) { - cg.deferredPlayerLoading = 0; - firstTime = qtrue; - return qfalse; - } - - // don't draw scoreboard during death while warmup up - if ( cg.warmup && !cg.showScores ) { - return qfalse; - } - - if ( cg.showScores || cg.predictedPlayerState.pm_type == PM_DEAD || cg.predictedPlayerState.pm_type == PM_INTERMISSION ) { - fade = 1.0; - fadeColor = colorWhite; - } else { - fadeColor = CG_FadeColor( cg.scoreFadeTime, FADE_TIME ); - if ( !fadeColor ) { - // next time scoreboard comes up, don't print killer - cg.deferredPlayerLoading = 0; - cg.killerName[0] = 0; - firstTime = qtrue; - return qfalse; - } - fade = *fadeColor; - } - - - if (menuScoreboard == NULL) { - if ( cgs.gametype >= GT_TEAM ) { - menuScoreboard = Menus_FindByName("teamscore_menu"); - } else { - menuScoreboard = Menus_FindByName("score_menu"); - } - } - - if (menuScoreboard) { - if (firstTime) { - CG_SetScoreSelection(menuScoreboard); - firstTime = qfalse; - } - Menu_Paint(menuScoreboard, qtrue); - } - - // load any models that have been deferred - if ( ++cg.deferredPlayerLoading > 10 ) { - CG_LoadDeferredPlayers(); - } - - return qtrue; -#else - return CG_DrawOldScoreboard(); -#endif -} - -/* -================= -CG_DrawIntermission -================= -*/ -static void CG_DrawIntermission( void ) { -// int key; -#ifdef MISSIONPACK - //if (cg_singlePlayer.integer) { - // CG_DrawCenterString(); - // return; - //} -#else - if ( cgs.gametype == GT_SINGLE_PLAYER ) { - CG_DrawCenterString(); - return; - } -#endif - cg.scoreFadeTime = cg.time; - cg.scoreBoardShowing = CG_DrawScoreboard(); -} - -/* -================= -CG_DrawFollow -================= -*/ -static qboolean CG_DrawFollow( void ) { - float x; - vec4_t color; - const char *name; - - if ( !(cg.snap->ps.pm_flags & PMF_FOLLOW) ) { - return qfalse; - } - color[0] = 1; - color[1] = 1; - color[2] = 1; - color[3] = 1; - - - CG_DrawBigString( 320 - 9 * 8, 24, "following", 1.0F ); - - name = cgs.clientinfo[ cg.snap->ps.clientNum ].name; - - x = 0.5 * ( 640 - GIANT_WIDTH * CG_DrawStrlen( name ) ); - - CG_DrawStringExt( x, 40, name, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); - - return qtrue; -} - - - -/* -================= -CG_DrawAmmoWarning -================= -*/ -static void CG_DrawAmmoWarning( void ) { - const char *s; - int w; - - if ( cg_drawAmmoWarning.integer == 0 ) { - return; - } - - if ( !cg.lowAmmoWarning ) { - return; - } - - if ( cg.lowAmmoWarning == 2 ) { - s = "OUT OF AMMO"; - } else { - s = "LOW AMMO WARNING"; - } - w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; - CG_DrawBigString(320 - w / 2, 64, s, 1.0F); -} - - -#ifdef MISSIONPACK -/* -================= -CG_DrawProxWarning -================= -*/ -static void CG_DrawProxWarning( void ) { - char s [32]; - int w; - static int proxTime; - static int proxCounter; - static int proxTick; - - if( !(cg.snap->ps.eFlags & EF_TICKING ) ) { - proxTime = 0; - return; - } - - if (proxTime == 0) { - proxTime = cg.time + 5000; - proxCounter = 5; - proxTick = 0; - } - - if (cg.time > proxTime) { - proxTick = proxCounter--; - proxTime = cg.time + 1000; - } - - if (proxTick != 0) { - Com_sprintf(s, sizeof(s), "INTERNAL COMBUSTION IN: %i", proxTick); - } else { - Com_sprintf(s, sizeof(s), "YOU HAVE BEEN MINED"); - } - - w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; - CG_DrawBigStringColor( 320 - w / 2, 64 + BIGCHAR_HEIGHT, s, g_color_table[ColorIndex(COLOR_RED)] ); -} -#endif - - -/* -================= -CG_DrawWarmup -================= -*/ -static void CG_DrawWarmup( void ) { - int w; - int sec; - int i; - float scale; - clientInfo_t *ci1, *ci2; - int cw; - const char *s; - - sec = cg.warmup; - if ( !sec ) { - return; - } - - if ( sec < 0 ) { - s = "Waiting for players"; - w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; - CG_DrawBigString(320 - w / 2, 24, s, 1.0F); - cg.warmupCount = 0; - return; - } - - if (cgs.gametype == GT_TOURNAMENT) { - // find the two active players - ci1 = NULL; - ci2 = NULL; - for ( i = 0 ; i < cgs.maxclients ; i++ ) { - if ( cgs.clientinfo[i].infoValid && cgs.clientinfo[i].team == TEAM_FREE ) { - if ( !ci1 ) { - ci1 = &cgs.clientinfo[i]; - } else { - ci2 = &cgs.clientinfo[i]; - } - } - } - - if ( ci1 && ci2 ) { - s = va( "%s vs %s", ci1->name, ci2->name ); -#ifdef MISSIONPACK - w = CG_Text_Width(s, 0.6f, 0); - CG_Text_Paint(320 - w / 2, 60, 0.6f, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE); -#else - w = CG_DrawStrlen( s ); - if ( w > 640 / GIANT_WIDTH ) { - cw = 640 / w; - } else { - cw = GIANT_WIDTH; - } - CG_DrawStringExt( 320 - w * cw/2, 20,s, colorWhite, - qfalse, qtrue, cw, (int)(cw * 1.5f), 0 ); -#endif - } - } else { - if ( cgs.gametype == GT_FFA ) { - s = "Free For All"; - } else if ( cgs.gametype == GT_TEAM ) { - s = "Team Deathmatch"; - } else if ( cgs.gametype == GT_CTF ) { - s = "Capture the Flag"; -#ifdef MISSIONPACK - } else if ( cgs.gametype == GT_1FCTF ) { - s = "One Flag CTF"; - } else if ( cgs.gametype == GT_OBELISK ) { - s = "Overload"; - } else if ( cgs.gametype == GT_HARVESTER ) { - s = "Harvester"; -#endif - } else { - s = ""; - } -#ifdef MISSIONPACK - w = CG_Text_Width(s, 0.6f, 0); - CG_Text_Paint(320 - w / 2, 90, 0.6f, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE); -#else - w = CG_DrawStrlen( s ); - if ( w > 640 / GIANT_WIDTH ) { - cw = 640 / w; - } else { - cw = GIANT_WIDTH; - } - CG_DrawStringExt( 320 - w * cw/2, 25,s, colorWhite, - qfalse, qtrue, cw, (int)(cw * 1.1f), 0 ); -#endif - } - - sec = ( sec - cg.time ) / 1000; - if ( sec < 0 ) { - cg.warmup = 0; - sec = 0; - } - s = va( "Starts in: %i", sec + 1 ); - if ( sec != cg.warmupCount ) { - cg.warmupCount = sec; - switch ( sec ) { - case 0: - trap_S_StartLocalSound( cgs.media.count1Sound, CHAN_ANNOUNCER ); - break; - case 1: - trap_S_StartLocalSound( cgs.media.count2Sound, CHAN_ANNOUNCER ); - break; - case 2: - trap_S_StartLocalSound( cgs.media.count3Sound, CHAN_ANNOUNCER ); - break; - default: - break; - } - } - scale = 0.45f; - switch ( cg.warmupCount ) { - case 0: - cw = 28; - scale = 0.54f; - break; - case 1: - cw = 24; - scale = 0.51f; - break; - case 2: - cw = 20; - scale = 0.48f; - break; - default: - cw = 16; - scale = 0.45f; - break; - } - -#ifdef MISSIONPACK - w = CG_Text_Width(s, scale, 0); - CG_Text_Paint(320 - w / 2, 125, scale, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE); -#else - w = CG_DrawStrlen( s ); - CG_DrawStringExt( 320 - w * cw/2, 70, s, colorWhite, - qfalse, qtrue, cw, (int)(cw * 1.5), 0 ); -#endif -} - -//================================================================================== -#ifdef MISSIONPACK -/* -================= -CG_DrawTimedMenus -================= -*/ -void CG_DrawTimedMenus() { - if (cg.voiceTime) { - int t = cg.time - cg.voiceTime; - if ( t > 2500 ) { - Menus_CloseByName("voiceMenu"); - trap_Cvar_Set("cl_conXOffset", "0"); - cg.voiceTime = 0; - } - } -} -#endif -/* -================= -CG_Draw2D -================= -*/ -static void CG_Draw2D( void ) { -#ifdef MISSIONPACK - if (cgs.orderPending && cg.time > cgs.orderTime) { - CG_CheckOrderPending(); - } -#endif - // if we are taking a levelshot for the menu, don't draw anything - if ( cg.levelShot ) { - return; - } - - if ( cg_draw2D.integer == 0 ) { - return; - } - - if ( cg.snap->ps.pm_type == PM_INTERMISSION ) { - CG_DrawIntermission(); - return; - } - -/* - if (cg.cameraMode) { - return; - } -*/ - if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR ) { - CG_DrawSpectator(); - CG_DrawCrosshair(); - CG_DrawCrosshairNames(); - } else { - // don't draw any status if dead or the scoreboard is being explicitly shown - if ( !cg.showScores && cg.snap->ps.stats[STAT_HEALTH] > 0 ) { - -#ifdef MISSIONPACK - if ( cg_drawStatus.integer ) { - Menu_PaintAll(); - CG_DrawTimedMenus(); - } -#else - CG_DrawStatusBar(); -#endif - - CG_DrawAmmoWarning(); - -#ifdef MISSIONPACK - CG_DrawProxWarning(); -#endif - CG_DrawCrosshair(); - CG_DrawCrosshairNames(); - CG_DrawWeaponSelect(); - -#ifndef MISSIONPACK - CG_DrawHoldableItem(); -#else - //CG_DrawPersistantPowerup(); -#endif - CG_DrawReward(); - } - - if ( cgs.gametype >= GT_TEAM ) { -#ifndef MISSIONPACK - CG_DrawTeamInfo(); -#endif - } - } - - CG_DrawVote(); - CG_DrawTeamVote(); - - CG_DrawLagometer(); - -#ifdef MISSIONPACK - if (!cg_paused.integer) { - CG_DrawUpperRight(); - } -#else - CG_DrawUpperRight(); -#endif - -#ifndef MISSIONPACK - CG_DrawLowerRight(); - CG_DrawLowerLeft(); -#endif - - if ( !CG_DrawFollow() ) { - CG_DrawWarmup(); - } - - // don't draw center string if scoreboard is up - cg.scoreBoardShowing = CG_DrawScoreboard(); - if ( !cg.scoreBoardShowing) { - CG_DrawCenterString(); - } -} - - -static void CG_DrawTourneyScoreboard() { -#ifdef MISSIONPACK -#else - CG_DrawOldTourneyScoreboard(); -#endif -} - -/* -===================== -CG_DrawActive - -Perform all drawing needed to completely fill the screen -===================== -*/ -void CG_DrawActive( stereoFrame_t stereoView ) { - float separation; - vec3_t baseOrg; - - // optionally draw the info screen instead - if ( !cg.snap ) { - CG_DrawInformation(); - return; - } - - // optionally draw the tournement scoreboard instead - if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR && - ( cg.snap->ps.pm_flags & PMF_SCOREBOARD ) ) { - CG_DrawTourneyScoreboard(); - return; - } - - switch ( stereoView ) { - case STEREO_CENTER: - separation = 0; - break; - case STEREO_LEFT: - separation = -cg_stereoSeparation.value / 2; - break; - case STEREO_RIGHT: - separation = cg_stereoSeparation.value / 2; - break; - default: - separation = 0; - CG_Error( "CG_DrawActive: Undefined stereoView" ); - } - - - // clear around the rendered view if sized down - CG_TileClear(); - - // offset vieworg appropriately if we're doing stereo separation - VectorCopy( cg.refdef.vieworg, baseOrg ); - if ( separation != 0 ) { - VectorMA( cg.refdef.vieworg, -separation, cg.refdef.viewaxis[1], cg.refdef.vieworg ); - } - - // draw 3D view - trap_R_RenderScene( &cg.refdef ); - - // restore original viewpoint if running stereo - if ( separation != 0 ) { - VectorCopy( baseOrg, cg.refdef.vieworg ); - } - - // draw status bar and other floating elements - CG_Draw2D(); -} - - - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// cg_draw.c -- draw all of the graphical elements during +// active (after loading) gameplay + +#include "cg_local.h" + +#ifdef MISSIONPACK +#include "../ui/ui_shared.h" + +// used for scoreboard +extern displayContextDef_t cgDC; +menuDef_t *menuScoreboard = NULL; +#else +int drawTeamOverlayModificationCount = -1; +#endif + +int sortedTeamPlayers[TEAM_MAXOVERLAY]; +int numSortedTeamPlayers; + +char systemChat[256]; +char teamChat1[256]; +char teamChat2[256]; + +#ifdef MISSIONPACK + +int CG_Text_Width(const char *text, float scale, int limit) { + int count,len; + float out; + glyphInfo_t *glyph; + float useScale; +// FIXME: see ui_main.c, same problem +// const unsigned char *s = text; + const char *s = text; + fontInfo_t *font = &cgDC.Assets.textFont; + if (scale <= cg_smallFont.value) { + font = &cgDC.Assets.smallFont; + } else if (scale > cg_bigFont.value) { + font = &cgDC.Assets.bigFont; + } + useScale = scale * font->glyphScale; + out = 0; + if (text) { + len = strlen(text); + if (limit > 0 && len > limit) { + len = limit; + } + count = 0; + while (s && *s && count < len) { + if ( Q_IsColorString(s) ) { + s += 2; + continue; + } else { + glyph = &font->glyphs[(int)*s]; // TTimo: FIXME: getting nasty warnings without the cast, hopefully this doesn't break the VM build + out += glyph->xSkip; + s++; + count++; + } + } + } + return out * useScale; +} + +int CG_Text_Height(const char *text, float scale, int limit) { + int len, count; + float max; + glyphInfo_t *glyph; + float useScale; +// TTimo: FIXME +// const unsigned char *s = text; + const char *s = text; + fontInfo_t *font = &cgDC.Assets.textFont; + if (scale <= cg_smallFont.value) { + font = &cgDC.Assets.smallFont; + } else if (scale > cg_bigFont.value) { + font = &cgDC.Assets.bigFont; + } + useScale = scale * font->glyphScale; + max = 0; + if (text) { + len = strlen(text); + if (limit > 0 && len > limit) { + len = limit; + } + count = 0; + while (s && *s && count < len) { + if ( Q_IsColorString(s) ) { + s += 2; + continue; + } else { + glyph = &font->glyphs[(int)*s]; // TTimo: FIXME: getting nasty warnings without the cast, hopefully this doesn't break the VM build + if (max < glyph->height) { + max = glyph->height; + } + s++; + count++; + } + } + } + return max * useScale; +} + +void CG_Text_PaintChar(float x, float y, float width, float height, float scale, float s, float t, float s2, float t2, qhandle_t hShader) { + float w, h; + w = width * scale; + h = height * scale; + CG_AdjustFrom640( &x, &y, &w, &h ); + trap_R_DrawStretchPic( x, y, w, h, s, t, s2, t2, hShader ); +} + +void CG_Text_Paint(float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit, int style) { + int len, count; + vec4_t newColor; + glyphInfo_t *glyph; + float useScale; + fontInfo_t *font = &cgDC.Assets.textFont; + if (scale <= cg_smallFont.value) { + font = &cgDC.Assets.smallFont; + } else if (scale > cg_bigFont.value) { + font = &cgDC.Assets.bigFont; + } + useScale = scale * font->glyphScale; + if (text) { +// TTimo: FIXME +// const unsigned char *s = text; + const char *s = text; + trap_R_SetColor( color ); + memcpy(&newColor[0], &color[0], sizeof(vec4_t)); + len = strlen(text); + if (limit > 0 && len > limit) { + len = limit; + } + count = 0; + while (s && *s && count < len) { + glyph = &font->glyphs[(int)*s]; // TTimo: FIXME: getting nasty warnings without the cast, hopefully this doesn't break the VM build + //int yadj = Assets.textFont.glyphs[text[i]].bottom + Assets.textFont.glyphs[text[i]].top; + //float yadj = scale * (Assets.textFont.glyphs[text[i]].imageHeight - Assets.textFont.glyphs[text[i]].height); + if ( Q_IsColorString( s ) ) { + memcpy( newColor, g_color_table[ColorIndex(*(s+1))], sizeof( newColor ) ); + newColor[3] = color[3]; + trap_R_SetColor( newColor ); + s += 2; + continue; + } else { + float yadj = useScale * glyph->top; + if (style == ITEM_TEXTSTYLE_SHADOWED || style == ITEM_TEXTSTYLE_SHADOWEDMORE) { + int ofs = style == ITEM_TEXTSTYLE_SHADOWED ? 1 : 2; + colorBlack[3] = newColor[3]; + trap_R_SetColor( colorBlack ); + CG_Text_PaintChar(x + ofs, y - yadj + ofs, + glyph->imageWidth, + glyph->imageHeight, + useScale, + glyph->s, + glyph->t, + glyph->s2, + glyph->t2, + glyph->glyph); + colorBlack[3] = 1.0; + trap_R_SetColor( newColor ); + } + CG_Text_PaintChar(x, y - yadj, + glyph->imageWidth, + glyph->imageHeight, + useScale, + glyph->s, + glyph->t, + glyph->s2, + glyph->t2, + glyph->glyph); + // CG_DrawPic(x, y - yadj, scale * cgDC.Assets.textFont.glyphs[text[i]].imageWidth, scale * cgDC.Assets.textFont.glyphs[text[i]].imageHeight, cgDC.Assets.textFont.glyphs[text[i]].glyph); + x += (glyph->xSkip * useScale) + adjust; + s++; + count++; + } + } + trap_R_SetColor( NULL ); + } +} + + +#endif + +/* +============== +CG_DrawField + +Draws large numbers for status bar and powerups +============== +*/ +#ifndef MISSIONPACK +static void CG_DrawField (int x, int y, int width, int value) { + char num[16], *ptr; + int l; + int frame; + + if ( width < 1 ) { + return; + } + + // draw number string + if ( width > 5 ) { + width = 5; + } + + switch ( width ) { + case 1: + value = value > 9 ? 9 : value; + value = value < 0 ? 0 : value; + break; + case 2: + value = value > 99 ? 99 : value; + value = value < -9 ? -9 : value; + break; + case 3: + value = value > 999 ? 999 : value; + value = value < -99 ? -99 : value; + break; + case 4: + value = value > 9999 ? 9999 : value; + value = value < -999 ? -999 : value; + break; + } + + Com_sprintf (num, sizeof(num), "%i", value); + l = strlen(num); + if (l > width) + l = width; + x += 2 + CHAR_WIDTH*(width - l); + + ptr = num; + while (*ptr && l) + { + if (*ptr == '-') + frame = STAT_MINUS; + else + frame = *ptr -'0'; + + CG_DrawPic( x,y, CHAR_WIDTH, CHAR_HEIGHT, cgs.media.numberShaders[frame] ); + x += CHAR_WIDTH; + ptr++; + l--; + } +} +#endif // MISSIONPACK + +/* +================ +CG_Draw3DModel + +================ +*/ +void CG_Draw3DModel( float x, float y, float w, float h, qhandle_t model, qhandle_t skin, vec3_t origin, vec3_t angles ) { + refdef_t refdef; + refEntity_t ent; + + if ( !cg_draw3dIcons.integer || !cg_drawIcons.integer ) { + return; + } + + CG_AdjustFrom640( &x, &y, &w, &h ); + + memset( &refdef, 0, sizeof( refdef ) ); + + memset( &ent, 0, sizeof( ent ) ); + AnglesToAxis( angles, ent.axis ); + VectorCopy( origin, ent.origin ); + ent.hModel = model; + ent.customSkin = skin; + ent.renderfx = RF_NOSHADOW; // no stencil shadows + + refdef.rdflags = RDF_NOWORLDMODEL; + + AxisClear( refdef.viewaxis ); + + refdef.fov_x = 30; + refdef.fov_y = 30; + + refdef.x = x; + refdef.y = y; + refdef.width = w; + refdef.height = h; + + refdef.time = cg.time; + + trap_R_ClearScene(); + trap_R_AddRefEntityToScene( &ent ); + trap_R_RenderScene( &refdef ); +} + +/* +================ +CG_DrawHead + +Used for both the status bar and the scoreboard +================ +*/ +void CG_DrawHead( float x, float y, float w, float h, int clientNum, vec3_t headAngles ) { + clipHandle_t cm; + clientInfo_t *ci; + float len; + vec3_t origin; + vec3_t mins, maxs; + + ci = &cgs.clientinfo[ clientNum ]; + + if ( cg_draw3dIcons.integer ) { + cm = ci->headModel; + if ( !cm ) { + return; + } + + // offset the origin y and z to center the head + trap_R_ModelBounds( cm, mins, maxs ); + + origin[2] = -0.5 * ( mins[2] + maxs[2] ); + origin[1] = 0.5 * ( mins[1] + maxs[1] ); + + // calculate distance so the head nearly fills the box + // assume heads are taller than wide + len = 0.7 * ( maxs[2] - mins[2] ); + origin[0] = len / 0.268; // len / tan( fov/2 ) + + // allow per-model tweaking + VectorAdd( origin, ci->headOffset, origin ); + + CG_Draw3DModel( x, y, w, h, ci->headModel, ci->headSkin, origin, headAngles ); + } else if ( cg_drawIcons.integer ) { + CG_DrawPic( x, y, w, h, ci->modelIcon ); + } + + // if they are deferred, draw a cross out + if ( ci->deferred ) { + CG_DrawPic( x, y, w, h, cgs.media.deferShader ); + } +} + +/* +================ +CG_DrawFlagModel + +Used for both the status bar and the scoreboard +================ +*/ +void CG_DrawFlagModel( float x, float y, float w, float h, int team, qboolean force2D ) { + qhandle_t cm; + float len; + vec3_t origin, angles; + vec3_t mins, maxs; + qhandle_t handle; + + if ( !force2D && cg_draw3dIcons.integer ) { + + VectorClear( angles ); + + cm = cgs.media.redFlagModel; + + // offset the origin y and z to center the flag + trap_R_ModelBounds( cm, mins, maxs ); + + origin[2] = -0.5 * ( mins[2] + maxs[2] ); + origin[1] = 0.5 * ( mins[1] + maxs[1] ); + + // calculate distance so the flag nearly fills the box + // assume heads are taller than wide + len = 0.5 * ( maxs[2] - mins[2] ); + origin[0] = len / 0.268; // len / tan( fov/2 ) + + angles[YAW] = 60 * sin( cg.time / 2000.0 );; + + if( team == TEAM_RED ) { + handle = cgs.media.redFlagModel; + } else if( team == TEAM_BLUE ) { + handle = cgs.media.blueFlagModel; + } else if( team == TEAM_FREE ) { + handle = cgs.media.neutralFlagModel; + } else { + return; + } + CG_Draw3DModel( x, y, w, h, handle, 0, origin, angles ); + } else if ( cg_drawIcons.integer ) { + gitem_t *item; + + if( team == TEAM_RED ) { + item = BG_FindItemForPowerup( PW_REDFLAG ); + } else if( team == TEAM_BLUE ) { + item = BG_FindItemForPowerup( PW_BLUEFLAG ); + } else if( team == TEAM_FREE ) { + item = BG_FindItemForPowerup( PW_NEUTRALFLAG ); + } else { + return; + } + if (item) { + CG_DrawPic( x, y, w, h, cg_items[ ITEM_INDEX(item) ].icon ); + } + } +} + +/* +================ +CG_DrawStatusBarHead + +================ +*/ +#ifndef MISSIONPACK + +static void CG_DrawStatusBarHead( float x ) { + vec3_t angles; + float size, stretch; + float frac; + + VectorClear( angles ); + + if ( cg.damageTime && cg.time - cg.damageTime < DAMAGE_TIME ) { + frac = (float)(cg.time - cg.damageTime ) / DAMAGE_TIME; + size = ICON_SIZE * 1.25 * ( 1.5 - frac * 0.5 ); + + stretch = size - ICON_SIZE * 1.25; + // kick in the direction of damage + x -= stretch * 0.5 + cg.damageX * stretch * 0.5; + + cg.headStartYaw = 180 + cg.damageX * 45; + + cg.headEndYaw = 180 + 20 * cos( crandom()*M_PI ); + cg.headEndPitch = 5 * cos( crandom()*M_PI ); + + cg.headStartTime = cg.time; + cg.headEndTime = cg.time + 100 + random() * 2000; + } else { + if ( cg.time >= cg.headEndTime ) { + // select a new head angle + cg.headStartYaw = cg.headEndYaw; + cg.headStartPitch = cg.headEndPitch; + cg.headStartTime = cg.headEndTime; + cg.headEndTime = cg.time + 100 + random() * 2000; + + cg.headEndYaw = 180 + 20 * cos( crandom()*M_PI ); + cg.headEndPitch = 5 * cos( crandom()*M_PI ); + } + + size = ICON_SIZE * 1.25; + } + + // if the server was frozen for a while we may have a bad head start time + if ( cg.headStartTime > cg.time ) { + cg.headStartTime = cg.time; + } + + frac = ( cg.time - cg.headStartTime ) / (float)( cg.headEndTime - cg.headStartTime ); + frac = frac * frac * ( 3 - 2 * frac ); + angles[YAW] = cg.headStartYaw + ( cg.headEndYaw - cg.headStartYaw ) * frac; + angles[PITCH] = cg.headStartPitch + ( cg.headEndPitch - cg.headStartPitch ) * frac; + + CG_DrawHead( x, 480 - size, size, size, + cg.snap->ps.clientNum, angles ); +} +#endif // MISSIONPACK + +/* +================ +CG_DrawStatusBarFlag + +================ +*/ +#ifndef MISSIONPACK +static void CG_DrawStatusBarFlag( float x, int team ) { + CG_DrawFlagModel( x, 480 - ICON_SIZE, ICON_SIZE, ICON_SIZE, team, qfalse ); +} +#endif // MISSIONPACK + +/* +================ +CG_DrawTeamBackground + +================ +*/ +void CG_DrawTeamBackground( int x, int y, int w, int h, float alpha, int team ) +{ + vec4_t hcolor; + + hcolor[3] = alpha; + if ( team == TEAM_RED ) { + hcolor[0] = 1; + hcolor[1] = 0; + hcolor[2] = 0; + } else if ( team == TEAM_BLUE ) { + hcolor[0] = 0; + hcolor[1] = 0; + hcolor[2] = 1; + } else { + return; + } + trap_R_SetColor( hcolor ); + CG_DrawPic( x, y, w, h, cgs.media.teamStatusBar ); + trap_R_SetColor( NULL ); +} + +/* +================ +CG_DrawStatusBar + +================ +*/ +#ifndef MISSIONPACK +static void CG_DrawStatusBar( void ) { + int color; + centity_t *cent; + playerState_t *ps; + int value; + vec4_t hcolor; + vec3_t angles; + vec3_t origin; +#ifdef MISSIONPACK + qhandle_t handle; +#endif + static float colors[4][4] = { +// { 0.2, 1.0, 0.2, 1.0 } , { 1.0, 0.2, 0.2, 1.0 }, {0.5, 0.5, 0.5, 1} }; + { 1.0f, 0.69f, 0.0f, 1.0f }, // normal + { 1.0f, 0.2f, 0.2f, 1.0f }, // low health + { 0.5f, 0.5f, 0.5f, 1.0f }, // weapon firing + { 1.0f, 1.0f, 1.0f, 1.0f } }; // health > 100 + + if ( cg_drawStatus.integer == 0 ) { + return; + } + + // draw the team background + CG_DrawTeamBackground( 0, 420, 640, 60, 0.33f, cg.snap->ps.persistant[PERS_TEAM] ); + + cent = &cg_entities[cg.snap->ps.clientNum]; + ps = &cg.snap->ps; + + VectorClear( angles ); + + // draw any 3D icons first, so the changes back to 2D are minimized + if ( cent->currentState.weapon && cg_weapons[ cent->currentState.weapon ].ammoModel ) { + origin[0] = 70; + origin[1] = 0; + origin[2] = 0; + angles[YAW] = 90 + 20 * sin( cg.time / 1000.0 ); + CG_Draw3DModel( CHAR_WIDTH*3 + TEXT_ICON_SPACE, 432, ICON_SIZE, ICON_SIZE, + cg_weapons[ cent->currentState.weapon ].ammoModel, 0, origin, angles ); + } + + CG_DrawStatusBarHead( 185 + CHAR_WIDTH*3 + TEXT_ICON_SPACE ); + + if( cg.predictedPlayerState.powerups[PW_REDFLAG] ) { + CG_DrawStatusBarFlag( 185 + CHAR_WIDTH*3 + TEXT_ICON_SPACE + ICON_SIZE, TEAM_RED ); + } else if( cg.predictedPlayerState.powerups[PW_BLUEFLAG] ) { + CG_DrawStatusBarFlag( 185 + CHAR_WIDTH*3 + TEXT_ICON_SPACE + ICON_SIZE, TEAM_BLUE ); + } else if( cg.predictedPlayerState.powerups[PW_NEUTRALFLAG] ) { + CG_DrawStatusBarFlag( 185 + CHAR_WIDTH*3 + TEXT_ICON_SPACE + ICON_SIZE, TEAM_FREE ); + } + + if ( ps->stats[ STAT_ARMOR ] ) { + origin[0] = 90; + origin[1] = 0; + origin[2] = -10; + angles[YAW] = ( cg.time & 2047 ) * 360 / 2048.0; + CG_Draw3DModel( 370 + CHAR_WIDTH*3 + TEXT_ICON_SPACE, 432, ICON_SIZE, ICON_SIZE, + cgs.media.armorModel, 0, origin, angles ); + } +#ifdef MISSIONPACK + if( cgs.gametype == GT_HARVESTER ) { + origin[0] = 90; + origin[1] = 0; + origin[2] = -10; + angles[YAW] = ( cg.time & 2047 ) * 360 / 2048.0; + if( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { + handle = cgs.media.redCubeModel; + } else { + handle = cgs.media.blueCubeModel; + } + CG_Draw3DModel( 640 - (TEXT_ICON_SPACE + ICON_SIZE), 416, ICON_SIZE, ICON_SIZE, handle, 0, origin, angles ); + } +#endif + // + // ammo + // + if ( cent->currentState.weapon ) { + value = ps->ammo[cent->currentState.weapon]; + if ( value > -1 ) { + if ( cg.predictedPlayerState.weaponstate == WEAPON_FIRING + && cg.predictedPlayerState.weaponTime > 100 ) { + // draw as dark grey when reloading + color = 2; // dark grey + } else { + if ( value >= 0 ) { + color = 0; // green + } else { + color = 1; // red + } + } + trap_R_SetColor( colors[color] ); + + CG_DrawField (0, 432, 3, value); + trap_R_SetColor( NULL ); + + // if we didn't draw a 3D icon, draw a 2D icon for ammo + if ( !cg_draw3dIcons.integer && cg_drawIcons.integer ) { + qhandle_t icon; + + icon = cg_weapons[ cg.predictedPlayerState.weapon ].ammoIcon; + if ( icon ) { + CG_DrawPic( CHAR_WIDTH*3 + TEXT_ICON_SPACE, 432, ICON_SIZE, ICON_SIZE, icon ); + } + } + } + } + + // + // health + // + value = ps->stats[STAT_HEALTH]; + if ( value > 100 ) { + trap_R_SetColor( colors[3] ); // white + } else if (value > 25) { + trap_R_SetColor( colors[0] ); // green + } else if (value > 0) { + color = (cg.time >> 8) & 1; // flash + trap_R_SetColor( colors[color] ); + } else { + trap_R_SetColor( colors[1] ); // red + } + + // stretch the health up when taking damage + CG_DrawField ( 185, 432, 3, value); + CG_ColorForHealth( hcolor ); + trap_R_SetColor( hcolor ); + + + // + // armor + // + value = ps->stats[STAT_ARMOR]; + if (value > 0 ) { + trap_R_SetColor( colors[0] ); + CG_DrawField (370, 432, 3, value); + trap_R_SetColor( NULL ); + // if we didn't draw a 3D icon, draw a 2D icon for armor + if ( !cg_draw3dIcons.integer && cg_drawIcons.integer ) { + CG_DrawPic( 370 + CHAR_WIDTH*3 + TEXT_ICON_SPACE, 432, ICON_SIZE, ICON_SIZE, cgs.media.armorIcon ); + } + + } +#ifdef MISSIONPACK + // + // cubes + // + if( cgs.gametype == GT_HARVESTER ) { + value = ps->generic1; + if( value > 99 ) { + value = 99; + } + trap_R_SetColor( colors[0] ); + CG_DrawField (640 - (CHAR_WIDTH*2 + TEXT_ICON_SPACE + ICON_SIZE), 432, 2, value); + trap_R_SetColor( NULL ); + // if we didn't draw a 3D icon, draw a 2D icon for armor + if ( !cg_draw3dIcons.integer && cg_drawIcons.integer ) { + if( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { + handle = cgs.media.redCubeIcon; + } else { + handle = cgs.media.blueCubeIcon; + } + CG_DrawPic( 640 - (TEXT_ICON_SPACE + ICON_SIZE), 432, ICON_SIZE, ICON_SIZE, handle ); + } + } +#endif +} +#endif + +/* +=========================================================================================== + + UPPER RIGHT CORNER + +=========================================================================================== +*/ + +/* +================ +CG_DrawAttacker + +================ +*/ +static float CG_DrawAttacker( float y ) { + int t; + float size; + vec3_t angles; + const char *info; + const char *name; + int clientNum; + + if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) { + return y; + } + + if ( !cg.attackerTime ) { + return y; + } + + clientNum = cg.predictedPlayerState.persistant[PERS_ATTACKER]; + if ( clientNum < 0 || clientNum >= MAX_CLIENTS || clientNum == cg.snap->ps.clientNum ) { + return y; + } + + t = cg.time - cg.attackerTime; + if ( t > ATTACKER_HEAD_TIME ) { + cg.attackerTime = 0; + return y; + } + + size = ICON_SIZE * 1.25; + + angles[PITCH] = 0; + angles[YAW] = 180; + angles[ROLL] = 0; + CG_DrawHead( 640 - size, y, size, size, clientNum, angles ); + + info = CG_ConfigString( CS_PLAYERS + clientNum ); + name = Info_ValueForKey( info, "n" ); + y += size; + CG_DrawBigString( 640 - ( Q_PrintStrlen( name ) * BIGCHAR_WIDTH), y, name, 0.5 ); + + return y + BIGCHAR_HEIGHT + 2; +} + +/* +================== +CG_DrawSnapshot +================== +*/ +static float CG_DrawSnapshot( float y ) { + char *s; + int w; + + s = va( "time:%i snap:%i cmd:%i", cg.snap->serverTime, + cg.latestSnapshotNum, cgs.serverCommandSequence ); + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + + CG_DrawBigString( 635 - w, y + 2, s, 1.0F); + + return y + BIGCHAR_HEIGHT + 4; +} + +/* +================== +CG_DrawFPS +================== +*/ +#define FPS_FRAMES 4 +static float CG_DrawFPS( float y ) { + char *s; + int w; + static int previousTimes[FPS_FRAMES]; + static int index; + int i, total; + int fps; + static int previous; + int t, frameTime; + + // don't use serverTime, because that will be drifting to + // correct for internet lag changes, timescales, timedemos, etc + t = trap_Milliseconds(); + frameTime = t - previous; + previous = t; + + previousTimes[index % FPS_FRAMES] = frameTime; + index++; + if ( index > FPS_FRAMES ) { + // average multiple frames together to smooth changes out a bit + total = 0; + for ( i = 0 ; i < FPS_FRAMES ; i++ ) { + total += previousTimes[i]; + } + if ( !total ) { + total = 1; + } + fps = 1000 * FPS_FRAMES / total; + + s = va( "%ifps", fps ); + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + + CG_DrawBigString( 635 - w, y + 2, s, 1.0F); + } + + return y + BIGCHAR_HEIGHT + 4; +} + +/* +================= +CG_DrawTimer +================= +*/ +static float CG_DrawTimer( float y ) { + char *s; + int w; + int mins, seconds, tens; + int msec; + + msec = cg.time - cgs.levelStartTime; + + seconds = msec / 1000; + mins = seconds / 60; + seconds -= mins * 60; + tens = seconds / 10; + seconds -= tens * 10; + + s = va( "%i:%i%i", mins, tens, seconds ); + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + + CG_DrawBigString( 635 - w, y + 2, s, 1.0F); + + return y + BIGCHAR_HEIGHT + 4; +} + + +/* +================= +CG_DrawTeamOverlay +================= +*/ + +static float CG_DrawTeamOverlay( float y, qboolean right, qboolean upper ) { + int x, w, h, xx; + int i, j, len; + const char *p; + vec4_t hcolor; + int pwidth, lwidth; + int plyrs; + char st[16]; + clientInfo_t *ci; + gitem_t *item; + int ret_y, count; + + if ( !cg_drawTeamOverlay.integer ) { + return y; + } + + if ( cg.snap->ps.persistant[PERS_TEAM] != TEAM_RED && cg.snap->ps.persistant[PERS_TEAM] != TEAM_BLUE ) { + return y; // Not on any team + } + + plyrs = 0; + + // max player name width + pwidth = 0; + count = (numSortedTeamPlayers > 8) ? 8 : numSortedTeamPlayers; + for (i = 0; i < count; i++) { + ci = cgs.clientinfo + sortedTeamPlayers[i]; + if ( ci->infoValid && ci->team == cg.snap->ps.persistant[PERS_TEAM]) { + plyrs++; + len = CG_DrawStrlen(ci->name); + if (len > pwidth) + pwidth = len; + } + } + + if (!plyrs) + return y; + + if (pwidth > TEAM_OVERLAY_MAXNAME_WIDTH) + pwidth = TEAM_OVERLAY_MAXNAME_WIDTH; + + // max location name width + lwidth = 0; + for (i = 1; i < MAX_LOCATIONS; i++) { + p = CG_ConfigString(CS_LOCATIONS + i); + if (p && *p) { + len = CG_DrawStrlen(p); + if (len > lwidth) + lwidth = len; + } + } + + if (lwidth > TEAM_OVERLAY_MAXLOCATION_WIDTH) + lwidth = TEAM_OVERLAY_MAXLOCATION_WIDTH; + + w = (pwidth + lwidth + 4 + 7) * TINYCHAR_WIDTH; + + if ( right ) + x = 640 - w; + else + x = 0; + + h = plyrs * TINYCHAR_HEIGHT; + + if ( upper ) { + ret_y = y + h; + } else { + y -= h; + ret_y = y; + } + + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED ) { + hcolor[0] = 1.0f; + hcolor[1] = 0.0f; + hcolor[2] = 0.0f; + hcolor[3] = 0.33f; + } else { // if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) + hcolor[0] = 0.0f; + hcolor[1] = 0.0f; + hcolor[2] = 1.0f; + hcolor[3] = 0.33f; + } + trap_R_SetColor( hcolor ); + CG_DrawPic( x, y, w, h, cgs.media.teamStatusBar ); + trap_R_SetColor( NULL ); + + for (i = 0; i < count; i++) { + ci = cgs.clientinfo + sortedTeamPlayers[i]; + if ( ci->infoValid && ci->team == cg.snap->ps.persistant[PERS_TEAM]) { + + hcolor[0] = hcolor[1] = hcolor[2] = hcolor[3] = 1.0; + + xx = x + TINYCHAR_WIDTH; + + CG_DrawStringExt( xx, y, + ci->name, hcolor, qfalse, qfalse, + TINYCHAR_WIDTH, TINYCHAR_HEIGHT, TEAM_OVERLAY_MAXNAME_WIDTH); + + if (lwidth) { + p = CG_ConfigString(CS_LOCATIONS + ci->location); + if (!p || !*p) + p = "unknown"; + len = CG_DrawStrlen(p); + if (len > lwidth) + len = lwidth; + +// xx = x + TINYCHAR_WIDTH * 2 + TINYCHAR_WIDTH * pwidth + +// ((lwidth/2 - len/2) * TINYCHAR_WIDTH); + xx = x + TINYCHAR_WIDTH * 2 + TINYCHAR_WIDTH * pwidth; + CG_DrawStringExt( xx, y, + p, hcolor, qfalse, qfalse, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, + TEAM_OVERLAY_MAXLOCATION_WIDTH); + } + + CG_GetColorForHealth( ci->health, ci->armor, hcolor ); + + Com_sprintf (st, sizeof(st), "%3i %3i", ci->health, ci->armor); + + xx = x + TINYCHAR_WIDTH * 3 + + TINYCHAR_WIDTH * pwidth + TINYCHAR_WIDTH * lwidth; + + CG_DrawStringExt( xx, y, + st, hcolor, qfalse, qfalse, + TINYCHAR_WIDTH, TINYCHAR_HEIGHT, 0 ); + + // draw weapon icon + xx += TINYCHAR_WIDTH * 3; + + if ( cg_weapons[ci->curWeapon].weaponIcon ) { + CG_DrawPic( xx, y, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, + cg_weapons[ci->curWeapon].weaponIcon ); + } else { + CG_DrawPic( xx, y, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, + cgs.media.deferShader ); + } + + // Draw powerup icons + if (right) { + xx = x; + } else { + xx = x + w - TINYCHAR_WIDTH; + } + for (j = 0; j <= PW_NUM_POWERUPS; j++) { + if (ci->powerups & (1 << j)) { + + item = BG_FindItemForPowerup( j ); + + if (item) { + CG_DrawPic( xx, y, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, + trap_R_RegisterShader( item->icon ) ); + if (right) { + xx -= TINYCHAR_WIDTH; + } else { + xx += TINYCHAR_WIDTH; + } + } + } + } + + y += TINYCHAR_HEIGHT; + } + } + + return ret_y; +//#endif +} + + +/* +===================== +CG_DrawUpperRight + +===================== +*/ +static void CG_DrawUpperRight( void ) { + float y; + + y = 0; + + if ( cgs.gametype >= GT_TEAM && cg_drawTeamOverlay.integer == 1 ) { + y = CG_DrawTeamOverlay( y, qtrue, qtrue ); + } + if ( cg_drawSnapshot.integer ) { + y = CG_DrawSnapshot( y ); + } + if ( cg_drawFPS.integer ) { + y = CG_DrawFPS( y ); + } + if ( cg_drawTimer.integer ) { + y = CG_DrawTimer( y ); + } + if ( cg_drawAttacker.integer ) { + y = CG_DrawAttacker( y ); + } + +} + +/* +=========================================================================================== + + LOWER RIGHT CORNER + +=========================================================================================== +*/ + +/* +================= +CG_DrawScores + +Draw the small two score display +================= +*/ +#ifndef MISSIONPACK +static float CG_DrawScores( float y ) { + const char *s; + int s1, s2, score; + int x, w; + int v; + vec4_t color; + float y1; + gitem_t *item; + + s1 = cgs.scores1; + s2 = cgs.scores2; + + y -= BIGCHAR_HEIGHT + 8; + + y1 = y; + + // draw from the right side to left + if ( cgs.gametype >= GT_TEAM ) { + x = 640; + color[0] = 0.0f; + color[1] = 0.0f; + color[2] = 1.0f; + color[3] = 0.33f; + s = va( "%2i", s2 ); + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; + x -= w; + CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { + CG_DrawPic( x, y-4, w, BIGCHAR_HEIGHT+8, cgs.media.selectShader ); + } + CG_DrawBigString( x + 4, y, s, 1.0F); + + if ( cgs.gametype == GT_CTF ) { + // Display flag status + item = BG_FindItemForPowerup( PW_BLUEFLAG ); + + if (item) { + y1 = y - BIGCHAR_HEIGHT - 8; + if( cgs.blueflag >= 0 && cgs.blueflag <= 2 ) { + CG_DrawPic( x, y1-4, w, BIGCHAR_HEIGHT+8, cgs.media.blueFlagShader[cgs.blueflag] ); + } + } + } + color[0] = 1.0f; + color[1] = 0.0f; + color[2] = 0.0f; + color[3] = 0.33f; + s = va( "%2i", s1 ); + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; + x -= w; + CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED ) { + CG_DrawPic( x, y-4, w, BIGCHAR_HEIGHT+8, cgs.media.selectShader ); + } + CG_DrawBigString( x + 4, y, s, 1.0F); + + if ( cgs.gametype == GT_CTF ) { + // Display flag status + item = BG_FindItemForPowerup( PW_REDFLAG ); + + if (item) { + y1 = y - BIGCHAR_HEIGHT - 8; + if( cgs.redflag >= 0 && cgs.redflag <= 2 ) { + CG_DrawPic( x, y1-4, w, BIGCHAR_HEIGHT+8, cgs.media.redFlagShader[cgs.redflag] ); + } + } + } + +#ifdef MISSIONPACK + if ( cgs.gametype == GT_1FCTF ) { + // Display flag status + item = BG_FindItemForPowerup( PW_NEUTRALFLAG ); + + if (item) { + y1 = y - BIGCHAR_HEIGHT - 8; + if( cgs.flagStatus >= 0 && cgs.flagStatus <= 3 ) { + CG_DrawPic( x, y1-4, w, BIGCHAR_HEIGHT+8, cgs.media.flagShader[cgs.flagStatus] ); + } + } + } +#endif + if ( cgs.gametype >= GT_CTF ) { + v = cgs.capturelimit; + } else { + v = cgs.fraglimit; + } + if ( v ) { + s = va( "%2i", v ); + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; + x -= w; + CG_DrawBigString( x + 4, y, s, 1.0F); + } + + } else { + qboolean spectator; + + x = 640; + score = cg.snap->ps.persistant[PERS_SCORE]; + spectator = ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR ); + + // always show your score in the second box if not in first place + if ( s1 != score ) { + s2 = score; + } + if ( s2 != SCORE_NOT_PRESENT ) { + s = va( "%2i", s2 ); + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; + x -= w; + if ( !spectator && score == s2 && score != s1 ) { + color[0] = 1.0f; + color[1] = 0.0f; + color[2] = 0.0f; + color[3] = 0.33f; + CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); + CG_DrawPic( x, y-4, w, BIGCHAR_HEIGHT+8, cgs.media.selectShader ); + } else { + color[0] = 0.5f; + color[1] = 0.5f; + color[2] = 0.5f; + color[3] = 0.33f; + CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); + } + CG_DrawBigString( x + 4, y, s, 1.0F); + } + + // first place + if ( s1 != SCORE_NOT_PRESENT ) { + s = va( "%2i", s1 ); + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; + x -= w; + if ( !spectator && score == s1 ) { + color[0] = 0.0f; + color[1] = 0.0f; + color[2] = 1.0f; + color[3] = 0.33f; + CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); + CG_DrawPic( x, y-4, w, BIGCHAR_HEIGHT+8, cgs.media.selectShader ); + } else { + color[0] = 0.5f; + color[1] = 0.5f; + color[2] = 0.5f; + color[3] = 0.33f; + CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); + } + CG_DrawBigString( x + 4, y, s, 1.0F); + } + + if ( cgs.fraglimit ) { + s = va( "%2i", cgs.fraglimit ); + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; + x -= w; + CG_DrawBigString( x + 4, y, s, 1.0F); + } + + } + + return y1 - 8; +} +#endif // MISSIONPACK + +/* +================ +CG_DrawPowerups +================ +*/ +#ifndef MISSIONPACK +static float CG_DrawPowerups( float y ) { + int sorted[MAX_POWERUPS]; + int sortedTime[MAX_POWERUPS]; + int i, j, k; + int active; + playerState_t *ps; + int t; + gitem_t *item; + int x; + int color; + float size; + float f; + static float colors[2][4] = { + { 0.2f, 1.0f, 0.2f, 1.0f } , + { 1.0f, 0.2f, 0.2f, 1.0f } + }; + + ps = &cg.snap->ps; + + if ( ps->stats[STAT_HEALTH] <= 0 ) { + return y; + } + + // sort the list by time remaining + active = 0; + for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { + if ( !ps->powerups[ i ] ) { + continue; + } + t = ps->powerups[ i ] - cg.time; + // ZOID--don't draw if the power up has unlimited time (999 seconds) + // This is true of the CTF flags + if ( t < 0 || t > 999000) { + continue; + } + + // insert into the list + for ( j = 0 ; j < active ; j++ ) { + if ( sortedTime[j] >= t ) { + for ( k = active - 1 ; k >= j ; k-- ) { + sorted[k+1] = sorted[k]; + sortedTime[k+1] = sortedTime[k]; + } + break; + } + } + sorted[j] = i; + sortedTime[j] = t; + active++; + } + + // draw the icons and timers + x = 640 - ICON_SIZE - CHAR_WIDTH * 2; + for ( i = 0 ; i < active ; i++ ) { + item = BG_FindItemForPowerup( sorted[i] ); + + if (item) { + + color = 1; + + y -= ICON_SIZE; + + trap_R_SetColor( colors[color] ); + CG_DrawField( x, y, 2, sortedTime[ i ] / 1000 ); + + t = ps->powerups[ sorted[i] ]; + if ( t - cg.time >= POWERUP_BLINKS * POWERUP_BLINK_TIME ) { + trap_R_SetColor( NULL ); + } else { + vec4_t modulate; + + f = (float)( t - cg.time ) / POWERUP_BLINK_TIME; + f -= (int)f; + modulate[0] = modulate[1] = modulate[2] = modulate[3] = f; + trap_R_SetColor( modulate ); + } + + if ( cg.powerupActive == sorted[i] && + cg.time - cg.powerupTime < PULSE_TIME ) { + f = 1.0 - ( ( (float)cg.time - cg.powerupTime ) / PULSE_TIME ); + size = ICON_SIZE * ( 1.0 + ( PULSE_SCALE - 1.0 ) * f ); + } else { + size = ICON_SIZE; + } + + CG_DrawPic( 640 - size, y + ICON_SIZE / 2 - size / 2, + size, size, trap_R_RegisterShader( item->icon ) ); + } + } + trap_R_SetColor( NULL ); + + return y; +} +#endif // MISSIONPACK + +/* +===================== +CG_DrawLowerRight + +===================== +*/ +#ifndef MISSIONPACK +static void CG_DrawLowerRight( void ) { + float y; + + y = 480 - ICON_SIZE; + + if ( cgs.gametype >= GT_TEAM && cg_drawTeamOverlay.integer == 2 ) { + y = CG_DrawTeamOverlay( y, qtrue, qfalse ); + } + + y = CG_DrawScores( y ); + y = CG_DrawPowerups( y ); +} +#endif // MISSIONPACK + +/* +=================== +CG_DrawPickupItem +=================== +*/ +#ifndef MISSIONPACK +static int CG_DrawPickupItem( int y ) { + int value; + float *fadeColor; + + if ( cg.snap->ps.stats[STAT_HEALTH] <= 0 ) { + return y; + } + + y -= ICON_SIZE; + + value = cg.itemPickup; + if ( value ) { + fadeColor = CG_FadeColor( cg.itemPickupTime, 3000 ); + if ( fadeColor ) { + CG_RegisterItemVisuals( value ); + trap_R_SetColor( fadeColor ); + CG_DrawPic( 8, y, ICON_SIZE, ICON_SIZE, cg_items[ value ].icon ); + CG_DrawBigString( ICON_SIZE + 16, y + (ICON_SIZE/2 - BIGCHAR_HEIGHT/2), bg_itemlist[ value ].pickup_name, fadeColor[0] ); + trap_R_SetColor( NULL ); + } + } + + return y; +} +#endif // MISSIONPACK + +/* +===================== +CG_DrawLowerLeft + +===================== +*/ +#ifndef MISSIONPACK +static void CG_DrawLowerLeft( void ) { + float y; + + y = 480 - ICON_SIZE; + + if ( cgs.gametype >= GT_TEAM && cg_drawTeamOverlay.integer == 3 ) { + y = CG_DrawTeamOverlay( y, qfalse, qfalse ); + } + + + y = CG_DrawPickupItem( y ); +} +#endif // MISSIONPACK + + +//=========================================================================================== + +/* +================= +CG_DrawTeamInfo +================= +*/ +#ifndef MISSIONPACK +static void CG_DrawTeamInfo( void ) { + int w, h; + int i, len; + vec4_t hcolor; + int chatHeight; + +#define CHATLOC_Y 420 // bottom end +#define CHATLOC_X 0 + + if (cg_teamChatHeight.integer < TEAMCHAT_HEIGHT) + chatHeight = cg_teamChatHeight.integer; + else + chatHeight = TEAMCHAT_HEIGHT; + if (chatHeight <= 0) + return; // disabled + + if (cgs.teamLastChatPos != cgs.teamChatPos) { + if (cg.time - cgs.teamChatMsgTimes[cgs.teamLastChatPos % chatHeight] > cg_teamChatTime.integer) { + cgs.teamLastChatPos++; + } + + h = (cgs.teamChatPos - cgs.teamLastChatPos) * TINYCHAR_HEIGHT; + + w = 0; + + for (i = cgs.teamLastChatPos; i < cgs.teamChatPos; i++) { + len = CG_DrawStrlen(cgs.teamChatMsgs[i % chatHeight]); + if (len > w) + w = len; + } + w *= TINYCHAR_WIDTH; + w += TINYCHAR_WIDTH * 2; + + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED ) { + hcolor[0] = 1.0f; + hcolor[1] = 0.0f; + hcolor[2] = 0.0f; + hcolor[3] = 0.33f; + } else if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { + hcolor[0] = 0.0f; + hcolor[1] = 0.0f; + hcolor[2] = 1.0f; + hcolor[3] = 0.33f; + } else { + hcolor[0] = 0.0f; + hcolor[1] = 1.0f; + hcolor[2] = 0.0f; + hcolor[3] = 0.33f; + } + + trap_R_SetColor( hcolor ); + CG_DrawPic( CHATLOC_X, CHATLOC_Y - h, 640, h, cgs.media.teamStatusBar ); + trap_R_SetColor( NULL ); + + hcolor[0] = hcolor[1] = hcolor[2] = 1.0f; + hcolor[3] = 1.0f; + + for (i = cgs.teamChatPos - 1; i >= cgs.teamLastChatPos; i--) { + CG_DrawStringExt( CHATLOC_X + TINYCHAR_WIDTH, + CHATLOC_Y - (cgs.teamChatPos - i)*TINYCHAR_HEIGHT, + cgs.teamChatMsgs[i % chatHeight], hcolor, qfalse, qfalse, + TINYCHAR_WIDTH, TINYCHAR_HEIGHT, 0 ); + } + } +} +#endif // MISSIONPACK + +/* +=================== +CG_DrawHoldableItem +=================== +*/ +#ifndef MISSIONPACK +static void CG_DrawHoldableItem( void ) { + int value; + + value = cg.snap->ps.stats[STAT_HOLDABLE_ITEM]; + if ( value ) { + CG_RegisterItemVisuals( value ); + CG_DrawPic( 640-ICON_SIZE, (SCREEN_HEIGHT-ICON_SIZE)/2, ICON_SIZE, ICON_SIZE, cg_items[ value ].icon ); + } + +} +#endif // MISSIONPACK + +#ifdef MISSIONPACK +/* +=================== +CG_DrawPersistantPowerup +=================== +*/ +#if 0 // sos001208 - DEAD +static void CG_DrawPersistantPowerup( void ) { + int value; + + value = cg.snap->ps.stats[STAT_PERSISTANT_POWERUP]; + if ( value ) { + CG_RegisterItemVisuals( value ); + CG_DrawPic( 640-ICON_SIZE, (SCREEN_HEIGHT-ICON_SIZE)/2 - ICON_SIZE, ICON_SIZE, ICON_SIZE, cg_items[ value ].icon ); + } +} +#endif +#endif // MISSIONPACK + + +/* +=================== +CG_DrawReward +=================== +*/ +static void CG_DrawReward( void ) { + float *color; + int i, count; + float x, y; + char buf[32]; + + if ( !cg_drawRewards.integer ) { + return; + } + + color = CG_FadeColor( cg.rewardTime, REWARD_TIME ); + if ( !color ) { + if (cg.rewardStack > 0) { + for(i = 0; i < cg.rewardStack; i++) { + cg.rewardSound[i] = cg.rewardSound[i+1]; + cg.rewardShader[i] = cg.rewardShader[i+1]; + cg.rewardCount[i] = cg.rewardCount[i+1]; + } + cg.rewardTime = cg.time; + cg.rewardStack--; + color = CG_FadeColor( cg.rewardTime, REWARD_TIME ); + trap_S_StartLocalSound(cg.rewardSound[0], CHAN_ANNOUNCER); + } else { + return; + } + } + + trap_R_SetColor( color ); + + /* + count = cg.rewardCount[0]/10; // number of big rewards to draw + + if (count) { + y = 4; + x = 320 - count * ICON_SIZE; + for ( i = 0 ; i < count ; i++ ) { + CG_DrawPic( x, y, (ICON_SIZE*2)-4, (ICON_SIZE*2)-4, cg.rewardShader[0] ); + x += (ICON_SIZE*2); + } + } + + count = cg.rewardCount[0] - count*10; // number of small rewards to draw + */ + + if ( cg.rewardCount[0] >= 10 ) { + y = 56; + x = 320 - ICON_SIZE/2; + CG_DrawPic( x, y, ICON_SIZE-4, ICON_SIZE-4, cg.rewardShader[0] ); + Com_sprintf(buf, sizeof(buf), "%d", cg.rewardCount[0]); + x = ( SCREEN_WIDTH - SMALLCHAR_WIDTH * CG_DrawStrlen( buf ) ) / 2; + CG_DrawStringExt( x, y+ICON_SIZE, buf, color, qfalse, qtrue, + SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, 0 ); + } + else { + + count = cg.rewardCount[0]; + + y = 56; + x = 320 - count * ICON_SIZE/2; + for ( i = 0 ; i < count ; i++ ) { + CG_DrawPic( x, y, ICON_SIZE-4, ICON_SIZE-4, cg.rewardShader[0] ); + x += ICON_SIZE; + } + } + trap_R_SetColor( NULL ); +} + + +/* +=============================================================================== + +LAGOMETER + +=============================================================================== +*/ + +#define LAG_SAMPLES 128 + + +typedef struct { + int frameSamples[LAG_SAMPLES]; + int frameCount; + int snapshotFlags[LAG_SAMPLES]; + int snapshotSamples[LAG_SAMPLES]; + int snapshotCount; +} lagometer_t; + +lagometer_t lagometer; + +/* +============== +CG_AddLagometerFrameInfo + +Adds the current interpolate / extrapolate bar for this frame +============== +*/ +void CG_AddLagometerFrameInfo( void ) { + int offset; + + offset = cg.time - cg.latestSnapshotTime; + lagometer.frameSamples[ lagometer.frameCount & ( LAG_SAMPLES - 1) ] = offset; + lagometer.frameCount++; +} + +/* +============== +CG_AddLagometerSnapshotInfo + +Each time a snapshot is received, log its ping time and +the number of snapshots that were dropped before it. + +Pass NULL for a dropped packet. +============== +*/ +void CG_AddLagometerSnapshotInfo( snapshot_t *snap ) { + // dropped packet + if ( !snap ) { + lagometer.snapshotSamples[ lagometer.snapshotCount & ( LAG_SAMPLES - 1) ] = -1; + lagometer.snapshotCount++; + return; + } + + // add this snapshot's info + lagometer.snapshotSamples[ lagometer.snapshotCount & ( LAG_SAMPLES - 1) ] = snap->ping; + lagometer.snapshotFlags[ lagometer.snapshotCount & ( LAG_SAMPLES - 1) ] = snap->snapFlags; + lagometer.snapshotCount++; +} + +/* +============== +CG_DrawDisconnect + +Should we draw something differnet for long lag vs no packets? +============== +*/ +static void CG_DrawDisconnect( void ) { + float x, y; + int cmdNum; + usercmd_t cmd; + const char *s; + int w; // bk010215 - FIXME char message[1024]; + + // draw the phone jack if we are completely past our buffers + cmdNum = trap_GetCurrentCmdNumber() - CMD_BACKUP + 1; + trap_GetUserCmd( cmdNum, &cmd ); + if ( cmd.serverTime <= cg.snap->ps.commandTime + || cmd.serverTime > cg.time ) { // special check for map_restart // bk 0102165 - FIXME + return; + } + + // also add text in center of screen + s = "Connection Interrupted"; // bk 010215 - FIXME + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + CG_DrawBigString( 320 - w/2, 100, s, 1.0F); + + // blink the icon + if ( ( cg.time >> 9 ) & 1 ) { + return; + } + + x = 640 - 48; + y = 480 - 48; + + CG_DrawPic( x, y, 48, 48, trap_R_RegisterShader("gfx/2d/net.tga" ) ); +} + + +#define MAX_LAGOMETER_PING 900 +#define MAX_LAGOMETER_RANGE 300 + +/* +============== +CG_DrawLagometer +============== +*/ +static void CG_DrawLagometer( void ) { + int a, x, y, i; + float v; + float ax, ay, aw, ah, mid, range; + int color; + float vscale; + + if ( !cg_lagometer.integer || cgs.localServer ) { + CG_DrawDisconnect(); + return; + } + + // + // draw the graph + // +#ifdef MISSIONPACK + x = 640 - 48; + y = 480 - 144; +#else + x = 640 - 48; + y = 480 - 48; +#endif + + trap_R_SetColor( NULL ); + CG_DrawPic( x, y, 48, 48, cgs.media.lagometerShader ); + + ax = x; + ay = y; + aw = 48; + ah = 48; + CG_AdjustFrom640( &ax, &ay, &aw, &ah ); + + color = -1; + range = ah / 3; + mid = ay + range; + + vscale = range / MAX_LAGOMETER_RANGE; + + // draw the frame interpoalte / extrapolate graph + for ( a = 0 ; a < aw ; a++ ) { + i = ( lagometer.frameCount - 1 - a ) & (LAG_SAMPLES - 1); + v = lagometer.frameSamples[i]; + v *= vscale; + if ( v > 0 ) { + if ( color != 1 ) { + color = 1; + trap_R_SetColor( g_color_table[ColorIndex(COLOR_YELLOW)] ); + } + if ( v > range ) { + v = range; + } + trap_R_DrawStretchPic ( ax + aw - a, mid - v, 1, v, 0, 0, 0, 0, cgs.media.whiteShader ); + } else if ( v < 0 ) { + if ( color != 2 ) { + color = 2; + trap_R_SetColor( g_color_table[ColorIndex(COLOR_BLUE)] ); + } + v = -v; + if ( v > range ) { + v = range; + } + trap_R_DrawStretchPic( ax + aw - a, mid, 1, v, 0, 0, 0, 0, cgs.media.whiteShader ); + } + } + + // draw the snapshot latency / drop graph + range = ah / 2; + vscale = range / MAX_LAGOMETER_PING; + + for ( a = 0 ; a < aw ; a++ ) { + i = ( lagometer.snapshotCount - 1 - a ) & (LAG_SAMPLES - 1); + v = lagometer.snapshotSamples[i]; + if ( v > 0 ) { + if ( lagometer.snapshotFlags[i] & SNAPFLAG_RATE_DELAYED ) { + if ( color != 5 ) { + color = 5; // YELLOW for rate delay + trap_R_SetColor( g_color_table[ColorIndex(COLOR_YELLOW)] ); + } + } else { + if ( color != 3 ) { + color = 3; + trap_R_SetColor( g_color_table[ColorIndex(COLOR_GREEN)] ); + } + } + v = v * vscale; + if ( v > range ) { + v = range; + } + trap_R_DrawStretchPic( ax + aw - a, ay + ah - v, 1, v, 0, 0, 0, 0, cgs.media.whiteShader ); + } else if ( v < 0 ) { + if ( color != 4 ) { + color = 4; // RED for dropped snapshots + trap_R_SetColor( g_color_table[ColorIndex(COLOR_RED)] ); + } + trap_R_DrawStretchPic( ax + aw - a, ay + ah - range, 1, range, 0, 0, 0, 0, cgs.media.whiteShader ); + } + } + + trap_R_SetColor( NULL ); + + if ( cg_nopredict.integer || cg_synchronousClients.integer ) { + CG_DrawBigString( ax, ay, "snc", 1.0 ); + } + + CG_DrawDisconnect(); +} + + + +/* +=============================================================================== + +CENTER PRINTING + +=============================================================================== +*/ + + +/* +============== +CG_CenterPrint + +Called for important messages that should stay in the center of the screen +for a few moments +============== +*/ +void CG_CenterPrint( const char *str, int y, int charWidth ) { + char *s; + + Q_strncpyz( cg.centerPrint, str, sizeof(cg.centerPrint) ); + + cg.centerPrintTime = cg.time; + cg.centerPrintY = y; + cg.centerPrintCharWidth = charWidth; + + // count the number of lines for centering + cg.centerPrintLines = 1; + s = cg.centerPrint; + while( *s ) { + if (*s == '\n') + cg.centerPrintLines++; + s++; + } +} + + +/* +=================== +CG_DrawCenterString +=================== +*/ +static void CG_DrawCenterString( void ) { + char *start; + int l; + int x, y, w; +#ifdef MISSIONPACK // bk010221 - unused else + int h; +#endif + float *color; + + if ( !cg.centerPrintTime ) { + return; + } + + color = CG_FadeColor( cg.centerPrintTime, 1000 * cg_centertime.value ); + if ( !color ) { + return; + } + + trap_R_SetColor( color ); + + start = cg.centerPrint; + + y = cg.centerPrintY - cg.centerPrintLines * BIGCHAR_HEIGHT / 2; + + while ( 1 ) { + char linebuffer[1024]; + + for ( l = 0; l < 50; l++ ) { + if ( !start[l] || start[l] == '\n' ) { + break; + } + linebuffer[l] = start[l]; + } + linebuffer[l] = 0; + +#ifdef MISSIONPACK + w = CG_Text_Width(linebuffer, 0.5, 0); + h = CG_Text_Height(linebuffer, 0.5, 0); + x = (SCREEN_WIDTH - w) / 2; + CG_Text_Paint(x, y + h, 0.5, color, linebuffer, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE); + y += h + 6; +#else + w = cg.centerPrintCharWidth * CG_DrawStrlen( linebuffer ); + + x = ( SCREEN_WIDTH - w ) / 2; + + CG_DrawStringExt( x, y, linebuffer, color, qfalse, qtrue, + cg.centerPrintCharWidth, (int)(cg.centerPrintCharWidth * 1.5), 0 ); + + y += cg.centerPrintCharWidth * 1.5; +#endif + while ( *start && ( *start != '\n' ) ) { + start++; + } + if ( !*start ) { + break; + } + start++; + } + + trap_R_SetColor( NULL ); +} + + + +/* +================================================================================ + +CROSSHAIR + +================================================================================ +*/ + + +/* +================= +CG_DrawCrosshair +================= +*/ +static void CG_DrawCrosshair(void) { + float w, h; + qhandle_t hShader; + float f; + float x, y; + int ca; + + if ( !cg_drawCrosshair.integer ) { + return; + } + + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR) { + return; + } + + if ( cg.renderingThirdPerson ) { + return; + } + + // set color based on health + if ( cg_crosshairHealth.integer ) { + vec4_t hcolor; + + CG_ColorForHealth( hcolor ); + trap_R_SetColor( hcolor ); + } else { + trap_R_SetColor( NULL ); + } + + w = h = cg_crosshairSize.value; + + // pulse the size of the crosshair when picking up items + f = cg.time - cg.itemPickupBlendTime; + if ( f > 0 && f < ITEM_BLOB_TIME ) { + f /= ITEM_BLOB_TIME; + w *= ( 1 + f ); + h *= ( 1 + f ); + } + + x = cg_crosshairX.integer; + y = cg_crosshairY.integer; + CG_AdjustFrom640( &x, &y, &w, &h ); + + ca = cg_drawCrosshair.integer; + if (ca < 0) { + ca = 0; + } + hShader = cgs.media.crosshairShader[ ca % NUM_CROSSHAIRS ]; + + trap_R_DrawStretchPic( x + cg.refdef.x + 0.5 * (cg.refdef.width - w), + y + cg.refdef.y + 0.5 * (cg.refdef.height - h), + w, h, 0, 0, 1, 1, hShader ); +} + + + +/* +================= +CG_ScanForCrosshairEntity +================= +*/ +static void CG_ScanForCrosshairEntity( void ) { + trace_t trace; + vec3_t start, end; + int content; + + VectorCopy( cg.refdef.vieworg, start ); + VectorMA( start, 131072, cg.refdef.viewaxis[0], end ); + + CG_Trace( &trace, start, vec3_origin, vec3_origin, end, + cg.snap->ps.clientNum, CONTENTS_SOLID|CONTENTS_BODY ); + if ( trace.entityNum >= MAX_CLIENTS ) { + return; + } + + // if the player is in fog, don't show it + content = trap_CM_PointContents( trace.endpos, 0 ); + if ( content & CONTENTS_FOG ) { + return; + } + + // if the player is invisible, don't show it + if ( cg_entities[ trace.entityNum ].currentState.powerups & ( 1 << PW_INVIS ) ) { + return; + } + + // update the fade timer + cg.crosshairClientNum = trace.entityNum; + cg.crosshairClientTime = cg.time; +} + + +/* +===================== +CG_DrawCrosshairNames +===================== +*/ +static void CG_DrawCrosshairNames( void ) { + float *color; + char *name; + float w; + + if ( !cg_drawCrosshair.integer ) { + return; + } + if ( !cg_drawCrosshairNames.integer ) { + return; + } + if ( cg.renderingThirdPerson ) { + return; + } + + // scan the known entities to see if the crosshair is sighted on one + CG_ScanForCrosshairEntity(); + + // draw the name of the player being looked at + color = CG_FadeColor( cg.crosshairClientTime, 1000 ); + if ( !color ) { + trap_R_SetColor( NULL ); + return; + } + + name = cgs.clientinfo[ cg.crosshairClientNum ].name; +#ifdef MISSIONPACK + color[3] *= 0.5f; + w = CG_Text_Width(name, 0.3f, 0); + CG_Text_Paint( 320 - w / 2, 190, 0.3f, color, name, 0, 0, ITEM_TEXTSTYLE_SHADOWED); +#else + w = CG_DrawStrlen( name ) * BIGCHAR_WIDTH; + CG_DrawBigString( 320 - w / 2, 170, name, color[3] * 0.5f ); +#endif + trap_R_SetColor( NULL ); +} + + +//============================================================================== + +/* +================= +CG_DrawSpectator +================= +*/ +static void CG_DrawSpectator(void) { + CG_DrawBigString(320 - 9 * 8, 440, "SPECTATOR", 1.0F); + if ( cgs.gametype == GT_TOURNAMENT ) { + CG_DrawBigString(320 - 15 * 8, 460, "waiting to play", 1.0F); + } + else if ( cgs.gametype >= GT_TEAM ) { + CG_DrawBigString(320 - 39 * 8, 460, "press ESC and use the JOIN menu to play", 1.0F); + } +} + +/* +================= +CG_DrawVote +================= +*/ +static void CG_DrawVote(void) { + char *s; + int sec; + + if ( !cgs.voteTime ) { + return; + } + + // play a talk beep whenever it is modified + if ( cgs.voteModified ) { + cgs.voteModified = qfalse; + trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); + } + + sec = ( VOTE_TIME - ( cg.time - cgs.voteTime ) ) / 1000; + if ( sec < 0 ) { + sec = 0; + } +#ifdef MISSIONPACK + s = va("VOTE(%i):%s yes:%i no:%i", sec, cgs.voteString, cgs.voteYes, cgs.voteNo); + CG_DrawSmallString( 0, 58, s, 1.0F ); + s = "or press ESC then click Vote"; + CG_DrawSmallString( 0, 58 + SMALLCHAR_HEIGHT + 2, s, 1.0F ); +#else + s = va("VOTE(%i):%s yes:%i no:%i", sec, cgs.voteString, cgs.voteYes, cgs.voteNo ); + CG_DrawSmallString( 0, 58, s, 1.0F ); +#endif +} + +/* +================= +CG_DrawTeamVote +================= +*/ +static void CG_DrawTeamVote(void) { + char *s; + int sec, cs_offset; + + if ( cgs.clientinfo->team == TEAM_RED ) + cs_offset = 0; + else if ( cgs.clientinfo->team == TEAM_BLUE ) + cs_offset = 1; + else + return; + + if ( !cgs.teamVoteTime[cs_offset] ) { + return; + } + + // play a talk beep whenever it is modified + if ( cgs.teamVoteModified[cs_offset] ) { + cgs.teamVoteModified[cs_offset] = qfalse; + trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); + } + + sec = ( VOTE_TIME - ( cg.time - cgs.teamVoteTime[cs_offset] ) ) / 1000; + if ( sec < 0 ) { + sec = 0; + } + s = va("TEAMVOTE(%i):%s yes:%i no:%i", sec, cgs.teamVoteString[cs_offset], + cgs.teamVoteYes[cs_offset], cgs.teamVoteNo[cs_offset] ); + CG_DrawSmallString( 0, 90, s, 1.0F ); +} + + +static qboolean CG_DrawScoreboard() { +#ifdef MISSIONPACK + static qboolean firstTime = qtrue; + float fade, *fadeColor; + + if (menuScoreboard) { + menuScoreboard->window.flags &= ~WINDOW_FORCED; + } + if (cg_paused.integer) { + cg.deferredPlayerLoading = 0; + firstTime = qtrue; + return qfalse; + } + + // should never happen in Team Arena + if (cgs.gametype == GT_SINGLE_PLAYER && cg.predictedPlayerState.pm_type == PM_INTERMISSION ) { + cg.deferredPlayerLoading = 0; + firstTime = qtrue; + return qfalse; + } + + // don't draw scoreboard during death while warmup up + if ( cg.warmup && !cg.showScores ) { + return qfalse; + } + + if ( cg.showScores || cg.predictedPlayerState.pm_type == PM_DEAD || cg.predictedPlayerState.pm_type == PM_INTERMISSION ) { + fade = 1.0; + fadeColor = colorWhite; + } else { + fadeColor = CG_FadeColor( cg.scoreFadeTime, FADE_TIME ); + if ( !fadeColor ) { + // next time scoreboard comes up, don't print killer + cg.deferredPlayerLoading = 0; + cg.killerName[0] = 0; + firstTime = qtrue; + return qfalse; + } + fade = *fadeColor; + } + + + if (menuScoreboard == NULL) { + if ( cgs.gametype >= GT_TEAM ) { + menuScoreboard = Menus_FindByName("teamscore_menu"); + } else { + menuScoreboard = Menus_FindByName("score_menu"); + } + } + + if (menuScoreboard) { + if (firstTime) { + CG_SetScoreSelection(menuScoreboard); + firstTime = qfalse; + } + Menu_Paint(menuScoreboard, qtrue); + } + + // load any models that have been deferred + if ( ++cg.deferredPlayerLoading > 10 ) { + CG_LoadDeferredPlayers(); + } + + return qtrue; +#else + return CG_DrawOldScoreboard(); +#endif +} + +/* +================= +CG_DrawIntermission +================= +*/ +static void CG_DrawIntermission( void ) { +// int key; +#ifdef MISSIONPACK + //if (cg_singlePlayer.integer) { + // CG_DrawCenterString(); + // return; + //} +#else + if ( cgs.gametype == GT_SINGLE_PLAYER ) { + CG_DrawCenterString(); + return; + } +#endif + cg.scoreFadeTime = cg.time; + cg.scoreBoardShowing = CG_DrawScoreboard(); +} + +/* +================= +CG_DrawFollow +================= +*/ +static qboolean CG_DrawFollow( void ) { + float x; + vec4_t color; + const char *name; + + if ( !(cg.snap->ps.pm_flags & PMF_FOLLOW) ) { + return qfalse; + } + color[0] = 1; + color[1] = 1; + color[2] = 1; + color[3] = 1; + + + CG_DrawBigString( 320 - 9 * 8, 24, "following", 1.0F ); + + name = cgs.clientinfo[ cg.snap->ps.clientNum ].name; + + x = 0.5 * ( 640 - GIANT_WIDTH * CG_DrawStrlen( name ) ); + + CG_DrawStringExt( x, 40, name, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); + + return qtrue; +} + + + +/* +================= +CG_DrawAmmoWarning +================= +*/ +static void CG_DrawAmmoWarning( void ) { + const char *s; + int w; + + if ( cg_drawAmmoWarning.integer == 0 ) { + return; + } + + if ( !cg.lowAmmoWarning ) { + return; + } + + if ( cg.lowAmmoWarning == 2 ) { + s = "OUT OF AMMO"; + } else { + s = "LOW AMMO WARNING"; + } + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + CG_DrawBigString(320 - w / 2, 64, s, 1.0F); +} + + +#ifdef MISSIONPACK +/* +================= +CG_DrawProxWarning +================= +*/ +static void CG_DrawProxWarning( void ) { + char s [32]; + int w; + static int proxTime; + static int proxCounter; + static int proxTick; + + if( !(cg.snap->ps.eFlags & EF_TICKING ) ) { + proxTime = 0; + return; + } + + if (proxTime == 0) { + proxTime = cg.time + 5000; + proxCounter = 5; + proxTick = 0; + } + + if (cg.time > proxTime) { + proxTick = proxCounter--; + proxTime = cg.time + 1000; + } + + if (proxTick != 0) { + Com_sprintf(s, sizeof(s), "INTERNAL COMBUSTION IN: %i", proxTick); + } else { + Com_sprintf(s, sizeof(s), "YOU HAVE BEEN MINED"); + } + + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + CG_DrawBigStringColor( 320 - w / 2, 64 + BIGCHAR_HEIGHT, s, g_color_table[ColorIndex(COLOR_RED)] ); +} +#endif + + +/* +================= +CG_DrawWarmup +================= +*/ +static void CG_DrawWarmup( void ) { + int w; + int sec; + int i; + float scale; + clientInfo_t *ci1, *ci2; + int cw; + const char *s; + + sec = cg.warmup; + if ( !sec ) { + return; + } + + if ( sec < 0 ) { + s = "Waiting for players"; + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + CG_DrawBigString(320 - w / 2, 24, s, 1.0F); + cg.warmupCount = 0; + return; + } + + if (cgs.gametype == GT_TOURNAMENT) { + // find the two active players + ci1 = NULL; + ci2 = NULL; + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + if ( cgs.clientinfo[i].infoValid && cgs.clientinfo[i].team == TEAM_FREE ) { + if ( !ci1 ) { + ci1 = &cgs.clientinfo[i]; + } else { + ci2 = &cgs.clientinfo[i]; + } + } + } + + if ( ci1 && ci2 ) { + s = va( "%s vs %s", ci1->name, ci2->name ); +#ifdef MISSIONPACK + w = CG_Text_Width(s, 0.6f, 0); + CG_Text_Paint(320 - w / 2, 60, 0.6f, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE); +#else + w = CG_DrawStrlen( s ); + if ( w > 640 / GIANT_WIDTH ) { + cw = 640 / w; + } else { + cw = GIANT_WIDTH; + } + CG_DrawStringExt( 320 - w * cw/2, 20,s, colorWhite, + qfalse, qtrue, cw, (int)(cw * 1.5f), 0 ); +#endif + } + } else { + if ( cgs.gametype == GT_FFA ) { + s = "Free For All"; + } else if ( cgs.gametype == GT_TEAM ) { + s = "Team Deathmatch"; + } else if ( cgs.gametype == GT_CTF ) { + s = "Capture the Flag"; +#ifdef MISSIONPACK + } else if ( cgs.gametype == GT_1FCTF ) { + s = "One Flag CTF"; + } else if ( cgs.gametype == GT_OBELISK ) { + s = "Overload"; + } else if ( cgs.gametype == GT_HARVESTER ) { + s = "Harvester"; +#endif + } else { + s = ""; + } +#ifdef MISSIONPACK + w = CG_Text_Width(s, 0.6f, 0); + CG_Text_Paint(320 - w / 2, 90, 0.6f, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE); +#else + w = CG_DrawStrlen( s ); + if ( w > 640 / GIANT_WIDTH ) { + cw = 640 / w; + } else { + cw = GIANT_WIDTH; + } + CG_DrawStringExt( 320 - w * cw/2, 25,s, colorWhite, + qfalse, qtrue, cw, (int)(cw * 1.1f), 0 ); +#endif + } + + sec = ( sec - cg.time ) / 1000; + if ( sec < 0 ) { + cg.warmup = 0; + sec = 0; + } + s = va( "Starts in: %i", sec + 1 ); + if ( sec != cg.warmupCount ) { + cg.warmupCount = sec; + switch ( sec ) { + case 0: + trap_S_StartLocalSound( cgs.media.count1Sound, CHAN_ANNOUNCER ); + break; + case 1: + trap_S_StartLocalSound( cgs.media.count2Sound, CHAN_ANNOUNCER ); + break; + case 2: + trap_S_StartLocalSound( cgs.media.count3Sound, CHAN_ANNOUNCER ); + break; + default: + break; + } + } + scale = 0.45f; + switch ( cg.warmupCount ) { + case 0: + cw = 28; + scale = 0.54f; + break; + case 1: + cw = 24; + scale = 0.51f; + break; + case 2: + cw = 20; + scale = 0.48f; + break; + default: + cw = 16; + scale = 0.45f; + break; + } + +#ifdef MISSIONPACK + w = CG_Text_Width(s, scale, 0); + CG_Text_Paint(320 - w / 2, 125, scale, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE); +#else + w = CG_DrawStrlen( s ); + CG_DrawStringExt( 320 - w * cw/2, 70, s, colorWhite, + qfalse, qtrue, cw, (int)(cw * 1.5), 0 ); +#endif +} + +//================================================================================== +#ifdef MISSIONPACK +/* +================= +CG_DrawTimedMenus +================= +*/ +void CG_DrawTimedMenus() { + if (cg.voiceTime) { + int t = cg.time - cg.voiceTime; + if ( t > 2500 ) { + Menus_CloseByName("voiceMenu"); + trap_Cvar_Set("cl_conXOffset", "0"); + cg.voiceTime = 0; + } + } +} +#endif +/* +================= +CG_Draw2D +================= +*/ +static void CG_Draw2D( void ) { +#ifdef MISSIONPACK + if (cgs.orderPending && cg.time > cgs.orderTime) { + CG_CheckOrderPending(); + } +#endif + // if we are taking a levelshot for the menu, don't draw anything + if ( cg.levelShot ) { + return; + } + + if ( cg_draw2D.integer == 0 ) { + return; + } + + if ( cg.snap->ps.pm_type == PM_INTERMISSION ) { + CG_DrawIntermission(); + return; + } + +/* + if (cg.cameraMode) { + return; + } +*/ + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR ) { + CG_DrawSpectator(); + CG_DrawCrosshair(); + CG_DrawCrosshairNames(); + } else { + // don't draw any status if dead or the scoreboard is being explicitly shown + if ( !cg.showScores && cg.snap->ps.stats[STAT_HEALTH] > 0 ) { + +#ifdef MISSIONPACK + if ( cg_drawStatus.integer ) { + Menu_PaintAll(); + CG_DrawTimedMenus(); + } +#else + CG_DrawStatusBar(); +#endif + + CG_DrawAmmoWarning(); + +#ifdef MISSIONPACK + CG_DrawProxWarning(); +#endif + CG_DrawCrosshair(); + CG_DrawCrosshairNames(); + CG_DrawWeaponSelect(); + +#ifndef MISSIONPACK + CG_DrawHoldableItem(); +#else + //CG_DrawPersistantPowerup(); +#endif + CG_DrawReward(); + } + + if ( cgs.gametype >= GT_TEAM ) { +#ifndef MISSIONPACK + CG_DrawTeamInfo(); +#endif + } + } + + CG_DrawVote(); + CG_DrawTeamVote(); + + CG_DrawLagometer(); + +#ifdef MISSIONPACK + if (!cg_paused.integer) { + CG_DrawUpperRight(); + } +#else + CG_DrawUpperRight(); +#endif + +#ifndef MISSIONPACK + CG_DrawLowerRight(); + CG_DrawLowerLeft(); +#endif + + if ( !CG_DrawFollow() ) { + CG_DrawWarmup(); + } + + // don't draw center string if scoreboard is up + cg.scoreBoardShowing = CG_DrawScoreboard(); + if ( !cg.scoreBoardShowing) { + CG_DrawCenterString(); + } +} + + +static void CG_DrawTourneyScoreboard() { +#ifdef MISSIONPACK +#else + CG_DrawOldTourneyScoreboard(); +#endif +} + +/* +===================== +CG_DrawActive + +Perform all drawing needed to completely fill the screen +===================== +*/ +void CG_DrawActive( stereoFrame_t stereoView ) { + float separation; + vec3_t baseOrg; + + // optionally draw the info screen instead + if ( !cg.snap ) { + CG_DrawInformation(); + return; + } + + // optionally draw the tournement scoreboard instead + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR && + ( cg.snap->ps.pm_flags & PMF_SCOREBOARD ) ) { + CG_DrawTourneyScoreboard(); + return; + } + + switch ( stereoView ) { + case STEREO_CENTER: + separation = 0; + break; + case STEREO_LEFT: + separation = -cg_stereoSeparation.value / 2; + break; + case STEREO_RIGHT: + separation = cg_stereoSeparation.value / 2; + break; + default: + separation = 0; + CG_Error( "CG_DrawActive: Undefined stereoView" ); + } + + + // clear around the rendered view if sized down + CG_TileClear(); + + // offset vieworg appropriately if we're doing stereo separation + VectorCopy( cg.refdef.vieworg, baseOrg ); + if ( separation != 0 ) { + VectorMA( cg.refdef.vieworg, -separation, cg.refdef.viewaxis[1], cg.refdef.vieworg ); + } + + // draw 3D view + trap_R_RenderScene( &cg.refdef ); + + // restore original viewpoint if running stereo + if ( separation != 0 ) { + VectorCopy( baseOrg, cg.refdef.vieworg ); + } + + // draw status bar and other floating elements + CG_Draw2D(); +} + + + diff --git a/code/cgame/cg_drawtools.c b/code/cgame/cg_drawtools.c index 9c27606..2ec45ed 100755 --- a/code/cgame/cg_drawtools.c +++ b/code/cgame/cg_drawtools.c @@ -1,824 +1,824 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// cg_drawtools.c -- helper functions called by cg_draw, cg_scoreboard, cg_info, etc -#include "cg_local.h" - -/* -================ -CG_AdjustFrom640 - -Adjusted for resolution and screen aspect ratio -================ -*/ -void CG_AdjustFrom640( float *x, float *y, float *w, float *h ) { -#if 0 - // adjust for wide screens - if ( cgs.glconfig.vidWidth * 480 > cgs.glconfig.vidHeight * 640 ) { - *x += 0.5 * ( cgs.glconfig.vidWidth - ( cgs.glconfig.vidHeight * 640 / 480 ) ); - } -#endif - // scale for screen sizes - *x *= cgs.screenXScale; - *y *= cgs.screenYScale; - *w *= cgs.screenXScale; - *h *= cgs.screenYScale; -} - -/* -================ -CG_FillRect - -Coordinates are 640*480 virtual values -================= -*/ -void CG_FillRect( float x, float y, float width, float height, const float *color ) { - trap_R_SetColor( color ); - - CG_AdjustFrom640( &x, &y, &width, &height ); - trap_R_DrawStretchPic( x, y, width, height, 0, 0, 0, 0, cgs.media.whiteShader ); - - trap_R_SetColor( NULL ); -} - -/* -================ -CG_DrawSides - -Coords are virtual 640x480 -================ -*/ -void CG_DrawSides(float x, float y, float w, float h, float size) { - CG_AdjustFrom640( &x, &y, &w, &h ); - size *= cgs.screenXScale; - trap_R_DrawStretchPic( x, y, size, h, 0, 0, 0, 0, cgs.media.whiteShader ); - trap_R_DrawStretchPic( x + w - size, y, size, h, 0, 0, 0, 0, cgs.media.whiteShader ); -} - -void CG_DrawTopBottom(float x, float y, float w, float h, float size) { - CG_AdjustFrom640( &x, &y, &w, &h ); - size *= cgs.screenYScale; - trap_R_DrawStretchPic( x, y, w, size, 0, 0, 0, 0, cgs.media.whiteShader ); - trap_R_DrawStretchPic( x, y + h - size, w, size, 0, 0, 0, 0, cgs.media.whiteShader ); -} -/* -================ -UI_DrawRect - -Coordinates are 640*480 virtual values -================= -*/ -void CG_DrawRect( float x, float y, float width, float height, float size, const float *color ) { - trap_R_SetColor( color ); - - CG_DrawTopBottom(x, y, width, height, size); - CG_DrawSides(x, y, width, height, size); - - trap_R_SetColor( NULL ); -} - - - -/* -================ -CG_DrawPic - -Coordinates are 640*480 virtual values -================= -*/ -void CG_DrawPic( float x, float y, float width, float height, qhandle_t hShader ) { - CG_AdjustFrom640( &x, &y, &width, &height ); - trap_R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader ); -} - - - -/* -=============== -CG_DrawChar - -Coordinates and size in 640*480 virtual screen size -=============== -*/ -void CG_DrawChar( int x, int y, int width, int height, int ch ) { - int row, col; - float frow, fcol; - float size; - float ax, ay, aw, ah; - - ch &= 255; - - if ( ch == ' ' ) { - return; - } - - ax = x; - ay = y; - aw = width; - ah = height; - CG_AdjustFrom640( &ax, &ay, &aw, &ah ); - - row = ch>>4; - col = ch&15; - - frow = row*0.0625; - fcol = col*0.0625; - size = 0.0625; - - trap_R_DrawStretchPic( ax, ay, aw, ah, - fcol, frow, - fcol + size, frow + size, - cgs.media.charsetShader ); -} - - -/* -================== -CG_DrawStringExt - -Draws a multi-colored string with a drop shadow, optionally forcing -to a fixed color. - -Coordinates are at 640 by 480 virtual resolution -================== -*/ -void CG_DrawStringExt( int x, int y, const char *string, const float *setColor, - qboolean forceColor, qboolean shadow, int charWidth, int charHeight, int maxChars ) { - vec4_t color; - const char *s; - int xx; - int cnt; - - if (maxChars <= 0) - maxChars = 32767; // do them all! - - // draw the drop shadow - if (shadow) { - color[0] = color[1] = color[2] = 0; - color[3] = setColor[3]; - trap_R_SetColor( color ); - s = string; - xx = x; - cnt = 0; - while ( *s && cnt < maxChars) { - if ( Q_IsColorString( s ) ) { - s += 2; - continue; - } - CG_DrawChar( xx + 2, y + 2, charWidth, charHeight, *s ); - cnt++; - xx += charWidth; - s++; - } - } - - // draw the colored text - s = string; - xx = x; - cnt = 0; - trap_R_SetColor( setColor ); - while ( *s && cnt < maxChars) { - if ( Q_IsColorString( s ) ) { - if ( !forceColor ) { - memcpy( color, g_color_table[ColorIndex(*(s+1))], sizeof( color ) ); - color[3] = setColor[3]; - trap_R_SetColor( color ); - } - s += 2; - continue; - } - CG_DrawChar( xx, y, charWidth, charHeight, *s ); - xx += charWidth; - cnt++; - s++; - } - trap_R_SetColor( NULL ); -} - -void CG_DrawBigString( int x, int y, const char *s, float alpha ) { - float color[4]; - - color[0] = color[1] = color[2] = 1.0; - color[3] = alpha; - CG_DrawStringExt( x, y, s, color, qfalse, qtrue, BIGCHAR_WIDTH, BIGCHAR_HEIGHT, 0 ); -} - -void CG_DrawBigStringColor( int x, int y, const char *s, vec4_t color ) { - CG_DrawStringExt( x, y, s, color, qtrue, qtrue, BIGCHAR_WIDTH, BIGCHAR_HEIGHT, 0 ); -} - -void CG_DrawSmallString( int x, int y, const char *s, float alpha ) { - float color[4]; - - color[0] = color[1] = color[2] = 1.0; - color[3] = alpha; - CG_DrawStringExt( x, y, s, color, qfalse, qfalse, SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, 0 ); -} - -void CG_DrawSmallStringColor( int x, int y, const char *s, vec4_t color ) { - CG_DrawStringExt( x, y, s, color, qtrue, qfalse, SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, 0 ); -} - -/* -================= -CG_DrawStrlen - -Returns character count, skiping color escape codes -================= -*/ -int CG_DrawStrlen( const char *str ) { - const char *s = str; - int count = 0; - - while ( *s ) { - if ( Q_IsColorString( s ) ) { - s += 2; - } else { - count++; - s++; - } - } - - return count; -} - -/* -============= -CG_TileClearBox - -This repeats a 64*64 tile graphic to fill the screen around a sized down -refresh window. -============= -*/ -static void CG_TileClearBox( int x, int y, int w, int h, qhandle_t hShader ) { - float s1, t1, s2, t2; - - s1 = x/64.0; - t1 = y/64.0; - s2 = (x+w)/64.0; - t2 = (y+h)/64.0; - trap_R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, hShader ); -} - - - -/* -============== -CG_TileClear - -Clear around a sized down screen -============== -*/ -void CG_TileClear( void ) { - int top, bottom, left, right; - int w, h; - - w = cgs.glconfig.vidWidth; - h = cgs.glconfig.vidHeight; - - if ( cg.refdef.x == 0 && cg.refdef.y == 0 && - cg.refdef.width == w && cg.refdef.height == h ) { - return; // full screen rendering - } - - top = cg.refdef.y; - bottom = top + cg.refdef.height-1; - left = cg.refdef.x; - right = left + cg.refdef.width-1; - - // clear above view screen - CG_TileClearBox( 0, 0, w, top, cgs.media.backTileShader ); - - // clear below view screen - CG_TileClearBox( 0, bottom, w, h - bottom, cgs.media.backTileShader ); - - // clear left of view screen - CG_TileClearBox( 0, top, left, bottom - top + 1, cgs.media.backTileShader ); - - // clear right of view screen - CG_TileClearBox( right, top, w - right, bottom - top + 1, cgs.media.backTileShader ); -} - - - -/* -================ -CG_FadeColor -================ -*/ -float *CG_FadeColor( int startMsec, int totalMsec ) { - static vec4_t color; - int t; - - if ( startMsec == 0 ) { - return NULL; - } - - t = cg.time - startMsec; - - if ( t >= totalMsec ) { - return NULL; - } - - // fade out - if ( totalMsec - t < FADE_TIME ) { - color[3] = ( totalMsec - t ) * 1.0/FADE_TIME; - } else { - color[3] = 1.0; - } - color[0] = color[1] = color[2] = 1; - - return color; -} - - -/* -================ -CG_TeamColor -================ -*/ -float *CG_TeamColor( int team ) { - static vec4_t red = {1, 0.2f, 0.2f, 1}; - static vec4_t blue = {0.2f, 0.2f, 1, 1}; - static vec4_t other = {1, 1, 1, 1}; - static vec4_t spectator = {0.7f, 0.7f, 0.7f, 1}; - - switch ( team ) { - case TEAM_RED: - return red; - case TEAM_BLUE: - return blue; - case TEAM_SPECTATOR: - return spectator; - default: - return other; - } -} - - - -/* -================= -CG_GetColorForHealth -================= -*/ -void CG_GetColorForHealth( int health, int armor, vec4_t hcolor ) { - int count; - int max; - - // calculate the total points of damage that can - // be sustained at the current health / armor level - if ( health <= 0 ) { - VectorClear( hcolor ); // black - hcolor[3] = 1; - return; - } - count = armor; - max = health * ARMOR_PROTECTION / ( 1.0 - ARMOR_PROTECTION ); - if ( max < count ) { - count = max; - } - health += count; - - // set the color based on health - hcolor[0] = 1.0; - hcolor[3] = 1.0; - if ( health >= 100 ) { - hcolor[2] = 1.0; - } else if ( health < 66 ) { - hcolor[2] = 0; - } else { - hcolor[2] = ( health - 66 ) / 33.0; - } - - if ( health > 60 ) { - hcolor[1] = 1.0; - } else if ( health < 30 ) { - hcolor[1] = 0; - } else { - hcolor[1] = ( health - 30 ) / 30.0; - } -} - -/* -================= -CG_ColorForHealth -================= -*/ -void CG_ColorForHealth( vec4_t hcolor ) { - - CG_GetColorForHealth( cg.snap->ps.stats[STAT_HEALTH], - cg.snap->ps.stats[STAT_ARMOR], hcolor ); -} - - - - -// bk001205 - code below duplicated in q3_ui/ui-atoms.c -// bk001205 - FIXME: does this belong in ui_shared.c? -// bk001205 - FIXME: HARD_LINKED flags not visible here -#ifndef Q3_STATIC // bk001205 - q_shared defines not visible here -/* -================= -UI_DrawProportionalString2 -================= -*/ -static int propMap[128][3] = { -{0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, -{0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, - -{0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, -{0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, - -{0, 0, PROP_SPACE_WIDTH}, // SPACE -{11, 122, 7}, // ! -{154, 181, 14}, // " -{55, 122, 17}, // # -{79, 122, 18}, // $ -{101, 122, 23}, // % -{153, 122, 18}, // & -{9, 93, 7}, // ' -{207, 122, 8}, // ( -{230, 122, 9}, // ) -{177, 122, 18}, // * -{30, 152, 18}, // + -{85, 181, 7}, // , -{34, 93, 11}, // - -{110, 181, 6}, // . -{130, 152, 14}, // / - -{22, 64, 17}, // 0 -{41, 64, 12}, // 1 -{58, 64, 17}, // 2 -{78, 64, 18}, // 3 -{98, 64, 19}, // 4 -{120, 64, 18}, // 5 -{141, 64, 18}, // 6 -{204, 64, 16}, // 7 -{162, 64, 17}, // 8 -{182, 64, 18}, // 9 -{59, 181, 7}, // : -{35,181, 7}, // ; -{203, 152, 14}, // < -{56, 93, 14}, // = -{228, 152, 14}, // > -{177, 181, 18}, // ? - -{28, 122, 22}, // @ -{5, 4, 18}, // A -{27, 4, 18}, // B -{48, 4, 18}, // C -{69, 4, 17}, // D -{90, 4, 13}, // E -{106, 4, 13}, // F -{121, 4, 18}, // G -{143, 4, 17}, // H -{164, 4, 8}, // I -{175, 4, 16}, // J -{195, 4, 18}, // K -{216, 4, 12}, // L -{230, 4, 23}, // M -{6, 34, 18}, // N -{27, 34, 18}, // O - -{48, 34, 18}, // P -{68, 34, 18}, // Q -{90, 34, 17}, // R -{110, 34, 18}, // S -{130, 34, 14}, // T -{146, 34, 18}, // U -{166, 34, 19}, // V -{185, 34, 29}, // W -{215, 34, 18}, // X -{234, 34, 18}, // Y -{5, 64, 14}, // Z -{60, 152, 7}, // [ -{106, 151, 13}, // '\' -{83, 152, 7}, // ] -{128, 122, 17}, // ^ -{4, 152, 21}, // _ - -{134, 181, 5}, // ' -{5, 4, 18}, // A -{27, 4, 18}, // B -{48, 4, 18}, // C -{69, 4, 17}, // D -{90, 4, 13}, // E -{106, 4, 13}, // F -{121, 4, 18}, // G -{143, 4, 17}, // H -{164, 4, 8}, // I -{175, 4, 16}, // J -{195, 4, 18}, // K -{216, 4, 12}, // L -{230, 4, 23}, // M -{6, 34, 18}, // N -{27, 34, 18}, // O - -{48, 34, 18}, // P -{68, 34, 18}, // Q -{90, 34, 17}, // R -{110, 34, 18}, // S -{130, 34, 14}, // T -{146, 34, 18}, // U -{166, 34, 19}, // V -{185, 34, 29}, // W -{215, 34, 18}, // X -{234, 34, 18}, // Y -{5, 64, 14}, // Z -{153, 152, 13}, // { -{11, 181, 5}, // | -{180, 152, 13}, // } -{79, 93, 17}, // ~ -{0, 0, -1} // DEL -}; - -static int propMapB[26][3] = { -{11, 12, 33}, -{49, 12, 31}, -{85, 12, 31}, -{120, 12, 30}, -{156, 12, 21}, -{183, 12, 21}, -{207, 12, 32}, - -{13, 55, 30}, -{49, 55, 13}, -{66, 55, 29}, -{101, 55, 31}, -{135, 55, 21}, -{158, 55, 40}, -{204, 55, 32}, - -{12, 97, 31}, -{48, 97, 31}, -{82, 97, 30}, -{118, 97, 30}, -{153, 97, 30}, -{185, 97, 25}, -{213, 97, 30}, - -{11, 139, 32}, -{42, 139, 51}, -{93, 139, 32}, -{126, 139, 31}, -{158, 139, 25}, -}; - -#define PROPB_GAP_WIDTH 4 -#define PROPB_SPACE_WIDTH 12 -#define PROPB_HEIGHT 36 - -/* -================= -UI_DrawBannerString -================= -*/ -static void UI_DrawBannerString2( int x, int y, const char* str, vec4_t color ) -{ - const char* s; - unsigned char ch; // bk001204 : array subscript - float ax; - float ay; - float aw; - float ah; - float frow; - float fcol; - float fwidth; - float fheight; - - // draw the colored text - trap_R_SetColor( color ); - - ax = x * cgs.screenXScale + cgs.screenXBias; - ay = y * cgs.screenXScale; - - s = str; - while ( *s ) - { - ch = *s & 127; - if ( ch == ' ' ) { - ax += ((float)PROPB_SPACE_WIDTH + (float)PROPB_GAP_WIDTH)* cgs.screenXScale; - } - else if ( ch >= 'A' && ch <= 'Z' ) { - ch -= 'A'; - fcol = (float)propMapB[ch][0] / 256.0f; - frow = (float)propMapB[ch][1] / 256.0f; - fwidth = (float)propMapB[ch][2] / 256.0f; - fheight = (float)PROPB_HEIGHT / 256.0f; - aw = (float)propMapB[ch][2] * cgs.screenXScale; - ah = (float)PROPB_HEIGHT * cgs.screenXScale; - trap_R_DrawStretchPic( ax, ay, aw, ah, fcol, frow, fcol+fwidth, frow+fheight, cgs.media.charsetPropB ); - ax += (aw + (float)PROPB_GAP_WIDTH * cgs.screenXScale); - } - s++; - } - - trap_R_SetColor( NULL ); -} - -void UI_DrawBannerString( int x, int y, const char* str, int style, vec4_t color ) { - const char * s; - int ch; - int width; - vec4_t drawcolor; - - // find the width of the drawn text - s = str; - width = 0; - while ( *s ) { - ch = *s; - if ( ch == ' ' ) { - width += PROPB_SPACE_WIDTH; - } - else if ( ch >= 'A' && ch <= 'Z' ) { - width += propMapB[ch - 'A'][2] + PROPB_GAP_WIDTH; - } - s++; - } - width -= PROPB_GAP_WIDTH; - - switch( style & UI_FORMATMASK ) { - case UI_CENTER: - x -= width / 2; - break; - - case UI_RIGHT: - x -= width; - break; - - case UI_LEFT: - default: - break; - } - - if ( style & UI_DROPSHADOW ) { - drawcolor[0] = drawcolor[1] = drawcolor[2] = 0; - drawcolor[3] = color[3]; - UI_DrawBannerString2( x+2, y+2, str, drawcolor ); - } - - UI_DrawBannerString2( x, y, str, color ); -} - - -int UI_ProportionalStringWidth( const char* str ) { - const char * s; - int ch; - int charWidth; - int width; - - s = str; - width = 0; - while ( *s ) { - ch = *s & 127; - charWidth = propMap[ch][2]; - if ( charWidth != -1 ) { - width += charWidth; - width += PROP_GAP_WIDTH; - } - s++; - } - - width -= PROP_GAP_WIDTH; - return width; -} - -static void UI_DrawProportionalString2( int x, int y, const char* str, vec4_t color, float sizeScale, qhandle_t charset ) -{ - const char* s; - unsigned char ch; // bk001204 - unsigned - float ax; - float ay; - float aw; - float ah; - float frow; - float fcol; - float fwidth; - float fheight; - - // draw the colored text - trap_R_SetColor( color ); - - ax = x * cgs.screenXScale + cgs.screenXBias; - ay = y * cgs.screenXScale; - - s = str; - while ( *s ) - { - ch = *s & 127; - if ( ch == ' ' ) { - aw = (float)PROP_SPACE_WIDTH * cgs.screenXScale * sizeScale; - } else if ( propMap[ch][2] != -1 ) { - fcol = (float)propMap[ch][0] / 256.0f; - frow = (float)propMap[ch][1] / 256.0f; - fwidth = (float)propMap[ch][2] / 256.0f; - fheight = (float)PROP_HEIGHT / 256.0f; - aw = (float)propMap[ch][2] * cgs.screenXScale * sizeScale; - ah = (float)PROP_HEIGHT * cgs.screenXScale * sizeScale; - trap_R_DrawStretchPic( ax, ay, aw, ah, fcol, frow, fcol+fwidth, frow+fheight, charset ); - } else { - aw = 0; - } - - ax += (aw + (float)PROP_GAP_WIDTH * cgs.screenXScale * sizeScale); - s++; - } - - trap_R_SetColor( NULL ); -} - -/* -================= -UI_ProportionalSizeScale -================= -*/ -float UI_ProportionalSizeScale( int style ) { - if( style & UI_SMALLFONT ) { - return 0.75; - } - - return 1.00; -} - - -/* -================= -UI_DrawProportionalString -================= -*/ -void UI_DrawProportionalString( int x, int y, const char* str, int style, vec4_t color ) { - vec4_t drawcolor; - int width; - float sizeScale; - - sizeScale = UI_ProportionalSizeScale( style ); - - switch( style & UI_FORMATMASK ) { - case UI_CENTER: - width = UI_ProportionalStringWidth( str ) * sizeScale; - x -= width / 2; - break; - - case UI_RIGHT: - width = UI_ProportionalStringWidth( str ) * sizeScale; - x -= width; - break; - - case UI_LEFT: - default: - break; - } - - if ( style & UI_DROPSHADOW ) { - drawcolor[0] = drawcolor[1] = drawcolor[2] = 0; - drawcolor[3] = color[3]; - UI_DrawProportionalString2( x+2, y+2, str, drawcolor, sizeScale, cgs.media.charsetProp ); - } - - if ( style & UI_INVERSE ) { - drawcolor[0] = color[0] * 0.8; - drawcolor[1] = color[1] * 0.8; - drawcolor[2] = color[2] * 0.8; - drawcolor[3] = color[3]; - UI_DrawProportionalString2( x, y, str, drawcolor, sizeScale, cgs.media.charsetProp ); - return; - } - - if ( style & UI_PULSE ) { - drawcolor[0] = color[0] * 0.8; - drawcolor[1] = color[1] * 0.8; - drawcolor[2] = color[2] * 0.8; - drawcolor[3] = color[3]; - UI_DrawProportionalString2( x, y, str, color, sizeScale, cgs.media.charsetProp ); - - drawcolor[0] = color[0]; - drawcolor[1] = color[1]; - drawcolor[2] = color[2]; - drawcolor[3] = 0.5 + 0.5 * sin( cg.time / PULSE_DIVISOR ); - UI_DrawProportionalString2( x, y, str, drawcolor, sizeScale, cgs.media.charsetPropGlow ); - return; - } - - UI_DrawProportionalString2( x, y, str, color, sizeScale, cgs.media.charsetProp ); -} -#endif // Q3STATIC +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// cg_drawtools.c -- helper functions called by cg_draw, cg_scoreboard, cg_info, etc +#include "cg_local.h" + +/* +================ +CG_AdjustFrom640 + +Adjusted for resolution and screen aspect ratio +================ +*/ +void CG_AdjustFrom640( float *x, float *y, float *w, float *h ) { +#if 0 + // adjust for wide screens + if ( cgs.glconfig.vidWidth * 480 > cgs.glconfig.vidHeight * 640 ) { + *x += 0.5 * ( cgs.glconfig.vidWidth - ( cgs.glconfig.vidHeight * 640 / 480 ) ); + } +#endif + // scale for screen sizes + *x *= cgs.screenXScale; + *y *= cgs.screenYScale; + *w *= cgs.screenXScale; + *h *= cgs.screenYScale; +} + +/* +================ +CG_FillRect + +Coordinates are 640*480 virtual values +================= +*/ +void CG_FillRect( float x, float y, float width, float height, const float *color ) { + trap_R_SetColor( color ); + + CG_AdjustFrom640( &x, &y, &width, &height ); + trap_R_DrawStretchPic( x, y, width, height, 0, 0, 0, 0, cgs.media.whiteShader ); + + trap_R_SetColor( NULL ); +} + +/* +================ +CG_DrawSides + +Coords are virtual 640x480 +================ +*/ +void CG_DrawSides(float x, float y, float w, float h, float size) { + CG_AdjustFrom640( &x, &y, &w, &h ); + size *= cgs.screenXScale; + trap_R_DrawStretchPic( x, y, size, h, 0, 0, 0, 0, cgs.media.whiteShader ); + trap_R_DrawStretchPic( x + w - size, y, size, h, 0, 0, 0, 0, cgs.media.whiteShader ); +} + +void CG_DrawTopBottom(float x, float y, float w, float h, float size) { + CG_AdjustFrom640( &x, &y, &w, &h ); + size *= cgs.screenYScale; + trap_R_DrawStretchPic( x, y, w, size, 0, 0, 0, 0, cgs.media.whiteShader ); + trap_R_DrawStretchPic( x, y + h - size, w, size, 0, 0, 0, 0, cgs.media.whiteShader ); +} +/* +================ +UI_DrawRect + +Coordinates are 640*480 virtual values +================= +*/ +void CG_DrawRect( float x, float y, float width, float height, float size, const float *color ) { + trap_R_SetColor( color ); + + CG_DrawTopBottom(x, y, width, height, size); + CG_DrawSides(x, y, width, height, size); + + trap_R_SetColor( NULL ); +} + + + +/* +================ +CG_DrawPic + +Coordinates are 640*480 virtual values +================= +*/ +void CG_DrawPic( float x, float y, float width, float height, qhandle_t hShader ) { + CG_AdjustFrom640( &x, &y, &width, &height ); + trap_R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader ); +} + + + +/* +=============== +CG_DrawChar + +Coordinates and size in 640*480 virtual screen size +=============== +*/ +void CG_DrawChar( int x, int y, int width, int height, int ch ) { + int row, col; + float frow, fcol; + float size; + float ax, ay, aw, ah; + + ch &= 255; + + if ( ch == ' ' ) { + return; + } + + ax = x; + ay = y; + aw = width; + ah = height; + CG_AdjustFrom640( &ax, &ay, &aw, &ah ); + + row = ch>>4; + col = ch&15; + + frow = row*0.0625; + fcol = col*0.0625; + size = 0.0625; + + trap_R_DrawStretchPic( ax, ay, aw, ah, + fcol, frow, + fcol + size, frow + size, + cgs.media.charsetShader ); +} + + +/* +================== +CG_DrawStringExt + +Draws a multi-colored string with a drop shadow, optionally forcing +to a fixed color. + +Coordinates are at 640 by 480 virtual resolution +================== +*/ +void CG_DrawStringExt( int x, int y, const char *string, const float *setColor, + qboolean forceColor, qboolean shadow, int charWidth, int charHeight, int maxChars ) { + vec4_t color; + const char *s; + int xx; + int cnt; + + if (maxChars <= 0) + maxChars = 32767; // do them all! + + // draw the drop shadow + if (shadow) { + color[0] = color[1] = color[2] = 0; + color[3] = setColor[3]; + trap_R_SetColor( color ); + s = string; + xx = x; + cnt = 0; + while ( *s && cnt < maxChars) { + if ( Q_IsColorString( s ) ) { + s += 2; + continue; + } + CG_DrawChar( xx + 2, y + 2, charWidth, charHeight, *s ); + cnt++; + xx += charWidth; + s++; + } + } + + // draw the colored text + s = string; + xx = x; + cnt = 0; + trap_R_SetColor( setColor ); + while ( *s && cnt < maxChars) { + if ( Q_IsColorString( s ) ) { + if ( !forceColor ) { + memcpy( color, g_color_table[ColorIndex(*(s+1))], sizeof( color ) ); + color[3] = setColor[3]; + trap_R_SetColor( color ); + } + s += 2; + continue; + } + CG_DrawChar( xx, y, charWidth, charHeight, *s ); + xx += charWidth; + cnt++; + s++; + } + trap_R_SetColor( NULL ); +} + +void CG_DrawBigString( int x, int y, const char *s, float alpha ) { + float color[4]; + + color[0] = color[1] = color[2] = 1.0; + color[3] = alpha; + CG_DrawStringExt( x, y, s, color, qfalse, qtrue, BIGCHAR_WIDTH, BIGCHAR_HEIGHT, 0 ); +} + +void CG_DrawBigStringColor( int x, int y, const char *s, vec4_t color ) { + CG_DrawStringExt( x, y, s, color, qtrue, qtrue, BIGCHAR_WIDTH, BIGCHAR_HEIGHT, 0 ); +} + +void CG_DrawSmallString( int x, int y, const char *s, float alpha ) { + float color[4]; + + color[0] = color[1] = color[2] = 1.0; + color[3] = alpha; + CG_DrawStringExt( x, y, s, color, qfalse, qfalse, SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, 0 ); +} + +void CG_DrawSmallStringColor( int x, int y, const char *s, vec4_t color ) { + CG_DrawStringExt( x, y, s, color, qtrue, qfalse, SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, 0 ); +} + +/* +================= +CG_DrawStrlen + +Returns character count, skiping color escape codes +================= +*/ +int CG_DrawStrlen( const char *str ) { + const char *s = str; + int count = 0; + + while ( *s ) { + if ( Q_IsColorString( s ) ) { + s += 2; + } else { + count++; + s++; + } + } + + return count; +} + +/* +============= +CG_TileClearBox + +This repeats a 64*64 tile graphic to fill the screen around a sized down +refresh window. +============= +*/ +static void CG_TileClearBox( int x, int y, int w, int h, qhandle_t hShader ) { + float s1, t1, s2, t2; + + s1 = x/64.0; + t1 = y/64.0; + s2 = (x+w)/64.0; + t2 = (y+h)/64.0; + trap_R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, hShader ); +} + + + +/* +============== +CG_TileClear + +Clear around a sized down screen +============== +*/ +void CG_TileClear( void ) { + int top, bottom, left, right; + int w, h; + + w = cgs.glconfig.vidWidth; + h = cgs.glconfig.vidHeight; + + if ( cg.refdef.x == 0 && cg.refdef.y == 0 && + cg.refdef.width == w && cg.refdef.height == h ) { + return; // full screen rendering + } + + top = cg.refdef.y; + bottom = top + cg.refdef.height-1; + left = cg.refdef.x; + right = left + cg.refdef.width-1; + + // clear above view screen + CG_TileClearBox( 0, 0, w, top, cgs.media.backTileShader ); + + // clear below view screen + CG_TileClearBox( 0, bottom, w, h - bottom, cgs.media.backTileShader ); + + // clear left of view screen + CG_TileClearBox( 0, top, left, bottom - top + 1, cgs.media.backTileShader ); + + // clear right of view screen + CG_TileClearBox( right, top, w - right, bottom - top + 1, cgs.media.backTileShader ); +} + + + +/* +================ +CG_FadeColor +================ +*/ +float *CG_FadeColor( int startMsec, int totalMsec ) { + static vec4_t color; + int t; + + if ( startMsec == 0 ) { + return NULL; + } + + t = cg.time - startMsec; + + if ( t >= totalMsec ) { + return NULL; + } + + // fade out + if ( totalMsec - t < FADE_TIME ) { + color[3] = ( totalMsec - t ) * 1.0/FADE_TIME; + } else { + color[3] = 1.0; + } + color[0] = color[1] = color[2] = 1; + + return color; +} + + +/* +================ +CG_TeamColor +================ +*/ +float *CG_TeamColor( int team ) { + static vec4_t red = {1, 0.2f, 0.2f, 1}; + static vec4_t blue = {0.2f, 0.2f, 1, 1}; + static vec4_t other = {1, 1, 1, 1}; + static vec4_t spectator = {0.7f, 0.7f, 0.7f, 1}; + + switch ( team ) { + case TEAM_RED: + return red; + case TEAM_BLUE: + return blue; + case TEAM_SPECTATOR: + return spectator; + default: + return other; + } +} + + + +/* +================= +CG_GetColorForHealth +================= +*/ +void CG_GetColorForHealth( int health, int armor, vec4_t hcolor ) { + int count; + int max; + + // calculate the total points of damage that can + // be sustained at the current health / armor level + if ( health <= 0 ) { + VectorClear( hcolor ); // black + hcolor[3] = 1; + return; + } + count = armor; + max = health * ARMOR_PROTECTION / ( 1.0 - ARMOR_PROTECTION ); + if ( max < count ) { + count = max; + } + health += count; + + // set the color based on health + hcolor[0] = 1.0; + hcolor[3] = 1.0; + if ( health >= 100 ) { + hcolor[2] = 1.0; + } else if ( health < 66 ) { + hcolor[2] = 0; + } else { + hcolor[2] = ( health - 66 ) / 33.0; + } + + if ( health > 60 ) { + hcolor[1] = 1.0; + } else if ( health < 30 ) { + hcolor[1] = 0; + } else { + hcolor[1] = ( health - 30 ) / 30.0; + } +} + +/* +================= +CG_ColorForHealth +================= +*/ +void CG_ColorForHealth( vec4_t hcolor ) { + + CG_GetColorForHealth( cg.snap->ps.stats[STAT_HEALTH], + cg.snap->ps.stats[STAT_ARMOR], hcolor ); +} + + + + +// bk001205 - code below duplicated in q3_ui/ui-atoms.c +// bk001205 - FIXME: does this belong in ui_shared.c? +// bk001205 - FIXME: HARD_LINKED flags not visible here +#ifndef Q3_STATIC // bk001205 - q_shared defines not visible here +/* +================= +UI_DrawProportionalString2 +================= +*/ +static int propMap[128][3] = { +{0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, +{0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, + +{0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, +{0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, + +{0, 0, PROP_SPACE_WIDTH}, // SPACE +{11, 122, 7}, // ! +{154, 181, 14}, // " +{55, 122, 17}, // # +{79, 122, 18}, // $ +{101, 122, 23}, // % +{153, 122, 18}, // & +{9, 93, 7}, // ' +{207, 122, 8}, // ( +{230, 122, 9}, // ) +{177, 122, 18}, // * +{30, 152, 18}, // + +{85, 181, 7}, // , +{34, 93, 11}, // - +{110, 181, 6}, // . +{130, 152, 14}, // / + +{22, 64, 17}, // 0 +{41, 64, 12}, // 1 +{58, 64, 17}, // 2 +{78, 64, 18}, // 3 +{98, 64, 19}, // 4 +{120, 64, 18}, // 5 +{141, 64, 18}, // 6 +{204, 64, 16}, // 7 +{162, 64, 17}, // 8 +{182, 64, 18}, // 9 +{59, 181, 7}, // : +{35,181, 7}, // ; +{203, 152, 14}, // < +{56, 93, 14}, // = +{228, 152, 14}, // > +{177, 181, 18}, // ? + +{28, 122, 22}, // @ +{5, 4, 18}, // A +{27, 4, 18}, // B +{48, 4, 18}, // C +{69, 4, 17}, // D +{90, 4, 13}, // E +{106, 4, 13}, // F +{121, 4, 18}, // G +{143, 4, 17}, // H +{164, 4, 8}, // I +{175, 4, 16}, // J +{195, 4, 18}, // K +{216, 4, 12}, // L +{230, 4, 23}, // M +{6, 34, 18}, // N +{27, 34, 18}, // O + +{48, 34, 18}, // P +{68, 34, 18}, // Q +{90, 34, 17}, // R +{110, 34, 18}, // S +{130, 34, 14}, // T +{146, 34, 18}, // U +{166, 34, 19}, // V +{185, 34, 29}, // W +{215, 34, 18}, // X +{234, 34, 18}, // Y +{5, 64, 14}, // Z +{60, 152, 7}, // [ +{106, 151, 13}, // '\' +{83, 152, 7}, // ] +{128, 122, 17}, // ^ +{4, 152, 21}, // _ + +{134, 181, 5}, // ' +{5, 4, 18}, // A +{27, 4, 18}, // B +{48, 4, 18}, // C +{69, 4, 17}, // D +{90, 4, 13}, // E +{106, 4, 13}, // F +{121, 4, 18}, // G +{143, 4, 17}, // H +{164, 4, 8}, // I +{175, 4, 16}, // J +{195, 4, 18}, // K +{216, 4, 12}, // L +{230, 4, 23}, // M +{6, 34, 18}, // N +{27, 34, 18}, // O + +{48, 34, 18}, // P +{68, 34, 18}, // Q +{90, 34, 17}, // R +{110, 34, 18}, // S +{130, 34, 14}, // T +{146, 34, 18}, // U +{166, 34, 19}, // V +{185, 34, 29}, // W +{215, 34, 18}, // X +{234, 34, 18}, // Y +{5, 64, 14}, // Z +{153, 152, 13}, // { +{11, 181, 5}, // | +{180, 152, 13}, // } +{79, 93, 17}, // ~ +{0, 0, -1} // DEL +}; + +static int propMapB[26][3] = { +{11, 12, 33}, +{49, 12, 31}, +{85, 12, 31}, +{120, 12, 30}, +{156, 12, 21}, +{183, 12, 21}, +{207, 12, 32}, + +{13, 55, 30}, +{49, 55, 13}, +{66, 55, 29}, +{101, 55, 31}, +{135, 55, 21}, +{158, 55, 40}, +{204, 55, 32}, + +{12, 97, 31}, +{48, 97, 31}, +{82, 97, 30}, +{118, 97, 30}, +{153, 97, 30}, +{185, 97, 25}, +{213, 97, 30}, + +{11, 139, 32}, +{42, 139, 51}, +{93, 139, 32}, +{126, 139, 31}, +{158, 139, 25}, +}; + +#define PROPB_GAP_WIDTH 4 +#define PROPB_SPACE_WIDTH 12 +#define PROPB_HEIGHT 36 + +/* +================= +UI_DrawBannerString +================= +*/ +static void UI_DrawBannerString2( int x, int y, const char* str, vec4_t color ) +{ + const char* s; + unsigned char ch; // bk001204 : array subscript + float ax; + float ay; + float aw; + float ah; + float frow; + float fcol; + float fwidth; + float fheight; + + // draw the colored text + trap_R_SetColor( color ); + + ax = x * cgs.screenXScale + cgs.screenXBias; + ay = y * cgs.screenXScale; + + s = str; + while ( *s ) + { + ch = *s & 127; + if ( ch == ' ' ) { + ax += ((float)PROPB_SPACE_WIDTH + (float)PROPB_GAP_WIDTH)* cgs.screenXScale; + } + else if ( ch >= 'A' && ch <= 'Z' ) { + ch -= 'A'; + fcol = (float)propMapB[ch][0] / 256.0f; + frow = (float)propMapB[ch][1] / 256.0f; + fwidth = (float)propMapB[ch][2] / 256.0f; + fheight = (float)PROPB_HEIGHT / 256.0f; + aw = (float)propMapB[ch][2] * cgs.screenXScale; + ah = (float)PROPB_HEIGHT * cgs.screenXScale; + trap_R_DrawStretchPic( ax, ay, aw, ah, fcol, frow, fcol+fwidth, frow+fheight, cgs.media.charsetPropB ); + ax += (aw + (float)PROPB_GAP_WIDTH * cgs.screenXScale); + } + s++; + } + + trap_R_SetColor( NULL ); +} + +void UI_DrawBannerString( int x, int y, const char* str, int style, vec4_t color ) { + const char * s; + int ch; + int width; + vec4_t drawcolor; + + // find the width of the drawn text + s = str; + width = 0; + while ( *s ) { + ch = *s; + if ( ch == ' ' ) { + width += PROPB_SPACE_WIDTH; + } + else if ( ch >= 'A' && ch <= 'Z' ) { + width += propMapB[ch - 'A'][2] + PROPB_GAP_WIDTH; + } + s++; + } + width -= PROPB_GAP_WIDTH; + + switch( style & UI_FORMATMASK ) { + case UI_CENTER: + x -= width / 2; + break; + + case UI_RIGHT: + x -= width; + break; + + case UI_LEFT: + default: + break; + } + + if ( style & UI_DROPSHADOW ) { + drawcolor[0] = drawcolor[1] = drawcolor[2] = 0; + drawcolor[3] = color[3]; + UI_DrawBannerString2( x+2, y+2, str, drawcolor ); + } + + UI_DrawBannerString2( x, y, str, color ); +} + + +int UI_ProportionalStringWidth( const char* str ) { + const char * s; + int ch; + int charWidth; + int width; + + s = str; + width = 0; + while ( *s ) { + ch = *s & 127; + charWidth = propMap[ch][2]; + if ( charWidth != -1 ) { + width += charWidth; + width += PROP_GAP_WIDTH; + } + s++; + } + + width -= PROP_GAP_WIDTH; + return width; +} + +static void UI_DrawProportionalString2( int x, int y, const char* str, vec4_t color, float sizeScale, qhandle_t charset ) +{ + const char* s; + unsigned char ch; // bk001204 - unsigned + float ax; + float ay; + float aw; + float ah; + float frow; + float fcol; + float fwidth; + float fheight; + + // draw the colored text + trap_R_SetColor( color ); + + ax = x * cgs.screenXScale + cgs.screenXBias; + ay = y * cgs.screenXScale; + + s = str; + while ( *s ) + { + ch = *s & 127; + if ( ch == ' ' ) { + aw = (float)PROP_SPACE_WIDTH * cgs.screenXScale * sizeScale; + } else if ( propMap[ch][2] != -1 ) { + fcol = (float)propMap[ch][0] / 256.0f; + frow = (float)propMap[ch][1] / 256.0f; + fwidth = (float)propMap[ch][2] / 256.0f; + fheight = (float)PROP_HEIGHT / 256.0f; + aw = (float)propMap[ch][2] * cgs.screenXScale * sizeScale; + ah = (float)PROP_HEIGHT * cgs.screenXScale * sizeScale; + trap_R_DrawStretchPic( ax, ay, aw, ah, fcol, frow, fcol+fwidth, frow+fheight, charset ); + } else { + aw = 0; + } + + ax += (aw + (float)PROP_GAP_WIDTH * cgs.screenXScale * sizeScale); + s++; + } + + trap_R_SetColor( NULL ); +} + +/* +================= +UI_ProportionalSizeScale +================= +*/ +float UI_ProportionalSizeScale( int style ) { + if( style & UI_SMALLFONT ) { + return 0.75; + } + + return 1.00; +} + + +/* +================= +UI_DrawProportionalString +================= +*/ +void UI_DrawProportionalString( int x, int y, const char* str, int style, vec4_t color ) { + vec4_t drawcolor; + int width; + float sizeScale; + + sizeScale = UI_ProportionalSizeScale( style ); + + switch( style & UI_FORMATMASK ) { + case UI_CENTER: + width = UI_ProportionalStringWidth( str ) * sizeScale; + x -= width / 2; + break; + + case UI_RIGHT: + width = UI_ProportionalStringWidth( str ) * sizeScale; + x -= width; + break; + + case UI_LEFT: + default: + break; + } + + if ( style & UI_DROPSHADOW ) { + drawcolor[0] = drawcolor[1] = drawcolor[2] = 0; + drawcolor[3] = color[3]; + UI_DrawProportionalString2( x+2, y+2, str, drawcolor, sizeScale, cgs.media.charsetProp ); + } + + if ( style & UI_INVERSE ) { + drawcolor[0] = color[0] * 0.8; + drawcolor[1] = color[1] * 0.8; + drawcolor[2] = color[2] * 0.8; + drawcolor[3] = color[3]; + UI_DrawProportionalString2( x, y, str, drawcolor, sizeScale, cgs.media.charsetProp ); + return; + } + + if ( style & UI_PULSE ) { + drawcolor[0] = color[0] * 0.8; + drawcolor[1] = color[1] * 0.8; + drawcolor[2] = color[2] * 0.8; + drawcolor[3] = color[3]; + UI_DrawProportionalString2( x, y, str, color, sizeScale, cgs.media.charsetProp ); + + drawcolor[0] = color[0]; + drawcolor[1] = color[1]; + drawcolor[2] = color[2]; + drawcolor[3] = 0.5 + 0.5 * sin( cg.time / PULSE_DIVISOR ); + UI_DrawProportionalString2( x, y, str, drawcolor, sizeScale, cgs.media.charsetPropGlow ); + return; + } + + UI_DrawProportionalString2( x, y, str, color, sizeScale, cgs.media.charsetProp ); +} +#endif // Q3STATIC diff --git a/code/cgame/cg_effects.c b/code/cgame/cg_effects.c index 1ea447d..a29b856 100755 --- a/code/cgame/cg_effects.c +++ b/code/cgame/cg_effects.c @@ -1,718 +1,718 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// cg_effects.c -- these functions generate localentities, usually as a result -// of event processing - -#include "cg_local.h" - - -/* -================== -CG_BubbleTrail - -Bullets shot underwater -================== -*/ -void CG_BubbleTrail( vec3_t start, vec3_t end, float spacing ) { - vec3_t move; - vec3_t vec; - float len; - int i; - - if ( cg_noProjectileTrail.integer ) { - return; - } - - VectorCopy (start, move); - VectorSubtract (end, start, vec); - len = VectorNormalize (vec); - - // advance a random amount first - i = rand() % (int)spacing; - VectorMA( move, i, vec, move ); - - VectorScale (vec, spacing, vec); - - for ( ; i < len; i += spacing ) { - localEntity_t *le; - refEntity_t *re; - - le = CG_AllocLocalEntity(); - le->leFlags = LEF_PUFF_DONT_SCALE; - le->leType = LE_MOVE_SCALE_FADE; - le->startTime = cg.time; - le->endTime = cg.time + 1000 + random() * 250; - le->lifeRate = 1.0 / ( le->endTime - le->startTime ); - - re = &le->refEntity; - re->shaderTime = cg.time / 1000.0f; - - re->reType = RT_SPRITE; - re->rotation = 0; - re->radius = 3; - re->customShader = cgs.media.waterBubbleShader; - re->shaderRGBA[0] = 0xff; - re->shaderRGBA[1] = 0xff; - re->shaderRGBA[2] = 0xff; - re->shaderRGBA[3] = 0xff; - - le->color[3] = 1.0; - - le->pos.trType = TR_LINEAR; - le->pos.trTime = cg.time; - VectorCopy( move, le->pos.trBase ); - le->pos.trDelta[0] = crandom()*5; - le->pos.trDelta[1] = crandom()*5; - le->pos.trDelta[2] = crandom()*5 + 6; - - VectorAdd (move, vec, move); - } -} - -/* -===================== -CG_SmokePuff - -Adds a smoke puff or blood trail localEntity. -===================== -*/ -localEntity_t *CG_SmokePuff( const vec3_t p, const vec3_t vel, - float radius, - float r, float g, float b, float a, - float duration, - int startTime, - int fadeInTime, - int leFlags, - qhandle_t hShader ) { - static int seed = 0x92; - localEntity_t *le; - refEntity_t *re; -// int fadeInTime = startTime + duration / 2; - - le = CG_AllocLocalEntity(); - le->leFlags = leFlags; - le->radius = radius; - - re = &le->refEntity; - re->rotation = Q_random( &seed ) * 360; - re->radius = radius; - re->shaderTime = startTime / 1000.0f; - - le->leType = LE_MOVE_SCALE_FADE; - le->startTime = startTime; - le->fadeInTime = fadeInTime; - le->endTime = startTime + duration; - if ( fadeInTime > startTime ) { - le->lifeRate = 1.0 / ( le->endTime - le->fadeInTime ); - } - else { - le->lifeRate = 1.0 / ( le->endTime - le->startTime ); - } - le->color[0] = r; - le->color[1] = g; - le->color[2] = b; - le->color[3] = a; - - - le->pos.trType = TR_LINEAR; - le->pos.trTime = startTime; - VectorCopy( vel, le->pos.trDelta ); - VectorCopy( p, le->pos.trBase ); - - VectorCopy( p, re->origin ); - re->customShader = hShader; - - // rage pro can't alpha fade, so use a different shader - if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) { - re->customShader = cgs.media.smokePuffRageProShader; - re->shaderRGBA[0] = 0xff; - re->shaderRGBA[1] = 0xff; - re->shaderRGBA[2] = 0xff; - re->shaderRGBA[3] = 0xff; - } else { - re->shaderRGBA[0] = le->color[0] * 0xff; - re->shaderRGBA[1] = le->color[1] * 0xff; - re->shaderRGBA[2] = le->color[2] * 0xff; - re->shaderRGBA[3] = 0xff; - } - - re->reType = RT_SPRITE; - re->radius = le->radius; - - return le; -} - -/* -================== -CG_SpawnEffect - -Player teleporting in or out -================== -*/ -void CG_SpawnEffect( vec3_t org ) { - localEntity_t *le; - refEntity_t *re; - - le = CG_AllocLocalEntity(); - le->leFlags = 0; - le->leType = LE_FADE_RGB; - le->startTime = cg.time; - le->endTime = cg.time + 500; - le->lifeRate = 1.0 / ( le->endTime - le->startTime ); - - le->color[0] = le->color[1] = le->color[2] = le->color[3] = 1.0; - - re = &le->refEntity; - - re->reType = RT_MODEL; - re->shaderTime = cg.time / 1000.0f; - -#ifndef MISSIONPACK - re->customShader = cgs.media.teleportEffectShader; -#endif - re->hModel = cgs.media.teleportEffectModel; - AxisClear( re->axis ); - - VectorCopy( org, re->origin ); -#ifdef MISSIONPACK - re->origin[2] += 16; -#else - re->origin[2] -= 24; -#endif -} - - -#ifdef MISSIONPACK -/* -=============== -CG_LightningBoltBeam -=============== -*/ -void CG_LightningBoltBeam( vec3_t start, vec3_t end ) { - localEntity_t *le; - refEntity_t *beam; - - le = CG_AllocLocalEntity(); - le->leFlags = 0; - le->leType = LE_SHOWREFENTITY; - le->startTime = cg.time; - le->endTime = cg.time + 50; - - beam = &le->refEntity; - - VectorCopy( start, beam->origin ); - // this is the end point - VectorCopy( end, beam->oldorigin ); - - beam->reType = RT_LIGHTNING; - beam->customShader = cgs.media.lightningShader; -} - -/* -================== -CG_KamikazeEffect -================== -*/ -void CG_KamikazeEffect( vec3_t org ) { - localEntity_t *le; - refEntity_t *re; - - le = CG_AllocLocalEntity(); - le->leFlags = 0; - le->leType = LE_KAMIKAZE; - le->startTime = cg.time; - le->endTime = cg.time + 3000;//2250; - le->lifeRate = 1.0 / ( le->endTime - le->startTime ); - - le->color[0] = le->color[1] = le->color[2] = le->color[3] = 1.0; - - VectorClear(le->angles.trBase); - - re = &le->refEntity; - - re->reType = RT_MODEL; - re->shaderTime = cg.time / 1000.0f; - - re->hModel = cgs.media.kamikazeEffectModel; - - VectorCopy( org, re->origin ); - -} - -/* -================== -CG_ObeliskExplode -================== -*/ -void CG_ObeliskExplode( vec3_t org, int entityNum ) { - localEntity_t *le; - vec3_t origin; - - // create an explosion - VectorCopy( org, origin ); - origin[2] += 64; - le = CG_MakeExplosion( origin, vec3_origin, - cgs.media.dishFlashModel, - cgs.media.rocketExplosionShader, - 600, qtrue ); - le->light = 300; - le->lightColor[0] = 1; - le->lightColor[1] = 0.75; - le->lightColor[2] = 0.0; -} - -/* -================== -CG_ObeliskPain -================== -*/ -void CG_ObeliskPain( vec3_t org ) { - float r; - sfxHandle_t sfx; - - // hit sound - r = rand() & 3; - if ( r < 2 ) { - sfx = cgs.media.obeliskHitSound1; - } else if ( r == 2 ) { - sfx = cgs.media.obeliskHitSound2; - } else { - sfx = cgs.media.obeliskHitSound3; - } - trap_S_StartSound ( org, ENTITYNUM_NONE, CHAN_BODY, sfx ); -} - - -/* -================== -CG_InvulnerabilityImpact -================== -*/ -void CG_InvulnerabilityImpact( vec3_t org, vec3_t angles ) { - localEntity_t *le; - refEntity_t *re; - int r; - sfxHandle_t sfx; - - le = CG_AllocLocalEntity(); - le->leFlags = 0; - le->leType = LE_INVULIMPACT; - le->startTime = cg.time; - le->endTime = cg.time + 1000; - le->lifeRate = 1.0 / ( le->endTime - le->startTime ); - - le->color[0] = le->color[1] = le->color[2] = le->color[3] = 1.0; - - re = &le->refEntity; - - re->reType = RT_MODEL; - re->shaderTime = cg.time / 1000.0f; - - re->hModel = cgs.media.invulnerabilityImpactModel; - - VectorCopy( org, re->origin ); - AnglesToAxis( angles, re->axis ); - - r = rand() & 3; - if ( r < 2 ) { - sfx = cgs.media.invulnerabilityImpactSound1; - } else if ( r == 2 ) { - sfx = cgs.media.invulnerabilityImpactSound2; - } else { - sfx = cgs.media.invulnerabilityImpactSound3; - } - trap_S_StartSound (org, ENTITYNUM_NONE, CHAN_BODY, sfx ); -} - -/* -================== -CG_InvulnerabilityJuiced -================== -*/ -void CG_InvulnerabilityJuiced( vec3_t org ) { - localEntity_t *le; - refEntity_t *re; - vec3_t angles; - - le = CG_AllocLocalEntity(); - le->leFlags = 0; - le->leType = LE_INVULJUICED; - le->startTime = cg.time; - le->endTime = cg.time + 10000; - le->lifeRate = 1.0 / ( le->endTime - le->startTime ); - - le->color[0] = le->color[1] = le->color[2] = le->color[3] = 1.0; - - re = &le->refEntity; - - re->reType = RT_MODEL; - re->shaderTime = cg.time / 1000.0f; - - re->hModel = cgs.media.invulnerabilityJuicedModel; - - VectorCopy( org, re->origin ); - VectorClear(angles); - AnglesToAxis( angles, re->axis ); - - trap_S_StartSound (org, ENTITYNUM_NONE, CHAN_BODY, cgs.media.invulnerabilityJuicedSound ); -} - -#endif - -/* -================== -CG_ScorePlum -================== -*/ -void CG_ScorePlum( int client, vec3_t org, int score ) { - localEntity_t *le; - refEntity_t *re; - vec3_t angles; - static vec3_t lastPos; - - // only visualize for the client that scored - if (client != cg.predictedPlayerState.clientNum || cg_scorePlum.integer == 0) { - return; - } - - le = CG_AllocLocalEntity(); - le->leFlags = 0; - le->leType = LE_SCOREPLUM; - le->startTime = cg.time; - le->endTime = cg.time + 4000; - le->lifeRate = 1.0 / ( le->endTime - le->startTime ); - - - le->color[0] = le->color[1] = le->color[2] = le->color[3] = 1.0; - le->radius = score; - - VectorCopy( org, le->pos.trBase ); - if (org[2] >= lastPos[2] - 20 && org[2] <= lastPos[2] + 20) { - le->pos.trBase[2] -= 20; - } - - //CG_Printf( "Plum origin %i %i %i -- %i\n", (int)org[0], (int)org[1], (int)org[2], (int)Distance(org, lastPos)); - VectorCopy(org, lastPos); - - - re = &le->refEntity; - - re->reType = RT_SPRITE; - re->radius = 16; - - VectorClear(angles); - AnglesToAxis( angles, re->axis ); -} - - -/* -==================== -CG_MakeExplosion -==================== -*/ -localEntity_t *CG_MakeExplosion( vec3_t origin, vec3_t dir, - qhandle_t hModel, qhandle_t shader, - int msec, qboolean isSprite ) { - float ang; - localEntity_t *ex; - int offset; - vec3_t tmpVec, newOrigin; - - if ( msec <= 0 ) { - CG_Error( "CG_MakeExplosion: msec = %i", msec ); - } - - // skew the time a bit so they aren't all in sync - offset = rand() & 63; - - ex = CG_AllocLocalEntity(); - if ( isSprite ) { - ex->leType = LE_SPRITE_EXPLOSION; - - // randomly rotate sprite orientation - ex->refEntity.rotation = rand() % 360; - VectorScale( dir, 16, tmpVec ); - VectorAdd( tmpVec, origin, newOrigin ); - } else { - ex->leType = LE_EXPLOSION; - VectorCopy( origin, newOrigin ); - - // set axis with random rotate - if ( !dir ) { - AxisClear( ex->refEntity.axis ); - } else { - ang = rand() % 360; - VectorCopy( dir, ex->refEntity.axis[0] ); - RotateAroundDirection( ex->refEntity.axis, ang ); - } - } - - ex->startTime = cg.time - offset; - ex->endTime = ex->startTime + msec; - - // bias the time so all shader effects start correctly - ex->refEntity.shaderTime = ex->startTime / 1000.0f; - - ex->refEntity.hModel = hModel; - ex->refEntity.customShader = shader; - - // set origin - VectorCopy( newOrigin, ex->refEntity.origin ); - VectorCopy( newOrigin, ex->refEntity.oldorigin ); - - ex->color[0] = ex->color[1] = ex->color[2] = 1.0; - - return ex; -} - - -/* -================= -CG_Bleed - -This is the spurt of blood when a character gets hit -================= -*/ -void CG_Bleed( vec3_t origin, int entityNum ) { - localEntity_t *ex; - - if ( !cg_blood.integer ) { - return; - } - - ex = CG_AllocLocalEntity(); - ex->leType = LE_EXPLOSION; - - ex->startTime = cg.time; - ex->endTime = ex->startTime + 500; - - VectorCopy ( origin, ex->refEntity.origin); - ex->refEntity.reType = RT_SPRITE; - ex->refEntity.rotation = rand() % 360; - ex->refEntity.radius = 24; - - ex->refEntity.customShader = cgs.media.bloodExplosionShader; - - // don't show player's own blood in view - if ( entityNum == cg.snap->ps.clientNum ) { - ex->refEntity.renderfx |= RF_THIRD_PERSON; - } -} - - - -/* -================== -CG_LaunchGib -================== -*/ -void CG_LaunchGib( vec3_t origin, vec3_t velocity, qhandle_t hModel ) { - localEntity_t *le; - refEntity_t *re; - - le = CG_AllocLocalEntity(); - re = &le->refEntity; - - le->leType = LE_FRAGMENT; - le->startTime = cg.time; - le->endTime = le->startTime + 5000 + random() * 3000; - - VectorCopy( origin, re->origin ); - AxisCopy( axisDefault, re->axis ); - re->hModel = hModel; - - le->pos.trType = TR_GRAVITY; - VectorCopy( origin, le->pos.trBase ); - VectorCopy( velocity, le->pos.trDelta ); - le->pos.trTime = cg.time; - - le->bounceFactor = 0.6f; - - le->leBounceSoundType = LEBS_BLOOD; - le->leMarkType = LEMT_BLOOD; -} - -/* -=================== -CG_GibPlayer - -Generated a bunch of gibs launching out from the bodies location -=================== -*/ -#define GIB_VELOCITY 250 -#define GIB_JUMP 250 -void CG_GibPlayer( vec3_t playerOrigin ) { - vec3_t origin, velocity; - - if ( !cg_blood.integer ) { - return; - } - - VectorCopy( playerOrigin, origin ); - velocity[0] = crandom()*GIB_VELOCITY; - velocity[1] = crandom()*GIB_VELOCITY; - velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - if ( rand() & 1 ) { - CG_LaunchGib( origin, velocity, cgs.media.gibSkull ); - } else { - CG_LaunchGib( origin, velocity, cgs.media.gibBrain ); - } - - // allow gibs to be turned off for speed - if ( !cg_gibs.integer ) { - return; - } - - VectorCopy( playerOrigin, origin ); - velocity[0] = crandom()*GIB_VELOCITY; - velocity[1] = crandom()*GIB_VELOCITY; - velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, velocity, cgs.media.gibAbdomen ); - - VectorCopy( playerOrigin, origin ); - velocity[0] = crandom()*GIB_VELOCITY; - velocity[1] = crandom()*GIB_VELOCITY; - velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, velocity, cgs.media.gibArm ); - - VectorCopy( playerOrigin, origin ); - velocity[0] = crandom()*GIB_VELOCITY; - velocity[1] = crandom()*GIB_VELOCITY; - velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, velocity, cgs.media.gibChest ); - - VectorCopy( playerOrigin, origin ); - velocity[0] = crandom()*GIB_VELOCITY; - velocity[1] = crandom()*GIB_VELOCITY; - velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, velocity, cgs.media.gibFist ); - - VectorCopy( playerOrigin, origin ); - velocity[0] = crandom()*GIB_VELOCITY; - velocity[1] = crandom()*GIB_VELOCITY; - velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, velocity, cgs.media.gibFoot ); - - VectorCopy( playerOrigin, origin ); - velocity[0] = crandom()*GIB_VELOCITY; - velocity[1] = crandom()*GIB_VELOCITY; - velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, velocity, cgs.media.gibForearm ); - - VectorCopy( playerOrigin, origin ); - velocity[0] = crandom()*GIB_VELOCITY; - velocity[1] = crandom()*GIB_VELOCITY; - velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, velocity, cgs.media.gibIntestine ); - - VectorCopy( playerOrigin, origin ); - velocity[0] = crandom()*GIB_VELOCITY; - velocity[1] = crandom()*GIB_VELOCITY; - velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, velocity, cgs.media.gibLeg ); - - VectorCopy( playerOrigin, origin ); - velocity[0] = crandom()*GIB_VELOCITY; - velocity[1] = crandom()*GIB_VELOCITY; - velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, velocity, cgs.media.gibLeg ); -} - -/* -================== -CG_LaunchGib -================== -*/ -void CG_LaunchExplode( vec3_t origin, vec3_t velocity, qhandle_t hModel ) { - localEntity_t *le; - refEntity_t *re; - - le = CG_AllocLocalEntity(); - re = &le->refEntity; - - le->leType = LE_FRAGMENT; - le->startTime = cg.time; - le->endTime = le->startTime + 10000 + random() * 6000; - - VectorCopy( origin, re->origin ); - AxisCopy( axisDefault, re->axis ); - re->hModel = hModel; - - le->pos.trType = TR_GRAVITY; - VectorCopy( origin, le->pos.trBase ); - VectorCopy( velocity, le->pos.trDelta ); - le->pos.trTime = cg.time; - - le->bounceFactor = 0.1f; - - le->leBounceSoundType = LEBS_BRASS; - le->leMarkType = LEMT_NONE; -} - -#define EXP_VELOCITY 100 -#define EXP_JUMP 150 -/* -=================== -CG_GibPlayer - -Generated a bunch of gibs launching out from the bodies location -=================== -*/ -void CG_BigExplode( vec3_t playerOrigin ) { - vec3_t origin, velocity; - - if ( !cg_blood.integer ) { - return; - } - - VectorCopy( playerOrigin, origin ); - velocity[0] = crandom()*EXP_VELOCITY; - velocity[1] = crandom()*EXP_VELOCITY; - velocity[2] = EXP_JUMP + crandom()*EXP_VELOCITY; - CG_LaunchExplode( origin, velocity, cgs.media.smoke2 ); - - VectorCopy( playerOrigin, origin ); - velocity[0] = crandom()*EXP_VELOCITY; - velocity[1] = crandom()*EXP_VELOCITY; - velocity[2] = EXP_JUMP + crandom()*EXP_VELOCITY; - CG_LaunchExplode( origin, velocity, cgs.media.smoke2 ); - - VectorCopy( playerOrigin, origin ); - velocity[0] = crandom()*EXP_VELOCITY*1.5; - velocity[1] = crandom()*EXP_VELOCITY*1.5; - velocity[2] = EXP_JUMP + crandom()*EXP_VELOCITY; - CG_LaunchExplode( origin, velocity, cgs.media.smoke2 ); - - VectorCopy( playerOrigin, origin ); - velocity[0] = crandom()*EXP_VELOCITY*2.0; - velocity[1] = crandom()*EXP_VELOCITY*2.0; - velocity[2] = EXP_JUMP + crandom()*EXP_VELOCITY; - CG_LaunchExplode( origin, velocity, cgs.media.smoke2 ); - - VectorCopy( playerOrigin, origin ); - velocity[0] = crandom()*EXP_VELOCITY*2.5; - velocity[1] = crandom()*EXP_VELOCITY*2.5; - velocity[2] = EXP_JUMP + crandom()*EXP_VELOCITY; - CG_LaunchExplode( origin, velocity, cgs.media.smoke2 ); -} - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// cg_effects.c -- these functions generate localentities, usually as a result +// of event processing + +#include "cg_local.h" + + +/* +================== +CG_BubbleTrail + +Bullets shot underwater +================== +*/ +void CG_BubbleTrail( vec3_t start, vec3_t end, float spacing ) { + vec3_t move; + vec3_t vec; + float len; + int i; + + if ( cg_noProjectileTrail.integer ) { + return; + } + + VectorCopy (start, move); + VectorSubtract (end, start, vec); + len = VectorNormalize (vec); + + // advance a random amount first + i = rand() % (int)spacing; + VectorMA( move, i, vec, move ); + + VectorScale (vec, spacing, vec); + + for ( ; i < len; i += spacing ) { + localEntity_t *le; + refEntity_t *re; + + le = CG_AllocLocalEntity(); + le->leFlags = LEF_PUFF_DONT_SCALE; + le->leType = LE_MOVE_SCALE_FADE; + le->startTime = cg.time; + le->endTime = cg.time + 1000 + random() * 250; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + re = &le->refEntity; + re->shaderTime = cg.time / 1000.0f; + + re->reType = RT_SPRITE; + re->rotation = 0; + re->radius = 3; + re->customShader = cgs.media.waterBubbleShader; + re->shaderRGBA[0] = 0xff; + re->shaderRGBA[1] = 0xff; + re->shaderRGBA[2] = 0xff; + re->shaderRGBA[3] = 0xff; + + le->color[3] = 1.0; + + le->pos.trType = TR_LINEAR; + le->pos.trTime = cg.time; + VectorCopy( move, le->pos.trBase ); + le->pos.trDelta[0] = crandom()*5; + le->pos.trDelta[1] = crandom()*5; + le->pos.trDelta[2] = crandom()*5 + 6; + + VectorAdd (move, vec, move); + } +} + +/* +===================== +CG_SmokePuff + +Adds a smoke puff or blood trail localEntity. +===================== +*/ +localEntity_t *CG_SmokePuff( const vec3_t p, const vec3_t vel, + float radius, + float r, float g, float b, float a, + float duration, + int startTime, + int fadeInTime, + int leFlags, + qhandle_t hShader ) { + static int seed = 0x92; + localEntity_t *le; + refEntity_t *re; +// int fadeInTime = startTime + duration / 2; + + le = CG_AllocLocalEntity(); + le->leFlags = leFlags; + le->radius = radius; + + re = &le->refEntity; + re->rotation = Q_random( &seed ) * 360; + re->radius = radius; + re->shaderTime = startTime / 1000.0f; + + le->leType = LE_MOVE_SCALE_FADE; + le->startTime = startTime; + le->fadeInTime = fadeInTime; + le->endTime = startTime + duration; + if ( fadeInTime > startTime ) { + le->lifeRate = 1.0 / ( le->endTime - le->fadeInTime ); + } + else { + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + } + le->color[0] = r; + le->color[1] = g; + le->color[2] = b; + le->color[3] = a; + + + le->pos.trType = TR_LINEAR; + le->pos.trTime = startTime; + VectorCopy( vel, le->pos.trDelta ); + VectorCopy( p, le->pos.trBase ); + + VectorCopy( p, re->origin ); + re->customShader = hShader; + + // rage pro can't alpha fade, so use a different shader + if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) { + re->customShader = cgs.media.smokePuffRageProShader; + re->shaderRGBA[0] = 0xff; + re->shaderRGBA[1] = 0xff; + re->shaderRGBA[2] = 0xff; + re->shaderRGBA[3] = 0xff; + } else { + re->shaderRGBA[0] = le->color[0] * 0xff; + re->shaderRGBA[1] = le->color[1] * 0xff; + re->shaderRGBA[2] = le->color[2] * 0xff; + re->shaderRGBA[3] = 0xff; + } + + re->reType = RT_SPRITE; + re->radius = le->radius; + + return le; +} + +/* +================== +CG_SpawnEffect + +Player teleporting in or out +================== +*/ +void CG_SpawnEffect( vec3_t org ) { + localEntity_t *le; + refEntity_t *re; + + le = CG_AllocLocalEntity(); + le->leFlags = 0; + le->leType = LE_FADE_RGB; + le->startTime = cg.time; + le->endTime = cg.time + 500; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + le->color[0] = le->color[1] = le->color[2] = le->color[3] = 1.0; + + re = &le->refEntity; + + re->reType = RT_MODEL; + re->shaderTime = cg.time / 1000.0f; + +#ifndef MISSIONPACK + re->customShader = cgs.media.teleportEffectShader; +#endif + re->hModel = cgs.media.teleportEffectModel; + AxisClear( re->axis ); + + VectorCopy( org, re->origin ); +#ifdef MISSIONPACK + re->origin[2] += 16; +#else + re->origin[2] -= 24; +#endif +} + + +#ifdef MISSIONPACK +/* +=============== +CG_LightningBoltBeam +=============== +*/ +void CG_LightningBoltBeam( vec3_t start, vec3_t end ) { + localEntity_t *le; + refEntity_t *beam; + + le = CG_AllocLocalEntity(); + le->leFlags = 0; + le->leType = LE_SHOWREFENTITY; + le->startTime = cg.time; + le->endTime = cg.time + 50; + + beam = &le->refEntity; + + VectorCopy( start, beam->origin ); + // this is the end point + VectorCopy( end, beam->oldorigin ); + + beam->reType = RT_LIGHTNING; + beam->customShader = cgs.media.lightningShader; +} + +/* +================== +CG_KamikazeEffect +================== +*/ +void CG_KamikazeEffect( vec3_t org ) { + localEntity_t *le; + refEntity_t *re; + + le = CG_AllocLocalEntity(); + le->leFlags = 0; + le->leType = LE_KAMIKAZE; + le->startTime = cg.time; + le->endTime = cg.time + 3000;//2250; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + le->color[0] = le->color[1] = le->color[2] = le->color[3] = 1.0; + + VectorClear(le->angles.trBase); + + re = &le->refEntity; + + re->reType = RT_MODEL; + re->shaderTime = cg.time / 1000.0f; + + re->hModel = cgs.media.kamikazeEffectModel; + + VectorCopy( org, re->origin ); + +} + +/* +================== +CG_ObeliskExplode +================== +*/ +void CG_ObeliskExplode( vec3_t org, int entityNum ) { + localEntity_t *le; + vec3_t origin; + + // create an explosion + VectorCopy( org, origin ); + origin[2] += 64; + le = CG_MakeExplosion( origin, vec3_origin, + cgs.media.dishFlashModel, + cgs.media.rocketExplosionShader, + 600, qtrue ); + le->light = 300; + le->lightColor[0] = 1; + le->lightColor[1] = 0.75; + le->lightColor[2] = 0.0; +} + +/* +================== +CG_ObeliskPain +================== +*/ +void CG_ObeliskPain( vec3_t org ) { + float r; + sfxHandle_t sfx; + + // hit sound + r = rand() & 3; + if ( r < 2 ) { + sfx = cgs.media.obeliskHitSound1; + } else if ( r == 2 ) { + sfx = cgs.media.obeliskHitSound2; + } else { + sfx = cgs.media.obeliskHitSound3; + } + trap_S_StartSound ( org, ENTITYNUM_NONE, CHAN_BODY, sfx ); +} + + +/* +================== +CG_InvulnerabilityImpact +================== +*/ +void CG_InvulnerabilityImpact( vec3_t org, vec3_t angles ) { + localEntity_t *le; + refEntity_t *re; + int r; + sfxHandle_t sfx; + + le = CG_AllocLocalEntity(); + le->leFlags = 0; + le->leType = LE_INVULIMPACT; + le->startTime = cg.time; + le->endTime = cg.time + 1000; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + le->color[0] = le->color[1] = le->color[2] = le->color[3] = 1.0; + + re = &le->refEntity; + + re->reType = RT_MODEL; + re->shaderTime = cg.time / 1000.0f; + + re->hModel = cgs.media.invulnerabilityImpactModel; + + VectorCopy( org, re->origin ); + AnglesToAxis( angles, re->axis ); + + r = rand() & 3; + if ( r < 2 ) { + sfx = cgs.media.invulnerabilityImpactSound1; + } else if ( r == 2 ) { + sfx = cgs.media.invulnerabilityImpactSound2; + } else { + sfx = cgs.media.invulnerabilityImpactSound3; + } + trap_S_StartSound (org, ENTITYNUM_NONE, CHAN_BODY, sfx ); +} + +/* +================== +CG_InvulnerabilityJuiced +================== +*/ +void CG_InvulnerabilityJuiced( vec3_t org ) { + localEntity_t *le; + refEntity_t *re; + vec3_t angles; + + le = CG_AllocLocalEntity(); + le->leFlags = 0; + le->leType = LE_INVULJUICED; + le->startTime = cg.time; + le->endTime = cg.time + 10000; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + le->color[0] = le->color[1] = le->color[2] = le->color[3] = 1.0; + + re = &le->refEntity; + + re->reType = RT_MODEL; + re->shaderTime = cg.time / 1000.0f; + + re->hModel = cgs.media.invulnerabilityJuicedModel; + + VectorCopy( org, re->origin ); + VectorClear(angles); + AnglesToAxis( angles, re->axis ); + + trap_S_StartSound (org, ENTITYNUM_NONE, CHAN_BODY, cgs.media.invulnerabilityJuicedSound ); +} + +#endif + +/* +================== +CG_ScorePlum +================== +*/ +void CG_ScorePlum( int client, vec3_t org, int score ) { + localEntity_t *le; + refEntity_t *re; + vec3_t angles; + static vec3_t lastPos; + + // only visualize for the client that scored + if (client != cg.predictedPlayerState.clientNum || cg_scorePlum.integer == 0) { + return; + } + + le = CG_AllocLocalEntity(); + le->leFlags = 0; + le->leType = LE_SCOREPLUM; + le->startTime = cg.time; + le->endTime = cg.time + 4000; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + + le->color[0] = le->color[1] = le->color[2] = le->color[3] = 1.0; + le->radius = score; + + VectorCopy( org, le->pos.trBase ); + if (org[2] >= lastPos[2] - 20 && org[2] <= lastPos[2] + 20) { + le->pos.trBase[2] -= 20; + } + + //CG_Printf( "Plum origin %i %i %i -- %i\n", (int)org[0], (int)org[1], (int)org[2], (int)Distance(org, lastPos)); + VectorCopy(org, lastPos); + + + re = &le->refEntity; + + re->reType = RT_SPRITE; + re->radius = 16; + + VectorClear(angles); + AnglesToAxis( angles, re->axis ); +} + + +/* +==================== +CG_MakeExplosion +==================== +*/ +localEntity_t *CG_MakeExplosion( vec3_t origin, vec3_t dir, + qhandle_t hModel, qhandle_t shader, + int msec, qboolean isSprite ) { + float ang; + localEntity_t *ex; + int offset; + vec3_t tmpVec, newOrigin; + + if ( msec <= 0 ) { + CG_Error( "CG_MakeExplosion: msec = %i", msec ); + } + + // skew the time a bit so they aren't all in sync + offset = rand() & 63; + + ex = CG_AllocLocalEntity(); + if ( isSprite ) { + ex->leType = LE_SPRITE_EXPLOSION; + + // randomly rotate sprite orientation + ex->refEntity.rotation = rand() % 360; + VectorScale( dir, 16, tmpVec ); + VectorAdd( tmpVec, origin, newOrigin ); + } else { + ex->leType = LE_EXPLOSION; + VectorCopy( origin, newOrigin ); + + // set axis with random rotate + if ( !dir ) { + AxisClear( ex->refEntity.axis ); + } else { + ang = rand() % 360; + VectorCopy( dir, ex->refEntity.axis[0] ); + RotateAroundDirection( ex->refEntity.axis, ang ); + } + } + + ex->startTime = cg.time - offset; + ex->endTime = ex->startTime + msec; + + // bias the time so all shader effects start correctly + ex->refEntity.shaderTime = ex->startTime / 1000.0f; + + ex->refEntity.hModel = hModel; + ex->refEntity.customShader = shader; + + // set origin + VectorCopy( newOrigin, ex->refEntity.origin ); + VectorCopy( newOrigin, ex->refEntity.oldorigin ); + + ex->color[0] = ex->color[1] = ex->color[2] = 1.0; + + return ex; +} + + +/* +================= +CG_Bleed + +This is the spurt of blood when a character gets hit +================= +*/ +void CG_Bleed( vec3_t origin, int entityNum ) { + localEntity_t *ex; + + if ( !cg_blood.integer ) { + return; + } + + ex = CG_AllocLocalEntity(); + ex->leType = LE_EXPLOSION; + + ex->startTime = cg.time; + ex->endTime = ex->startTime + 500; + + VectorCopy ( origin, ex->refEntity.origin); + ex->refEntity.reType = RT_SPRITE; + ex->refEntity.rotation = rand() % 360; + ex->refEntity.radius = 24; + + ex->refEntity.customShader = cgs.media.bloodExplosionShader; + + // don't show player's own blood in view + if ( entityNum == cg.snap->ps.clientNum ) { + ex->refEntity.renderfx |= RF_THIRD_PERSON; + } +} + + + +/* +================== +CG_LaunchGib +================== +*/ +void CG_LaunchGib( vec3_t origin, vec3_t velocity, qhandle_t hModel ) { + localEntity_t *le; + refEntity_t *re; + + le = CG_AllocLocalEntity(); + re = &le->refEntity; + + le->leType = LE_FRAGMENT; + le->startTime = cg.time; + le->endTime = le->startTime + 5000 + random() * 3000; + + VectorCopy( origin, re->origin ); + AxisCopy( axisDefault, re->axis ); + re->hModel = hModel; + + le->pos.trType = TR_GRAVITY; + VectorCopy( origin, le->pos.trBase ); + VectorCopy( velocity, le->pos.trDelta ); + le->pos.trTime = cg.time; + + le->bounceFactor = 0.6f; + + le->leBounceSoundType = LEBS_BLOOD; + le->leMarkType = LEMT_BLOOD; +} + +/* +=================== +CG_GibPlayer + +Generated a bunch of gibs launching out from the bodies location +=================== +*/ +#define GIB_VELOCITY 250 +#define GIB_JUMP 250 +void CG_GibPlayer( vec3_t playerOrigin ) { + vec3_t origin, velocity; + + if ( !cg_blood.integer ) { + return; + } + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + if ( rand() & 1 ) { + CG_LaunchGib( origin, velocity, cgs.media.gibSkull ); + } else { + CG_LaunchGib( origin, velocity, cgs.media.gibBrain ); + } + + // allow gibs to be turned off for speed + if ( !cg_gibs.integer ) { + return; + } + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGib( origin, velocity, cgs.media.gibAbdomen ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGib( origin, velocity, cgs.media.gibArm ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGib( origin, velocity, cgs.media.gibChest ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGib( origin, velocity, cgs.media.gibFist ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGib( origin, velocity, cgs.media.gibFoot ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGib( origin, velocity, cgs.media.gibForearm ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGib( origin, velocity, cgs.media.gibIntestine ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGib( origin, velocity, cgs.media.gibLeg ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGib( origin, velocity, cgs.media.gibLeg ); +} + +/* +================== +CG_LaunchGib +================== +*/ +void CG_LaunchExplode( vec3_t origin, vec3_t velocity, qhandle_t hModel ) { + localEntity_t *le; + refEntity_t *re; + + le = CG_AllocLocalEntity(); + re = &le->refEntity; + + le->leType = LE_FRAGMENT; + le->startTime = cg.time; + le->endTime = le->startTime + 10000 + random() * 6000; + + VectorCopy( origin, re->origin ); + AxisCopy( axisDefault, re->axis ); + re->hModel = hModel; + + le->pos.trType = TR_GRAVITY; + VectorCopy( origin, le->pos.trBase ); + VectorCopy( velocity, le->pos.trDelta ); + le->pos.trTime = cg.time; + + le->bounceFactor = 0.1f; + + le->leBounceSoundType = LEBS_BRASS; + le->leMarkType = LEMT_NONE; +} + +#define EXP_VELOCITY 100 +#define EXP_JUMP 150 +/* +=================== +CG_GibPlayer + +Generated a bunch of gibs launching out from the bodies location +=================== +*/ +void CG_BigExplode( vec3_t playerOrigin ) { + vec3_t origin, velocity; + + if ( !cg_blood.integer ) { + return; + } + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*EXP_VELOCITY; + velocity[1] = crandom()*EXP_VELOCITY; + velocity[2] = EXP_JUMP + crandom()*EXP_VELOCITY; + CG_LaunchExplode( origin, velocity, cgs.media.smoke2 ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*EXP_VELOCITY; + velocity[1] = crandom()*EXP_VELOCITY; + velocity[2] = EXP_JUMP + crandom()*EXP_VELOCITY; + CG_LaunchExplode( origin, velocity, cgs.media.smoke2 ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*EXP_VELOCITY*1.5; + velocity[1] = crandom()*EXP_VELOCITY*1.5; + velocity[2] = EXP_JUMP + crandom()*EXP_VELOCITY; + CG_LaunchExplode( origin, velocity, cgs.media.smoke2 ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*EXP_VELOCITY*2.0; + velocity[1] = crandom()*EXP_VELOCITY*2.0; + velocity[2] = EXP_JUMP + crandom()*EXP_VELOCITY; + CG_LaunchExplode( origin, velocity, cgs.media.smoke2 ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*EXP_VELOCITY*2.5; + velocity[1] = crandom()*EXP_VELOCITY*2.5; + velocity[2] = EXP_JUMP + crandom()*EXP_VELOCITY; + CG_LaunchExplode( origin, velocity, cgs.media.smoke2 ); +} + diff --git a/code/cgame/cg_ents.c b/code/cgame/cg_ents.c index 5855089..aa6398f 100755 --- a/code/cgame/cg_ents.c +++ b/code/cgame/cg_ents.c @@ -1,1037 +1,1037 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// cg_ents.c -- present snapshot entities, happens every single frame - -#include "cg_local.h" - - -/* -====================== -CG_PositionEntityOnTag - -Modifies the entities position and axis by the given -tag location -====================== -*/ -void CG_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent, - qhandle_t parentModel, char *tagName ) { - int i; - orientation_t lerped; - - // lerp the tag - trap_R_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame, - 1.0 - parent->backlerp, tagName ); - - // FIXME: allow origin offsets along tag? - VectorCopy( parent->origin, entity->origin ); - for ( i = 0 ; i < 3 ; i++ ) { - VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin ); - } - - // had to cast away the const to avoid compiler problems... - MatrixMultiply( lerped.axis, ((refEntity_t *)parent)->axis, entity->axis ); - entity->backlerp = parent->backlerp; -} - - -/* -====================== -CG_PositionRotatedEntityOnTag - -Modifies the entities position and axis by the given -tag location -====================== -*/ -void CG_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent, - qhandle_t parentModel, char *tagName ) { - int i; - orientation_t lerped; - vec3_t tempAxis[3]; - -//AxisClear( entity->axis ); - // lerp the tag - trap_R_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame, - 1.0 - parent->backlerp, tagName ); - - // FIXME: allow origin offsets along tag? - VectorCopy( parent->origin, entity->origin ); - for ( i = 0 ; i < 3 ; i++ ) { - VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin ); - } - - // had to cast away the const to avoid compiler problems... - MatrixMultiply( entity->axis, lerped.axis, tempAxis ); - MatrixMultiply( tempAxis, ((refEntity_t *)parent)->axis, entity->axis ); -} - - - -/* -========================================================================== - -FUNCTIONS CALLED EACH FRAME - -========================================================================== -*/ - -/* -====================== -CG_SetEntitySoundPosition - -Also called by event processing code -====================== -*/ -void CG_SetEntitySoundPosition( centity_t *cent ) { - if ( cent->currentState.solid == SOLID_BMODEL ) { - vec3_t origin; - float *v; - - v = cgs.inlineModelMidpoints[ cent->currentState.modelindex ]; - VectorAdd( cent->lerpOrigin, v, origin ); - trap_S_UpdateEntityPosition( cent->currentState.number, origin ); - } else { - trap_S_UpdateEntityPosition( cent->currentState.number, cent->lerpOrigin ); - } -} - -/* -================== -CG_EntityEffects - -Add continuous entity effects, like local entity emission and lighting -================== -*/ -static void CG_EntityEffects( centity_t *cent ) { - - // update sound origins - CG_SetEntitySoundPosition( cent ); - - // add loop sound - if ( cent->currentState.loopSound ) { - if (cent->currentState.eType != ET_SPEAKER) { - trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, - cgs.gameSounds[ cent->currentState.loopSound ] ); - } else { - trap_S_AddRealLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, - cgs.gameSounds[ cent->currentState.loopSound ] ); - } - } - - - // constant light glow - if ( cent->currentState.constantLight ) { - int cl; - int i, r, g, b; - - cl = cent->currentState.constantLight; - r = cl & 255; - g = ( cl >> 8 ) & 255; - b = ( cl >> 16 ) & 255; - i = ( ( cl >> 24 ) & 255 ) * 4; - trap_R_AddLightToScene( cent->lerpOrigin, i, r, g, b ); - } - -} - - -/* -================== -CG_General -================== -*/ -static void CG_General( centity_t *cent ) { - refEntity_t ent; - entityState_t *s1; - - s1 = ¢->currentState; - - // if set to invisible, skip - if (!s1->modelindex) { - return; - } - - memset (&ent, 0, sizeof(ent)); - - // set frame - - ent.frame = s1->frame; - ent.oldframe = ent.frame; - ent.backlerp = 0; - - VectorCopy( cent->lerpOrigin, ent.origin); - VectorCopy( cent->lerpOrigin, ent.oldorigin); - - ent.hModel = cgs.gameModels[s1->modelindex]; - - // player model - if (s1->number == cg.snap->ps.clientNum) { - ent.renderfx |= RF_THIRD_PERSON; // only draw from mirrors - } - - // convert angles to axis - AnglesToAxis( cent->lerpAngles, ent.axis ); - - // add to refresh list - trap_R_AddRefEntityToScene (&ent); -} - -/* -================== -CG_Speaker - -Speaker entities can automatically play sounds -================== -*/ -static void CG_Speaker( centity_t *cent ) { - if ( ! cent->currentState.clientNum ) { // FIXME: use something other than clientNum... - return; // not auto triggering - } - - if ( cg.time < cent->miscTime ) { - return; - } - - trap_S_StartSound (NULL, cent->currentState.number, CHAN_ITEM, cgs.gameSounds[cent->currentState.eventParm] ); - - // ent->s.frame = ent->wait * 10; - // ent->s.clientNum = ent->random * 10; - cent->miscTime = cg.time + cent->currentState.frame * 100 + cent->currentState.clientNum * 100 * crandom(); -} - -/* -================== -CG_Item -================== -*/ -static void CG_Item( centity_t *cent ) { - refEntity_t ent; - entityState_t *es; - gitem_t *item; - int msec; - float frac; - float scale; - weaponInfo_t *wi; - - es = ¢->currentState; - if ( es->modelindex >= bg_numItems ) { - CG_Error( "Bad item index %i on entity", es->modelindex ); - } - - // if set to invisible, skip - if ( !es->modelindex || ( es->eFlags & EF_NODRAW ) ) { - return; - } - - item = &bg_itemlist[ es->modelindex ]; - if ( cg_simpleItems.integer && item->giType != IT_TEAM ) { - memset( &ent, 0, sizeof( ent ) ); - ent.reType = RT_SPRITE; - VectorCopy( cent->lerpOrigin, ent.origin ); - ent.radius = 14; - ent.customShader = cg_items[es->modelindex].icon; - ent.shaderRGBA[0] = 255; - ent.shaderRGBA[1] = 255; - ent.shaderRGBA[2] = 255; - ent.shaderRGBA[3] = 255; - trap_R_AddRefEntityToScene(&ent); - return; - } - - // items bob up and down continuously - scale = 0.005 + cent->currentState.number * 0.00001; - cent->lerpOrigin[2] += 4 + cos( ( cg.time + 1000 ) * scale ) * 4; - - memset (&ent, 0, sizeof(ent)); - - // autorotate at one of two speeds - if ( item->giType == IT_HEALTH ) { - VectorCopy( cg.autoAnglesFast, cent->lerpAngles ); - AxisCopy( cg.autoAxisFast, ent.axis ); - } else { - VectorCopy( cg.autoAngles, cent->lerpAngles ); - AxisCopy( cg.autoAxis, ent.axis ); - } - - wi = NULL; - // the weapons have their origin where they attatch to player - // models, so we need to offset them or they will rotate - // eccentricly - if ( item->giType == IT_WEAPON ) { - wi = &cg_weapons[item->giTag]; - cent->lerpOrigin[0] -= - wi->weaponMidpoint[0] * ent.axis[0][0] + - wi->weaponMidpoint[1] * ent.axis[1][0] + - wi->weaponMidpoint[2] * ent.axis[2][0]; - cent->lerpOrigin[1] -= - wi->weaponMidpoint[0] * ent.axis[0][1] + - wi->weaponMidpoint[1] * ent.axis[1][1] + - wi->weaponMidpoint[2] * ent.axis[2][1]; - cent->lerpOrigin[2] -= - wi->weaponMidpoint[0] * ent.axis[0][2] + - wi->weaponMidpoint[1] * ent.axis[1][2] + - wi->weaponMidpoint[2] * ent.axis[2][2]; - - cent->lerpOrigin[2] += 8; // an extra height boost - } - - ent.hModel = cg_items[es->modelindex].models[0]; - - VectorCopy( cent->lerpOrigin, ent.origin); - VectorCopy( cent->lerpOrigin, ent.oldorigin); - - ent.nonNormalizedAxes = qfalse; - - // if just respawned, slowly scale up - msec = cg.time - cent->miscTime; - if ( msec >= 0 && msec < ITEM_SCALEUP_TIME ) { - frac = (float)msec / ITEM_SCALEUP_TIME; - VectorScale( ent.axis[0], frac, ent.axis[0] ); - VectorScale( ent.axis[1], frac, ent.axis[1] ); - VectorScale( ent.axis[2], frac, ent.axis[2] ); - ent.nonNormalizedAxes = qtrue; - } else { - frac = 1.0; - } - - // items without glow textures need to keep a minimum light value - // so they are always visible - if ( ( item->giType == IT_WEAPON ) || - ( item->giType == IT_ARMOR ) ) { - ent.renderfx |= RF_MINLIGHT; - } - - // increase the size of the weapons when they are presented as items - if ( item->giType == IT_WEAPON ) { - VectorScale( ent.axis[0], 1.5, ent.axis[0] ); - VectorScale( ent.axis[1], 1.5, ent.axis[1] ); - VectorScale( ent.axis[2], 1.5, ent.axis[2] ); - ent.nonNormalizedAxes = qtrue; -#ifdef MISSIONPACK - trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.media.weaponHoverSound ); -#endif - } - -#ifdef MISSIONPACK - if ( item->giType == IT_HOLDABLE && item->giTag == HI_KAMIKAZE ) { - VectorScale( ent.axis[0], 2, ent.axis[0] ); - VectorScale( ent.axis[1], 2, ent.axis[1] ); - VectorScale( ent.axis[2], 2, ent.axis[2] ); - ent.nonNormalizedAxes = qtrue; - } -#endif - - // add to refresh list - trap_R_AddRefEntityToScene(&ent); - -#ifdef MISSIONPACK - if ( item->giType == IT_WEAPON && wi->barrelModel ) { - refEntity_t barrel; - - memset( &barrel, 0, sizeof( barrel ) ); - - barrel.hModel = wi->barrelModel; - - VectorCopy( ent.lightingOrigin, barrel.lightingOrigin ); - barrel.shadowPlane = ent.shadowPlane; - barrel.renderfx = ent.renderfx; - - CG_PositionRotatedEntityOnTag( &barrel, &ent, wi->weaponModel, "tag_barrel" ); - - AxisCopy( ent.axis, barrel.axis ); - barrel.nonNormalizedAxes = ent.nonNormalizedAxes; - - trap_R_AddRefEntityToScene( &barrel ); - } -#endif - - // accompanying rings / spheres for powerups - if ( !cg_simpleItems.integer ) - { - vec3_t spinAngles; - - VectorClear( spinAngles ); - - if ( item->giType == IT_HEALTH || item->giType == IT_POWERUP ) - { - if ( ( ent.hModel = cg_items[es->modelindex].models[1] ) != 0 ) - { - if ( item->giType == IT_POWERUP ) - { - ent.origin[2] += 12; - spinAngles[1] = ( cg.time & 1023 ) * 360 / -1024.0f; - } - AnglesToAxis( spinAngles, ent.axis ); - - // scale up if respawning - if ( frac != 1.0 ) { - VectorScale( ent.axis[0], frac, ent.axis[0] ); - VectorScale( ent.axis[1], frac, ent.axis[1] ); - VectorScale( ent.axis[2], frac, ent.axis[2] ); - ent.nonNormalizedAxes = qtrue; - } - trap_R_AddRefEntityToScene( &ent ); - } - } - } -} - -//============================================================================ - -/* -=============== -CG_Missile -=============== -*/ -static void CG_Missile( centity_t *cent ) { - refEntity_t ent; - entityState_t *s1; - const weaponInfo_t *weapon; -// int col; - - s1 = ¢->currentState; - if ( s1->weapon > WP_NUM_WEAPONS ) { - s1->weapon = 0; - } - weapon = &cg_weapons[s1->weapon]; - - // calculate the axis - VectorCopy( s1->angles, cent->lerpAngles); - - // add trails - if ( weapon->missileTrailFunc ) - { - weapon->missileTrailFunc( cent, weapon ); - } -/* - if ( cent->currentState.modelindex == TEAM_RED ) { - col = 1; - } - else if ( cent->currentState.modelindex == TEAM_BLUE ) { - col = 2; - } - else { - col = 0; - } - - // add dynamic light - if ( weapon->missileDlight ) { - trap_R_AddLightToScene(cent->lerpOrigin, weapon->missileDlight, - weapon->missileDlightColor[col][0], weapon->missileDlightColor[col][1], weapon->missileDlightColor[col][2] ); - } -*/ - // add dynamic light - if ( weapon->missileDlight ) { - trap_R_AddLightToScene(cent->lerpOrigin, weapon->missileDlight, - weapon->missileDlightColor[0], weapon->missileDlightColor[1], weapon->missileDlightColor[2] ); - } - - // add missile sound - if ( weapon->missileSound ) { - vec3_t velocity; - - BG_EvaluateTrajectoryDelta( ¢->currentState.pos, cg.time, velocity ); - - trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, velocity, weapon->missileSound ); - } - - // create the render entity - memset (&ent, 0, sizeof(ent)); - VectorCopy( cent->lerpOrigin, ent.origin); - VectorCopy( cent->lerpOrigin, ent.oldorigin); - - if ( cent->currentState.weapon == WP_PLASMAGUN ) { - ent.reType = RT_SPRITE; - ent.radius = 16; - ent.rotation = 0; - ent.customShader = cgs.media.plasmaBallShader; - trap_R_AddRefEntityToScene( &ent ); - return; - } - - // flicker between two skins - ent.skinNum = cg.clientFrame & 1; - ent.hModel = weapon->missileModel; - ent.renderfx = weapon->missileRenderfx | RF_NOSHADOW; - -#ifdef MISSIONPACK - if ( cent->currentState.weapon == WP_PROX_LAUNCHER ) { - if (s1->generic1 == TEAM_BLUE) { - ent.hModel = cgs.media.blueProxMine; - } - } -#endif - - // convert direction of travel into axis - if ( VectorNormalize2( s1->pos.trDelta, ent.axis[0] ) == 0 ) { - ent.axis[0][2] = 1; - } - - // spin as it moves - if ( s1->pos.trType != TR_STATIONARY ) { - RotateAroundDirection( ent.axis, cg.time / 4 ); - } else { -#ifdef MISSIONPACK - if ( s1->weapon == WP_PROX_LAUNCHER ) { - AnglesToAxis( cent->lerpAngles, ent.axis ); - } - else -#endif - { - RotateAroundDirection( ent.axis, s1->time ); - } - } - - // add to refresh list, possibly with quad glow - CG_AddRefEntityWithPowerups( &ent, s1, TEAM_FREE ); -} - -/* -=============== -CG_Grapple - -This is called when the grapple is sitting up against the wall -=============== -*/ -static void CG_Grapple( centity_t *cent ) { - refEntity_t ent; - entityState_t *s1; - const weaponInfo_t *weapon; - - s1 = ¢->currentState; - if ( s1->weapon > WP_NUM_WEAPONS ) { - s1->weapon = 0; - } - weapon = &cg_weapons[s1->weapon]; - - // calculate the axis - VectorCopy( s1->angles, cent->lerpAngles); - -#if 0 // FIXME add grapple pull sound here..? - // add missile sound - if ( weapon->missileSound ) { - trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->missileSound ); - } -#endif - - // Will draw cable if needed - CG_GrappleTrail ( cent, weapon ); - - // create the render entity - memset (&ent, 0, sizeof(ent)); - VectorCopy( cent->lerpOrigin, ent.origin); - VectorCopy( cent->lerpOrigin, ent.oldorigin); - - // flicker between two skins - ent.skinNum = cg.clientFrame & 1; - ent.hModel = weapon->missileModel; - ent.renderfx = weapon->missileRenderfx | RF_NOSHADOW; - - // convert direction of travel into axis - if ( VectorNormalize2( s1->pos.trDelta, ent.axis[0] ) == 0 ) { - ent.axis[0][2] = 1; - } - - trap_R_AddRefEntityToScene( &ent ); -} - -/* -=============== -CG_Mover -=============== -*/ -static void CG_Mover( centity_t *cent ) { - refEntity_t ent; - entityState_t *s1; - - s1 = ¢->currentState; - - // create the render entity - memset (&ent, 0, sizeof(ent)); - VectorCopy( cent->lerpOrigin, ent.origin); - VectorCopy( cent->lerpOrigin, ent.oldorigin); - AnglesToAxis( cent->lerpAngles, ent.axis ); - - ent.renderfx = RF_NOSHADOW; - - // flicker between two skins (FIXME?) - ent.skinNum = ( cg.time >> 6 ) & 1; - - // get the model, either as a bmodel or a modelindex - if ( s1->solid == SOLID_BMODEL ) { - ent.hModel = cgs.inlineDrawModel[s1->modelindex]; - } else { - ent.hModel = cgs.gameModels[s1->modelindex]; - } - - // add to refresh list - trap_R_AddRefEntityToScene(&ent); - - // add the secondary model - if ( s1->modelindex2 ) { - ent.skinNum = 0; - ent.hModel = cgs.gameModels[s1->modelindex2]; - trap_R_AddRefEntityToScene(&ent); - } - -} - -/* -=============== -CG_Beam - -Also called as an event -=============== -*/ -void CG_Beam( centity_t *cent ) { - refEntity_t ent; - entityState_t *s1; - - s1 = ¢->currentState; - - // create the render entity - memset (&ent, 0, sizeof(ent)); - VectorCopy( s1->pos.trBase, ent.origin ); - VectorCopy( s1->origin2, ent.oldorigin ); - AxisClear( ent.axis ); - ent.reType = RT_BEAM; - - ent.renderfx = RF_NOSHADOW; - - // add to refresh list - trap_R_AddRefEntityToScene(&ent); -} - - -/* -=============== -CG_Portal -=============== -*/ -static void CG_Portal( centity_t *cent ) { - refEntity_t ent; - entityState_t *s1; - - s1 = ¢->currentState; - - // create the render entity - memset (&ent, 0, sizeof(ent)); - VectorCopy( cent->lerpOrigin, ent.origin ); - VectorCopy( s1->origin2, ent.oldorigin ); - ByteToDir( s1->eventParm, ent.axis[0] ); - PerpendicularVector( ent.axis[1], ent.axis[0] ); - - // negating this tends to get the directions like they want - // we really should have a camera roll value - VectorSubtract( vec3_origin, ent.axis[1], ent.axis[1] ); - - CrossProduct( ent.axis[0], ent.axis[1], ent.axis[2] ); - ent.reType = RT_PORTALSURFACE; - ent.oldframe = s1->powerups; - ent.frame = s1->frame; // rotation speed - ent.skinNum = s1->clientNum/256.0 * 360; // roll offset - - // add to refresh list - trap_R_AddRefEntityToScene(&ent); -} - - -/* -========================= -CG_AdjustPositionForMover - -Also called by client movement prediction code -========================= -*/ -void CG_AdjustPositionForMover( const vec3_t in, int moverNum, int fromTime, int toTime, vec3_t out ) { - centity_t *cent; - vec3_t oldOrigin, origin, deltaOrigin; - vec3_t oldAngles, angles, deltaAngles; - - if ( moverNum <= 0 || moverNum >= ENTITYNUM_MAX_NORMAL ) { - VectorCopy( in, out ); - return; - } - - cent = &cg_entities[ moverNum ]; - if ( cent->currentState.eType != ET_MOVER ) { - VectorCopy( in, out ); - return; - } - - BG_EvaluateTrajectory( ¢->currentState.pos, fromTime, oldOrigin ); - BG_EvaluateTrajectory( ¢->currentState.apos, fromTime, oldAngles ); - - BG_EvaluateTrajectory( ¢->currentState.pos, toTime, origin ); - BG_EvaluateTrajectory( ¢->currentState.apos, toTime, angles ); - - VectorSubtract( origin, oldOrigin, deltaOrigin ); - VectorSubtract( angles, oldAngles, deltaAngles ); - - VectorAdd( in, deltaOrigin, out ); - - // FIXME: origin change when on a rotating object -} - - -/* -============================= -CG_InterpolateEntityPosition -============================= -*/ -static void CG_InterpolateEntityPosition( centity_t *cent ) { - vec3_t current, next; - float f; - - // it would be an internal error to find an entity that interpolates without - // a snapshot ahead of the current one - if ( cg.nextSnap == NULL ) { - CG_Error( "CG_InterpoateEntityPosition: cg.nextSnap == NULL" ); - } - - f = cg.frameInterpolation; - - // this will linearize a sine or parabolic curve, but it is important - // to not extrapolate player positions if more recent data is available - BG_EvaluateTrajectory( ¢->currentState.pos, cg.snap->serverTime, current ); - BG_EvaluateTrajectory( ¢->nextState.pos, cg.nextSnap->serverTime, next ); - - cent->lerpOrigin[0] = current[0] + f * ( next[0] - current[0] ); - cent->lerpOrigin[1] = current[1] + f * ( next[1] - current[1] ); - cent->lerpOrigin[2] = current[2] + f * ( next[2] - current[2] ); - - BG_EvaluateTrajectory( ¢->currentState.apos, cg.snap->serverTime, current ); - BG_EvaluateTrajectory( ¢->nextState.apos, cg.nextSnap->serverTime, next ); - - cent->lerpAngles[0] = LerpAngle( current[0], next[0], f ); - cent->lerpAngles[1] = LerpAngle( current[1], next[1], f ); - cent->lerpAngles[2] = LerpAngle( current[2], next[2], f ); - -} - -/* -=============== -CG_CalcEntityLerpPositions - -=============== -*/ -static void CG_CalcEntityLerpPositions( centity_t *cent ) { - - // if this player does not want to see extrapolated players - if ( !cg_smoothClients.integer ) { - // make sure the clients use TR_INTERPOLATE - if ( cent->currentState.number < MAX_CLIENTS ) { - cent->currentState.pos.trType = TR_INTERPOLATE; - cent->nextState.pos.trType = TR_INTERPOLATE; - } - } - - if ( cent->interpolate && cent->currentState.pos.trType == TR_INTERPOLATE ) { - CG_InterpolateEntityPosition( cent ); - return; - } - - // first see if we can interpolate between two snaps for - // linear extrapolated clients - if ( cent->interpolate && cent->currentState.pos.trType == TR_LINEAR_STOP && - cent->currentState.number < MAX_CLIENTS) { - CG_InterpolateEntityPosition( cent ); - return; - } - - // just use the current frame and evaluate as best we can - BG_EvaluateTrajectory( ¢->currentState.pos, cg.time, cent->lerpOrigin ); - BG_EvaluateTrajectory( ¢->currentState.apos, cg.time, cent->lerpAngles ); - - // adjust for riding a mover if it wasn't rolled into the predicted - // player state - if ( cent != &cg.predictedPlayerEntity ) { - CG_AdjustPositionForMover( cent->lerpOrigin, cent->currentState.groundEntityNum, - cg.snap->serverTime, cg.time, cent->lerpOrigin ); - } -} - -/* -=============== -CG_TeamBase -=============== -*/ -static void CG_TeamBase( centity_t *cent ) { - refEntity_t model; -#ifdef MISSIONPACK - vec3_t angles; - int t, h; - float c; - - if ( cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF ) { -#else - if ( cgs.gametype == GT_CTF) { -#endif - // show the flag base - memset(&model, 0, sizeof(model)); - model.reType = RT_MODEL; - VectorCopy( cent->lerpOrigin, model.lightingOrigin ); - VectorCopy( cent->lerpOrigin, model.origin ); - AnglesToAxis( cent->currentState.angles, model.axis ); - if ( cent->currentState.modelindex == TEAM_RED ) { - model.hModel = cgs.media.redFlagBaseModel; - } - else if ( cent->currentState.modelindex == TEAM_BLUE ) { - model.hModel = cgs.media.blueFlagBaseModel; - } - else { - model.hModel = cgs.media.neutralFlagBaseModel; - } - trap_R_AddRefEntityToScene( &model ); - } -#ifdef MISSIONPACK - else if ( cgs.gametype == GT_OBELISK ) { - // show the obelisk - memset(&model, 0, sizeof(model)); - model.reType = RT_MODEL; - VectorCopy( cent->lerpOrigin, model.lightingOrigin ); - VectorCopy( cent->lerpOrigin, model.origin ); - AnglesToAxis( cent->currentState.angles, model.axis ); - - model.hModel = cgs.media.overloadBaseModel; - trap_R_AddRefEntityToScene( &model ); - // if hit - if ( cent->currentState.frame == 1) { - // show hit model - // modelindex2 is the health value of the obelisk - c = cent->currentState.modelindex2; - model.shaderRGBA[0] = 0xff; - model.shaderRGBA[1] = c; - model.shaderRGBA[2] = c; - model.shaderRGBA[3] = 0xff; - // - model.hModel = cgs.media.overloadEnergyModel; - trap_R_AddRefEntityToScene( &model ); - } - // if respawning - if ( cent->currentState.frame == 2) { - if ( !cent->miscTime ) { - cent->miscTime = cg.time; - } - t = cg.time - cent->miscTime; - h = (cg_obeliskRespawnDelay.integer - 5) * 1000; - // - if (t > h) { - c = (float) (t - h) / h; - if (c > 1) - c = 1; - } - else { - c = 0; - } - // show the lights - AnglesToAxis( cent->currentState.angles, model.axis ); - // - model.shaderRGBA[0] = c * 0xff; - model.shaderRGBA[1] = c * 0xff; - model.shaderRGBA[2] = c * 0xff; - model.shaderRGBA[3] = c * 0xff; - - model.hModel = cgs.media.overloadLightsModel; - trap_R_AddRefEntityToScene( &model ); - // show the target - if (t > h) { - if ( !cent->muzzleFlashTime ) { - trap_S_StartSound (cent->lerpOrigin, ENTITYNUM_NONE, CHAN_BODY, cgs.media.obeliskRespawnSound); - cent->muzzleFlashTime = 1; - } - VectorCopy(cent->currentState.angles, angles); - angles[YAW] += (float) 16 * acos(1-c) * 180 / M_PI; - AnglesToAxis( angles, model.axis ); - - VectorScale( model.axis[0], c, model.axis[0]); - VectorScale( model.axis[1], c, model.axis[1]); - VectorScale( model.axis[2], c, model.axis[2]); - - model.shaderRGBA[0] = 0xff; - model.shaderRGBA[1] = 0xff; - model.shaderRGBA[2] = 0xff; - model.shaderRGBA[3] = 0xff; - // - model.origin[2] += 56; - model.hModel = cgs.media.overloadTargetModel; - trap_R_AddRefEntityToScene( &model ); - } - else { - //FIXME: show animated smoke - } - } - else { - cent->miscTime = 0; - cent->muzzleFlashTime = 0; - // modelindex2 is the health value of the obelisk - c = cent->currentState.modelindex2; - model.shaderRGBA[0] = 0xff; - model.shaderRGBA[1] = c; - model.shaderRGBA[2] = c; - model.shaderRGBA[3] = 0xff; - // show the lights - model.hModel = cgs.media.overloadLightsModel; - trap_R_AddRefEntityToScene( &model ); - // show the target - model.origin[2] += 56; - model.hModel = cgs.media.overloadTargetModel; - trap_R_AddRefEntityToScene( &model ); - } - } - else if ( cgs.gametype == GT_HARVESTER ) { - // show harvester model - memset(&model, 0, sizeof(model)); - model.reType = RT_MODEL; - VectorCopy( cent->lerpOrigin, model.lightingOrigin ); - VectorCopy( cent->lerpOrigin, model.origin ); - AnglesToAxis( cent->currentState.angles, model.axis ); - - if ( cent->currentState.modelindex == TEAM_RED ) { - model.hModel = cgs.media.harvesterModel; - model.customSkin = cgs.media.harvesterRedSkin; - } - else if ( cent->currentState.modelindex == TEAM_BLUE ) { - model.hModel = cgs.media.harvesterModel; - model.customSkin = cgs.media.harvesterBlueSkin; - } - else { - model.hModel = cgs.media.harvesterNeutralModel; - model.customSkin = 0; - } - trap_R_AddRefEntityToScene( &model ); - } -#endif -} - -/* -=============== -CG_AddCEntity - -=============== -*/ -static void CG_AddCEntity( centity_t *cent ) { - // event-only entities will have been dealt with already - if ( cent->currentState.eType >= ET_EVENTS ) { - return; - } - - // calculate the current origin - CG_CalcEntityLerpPositions( cent ); - - // add automatic effects - CG_EntityEffects( cent ); - - switch ( cent->currentState.eType ) { - default: - CG_Error( "Bad entity type: %i\n", cent->currentState.eType ); - break; - case ET_INVISIBLE: - case ET_PUSH_TRIGGER: - case ET_TELEPORT_TRIGGER: - break; - case ET_GENERAL: - CG_General( cent ); - break; - case ET_PLAYER: - CG_Player( cent ); - break; - case ET_ITEM: - CG_Item( cent ); - break; - case ET_MISSILE: - CG_Missile( cent ); - break; - case ET_MOVER: - CG_Mover( cent ); - break; - case ET_BEAM: - CG_Beam( cent ); - break; - case ET_PORTAL: - CG_Portal( cent ); - break; - case ET_SPEAKER: - CG_Speaker( cent ); - break; - case ET_GRAPPLE: - CG_Grapple( cent ); - break; - case ET_TEAM: - CG_TeamBase( cent ); - break; - } -} - -/* -=============== -CG_AddPacketEntities - -=============== -*/ -void CG_AddPacketEntities( void ) { - int num; - centity_t *cent; - playerState_t *ps; - - // set cg.frameInterpolation - if ( cg.nextSnap ) { - int delta; - - delta = (cg.nextSnap->serverTime - cg.snap->serverTime); - if ( delta == 0 ) { - cg.frameInterpolation = 0; - } else { - cg.frameInterpolation = (float)( cg.time - cg.snap->serverTime ) / delta; - } - } else { - cg.frameInterpolation = 0; // actually, it should never be used, because - // no entities should be marked as interpolating - } - - // the auto-rotating items will all have the same axis - cg.autoAngles[0] = 0; - cg.autoAngles[1] = ( cg.time & 2047 ) * 360 / 2048.0; - cg.autoAngles[2] = 0; - - cg.autoAnglesFast[0] = 0; - cg.autoAnglesFast[1] = ( cg.time & 1023 ) * 360 / 1024.0f; - cg.autoAnglesFast[2] = 0; - - AnglesToAxis( cg.autoAngles, cg.autoAxis ); - AnglesToAxis( cg.autoAnglesFast, cg.autoAxisFast ); - - // generate and add the entity from the playerstate - ps = &cg.predictedPlayerState; - BG_PlayerStateToEntityState( ps, &cg.predictedPlayerEntity.currentState, qfalse ); - CG_AddCEntity( &cg.predictedPlayerEntity ); - - // lerp the non-predicted value for lightning gun origins - CG_CalcEntityLerpPositions( &cg_entities[ cg.snap->ps.clientNum ] ); - - // add each entity sent over by the server - for ( num = 0 ; num < cg.snap->numEntities ; num++ ) { - cent = &cg_entities[ cg.snap->entities[ num ].number ]; - CG_AddCEntity( cent ); - } -} - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// cg_ents.c -- present snapshot entities, happens every single frame + +#include "cg_local.h" + + +/* +====================== +CG_PositionEntityOnTag + +Modifies the entities position and axis by the given +tag location +====================== +*/ +void CG_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent, + qhandle_t parentModel, char *tagName ) { + int i; + orientation_t lerped; + + // lerp the tag + trap_R_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame, + 1.0 - parent->backlerp, tagName ); + + // FIXME: allow origin offsets along tag? + VectorCopy( parent->origin, entity->origin ); + for ( i = 0 ; i < 3 ; i++ ) { + VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin ); + } + + // had to cast away the const to avoid compiler problems... + MatrixMultiply( lerped.axis, ((refEntity_t *)parent)->axis, entity->axis ); + entity->backlerp = parent->backlerp; +} + + +/* +====================== +CG_PositionRotatedEntityOnTag + +Modifies the entities position and axis by the given +tag location +====================== +*/ +void CG_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent, + qhandle_t parentModel, char *tagName ) { + int i; + orientation_t lerped; + vec3_t tempAxis[3]; + +//AxisClear( entity->axis ); + // lerp the tag + trap_R_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame, + 1.0 - parent->backlerp, tagName ); + + // FIXME: allow origin offsets along tag? + VectorCopy( parent->origin, entity->origin ); + for ( i = 0 ; i < 3 ; i++ ) { + VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin ); + } + + // had to cast away the const to avoid compiler problems... + MatrixMultiply( entity->axis, lerped.axis, tempAxis ); + MatrixMultiply( tempAxis, ((refEntity_t *)parent)->axis, entity->axis ); +} + + + +/* +========================================================================== + +FUNCTIONS CALLED EACH FRAME + +========================================================================== +*/ + +/* +====================== +CG_SetEntitySoundPosition + +Also called by event processing code +====================== +*/ +void CG_SetEntitySoundPosition( centity_t *cent ) { + if ( cent->currentState.solid == SOLID_BMODEL ) { + vec3_t origin; + float *v; + + v = cgs.inlineModelMidpoints[ cent->currentState.modelindex ]; + VectorAdd( cent->lerpOrigin, v, origin ); + trap_S_UpdateEntityPosition( cent->currentState.number, origin ); + } else { + trap_S_UpdateEntityPosition( cent->currentState.number, cent->lerpOrigin ); + } +} + +/* +================== +CG_EntityEffects + +Add continuous entity effects, like local entity emission and lighting +================== +*/ +static void CG_EntityEffects( centity_t *cent ) { + + // update sound origins + CG_SetEntitySoundPosition( cent ); + + // add loop sound + if ( cent->currentState.loopSound ) { + if (cent->currentState.eType != ET_SPEAKER) { + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, + cgs.gameSounds[ cent->currentState.loopSound ] ); + } else { + trap_S_AddRealLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, + cgs.gameSounds[ cent->currentState.loopSound ] ); + } + } + + + // constant light glow + if ( cent->currentState.constantLight ) { + int cl; + int i, r, g, b; + + cl = cent->currentState.constantLight; + r = cl & 255; + g = ( cl >> 8 ) & 255; + b = ( cl >> 16 ) & 255; + i = ( ( cl >> 24 ) & 255 ) * 4; + trap_R_AddLightToScene( cent->lerpOrigin, i, r, g, b ); + } + +} + + +/* +================== +CG_General +================== +*/ +static void CG_General( centity_t *cent ) { + refEntity_t ent; + entityState_t *s1; + + s1 = ¢->currentState; + + // if set to invisible, skip + if (!s1->modelindex) { + return; + } + + memset (&ent, 0, sizeof(ent)); + + // set frame + + ent.frame = s1->frame; + ent.oldframe = ent.frame; + ent.backlerp = 0; + + VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin); + + ent.hModel = cgs.gameModels[s1->modelindex]; + + // player model + if (s1->number == cg.snap->ps.clientNum) { + ent.renderfx |= RF_THIRD_PERSON; // only draw from mirrors + } + + // convert angles to axis + AnglesToAxis( cent->lerpAngles, ent.axis ); + + // add to refresh list + trap_R_AddRefEntityToScene (&ent); +} + +/* +================== +CG_Speaker + +Speaker entities can automatically play sounds +================== +*/ +static void CG_Speaker( centity_t *cent ) { + if ( ! cent->currentState.clientNum ) { // FIXME: use something other than clientNum... + return; // not auto triggering + } + + if ( cg.time < cent->miscTime ) { + return; + } + + trap_S_StartSound (NULL, cent->currentState.number, CHAN_ITEM, cgs.gameSounds[cent->currentState.eventParm] ); + + // ent->s.frame = ent->wait * 10; + // ent->s.clientNum = ent->random * 10; + cent->miscTime = cg.time + cent->currentState.frame * 100 + cent->currentState.clientNum * 100 * crandom(); +} + +/* +================== +CG_Item +================== +*/ +static void CG_Item( centity_t *cent ) { + refEntity_t ent; + entityState_t *es; + gitem_t *item; + int msec; + float frac; + float scale; + weaponInfo_t *wi; + + es = ¢->currentState; + if ( es->modelindex >= bg_numItems ) { + CG_Error( "Bad item index %i on entity", es->modelindex ); + } + + // if set to invisible, skip + if ( !es->modelindex || ( es->eFlags & EF_NODRAW ) ) { + return; + } + + item = &bg_itemlist[ es->modelindex ]; + if ( cg_simpleItems.integer && item->giType != IT_TEAM ) { + memset( &ent, 0, sizeof( ent ) ); + ent.reType = RT_SPRITE; + VectorCopy( cent->lerpOrigin, ent.origin ); + ent.radius = 14; + ent.customShader = cg_items[es->modelindex].icon; + ent.shaderRGBA[0] = 255; + ent.shaderRGBA[1] = 255; + ent.shaderRGBA[2] = 255; + ent.shaderRGBA[3] = 255; + trap_R_AddRefEntityToScene(&ent); + return; + } + + // items bob up and down continuously + scale = 0.005 + cent->currentState.number * 0.00001; + cent->lerpOrigin[2] += 4 + cos( ( cg.time + 1000 ) * scale ) * 4; + + memset (&ent, 0, sizeof(ent)); + + // autorotate at one of two speeds + if ( item->giType == IT_HEALTH ) { + VectorCopy( cg.autoAnglesFast, cent->lerpAngles ); + AxisCopy( cg.autoAxisFast, ent.axis ); + } else { + VectorCopy( cg.autoAngles, cent->lerpAngles ); + AxisCopy( cg.autoAxis, ent.axis ); + } + + wi = NULL; + // the weapons have their origin where they attatch to player + // models, so we need to offset them or they will rotate + // eccentricly + if ( item->giType == IT_WEAPON ) { + wi = &cg_weapons[item->giTag]; + cent->lerpOrigin[0] -= + wi->weaponMidpoint[0] * ent.axis[0][0] + + wi->weaponMidpoint[1] * ent.axis[1][0] + + wi->weaponMidpoint[2] * ent.axis[2][0]; + cent->lerpOrigin[1] -= + wi->weaponMidpoint[0] * ent.axis[0][1] + + wi->weaponMidpoint[1] * ent.axis[1][1] + + wi->weaponMidpoint[2] * ent.axis[2][1]; + cent->lerpOrigin[2] -= + wi->weaponMidpoint[0] * ent.axis[0][2] + + wi->weaponMidpoint[1] * ent.axis[1][2] + + wi->weaponMidpoint[2] * ent.axis[2][2]; + + cent->lerpOrigin[2] += 8; // an extra height boost + } + + ent.hModel = cg_items[es->modelindex].models[0]; + + VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin); + + ent.nonNormalizedAxes = qfalse; + + // if just respawned, slowly scale up + msec = cg.time - cent->miscTime; + if ( msec >= 0 && msec < ITEM_SCALEUP_TIME ) { + frac = (float)msec / ITEM_SCALEUP_TIME; + VectorScale( ent.axis[0], frac, ent.axis[0] ); + VectorScale( ent.axis[1], frac, ent.axis[1] ); + VectorScale( ent.axis[2], frac, ent.axis[2] ); + ent.nonNormalizedAxes = qtrue; + } else { + frac = 1.0; + } + + // items without glow textures need to keep a minimum light value + // so they are always visible + if ( ( item->giType == IT_WEAPON ) || + ( item->giType == IT_ARMOR ) ) { + ent.renderfx |= RF_MINLIGHT; + } + + // increase the size of the weapons when they are presented as items + if ( item->giType == IT_WEAPON ) { + VectorScale( ent.axis[0], 1.5, ent.axis[0] ); + VectorScale( ent.axis[1], 1.5, ent.axis[1] ); + VectorScale( ent.axis[2], 1.5, ent.axis[2] ); + ent.nonNormalizedAxes = qtrue; +#ifdef MISSIONPACK + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.media.weaponHoverSound ); +#endif + } + +#ifdef MISSIONPACK + if ( item->giType == IT_HOLDABLE && item->giTag == HI_KAMIKAZE ) { + VectorScale( ent.axis[0], 2, ent.axis[0] ); + VectorScale( ent.axis[1], 2, ent.axis[1] ); + VectorScale( ent.axis[2], 2, ent.axis[2] ); + ent.nonNormalizedAxes = qtrue; + } +#endif + + // add to refresh list + trap_R_AddRefEntityToScene(&ent); + +#ifdef MISSIONPACK + if ( item->giType == IT_WEAPON && wi->barrelModel ) { + refEntity_t barrel; + + memset( &barrel, 0, sizeof( barrel ) ); + + barrel.hModel = wi->barrelModel; + + VectorCopy( ent.lightingOrigin, barrel.lightingOrigin ); + barrel.shadowPlane = ent.shadowPlane; + barrel.renderfx = ent.renderfx; + + CG_PositionRotatedEntityOnTag( &barrel, &ent, wi->weaponModel, "tag_barrel" ); + + AxisCopy( ent.axis, barrel.axis ); + barrel.nonNormalizedAxes = ent.nonNormalizedAxes; + + trap_R_AddRefEntityToScene( &barrel ); + } +#endif + + // accompanying rings / spheres for powerups + if ( !cg_simpleItems.integer ) + { + vec3_t spinAngles; + + VectorClear( spinAngles ); + + if ( item->giType == IT_HEALTH || item->giType == IT_POWERUP ) + { + if ( ( ent.hModel = cg_items[es->modelindex].models[1] ) != 0 ) + { + if ( item->giType == IT_POWERUP ) + { + ent.origin[2] += 12; + spinAngles[1] = ( cg.time & 1023 ) * 360 / -1024.0f; + } + AnglesToAxis( spinAngles, ent.axis ); + + // scale up if respawning + if ( frac != 1.0 ) { + VectorScale( ent.axis[0], frac, ent.axis[0] ); + VectorScale( ent.axis[1], frac, ent.axis[1] ); + VectorScale( ent.axis[2], frac, ent.axis[2] ); + ent.nonNormalizedAxes = qtrue; + } + trap_R_AddRefEntityToScene( &ent ); + } + } + } +} + +//============================================================================ + +/* +=============== +CG_Missile +=============== +*/ +static void CG_Missile( centity_t *cent ) { + refEntity_t ent; + entityState_t *s1; + const weaponInfo_t *weapon; +// int col; + + s1 = ¢->currentState; + if ( s1->weapon > WP_NUM_WEAPONS ) { + s1->weapon = 0; + } + weapon = &cg_weapons[s1->weapon]; + + // calculate the axis + VectorCopy( s1->angles, cent->lerpAngles); + + // add trails + if ( weapon->missileTrailFunc ) + { + weapon->missileTrailFunc( cent, weapon ); + } +/* + if ( cent->currentState.modelindex == TEAM_RED ) { + col = 1; + } + else if ( cent->currentState.modelindex == TEAM_BLUE ) { + col = 2; + } + else { + col = 0; + } + + // add dynamic light + if ( weapon->missileDlight ) { + trap_R_AddLightToScene(cent->lerpOrigin, weapon->missileDlight, + weapon->missileDlightColor[col][0], weapon->missileDlightColor[col][1], weapon->missileDlightColor[col][2] ); + } +*/ + // add dynamic light + if ( weapon->missileDlight ) { + trap_R_AddLightToScene(cent->lerpOrigin, weapon->missileDlight, + weapon->missileDlightColor[0], weapon->missileDlightColor[1], weapon->missileDlightColor[2] ); + } + + // add missile sound + if ( weapon->missileSound ) { + vec3_t velocity; + + BG_EvaluateTrajectoryDelta( ¢->currentState.pos, cg.time, velocity ); + + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, velocity, weapon->missileSound ); + } + + // create the render entity + memset (&ent, 0, sizeof(ent)); + VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin); + + if ( cent->currentState.weapon == WP_PLASMAGUN ) { + ent.reType = RT_SPRITE; + ent.radius = 16; + ent.rotation = 0; + ent.customShader = cgs.media.plasmaBallShader; + trap_R_AddRefEntityToScene( &ent ); + return; + } + + // flicker between two skins + ent.skinNum = cg.clientFrame & 1; + ent.hModel = weapon->missileModel; + ent.renderfx = weapon->missileRenderfx | RF_NOSHADOW; + +#ifdef MISSIONPACK + if ( cent->currentState.weapon == WP_PROX_LAUNCHER ) { + if (s1->generic1 == TEAM_BLUE) { + ent.hModel = cgs.media.blueProxMine; + } + } +#endif + + // convert direction of travel into axis + if ( VectorNormalize2( s1->pos.trDelta, ent.axis[0] ) == 0 ) { + ent.axis[0][2] = 1; + } + + // spin as it moves + if ( s1->pos.trType != TR_STATIONARY ) { + RotateAroundDirection( ent.axis, cg.time / 4 ); + } else { +#ifdef MISSIONPACK + if ( s1->weapon == WP_PROX_LAUNCHER ) { + AnglesToAxis( cent->lerpAngles, ent.axis ); + } + else +#endif + { + RotateAroundDirection( ent.axis, s1->time ); + } + } + + // add to refresh list, possibly with quad glow + CG_AddRefEntityWithPowerups( &ent, s1, TEAM_FREE ); +} + +/* +=============== +CG_Grapple + +This is called when the grapple is sitting up against the wall +=============== +*/ +static void CG_Grapple( centity_t *cent ) { + refEntity_t ent; + entityState_t *s1; + const weaponInfo_t *weapon; + + s1 = ¢->currentState; + if ( s1->weapon > WP_NUM_WEAPONS ) { + s1->weapon = 0; + } + weapon = &cg_weapons[s1->weapon]; + + // calculate the axis + VectorCopy( s1->angles, cent->lerpAngles); + +#if 0 // FIXME add grapple pull sound here..? + // add missile sound + if ( weapon->missileSound ) { + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->missileSound ); + } +#endif + + // Will draw cable if needed + CG_GrappleTrail ( cent, weapon ); + + // create the render entity + memset (&ent, 0, sizeof(ent)); + VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin); + + // flicker between two skins + ent.skinNum = cg.clientFrame & 1; + ent.hModel = weapon->missileModel; + ent.renderfx = weapon->missileRenderfx | RF_NOSHADOW; + + // convert direction of travel into axis + if ( VectorNormalize2( s1->pos.trDelta, ent.axis[0] ) == 0 ) { + ent.axis[0][2] = 1; + } + + trap_R_AddRefEntityToScene( &ent ); +} + +/* +=============== +CG_Mover +=============== +*/ +static void CG_Mover( centity_t *cent ) { + refEntity_t ent; + entityState_t *s1; + + s1 = ¢->currentState; + + // create the render entity + memset (&ent, 0, sizeof(ent)); + VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin); + AnglesToAxis( cent->lerpAngles, ent.axis ); + + ent.renderfx = RF_NOSHADOW; + + // flicker between two skins (FIXME?) + ent.skinNum = ( cg.time >> 6 ) & 1; + + // get the model, either as a bmodel or a modelindex + if ( s1->solid == SOLID_BMODEL ) { + ent.hModel = cgs.inlineDrawModel[s1->modelindex]; + } else { + ent.hModel = cgs.gameModels[s1->modelindex]; + } + + // add to refresh list + trap_R_AddRefEntityToScene(&ent); + + // add the secondary model + if ( s1->modelindex2 ) { + ent.skinNum = 0; + ent.hModel = cgs.gameModels[s1->modelindex2]; + trap_R_AddRefEntityToScene(&ent); + } + +} + +/* +=============== +CG_Beam + +Also called as an event +=============== +*/ +void CG_Beam( centity_t *cent ) { + refEntity_t ent; + entityState_t *s1; + + s1 = ¢->currentState; + + // create the render entity + memset (&ent, 0, sizeof(ent)); + VectorCopy( s1->pos.trBase, ent.origin ); + VectorCopy( s1->origin2, ent.oldorigin ); + AxisClear( ent.axis ); + ent.reType = RT_BEAM; + + ent.renderfx = RF_NOSHADOW; + + // add to refresh list + trap_R_AddRefEntityToScene(&ent); +} + + +/* +=============== +CG_Portal +=============== +*/ +static void CG_Portal( centity_t *cent ) { + refEntity_t ent; + entityState_t *s1; + + s1 = ¢->currentState; + + // create the render entity + memset (&ent, 0, sizeof(ent)); + VectorCopy( cent->lerpOrigin, ent.origin ); + VectorCopy( s1->origin2, ent.oldorigin ); + ByteToDir( s1->eventParm, ent.axis[0] ); + PerpendicularVector( ent.axis[1], ent.axis[0] ); + + // negating this tends to get the directions like they want + // we really should have a camera roll value + VectorSubtract( vec3_origin, ent.axis[1], ent.axis[1] ); + + CrossProduct( ent.axis[0], ent.axis[1], ent.axis[2] ); + ent.reType = RT_PORTALSURFACE; + ent.oldframe = s1->powerups; + ent.frame = s1->frame; // rotation speed + ent.skinNum = s1->clientNum/256.0 * 360; // roll offset + + // add to refresh list + trap_R_AddRefEntityToScene(&ent); +} + + +/* +========================= +CG_AdjustPositionForMover + +Also called by client movement prediction code +========================= +*/ +void CG_AdjustPositionForMover( const vec3_t in, int moverNum, int fromTime, int toTime, vec3_t out ) { + centity_t *cent; + vec3_t oldOrigin, origin, deltaOrigin; + vec3_t oldAngles, angles, deltaAngles; + + if ( moverNum <= 0 || moverNum >= ENTITYNUM_MAX_NORMAL ) { + VectorCopy( in, out ); + return; + } + + cent = &cg_entities[ moverNum ]; + if ( cent->currentState.eType != ET_MOVER ) { + VectorCopy( in, out ); + return; + } + + BG_EvaluateTrajectory( ¢->currentState.pos, fromTime, oldOrigin ); + BG_EvaluateTrajectory( ¢->currentState.apos, fromTime, oldAngles ); + + BG_EvaluateTrajectory( ¢->currentState.pos, toTime, origin ); + BG_EvaluateTrajectory( ¢->currentState.apos, toTime, angles ); + + VectorSubtract( origin, oldOrigin, deltaOrigin ); + VectorSubtract( angles, oldAngles, deltaAngles ); + + VectorAdd( in, deltaOrigin, out ); + + // FIXME: origin change when on a rotating object +} + + +/* +============================= +CG_InterpolateEntityPosition +============================= +*/ +static void CG_InterpolateEntityPosition( centity_t *cent ) { + vec3_t current, next; + float f; + + // it would be an internal error to find an entity that interpolates without + // a snapshot ahead of the current one + if ( cg.nextSnap == NULL ) { + CG_Error( "CG_InterpoateEntityPosition: cg.nextSnap == NULL" ); + } + + f = cg.frameInterpolation; + + // this will linearize a sine or parabolic curve, but it is important + // to not extrapolate player positions if more recent data is available + BG_EvaluateTrajectory( ¢->currentState.pos, cg.snap->serverTime, current ); + BG_EvaluateTrajectory( ¢->nextState.pos, cg.nextSnap->serverTime, next ); + + cent->lerpOrigin[0] = current[0] + f * ( next[0] - current[0] ); + cent->lerpOrigin[1] = current[1] + f * ( next[1] - current[1] ); + cent->lerpOrigin[2] = current[2] + f * ( next[2] - current[2] ); + + BG_EvaluateTrajectory( ¢->currentState.apos, cg.snap->serverTime, current ); + BG_EvaluateTrajectory( ¢->nextState.apos, cg.nextSnap->serverTime, next ); + + cent->lerpAngles[0] = LerpAngle( current[0], next[0], f ); + cent->lerpAngles[1] = LerpAngle( current[1], next[1], f ); + cent->lerpAngles[2] = LerpAngle( current[2], next[2], f ); + +} + +/* +=============== +CG_CalcEntityLerpPositions + +=============== +*/ +static void CG_CalcEntityLerpPositions( centity_t *cent ) { + + // if this player does not want to see extrapolated players + if ( !cg_smoothClients.integer ) { + // make sure the clients use TR_INTERPOLATE + if ( cent->currentState.number < MAX_CLIENTS ) { + cent->currentState.pos.trType = TR_INTERPOLATE; + cent->nextState.pos.trType = TR_INTERPOLATE; + } + } + + if ( cent->interpolate && cent->currentState.pos.trType == TR_INTERPOLATE ) { + CG_InterpolateEntityPosition( cent ); + return; + } + + // first see if we can interpolate between two snaps for + // linear extrapolated clients + if ( cent->interpolate && cent->currentState.pos.trType == TR_LINEAR_STOP && + cent->currentState.number < MAX_CLIENTS) { + CG_InterpolateEntityPosition( cent ); + return; + } + + // just use the current frame and evaluate as best we can + BG_EvaluateTrajectory( ¢->currentState.pos, cg.time, cent->lerpOrigin ); + BG_EvaluateTrajectory( ¢->currentState.apos, cg.time, cent->lerpAngles ); + + // adjust for riding a mover if it wasn't rolled into the predicted + // player state + if ( cent != &cg.predictedPlayerEntity ) { + CG_AdjustPositionForMover( cent->lerpOrigin, cent->currentState.groundEntityNum, + cg.snap->serverTime, cg.time, cent->lerpOrigin ); + } +} + +/* +=============== +CG_TeamBase +=============== +*/ +static void CG_TeamBase( centity_t *cent ) { + refEntity_t model; +#ifdef MISSIONPACK + vec3_t angles; + int t, h; + float c; + + if ( cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF ) { +#else + if ( cgs.gametype == GT_CTF) { +#endif + // show the flag base + memset(&model, 0, sizeof(model)); + model.reType = RT_MODEL; + VectorCopy( cent->lerpOrigin, model.lightingOrigin ); + VectorCopy( cent->lerpOrigin, model.origin ); + AnglesToAxis( cent->currentState.angles, model.axis ); + if ( cent->currentState.modelindex == TEAM_RED ) { + model.hModel = cgs.media.redFlagBaseModel; + } + else if ( cent->currentState.modelindex == TEAM_BLUE ) { + model.hModel = cgs.media.blueFlagBaseModel; + } + else { + model.hModel = cgs.media.neutralFlagBaseModel; + } + trap_R_AddRefEntityToScene( &model ); + } +#ifdef MISSIONPACK + else if ( cgs.gametype == GT_OBELISK ) { + // show the obelisk + memset(&model, 0, sizeof(model)); + model.reType = RT_MODEL; + VectorCopy( cent->lerpOrigin, model.lightingOrigin ); + VectorCopy( cent->lerpOrigin, model.origin ); + AnglesToAxis( cent->currentState.angles, model.axis ); + + model.hModel = cgs.media.overloadBaseModel; + trap_R_AddRefEntityToScene( &model ); + // if hit + if ( cent->currentState.frame == 1) { + // show hit model + // modelindex2 is the health value of the obelisk + c = cent->currentState.modelindex2; + model.shaderRGBA[0] = 0xff; + model.shaderRGBA[1] = c; + model.shaderRGBA[2] = c; + model.shaderRGBA[3] = 0xff; + // + model.hModel = cgs.media.overloadEnergyModel; + trap_R_AddRefEntityToScene( &model ); + } + // if respawning + if ( cent->currentState.frame == 2) { + if ( !cent->miscTime ) { + cent->miscTime = cg.time; + } + t = cg.time - cent->miscTime; + h = (cg_obeliskRespawnDelay.integer - 5) * 1000; + // + if (t > h) { + c = (float) (t - h) / h; + if (c > 1) + c = 1; + } + else { + c = 0; + } + // show the lights + AnglesToAxis( cent->currentState.angles, model.axis ); + // + model.shaderRGBA[0] = c * 0xff; + model.shaderRGBA[1] = c * 0xff; + model.shaderRGBA[2] = c * 0xff; + model.shaderRGBA[3] = c * 0xff; + + model.hModel = cgs.media.overloadLightsModel; + trap_R_AddRefEntityToScene( &model ); + // show the target + if (t > h) { + if ( !cent->muzzleFlashTime ) { + trap_S_StartSound (cent->lerpOrigin, ENTITYNUM_NONE, CHAN_BODY, cgs.media.obeliskRespawnSound); + cent->muzzleFlashTime = 1; + } + VectorCopy(cent->currentState.angles, angles); + angles[YAW] += (float) 16 * acos(1-c) * 180 / M_PI; + AnglesToAxis( angles, model.axis ); + + VectorScale( model.axis[0], c, model.axis[0]); + VectorScale( model.axis[1], c, model.axis[1]); + VectorScale( model.axis[2], c, model.axis[2]); + + model.shaderRGBA[0] = 0xff; + model.shaderRGBA[1] = 0xff; + model.shaderRGBA[2] = 0xff; + model.shaderRGBA[3] = 0xff; + // + model.origin[2] += 56; + model.hModel = cgs.media.overloadTargetModel; + trap_R_AddRefEntityToScene( &model ); + } + else { + //FIXME: show animated smoke + } + } + else { + cent->miscTime = 0; + cent->muzzleFlashTime = 0; + // modelindex2 is the health value of the obelisk + c = cent->currentState.modelindex2; + model.shaderRGBA[0] = 0xff; + model.shaderRGBA[1] = c; + model.shaderRGBA[2] = c; + model.shaderRGBA[3] = 0xff; + // show the lights + model.hModel = cgs.media.overloadLightsModel; + trap_R_AddRefEntityToScene( &model ); + // show the target + model.origin[2] += 56; + model.hModel = cgs.media.overloadTargetModel; + trap_R_AddRefEntityToScene( &model ); + } + } + else if ( cgs.gametype == GT_HARVESTER ) { + // show harvester model + memset(&model, 0, sizeof(model)); + model.reType = RT_MODEL; + VectorCopy( cent->lerpOrigin, model.lightingOrigin ); + VectorCopy( cent->lerpOrigin, model.origin ); + AnglesToAxis( cent->currentState.angles, model.axis ); + + if ( cent->currentState.modelindex == TEAM_RED ) { + model.hModel = cgs.media.harvesterModel; + model.customSkin = cgs.media.harvesterRedSkin; + } + else if ( cent->currentState.modelindex == TEAM_BLUE ) { + model.hModel = cgs.media.harvesterModel; + model.customSkin = cgs.media.harvesterBlueSkin; + } + else { + model.hModel = cgs.media.harvesterNeutralModel; + model.customSkin = 0; + } + trap_R_AddRefEntityToScene( &model ); + } +#endif +} + +/* +=============== +CG_AddCEntity + +=============== +*/ +static void CG_AddCEntity( centity_t *cent ) { + // event-only entities will have been dealt with already + if ( cent->currentState.eType >= ET_EVENTS ) { + return; + } + + // calculate the current origin + CG_CalcEntityLerpPositions( cent ); + + // add automatic effects + CG_EntityEffects( cent ); + + switch ( cent->currentState.eType ) { + default: + CG_Error( "Bad entity type: %i\n", cent->currentState.eType ); + break; + case ET_INVISIBLE: + case ET_PUSH_TRIGGER: + case ET_TELEPORT_TRIGGER: + break; + case ET_GENERAL: + CG_General( cent ); + break; + case ET_PLAYER: + CG_Player( cent ); + break; + case ET_ITEM: + CG_Item( cent ); + break; + case ET_MISSILE: + CG_Missile( cent ); + break; + case ET_MOVER: + CG_Mover( cent ); + break; + case ET_BEAM: + CG_Beam( cent ); + break; + case ET_PORTAL: + CG_Portal( cent ); + break; + case ET_SPEAKER: + CG_Speaker( cent ); + break; + case ET_GRAPPLE: + CG_Grapple( cent ); + break; + case ET_TEAM: + CG_TeamBase( cent ); + break; + } +} + +/* +=============== +CG_AddPacketEntities + +=============== +*/ +void CG_AddPacketEntities( void ) { + int num; + centity_t *cent; + playerState_t *ps; + + // set cg.frameInterpolation + if ( cg.nextSnap ) { + int delta; + + delta = (cg.nextSnap->serverTime - cg.snap->serverTime); + if ( delta == 0 ) { + cg.frameInterpolation = 0; + } else { + cg.frameInterpolation = (float)( cg.time - cg.snap->serverTime ) / delta; + } + } else { + cg.frameInterpolation = 0; // actually, it should never be used, because + // no entities should be marked as interpolating + } + + // the auto-rotating items will all have the same axis + cg.autoAngles[0] = 0; + cg.autoAngles[1] = ( cg.time & 2047 ) * 360 / 2048.0; + cg.autoAngles[2] = 0; + + cg.autoAnglesFast[0] = 0; + cg.autoAnglesFast[1] = ( cg.time & 1023 ) * 360 / 1024.0f; + cg.autoAnglesFast[2] = 0; + + AnglesToAxis( cg.autoAngles, cg.autoAxis ); + AnglesToAxis( cg.autoAnglesFast, cg.autoAxisFast ); + + // generate and add the entity from the playerstate + ps = &cg.predictedPlayerState; + BG_PlayerStateToEntityState( ps, &cg.predictedPlayerEntity.currentState, qfalse ); + CG_AddCEntity( &cg.predictedPlayerEntity ); + + // lerp the non-predicted value for lightning gun origins + CG_CalcEntityLerpPositions( &cg_entities[ cg.snap->ps.clientNum ] ); + + // add each entity sent over by the server + for ( num = 0 ; num < cg.snap->numEntities ; num++ ) { + cent = &cg_entities[ cg.snap->entities[ num ].number ]; + CG_AddCEntity( cent ); + } +} + diff --git a/code/cgame/cg_event.c b/code/cgame/cg_event.c index 86167c6..373c9a3 100755 --- a/code/cgame/cg_event.c +++ b/code/cgame/cg_event.c @@ -1,1205 +1,1205 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// cg_event.c -- handle entity events at snapshot or playerstate transitions - -#include "cg_local.h" - -// for the voice chats -#ifdef MISSIONPACK // bk001205 -#include "../../ui/menudef.h" -#endif -//========================================================================== - -/* -=================== -CG_PlaceString - -Also called by scoreboard drawing -=================== -*/ -const char *CG_PlaceString( int rank ) { - static char str[64]; - char *s, *t; - - if ( rank & RANK_TIED_FLAG ) { - rank &= ~RANK_TIED_FLAG; - t = "Tied for "; - } else { - t = ""; - } - - if ( rank == 1 ) { - s = S_COLOR_BLUE "1st" S_COLOR_WHITE; // draw in blue - } else if ( rank == 2 ) { - s = S_COLOR_RED "2nd" S_COLOR_WHITE; // draw in red - } else if ( rank == 3 ) { - s = S_COLOR_YELLOW "3rd" S_COLOR_WHITE; // draw in yellow - } else if ( rank == 11 ) { - s = "11th"; - } else if ( rank == 12 ) { - s = "12th"; - } else if ( rank == 13 ) { - s = "13th"; - } else if ( rank % 10 == 1 ) { - s = va("%ist", rank); - } else if ( rank % 10 == 2 ) { - s = va("%ind", rank); - } else if ( rank % 10 == 3 ) { - s = va("%ird", rank); - } else { - s = va("%ith", rank); - } - - Com_sprintf( str, sizeof( str ), "%s%s", t, s ); - return str; -} - -/* -============= -CG_Obituary -============= -*/ -static void CG_Obituary( entityState_t *ent ) { - int mod; - int target, attacker; - char *message; - char *message2; - const char *targetInfo; - const char *attackerInfo; - char targetName[32]; - char attackerName[32]; - gender_t gender; - clientInfo_t *ci; - - target = ent->otherEntityNum; - attacker = ent->otherEntityNum2; - mod = ent->eventParm; - - if ( target < 0 || target >= MAX_CLIENTS ) { - CG_Error( "CG_Obituary: target out of range" ); - } - ci = &cgs.clientinfo[target]; - - if ( attacker < 0 || attacker >= MAX_CLIENTS ) { - attacker = ENTITYNUM_WORLD; - attackerInfo = NULL; - } else { - attackerInfo = CG_ConfigString( CS_PLAYERS + attacker ); - } - - targetInfo = CG_ConfigString( CS_PLAYERS + target ); - if ( !targetInfo ) { - return; - } - Q_strncpyz( targetName, Info_ValueForKey( targetInfo, "n" ), sizeof(targetName) - 2); - strcat( targetName, S_COLOR_WHITE ); - - message2 = ""; - - // check for single client messages - - switch( mod ) { - case MOD_SUICIDE: - message = "suicides"; - break; - case MOD_FALLING: - message = "cratered"; - break; - case MOD_CRUSH: - message = "was squished"; - break; - case MOD_WATER: - message = "sank like a rock"; - break; - case MOD_SLIME: - message = "melted"; - break; - case MOD_LAVA: - message = "does a back flip into the lava"; - break; - case MOD_TARGET_LASER: - message = "saw the light"; - break; - case MOD_TRIGGER_HURT: - message = "was in the wrong place"; - break; - default: - message = NULL; - break; - } - - if (attacker == target) { - gender = ci->gender; - switch (mod) { -#ifdef MISSIONPACK - case MOD_KAMIKAZE: - message = "goes out with a bang"; - break; -#endif - case MOD_GRENADE_SPLASH: - if ( gender == GENDER_FEMALE ) - message = "tripped on her own grenade"; - else if ( gender == GENDER_NEUTER ) - message = "tripped on its own grenade"; - else - message = "tripped on his own grenade"; - break; - case MOD_ROCKET_SPLASH: - if ( gender == GENDER_FEMALE ) - message = "blew herself up"; - else if ( gender == GENDER_NEUTER ) - message = "blew itself up"; - else - message = "blew himself up"; - break; - case MOD_PLASMA_SPLASH: - if ( gender == GENDER_FEMALE ) - message = "melted herself"; - else if ( gender == GENDER_NEUTER ) - message = "melted itself"; - else - message = "melted himself"; - break; - case MOD_BFG_SPLASH: - message = "should have used a smaller gun"; - break; -#ifdef MISSIONPACK - case MOD_PROXIMITY_MINE: - if( gender == GENDER_FEMALE ) { - message = "found her prox mine"; - } else if ( gender == GENDER_NEUTER ) { - message = "found it's prox mine"; - } else { - message = "found his prox mine"; - } - break; -#endif - default: - if ( gender == GENDER_FEMALE ) - message = "killed herself"; - else if ( gender == GENDER_NEUTER ) - message = "killed itself"; - else - message = "killed himself"; - break; - } - } - - if (message) { - CG_Printf( "%s %s.\n", targetName, message); - return; - } - - // check for kill messages from the current clientNum - if ( attacker == cg.snap->ps.clientNum ) { - char *s; - - if ( cgs.gametype < GT_TEAM ) { - s = va("You fragged %s\n%s place with %i", targetName, - CG_PlaceString( cg.snap->ps.persistant[PERS_RANK] + 1 ), - cg.snap->ps.persistant[PERS_SCORE] ); - } else { - s = va("You fragged %s", targetName ); - } -#ifdef MISSIONPACK - if (!(cg_singlePlayerActive.integer && cg_cameraOrbit.integer)) { - CG_CenterPrint( s, SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); - } -#else - CG_CenterPrint( s, SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); -#endif - - // print the text message as well - } - - // check for double client messages - if ( !attackerInfo ) { - attacker = ENTITYNUM_WORLD; - strcpy( attackerName, "noname" ); - } else { - Q_strncpyz( attackerName, Info_ValueForKey( attackerInfo, "n" ), sizeof(attackerName) - 2); - strcat( attackerName, S_COLOR_WHITE ); - // check for kill messages about the current clientNum - if ( target == cg.snap->ps.clientNum ) { - Q_strncpyz( cg.killerName, attackerName, sizeof( cg.killerName ) ); - } - } - - if ( attacker != ENTITYNUM_WORLD ) { - switch (mod) { - case MOD_GRAPPLE: - message = "was caught by"; - break; - case MOD_GAUNTLET: - message = "was pummeled by"; - break; - case MOD_MACHINEGUN: - message = "was machinegunned by"; - break; - case MOD_SHOTGUN: - message = "was gunned down by"; - break; - case MOD_GRENADE: - message = "ate"; - message2 = "'s grenade"; - break; - case MOD_GRENADE_SPLASH: - message = "was shredded by"; - message2 = "'s shrapnel"; - break; - case MOD_ROCKET: - message = "ate"; - message2 = "'s rocket"; - break; - case MOD_ROCKET_SPLASH: - message = "almost dodged"; - message2 = "'s rocket"; - break; - case MOD_PLASMA: - message = "was melted by"; - message2 = "'s plasmagun"; - break; - case MOD_PLASMA_SPLASH: - message = "was melted by"; - message2 = "'s plasmagun"; - break; - case MOD_RAILGUN: - message = "was railed by"; - break; - case MOD_LIGHTNING: - message = "was electrocuted by"; - break; - case MOD_BFG: - case MOD_BFG_SPLASH: - message = "was blasted by"; - message2 = "'s BFG"; - break; -#ifdef MISSIONPACK - case MOD_NAIL: - message = "was nailed by"; - break; - case MOD_CHAINGUN: - message = "got lead poisoning from"; - message2 = "'s Chaingun"; - break; - case MOD_PROXIMITY_MINE: - message = "was too close to"; - message2 = "'s Prox Mine"; - break; - case MOD_KAMIKAZE: - message = "falls to"; - message2 = "'s Kamikaze blast"; - break; - case MOD_JUICED: - message = "was juiced by"; - break; -#endif - case MOD_TELEFRAG: - message = "tried to invade"; - message2 = "'s personal space"; - break; - default: - message = "was killed by"; - break; - } - - if (message) { - CG_Printf( "%s %s %s%s\n", - targetName, message, attackerName, message2); - return; - } - } - - // we don't know what it was - CG_Printf( "%s died.\n", targetName ); -} - -//========================================================================== - -/* -=============== -CG_UseItem -=============== -*/ -static void CG_UseItem( centity_t *cent ) { - clientInfo_t *ci; - int itemNum, clientNum; - gitem_t *item; - entityState_t *es; - - es = ¢->currentState; - - itemNum = (es->event & ~EV_EVENT_BITS) - EV_USE_ITEM0; - if ( itemNum < 0 || itemNum > HI_NUM_HOLDABLE ) { - itemNum = 0; - } - - // print a message if the local player - if ( es->number == cg.snap->ps.clientNum ) { - if ( !itemNum ) { - CG_CenterPrint( "No item to use", SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); - } else { - item = BG_FindItemForHoldable( itemNum ); - CG_CenterPrint( va("Use %s", item->pickup_name), SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); - } - } - - switch ( itemNum ) { - default: - case HI_NONE: - trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.useNothingSound ); - break; - - case HI_TELEPORTER: - break; - - case HI_MEDKIT: - clientNum = cent->currentState.clientNum; - if ( clientNum >= 0 && clientNum < MAX_CLIENTS ) { - ci = &cgs.clientinfo[ clientNum ]; - ci->medkitUsageTime = cg.time; - } - trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.medkitSound ); - break; - -#ifdef MISSIONPACK - case HI_KAMIKAZE: - break; - - case HI_PORTAL: - break; - case HI_INVULNERABILITY: - trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.useInvulnerabilitySound ); - break; -#endif - } - -} - -/* -================ -CG_ItemPickup - -A new item was picked up this frame -================ -*/ -static void CG_ItemPickup( int itemNum ) { - cg.itemPickup = itemNum; - cg.itemPickupTime = cg.time; - cg.itemPickupBlendTime = cg.time; - // see if it should be the grabbed weapon - if ( bg_itemlist[itemNum].giType == IT_WEAPON ) { - // select it immediately - if ( cg_autoswitch.integer && bg_itemlist[itemNum].giTag != WP_MACHINEGUN ) { - cg.weaponSelectTime = cg.time; - cg.weaponSelect = bg_itemlist[itemNum].giTag; - } - } - -} - - -/* -================ -CG_PainEvent - -Also called by playerstate transition -================ -*/ -void CG_PainEvent( centity_t *cent, int health ) { - char *snd; - - // don't do more than two pain sounds a second - if ( cg.time - cent->pe.painTime < 500 ) { - return; - } - - if ( health < 25 ) { - snd = "*pain25_1.wav"; - } else if ( health < 50 ) { - snd = "*pain50_1.wav"; - } else if ( health < 75 ) { - snd = "*pain75_1.wav"; - } else { - snd = "*pain100_1.wav"; - } - trap_S_StartSound( NULL, cent->currentState.number, CHAN_VOICE, - CG_CustomSound( cent->currentState.number, snd ) ); - - // save pain time for programitic twitch animation - cent->pe.painTime = cg.time; - cent->pe.painDirection ^= 1; -} - - - -/* -============== -CG_EntityEvent - -An entity has an event value -also called by CG_CheckPlayerstateEvents -============== -*/ -#define DEBUGNAME(x) if(cg_debugEvents.integer){CG_Printf(x"\n");} -void CG_EntityEvent( centity_t *cent, vec3_t position ) { - entityState_t *es; - int event; - vec3_t dir; - const char *s; - int clientNum; - clientInfo_t *ci; - - es = ¢->currentState; - event = es->event & ~EV_EVENT_BITS; - - if ( cg_debugEvents.integer ) { - CG_Printf( "ent:%3i event:%3i ", es->number, event ); - } - - if ( !event ) { - DEBUGNAME("ZEROEVENT"); - return; - } - - clientNum = es->clientNum; - if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { - clientNum = 0; - } - ci = &cgs.clientinfo[ clientNum ]; - - switch ( event ) { - // - // movement generated events - // - case EV_FOOTSTEP: - DEBUGNAME("EV_FOOTSTEP"); - if (cg_footsteps.integer) { - trap_S_StartSound (NULL, es->number, CHAN_BODY, - cgs.media.footsteps[ ci->footsteps ][rand()&3] ); - } - break; - case EV_FOOTSTEP_METAL: - DEBUGNAME("EV_FOOTSTEP_METAL"); - if (cg_footsteps.integer) { - trap_S_StartSound (NULL, es->number, CHAN_BODY, - cgs.media.footsteps[ FOOTSTEP_METAL ][rand()&3] ); - } - break; - case EV_FOOTSPLASH: - DEBUGNAME("EV_FOOTSPLASH"); - if (cg_footsteps.integer) { - trap_S_StartSound (NULL, es->number, CHAN_BODY, - cgs.media.footsteps[ FOOTSTEP_SPLASH ][rand()&3] ); - } - break; - case EV_FOOTWADE: - DEBUGNAME("EV_FOOTWADE"); - if (cg_footsteps.integer) { - trap_S_StartSound (NULL, es->number, CHAN_BODY, - cgs.media.footsteps[ FOOTSTEP_SPLASH ][rand()&3] ); - } - break; - case EV_SWIM: - DEBUGNAME("EV_SWIM"); - if (cg_footsteps.integer) { - trap_S_StartSound (NULL, es->number, CHAN_BODY, - cgs.media.footsteps[ FOOTSTEP_SPLASH ][rand()&3] ); - } - break; - - - case EV_FALL_SHORT: - DEBUGNAME("EV_FALL_SHORT"); - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.landSound ); - if ( clientNum == cg.predictedPlayerState.clientNum ) { - // smooth landing z changes - cg.landChange = -8; - cg.landTime = cg.time; - } - break; - case EV_FALL_MEDIUM: - DEBUGNAME("EV_FALL_MEDIUM"); - // use normal pain sound - trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*pain100_1.wav" ) ); - if ( clientNum == cg.predictedPlayerState.clientNum ) { - // smooth landing z changes - cg.landChange = -16; - cg.landTime = cg.time; - } - break; - case EV_FALL_FAR: - DEBUGNAME("EV_FALL_FAR"); - trap_S_StartSound (NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*fall1.wav" ) ); - cent->pe.painTime = cg.time; // don't play a pain sound right after this - if ( clientNum == cg.predictedPlayerState.clientNum ) { - // smooth landing z changes - cg.landChange = -24; - cg.landTime = cg.time; - } - break; - - case EV_STEP_4: - case EV_STEP_8: - case EV_STEP_12: - case EV_STEP_16: // smooth out step up transitions - DEBUGNAME("EV_STEP"); - { - float oldStep; - int delta; - int step; - - if ( clientNum != cg.predictedPlayerState.clientNum ) { - break; - } - // if we are interpolating, we don't need to smooth steps - if ( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW) || - cg_nopredict.integer || cg_synchronousClients.integer ) { - break; - } - // check for stepping up before a previous step is completed - delta = cg.time - cg.stepTime; - if (delta < STEP_TIME) { - oldStep = cg.stepChange * (STEP_TIME - delta) / STEP_TIME; - } else { - oldStep = 0; - } - - // add this amount - step = 4 * (event - EV_STEP_4 + 1 ); - cg.stepChange = oldStep + step; - if ( cg.stepChange > MAX_STEP_CHANGE ) { - cg.stepChange = MAX_STEP_CHANGE; - } - cg.stepTime = cg.time; - break; - } - - case EV_JUMP_PAD: - DEBUGNAME("EV_JUMP_PAD"); -// CG_Printf( "EV_JUMP_PAD w/effect #%i\n", es->eventParm ); - { - localEntity_t *smoke; - vec3_t up = {0, 0, 1}; - - - smoke = CG_SmokePuff( cent->lerpOrigin, up, - 32, - 1, 1, 1, 0.33f, - 1000, - cg.time, 0, - LEF_PUFF_DONT_SCALE, - cgs.media.smokePuffShader ); - } - - // boing sound at origin, jump sound on player - trap_S_StartSound ( cent->lerpOrigin, -1, CHAN_VOICE, cgs.media.jumpPadSound ); - trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*jump1.wav" ) ); - break; - - case EV_JUMP: - DEBUGNAME("EV_JUMP"); - trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*jump1.wav" ) ); - break; - case EV_TAUNT: - DEBUGNAME("EV_TAUNT"); - trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*taunt.wav" ) ); - break; -#ifdef MISSIONPACK - case EV_TAUNT_YES: - DEBUGNAME("EV_TAUNT_YES"); - CG_VoiceChatLocal(SAY_TEAM, qfalse, es->number, COLOR_CYAN, VOICECHAT_YES); - break; - case EV_TAUNT_NO: - DEBUGNAME("EV_TAUNT_NO"); - CG_VoiceChatLocal(SAY_TEAM, qfalse, es->number, COLOR_CYAN, VOICECHAT_NO); - break; - case EV_TAUNT_FOLLOWME: - DEBUGNAME("EV_TAUNT_FOLLOWME"); - CG_VoiceChatLocal(SAY_TEAM, qfalse, es->number, COLOR_CYAN, VOICECHAT_FOLLOWME); - break; - case EV_TAUNT_GETFLAG: - DEBUGNAME("EV_TAUNT_GETFLAG"); - CG_VoiceChatLocal(SAY_TEAM, qfalse, es->number, COLOR_CYAN, VOICECHAT_ONGETFLAG); - break; - case EV_TAUNT_GUARDBASE: - DEBUGNAME("EV_TAUNT_GUARDBASE"); - CG_VoiceChatLocal(SAY_TEAM, qfalse, es->number, COLOR_CYAN, VOICECHAT_ONDEFENSE); - break; - case EV_TAUNT_PATROL: - DEBUGNAME("EV_TAUNT_PATROL"); - CG_VoiceChatLocal(SAY_TEAM, qfalse, es->number, COLOR_CYAN, VOICECHAT_ONPATROL); - break; -#endif - case EV_WATER_TOUCH: - DEBUGNAME("EV_WATER_TOUCH"); - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.watrInSound ); - break; - case EV_WATER_LEAVE: - DEBUGNAME("EV_WATER_LEAVE"); - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.watrOutSound ); - break; - case EV_WATER_UNDER: - DEBUGNAME("EV_WATER_UNDER"); - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.watrUnSound ); - break; - case EV_WATER_CLEAR: - DEBUGNAME("EV_WATER_CLEAR"); - trap_S_StartSound (NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*gasp.wav" ) ); - break; - - case EV_ITEM_PICKUP: - DEBUGNAME("EV_ITEM_PICKUP"); - { - gitem_t *item; - int index; - - index = es->eventParm; // player predicted - - if ( index < 1 || index >= bg_numItems ) { - break; - } - item = &bg_itemlist[ index ]; - - // powerups and team items will have a separate global sound, this one - // will be played at prediction time - if ( item->giType == IT_POWERUP || item->giType == IT_TEAM) { - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.n_healthSound ); - } else if (item->giType == IT_PERSISTANT_POWERUP) { -#ifdef MISSIONPACK - switch (item->giTag ) { - case PW_SCOUT: - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.scoutSound ); - break; - case PW_GUARD: - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.guardSound ); - break; - case PW_DOUBLER: - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.doublerSound ); - break; - case PW_AMMOREGEN: - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.ammoregenSound ); - break; - } -#endif - } else { - trap_S_StartSound (NULL, es->number, CHAN_AUTO, trap_S_RegisterSound( item->pickup_sound, qfalse ) ); - } - - // show icon and name on status bar - if ( es->number == cg.snap->ps.clientNum ) { - CG_ItemPickup( index ); - } - } - break; - - case EV_GLOBAL_ITEM_PICKUP: - DEBUGNAME("EV_GLOBAL_ITEM_PICKUP"); - { - gitem_t *item; - int index; - - index = es->eventParm; // player predicted - - if ( index < 1 || index >= bg_numItems ) { - break; - } - item = &bg_itemlist[ index ]; - // powerup pickups are global - if( item->pickup_sound ) { - trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_AUTO, trap_S_RegisterSound( item->pickup_sound, qfalse ) ); - } - - // show icon and name on status bar - if ( es->number == cg.snap->ps.clientNum ) { - CG_ItemPickup( index ); - } - } - break; - - // - // weapon events - // - case EV_NOAMMO: - DEBUGNAME("EV_NOAMMO"); -// trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.noAmmoSound ); - if ( es->number == cg.snap->ps.clientNum ) { - CG_OutOfAmmoChange(); - } - break; - case EV_CHANGE_WEAPON: - DEBUGNAME("EV_CHANGE_WEAPON"); - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.selectSound ); - break; - case EV_FIRE_WEAPON: - DEBUGNAME("EV_FIRE_WEAPON"); - CG_FireWeapon( cent ); - break; - - case EV_USE_ITEM0: - DEBUGNAME("EV_USE_ITEM0"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM1: - DEBUGNAME("EV_USE_ITEM1"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM2: - DEBUGNAME("EV_USE_ITEM2"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM3: - DEBUGNAME("EV_USE_ITEM3"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM4: - DEBUGNAME("EV_USE_ITEM4"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM5: - DEBUGNAME("EV_USE_ITEM5"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM6: - DEBUGNAME("EV_USE_ITEM6"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM7: - DEBUGNAME("EV_USE_ITEM7"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM8: - DEBUGNAME("EV_USE_ITEM8"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM9: - DEBUGNAME("EV_USE_ITEM9"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM10: - DEBUGNAME("EV_USE_ITEM10"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM11: - DEBUGNAME("EV_USE_ITEM11"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM12: - DEBUGNAME("EV_USE_ITEM12"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM13: - DEBUGNAME("EV_USE_ITEM13"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM14: - DEBUGNAME("EV_USE_ITEM14"); - CG_UseItem( cent ); - break; - - //================================================================= - - // - // other events - // - case EV_PLAYER_TELEPORT_IN: - DEBUGNAME("EV_PLAYER_TELEPORT_IN"); - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.teleInSound ); - CG_SpawnEffect( position); - break; - - case EV_PLAYER_TELEPORT_OUT: - DEBUGNAME("EV_PLAYER_TELEPORT_OUT"); - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.teleOutSound ); - CG_SpawnEffect( position); - break; - - case EV_ITEM_POP: - DEBUGNAME("EV_ITEM_POP"); - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.respawnSound ); - break; - case EV_ITEM_RESPAWN: - DEBUGNAME("EV_ITEM_RESPAWN"); - cent->miscTime = cg.time; // scale up from this - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.respawnSound ); - break; - - case EV_GRENADE_BOUNCE: - DEBUGNAME("EV_GRENADE_BOUNCE"); - if ( rand() & 1 ) { - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.hgrenb1aSound ); - } else { - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.hgrenb2aSound ); - } - break; - -#ifdef MISSIONPACK - case EV_PROXIMITY_MINE_STICK: - DEBUGNAME("EV_PROXIMITY_MINE_STICK"); - if( es->eventParm & SURF_FLESH ) { - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.wstbimplSound ); - } else if( es->eventParm & SURF_METALSTEPS ) { - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.wstbimpmSound ); - } else { - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.wstbimpdSound ); - } - break; - - case EV_PROXIMITY_MINE_TRIGGER: - DEBUGNAME("EV_PROXIMITY_MINE_TRIGGER"); - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.wstbactvSound ); - break; - case EV_KAMIKAZE: - DEBUGNAME("EV_KAMIKAZE"); - CG_KamikazeEffect( cent->lerpOrigin ); - break; - case EV_OBELISKEXPLODE: - DEBUGNAME("EV_OBELISKEXPLODE"); - CG_ObeliskExplode( cent->lerpOrigin, es->eventParm ); - break; - case EV_OBELISKPAIN: - DEBUGNAME("EV_OBELISKPAIN"); - CG_ObeliskPain( cent->lerpOrigin ); - break; - case EV_INVUL_IMPACT: - DEBUGNAME("EV_INVUL_IMPACT"); - CG_InvulnerabilityImpact( cent->lerpOrigin, cent->currentState.angles ); - break; - case EV_JUICED: - DEBUGNAME("EV_JUICED"); - CG_InvulnerabilityJuiced( cent->lerpOrigin ); - break; - case EV_LIGHTNINGBOLT: - DEBUGNAME("EV_LIGHTNINGBOLT"); - CG_LightningBoltBeam(es->origin2, es->pos.trBase); - break; -#endif - case EV_SCOREPLUM: - DEBUGNAME("EV_SCOREPLUM"); - CG_ScorePlum( cent->currentState.otherEntityNum, cent->lerpOrigin, cent->currentState.time ); - break; - - // - // missile impacts - // - case EV_MISSILE_HIT: - DEBUGNAME("EV_MISSILE_HIT"); - ByteToDir( es->eventParm, dir ); - CG_MissileHitPlayer( es->weapon, position, dir, es->otherEntityNum ); - break; - - case EV_MISSILE_MISS: - DEBUGNAME("EV_MISSILE_MISS"); - ByteToDir( es->eventParm, dir ); - CG_MissileHitWall( es->weapon, 0, position, dir, IMPACTSOUND_DEFAULT ); - break; - - case EV_MISSILE_MISS_METAL: - DEBUGNAME("EV_MISSILE_MISS_METAL"); - ByteToDir( es->eventParm, dir ); - CG_MissileHitWall( es->weapon, 0, position, dir, IMPACTSOUND_METAL ); - break; - - case EV_RAILTRAIL: - DEBUGNAME("EV_RAILTRAIL"); - cent->currentState.weapon = WP_RAILGUN; - // if the end was on a nomark surface, don't make an explosion - CG_RailTrail( ci, es->origin2, es->pos.trBase ); - if ( es->eventParm != 255 ) { - ByteToDir( es->eventParm, dir ); - CG_MissileHitWall( es->weapon, es->clientNum, position, dir, IMPACTSOUND_DEFAULT ); - } - break; - - case EV_BULLET_HIT_WALL: - DEBUGNAME("EV_BULLET_HIT_WALL"); - ByteToDir( es->eventParm, dir ); - CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qfalse, ENTITYNUM_WORLD ); - break; - - case EV_BULLET_HIT_FLESH: - DEBUGNAME("EV_BULLET_HIT_FLESH"); - CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qtrue, es->eventParm ); - break; - - case EV_SHOTGUN: - DEBUGNAME("EV_SHOTGUN"); - CG_ShotgunFire( es ); - break; - - case EV_GENERAL_SOUND: - DEBUGNAME("EV_GENERAL_SOUND"); - if ( cgs.gameSounds[ es->eventParm ] ) { - trap_S_StartSound (NULL, es->number, CHAN_VOICE, cgs.gameSounds[ es->eventParm ] ); - } else { - s = CG_ConfigString( CS_SOUNDS + es->eventParm ); - trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, s ) ); - } - break; - - case EV_GLOBAL_SOUND: // play from the player's head so it never diminishes - DEBUGNAME("EV_GLOBAL_SOUND"); - if ( cgs.gameSounds[ es->eventParm ] ) { - trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_AUTO, cgs.gameSounds[ es->eventParm ] ); - } else { - s = CG_ConfigString( CS_SOUNDS + es->eventParm ); - trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_AUTO, CG_CustomSound( es->number, s ) ); - } - break; - - case EV_GLOBAL_TEAM_SOUND: // play from the player's head so it never diminishes - { - DEBUGNAME("EV_GLOBAL_TEAM_SOUND"); - switch( es->eventParm ) { - case GTS_RED_CAPTURE: // CTF: red team captured the blue flag, 1FCTF: red team captured the neutral flag - if ( cgs.clientinfo[cg.clientNum].team == TEAM_RED ) - CG_AddBufferedSound( cgs.media.captureYourTeamSound ); - else - CG_AddBufferedSound( cgs.media.captureOpponentSound ); - break; - case GTS_BLUE_CAPTURE: // CTF: blue team captured the red flag, 1FCTF: blue team captured the neutral flag - if ( cgs.clientinfo[cg.clientNum].team == TEAM_BLUE ) - CG_AddBufferedSound( cgs.media.captureYourTeamSound ); - else - CG_AddBufferedSound( cgs.media.captureOpponentSound ); - break; - case GTS_RED_RETURN: // CTF: blue flag returned, 1FCTF: never used - if ( cgs.clientinfo[cg.clientNum].team == TEAM_RED ) - CG_AddBufferedSound( cgs.media.returnYourTeamSound ); - else - CG_AddBufferedSound( cgs.media.returnOpponentSound ); - // - CG_AddBufferedSound( cgs.media.blueFlagReturnedSound ); - break; - case GTS_BLUE_RETURN: // CTF red flag returned, 1FCTF: neutral flag returned - if ( cgs.clientinfo[cg.clientNum].team == TEAM_BLUE ) - CG_AddBufferedSound( cgs.media.returnYourTeamSound ); - else - CG_AddBufferedSound( cgs.media.returnOpponentSound ); - // - CG_AddBufferedSound( cgs.media.redFlagReturnedSound ); - break; - - case GTS_RED_TAKEN: // CTF: red team took blue flag, 1FCTF: blue team took the neutral flag - // if this player picked up the flag then a sound is played in CG_CheckLocalSounds - if (cg.snap->ps.powerups[PW_BLUEFLAG] || cg.snap->ps.powerups[PW_NEUTRALFLAG]) { - } - else { - if (cgs.clientinfo[cg.clientNum].team == TEAM_BLUE) { -#ifdef MISSIONPACK - if (cgs.gametype == GT_1FCTF) - CG_AddBufferedSound( cgs.media.yourTeamTookTheFlagSound ); - else -#endif - CG_AddBufferedSound( cgs.media.enemyTookYourFlagSound ); - } - else if (cgs.clientinfo[cg.clientNum].team == TEAM_RED) { -#ifdef MISSIONPACK - if (cgs.gametype == GT_1FCTF) - CG_AddBufferedSound( cgs.media.enemyTookTheFlagSound ); - else -#endif - CG_AddBufferedSound( cgs.media.yourTeamTookEnemyFlagSound ); - } - } - break; - case GTS_BLUE_TAKEN: // CTF: blue team took the red flag, 1FCTF red team took the neutral flag - // if this player picked up the flag then a sound is played in CG_CheckLocalSounds - if (cg.snap->ps.powerups[PW_REDFLAG] || cg.snap->ps.powerups[PW_NEUTRALFLAG]) { - } - else { - if (cgs.clientinfo[cg.clientNum].team == TEAM_RED) { -#ifdef MISSIONPACK - if (cgs.gametype == GT_1FCTF) - CG_AddBufferedSound( cgs.media.yourTeamTookTheFlagSound ); - else -#endif - CG_AddBufferedSound( cgs.media.enemyTookYourFlagSound ); - } - else if (cgs.clientinfo[cg.clientNum].team == TEAM_BLUE) { -#ifdef MISSIONPACK - if (cgs.gametype == GT_1FCTF) - CG_AddBufferedSound( cgs.media.enemyTookTheFlagSound ); - else -#endif - CG_AddBufferedSound( cgs.media.yourTeamTookEnemyFlagSound ); - } - } - break; - case GTS_REDOBELISK_ATTACKED: // Overload: red obelisk is being attacked - if (cgs.clientinfo[cg.clientNum].team == TEAM_RED) { - CG_AddBufferedSound( cgs.media.yourBaseIsUnderAttackSound ); - } - break; - case GTS_BLUEOBELISK_ATTACKED: // Overload: blue obelisk is being attacked - if (cgs.clientinfo[cg.clientNum].team == TEAM_BLUE) { - CG_AddBufferedSound( cgs.media.yourBaseIsUnderAttackSound ); - } - break; - - case GTS_REDTEAM_SCORED: - CG_AddBufferedSound(cgs.media.redScoredSound); - break; - case GTS_BLUETEAM_SCORED: - CG_AddBufferedSound(cgs.media.blueScoredSound); - break; - case GTS_REDTEAM_TOOK_LEAD: - CG_AddBufferedSound(cgs.media.redLeadsSound); - break; - case GTS_BLUETEAM_TOOK_LEAD: - CG_AddBufferedSound(cgs.media.blueLeadsSound); - break; - case GTS_TEAMS_ARE_TIED: - CG_AddBufferedSound( cgs.media.teamsTiedSound ); - break; -#ifdef MISSIONPACK - case GTS_KAMIKAZE: - trap_S_StartLocalSound(cgs.media.kamikazeFarSound, CHAN_ANNOUNCER); - break; -#endif - default: - break; - } - break; - } - - case EV_PAIN: - // local player sounds are triggered in CG_CheckLocalSounds, - // so ignore events on the player - DEBUGNAME("EV_PAIN"); - if ( cent->currentState.number != cg.snap->ps.clientNum ) { - CG_PainEvent( cent, es->eventParm ); - } - break; - - case EV_DEATH1: - case EV_DEATH2: - case EV_DEATH3: - DEBUGNAME("EV_DEATHx"); - trap_S_StartSound( NULL, es->number, CHAN_VOICE, - CG_CustomSound( es->number, va("*death%i.wav", event - EV_DEATH1 + 1) ) ); - break; - - - case EV_OBITUARY: - DEBUGNAME("EV_OBITUARY"); - CG_Obituary( es ); - break; - - // - // powerup events - // - case EV_POWERUP_QUAD: - DEBUGNAME("EV_POWERUP_QUAD"); - if ( es->number == cg.snap->ps.clientNum ) { - cg.powerupActive = PW_QUAD; - cg.powerupTime = cg.time; - } - trap_S_StartSound (NULL, es->number, CHAN_ITEM, cgs.media.quadSound ); - break; - case EV_POWERUP_BATTLESUIT: - DEBUGNAME("EV_POWERUP_BATTLESUIT"); - if ( es->number == cg.snap->ps.clientNum ) { - cg.powerupActive = PW_BATTLESUIT; - cg.powerupTime = cg.time; - } - trap_S_StartSound (NULL, es->number, CHAN_ITEM, cgs.media.protectSound ); - break; - case EV_POWERUP_REGEN: - DEBUGNAME("EV_POWERUP_REGEN"); - if ( es->number == cg.snap->ps.clientNum ) { - cg.powerupActive = PW_REGEN; - cg.powerupTime = cg.time; - } - trap_S_StartSound (NULL, es->number, CHAN_ITEM, cgs.media.regenSound ); - break; - - case EV_GIB_PLAYER: - DEBUGNAME("EV_GIB_PLAYER"); - // don't play gib sound when using the kamikaze because it interferes - // with the kamikaze sound, downside is that the gib sound will also - // not be played when someone is gibbed while just carrying the kamikaze - if ( !(es->eFlags & EF_KAMIKAZE) ) { - trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.gibSound ); - } - CG_GibPlayer( cent->lerpOrigin ); - break; - - case EV_STOPLOOPINGSOUND: - DEBUGNAME("EV_STOPLOOPINGSOUND"); - trap_S_StopLoopingSound( es->number ); - es->loopSound = 0; - break; - - case EV_DEBUG_LINE: - DEBUGNAME("EV_DEBUG_LINE"); - CG_Beam( cent ); - break; - - default: - DEBUGNAME("UNKNOWN"); - CG_Error( "Unknown event: %i", event ); - break; - } - -} - - -/* -============== -CG_CheckEvents - -============== -*/ -void CG_CheckEvents( centity_t *cent ) { - // check for event-only entities - if ( cent->currentState.eType > ET_EVENTS ) { - if ( cent->previousEvent ) { - return; // already fired - } - // if this is a player event set the entity number of the client entity number - if ( cent->currentState.eFlags & EF_PLAYER_EVENT ) { - cent->currentState.number = cent->currentState.otherEntityNum; - } - - cent->previousEvent = 1; - - cent->currentState.event = cent->currentState.eType - ET_EVENTS; - } else { - // check for events riding with another entity - if ( cent->currentState.event == cent->previousEvent ) { - return; - } - cent->previousEvent = cent->currentState.event; - if ( ( cent->currentState.event & ~EV_EVENT_BITS ) == 0 ) { - return; - } - } - - // calculate the position at exactly the frame time - BG_EvaluateTrajectory( ¢->currentState.pos, cg.snap->serverTime, cent->lerpOrigin ); - CG_SetEntitySoundPosition( cent ); - - CG_EntityEvent( cent, cent->lerpOrigin ); -} - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// cg_event.c -- handle entity events at snapshot or playerstate transitions + +#include "cg_local.h" + +// for the voice chats +#ifdef MISSIONPACK // bk001205 +#include "../../ui/menudef.h" +#endif +//========================================================================== + +/* +=================== +CG_PlaceString + +Also called by scoreboard drawing +=================== +*/ +const char *CG_PlaceString( int rank ) { + static char str[64]; + char *s, *t; + + if ( rank & RANK_TIED_FLAG ) { + rank &= ~RANK_TIED_FLAG; + t = "Tied for "; + } else { + t = ""; + } + + if ( rank == 1 ) { + s = S_COLOR_BLUE "1st" S_COLOR_WHITE; // draw in blue + } else if ( rank == 2 ) { + s = S_COLOR_RED "2nd" S_COLOR_WHITE; // draw in red + } else if ( rank == 3 ) { + s = S_COLOR_YELLOW "3rd" S_COLOR_WHITE; // draw in yellow + } else if ( rank == 11 ) { + s = "11th"; + } else if ( rank == 12 ) { + s = "12th"; + } else if ( rank == 13 ) { + s = "13th"; + } else if ( rank % 10 == 1 ) { + s = va("%ist", rank); + } else if ( rank % 10 == 2 ) { + s = va("%ind", rank); + } else if ( rank % 10 == 3 ) { + s = va("%ird", rank); + } else { + s = va("%ith", rank); + } + + Com_sprintf( str, sizeof( str ), "%s%s", t, s ); + return str; +} + +/* +============= +CG_Obituary +============= +*/ +static void CG_Obituary( entityState_t *ent ) { + int mod; + int target, attacker; + char *message; + char *message2; + const char *targetInfo; + const char *attackerInfo; + char targetName[32]; + char attackerName[32]; + gender_t gender; + clientInfo_t *ci; + + target = ent->otherEntityNum; + attacker = ent->otherEntityNum2; + mod = ent->eventParm; + + if ( target < 0 || target >= MAX_CLIENTS ) { + CG_Error( "CG_Obituary: target out of range" ); + } + ci = &cgs.clientinfo[target]; + + if ( attacker < 0 || attacker >= MAX_CLIENTS ) { + attacker = ENTITYNUM_WORLD; + attackerInfo = NULL; + } else { + attackerInfo = CG_ConfigString( CS_PLAYERS + attacker ); + } + + targetInfo = CG_ConfigString( CS_PLAYERS + target ); + if ( !targetInfo ) { + return; + } + Q_strncpyz( targetName, Info_ValueForKey( targetInfo, "n" ), sizeof(targetName) - 2); + strcat( targetName, S_COLOR_WHITE ); + + message2 = ""; + + // check for single client messages + + switch( mod ) { + case MOD_SUICIDE: + message = "suicides"; + break; + case MOD_FALLING: + message = "cratered"; + break; + case MOD_CRUSH: + message = "was squished"; + break; + case MOD_WATER: + message = "sank like a rock"; + break; + case MOD_SLIME: + message = "melted"; + break; + case MOD_LAVA: + message = "does a back flip into the lava"; + break; + case MOD_TARGET_LASER: + message = "saw the light"; + break; + case MOD_TRIGGER_HURT: + message = "was in the wrong place"; + break; + default: + message = NULL; + break; + } + + if (attacker == target) { + gender = ci->gender; + switch (mod) { +#ifdef MISSIONPACK + case MOD_KAMIKAZE: + message = "goes out with a bang"; + break; +#endif + case MOD_GRENADE_SPLASH: + if ( gender == GENDER_FEMALE ) + message = "tripped on her own grenade"; + else if ( gender == GENDER_NEUTER ) + message = "tripped on its own grenade"; + else + message = "tripped on his own grenade"; + break; + case MOD_ROCKET_SPLASH: + if ( gender == GENDER_FEMALE ) + message = "blew herself up"; + else if ( gender == GENDER_NEUTER ) + message = "blew itself up"; + else + message = "blew himself up"; + break; + case MOD_PLASMA_SPLASH: + if ( gender == GENDER_FEMALE ) + message = "melted herself"; + else if ( gender == GENDER_NEUTER ) + message = "melted itself"; + else + message = "melted himself"; + break; + case MOD_BFG_SPLASH: + message = "should have used a smaller gun"; + break; +#ifdef MISSIONPACK + case MOD_PROXIMITY_MINE: + if( gender == GENDER_FEMALE ) { + message = "found her prox mine"; + } else if ( gender == GENDER_NEUTER ) { + message = "found it's prox mine"; + } else { + message = "found his prox mine"; + } + break; +#endif + default: + if ( gender == GENDER_FEMALE ) + message = "killed herself"; + else if ( gender == GENDER_NEUTER ) + message = "killed itself"; + else + message = "killed himself"; + break; + } + } + + if (message) { + CG_Printf( "%s %s.\n", targetName, message); + return; + } + + // check for kill messages from the current clientNum + if ( attacker == cg.snap->ps.clientNum ) { + char *s; + + if ( cgs.gametype < GT_TEAM ) { + s = va("You fragged %s\n%s place with %i", targetName, + CG_PlaceString( cg.snap->ps.persistant[PERS_RANK] + 1 ), + cg.snap->ps.persistant[PERS_SCORE] ); + } else { + s = va("You fragged %s", targetName ); + } +#ifdef MISSIONPACK + if (!(cg_singlePlayerActive.integer && cg_cameraOrbit.integer)) { + CG_CenterPrint( s, SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); + } +#else + CG_CenterPrint( s, SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); +#endif + + // print the text message as well + } + + // check for double client messages + if ( !attackerInfo ) { + attacker = ENTITYNUM_WORLD; + strcpy( attackerName, "noname" ); + } else { + Q_strncpyz( attackerName, Info_ValueForKey( attackerInfo, "n" ), sizeof(attackerName) - 2); + strcat( attackerName, S_COLOR_WHITE ); + // check for kill messages about the current clientNum + if ( target == cg.snap->ps.clientNum ) { + Q_strncpyz( cg.killerName, attackerName, sizeof( cg.killerName ) ); + } + } + + if ( attacker != ENTITYNUM_WORLD ) { + switch (mod) { + case MOD_GRAPPLE: + message = "was caught by"; + break; + case MOD_GAUNTLET: + message = "was pummeled by"; + break; + case MOD_MACHINEGUN: + message = "was machinegunned by"; + break; + case MOD_SHOTGUN: + message = "was gunned down by"; + break; + case MOD_GRENADE: + message = "ate"; + message2 = "'s grenade"; + break; + case MOD_GRENADE_SPLASH: + message = "was shredded by"; + message2 = "'s shrapnel"; + break; + case MOD_ROCKET: + message = "ate"; + message2 = "'s rocket"; + break; + case MOD_ROCKET_SPLASH: + message = "almost dodged"; + message2 = "'s rocket"; + break; + case MOD_PLASMA: + message = "was melted by"; + message2 = "'s plasmagun"; + break; + case MOD_PLASMA_SPLASH: + message = "was melted by"; + message2 = "'s plasmagun"; + break; + case MOD_RAILGUN: + message = "was railed by"; + break; + case MOD_LIGHTNING: + message = "was electrocuted by"; + break; + case MOD_BFG: + case MOD_BFG_SPLASH: + message = "was blasted by"; + message2 = "'s BFG"; + break; +#ifdef MISSIONPACK + case MOD_NAIL: + message = "was nailed by"; + break; + case MOD_CHAINGUN: + message = "got lead poisoning from"; + message2 = "'s Chaingun"; + break; + case MOD_PROXIMITY_MINE: + message = "was too close to"; + message2 = "'s Prox Mine"; + break; + case MOD_KAMIKAZE: + message = "falls to"; + message2 = "'s Kamikaze blast"; + break; + case MOD_JUICED: + message = "was juiced by"; + break; +#endif + case MOD_TELEFRAG: + message = "tried to invade"; + message2 = "'s personal space"; + break; + default: + message = "was killed by"; + break; + } + + if (message) { + CG_Printf( "%s %s %s%s\n", + targetName, message, attackerName, message2); + return; + } + } + + // we don't know what it was + CG_Printf( "%s died.\n", targetName ); +} + +//========================================================================== + +/* +=============== +CG_UseItem +=============== +*/ +static void CG_UseItem( centity_t *cent ) { + clientInfo_t *ci; + int itemNum, clientNum; + gitem_t *item; + entityState_t *es; + + es = ¢->currentState; + + itemNum = (es->event & ~EV_EVENT_BITS) - EV_USE_ITEM0; + if ( itemNum < 0 || itemNum > HI_NUM_HOLDABLE ) { + itemNum = 0; + } + + // print a message if the local player + if ( es->number == cg.snap->ps.clientNum ) { + if ( !itemNum ) { + CG_CenterPrint( "No item to use", SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); + } else { + item = BG_FindItemForHoldable( itemNum ); + CG_CenterPrint( va("Use %s", item->pickup_name), SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); + } + } + + switch ( itemNum ) { + default: + case HI_NONE: + trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.useNothingSound ); + break; + + case HI_TELEPORTER: + break; + + case HI_MEDKIT: + clientNum = cent->currentState.clientNum; + if ( clientNum >= 0 && clientNum < MAX_CLIENTS ) { + ci = &cgs.clientinfo[ clientNum ]; + ci->medkitUsageTime = cg.time; + } + trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.medkitSound ); + break; + +#ifdef MISSIONPACK + case HI_KAMIKAZE: + break; + + case HI_PORTAL: + break; + case HI_INVULNERABILITY: + trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.useInvulnerabilitySound ); + break; +#endif + } + +} + +/* +================ +CG_ItemPickup + +A new item was picked up this frame +================ +*/ +static void CG_ItemPickup( int itemNum ) { + cg.itemPickup = itemNum; + cg.itemPickupTime = cg.time; + cg.itemPickupBlendTime = cg.time; + // see if it should be the grabbed weapon + if ( bg_itemlist[itemNum].giType == IT_WEAPON ) { + // select it immediately + if ( cg_autoswitch.integer && bg_itemlist[itemNum].giTag != WP_MACHINEGUN ) { + cg.weaponSelectTime = cg.time; + cg.weaponSelect = bg_itemlist[itemNum].giTag; + } + } + +} + + +/* +================ +CG_PainEvent + +Also called by playerstate transition +================ +*/ +void CG_PainEvent( centity_t *cent, int health ) { + char *snd; + + // don't do more than two pain sounds a second + if ( cg.time - cent->pe.painTime < 500 ) { + return; + } + + if ( health < 25 ) { + snd = "*pain25_1.wav"; + } else if ( health < 50 ) { + snd = "*pain50_1.wav"; + } else if ( health < 75 ) { + snd = "*pain75_1.wav"; + } else { + snd = "*pain100_1.wav"; + } + trap_S_StartSound( NULL, cent->currentState.number, CHAN_VOICE, + CG_CustomSound( cent->currentState.number, snd ) ); + + // save pain time for programitic twitch animation + cent->pe.painTime = cg.time; + cent->pe.painDirection ^= 1; +} + + + +/* +============== +CG_EntityEvent + +An entity has an event value +also called by CG_CheckPlayerstateEvents +============== +*/ +#define DEBUGNAME(x) if(cg_debugEvents.integer){CG_Printf(x"\n");} +void CG_EntityEvent( centity_t *cent, vec3_t position ) { + entityState_t *es; + int event; + vec3_t dir; + const char *s; + int clientNum; + clientInfo_t *ci; + + es = ¢->currentState; + event = es->event & ~EV_EVENT_BITS; + + if ( cg_debugEvents.integer ) { + CG_Printf( "ent:%3i event:%3i ", es->number, event ); + } + + if ( !event ) { + DEBUGNAME("ZEROEVENT"); + return; + } + + clientNum = es->clientNum; + if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { + clientNum = 0; + } + ci = &cgs.clientinfo[ clientNum ]; + + switch ( event ) { + // + // movement generated events + // + case EV_FOOTSTEP: + DEBUGNAME("EV_FOOTSTEP"); + if (cg_footsteps.integer) { + trap_S_StartSound (NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ ci->footsteps ][rand()&3] ); + } + break; + case EV_FOOTSTEP_METAL: + DEBUGNAME("EV_FOOTSTEP_METAL"); + if (cg_footsteps.integer) { + trap_S_StartSound (NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_METAL ][rand()&3] ); + } + break; + case EV_FOOTSPLASH: + DEBUGNAME("EV_FOOTSPLASH"); + if (cg_footsteps.integer) { + trap_S_StartSound (NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_SPLASH ][rand()&3] ); + } + break; + case EV_FOOTWADE: + DEBUGNAME("EV_FOOTWADE"); + if (cg_footsteps.integer) { + trap_S_StartSound (NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_SPLASH ][rand()&3] ); + } + break; + case EV_SWIM: + DEBUGNAME("EV_SWIM"); + if (cg_footsteps.integer) { + trap_S_StartSound (NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_SPLASH ][rand()&3] ); + } + break; + + + case EV_FALL_SHORT: + DEBUGNAME("EV_FALL_SHORT"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.landSound ); + if ( clientNum == cg.predictedPlayerState.clientNum ) { + // smooth landing z changes + cg.landChange = -8; + cg.landTime = cg.time; + } + break; + case EV_FALL_MEDIUM: + DEBUGNAME("EV_FALL_MEDIUM"); + // use normal pain sound + trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*pain100_1.wav" ) ); + if ( clientNum == cg.predictedPlayerState.clientNum ) { + // smooth landing z changes + cg.landChange = -16; + cg.landTime = cg.time; + } + break; + case EV_FALL_FAR: + DEBUGNAME("EV_FALL_FAR"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*fall1.wav" ) ); + cent->pe.painTime = cg.time; // don't play a pain sound right after this + if ( clientNum == cg.predictedPlayerState.clientNum ) { + // smooth landing z changes + cg.landChange = -24; + cg.landTime = cg.time; + } + break; + + case EV_STEP_4: + case EV_STEP_8: + case EV_STEP_12: + case EV_STEP_16: // smooth out step up transitions + DEBUGNAME("EV_STEP"); + { + float oldStep; + int delta; + int step; + + if ( clientNum != cg.predictedPlayerState.clientNum ) { + break; + } + // if we are interpolating, we don't need to smooth steps + if ( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW) || + cg_nopredict.integer || cg_synchronousClients.integer ) { + break; + } + // check for stepping up before a previous step is completed + delta = cg.time - cg.stepTime; + if (delta < STEP_TIME) { + oldStep = cg.stepChange * (STEP_TIME - delta) / STEP_TIME; + } else { + oldStep = 0; + } + + // add this amount + step = 4 * (event - EV_STEP_4 + 1 ); + cg.stepChange = oldStep + step; + if ( cg.stepChange > MAX_STEP_CHANGE ) { + cg.stepChange = MAX_STEP_CHANGE; + } + cg.stepTime = cg.time; + break; + } + + case EV_JUMP_PAD: + DEBUGNAME("EV_JUMP_PAD"); +// CG_Printf( "EV_JUMP_PAD w/effect #%i\n", es->eventParm ); + { + localEntity_t *smoke; + vec3_t up = {0, 0, 1}; + + + smoke = CG_SmokePuff( cent->lerpOrigin, up, + 32, + 1, 1, 1, 0.33f, + 1000, + cg.time, 0, + LEF_PUFF_DONT_SCALE, + cgs.media.smokePuffShader ); + } + + // boing sound at origin, jump sound on player + trap_S_StartSound ( cent->lerpOrigin, -1, CHAN_VOICE, cgs.media.jumpPadSound ); + trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*jump1.wav" ) ); + break; + + case EV_JUMP: + DEBUGNAME("EV_JUMP"); + trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*jump1.wav" ) ); + break; + case EV_TAUNT: + DEBUGNAME("EV_TAUNT"); + trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*taunt.wav" ) ); + break; +#ifdef MISSIONPACK + case EV_TAUNT_YES: + DEBUGNAME("EV_TAUNT_YES"); + CG_VoiceChatLocal(SAY_TEAM, qfalse, es->number, COLOR_CYAN, VOICECHAT_YES); + break; + case EV_TAUNT_NO: + DEBUGNAME("EV_TAUNT_NO"); + CG_VoiceChatLocal(SAY_TEAM, qfalse, es->number, COLOR_CYAN, VOICECHAT_NO); + break; + case EV_TAUNT_FOLLOWME: + DEBUGNAME("EV_TAUNT_FOLLOWME"); + CG_VoiceChatLocal(SAY_TEAM, qfalse, es->number, COLOR_CYAN, VOICECHAT_FOLLOWME); + break; + case EV_TAUNT_GETFLAG: + DEBUGNAME("EV_TAUNT_GETFLAG"); + CG_VoiceChatLocal(SAY_TEAM, qfalse, es->number, COLOR_CYAN, VOICECHAT_ONGETFLAG); + break; + case EV_TAUNT_GUARDBASE: + DEBUGNAME("EV_TAUNT_GUARDBASE"); + CG_VoiceChatLocal(SAY_TEAM, qfalse, es->number, COLOR_CYAN, VOICECHAT_ONDEFENSE); + break; + case EV_TAUNT_PATROL: + DEBUGNAME("EV_TAUNT_PATROL"); + CG_VoiceChatLocal(SAY_TEAM, qfalse, es->number, COLOR_CYAN, VOICECHAT_ONPATROL); + break; +#endif + case EV_WATER_TOUCH: + DEBUGNAME("EV_WATER_TOUCH"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.watrInSound ); + break; + case EV_WATER_LEAVE: + DEBUGNAME("EV_WATER_LEAVE"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.watrOutSound ); + break; + case EV_WATER_UNDER: + DEBUGNAME("EV_WATER_UNDER"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.watrUnSound ); + break; + case EV_WATER_CLEAR: + DEBUGNAME("EV_WATER_CLEAR"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*gasp.wav" ) ); + break; + + case EV_ITEM_PICKUP: + DEBUGNAME("EV_ITEM_PICKUP"); + { + gitem_t *item; + int index; + + index = es->eventParm; // player predicted + + if ( index < 1 || index >= bg_numItems ) { + break; + } + item = &bg_itemlist[ index ]; + + // powerups and team items will have a separate global sound, this one + // will be played at prediction time + if ( item->giType == IT_POWERUP || item->giType == IT_TEAM) { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.n_healthSound ); + } else if (item->giType == IT_PERSISTANT_POWERUP) { +#ifdef MISSIONPACK + switch (item->giTag ) { + case PW_SCOUT: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.scoutSound ); + break; + case PW_GUARD: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.guardSound ); + break; + case PW_DOUBLER: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.doublerSound ); + break; + case PW_AMMOREGEN: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.ammoregenSound ); + break; + } +#endif + } else { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, trap_S_RegisterSound( item->pickup_sound, qfalse ) ); + } + + // show icon and name on status bar + if ( es->number == cg.snap->ps.clientNum ) { + CG_ItemPickup( index ); + } + } + break; + + case EV_GLOBAL_ITEM_PICKUP: + DEBUGNAME("EV_GLOBAL_ITEM_PICKUP"); + { + gitem_t *item; + int index; + + index = es->eventParm; // player predicted + + if ( index < 1 || index >= bg_numItems ) { + break; + } + item = &bg_itemlist[ index ]; + // powerup pickups are global + if( item->pickup_sound ) { + trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_AUTO, trap_S_RegisterSound( item->pickup_sound, qfalse ) ); + } + + // show icon and name on status bar + if ( es->number == cg.snap->ps.clientNum ) { + CG_ItemPickup( index ); + } + } + break; + + // + // weapon events + // + case EV_NOAMMO: + DEBUGNAME("EV_NOAMMO"); +// trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.noAmmoSound ); + if ( es->number == cg.snap->ps.clientNum ) { + CG_OutOfAmmoChange(); + } + break; + case EV_CHANGE_WEAPON: + DEBUGNAME("EV_CHANGE_WEAPON"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.selectSound ); + break; + case EV_FIRE_WEAPON: + DEBUGNAME("EV_FIRE_WEAPON"); + CG_FireWeapon( cent ); + break; + + case EV_USE_ITEM0: + DEBUGNAME("EV_USE_ITEM0"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM1: + DEBUGNAME("EV_USE_ITEM1"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM2: + DEBUGNAME("EV_USE_ITEM2"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM3: + DEBUGNAME("EV_USE_ITEM3"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM4: + DEBUGNAME("EV_USE_ITEM4"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM5: + DEBUGNAME("EV_USE_ITEM5"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM6: + DEBUGNAME("EV_USE_ITEM6"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM7: + DEBUGNAME("EV_USE_ITEM7"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM8: + DEBUGNAME("EV_USE_ITEM8"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM9: + DEBUGNAME("EV_USE_ITEM9"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM10: + DEBUGNAME("EV_USE_ITEM10"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM11: + DEBUGNAME("EV_USE_ITEM11"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM12: + DEBUGNAME("EV_USE_ITEM12"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM13: + DEBUGNAME("EV_USE_ITEM13"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM14: + DEBUGNAME("EV_USE_ITEM14"); + CG_UseItem( cent ); + break; + + //================================================================= + + // + // other events + // + case EV_PLAYER_TELEPORT_IN: + DEBUGNAME("EV_PLAYER_TELEPORT_IN"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.teleInSound ); + CG_SpawnEffect( position); + break; + + case EV_PLAYER_TELEPORT_OUT: + DEBUGNAME("EV_PLAYER_TELEPORT_OUT"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.teleOutSound ); + CG_SpawnEffect( position); + break; + + case EV_ITEM_POP: + DEBUGNAME("EV_ITEM_POP"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.respawnSound ); + break; + case EV_ITEM_RESPAWN: + DEBUGNAME("EV_ITEM_RESPAWN"); + cent->miscTime = cg.time; // scale up from this + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.respawnSound ); + break; + + case EV_GRENADE_BOUNCE: + DEBUGNAME("EV_GRENADE_BOUNCE"); + if ( rand() & 1 ) { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.hgrenb1aSound ); + } else { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.hgrenb2aSound ); + } + break; + +#ifdef MISSIONPACK + case EV_PROXIMITY_MINE_STICK: + DEBUGNAME("EV_PROXIMITY_MINE_STICK"); + if( es->eventParm & SURF_FLESH ) { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.wstbimplSound ); + } else if( es->eventParm & SURF_METALSTEPS ) { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.wstbimpmSound ); + } else { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.wstbimpdSound ); + } + break; + + case EV_PROXIMITY_MINE_TRIGGER: + DEBUGNAME("EV_PROXIMITY_MINE_TRIGGER"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.wstbactvSound ); + break; + case EV_KAMIKAZE: + DEBUGNAME("EV_KAMIKAZE"); + CG_KamikazeEffect( cent->lerpOrigin ); + break; + case EV_OBELISKEXPLODE: + DEBUGNAME("EV_OBELISKEXPLODE"); + CG_ObeliskExplode( cent->lerpOrigin, es->eventParm ); + break; + case EV_OBELISKPAIN: + DEBUGNAME("EV_OBELISKPAIN"); + CG_ObeliskPain( cent->lerpOrigin ); + break; + case EV_INVUL_IMPACT: + DEBUGNAME("EV_INVUL_IMPACT"); + CG_InvulnerabilityImpact( cent->lerpOrigin, cent->currentState.angles ); + break; + case EV_JUICED: + DEBUGNAME("EV_JUICED"); + CG_InvulnerabilityJuiced( cent->lerpOrigin ); + break; + case EV_LIGHTNINGBOLT: + DEBUGNAME("EV_LIGHTNINGBOLT"); + CG_LightningBoltBeam(es->origin2, es->pos.trBase); + break; +#endif + case EV_SCOREPLUM: + DEBUGNAME("EV_SCOREPLUM"); + CG_ScorePlum( cent->currentState.otherEntityNum, cent->lerpOrigin, cent->currentState.time ); + break; + + // + // missile impacts + // + case EV_MISSILE_HIT: + DEBUGNAME("EV_MISSILE_HIT"); + ByteToDir( es->eventParm, dir ); + CG_MissileHitPlayer( es->weapon, position, dir, es->otherEntityNum ); + break; + + case EV_MISSILE_MISS: + DEBUGNAME("EV_MISSILE_MISS"); + ByteToDir( es->eventParm, dir ); + CG_MissileHitWall( es->weapon, 0, position, dir, IMPACTSOUND_DEFAULT ); + break; + + case EV_MISSILE_MISS_METAL: + DEBUGNAME("EV_MISSILE_MISS_METAL"); + ByteToDir( es->eventParm, dir ); + CG_MissileHitWall( es->weapon, 0, position, dir, IMPACTSOUND_METAL ); + break; + + case EV_RAILTRAIL: + DEBUGNAME("EV_RAILTRAIL"); + cent->currentState.weapon = WP_RAILGUN; + // if the end was on a nomark surface, don't make an explosion + CG_RailTrail( ci, es->origin2, es->pos.trBase ); + if ( es->eventParm != 255 ) { + ByteToDir( es->eventParm, dir ); + CG_MissileHitWall( es->weapon, es->clientNum, position, dir, IMPACTSOUND_DEFAULT ); + } + break; + + case EV_BULLET_HIT_WALL: + DEBUGNAME("EV_BULLET_HIT_WALL"); + ByteToDir( es->eventParm, dir ); + CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qfalse, ENTITYNUM_WORLD ); + break; + + case EV_BULLET_HIT_FLESH: + DEBUGNAME("EV_BULLET_HIT_FLESH"); + CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qtrue, es->eventParm ); + break; + + case EV_SHOTGUN: + DEBUGNAME("EV_SHOTGUN"); + CG_ShotgunFire( es ); + break; + + case EV_GENERAL_SOUND: + DEBUGNAME("EV_GENERAL_SOUND"); + if ( cgs.gameSounds[ es->eventParm ] ) { + trap_S_StartSound (NULL, es->number, CHAN_VOICE, cgs.gameSounds[ es->eventParm ] ); + } else { + s = CG_ConfigString( CS_SOUNDS + es->eventParm ); + trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, s ) ); + } + break; + + case EV_GLOBAL_SOUND: // play from the player's head so it never diminishes + DEBUGNAME("EV_GLOBAL_SOUND"); + if ( cgs.gameSounds[ es->eventParm ] ) { + trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_AUTO, cgs.gameSounds[ es->eventParm ] ); + } else { + s = CG_ConfigString( CS_SOUNDS + es->eventParm ); + trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_AUTO, CG_CustomSound( es->number, s ) ); + } + break; + + case EV_GLOBAL_TEAM_SOUND: // play from the player's head so it never diminishes + { + DEBUGNAME("EV_GLOBAL_TEAM_SOUND"); + switch( es->eventParm ) { + case GTS_RED_CAPTURE: // CTF: red team captured the blue flag, 1FCTF: red team captured the neutral flag + if ( cgs.clientinfo[cg.clientNum].team == TEAM_RED ) + CG_AddBufferedSound( cgs.media.captureYourTeamSound ); + else + CG_AddBufferedSound( cgs.media.captureOpponentSound ); + break; + case GTS_BLUE_CAPTURE: // CTF: blue team captured the red flag, 1FCTF: blue team captured the neutral flag + if ( cgs.clientinfo[cg.clientNum].team == TEAM_BLUE ) + CG_AddBufferedSound( cgs.media.captureYourTeamSound ); + else + CG_AddBufferedSound( cgs.media.captureOpponentSound ); + break; + case GTS_RED_RETURN: // CTF: blue flag returned, 1FCTF: never used + if ( cgs.clientinfo[cg.clientNum].team == TEAM_RED ) + CG_AddBufferedSound( cgs.media.returnYourTeamSound ); + else + CG_AddBufferedSound( cgs.media.returnOpponentSound ); + // + CG_AddBufferedSound( cgs.media.blueFlagReturnedSound ); + break; + case GTS_BLUE_RETURN: // CTF red flag returned, 1FCTF: neutral flag returned + if ( cgs.clientinfo[cg.clientNum].team == TEAM_BLUE ) + CG_AddBufferedSound( cgs.media.returnYourTeamSound ); + else + CG_AddBufferedSound( cgs.media.returnOpponentSound ); + // + CG_AddBufferedSound( cgs.media.redFlagReturnedSound ); + break; + + case GTS_RED_TAKEN: // CTF: red team took blue flag, 1FCTF: blue team took the neutral flag + // if this player picked up the flag then a sound is played in CG_CheckLocalSounds + if (cg.snap->ps.powerups[PW_BLUEFLAG] || cg.snap->ps.powerups[PW_NEUTRALFLAG]) { + } + else { + if (cgs.clientinfo[cg.clientNum].team == TEAM_BLUE) { +#ifdef MISSIONPACK + if (cgs.gametype == GT_1FCTF) + CG_AddBufferedSound( cgs.media.yourTeamTookTheFlagSound ); + else +#endif + CG_AddBufferedSound( cgs.media.enemyTookYourFlagSound ); + } + else if (cgs.clientinfo[cg.clientNum].team == TEAM_RED) { +#ifdef MISSIONPACK + if (cgs.gametype == GT_1FCTF) + CG_AddBufferedSound( cgs.media.enemyTookTheFlagSound ); + else +#endif + CG_AddBufferedSound( cgs.media.yourTeamTookEnemyFlagSound ); + } + } + break; + case GTS_BLUE_TAKEN: // CTF: blue team took the red flag, 1FCTF red team took the neutral flag + // if this player picked up the flag then a sound is played in CG_CheckLocalSounds + if (cg.snap->ps.powerups[PW_REDFLAG] || cg.snap->ps.powerups[PW_NEUTRALFLAG]) { + } + else { + if (cgs.clientinfo[cg.clientNum].team == TEAM_RED) { +#ifdef MISSIONPACK + if (cgs.gametype == GT_1FCTF) + CG_AddBufferedSound( cgs.media.yourTeamTookTheFlagSound ); + else +#endif + CG_AddBufferedSound( cgs.media.enemyTookYourFlagSound ); + } + else if (cgs.clientinfo[cg.clientNum].team == TEAM_BLUE) { +#ifdef MISSIONPACK + if (cgs.gametype == GT_1FCTF) + CG_AddBufferedSound( cgs.media.enemyTookTheFlagSound ); + else +#endif + CG_AddBufferedSound( cgs.media.yourTeamTookEnemyFlagSound ); + } + } + break; + case GTS_REDOBELISK_ATTACKED: // Overload: red obelisk is being attacked + if (cgs.clientinfo[cg.clientNum].team == TEAM_RED) { + CG_AddBufferedSound( cgs.media.yourBaseIsUnderAttackSound ); + } + break; + case GTS_BLUEOBELISK_ATTACKED: // Overload: blue obelisk is being attacked + if (cgs.clientinfo[cg.clientNum].team == TEAM_BLUE) { + CG_AddBufferedSound( cgs.media.yourBaseIsUnderAttackSound ); + } + break; + + case GTS_REDTEAM_SCORED: + CG_AddBufferedSound(cgs.media.redScoredSound); + break; + case GTS_BLUETEAM_SCORED: + CG_AddBufferedSound(cgs.media.blueScoredSound); + break; + case GTS_REDTEAM_TOOK_LEAD: + CG_AddBufferedSound(cgs.media.redLeadsSound); + break; + case GTS_BLUETEAM_TOOK_LEAD: + CG_AddBufferedSound(cgs.media.blueLeadsSound); + break; + case GTS_TEAMS_ARE_TIED: + CG_AddBufferedSound( cgs.media.teamsTiedSound ); + break; +#ifdef MISSIONPACK + case GTS_KAMIKAZE: + trap_S_StartLocalSound(cgs.media.kamikazeFarSound, CHAN_ANNOUNCER); + break; +#endif + default: + break; + } + break; + } + + case EV_PAIN: + // local player sounds are triggered in CG_CheckLocalSounds, + // so ignore events on the player + DEBUGNAME("EV_PAIN"); + if ( cent->currentState.number != cg.snap->ps.clientNum ) { + CG_PainEvent( cent, es->eventParm ); + } + break; + + case EV_DEATH1: + case EV_DEATH2: + case EV_DEATH3: + DEBUGNAME("EV_DEATHx"); + trap_S_StartSound( NULL, es->number, CHAN_VOICE, + CG_CustomSound( es->number, va("*death%i.wav", event - EV_DEATH1 + 1) ) ); + break; + + + case EV_OBITUARY: + DEBUGNAME("EV_OBITUARY"); + CG_Obituary( es ); + break; + + // + // powerup events + // + case EV_POWERUP_QUAD: + DEBUGNAME("EV_POWERUP_QUAD"); + if ( es->number == cg.snap->ps.clientNum ) { + cg.powerupActive = PW_QUAD; + cg.powerupTime = cg.time; + } + trap_S_StartSound (NULL, es->number, CHAN_ITEM, cgs.media.quadSound ); + break; + case EV_POWERUP_BATTLESUIT: + DEBUGNAME("EV_POWERUP_BATTLESUIT"); + if ( es->number == cg.snap->ps.clientNum ) { + cg.powerupActive = PW_BATTLESUIT; + cg.powerupTime = cg.time; + } + trap_S_StartSound (NULL, es->number, CHAN_ITEM, cgs.media.protectSound ); + break; + case EV_POWERUP_REGEN: + DEBUGNAME("EV_POWERUP_REGEN"); + if ( es->number == cg.snap->ps.clientNum ) { + cg.powerupActive = PW_REGEN; + cg.powerupTime = cg.time; + } + trap_S_StartSound (NULL, es->number, CHAN_ITEM, cgs.media.regenSound ); + break; + + case EV_GIB_PLAYER: + DEBUGNAME("EV_GIB_PLAYER"); + // don't play gib sound when using the kamikaze because it interferes + // with the kamikaze sound, downside is that the gib sound will also + // not be played when someone is gibbed while just carrying the kamikaze + if ( !(es->eFlags & EF_KAMIKAZE) ) { + trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.gibSound ); + } + CG_GibPlayer( cent->lerpOrigin ); + break; + + case EV_STOPLOOPINGSOUND: + DEBUGNAME("EV_STOPLOOPINGSOUND"); + trap_S_StopLoopingSound( es->number ); + es->loopSound = 0; + break; + + case EV_DEBUG_LINE: + DEBUGNAME("EV_DEBUG_LINE"); + CG_Beam( cent ); + break; + + default: + DEBUGNAME("UNKNOWN"); + CG_Error( "Unknown event: %i", event ); + break; + } + +} + + +/* +============== +CG_CheckEvents + +============== +*/ +void CG_CheckEvents( centity_t *cent ) { + // check for event-only entities + if ( cent->currentState.eType > ET_EVENTS ) { + if ( cent->previousEvent ) { + return; // already fired + } + // if this is a player event set the entity number of the client entity number + if ( cent->currentState.eFlags & EF_PLAYER_EVENT ) { + cent->currentState.number = cent->currentState.otherEntityNum; + } + + cent->previousEvent = 1; + + cent->currentState.event = cent->currentState.eType - ET_EVENTS; + } else { + // check for events riding with another entity + if ( cent->currentState.event == cent->previousEvent ) { + return; + } + cent->previousEvent = cent->currentState.event; + if ( ( cent->currentState.event & ~EV_EVENT_BITS ) == 0 ) { + return; + } + } + + // calculate the position at exactly the frame time + BG_EvaluateTrajectory( ¢->currentState.pos, cg.snap->serverTime, cent->lerpOrigin ); + CG_SetEntitySoundPosition( cent ); + + CG_EntityEvent( cent, cent->lerpOrigin ); +} + diff --git a/code/cgame/cg_info.c b/code/cgame/cg_info.c index 04124d1..53f0b05 100755 --- a/code/cgame/cg_info.c +++ b/code/cgame/cg_info.c @@ -1,297 +1,297 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// cg_info.c -- display information while data is being loading - -#include "cg_local.h" - -#define MAX_LOADING_PLAYER_ICONS 16 -#define MAX_LOADING_ITEM_ICONS 26 - -static int loadingPlayerIconCount; -static int loadingItemIconCount; -static qhandle_t loadingPlayerIcons[MAX_LOADING_PLAYER_ICONS]; -static qhandle_t loadingItemIcons[MAX_LOADING_ITEM_ICONS]; - - -/* -=================== -CG_DrawLoadingIcons -=================== -*/ -static void CG_DrawLoadingIcons( void ) { - int n; - int x, y; - - for( n = 0; n < loadingPlayerIconCount; n++ ) { - x = 16 + n * 78; - y = 324-40; - CG_DrawPic( x, y, 64, 64, loadingPlayerIcons[n] ); - } - - for( n = 0; n < loadingItemIconCount; n++ ) { - y = 400-40; - if( n >= 13 ) { - y += 40; - } - x = 16 + n % 13 * 48; - CG_DrawPic( x, y, 32, 32, loadingItemIcons[n] ); - } -} - - -/* -====================== -CG_LoadingString - -====================== -*/ -void CG_LoadingString( const char *s ) { - Q_strncpyz( cg.infoScreenText, s, sizeof( cg.infoScreenText ) ); - - trap_UpdateScreen(); -} - -/* -=================== -CG_LoadingItem -=================== -*/ -void CG_LoadingItem( int itemNum ) { - gitem_t *item; - - item = &bg_itemlist[itemNum]; - - if ( item->icon && loadingItemIconCount < MAX_LOADING_ITEM_ICONS ) { - loadingItemIcons[loadingItemIconCount++] = trap_R_RegisterShaderNoMip( item->icon ); - } - - CG_LoadingString( item->pickup_name ); -} - -/* -=================== -CG_LoadingClient -=================== -*/ -void CG_LoadingClient( int clientNum ) { - const char *info; - char *skin; - char personality[MAX_QPATH]; - char model[MAX_QPATH]; - char iconName[MAX_QPATH]; - - info = CG_ConfigString( CS_PLAYERS + clientNum ); - - if ( loadingPlayerIconCount < MAX_LOADING_PLAYER_ICONS ) { - Q_strncpyz( model, Info_ValueForKey( info, "model" ), sizeof( model ) ); - skin = Q_strrchr( model, '/' ); - if ( skin ) { - *skin++ = '\0'; - } else { - skin = "default"; - } - - Com_sprintf( iconName, MAX_QPATH, "models/players/%s/icon_%s.tga", model, skin ); - - loadingPlayerIcons[loadingPlayerIconCount] = trap_R_RegisterShaderNoMip( iconName ); - if ( !loadingPlayerIcons[loadingPlayerIconCount] ) { - Com_sprintf( iconName, MAX_QPATH, "models/players/characters/%s/icon_%s.tga", model, skin ); - loadingPlayerIcons[loadingPlayerIconCount] = trap_R_RegisterShaderNoMip( iconName ); - } - if ( !loadingPlayerIcons[loadingPlayerIconCount] ) { - Com_sprintf( iconName, MAX_QPATH, "models/players/%s/icon_%s.tga", DEFAULT_MODEL, "default" ); - loadingPlayerIcons[loadingPlayerIconCount] = trap_R_RegisterShaderNoMip( iconName ); - } - if ( loadingPlayerIcons[loadingPlayerIconCount] ) { - loadingPlayerIconCount++; - } - } - - Q_strncpyz( personality, Info_ValueForKey( info, "n" ), sizeof(personality) ); - Q_CleanStr( personality ); - - if( cgs.gametype == GT_SINGLE_PLAYER ) { - trap_S_RegisterSound( va( "sound/player/announce/%s.wav", personality ), qtrue ); - } - - CG_LoadingString( personality ); -} - - -/* -==================== -CG_DrawInformation - -Draw all the status / pacifier stuff during level loading -==================== -*/ -void CG_DrawInformation( void ) { - const char *s; - const char *info; - const char *sysInfo; - int y; - int value; - qhandle_t levelshot; - qhandle_t detail; - char buf[1024]; - - info = CG_ConfigString( CS_SERVERINFO ); - sysInfo = CG_ConfigString( CS_SYSTEMINFO ); - - s = Info_ValueForKey( info, "mapname" ); - levelshot = trap_R_RegisterShaderNoMip( va( "levelshots/%s.tga", s ) ); - if ( !levelshot ) { - levelshot = trap_R_RegisterShaderNoMip( "menu/art/unknownmap" ); - } - trap_R_SetColor( NULL ); - CG_DrawPic( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, levelshot ); - - // blend a detail texture over it - detail = trap_R_RegisterShader( "levelShotDetail" ); - trap_R_DrawStretchPic( 0, 0, cgs.glconfig.vidWidth, cgs.glconfig.vidHeight, 0, 0, 2.5, 2, detail ); - - // draw the icons of things as they are loaded - CG_DrawLoadingIcons(); - - // the first 150 rows are reserved for the client connection - // screen to write into - if ( cg.infoScreenText[0] ) { - UI_DrawProportionalString( 320, 128-32, va("Loading... %s", cg.infoScreenText), - UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); - } else { - UI_DrawProportionalString( 320, 128-32, "Awaiting snapshot...", - UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); - } - - // draw info string information - - y = 180-32; - - // don't print server lines if playing a local game - trap_Cvar_VariableStringBuffer( "sv_running", buf, sizeof( buf ) ); - if ( !atoi( buf ) ) { - // server hostname - Q_strncpyz(buf, Info_ValueForKey( info, "sv_hostname" ), 1024); - Q_CleanStr(buf); - UI_DrawProportionalString( 320, y, buf, - UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); - y += PROP_HEIGHT; - - // pure server - s = Info_ValueForKey( sysInfo, "sv_pure" ); - if ( s[0] == '1' ) { - UI_DrawProportionalString( 320, y, "Pure Server", - UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); - y += PROP_HEIGHT; - } - - // server-specific message of the day - s = CG_ConfigString( CS_MOTD ); - if ( s[0] ) { - UI_DrawProportionalString( 320, y, s, - UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); - y += PROP_HEIGHT; - } - - // some extra space after hostname and motd - y += 10; - } - - // map-specific message (long map name) - s = CG_ConfigString( CS_MESSAGE ); - if ( s[0] ) { - UI_DrawProportionalString( 320, y, s, - UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); - y += PROP_HEIGHT; - } - - // cheats warning - s = Info_ValueForKey( sysInfo, "sv_cheats" ); - if ( s[0] == '1' ) { - UI_DrawProportionalString( 320, y, "CHEATS ARE ENABLED", - UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); - y += PROP_HEIGHT; - } - - // game type - switch ( cgs.gametype ) { - case GT_FFA: - s = "Free For All"; - break; - case GT_SINGLE_PLAYER: - s = "Single Player"; - break; - case GT_TOURNAMENT: - s = "Tournament"; - break; - case GT_TEAM: - s = "Team Deathmatch"; - break; - case GT_CTF: - s = "Capture The Flag"; - break; -#ifdef MISSIONPACK - case GT_1FCTF: - s = "One Flag CTF"; - break; - case GT_OBELISK: - s = "Overload"; - break; - case GT_HARVESTER: - s = "Harvester"; - break; -#endif - default: - s = "Unknown Gametype"; - break; - } - UI_DrawProportionalString( 320, y, s, - UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); - y += PROP_HEIGHT; - - value = atoi( Info_ValueForKey( info, "timelimit" ) ); - if ( value ) { - UI_DrawProportionalString( 320, y, va( "timelimit %i", value ), - UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); - y += PROP_HEIGHT; - } - - if (cgs.gametype < GT_CTF ) { - value = atoi( Info_ValueForKey( info, "fraglimit" ) ); - if ( value ) { - UI_DrawProportionalString( 320, y, va( "fraglimit %i", value ), - UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); - y += PROP_HEIGHT; - } - } - - if (cgs.gametype >= GT_CTF) { - value = atoi( Info_ValueForKey( info, "capturelimit" ) ); - if ( value ) { - UI_DrawProportionalString( 320, y, va( "capturelimit %i", value ), - UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); - y += PROP_HEIGHT; - } - } -} - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// cg_info.c -- display information while data is being loading + +#include "cg_local.h" + +#define MAX_LOADING_PLAYER_ICONS 16 +#define MAX_LOADING_ITEM_ICONS 26 + +static int loadingPlayerIconCount; +static int loadingItemIconCount; +static qhandle_t loadingPlayerIcons[MAX_LOADING_PLAYER_ICONS]; +static qhandle_t loadingItemIcons[MAX_LOADING_ITEM_ICONS]; + + +/* +=================== +CG_DrawLoadingIcons +=================== +*/ +static void CG_DrawLoadingIcons( void ) { + int n; + int x, y; + + for( n = 0; n < loadingPlayerIconCount; n++ ) { + x = 16 + n * 78; + y = 324-40; + CG_DrawPic( x, y, 64, 64, loadingPlayerIcons[n] ); + } + + for( n = 0; n < loadingItemIconCount; n++ ) { + y = 400-40; + if( n >= 13 ) { + y += 40; + } + x = 16 + n % 13 * 48; + CG_DrawPic( x, y, 32, 32, loadingItemIcons[n] ); + } +} + + +/* +====================== +CG_LoadingString + +====================== +*/ +void CG_LoadingString( const char *s ) { + Q_strncpyz( cg.infoScreenText, s, sizeof( cg.infoScreenText ) ); + + trap_UpdateScreen(); +} + +/* +=================== +CG_LoadingItem +=================== +*/ +void CG_LoadingItem( int itemNum ) { + gitem_t *item; + + item = &bg_itemlist[itemNum]; + + if ( item->icon && loadingItemIconCount < MAX_LOADING_ITEM_ICONS ) { + loadingItemIcons[loadingItemIconCount++] = trap_R_RegisterShaderNoMip( item->icon ); + } + + CG_LoadingString( item->pickup_name ); +} + +/* +=================== +CG_LoadingClient +=================== +*/ +void CG_LoadingClient( int clientNum ) { + const char *info; + char *skin; + char personality[MAX_QPATH]; + char model[MAX_QPATH]; + char iconName[MAX_QPATH]; + + info = CG_ConfigString( CS_PLAYERS + clientNum ); + + if ( loadingPlayerIconCount < MAX_LOADING_PLAYER_ICONS ) { + Q_strncpyz( model, Info_ValueForKey( info, "model" ), sizeof( model ) ); + skin = Q_strrchr( model, '/' ); + if ( skin ) { + *skin++ = '\0'; + } else { + skin = "default"; + } + + Com_sprintf( iconName, MAX_QPATH, "models/players/%s/icon_%s.tga", model, skin ); + + loadingPlayerIcons[loadingPlayerIconCount] = trap_R_RegisterShaderNoMip( iconName ); + if ( !loadingPlayerIcons[loadingPlayerIconCount] ) { + Com_sprintf( iconName, MAX_QPATH, "models/players/characters/%s/icon_%s.tga", model, skin ); + loadingPlayerIcons[loadingPlayerIconCount] = trap_R_RegisterShaderNoMip( iconName ); + } + if ( !loadingPlayerIcons[loadingPlayerIconCount] ) { + Com_sprintf( iconName, MAX_QPATH, "models/players/%s/icon_%s.tga", DEFAULT_MODEL, "default" ); + loadingPlayerIcons[loadingPlayerIconCount] = trap_R_RegisterShaderNoMip( iconName ); + } + if ( loadingPlayerIcons[loadingPlayerIconCount] ) { + loadingPlayerIconCount++; + } + } + + Q_strncpyz( personality, Info_ValueForKey( info, "n" ), sizeof(personality) ); + Q_CleanStr( personality ); + + if( cgs.gametype == GT_SINGLE_PLAYER ) { + trap_S_RegisterSound( va( "sound/player/announce/%s.wav", personality ), qtrue ); + } + + CG_LoadingString( personality ); +} + + +/* +==================== +CG_DrawInformation + +Draw all the status / pacifier stuff during level loading +==================== +*/ +void CG_DrawInformation( void ) { + const char *s; + const char *info; + const char *sysInfo; + int y; + int value; + qhandle_t levelshot; + qhandle_t detail; + char buf[1024]; + + info = CG_ConfigString( CS_SERVERINFO ); + sysInfo = CG_ConfigString( CS_SYSTEMINFO ); + + s = Info_ValueForKey( info, "mapname" ); + levelshot = trap_R_RegisterShaderNoMip( va( "levelshots/%s.tga", s ) ); + if ( !levelshot ) { + levelshot = trap_R_RegisterShaderNoMip( "menu/art/unknownmap" ); + } + trap_R_SetColor( NULL ); + CG_DrawPic( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, levelshot ); + + // blend a detail texture over it + detail = trap_R_RegisterShader( "levelShotDetail" ); + trap_R_DrawStretchPic( 0, 0, cgs.glconfig.vidWidth, cgs.glconfig.vidHeight, 0, 0, 2.5, 2, detail ); + + // draw the icons of things as they are loaded + CG_DrawLoadingIcons(); + + // the first 150 rows are reserved for the client connection + // screen to write into + if ( cg.infoScreenText[0] ) { + UI_DrawProportionalString( 320, 128-32, va("Loading... %s", cg.infoScreenText), + UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); + } else { + UI_DrawProportionalString( 320, 128-32, "Awaiting snapshot...", + UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); + } + + // draw info string information + + y = 180-32; + + // don't print server lines if playing a local game + trap_Cvar_VariableStringBuffer( "sv_running", buf, sizeof( buf ) ); + if ( !atoi( buf ) ) { + // server hostname + Q_strncpyz(buf, Info_ValueForKey( info, "sv_hostname" ), 1024); + Q_CleanStr(buf); + UI_DrawProportionalString( 320, y, buf, + UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); + y += PROP_HEIGHT; + + // pure server + s = Info_ValueForKey( sysInfo, "sv_pure" ); + if ( s[0] == '1' ) { + UI_DrawProportionalString( 320, y, "Pure Server", + UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); + y += PROP_HEIGHT; + } + + // server-specific message of the day + s = CG_ConfigString( CS_MOTD ); + if ( s[0] ) { + UI_DrawProportionalString( 320, y, s, + UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); + y += PROP_HEIGHT; + } + + // some extra space after hostname and motd + y += 10; + } + + // map-specific message (long map name) + s = CG_ConfigString( CS_MESSAGE ); + if ( s[0] ) { + UI_DrawProportionalString( 320, y, s, + UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); + y += PROP_HEIGHT; + } + + // cheats warning + s = Info_ValueForKey( sysInfo, "sv_cheats" ); + if ( s[0] == '1' ) { + UI_DrawProportionalString( 320, y, "CHEATS ARE ENABLED", + UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); + y += PROP_HEIGHT; + } + + // game type + switch ( cgs.gametype ) { + case GT_FFA: + s = "Free For All"; + break; + case GT_SINGLE_PLAYER: + s = "Single Player"; + break; + case GT_TOURNAMENT: + s = "Tournament"; + break; + case GT_TEAM: + s = "Team Deathmatch"; + break; + case GT_CTF: + s = "Capture The Flag"; + break; +#ifdef MISSIONPACK + case GT_1FCTF: + s = "One Flag CTF"; + break; + case GT_OBELISK: + s = "Overload"; + break; + case GT_HARVESTER: + s = "Harvester"; + break; +#endif + default: + s = "Unknown Gametype"; + break; + } + UI_DrawProportionalString( 320, y, s, + UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); + y += PROP_HEIGHT; + + value = atoi( Info_ValueForKey( info, "timelimit" ) ); + if ( value ) { + UI_DrawProportionalString( 320, y, va( "timelimit %i", value ), + UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); + y += PROP_HEIGHT; + } + + if (cgs.gametype < GT_CTF ) { + value = atoi( Info_ValueForKey( info, "fraglimit" ) ); + if ( value ) { + UI_DrawProportionalString( 320, y, va( "fraglimit %i", value ), + UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); + y += PROP_HEIGHT; + } + } + + if (cgs.gametype >= GT_CTF) { + value = atoi( Info_ValueForKey( info, "capturelimit" ) ); + if ( value ) { + UI_DrawProportionalString( 320, y, va( "capturelimit %i", value ), + UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); + y += PROP_HEIGHT; + } + } +} + diff --git a/code/cgame/cg_local.h b/code/cgame/cg_local.h index 4dd5c9d..a23f715 100755 --- a/code/cgame/cg_local.h +++ b/code/cgame/cg_local.h @@ -1,1669 +1,1669 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -#include "../game/q_shared.h" -#include "tr_types.h" -#include "../game/bg_public.h" -#include "cg_public.h" - - -// The entire cgame module is unloaded and reloaded on each level change, -// so there is NO persistant data between levels on the client side. -// If you absolutely need something stored, it can either be kept -// by the server in the server stored userinfos, or stashed in a cvar. - -#ifdef MISSIONPACK -#define CG_FONT_THRESHOLD 0.1 -#endif - -#define POWERUP_BLINKS 5 - -#define POWERUP_BLINK_TIME 1000 -#define FADE_TIME 200 -#define PULSE_TIME 200 -#define DAMAGE_DEFLECT_TIME 100 -#define DAMAGE_RETURN_TIME 400 -#define DAMAGE_TIME 500 -#define LAND_DEFLECT_TIME 150 -#define LAND_RETURN_TIME 300 -#define STEP_TIME 200 -#define DUCK_TIME 100 -#define PAIN_TWITCH_TIME 200 -#define WEAPON_SELECT_TIME 1400 -#define ITEM_SCALEUP_TIME 1000 -#define ZOOM_TIME 150 -#define ITEM_BLOB_TIME 200 -#define MUZZLE_FLASH_TIME 20 -#define SINK_TIME 1000 // time for fragments to sink into ground before going away -#define ATTACKER_HEAD_TIME 10000 -#define REWARD_TIME 3000 - -#define PULSE_SCALE 1.5 // amount to scale up the icons when activating - -#define MAX_STEP_CHANGE 32 - -#define MAX_VERTS_ON_POLY 10 -#define MAX_MARK_POLYS 256 - -#define STAT_MINUS 10 // num frame for '-' stats digit - -#define ICON_SIZE 48 -#define CHAR_WIDTH 32 -#define CHAR_HEIGHT 48 -#define TEXT_ICON_SPACE 4 - -#define TEAMCHAT_WIDTH 80 -#define TEAMCHAT_HEIGHT 8 - -// very large characters -#define GIANT_WIDTH 32 -#define GIANT_HEIGHT 48 - -#define NUM_CROSSHAIRS 10 - -#define TEAM_OVERLAY_MAXNAME_WIDTH 12 -#define TEAM_OVERLAY_MAXLOCATION_WIDTH 16 - -#define DEFAULT_MODEL "sarge" -#ifdef MISSIONPACK -#define DEFAULT_TEAM_MODEL "james" -#define DEFAULT_TEAM_HEAD "*james" -#else -#define DEFAULT_TEAM_MODEL "sarge" -#define DEFAULT_TEAM_HEAD "sarge" -#endif - -#define DEFAULT_REDTEAM_NAME "Stroggs" -#define DEFAULT_BLUETEAM_NAME "Pagans" - -typedef enum { - FOOTSTEP_NORMAL, - FOOTSTEP_BOOT, - FOOTSTEP_FLESH, - FOOTSTEP_MECH, - FOOTSTEP_ENERGY, - FOOTSTEP_METAL, - FOOTSTEP_SPLASH, - - FOOTSTEP_TOTAL -} footstep_t; - -typedef enum { - IMPACTSOUND_DEFAULT, - IMPACTSOUND_METAL, - IMPACTSOUND_FLESH -} impactSound_t; - -//================================================= - -// player entities need to track more information -// than any other type of entity. - -// note that not every player entity is a client entity, -// because corpses after respawn are outside the normal -// client numbering range - -// when changing animation, set animationTime to frameTime + lerping time -// The current lerp will finish out, then it will lerp to the new animation -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; // may include ANIM_TOGGLEBIT - animation_t *animation; - int animationTime; // time when the first frame of the animation will be exact -} lerpFrame_t; - - -typedef struct { - lerpFrame_t legs, torso, flag; - int painTime; - int painDirection; // flip from 0 to 1 - int lightningFiring; - - // railgun trail spawning - vec3_t railgunImpact; - qboolean railgunFlash; - - // machinegun spinning - float barrelAngle; - int barrelTime; - qboolean barrelSpinning; -} playerEntity_t; - -//================================================= - - - -// centity_t have a direct corespondence with gentity_t in the game, but -// only the entityState_t is directly communicated to the cgame -typedef struct centity_s { - entityState_t currentState; // from cg.frame - entityState_t nextState; // from cg.nextFrame, if available - qboolean interpolate; // true if next is valid to interpolate to - qboolean currentValid; // true if cg.frame holds this entity - - int muzzleFlashTime; // move to playerEntity? - int previousEvent; - int teleportFlag; - - int trailTime; // so missile trails can handle dropped initial packets - int dustTrailTime; - int miscTime; - - int snapShotTime; // last time this entity was found in a snapshot - - playerEntity_t pe; - - int errorTime; // decay the error from this time - vec3_t errorOrigin; - vec3_t errorAngles; - - qboolean extrapolated; // false if origin / angles is an interpolation - vec3_t rawOrigin; - vec3_t rawAngles; - - vec3_t beamEnd; - - // exact interpolated position of entity on this frame - vec3_t lerpOrigin; - vec3_t lerpAngles; -} centity_t; - - -//====================================================================== - -// local entities are created as a result of events or predicted actions, -// and live independantly from all server transmitted entities - -typedef struct markPoly_s { - struct markPoly_s *prevMark, *nextMark; - int time; - qhandle_t markShader; - qboolean alphaFade; // fade alpha instead of rgb - float color[4]; - poly_t poly; - polyVert_t verts[MAX_VERTS_ON_POLY]; -} markPoly_t; - - -typedef enum { - LE_MARK, - LE_EXPLOSION, - LE_SPRITE_EXPLOSION, - LE_FRAGMENT, - LE_MOVE_SCALE_FADE, - LE_FALL_SCALE_FADE, - LE_FADE_RGB, - LE_SCALE_FADE, - LE_SCOREPLUM, -#ifdef MISSIONPACK - LE_KAMIKAZE, - LE_INVULIMPACT, - LE_INVULJUICED, - LE_SHOWREFENTITY -#endif -} leType_t; - -typedef enum { - LEF_PUFF_DONT_SCALE = 0x0001, // do not scale size over time - LEF_TUMBLE = 0x0002, // tumble over time, used for ejecting shells - LEF_SOUND1 = 0x0004, // sound 1 for kamikaze - LEF_SOUND2 = 0x0008 // sound 2 for kamikaze -} leFlag_t; - -typedef enum { - LEMT_NONE, - LEMT_BURN, - LEMT_BLOOD -} leMarkType_t; // fragment local entities can leave marks on walls - -typedef enum { - LEBS_NONE, - LEBS_BLOOD, - LEBS_BRASS -} leBounceSoundType_t; // fragment local entities can make sounds on impacts - -typedef struct localEntity_s { - struct localEntity_s *prev, *next; - leType_t leType; - int leFlags; - - int startTime; - int endTime; - int fadeInTime; - - float lifeRate; // 1.0 / (endTime - startTime) - - trajectory_t pos; - trajectory_t angles; - - float bounceFactor; // 0.0 = no bounce, 1.0 = perfect - - float color[4]; - - float radius; - - float light; - vec3_t lightColor; - - leMarkType_t leMarkType; // mark to leave on fragment impact - leBounceSoundType_t leBounceSoundType; - - refEntity_t refEntity; -} localEntity_t; - -//====================================================================== - - -typedef struct { - int client; - int score; - int ping; - int time; - int scoreFlags; - int powerUps; - int accuracy; - int impressiveCount; - int excellentCount; - int guantletCount; - int defendCount; - int assistCount; - int captures; - qboolean perfect; - int team; -} score_t; - -// each client has an associated clientInfo_t -// that contains media references necessary to present the -// client model and other color coded effects -// this is regenerated each time a client's configstring changes, -// usually as a result of a userinfo (name, model, etc) change -#define MAX_CUSTOM_SOUNDS 32 - -typedef struct { - qboolean infoValid; - - char name[MAX_QPATH]; - team_t team; - - int botSkill; // 0 = not bot, 1-5 = bot - - vec3_t color1; - vec3_t color2; - - int score; // updated by score servercmds - int location; // location index for team mode - int health; // you only get this info about your teammates - int armor; - int curWeapon; - - int handicap; - int wins, losses; // in tourney mode - - int teamTask; // task in teamplay (offence/defence) - qboolean teamLeader; // true when this is a team leader - - int powerups; // so can display quad/flag status - - int medkitUsageTime; - int invulnerabilityStartTime; - int invulnerabilityStopTime; - - int breathPuffTime; - - // when clientinfo is changed, the loading of models/skins/sounds - // can be deferred until you are dead, to prevent hitches in - // gameplay - char modelName[MAX_QPATH]; - char skinName[MAX_QPATH]; - char headModelName[MAX_QPATH]; - char headSkinName[MAX_QPATH]; - char redTeam[MAX_TEAMNAME]; - char blueTeam[MAX_TEAMNAME]; - qboolean deferred; - - qboolean newAnims; // true if using the new mission pack animations - qboolean fixedlegs; // true if legs yaw is always the same as torso yaw - qboolean fixedtorso; // true if torso never changes yaw - - vec3_t headOffset; // move head in icon views - footstep_t footsteps; - gender_t gender; // from model - - qhandle_t legsModel; - qhandle_t legsSkin; - - qhandle_t torsoModel; - qhandle_t torsoSkin; - - qhandle_t headModel; - qhandle_t headSkin; - - qhandle_t modelIcon; - - animation_t animations[MAX_TOTALANIMATIONS]; - - sfxHandle_t sounds[MAX_CUSTOM_SOUNDS]; -} clientInfo_t; - - -// each WP_* weapon enum has an associated weaponInfo_t -// that contains media references necessary to present the -// weapon and its effects -typedef struct weaponInfo_s { - qboolean registered; - gitem_t *item; - - qhandle_t handsModel; // the hands don't actually draw, they just position the weapon - qhandle_t weaponModel; - qhandle_t barrelModel; - qhandle_t flashModel; - - vec3_t weaponMidpoint; // so it will rotate centered instead of by tag - - float flashDlight; - vec3_t flashDlightColor; - sfxHandle_t flashSound[4]; // fast firing weapons randomly choose - - qhandle_t weaponIcon; - qhandle_t ammoIcon; - - qhandle_t ammoModel; - - qhandle_t missileModel; - sfxHandle_t missileSound; - void (*missileTrailFunc)( centity_t *, const struct weaponInfo_s *wi ); - float missileDlight; - vec3_t missileDlightColor; - int missileRenderfx; - - void (*ejectBrassFunc)( centity_t * ); - - float trailRadius; - float wiTrailTime; - - sfxHandle_t readySound; - sfxHandle_t firingSound; - qboolean loopFireSound; -} weaponInfo_t; - - -// each IT_* item has an associated itemInfo_t -// that constains media references necessary to present the -// item and its effects -typedef struct { - qboolean registered; - qhandle_t models[MAX_ITEM_MODELS]; - qhandle_t icon; -} itemInfo_t; - - -typedef struct { - int itemNum; -} powerupInfo_t; - - -#define MAX_SKULLTRAIL 10 - -typedef struct { - vec3_t positions[MAX_SKULLTRAIL]; - int numpositions; -} skulltrail_t; - - -#define MAX_REWARDSTACK 10 -#define MAX_SOUNDBUFFER 20 - -//====================================================================== - -// all cg.stepTime, cg.duckTime, cg.landTime, etc are set to cg.time when the action -// occurs, and they will have visible effects for #define STEP_TIME or whatever msec after - -#define MAX_PREDICTED_EVENTS 16 - -typedef struct { - int clientFrame; // incremented each frame - - int clientNum; - - qboolean demoPlayback; - qboolean levelShot; // taking a level menu screenshot - int deferredPlayerLoading; - qboolean loading; // don't defer players at initial startup - qboolean intermissionStarted; // don't play voice rewards, because game will end shortly - - // there are only one or two snapshot_t that are relevent at a time - int latestSnapshotNum; // the number of snapshots the client system has received - int latestSnapshotTime; // the time from latestSnapshotNum, so we don't need to read the snapshot yet - - snapshot_t *snap; // cg.snap->serverTime <= cg.time - snapshot_t *nextSnap; // cg.nextSnap->serverTime > cg.time, or NULL - snapshot_t activeSnapshots[2]; - - float frameInterpolation; // (float)( cg.time - cg.frame->serverTime ) / (cg.nextFrame->serverTime - cg.frame->serverTime) - - qboolean thisFrameTeleport; - qboolean nextFrameTeleport; - - int frametime; // cg.time - cg.oldTime - - int time; // this is the time value that the client - // is rendering at. - int oldTime; // time at last frame, used for missile trails and prediction checking - - int physicsTime; // either cg.snap->time or cg.nextSnap->time - - int timelimitWarnings; // 5 min, 1 min, overtime - int fraglimitWarnings; - - qboolean mapRestart; // set on a map restart to set back the weapon - - qboolean renderingThirdPerson; // during deaths, chasecams, etc - - // prediction state - qboolean hyperspace; // true if prediction has hit a trigger_teleport - playerState_t predictedPlayerState; - centity_t predictedPlayerEntity; - qboolean validPPS; // clear until the first call to CG_PredictPlayerState - int predictedErrorTime; - vec3_t predictedError; - - int eventSequence; - int predictableEvents[MAX_PREDICTED_EVENTS]; - - float stepChange; // for stair up smoothing - int stepTime; - - float duckChange; // for duck viewheight smoothing - int duckTime; - - float landChange; // for landing hard - int landTime; - - // input state sent to server - int weaponSelect; - - // auto rotating items - vec3_t autoAngles; - vec3_t autoAxis[3]; - vec3_t autoAnglesFast; - vec3_t autoAxisFast[3]; - - // view rendering - refdef_t refdef; - vec3_t refdefViewAngles; // will be converted to refdef.viewaxis - - // zoom key - qboolean zoomed; - int zoomTime; - float zoomSensitivity; - - // information screen text during loading - char infoScreenText[MAX_STRING_CHARS]; - - // scoreboard - int scoresRequestTime; - int numScores; - int selectedScore; - int teamScores[2]; - score_t scores[MAX_CLIENTS]; - qboolean showScores; - qboolean scoreBoardShowing; - int scoreFadeTime; - char killerName[MAX_NAME_LENGTH]; - char spectatorList[MAX_STRING_CHARS]; // list of names - int spectatorLen; // length of list - float spectatorWidth; // width in device units - int spectatorTime; // next time to offset - int spectatorPaintX; // current paint x - int spectatorPaintX2; // current paint x - int spectatorOffset; // current offset from start - int spectatorPaintLen; // current offset from start - - // skull trails - skulltrail_t skulltrails[MAX_CLIENTS]; - - // centerprinting - int centerPrintTime; - int centerPrintCharWidth; - int centerPrintY; - char centerPrint[1024]; - int centerPrintLines; - - // low ammo warning state - int lowAmmoWarning; // 1 = low, 2 = empty - - // kill timers for carnage reward - int lastKillTime; - - // crosshair client ID - int crosshairClientNum; - int crosshairClientTime; - - // powerup active flashing - int powerupActive; - int powerupTime; - - // attacking player - int attackerTime; - int voiceTime; - - // reward medals - int rewardStack; - int rewardTime; - int rewardCount[MAX_REWARDSTACK]; - qhandle_t rewardShader[MAX_REWARDSTACK]; - qhandle_t rewardSound[MAX_REWARDSTACK]; - - // sound buffer mainly for announcer sounds - int soundBufferIn; - int soundBufferOut; - int soundTime; - qhandle_t soundBuffer[MAX_SOUNDBUFFER]; - - // for voice chat buffer - int voiceChatTime; - int voiceChatBufferIn; - int voiceChatBufferOut; - - // warmup countdown - int warmup; - int warmupCount; - - //========================== - - int itemPickup; - int itemPickupTime; - int itemPickupBlendTime; // the pulse around the crosshair is timed seperately - - int weaponSelectTime; - int weaponAnimation; - int weaponAnimationTime; - - // blend blobs - float damageTime; - float damageX, damageY, damageValue; - - // status bar head - float headYaw; - float headEndPitch; - float headEndYaw; - int headEndTime; - float headStartPitch; - float headStartYaw; - int headStartTime; - - // view movement - float v_dmg_time; - float v_dmg_pitch; - float v_dmg_roll; - - vec3_t kick_angles; // weapon kicks - vec3_t kick_origin; - - // temp working variables for player view - float bobfracsin; - int bobcycle; - float xyspeed; - int nextOrbitTime; - - //qboolean cameraMode; // if rendering from a loaded camera - - - // development tool - refEntity_t testModelEntity; - char testModelName[MAX_QPATH]; - qboolean testGun; - -} cg_t; - - -// all of the model, shader, and sound references that are -// loaded at gamestate time are stored in cgMedia_t -// Other media that can be tied to clients, weapons, or items are -// stored in the clientInfo_t, itemInfo_t, weaponInfo_t, and powerupInfo_t -typedef struct { - qhandle_t charsetShader; - qhandle_t charsetProp; - qhandle_t charsetPropGlow; - qhandle_t charsetPropB; - qhandle_t whiteShader; - - qhandle_t redCubeModel; - qhandle_t blueCubeModel; - qhandle_t redCubeIcon; - qhandle_t blueCubeIcon; - qhandle_t redFlagModel; - qhandle_t blueFlagModel; - qhandle_t neutralFlagModel; - qhandle_t redFlagShader[3]; - qhandle_t blueFlagShader[3]; - qhandle_t flagShader[4]; - - qhandle_t flagPoleModel; - qhandle_t flagFlapModel; - - qhandle_t redFlagFlapSkin; - qhandle_t blueFlagFlapSkin; - qhandle_t neutralFlagFlapSkin; - - qhandle_t redFlagBaseModel; - qhandle_t blueFlagBaseModel; - qhandle_t neutralFlagBaseModel; - -#ifdef MISSIONPACK - qhandle_t overloadBaseModel; - qhandle_t overloadTargetModel; - qhandle_t overloadLightsModel; - qhandle_t overloadEnergyModel; - - qhandle_t harvesterModel; - qhandle_t harvesterRedSkin; - qhandle_t harvesterBlueSkin; - qhandle_t harvesterNeutralModel; -#endif - - qhandle_t armorModel; - qhandle_t armorIcon; - - qhandle_t teamStatusBar; - - qhandle_t deferShader; - - // gib explosions - qhandle_t gibAbdomen; - qhandle_t gibArm; - qhandle_t gibChest; - qhandle_t gibFist; - qhandle_t gibFoot; - qhandle_t gibForearm; - qhandle_t gibIntestine; - qhandle_t gibLeg; - qhandle_t gibSkull; - qhandle_t gibBrain; - - qhandle_t smoke2; - - qhandle_t machinegunBrassModel; - qhandle_t shotgunBrassModel; - - qhandle_t railRingsShader; - qhandle_t railCoreShader; - - qhandle_t lightningShader; - - qhandle_t friendShader; - - qhandle_t balloonShader; - qhandle_t connectionShader; - - qhandle_t selectShader; - qhandle_t viewBloodShader; - qhandle_t tracerShader; - qhandle_t crosshairShader[NUM_CROSSHAIRS]; - qhandle_t lagometerShader; - qhandle_t backTileShader; - qhandle_t noammoShader; - - qhandle_t smokePuffShader; - qhandle_t smokePuffRageProShader; - qhandle_t shotgunSmokePuffShader; - qhandle_t plasmaBallShader; - qhandle_t waterBubbleShader; - qhandle_t bloodTrailShader; -#ifdef MISSIONPACK - qhandle_t nailPuffShader; - qhandle_t blueProxMine; -#endif - - qhandle_t numberShaders[11]; - - qhandle_t shadowMarkShader; - - qhandle_t botSkillShaders[5]; - - // wall mark shaders - qhandle_t wakeMarkShader; - qhandle_t bloodMarkShader; - qhandle_t bulletMarkShader; - qhandle_t burnMarkShader; - qhandle_t holeMarkShader; - qhandle_t energyMarkShader; - - // powerup shaders - qhandle_t quadShader; - qhandle_t redQuadShader; - qhandle_t quadWeaponShader; - qhandle_t invisShader; - qhandle_t regenShader; - qhandle_t battleSuitShader; - qhandle_t battleWeaponShader; - qhandle_t hastePuffShader; - qhandle_t redKamikazeShader; - qhandle_t blueKamikazeShader; - - // weapon effect models - qhandle_t bulletFlashModel; - qhandle_t ringFlashModel; - qhandle_t dishFlashModel; - qhandle_t lightningExplosionModel; - - // weapon effect shaders - qhandle_t railExplosionShader; - qhandle_t plasmaExplosionShader; - qhandle_t bulletExplosionShader; - qhandle_t rocketExplosionShader; - qhandle_t grenadeExplosionShader; - qhandle_t bfgExplosionShader; - qhandle_t bloodExplosionShader; - - // special effects models - qhandle_t teleportEffectModel; - qhandle_t teleportEffectShader; -#ifdef MISSIONPACK - qhandle_t kamikazeEffectModel; - qhandle_t kamikazeShockWave; - qhandle_t kamikazeHeadModel; - qhandle_t kamikazeHeadTrail; - qhandle_t guardPowerupModel; - qhandle_t scoutPowerupModel; - qhandle_t doublerPowerupModel; - qhandle_t ammoRegenPowerupModel; - qhandle_t invulnerabilityImpactModel; - qhandle_t invulnerabilityJuicedModel; - qhandle_t medkitUsageModel; - qhandle_t dustPuffShader; - qhandle_t heartShader; -#endif - qhandle_t invulnerabilityPowerupModel; - - // scoreboard headers - qhandle_t scoreboardName; - qhandle_t scoreboardPing; - qhandle_t scoreboardScore; - qhandle_t scoreboardTime; - - // medals shown during gameplay - qhandle_t medalImpressive; - qhandle_t medalExcellent; - qhandle_t medalGauntlet; - qhandle_t medalDefend; - qhandle_t medalAssist; - qhandle_t medalCapture; - - // sounds - sfxHandle_t quadSound; - sfxHandle_t tracerSound; - sfxHandle_t selectSound; - sfxHandle_t useNothingSound; - sfxHandle_t wearOffSound; - sfxHandle_t footsteps[FOOTSTEP_TOTAL][4]; - sfxHandle_t sfx_lghit1; - sfxHandle_t sfx_lghit2; - sfxHandle_t sfx_lghit3; - sfxHandle_t sfx_ric1; - sfxHandle_t sfx_ric2; - sfxHandle_t sfx_ric3; - sfxHandle_t sfx_railg; - sfxHandle_t sfx_rockexp; - sfxHandle_t sfx_plasmaexp; -#ifdef MISSIONPACK - sfxHandle_t sfx_proxexp; - sfxHandle_t sfx_nghit; - sfxHandle_t sfx_nghitflesh; - sfxHandle_t sfx_nghitmetal; - sfxHandle_t sfx_chghit; - sfxHandle_t sfx_chghitflesh; - sfxHandle_t sfx_chghitmetal; - sfxHandle_t kamikazeExplodeSound; - sfxHandle_t kamikazeImplodeSound; - sfxHandle_t kamikazeFarSound; - sfxHandle_t useInvulnerabilitySound; - sfxHandle_t invulnerabilityImpactSound1; - sfxHandle_t invulnerabilityImpactSound2; - sfxHandle_t invulnerabilityImpactSound3; - sfxHandle_t invulnerabilityJuicedSound; - sfxHandle_t obeliskHitSound1; - sfxHandle_t obeliskHitSound2; - sfxHandle_t obeliskHitSound3; - sfxHandle_t obeliskRespawnSound; - sfxHandle_t winnerSound; - sfxHandle_t loserSound; - sfxHandle_t youSuckSound; -#endif - sfxHandle_t gibSound; - sfxHandle_t gibBounce1Sound; - sfxHandle_t gibBounce2Sound; - sfxHandle_t gibBounce3Sound; - sfxHandle_t teleInSound; - sfxHandle_t teleOutSound; - sfxHandle_t noAmmoSound; - sfxHandle_t respawnSound; - sfxHandle_t talkSound; - sfxHandle_t landSound; - sfxHandle_t fallSound; - sfxHandle_t jumpPadSound; - - sfxHandle_t oneMinuteSound; - sfxHandle_t fiveMinuteSound; - sfxHandle_t suddenDeathSound; - - sfxHandle_t threeFragSound; - sfxHandle_t twoFragSound; - sfxHandle_t oneFragSound; - - sfxHandle_t hitSound; - sfxHandle_t hitSoundHighArmor; - sfxHandle_t hitSoundLowArmor; - sfxHandle_t hitTeamSound; - sfxHandle_t impressiveSound; - sfxHandle_t excellentSound; - sfxHandle_t deniedSound; - sfxHandle_t humiliationSound; - sfxHandle_t assistSound; - sfxHandle_t defendSound; - sfxHandle_t firstImpressiveSound; - sfxHandle_t firstExcellentSound; - sfxHandle_t firstHumiliationSound; - - sfxHandle_t takenLeadSound; - sfxHandle_t tiedLeadSound; - sfxHandle_t lostLeadSound; - - sfxHandle_t voteNow; - sfxHandle_t votePassed; - sfxHandle_t voteFailed; - - sfxHandle_t watrInSound; - sfxHandle_t watrOutSound; - sfxHandle_t watrUnSound; - - sfxHandle_t flightSound; - sfxHandle_t medkitSound; - - sfxHandle_t weaponHoverSound; - - // teamplay sounds - sfxHandle_t captureAwardSound; - sfxHandle_t redScoredSound; - sfxHandle_t blueScoredSound; - sfxHandle_t redLeadsSound; - sfxHandle_t blueLeadsSound; - sfxHandle_t teamsTiedSound; - - sfxHandle_t captureYourTeamSound; - sfxHandle_t captureOpponentSound; - sfxHandle_t returnYourTeamSound; - sfxHandle_t returnOpponentSound; - sfxHandle_t takenYourTeamSound; - sfxHandle_t takenOpponentSound; - - sfxHandle_t redFlagReturnedSound; - sfxHandle_t blueFlagReturnedSound; - sfxHandle_t neutralFlagReturnedSound; - sfxHandle_t enemyTookYourFlagSound; - sfxHandle_t enemyTookTheFlagSound; - sfxHandle_t yourTeamTookEnemyFlagSound; - sfxHandle_t yourTeamTookTheFlagSound; - sfxHandle_t youHaveFlagSound; - sfxHandle_t yourBaseIsUnderAttackSound; - sfxHandle_t holyShitSound; - - // tournament sounds - sfxHandle_t count3Sound; - sfxHandle_t count2Sound; - sfxHandle_t count1Sound; - sfxHandle_t countFightSound; - sfxHandle_t countPrepareSound; - -#ifdef MISSIONPACK - // new stuff - qhandle_t patrolShader; - qhandle_t assaultShader; - qhandle_t campShader; - qhandle_t followShader; - qhandle_t defendShader; - qhandle_t teamLeaderShader; - qhandle_t retrieveShader; - qhandle_t escortShader; - qhandle_t flagShaders[3]; - sfxHandle_t countPrepareTeamSound; - - sfxHandle_t ammoregenSound; - sfxHandle_t doublerSound; - sfxHandle_t guardSound; - sfxHandle_t scoutSound; -#endif - qhandle_t cursor; - qhandle_t selectCursor; - qhandle_t sizeCursor; - - sfxHandle_t regenSound; - sfxHandle_t protectSound; - sfxHandle_t n_healthSound; - sfxHandle_t hgrenb1aSound; - sfxHandle_t hgrenb2aSound; - sfxHandle_t wstbimplSound; - sfxHandle_t wstbimpmSound; - sfxHandle_t wstbimpdSound; - sfxHandle_t wstbactvSound; - -} cgMedia_t; - - -// The client game static (cgs) structure hold everything -// loaded or calculated from the gamestate. It will NOT -// be cleared when a tournement restart is done, allowing -// all clients to begin playing instantly -typedef struct { - gameState_t gameState; // gamestate from server - glconfig_t glconfig; // rendering configuration - float screenXScale; // derived from glconfig - float screenYScale; - float screenXBias; - - int serverCommandSequence; // reliable command stream counter - int processedSnapshotNum;// the number of snapshots cgame has requested - - qboolean localServer; // detected on startup by checking sv_running - - // parsed from serverinfo - gametype_t gametype; - int dmflags; - int teamflags; - int fraglimit; - int capturelimit; - int timelimit; - int maxclients; - char mapname[MAX_QPATH]; - char redTeam[MAX_QPATH]; - char blueTeam[MAX_QPATH]; - - int voteTime; - int voteYes; - int voteNo; - qboolean voteModified; // beep whenever changed - char voteString[MAX_STRING_TOKENS]; - - int teamVoteTime[2]; - int teamVoteYes[2]; - int teamVoteNo[2]; - qboolean teamVoteModified[2]; // beep whenever changed - char teamVoteString[2][MAX_STRING_TOKENS]; - - int levelStartTime; - - int scores1, scores2; // from configstrings - int redflag, blueflag; // flag status from configstrings - int flagStatus; - - qboolean newHud; - - // - // locally derived information from gamestate - // - qhandle_t gameModels[MAX_MODELS]; - sfxHandle_t gameSounds[MAX_SOUNDS]; - - int numInlineModels; - qhandle_t inlineDrawModel[MAX_MODELS]; - vec3_t inlineModelMidpoints[MAX_MODELS]; - - clientInfo_t clientinfo[MAX_CLIENTS]; - - // teamchat width is *3 because of embedded color codes - char teamChatMsgs[TEAMCHAT_HEIGHT][TEAMCHAT_WIDTH*3+1]; - int teamChatMsgTimes[TEAMCHAT_HEIGHT]; - int teamChatPos; - int teamLastChatPos; - - int cursorX; - int cursorY; - qboolean eventHandling; - qboolean mouseCaptured; - qboolean sizingHud; - void *capturedItem; - qhandle_t activeCursor; - - // orders - int currentOrder; - qboolean orderPending; - int orderTime; - int currentVoiceClient; - int acceptOrderTime; - int acceptTask; - int acceptLeader; - char acceptVoice[MAX_NAME_LENGTH]; - - // media - cgMedia_t media; - -} cgs_t; - -//============================================================================== - -extern cgs_t cgs; -extern cg_t cg; -extern centity_t cg_entities[MAX_GENTITIES]; -extern weaponInfo_t cg_weapons[MAX_WEAPONS]; -extern itemInfo_t cg_items[MAX_ITEMS]; -extern markPoly_t cg_markPolys[MAX_MARK_POLYS]; - -extern vmCvar_t cg_centertime; -extern vmCvar_t cg_runpitch; -extern vmCvar_t cg_runroll; -extern vmCvar_t cg_bobup; -extern vmCvar_t cg_bobpitch; -extern vmCvar_t cg_bobroll; -extern vmCvar_t cg_swingSpeed; -extern vmCvar_t cg_shadows; -extern vmCvar_t cg_gibs; -extern vmCvar_t cg_drawTimer; -extern vmCvar_t cg_drawFPS; -extern vmCvar_t cg_drawSnapshot; -extern vmCvar_t cg_draw3dIcons; -extern vmCvar_t cg_drawIcons; -extern vmCvar_t cg_drawAmmoWarning; -extern vmCvar_t cg_drawCrosshair; -extern vmCvar_t cg_drawCrosshairNames; -extern vmCvar_t cg_drawRewards; -extern vmCvar_t cg_drawTeamOverlay; -extern vmCvar_t cg_teamOverlayUserinfo; -extern vmCvar_t cg_crosshairX; -extern vmCvar_t cg_crosshairY; -extern vmCvar_t cg_crosshairSize; -extern vmCvar_t cg_crosshairHealth; -extern vmCvar_t cg_drawStatus; -extern vmCvar_t cg_draw2D; -extern vmCvar_t cg_animSpeed; -extern vmCvar_t cg_debugAnim; -extern vmCvar_t cg_debugPosition; -extern vmCvar_t cg_debugEvents; -extern vmCvar_t cg_railTrailTime; -extern vmCvar_t cg_errorDecay; -extern vmCvar_t cg_nopredict; -extern vmCvar_t cg_noPlayerAnims; -extern vmCvar_t cg_showmiss; -extern vmCvar_t cg_footsteps; -extern vmCvar_t cg_addMarks; -extern vmCvar_t cg_brassTime; -extern vmCvar_t cg_gun_frame; -extern vmCvar_t cg_gun_x; -extern vmCvar_t cg_gun_y; -extern vmCvar_t cg_gun_z; -extern vmCvar_t cg_drawGun; -extern vmCvar_t cg_viewsize; -extern vmCvar_t cg_tracerChance; -extern vmCvar_t cg_tracerWidth; -extern vmCvar_t cg_tracerLength; -extern vmCvar_t cg_autoswitch; -extern vmCvar_t cg_ignore; -extern vmCvar_t cg_simpleItems; -extern vmCvar_t cg_fov; -extern vmCvar_t cg_zoomFov; -extern vmCvar_t cg_thirdPersonRange; -extern vmCvar_t cg_thirdPersonAngle; -extern vmCvar_t cg_thirdPerson; -extern vmCvar_t cg_stereoSeparation; -extern vmCvar_t cg_lagometer; -extern vmCvar_t cg_drawAttacker; -extern vmCvar_t cg_synchronousClients; -extern vmCvar_t cg_teamChatTime; -extern vmCvar_t cg_teamChatHeight; -extern vmCvar_t cg_stats; -extern vmCvar_t cg_forceModel; -extern vmCvar_t cg_buildScript; -extern vmCvar_t cg_paused; -extern vmCvar_t cg_blood; -extern vmCvar_t cg_predictItems; -extern vmCvar_t cg_deferPlayers; -extern vmCvar_t cg_drawFriend; -extern vmCvar_t cg_teamChatsOnly; -extern vmCvar_t cg_noVoiceChats; -extern vmCvar_t cg_noVoiceText; -extern vmCvar_t cg_scorePlum; -extern vmCvar_t cg_smoothClients; -extern vmCvar_t pmove_fixed; -extern vmCvar_t pmove_msec; -//extern vmCvar_t cg_pmove_fixed; -extern vmCvar_t cg_cameraOrbit; -extern vmCvar_t cg_cameraOrbitDelay; -extern vmCvar_t cg_timescaleFadeEnd; -extern vmCvar_t cg_timescaleFadeSpeed; -extern vmCvar_t cg_timescale; -extern vmCvar_t cg_cameraMode; -extern vmCvar_t cg_smallFont; -extern vmCvar_t cg_bigFont; -extern vmCvar_t cg_noTaunt; -extern vmCvar_t cg_noProjectileTrail; -extern vmCvar_t cg_oldRail; -extern vmCvar_t cg_oldRocket; -extern vmCvar_t cg_oldPlasma; -extern vmCvar_t cg_trueLightning; -#ifdef MISSIONPACK -extern vmCvar_t cg_redTeamName; -extern vmCvar_t cg_blueTeamName; -extern vmCvar_t cg_currentSelectedPlayer; -extern vmCvar_t cg_currentSelectedPlayerName; -extern vmCvar_t cg_singlePlayer; -extern vmCvar_t cg_enableDust; -extern vmCvar_t cg_enableBreath; -extern vmCvar_t cg_singlePlayerActive; -extern vmCvar_t cg_recordSPDemo; -extern vmCvar_t cg_recordSPDemoName; -extern vmCvar_t cg_obeliskRespawnDelay; -#endif - -// -// cg_main.c -// -const char *CG_ConfigString( int index ); -const char *CG_Argv( int arg ); - -void QDECL CG_Printf( const char *msg, ... ); -void QDECL CG_Error( const char *msg, ... ); - -void CG_StartMusic( void ); - -void CG_UpdateCvars( void ); - -int CG_CrosshairPlayer( void ); -int CG_LastAttacker( void ); -void CG_LoadMenus(const char *menuFile); -void CG_KeyEvent(int key, qboolean down); -void CG_MouseEvent(int x, int y); -void CG_EventHandling(int type); -void CG_RankRunFrame( void ); -void CG_SetScoreSelection(void *menu); -score_t *CG_GetSelectedScore(); -void CG_BuildSpectatorString(); - - -// -// cg_view.c -// -void CG_TestModel_f (void); -void CG_TestGun_f (void); -void CG_TestModelNextFrame_f (void); -void CG_TestModelPrevFrame_f (void); -void CG_TestModelNextSkin_f (void); -void CG_TestModelPrevSkin_f (void); -void CG_ZoomDown_f( void ); -void CG_ZoomUp_f( void ); -void CG_AddBufferedSound( sfxHandle_t sfx); - -void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ); - - -// -// cg_drawtools.c -// -void CG_AdjustFrom640( float *x, float *y, float *w, float *h ); -void CG_FillRect( float x, float y, float width, float height, const float *color ); -void CG_DrawPic( float x, float y, float width, float height, qhandle_t hShader ); -void CG_DrawString( float x, float y, const char *string, - float charWidth, float charHeight, const float *modulate ); - - -void CG_DrawStringExt( int x, int y, const char *string, const float *setColor, - qboolean forceColor, qboolean shadow, int charWidth, int charHeight, int maxChars ); -void CG_DrawBigString( int x, int y, const char *s, float alpha ); -void CG_DrawBigStringColor( int x, int y, const char *s, vec4_t color ); -void CG_DrawSmallString( int x, int y, const char *s, float alpha ); -void CG_DrawSmallStringColor( int x, int y, const char *s, vec4_t color ); - -int CG_DrawStrlen( const char *str ); - -float *CG_FadeColor( int startMsec, int totalMsec ); -float *CG_TeamColor( int team ); -void CG_TileClear( void ); -void CG_ColorForHealth( vec4_t hcolor ); -void CG_GetColorForHealth( int health, int armor, vec4_t hcolor ); - -void UI_DrawProportionalString( int x, int y, const char* str, int style, vec4_t color ); -void CG_DrawRect( float x, float y, float width, float height, float size, const float *color ); -void CG_DrawSides(float x, float y, float w, float h, float size); -void CG_DrawTopBottom(float x, float y, float w, float h, float size); - - -// -// cg_draw.c, cg_newDraw.c -// -extern int sortedTeamPlayers[TEAM_MAXOVERLAY]; -extern int numSortedTeamPlayers; -extern int drawTeamOverlayModificationCount; -extern char systemChat[256]; -extern char teamChat1[256]; -extern char teamChat2[256]; - -void CG_AddLagometerFrameInfo( void ); -void CG_AddLagometerSnapshotInfo( snapshot_t *snap ); -void CG_CenterPrint( const char *str, int y, int charWidth ); -void CG_DrawHead( float x, float y, float w, float h, int clientNum, vec3_t headAngles ); -void CG_DrawActive( stereoFrame_t stereoView ); -void CG_DrawFlagModel( float x, float y, float w, float h, int team, qboolean force2D ); -void CG_DrawTeamBackground( int x, int y, int w, int h, float alpha, int team ); -void CG_OwnerDraw(float x, float y, float w, float h, float text_x, float text_y, int ownerDraw, int ownerDrawFlags, int align, float special, float scale, vec4_t color, qhandle_t shader, int textStyle); -void CG_Text_Paint(float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit, int style); -int CG_Text_Width(const char *text, float scale, int limit); -int CG_Text_Height(const char *text, float scale, int limit); -void CG_SelectPrevPlayer(); -void CG_SelectNextPlayer(); -float CG_GetValue(int ownerDraw); -qboolean CG_OwnerDrawVisible(int flags); -void CG_RunMenuScript(char **args); -void CG_ShowResponseHead(); -void CG_SetPrintString(int type, const char *p); -void CG_InitTeamChat(); -void CG_GetTeamColor(vec4_t *color); -const char *CG_GetGameStatusText(); -const char *CG_GetKillerText(); -void CG_Draw3DModel( float x, float y, float w, float h, qhandle_t model, qhandle_t skin, vec3_t origin, vec3_t angles ); -void CG_Text_PaintChar(float x, float y, float width, float height, float scale, float s, float t, float s2, float t2, qhandle_t hShader); -void CG_CheckOrderPending(); -const char *CG_GameTypeString(); -qboolean CG_YourTeamHasFlag(); -qboolean CG_OtherTeamHasFlag(); -qhandle_t CG_StatusHandle(int task); - - - -// -// cg_player.c -// -void CG_Player( centity_t *cent ); -void CG_ResetPlayerEntity( centity_t *cent ); -void CG_AddRefEntityWithPowerups( refEntity_t *ent, entityState_t *state, int team ); -void CG_NewClientInfo( int clientNum ); -sfxHandle_t CG_CustomSound( int clientNum, const char *soundName ); - -// -// cg_predict.c -// -void CG_BuildSolidList( void ); -int CG_PointContents( const vec3_t point, int passEntityNum ); -void CG_Trace( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, - int skipNumber, int mask ); -void CG_PredictPlayerState( void ); -void CG_LoadDeferredPlayers( void ); - - -// -// cg_events.c -// -void CG_CheckEvents( centity_t *cent ); -const char *CG_PlaceString( int rank ); -void CG_EntityEvent( centity_t *cent, vec3_t position ); -void CG_PainEvent( centity_t *cent, int health ); - - -// -// cg_ents.c -// -void CG_SetEntitySoundPosition( centity_t *cent ); -void CG_AddPacketEntities( void ); -void CG_Beam( centity_t *cent ); -void CG_AdjustPositionForMover( const vec3_t in, int moverNum, int fromTime, int toTime, vec3_t out ); - -void CG_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent, - qhandle_t parentModel, char *tagName ); -void CG_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent, - qhandle_t parentModel, char *tagName ); - - - -// -// cg_weapons.c -// -void CG_NextWeapon_f( void ); -void CG_PrevWeapon_f( void ); -void CG_Weapon_f( void ); - -void CG_RegisterWeapon( int weaponNum ); -void CG_RegisterItemVisuals( int itemNum ); - -void CG_FireWeapon( centity_t *cent ); -void CG_MissileHitWall( int weapon, int clientNum, vec3_t origin, vec3_t dir, impactSound_t soundType ); -void CG_MissileHitPlayer( int weapon, vec3_t origin, vec3_t dir, int entityNum ); -void CG_ShotgunFire( entityState_t *es ); -void CG_Bullet( vec3_t origin, int sourceEntityNum, vec3_t normal, qboolean flesh, int fleshEntityNum ); - -void CG_RailTrail( clientInfo_t *ci, vec3_t start, vec3_t end ); -void CG_GrappleTrail( centity_t *ent, const weaponInfo_t *wi ); -void CG_AddViewWeapon (playerState_t *ps); -void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent, int team ); -void CG_DrawWeaponSelect( void ); - -void CG_OutOfAmmoChange( void ); // should this be in pmove? - -// -// cg_marks.c -// -void CG_InitMarkPolys( void ); -void CG_AddMarks( void ); -void CG_ImpactMark( qhandle_t markShader, - const vec3_t origin, const vec3_t dir, - float orientation, - float r, float g, float b, float a, - qboolean alphaFade, - float radius, qboolean temporary ); - -// -// cg_localents.c -// -void CG_InitLocalEntities( void ); -localEntity_t *CG_AllocLocalEntity( void ); -void CG_AddLocalEntities( void ); - -// -// cg_effects.c -// -localEntity_t *CG_SmokePuff( const vec3_t p, - const vec3_t vel, - float radius, - float r, float g, float b, float a, - float duration, - int startTime, - int fadeInTime, - int leFlags, - qhandle_t hShader ); -void CG_BubbleTrail( vec3_t start, vec3_t end, float spacing ); -void CG_SpawnEffect( vec3_t org ); -#ifdef MISSIONPACK -void CG_KamikazeEffect( vec3_t org ); -void CG_ObeliskExplode( vec3_t org, int entityNum ); -void CG_ObeliskPain( vec3_t org ); -void CG_InvulnerabilityImpact( vec3_t org, vec3_t angles ); -void CG_InvulnerabilityJuiced( vec3_t org ); -void CG_LightningBoltBeam( vec3_t start, vec3_t end ); -#endif -void CG_ScorePlum( int client, vec3_t org, int score ); - -void CG_GibPlayer( vec3_t playerOrigin ); -void CG_BigExplode( vec3_t playerOrigin ); - -void CG_Bleed( vec3_t origin, int entityNum ); - -localEntity_t *CG_MakeExplosion( vec3_t origin, vec3_t dir, - qhandle_t hModel, qhandle_t shader, int msec, - qboolean isSprite ); - -// -// cg_snapshot.c -// -void CG_ProcessSnapshots( void ); - -// -// cg_info.c -// -void CG_LoadingString( const char *s ); -void CG_LoadingItem( int itemNum ); -void CG_LoadingClient( int clientNum ); -void CG_DrawInformation( void ); - -// -// cg_scoreboard.c -// -qboolean CG_DrawOldScoreboard( void ); -void CG_DrawOldTourneyScoreboard( void ); - -// -// cg_consolecmds.c -// -qboolean CG_ConsoleCommand( void ); -void CG_InitConsoleCommands( void ); - -// -// cg_servercmds.c -// -void CG_ExecuteNewServerCommands( int latestSequence ); -void CG_ParseServerinfo( void ); -void CG_SetConfigValues( void ); -void CG_LoadVoiceChats( void ); -void CG_ShaderStateChanged(void); -void CG_VoiceChatLocal( int mode, qboolean voiceOnly, int clientNum, int color, const char *cmd ); -void CG_PlayBufferedVoiceChats( void ); - -// -// cg_playerstate.c -// -void CG_Respawn( void ); -void CG_TransitionPlayerState( playerState_t *ps, playerState_t *ops ); -void CG_CheckChangedPredictableEvents( playerState_t *ps ); - - -//=============================================== - -// -// system traps -// These functions are how the cgame communicates with the main game system -// - -// print message on the local console -void trap_Print( const char *fmt ); - -// abort the game -void trap_Error( const char *fmt ); - -// milliseconds should only be used for performance tuning, never -// for anything game related. Get time from the CG_DrawActiveFrame parameter -int trap_Milliseconds( void ); - -// console variable interaction -void trap_Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ); -void trap_Cvar_Update( vmCvar_t *vmCvar ); -void trap_Cvar_Set( const char *var_name, const char *value ); -void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ); - -// ServerCommand and ConsoleCommand parameter access -int trap_Argc( void ); -void trap_Argv( int n, char *buffer, int bufferLength ); -void trap_Args( char *buffer, int bufferLength ); - -// filesystem access -// returns length of file -int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ); -void trap_FS_Read( void *buffer, int len, fileHandle_t f ); -void trap_FS_Write( const void *buffer, int len, fileHandle_t f ); -void trap_FS_FCloseFile( fileHandle_t f ); -int trap_FS_Seek( fileHandle_t f, long offset, int origin ); // fsOrigin_t - -// add commands to the local console as if they were typed in -// for map changing, etc. The command is not executed immediately, -// but will be executed in order the next time console commands -// are processed -void trap_SendConsoleCommand( const char *text ); - -// register a command name so the console can perform command completion. -// FIXME: replace this with a normal console command "defineCommand"? -void trap_AddCommand( const char *cmdName ); - -// send a string to the server over the network -void trap_SendClientCommand( const char *s ); - -// force a screen update, only used during gamestate load -void trap_UpdateScreen( void ); - -// model collision -void trap_CM_LoadMap( const char *mapname ); -int trap_CM_NumInlineModels( void ); -clipHandle_t trap_CM_InlineModel( int index ); // 0 = world, 1+ = bmodels -clipHandle_t trap_CM_TempBoxModel( const vec3_t mins, const vec3_t maxs ); -int trap_CM_PointContents( const vec3_t p, clipHandle_t model ); -int trap_CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles ); -void trap_CM_BoxTrace( trace_t *results, const vec3_t start, const vec3_t end, - const vec3_t mins, const vec3_t maxs, - clipHandle_t model, int brushmask ); -void trap_CM_TransformedBoxTrace( trace_t *results, const vec3_t start, const vec3_t end, - const vec3_t mins, const vec3_t maxs, - clipHandle_t model, int brushmask, - const vec3_t origin, const vec3_t angles ); - -// Returns the projection of a polygon onto the solid brushes in the world -int trap_CM_MarkFragments( int numPoints, const vec3_t *points, - const vec3_t projection, - int maxPoints, vec3_t pointBuffer, - int maxFragments, markFragment_t *fragmentBuffer ); - -// normal sounds will have their volume dynamically changed as their entity -// moves and the listener moves -void trap_S_StartSound( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx ); -void trap_S_StopLoopingSound(int entnum); - -// a local sound is always played full volume -void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum ); -void trap_S_ClearLoopingSounds( qboolean killall ); -void trap_S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); -void trap_S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); -void trap_S_UpdateEntityPosition( int entityNum, const vec3_t origin ); - -// respatialize recalculates the volumes of sound as they should be heard by the -// given entityNum and position -void trap_S_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ); -sfxHandle_t trap_S_RegisterSound( const char *sample, qboolean compressed ); // returns buzz if not found -void trap_S_StartBackgroundTrack( const char *intro, const char *loop ); // empty name stops music -void trap_S_StopBackgroundTrack( void ); - - -void trap_R_LoadWorldMap( const char *mapname ); - -// all media should be registered during level startup to prevent -// hitches during gameplay -qhandle_t trap_R_RegisterModel( const char *name ); // returns rgb axis if not found -qhandle_t trap_R_RegisterSkin( const char *name ); // returns all white if not found -qhandle_t trap_R_RegisterShader( const char *name ); // returns all white if not found -qhandle_t trap_R_RegisterShaderNoMip( const char *name ); // returns all white if not found - -// a scene is built up by calls to R_ClearScene and the various R_Add functions. -// Nothing is drawn until R_RenderScene is called. -void trap_R_ClearScene( void ); -void trap_R_AddRefEntityToScene( const refEntity_t *re ); - -// polys are intended for simple wall marks, not really for doing -// significant construction -void trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts ); -void trap_R_AddPolysToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts, int numPolys ); -void trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ); -int trap_R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir ); -void trap_R_RenderScene( const refdef_t *fd ); -void trap_R_SetColor( const float *rgba ); // NULL = 1,1,1,1 -void trap_R_DrawStretchPic( float x, float y, float w, float h, - float s1, float t1, float s2, float t2, qhandle_t hShader ); -void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ); -int trap_R_LerpTag( orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame, - float frac, const char *tagName ); -void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset ); - -// The glconfig_t will not change during the life of a cgame. -// If it needs to change, the entire cgame will be restarted, because -// all the qhandle_t are then invalid. -void trap_GetGlconfig( glconfig_t *glconfig ); - -// the gamestate should be grabbed at startup, and whenever a -// configstring changes -void trap_GetGameState( gameState_t *gamestate ); - -// cgame will poll each frame to see if a newer snapshot has arrived -// that it is interested in. The time is returned seperately so that -// snapshot latency can be calculated. -void trap_GetCurrentSnapshotNumber( int *snapshotNumber, int *serverTime ); - -// a snapshot get can fail if the snapshot (or the entties it holds) is so -// old that it has fallen out of the client system queue -qboolean trap_GetSnapshot( int snapshotNumber, snapshot_t *snapshot ); - -// retrieve a text command from the server stream -// the current snapshot will hold the number of the most recent command -// qfalse can be returned if the client system handled the command -// argc() / argv() can be used to examine the parameters of the command -qboolean trap_GetServerCommand( int serverCommandNumber ); - -// returns the most recent command number that can be passed to GetUserCmd -// this will always be at least one higher than the number in the current -// snapshot, and it may be quite a few higher if it is a fast computer on -// a lagged connection -int trap_GetCurrentCmdNumber( void ); - -qboolean trap_GetUserCmd( int cmdNumber, usercmd_t *ucmd ); - -// used for the weapon select and zoom -void trap_SetUserCmdValue( int stateValue, float sensitivityScale ); - -// aids for VM testing -void testPrintInt( char *string, int i ); -void testPrintFloat( char *string, float f ); - -int trap_MemoryRemaining( void ); -void trap_R_RegisterFont(const char *fontName, int pointSize, fontInfo_t *font); -qboolean trap_Key_IsDown( int keynum ); -int trap_Key_GetCatcher( void ); -void trap_Key_SetCatcher( int catcher ); -int trap_Key_GetKey( const char *binding ); - - -typedef enum { - SYSTEM_PRINT, - CHAT_PRINT, - TEAMCHAT_PRINT -} q3print_t; // bk001201 - warning: useless keyword or type name in empty declaration - - -int trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits); -e_status trap_CIN_StopCinematic(int handle); -e_status trap_CIN_RunCinematic (int handle); -void trap_CIN_DrawCinematic (int handle); -void trap_CIN_SetExtents (int handle, int x, int y, int w, int h); - -void trap_SnapVector( float *v ); - -qboolean trap_loadCamera(const char *name); -void trap_startCamera(int time); -qboolean trap_getCameraInfo(int time, vec3_t *origin, vec3_t *angles); - -qboolean trap_GetEntityToken( char *buffer, int bufferSize ); - -void CG_ClearParticles (void); -void CG_AddParticles (void); -void CG_ParticleSnow (qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum); -void CG_ParticleSmoke (qhandle_t pshader, centity_t *cent); -void CG_AddParticleShrapnel (localEntity_t *le); -void CG_ParticleSnowFlurry (qhandle_t pshader, centity_t *cent); -void CG_ParticleBulletDebris (vec3_t org, vec3_t vel, int duration); -void CG_ParticleSparks (vec3_t org, vec3_t vel, int duration, float x, float y, float speed); -void CG_ParticleDust (centity_t *cent, vec3_t origin, vec3_t dir); -void CG_ParticleMisc (qhandle_t pshader, vec3_t origin, int size, int duration, float alpha); -void CG_ParticleExplosion (char *animStr, vec3_t origin, vec3_t vel, int duration, int sizeStart, int sizeEnd); -extern qboolean initparticles; -int CG_NewParticleArea ( int num ); - - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +#include "../game/q_shared.h" +#include "tr_types.h" +#include "../game/bg_public.h" +#include "cg_public.h" + + +// The entire cgame module is unloaded and reloaded on each level change, +// so there is NO persistant data between levels on the client side. +// If you absolutely need something stored, it can either be kept +// by the server in the server stored userinfos, or stashed in a cvar. + +#ifdef MISSIONPACK +#define CG_FONT_THRESHOLD 0.1 +#endif + +#define POWERUP_BLINKS 5 + +#define POWERUP_BLINK_TIME 1000 +#define FADE_TIME 200 +#define PULSE_TIME 200 +#define DAMAGE_DEFLECT_TIME 100 +#define DAMAGE_RETURN_TIME 400 +#define DAMAGE_TIME 500 +#define LAND_DEFLECT_TIME 150 +#define LAND_RETURN_TIME 300 +#define STEP_TIME 200 +#define DUCK_TIME 100 +#define PAIN_TWITCH_TIME 200 +#define WEAPON_SELECT_TIME 1400 +#define ITEM_SCALEUP_TIME 1000 +#define ZOOM_TIME 150 +#define ITEM_BLOB_TIME 200 +#define MUZZLE_FLASH_TIME 20 +#define SINK_TIME 1000 // time for fragments to sink into ground before going away +#define ATTACKER_HEAD_TIME 10000 +#define REWARD_TIME 3000 + +#define PULSE_SCALE 1.5 // amount to scale up the icons when activating + +#define MAX_STEP_CHANGE 32 + +#define MAX_VERTS_ON_POLY 10 +#define MAX_MARK_POLYS 256 + +#define STAT_MINUS 10 // num frame for '-' stats digit + +#define ICON_SIZE 48 +#define CHAR_WIDTH 32 +#define CHAR_HEIGHT 48 +#define TEXT_ICON_SPACE 4 + +#define TEAMCHAT_WIDTH 80 +#define TEAMCHAT_HEIGHT 8 + +// very large characters +#define GIANT_WIDTH 32 +#define GIANT_HEIGHT 48 + +#define NUM_CROSSHAIRS 10 + +#define TEAM_OVERLAY_MAXNAME_WIDTH 12 +#define TEAM_OVERLAY_MAXLOCATION_WIDTH 16 + +#define DEFAULT_MODEL "sarge" +#ifdef MISSIONPACK +#define DEFAULT_TEAM_MODEL "james" +#define DEFAULT_TEAM_HEAD "*james" +#else +#define DEFAULT_TEAM_MODEL "sarge" +#define DEFAULT_TEAM_HEAD "sarge" +#endif + +#define DEFAULT_REDTEAM_NAME "Stroggs" +#define DEFAULT_BLUETEAM_NAME "Pagans" + +typedef enum { + FOOTSTEP_NORMAL, + FOOTSTEP_BOOT, + FOOTSTEP_FLESH, + FOOTSTEP_MECH, + FOOTSTEP_ENERGY, + FOOTSTEP_METAL, + FOOTSTEP_SPLASH, + + FOOTSTEP_TOTAL +} footstep_t; + +typedef enum { + IMPACTSOUND_DEFAULT, + IMPACTSOUND_METAL, + IMPACTSOUND_FLESH +} impactSound_t; + +//================================================= + +// player entities need to track more information +// than any other type of entity. + +// note that not every player entity is a client entity, +// because corpses after respawn are outside the normal +// client numbering range + +// when changing animation, set animationTime to frameTime + lerping time +// The current lerp will finish out, then it will lerp to the new animation +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; // may include ANIM_TOGGLEBIT + animation_t *animation; + int animationTime; // time when the first frame of the animation will be exact +} lerpFrame_t; + + +typedef struct { + lerpFrame_t legs, torso, flag; + int painTime; + int painDirection; // flip from 0 to 1 + int lightningFiring; + + // railgun trail spawning + vec3_t railgunImpact; + qboolean railgunFlash; + + // machinegun spinning + float barrelAngle; + int barrelTime; + qboolean barrelSpinning; +} playerEntity_t; + +//================================================= + + + +// centity_t have a direct corespondence with gentity_t in the game, but +// only the entityState_t is directly communicated to the cgame +typedef struct centity_s { + entityState_t currentState; // from cg.frame + entityState_t nextState; // from cg.nextFrame, if available + qboolean interpolate; // true if next is valid to interpolate to + qboolean currentValid; // true if cg.frame holds this entity + + int muzzleFlashTime; // move to playerEntity? + int previousEvent; + int teleportFlag; + + int trailTime; // so missile trails can handle dropped initial packets + int dustTrailTime; + int miscTime; + + int snapShotTime; // last time this entity was found in a snapshot + + playerEntity_t pe; + + int errorTime; // decay the error from this time + vec3_t errorOrigin; + vec3_t errorAngles; + + qboolean extrapolated; // false if origin / angles is an interpolation + vec3_t rawOrigin; + vec3_t rawAngles; + + vec3_t beamEnd; + + // exact interpolated position of entity on this frame + vec3_t lerpOrigin; + vec3_t lerpAngles; +} centity_t; + + +//====================================================================== + +// local entities are created as a result of events or predicted actions, +// and live independantly from all server transmitted entities + +typedef struct markPoly_s { + struct markPoly_s *prevMark, *nextMark; + int time; + qhandle_t markShader; + qboolean alphaFade; // fade alpha instead of rgb + float color[4]; + poly_t poly; + polyVert_t verts[MAX_VERTS_ON_POLY]; +} markPoly_t; + + +typedef enum { + LE_MARK, + LE_EXPLOSION, + LE_SPRITE_EXPLOSION, + LE_FRAGMENT, + LE_MOVE_SCALE_FADE, + LE_FALL_SCALE_FADE, + LE_FADE_RGB, + LE_SCALE_FADE, + LE_SCOREPLUM, +#ifdef MISSIONPACK + LE_KAMIKAZE, + LE_INVULIMPACT, + LE_INVULJUICED, + LE_SHOWREFENTITY +#endif +} leType_t; + +typedef enum { + LEF_PUFF_DONT_SCALE = 0x0001, // do not scale size over time + LEF_TUMBLE = 0x0002, // tumble over time, used for ejecting shells + LEF_SOUND1 = 0x0004, // sound 1 for kamikaze + LEF_SOUND2 = 0x0008 // sound 2 for kamikaze +} leFlag_t; + +typedef enum { + LEMT_NONE, + LEMT_BURN, + LEMT_BLOOD +} leMarkType_t; // fragment local entities can leave marks on walls + +typedef enum { + LEBS_NONE, + LEBS_BLOOD, + LEBS_BRASS +} leBounceSoundType_t; // fragment local entities can make sounds on impacts + +typedef struct localEntity_s { + struct localEntity_s *prev, *next; + leType_t leType; + int leFlags; + + int startTime; + int endTime; + int fadeInTime; + + float lifeRate; // 1.0 / (endTime - startTime) + + trajectory_t pos; + trajectory_t angles; + + float bounceFactor; // 0.0 = no bounce, 1.0 = perfect + + float color[4]; + + float radius; + + float light; + vec3_t lightColor; + + leMarkType_t leMarkType; // mark to leave on fragment impact + leBounceSoundType_t leBounceSoundType; + + refEntity_t refEntity; +} localEntity_t; + +//====================================================================== + + +typedef struct { + int client; + int score; + int ping; + int time; + int scoreFlags; + int powerUps; + int accuracy; + int impressiveCount; + int excellentCount; + int guantletCount; + int defendCount; + int assistCount; + int captures; + qboolean perfect; + int team; +} score_t; + +// each client has an associated clientInfo_t +// that contains media references necessary to present the +// client model and other color coded effects +// this is regenerated each time a client's configstring changes, +// usually as a result of a userinfo (name, model, etc) change +#define MAX_CUSTOM_SOUNDS 32 + +typedef struct { + qboolean infoValid; + + char name[MAX_QPATH]; + team_t team; + + int botSkill; // 0 = not bot, 1-5 = bot + + vec3_t color1; + vec3_t color2; + + int score; // updated by score servercmds + int location; // location index for team mode + int health; // you only get this info about your teammates + int armor; + int curWeapon; + + int handicap; + int wins, losses; // in tourney mode + + int teamTask; // task in teamplay (offence/defence) + qboolean teamLeader; // true when this is a team leader + + int powerups; // so can display quad/flag status + + int medkitUsageTime; + int invulnerabilityStartTime; + int invulnerabilityStopTime; + + int breathPuffTime; + + // when clientinfo is changed, the loading of models/skins/sounds + // can be deferred until you are dead, to prevent hitches in + // gameplay + char modelName[MAX_QPATH]; + char skinName[MAX_QPATH]; + char headModelName[MAX_QPATH]; + char headSkinName[MAX_QPATH]; + char redTeam[MAX_TEAMNAME]; + char blueTeam[MAX_TEAMNAME]; + qboolean deferred; + + qboolean newAnims; // true if using the new mission pack animations + qboolean fixedlegs; // true if legs yaw is always the same as torso yaw + qboolean fixedtorso; // true if torso never changes yaw + + vec3_t headOffset; // move head in icon views + footstep_t footsteps; + gender_t gender; // from model + + qhandle_t legsModel; + qhandle_t legsSkin; + + qhandle_t torsoModel; + qhandle_t torsoSkin; + + qhandle_t headModel; + qhandle_t headSkin; + + qhandle_t modelIcon; + + animation_t animations[MAX_TOTALANIMATIONS]; + + sfxHandle_t sounds[MAX_CUSTOM_SOUNDS]; +} clientInfo_t; + + +// each WP_* weapon enum has an associated weaponInfo_t +// that contains media references necessary to present the +// weapon and its effects +typedef struct weaponInfo_s { + qboolean registered; + gitem_t *item; + + qhandle_t handsModel; // the hands don't actually draw, they just position the weapon + qhandle_t weaponModel; + qhandle_t barrelModel; + qhandle_t flashModel; + + vec3_t weaponMidpoint; // so it will rotate centered instead of by tag + + float flashDlight; + vec3_t flashDlightColor; + sfxHandle_t flashSound[4]; // fast firing weapons randomly choose + + qhandle_t weaponIcon; + qhandle_t ammoIcon; + + qhandle_t ammoModel; + + qhandle_t missileModel; + sfxHandle_t missileSound; + void (*missileTrailFunc)( centity_t *, const struct weaponInfo_s *wi ); + float missileDlight; + vec3_t missileDlightColor; + int missileRenderfx; + + void (*ejectBrassFunc)( centity_t * ); + + float trailRadius; + float wiTrailTime; + + sfxHandle_t readySound; + sfxHandle_t firingSound; + qboolean loopFireSound; +} weaponInfo_t; + + +// each IT_* item has an associated itemInfo_t +// that constains media references necessary to present the +// item and its effects +typedef struct { + qboolean registered; + qhandle_t models[MAX_ITEM_MODELS]; + qhandle_t icon; +} itemInfo_t; + + +typedef struct { + int itemNum; +} powerupInfo_t; + + +#define MAX_SKULLTRAIL 10 + +typedef struct { + vec3_t positions[MAX_SKULLTRAIL]; + int numpositions; +} skulltrail_t; + + +#define MAX_REWARDSTACK 10 +#define MAX_SOUNDBUFFER 20 + +//====================================================================== + +// all cg.stepTime, cg.duckTime, cg.landTime, etc are set to cg.time when the action +// occurs, and they will have visible effects for #define STEP_TIME or whatever msec after + +#define MAX_PREDICTED_EVENTS 16 + +typedef struct { + int clientFrame; // incremented each frame + + int clientNum; + + qboolean demoPlayback; + qboolean levelShot; // taking a level menu screenshot + int deferredPlayerLoading; + qboolean loading; // don't defer players at initial startup + qboolean intermissionStarted; // don't play voice rewards, because game will end shortly + + // there are only one or two snapshot_t that are relevent at a time + int latestSnapshotNum; // the number of snapshots the client system has received + int latestSnapshotTime; // the time from latestSnapshotNum, so we don't need to read the snapshot yet + + snapshot_t *snap; // cg.snap->serverTime <= cg.time + snapshot_t *nextSnap; // cg.nextSnap->serverTime > cg.time, or NULL + snapshot_t activeSnapshots[2]; + + float frameInterpolation; // (float)( cg.time - cg.frame->serverTime ) / (cg.nextFrame->serverTime - cg.frame->serverTime) + + qboolean thisFrameTeleport; + qboolean nextFrameTeleport; + + int frametime; // cg.time - cg.oldTime + + int time; // this is the time value that the client + // is rendering at. + int oldTime; // time at last frame, used for missile trails and prediction checking + + int physicsTime; // either cg.snap->time or cg.nextSnap->time + + int timelimitWarnings; // 5 min, 1 min, overtime + int fraglimitWarnings; + + qboolean mapRestart; // set on a map restart to set back the weapon + + qboolean renderingThirdPerson; // during deaths, chasecams, etc + + // prediction state + qboolean hyperspace; // true if prediction has hit a trigger_teleport + playerState_t predictedPlayerState; + centity_t predictedPlayerEntity; + qboolean validPPS; // clear until the first call to CG_PredictPlayerState + int predictedErrorTime; + vec3_t predictedError; + + int eventSequence; + int predictableEvents[MAX_PREDICTED_EVENTS]; + + float stepChange; // for stair up smoothing + int stepTime; + + float duckChange; // for duck viewheight smoothing + int duckTime; + + float landChange; // for landing hard + int landTime; + + // input state sent to server + int weaponSelect; + + // auto rotating items + vec3_t autoAngles; + vec3_t autoAxis[3]; + vec3_t autoAnglesFast; + vec3_t autoAxisFast[3]; + + // view rendering + refdef_t refdef; + vec3_t refdefViewAngles; // will be converted to refdef.viewaxis + + // zoom key + qboolean zoomed; + int zoomTime; + float zoomSensitivity; + + // information screen text during loading + char infoScreenText[MAX_STRING_CHARS]; + + // scoreboard + int scoresRequestTime; + int numScores; + int selectedScore; + int teamScores[2]; + score_t scores[MAX_CLIENTS]; + qboolean showScores; + qboolean scoreBoardShowing; + int scoreFadeTime; + char killerName[MAX_NAME_LENGTH]; + char spectatorList[MAX_STRING_CHARS]; // list of names + int spectatorLen; // length of list + float spectatorWidth; // width in device units + int spectatorTime; // next time to offset + int spectatorPaintX; // current paint x + int spectatorPaintX2; // current paint x + int spectatorOffset; // current offset from start + int spectatorPaintLen; // current offset from start + + // skull trails + skulltrail_t skulltrails[MAX_CLIENTS]; + + // centerprinting + int centerPrintTime; + int centerPrintCharWidth; + int centerPrintY; + char centerPrint[1024]; + int centerPrintLines; + + // low ammo warning state + int lowAmmoWarning; // 1 = low, 2 = empty + + // kill timers for carnage reward + int lastKillTime; + + // crosshair client ID + int crosshairClientNum; + int crosshairClientTime; + + // powerup active flashing + int powerupActive; + int powerupTime; + + // attacking player + int attackerTime; + int voiceTime; + + // reward medals + int rewardStack; + int rewardTime; + int rewardCount[MAX_REWARDSTACK]; + qhandle_t rewardShader[MAX_REWARDSTACK]; + qhandle_t rewardSound[MAX_REWARDSTACK]; + + // sound buffer mainly for announcer sounds + int soundBufferIn; + int soundBufferOut; + int soundTime; + qhandle_t soundBuffer[MAX_SOUNDBUFFER]; + + // for voice chat buffer + int voiceChatTime; + int voiceChatBufferIn; + int voiceChatBufferOut; + + // warmup countdown + int warmup; + int warmupCount; + + //========================== + + int itemPickup; + int itemPickupTime; + int itemPickupBlendTime; // the pulse around the crosshair is timed seperately + + int weaponSelectTime; + int weaponAnimation; + int weaponAnimationTime; + + // blend blobs + float damageTime; + float damageX, damageY, damageValue; + + // status bar head + float headYaw; + float headEndPitch; + float headEndYaw; + int headEndTime; + float headStartPitch; + float headStartYaw; + int headStartTime; + + // view movement + float v_dmg_time; + float v_dmg_pitch; + float v_dmg_roll; + + vec3_t kick_angles; // weapon kicks + vec3_t kick_origin; + + // temp working variables for player view + float bobfracsin; + int bobcycle; + float xyspeed; + int nextOrbitTime; + + //qboolean cameraMode; // if rendering from a loaded camera + + + // development tool + refEntity_t testModelEntity; + char testModelName[MAX_QPATH]; + qboolean testGun; + +} cg_t; + + +// all of the model, shader, and sound references that are +// loaded at gamestate time are stored in cgMedia_t +// Other media that can be tied to clients, weapons, or items are +// stored in the clientInfo_t, itemInfo_t, weaponInfo_t, and powerupInfo_t +typedef struct { + qhandle_t charsetShader; + qhandle_t charsetProp; + qhandle_t charsetPropGlow; + qhandle_t charsetPropB; + qhandle_t whiteShader; + + qhandle_t redCubeModel; + qhandle_t blueCubeModel; + qhandle_t redCubeIcon; + qhandle_t blueCubeIcon; + qhandle_t redFlagModel; + qhandle_t blueFlagModel; + qhandle_t neutralFlagModel; + qhandle_t redFlagShader[3]; + qhandle_t blueFlagShader[3]; + qhandle_t flagShader[4]; + + qhandle_t flagPoleModel; + qhandle_t flagFlapModel; + + qhandle_t redFlagFlapSkin; + qhandle_t blueFlagFlapSkin; + qhandle_t neutralFlagFlapSkin; + + qhandle_t redFlagBaseModel; + qhandle_t blueFlagBaseModel; + qhandle_t neutralFlagBaseModel; + +#ifdef MISSIONPACK + qhandle_t overloadBaseModel; + qhandle_t overloadTargetModel; + qhandle_t overloadLightsModel; + qhandle_t overloadEnergyModel; + + qhandle_t harvesterModel; + qhandle_t harvesterRedSkin; + qhandle_t harvesterBlueSkin; + qhandle_t harvesterNeutralModel; +#endif + + qhandle_t armorModel; + qhandle_t armorIcon; + + qhandle_t teamStatusBar; + + qhandle_t deferShader; + + // gib explosions + qhandle_t gibAbdomen; + qhandle_t gibArm; + qhandle_t gibChest; + qhandle_t gibFist; + qhandle_t gibFoot; + qhandle_t gibForearm; + qhandle_t gibIntestine; + qhandle_t gibLeg; + qhandle_t gibSkull; + qhandle_t gibBrain; + + qhandle_t smoke2; + + qhandle_t machinegunBrassModel; + qhandle_t shotgunBrassModel; + + qhandle_t railRingsShader; + qhandle_t railCoreShader; + + qhandle_t lightningShader; + + qhandle_t friendShader; + + qhandle_t balloonShader; + qhandle_t connectionShader; + + qhandle_t selectShader; + qhandle_t viewBloodShader; + qhandle_t tracerShader; + qhandle_t crosshairShader[NUM_CROSSHAIRS]; + qhandle_t lagometerShader; + qhandle_t backTileShader; + qhandle_t noammoShader; + + qhandle_t smokePuffShader; + qhandle_t smokePuffRageProShader; + qhandle_t shotgunSmokePuffShader; + qhandle_t plasmaBallShader; + qhandle_t waterBubbleShader; + qhandle_t bloodTrailShader; +#ifdef MISSIONPACK + qhandle_t nailPuffShader; + qhandle_t blueProxMine; +#endif + + qhandle_t numberShaders[11]; + + qhandle_t shadowMarkShader; + + qhandle_t botSkillShaders[5]; + + // wall mark shaders + qhandle_t wakeMarkShader; + qhandle_t bloodMarkShader; + qhandle_t bulletMarkShader; + qhandle_t burnMarkShader; + qhandle_t holeMarkShader; + qhandle_t energyMarkShader; + + // powerup shaders + qhandle_t quadShader; + qhandle_t redQuadShader; + qhandle_t quadWeaponShader; + qhandle_t invisShader; + qhandle_t regenShader; + qhandle_t battleSuitShader; + qhandle_t battleWeaponShader; + qhandle_t hastePuffShader; + qhandle_t redKamikazeShader; + qhandle_t blueKamikazeShader; + + // weapon effect models + qhandle_t bulletFlashModel; + qhandle_t ringFlashModel; + qhandle_t dishFlashModel; + qhandle_t lightningExplosionModel; + + // weapon effect shaders + qhandle_t railExplosionShader; + qhandle_t plasmaExplosionShader; + qhandle_t bulletExplosionShader; + qhandle_t rocketExplosionShader; + qhandle_t grenadeExplosionShader; + qhandle_t bfgExplosionShader; + qhandle_t bloodExplosionShader; + + // special effects models + qhandle_t teleportEffectModel; + qhandle_t teleportEffectShader; +#ifdef MISSIONPACK + qhandle_t kamikazeEffectModel; + qhandle_t kamikazeShockWave; + qhandle_t kamikazeHeadModel; + qhandle_t kamikazeHeadTrail; + qhandle_t guardPowerupModel; + qhandle_t scoutPowerupModel; + qhandle_t doublerPowerupModel; + qhandle_t ammoRegenPowerupModel; + qhandle_t invulnerabilityImpactModel; + qhandle_t invulnerabilityJuicedModel; + qhandle_t medkitUsageModel; + qhandle_t dustPuffShader; + qhandle_t heartShader; +#endif + qhandle_t invulnerabilityPowerupModel; + + // scoreboard headers + qhandle_t scoreboardName; + qhandle_t scoreboardPing; + qhandle_t scoreboardScore; + qhandle_t scoreboardTime; + + // medals shown during gameplay + qhandle_t medalImpressive; + qhandle_t medalExcellent; + qhandle_t medalGauntlet; + qhandle_t medalDefend; + qhandle_t medalAssist; + qhandle_t medalCapture; + + // sounds + sfxHandle_t quadSound; + sfxHandle_t tracerSound; + sfxHandle_t selectSound; + sfxHandle_t useNothingSound; + sfxHandle_t wearOffSound; + sfxHandle_t footsteps[FOOTSTEP_TOTAL][4]; + sfxHandle_t sfx_lghit1; + sfxHandle_t sfx_lghit2; + sfxHandle_t sfx_lghit3; + sfxHandle_t sfx_ric1; + sfxHandle_t sfx_ric2; + sfxHandle_t sfx_ric3; + sfxHandle_t sfx_railg; + sfxHandle_t sfx_rockexp; + sfxHandle_t sfx_plasmaexp; +#ifdef MISSIONPACK + sfxHandle_t sfx_proxexp; + sfxHandle_t sfx_nghit; + sfxHandle_t sfx_nghitflesh; + sfxHandle_t sfx_nghitmetal; + sfxHandle_t sfx_chghit; + sfxHandle_t sfx_chghitflesh; + sfxHandle_t sfx_chghitmetal; + sfxHandle_t kamikazeExplodeSound; + sfxHandle_t kamikazeImplodeSound; + sfxHandle_t kamikazeFarSound; + sfxHandle_t useInvulnerabilitySound; + sfxHandle_t invulnerabilityImpactSound1; + sfxHandle_t invulnerabilityImpactSound2; + sfxHandle_t invulnerabilityImpactSound3; + sfxHandle_t invulnerabilityJuicedSound; + sfxHandle_t obeliskHitSound1; + sfxHandle_t obeliskHitSound2; + sfxHandle_t obeliskHitSound3; + sfxHandle_t obeliskRespawnSound; + sfxHandle_t winnerSound; + sfxHandle_t loserSound; + sfxHandle_t youSuckSound; +#endif + sfxHandle_t gibSound; + sfxHandle_t gibBounce1Sound; + sfxHandle_t gibBounce2Sound; + sfxHandle_t gibBounce3Sound; + sfxHandle_t teleInSound; + sfxHandle_t teleOutSound; + sfxHandle_t noAmmoSound; + sfxHandle_t respawnSound; + sfxHandle_t talkSound; + sfxHandle_t landSound; + sfxHandle_t fallSound; + sfxHandle_t jumpPadSound; + + sfxHandle_t oneMinuteSound; + sfxHandle_t fiveMinuteSound; + sfxHandle_t suddenDeathSound; + + sfxHandle_t threeFragSound; + sfxHandle_t twoFragSound; + sfxHandle_t oneFragSound; + + sfxHandle_t hitSound; + sfxHandle_t hitSoundHighArmor; + sfxHandle_t hitSoundLowArmor; + sfxHandle_t hitTeamSound; + sfxHandle_t impressiveSound; + sfxHandle_t excellentSound; + sfxHandle_t deniedSound; + sfxHandle_t humiliationSound; + sfxHandle_t assistSound; + sfxHandle_t defendSound; + sfxHandle_t firstImpressiveSound; + sfxHandle_t firstExcellentSound; + sfxHandle_t firstHumiliationSound; + + sfxHandle_t takenLeadSound; + sfxHandle_t tiedLeadSound; + sfxHandle_t lostLeadSound; + + sfxHandle_t voteNow; + sfxHandle_t votePassed; + sfxHandle_t voteFailed; + + sfxHandle_t watrInSound; + sfxHandle_t watrOutSound; + sfxHandle_t watrUnSound; + + sfxHandle_t flightSound; + sfxHandle_t medkitSound; + + sfxHandle_t weaponHoverSound; + + // teamplay sounds + sfxHandle_t captureAwardSound; + sfxHandle_t redScoredSound; + sfxHandle_t blueScoredSound; + sfxHandle_t redLeadsSound; + sfxHandle_t blueLeadsSound; + sfxHandle_t teamsTiedSound; + + sfxHandle_t captureYourTeamSound; + sfxHandle_t captureOpponentSound; + sfxHandle_t returnYourTeamSound; + sfxHandle_t returnOpponentSound; + sfxHandle_t takenYourTeamSound; + sfxHandle_t takenOpponentSound; + + sfxHandle_t redFlagReturnedSound; + sfxHandle_t blueFlagReturnedSound; + sfxHandle_t neutralFlagReturnedSound; + sfxHandle_t enemyTookYourFlagSound; + sfxHandle_t enemyTookTheFlagSound; + sfxHandle_t yourTeamTookEnemyFlagSound; + sfxHandle_t yourTeamTookTheFlagSound; + sfxHandle_t youHaveFlagSound; + sfxHandle_t yourBaseIsUnderAttackSound; + sfxHandle_t holyShitSound; + + // tournament sounds + sfxHandle_t count3Sound; + sfxHandle_t count2Sound; + sfxHandle_t count1Sound; + sfxHandle_t countFightSound; + sfxHandle_t countPrepareSound; + +#ifdef MISSIONPACK + // new stuff + qhandle_t patrolShader; + qhandle_t assaultShader; + qhandle_t campShader; + qhandle_t followShader; + qhandle_t defendShader; + qhandle_t teamLeaderShader; + qhandle_t retrieveShader; + qhandle_t escortShader; + qhandle_t flagShaders[3]; + sfxHandle_t countPrepareTeamSound; + + sfxHandle_t ammoregenSound; + sfxHandle_t doublerSound; + sfxHandle_t guardSound; + sfxHandle_t scoutSound; +#endif + qhandle_t cursor; + qhandle_t selectCursor; + qhandle_t sizeCursor; + + sfxHandle_t regenSound; + sfxHandle_t protectSound; + sfxHandle_t n_healthSound; + sfxHandle_t hgrenb1aSound; + sfxHandle_t hgrenb2aSound; + sfxHandle_t wstbimplSound; + sfxHandle_t wstbimpmSound; + sfxHandle_t wstbimpdSound; + sfxHandle_t wstbactvSound; + +} cgMedia_t; + + +// The client game static (cgs) structure hold everything +// loaded or calculated from the gamestate. It will NOT +// be cleared when a tournement restart is done, allowing +// all clients to begin playing instantly +typedef struct { + gameState_t gameState; // gamestate from server + glconfig_t glconfig; // rendering configuration + float screenXScale; // derived from glconfig + float screenYScale; + float screenXBias; + + int serverCommandSequence; // reliable command stream counter + int processedSnapshotNum;// the number of snapshots cgame has requested + + qboolean localServer; // detected on startup by checking sv_running + + // parsed from serverinfo + gametype_t gametype; + int dmflags; + int teamflags; + int fraglimit; + int capturelimit; + int timelimit; + int maxclients; + char mapname[MAX_QPATH]; + char redTeam[MAX_QPATH]; + char blueTeam[MAX_QPATH]; + + int voteTime; + int voteYes; + int voteNo; + qboolean voteModified; // beep whenever changed + char voteString[MAX_STRING_TOKENS]; + + int teamVoteTime[2]; + int teamVoteYes[2]; + int teamVoteNo[2]; + qboolean teamVoteModified[2]; // beep whenever changed + char teamVoteString[2][MAX_STRING_TOKENS]; + + int levelStartTime; + + int scores1, scores2; // from configstrings + int redflag, blueflag; // flag status from configstrings + int flagStatus; + + qboolean newHud; + + // + // locally derived information from gamestate + // + qhandle_t gameModels[MAX_MODELS]; + sfxHandle_t gameSounds[MAX_SOUNDS]; + + int numInlineModels; + qhandle_t inlineDrawModel[MAX_MODELS]; + vec3_t inlineModelMidpoints[MAX_MODELS]; + + clientInfo_t clientinfo[MAX_CLIENTS]; + + // teamchat width is *3 because of embedded color codes + char teamChatMsgs[TEAMCHAT_HEIGHT][TEAMCHAT_WIDTH*3+1]; + int teamChatMsgTimes[TEAMCHAT_HEIGHT]; + int teamChatPos; + int teamLastChatPos; + + int cursorX; + int cursorY; + qboolean eventHandling; + qboolean mouseCaptured; + qboolean sizingHud; + void *capturedItem; + qhandle_t activeCursor; + + // orders + int currentOrder; + qboolean orderPending; + int orderTime; + int currentVoiceClient; + int acceptOrderTime; + int acceptTask; + int acceptLeader; + char acceptVoice[MAX_NAME_LENGTH]; + + // media + cgMedia_t media; + +} cgs_t; + +//============================================================================== + +extern cgs_t cgs; +extern cg_t cg; +extern centity_t cg_entities[MAX_GENTITIES]; +extern weaponInfo_t cg_weapons[MAX_WEAPONS]; +extern itemInfo_t cg_items[MAX_ITEMS]; +extern markPoly_t cg_markPolys[MAX_MARK_POLYS]; + +extern vmCvar_t cg_centertime; +extern vmCvar_t cg_runpitch; +extern vmCvar_t cg_runroll; +extern vmCvar_t cg_bobup; +extern vmCvar_t cg_bobpitch; +extern vmCvar_t cg_bobroll; +extern vmCvar_t cg_swingSpeed; +extern vmCvar_t cg_shadows; +extern vmCvar_t cg_gibs; +extern vmCvar_t cg_drawTimer; +extern vmCvar_t cg_drawFPS; +extern vmCvar_t cg_drawSnapshot; +extern vmCvar_t cg_draw3dIcons; +extern vmCvar_t cg_drawIcons; +extern vmCvar_t cg_drawAmmoWarning; +extern vmCvar_t cg_drawCrosshair; +extern vmCvar_t cg_drawCrosshairNames; +extern vmCvar_t cg_drawRewards; +extern vmCvar_t cg_drawTeamOverlay; +extern vmCvar_t cg_teamOverlayUserinfo; +extern vmCvar_t cg_crosshairX; +extern vmCvar_t cg_crosshairY; +extern vmCvar_t cg_crosshairSize; +extern vmCvar_t cg_crosshairHealth; +extern vmCvar_t cg_drawStatus; +extern vmCvar_t cg_draw2D; +extern vmCvar_t cg_animSpeed; +extern vmCvar_t cg_debugAnim; +extern vmCvar_t cg_debugPosition; +extern vmCvar_t cg_debugEvents; +extern vmCvar_t cg_railTrailTime; +extern vmCvar_t cg_errorDecay; +extern vmCvar_t cg_nopredict; +extern vmCvar_t cg_noPlayerAnims; +extern vmCvar_t cg_showmiss; +extern vmCvar_t cg_footsteps; +extern vmCvar_t cg_addMarks; +extern vmCvar_t cg_brassTime; +extern vmCvar_t cg_gun_frame; +extern vmCvar_t cg_gun_x; +extern vmCvar_t cg_gun_y; +extern vmCvar_t cg_gun_z; +extern vmCvar_t cg_drawGun; +extern vmCvar_t cg_viewsize; +extern vmCvar_t cg_tracerChance; +extern vmCvar_t cg_tracerWidth; +extern vmCvar_t cg_tracerLength; +extern vmCvar_t cg_autoswitch; +extern vmCvar_t cg_ignore; +extern vmCvar_t cg_simpleItems; +extern vmCvar_t cg_fov; +extern vmCvar_t cg_zoomFov; +extern vmCvar_t cg_thirdPersonRange; +extern vmCvar_t cg_thirdPersonAngle; +extern vmCvar_t cg_thirdPerson; +extern vmCvar_t cg_stereoSeparation; +extern vmCvar_t cg_lagometer; +extern vmCvar_t cg_drawAttacker; +extern vmCvar_t cg_synchronousClients; +extern vmCvar_t cg_teamChatTime; +extern vmCvar_t cg_teamChatHeight; +extern vmCvar_t cg_stats; +extern vmCvar_t cg_forceModel; +extern vmCvar_t cg_buildScript; +extern vmCvar_t cg_paused; +extern vmCvar_t cg_blood; +extern vmCvar_t cg_predictItems; +extern vmCvar_t cg_deferPlayers; +extern vmCvar_t cg_drawFriend; +extern vmCvar_t cg_teamChatsOnly; +extern vmCvar_t cg_noVoiceChats; +extern vmCvar_t cg_noVoiceText; +extern vmCvar_t cg_scorePlum; +extern vmCvar_t cg_smoothClients; +extern vmCvar_t pmove_fixed; +extern vmCvar_t pmove_msec; +//extern vmCvar_t cg_pmove_fixed; +extern vmCvar_t cg_cameraOrbit; +extern vmCvar_t cg_cameraOrbitDelay; +extern vmCvar_t cg_timescaleFadeEnd; +extern vmCvar_t cg_timescaleFadeSpeed; +extern vmCvar_t cg_timescale; +extern vmCvar_t cg_cameraMode; +extern vmCvar_t cg_smallFont; +extern vmCvar_t cg_bigFont; +extern vmCvar_t cg_noTaunt; +extern vmCvar_t cg_noProjectileTrail; +extern vmCvar_t cg_oldRail; +extern vmCvar_t cg_oldRocket; +extern vmCvar_t cg_oldPlasma; +extern vmCvar_t cg_trueLightning; +#ifdef MISSIONPACK +extern vmCvar_t cg_redTeamName; +extern vmCvar_t cg_blueTeamName; +extern vmCvar_t cg_currentSelectedPlayer; +extern vmCvar_t cg_currentSelectedPlayerName; +extern vmCvar_t cg_singlePlayer; +extern vmCvar_t cg_enableDust; +extern vmCvar_t cg_enableBreath; +extern vmCvar_t cg_singlePlayerActive; +extern vmCvar_t cg_recordSPDemo; +extern vmCvar_t cg_recordSPDemoName; +extern vmCvar_t cg_obeliskRespawnDelay; +#endif + +// +// cg_main.c +// +const char *CG_ConfigString( int index ); +const char *CG_Argv( int arg ); + +void QDECL CG_Printf( const char *msg, ... ); +void QDECL CG_Error( const char *msg, ... ); + +void CG_StartMusic( void ); + +void CG_UpdateCvars( void ); + +int CG_CrosshairPlayer( void ); +int CG_LastAttacker( void ); +void CG_LoadMenus(const char *menuFile); +void CG_KeyEvent(int key, qboolean down); +void CG_MouseEvent(int x, int y); +void CG_EventHandling(int type); +void CG_RankRunFrame( void ); +void CG_SetScoreSelection(void *menu); +score_t *CG_GetSelectedScore(); +void CG_BuildSpectatorString(); + + +// +// cg_view.c +// +void CG_TestModel_f (void); +void CG_TestGun_f (void); +void CG_TestModelNextFrame_f (void); +void CG_TestModelPrevFrame_f (void); +void CG_TestModelNextSkin_f (void); +void CG_TestModelPrevSkin_f (void); +void CG_ZoomDown_f( void ); +void CG_ZoomUp_f( void ); +void CG_AddBufferedSound( sfxHandle_t sfx); + +void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ); + + +// +// cg_drawtools.c +// +void CG_AdjustFrom640( float *x, float *y, float *w, float *h ); +void CG_FillRect( float x, float y, float width, float height, const float *color ); +void CG_DrawPic( float x, float y, float width, float height, qhandle_t hShader ); +void CG_DrawString( float x, float y, const char *string, + float charWidth, float charHeight, const float *modulate ); + + +void CG_DrawStringExt( int x, int y, const char *string, const float *setColor, + qboolean forceColor, qboolean shadow, int charWidth, int charHeight, int maxChars ); +void CG_DrawBigString( int x, int y, const char *s, float alpha ); +void CG_DrawBigStringColor( int x, int y, const char *s, vec4_t color ); +void CG_DrawSmallString( int x, int y, const char *s, float alpha ); +void CG_DrawSmallStringColor( int x, int y, const char *s, vec4_t color ); + +int CG_DrawStrlen( const char *str ); + +float *CG_FadeColor( int startMsec, int totalMsec ); +float *CG_TeamColor( int team ); +void CG_TileClear( void ); +void CG_ColorForHealth( vec4_t hcolor ); +void CG_GetColorForHealth( int health, int armor, vec4_t hcolor ); + +void UI_DrawProportionalString( int x, int y, const char* str, int style, vec4_t color ); +void CG_DrawRect( float x, float y, float width, float height, float size, const float *color ); +void CG_DrawSides(float x, float y, float w, float h, float size); +void CG_DrawTopBottom(float x, float y, float w, float h, float size); + + +// +// cg_draw.c, cg_newDraw.c +// +extern int sortedTeamPlayers[TEAM_MAXOVERLAY]; +extern int numSortedTeamPlayers; +extern int drawTeamOverlayModificationCount; +extern char systemChat[256]; +extern char teamChat1[256]; +extern char teamChat2[256]; + +void CG_AddLagometerFrameInfo( void ); +void CG_AddLagometerSnapshotInfo( snapshot_t *snap ); +void CG_CenterPrint( const char *str, int y, int charWidth ); +void CG_DrawHead( float x, float y, float w, float h, int clientNum, vec3_t headAngles ); +void CG_DrawActive( stereoFrame_t stereoView ); +void CG_DrawFlagModel( float x, float y, float w, float h, int team, qboolean force2D ); +void CG_DrawTeamBackground( int x, int y, int w, int h, float alpha, int team ); +void CG_OwnerDraw(float x, float y, float w, float h, float text_x, float text_y, int ownerDraw, int ownerDrawFlags, int align, float special, float scale, vec4_t color, qhandle_t shader, int textStyle); +void CG_Text_Paint(float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit, int style); +int CG_Text_Width(const char *text, float scale, int limit); +int CG_Text_Height(const char *text, float scale, int limit); +void CG_SelectPrevPlayer(); +void CG_SelectNextPlayer(); +float CG_GetValue(int ownerDraw); +qboolean CG_OwnerDrawVisible(int flags); +void CG_RunMenuScript(char **args); +void CG_ShowResponseHead(); +void CG_SetPrintString(int type, const char *p); +void CG_InitTeamChat(); +void CG_GetTeamColor(vec4_t *color); +const char *CG_GetGameStatusText(); +const char *CG_GetKillerText(); +void CG_Draw3DModel( float x, float y, float w, float h, qhandle_t model, qhandle_t skin, vec3_t origin, vec3_t angles ); +void CG_Text_PaintChar(float x, float y, float width, float height, float scale, float s, float t, float s2, float t2, qhandle_t hShader); +void CG_CheckOrderPending(); +const char *CG_GameTypeString(); +qboolean CG_YourTeamHasFlag(); +qboolean CG_OtherTeamHasFlag(); +qhandle_t CG_StatusHandle(int task); + + + +// +// cg_player.c +// +void CG_Player( centity_t *cent ); +void CG_ResetPlayerEntity( centity_t *cent ); +void CG_AddRefEntityWithPowerups( refEntity_t *ent, entityState_t *state, int team ); +void CG_NewClientInfo( int clientNum ); +sfxHandle_t CG_CustomSound( int clientNum, const char *soundName ); + +// +// cg_predict.c +// +void CG_BuildSolidList( void ); +int CG_PointContents( const vec3_t point, int passEntityNum ); +void CG_Trace( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, + int skipNumber, int mask ); +void CG_PredictPlayerState( void ); +void CG_LoadDeferredPlayers( void ); + + +// +// cg_events.c +// +void CG_CheckEvents( centity_t *cent ); +const char *CG_PlaceString( int rank ); +void CG_EntityEvent( centity_t *cent, vec3_t position ); +void CG_PainEvent( centity_t *cent, int health ); + + +// +// cg_ents.c +// +void CG_SetEntitySoundPosition( centity_t *cent ); +void CG_AddPacketEntities( void ); +void CG_Beam( centity_t *cent ); +void CG_AdjustPositionForMover( const vec3_t in, int moverNum, int fromTime, int toTime, vec3_t out ); + +void CG_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent, + qhandle_t parentModel, char *tagName ); +void CG_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent, + qhandle_t parentModel, char *tagName ); + + + +// +// cg_weapons.c +// +void CG_NextWeapon_f( void ); +void CG_PrevWeapon_f( void ); +void CG_Weapon_f( void ); + +void CG_RegisterWeapon( int weaponNum ); +void CG_RegisterItemVisuals( int itemNum ); + +void CG_FireWeapon( centity_t *cent ); +void CG_MissileHitWall( int weapon, int clientNum, vec3_t origin, vec3_t dir, impactSound_t soundType ); +void CG_MissileHitPlayer( int weapon, vec3_t origin, vec3_t dir, int entityNum ); +void CG_ShotgunFire( entityState_t *es ); +void CG_Bullet( vec3_t origin, int sourceEntityNum, vec3_t normal, qboolean flesh, int fleshEntityNum ); + +void CG_RailTrail( clientInfo_t *ci, vec3_t start, vec3_t end ); +void CG_GrappleTrail( centity_t *ent, const weaponInfo_t *wi ); +void CG_AddViewWeapon (playerState_t *ps); +void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent, int team ); +void CG_DrawWeaponSelect( void ); + +void CG_OutOfAmmoChange( void ); // should this be in pmove? + +// +// cg_marks.c +// +void CG_InitMarkPolys( void ); +void CG_AddMarks( void ); +void CG_ImpactMark( qhandle_t markShader, + const vec3_t origin, const vec3_t dir, + float orientation, + float r, float g, float b, float a, + qboolean alphaFade, + float radius, qboolean temporary ); + +// +// cg_localents.c +// +void CG_InitLocalEntities( void ); +localEntity_t *CG_AllocLocalEntity( void ); +void CG_AddLocalEntities( void ); + +// +// cg_effects.c +// +localEntity_t *CG_SmokePuff( const vec3_t p, + const vec3_t vel, + float radius, + float r, float g, float b, float a, + float duration, + int startTime, + int fadeInTime, + int leFlags, + qhandle_t hShader ); +void CG_BubbleTrail( vec3_t start, vec3_t end, float spacing ); +void CG_SpawnEffect( vec3_t org ); +#ifdef MISSIONPACK +void CG_KamikazeEffect( vec3_t org ); +void CG_ObeliskExplode( vec3_t org, int entityNum ); +void CG_ObeliskPain( vec3_t org ); +void CG_InvulnerabilityImpact( vec3_t org, vec3_t angles ); +void CG_InvulnerabilityJuiced( vec3_t org ); +void CG_LightningBoltBeam( vec3_t start, vec3_t end ); +#endif +void CG_ScorePlum( int client, vec3_t org, int score ); + +void CG_GibPlayer( vec3_t playerOrigin ); +void CG_BigExplode( vec3_t playerOrigin ); + +void CG_Bleed( vec3_t origin, int entityNum ); + +localEntity_t *CG_MakeExplosion( vec3_t origin, vec3_t dir, + qhandle_t hModel, qhandle_t shader, int msec, + qboolean isSprite ); + +// +// cg_snapshot.c +// +void CG_ProcessSnapshots( void ); + +// +// cg_info.c +// +void CG_LoadingString( const char *s ); +void CG_LoadingItem( int itemNum ); +void CG_LoadingClient( int clientNum ); +void CG_DrawInformation( void ); + +// +// cg_scoreboard.c +// +qboolean CG_DrawOldScoreboard( void ); +void CG_DrawOldTourneyScoreboard( void ); + +// +// cg_consolecmds.c +// +qboolean CG_ConsoleCommand( void ); +void CG_InitConsoleCommands( void ); + +// +// cg_servercmds.c +// +void CG_ExecuteNewServerCommands( int latestSequence ); +void CG_ParseServerinfo( void ); +void CG_SetConfigValues( void ); +void CG_LoadVoiceChats( void ); +void CG_ShaderStateChanged(void); +void CG_VoiceChatLocal( int mode, qboolean voiceOnly, int clientNum, int color, const char *cmd ); +void CG_PlayBufferedVoiceChats( void ); + +// +// cg_playerstate.c +// +void CG_Respawn( void ); +void CG_TransitionPlayerState( playerState_t *ps, playerState_t *ops ); +void CG_CheckChangedPredictableEvents( playerState_t *ps ); + + +//=============================================== + +// +// system traps +// These functions are how the cgame communicates with the main game system +// + +// print message on the local console +void trap_Print( const char *fmt ); + +// abort the game +void trap_Error( const char *fmt ); + +// milliseconds should only be used for performance tuning, never +// for anything game related. Get time from the CG_DrawActiveFrame parameter +int trap_Milliseconds( void ); + +// console variable interaction +void trap_Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ); +void trap_Cvar_Update( vmCvar_t *vmCvar ); +void trap_Cvar_Set( const char *var_name, const char *value ); +void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ); + +// ServerCommand and ConsoleCommand parameter access +int trap_Argc( void ); +void trap_Argv( int n, char *buffer, int bufferLength ); +void trap_Args( char *buffer, int bufferLength ); + +// filesystem access +// returns length of file +int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ); +void trap_FS_Read( void *buffer, int len, fileHandle_t f ); +void trap_FS_Write( const void *buffer, int len, fileHandle_t f ); +void trap_FS_FCloseFile( fileHandle_t f ); +int trap_FS_Seek( fileHandle_t f, long offset, int origin ); // fsOrigin_t + +// add commands to the local console as if they were typed in +// for map changing, etc. The command is not executed immediately, +// but will be executed in order the next time console commands +// are processed +void trap_SendConsoleCommand( const char *text ); + +// register a command name so the console can perform command completion. +// FIXME: replace this with a normal console command "defineCommand"? +void trap_AddCommand( const char *cmdName ); + +// send a string to the server over the network +void trap_SendClientCommand( const char *s ); + +// force a screen update, only used during gamestate load +void trap_UpdateScreen( void ); + +// model collision +void trap_CM_LoadMap( const char *mapname ); +int trap_CM_NumInlineModels( void ); +clipHandle_t trap_CM_InlineModel( int index ); // 0 = world, 1+ = bmodels +clipHandle_t trap_CM_TempBoxModel( const vec3_t mins, const vec3_t maxs ); +int trap_CM_PointContents( const vec3_t p, clipHandle_t model ); +int trap_CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles ); +void trap_CM_BoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask ); +void trap_CM_TransformedBoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask, + const vec3_t origin, const vec3_t angles ); + +// Returns the projection of a polygon onto the solid brushes in the world +int trap_CM_MarkFragments( int numPoints, const vec3_t *points, + const vec3_t projection, + int maxPoints, vec3_t pointBuffer, + int maxFragments, markFragment_t *fragmentBuffer ); + +// normal sounds will have their volume dynamically changed as their entity +// moves and the listener moves +void trap_S_StartSound( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx ); +void trap_S_StopLoopingSound(int entnum); + +// a local sound is always played full volume +void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum ); +void trap_S_ClearLoopingSounds( qboolean killall ); +void trap_S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); +void trap_S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); +void trap_S_UpdateEntityPosition( int entityNum, const vec3_t origin ); + +// respatialize recalculates the volumes of sound as they should be heard by the +// given entityNum and position +void trap_S_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ); +sfxHandle_t trap_S_RegisterSound( const char *sample, qboolean compressed ); // returns buzz if not found +void trap_S_StartBackgroundTrack( const char *intro, const char *loop ); // empty name stops music +void trap_S_StopBackgroundTrack( void ); + + +void trap_R_LoadWorldMap( const char *mapname ); + +// all media should be registered during level startup to prevent +// hitches during gameplay +qhandle_t trap_R_RegisterModel( const char *name ); // returns rgb axis if not found +qhandle_t trap_R_RegisterSkin( const char *name ); // returns all white if not found +qhandle_t trap_R_RegisterShader( const char *name ); // returns all white if not found +qhandle_t trap_R_RegisterShaderNoMip( const char *name ); // returns all white if not found + +// a scene is built up by calls to R_ClearScene and the various R_Add functions. +// Nothing is drawn until R_RenderScene is called. +void trap_R_ClearScene( void ); +void trap_R_AddRefEntityToScene( const refEntity_t *re ); + +// polys are intended for simple wall marks, not really for doing +// significant construction +void trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts ); +void trap_R_AddPolysToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts, int numPolys ); +void trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ); +int trap_R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir ); +void trap_R_RenderScene( const refdef_t *fd ); +void trap_R_SetColor( const float *rgba ); // NULL = 1,1,1,1 +void trap_R_DrawStretchPic( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader ); +void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ); +int trap_R_LerpTag( orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame, + float frac, const char *tagName ); +void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset ); + +// The glconfig_t will not change during the life of a cgame. +// If it needs to change, the entire cgame will be restarted, because +// all the qhandle_t are then invalid. +void trap_GetGlconfig( glconfig_t *glconfig ); + +// the gamestate should be grabbed at startup, and whenever a +// configstring changes +void trap_GetGameState( gameState_t *gamestate ); + +// cgame will poll each frame to see if a newer snapshot has arrived +// that it is interested in. The time is returned seperately so that +// snapshot latency can be calculated. +void trap_GetCurrentSnapshotNumber( int *snapshotNumber, int *serverTime ); + +// a snapshot get can fail if the snapshot (or the entties it holds) is so +// old that it has fallen out of the client system queue +qboolean trap_GetSnapshot( int snapshotNumber, snapshot_t *snapshot ); + +// retrieve a text command from the server stream +// the current snapshot will hold the number of the most recent command +// qfalse can be returned if the client system handled the command +// argc() / argv() can be used to examine the parameters of the command +qboolean trap_GetServerCommand( int serverCommandNumber ); + +// returns the most recent command number that can be passed to GetUserCmd +// this will always be at least one higher than the number in the current +// snapshot, and it may be quite a few higher if it is a fast computer on +// a lagged connection +int trap_GetCurrentCmdNumber( void ); + +qboolean trap_GetUserCmd( int cmdNumber, usercmd_t *ucmd ); + +// used for the weapon select and zoom +void trap_SetUserCmdValue( int stateValue, float sensitivityScale ); + +// aids for VM testing +void testPrintInt( char *string, int i ); +void testPrintFloat( char *string, float f ); + +int trap_MemoryRemaining( void ); +void trap_R_RegisterFont(const char *fontName, int pointSize, fontInfo_t *font); +qboolean trap_Key_IsDown( int keynum ); +int trap_Key_GetCatcher( void ); +void trap_Key_SetCatcher( int catcher ); +int trap_Key_GetKey( const char *binding ); + + +typedef enum { + SYSTEM_PRINT, + CHAT_PRINT, + TEAMCHAT_PRINT +} q3print_t; // bk001201 - warning: useless keyword or type name in empty declaration + + +int trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits); +e_status trap_CIN_StopCinematic(int handle); +e_status trap_CIN_RunCinematic (int handle); +void trap_CIN_DrawCinematic (int handle); +void trap_CIN_SetExtents (int handle, int x, int y, int w, int h); + +void trap_SnapVector( float *v ); + +qboolean trap_loadCamera(const char *name); +void trap_startCamera(int time); +qboolean trap_getCameraInfo(int time, vec3_t *origin, vec3_t *angles); + +qboolean trap_GetEntityToken( char *buffer, int bufferSize ); + +void CG_ClearParticles (void); +void CG_AddParticles (void); +void CG_ParticleSnow (qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum); +void CG_ParticleSmoke (qhandle_t pshader, centity_t *cent); +void CG_AddParticleShrapnel (localEntity_t *le); +void CG_ParticleSnowFlurry (qhandle_t pshader, centity_t *cent); +void CG_ParticleBulletDebris (vec3_t org, vec3_t vel, int duration); +void CG_ParticleSparks (vec3_t org, vec3_t vel, int duration, float x, float y, float speed); +void CG_ParticleDust (centity_t *cent, vec3_t origin, vec3_t dir); +void CG_ParticleMisc (qhandle_t pshader, vec3_t origin, int size, int duration, float alpha); +void CG_ParticleExplosion (char *animStr, vec3_t origin, vec3_t vel, int duration, int sizeStart, int sizeEnd); +extern qboolean initparticles; +int CG_NewParticleArea ( int num ); + + diff --git a/code/cgame/cg_localents.c b/code/cgame/cg_localents.c index 534b147..b14df64 100755 --- a/code/cgame/cg_localents.c +++ b/code/cgame/cg_localents.c @@ -1,886 +1,886 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -// cg_localents.c -- every frame, generate renderer commands for locally -// processed entities, like smoke puffs, gibs, shells, etc. - -#include "cg_local.h" - -#define MAX_LOCAL_ENTITIES 512 -localEntity_t cg_localEntities[MAX_LOCAL_ENTITIES]; -localEntity_t cg_activeLocalEntities; // double linked list -localEntity_t *cg_freeLocalEntities; // single linked list - -/* -=================== -CG_InitLocalEntities - -This is called at startup and for tournement restarts -=================== -*/ -void CG_InitLocalEntities( void ) { - int i; - - memset( cg_localEntities, 0, sizeof( cg_localEntities ) ); - cg_activeLocalEntities.next = &cg_activeLocalEntities; - cg_activeLocalEntities.prev = &cg_activeLocalEntities; - cg_freeLocalEntities = cg_localEntities; - for ( i = 0 ; i < MAX_LOCAL_ENTITIES - 1 ; i++ ) { - cg_localEntities[i].next = &cg_localEntities[i+1]; - } -} - - -/* -================== -CG_FreeLocalEntity -================== -*/ -void CG_FreeLocalEntity( localEntity_t *le ) { - if ( !le->prev ) { - CG_Error( "CG_FreeLocalEntity: not active" ); - } - - // remove from the doubly linked active list - le->prev->next = le->next; - le->next->prev = le->prev; - - // the free list is only singly linked - le->next = cg_freeLocalEntities; - cg_freeLocalEntities = le; -} - -/* -=================== -CG_AllocLocalEntity - -Will allways succeed, even if it requires freeing an old active entity -=================== -*/ -localEntity_t *CG_AllocLocalEntity( void ) { - localEntity_t *le; - - if ( !cg_freeLocalEntities ) { - // no free entities, so free the one at the end of the chain - // remove the oldest active entity - CG_FreeLocalEntity( cg_activeLocalEntities.prev ); - } - - le = cg_freeLocalEntities; - cg_freeLocalEntities = cg_freeLocalEntities->next; - - memset( le, 0, sizeof( *le ) ); - - // link into the active list - le->next = cg_activeLocalEntities.next; - le->prev = &cg_activeLocalEntities; - cg_activeLocalEntities.next->prev = le; - cg_activeLocalEntities.next = le; - return le; -} - - -/* -==================================================================================== - -FRAGMENT PROCESSING - -A fragment localentity interacts with the environment in some way (hitting walls), -or generates more localentities along a trail. - -==================================================================================== -*/ - -/* -================ -CG_BloodTrail - -Leave expanding blood puffs behind gibs -================ -*/ -void CG_BloodTrail( localEntity_t *le ) { - int t; - int t2; - int step; - vec3_t newOrigin; - localEntity_t *blood; - - step = 150; - t = step * ( (cg.time - cg.frametime + step ) / step ); - t2 = step * ( cg.time / step ); - - for ( ; t <= t2; t += step ) { - BG_EvaluateTrajectory( &le->pos, t, newOrigin ); - - blood = CG_SmokePuff( newOrigin, vec3_origin, - 20, // radius - 1, 1, 1, 1, // color - 2000, // trailTime - t, // startTime - 0, // fadeInTime - 0, // flags - cgs.media.bloodTrailShader ); - // use the optimized version - blood->leType = LE_FALL_SCALE_FADE; - // drop a total of 40 units over its lifetime - blood->pos.trDelta[2] = 40; - } -} - - -/* -================ -CG_FragmentBounceMark -================ -*/ -void CG_FragmentBounceMark( localEntity_t *le, trace_t *trace ) { - int radius; - - if ( le->leMarkType == LEMT_BLOOD ) { - - radius = 16 + (rand()&31); - CG_ImpactMark( cgs.media.bloodMarkShader, trace->endpos, trace->plane.normal, random()*360, - 1,1,1,1, qtrue, radius, qfalse ); - } else if ( le->leMarkType == LEMT_BURN ) { - - radius = 8 + (rand()&15); - CG_ImpactMark( cgs.media.burnMarkShader, trace->endpos, trace->plane.normal, random()*360, - 1,1,1,1, qtrue, radius, qfalse ); - } - - - // don't allow a fragment to make multiple marks, or they - // pile up while settling - le->leMarkType = LEMT_NONE; -} - -/* -================ -CG_FragmentBounceSound -================ -*/ -void CG_FragmentBounceSound( localEntity_t *le, trace_t *trace ) { - if ( le->leBounceSoundType == LEBS_BLOOD ) { - // half the gibs will make splat sounds - if ( rand() & 1 ) { - int r = rand()&3; - sfxHandle_t s; - - if ( r == 0 ) { - s = cgs.media.gibBounce1Sound; - } else if ( r == 1 ) { - s = cgs.media.gibBounce2Sound; - } else { - s = cgs.media.gibBounce3Sound; - } - trap_S_StartSound( trace->endpos, ENTITYNUM_WORLD, CHAN_AUTO, s ); - } - } else if ( le->leBounceSoundType == LEBS_BRASS ) { - - } - - // don't allow a fragment to make multiple bounce sounds, - // or it gets too noisy as they settle - le->leBounceSoundType = LEBS_NONE; -} - - -/* -================ -CG_ReflectVelocity -================ -*/ -void CG_ReflectVelocity( localEntity_t *le, trace_t *trace ) { - vec3_t velocity; - float dot; - int hitTime; - - // reflect the velocity on the trace plane - hitTime = cg.time - cg.frametime + cg.frametime * trace->fraction; - BG_EvaluateTrajectoryDelta( &le->pos, hitTime, velocity ); - dot = DotProduct( velocity, trace->plane.normal ); - VectorMA( velocity, -2*dot, trace->plane.normal, le->pos.trDelta ); - - VectorScale( le->pos.trDelta, le->bounceFactor, le->pos.trDelta ); - - VectorCopy( trace->endpos, le->pos.trBase ); - le->pos.trTime = cg.time; - - - // check for stop, making sure that even on low FPS systems it doesn't bobble - if ( trace->allsolid || - ( trace->plane.normal[2] > 0 && - ( le->pos.trDelta[2] < 40 || le->pos.trDelta[2] < -cg.frametime * le->pos.trDelta[2] ) ) ) { - le->pos.trType = TR_STATIONARY; - } else { - - } -} - -/* -================ -CG_AddFragment -================ -*/ -void CG_AddFragment( localEntity_t *le ) { - vec3_t newOrigin; - trace_t trace; - - if ( le->pos.trType == TR_STATIONARY ) { - // sink into the ground if near the removal time - int t; - float oldZ; - - t = le->endTime - cg.time; - if ( t < SINK_TIME ) { - // we must use an explicit lighting origin, otherwise the - // lighting would be lost as soon as the origin went - // into the ground - VectorCopy( le->refEntity.origin, le->refEntity.lightingOrigin ); - le->refEntity.renderfx |= RF_LIGHTING_ORIGIN; - oldZ = le->refEntity.origin[2]; - le->refEntity.origin[2] -= 16 * ( 1.0 - (float)t / SINK_TIME ); - trap_R_AddRefEntityToScene( &le->refEntity ); - le->refEntity.origin[2] = oldZ; - } else { - trap_R_AddRefEntityToScene( &le->refEntity ); - } - - return; - } - - // calculate new position - BG_EvaluateTrajectory( &le->pos, cg.time, newOrigin ); - - // trace a line from previous position to new position - CG_Trace( &trace, le->refEntity.origin, NULL, NULL, newOrigin, -1, CONTENTS_SOLID ); - if ( trace.fraction == 1.0 ) { - // still in free fall - VectorCopy( newOrigin, le->refEntity.origin ); - - if ( le->leFlags & LEF_TUMBLE ) { - vec3_t angles; - - BG_EvaluateTrajectory( &le->angles, cg.time, angles ); - AnglesToAxis( angles, le->refEntity.axis ); - } - - trap_R_AddRefEntityToScene( &le->refEntity ); - - // add a blood trail - if ( le->leBounceSoundType == LEBS_BLOOD ) { - CG_BloodTrail( le ); - } - - return; - } - - // if it is in a nodrop zone, remove it - // this keeps gibs from waiting at the bottom of pits of death - // and floating levels - if ( trap_CM_PointContents( trace.endpos, 0 ) & CONTENTS_NODROP ) { - CG_FreeLocalEntity( le ); - return; - } - - // leave a mark - CG_FragmentBounceMark( le, &trace ); - - // do a bouncy sound - CG_FragmentBounceSound( le, &trace ); - - // reflect the velocity on the trace plane - CG_ReflectVelocity( le, &trace ); - - trap_R_AddRefEntityToScene( &le->refEntity ); -} - -/* -===================================================================== - -TRIVIAL LOCAL ENTITIES - -These only do simple scaling or modulation before passing to the renderer -===================================================================== -*/ - -/* -==================== -CG_AddFadeRGB -==================== -*/ -void CG_AddFadeRGB( localEntity_t *le ) { - refEntity_t *re; - float c; - - re = &le->refEntity; - - c = ( le->endTime - cg.time ) * le->lifeRate; - c *= 0xff; - - re->shaderRGBA[0] = le->color[0] * c; - re->shaderRGBA[1] = le->color[1] * c; - re->shaderRGBA[2] = le->color[2] * c; - re->shaderRGBA[3] = le->color[3] * c; - - trap_R_AddRefEntityToScene( re ); -} - -/* -================== -CG_AddMoveScaleFade -================== -*/ -static void CG_AddMoveScaleFade( localEntity_t *le ) { - refEntity_t *re; - float c; - vec3_t delta; - float len; - - re = &le->refEntity; - - if ( le->fadeInTime > le->startTime && cg.time < le->fadeInTime ) { - // fade / grow time - c = 1.0 - (float) ( le->fadeInTime - cg.time ) / ( le->fadeInTime - le->startTime ); - } - else { - // fade / grow time - c = ( le->endTime - cg.time ) * le->lifeRate; - } - - re->shaderRGBA[3] = 0xff * c * le->color[3]; - - if ( !( le->leFlags & LEF_PUFF_DONT_SCALE ) ) { - re->radius = le->radius * ( 1.0 - c ) + 8; - } - - BG_EvaluateTrajectory( &le->pos, cg.time, re->origin ); - - // if the view would be "inside" the sprite, kill the sprite - // so it doesn't add too much overdraw - VectorSubtract( re->origin, cg.refdef.vieworg, delta ); - len = VectorLength( delta ); - if ( len < le->radius ) { - CG_FreeLocalEntity( le ); - return; - } - - trap_R_AddRefEntityToScene( re ); -} - - -/* -=================== -CG_AddScaleFade - -For rocket smokes that hang in place, fade out, and are -removed if the view passes through them. -There are often many of these, so it needs to be simple. -=================== -*/ -static void CG_AddScaleFade( localEntity_t *le ) { - refEntity_t *re; - float c; - vec3_t delta; - float len; - - re = &le->refEntity; - - // fade / grow time - c = ( le->endTime - cg.time ) * le->lifeRate; - - re->shaderRGBA[3] = 0xff * c * le->color[3]; - re->radius = le->radius * ( 1.0 - c ) + 8; - - // if the view would be "inside" the sprite, kill the sprite - // so it doesn't add too much overdraw - VectorSubtract( re->origin, cg.refdef.vieworg, delta ); - len = VectorLength( delta ); - if ( len < le->radius ) { - CG_FreeLocalEntity( le ); - return; - } - - trap_R_AddRefEntityToScene( re ); -} - - -/* -================= -CG_AddFallScaleFade - -This is just an optimized CG_AddMoveScaleFade -For blood mists that drift down, fade out, and are -removed if the view passes through them. -There are often 100+ of these, so it needs to be simple. -================= -*/ -static void CG_AddFallScaleFade( localEntity_t *le ) { - refEntity_t *re; - float c; - vec3_t delta; - float len; - - re = &le->refEntity; - - // fade time - c = ( le->endTime - cg.time ) * le->lifeRate; - - re->shaderRGBA[3] = 0xff * c * le->color[3]; - - re->origin[2] = le->pos.trBase[2] - ( 1.0 - c ) * le->pos.trDelta[2]; - - re->radius = le->radius * ( 1.0 - c ) + 16; - - // if the view would be "inside" the sprite, kill the sprite - // so it doesn't add too much overdraw - VectorSubtract( re->origin, cg.refdef.vieworg, delta ); - len = VectorLength( delta ); - if ( len < le->radius ) { - CG_FreeLocalEntity( le ); - return; - } - - trap_R_AddRefEntityToScene( re ); -} - - - -/* -================ -CG_AddExplosion -================ -*/ -static void CG_AddExplosion( localEntity_t *ex ) { - refEntity_t *ent; - - ent = &ex->refEntity; - - // add the entity - trap_R_AddRefEntityToScene(ent); - - // add the dlight - if ( ex->light ) { - float light; - - light = (float)( cg.time - ex->startTime ) / ( ex->endTime - ex->startTime ); - if ( light < 0.5 ) { - light = 1.0; - } else { - light = 1.0 - ( light - 0.5 ) * 2; - } - light = ex->light * light; - trap_R_AddLightToScene(ent->origin, light, ex->lightColor[0], ex->lightColor[1], ex->lightColor[2] ); - } -} - -/* -================ -CG_AddSpriteExplosion -================ -*/ -static void CG_AddSpriteExplosion( localEntity_t *le ) { - refEntity_t re; - float c; - - re = le->refEntity; - - c = ( le->endTime - cg.time ) / ( float ) ( le->endTime - le->startTime ); - if ( c > 1 ) { - c = 1.0; // can happen during connection problems - } - - re.shaderRGBA[0] = 0xff; - re.shaderRGBA[1] = 0xff; - re.shaderRGBA[2] = 0xff; - re.shaderRGBA[3] = 0xff * c * 0.33; - - re.reType = RT_SPRITE; - re.radius = 42 * ( 1.0 - c ) + 30; - - trap_R_AddRefEntityToScene( &re ); - - // add the dlight - if ( le->light ) { - float light; - - light = (float)( cg.time - le->startTime ) / ( le->endTime - le->startTime ); - if ( light < 0.5 ) { - light = 1.0; - } else { - light = 1.0 - ( light - 0.5 ) * 2; - } - light = le->light * light; - trap_R_AddLightToScene(re.origin, light, le->lightColor[0], le->lightColor[1], le->lightColor[2] ); - } -} - - -#ifdef MISSIONPACK -/* -==================== -CG_AddKamikaze -==================== -*/ -void CG_AddKamikaze( localEntity_t *le ) { - refEntity_t *re; - refEntity_t shockwave; - float c; - vec3_t test, axis[3]; - int t; - - re = &le->refEntity; - - t = cg.time - le->startTime; - VectorClear( test ); - AnglesToAxis( test, axis ); - - if (t > KAMI_SHOCKWAVE_STARTTIME && t < KAMI_SHOCKWAVE_ENDTIME) { - - if (!(le->leFlags & LEF_SOUND1)) { -// trap_S_StartSound (re->origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.kamikazeExplodeSound ); - trap_S_StartLocalSound(cgs.media.kamikazeExplodeSound, CHAN_AUTO); - le->leFlags |= LEF_SOUND1; - } - // 1st kamikaze shockwave - memset(&shockwave, 0, sizeof(shockwave)); - shockwave.hModel = cgs.media.kamikazeShockWave; - shockwave.reType = RT_MODEL; - shockwave.shaderTime = re->shaderTime; - VectorCopy(re->origin, shockwave.origin); - - c = (float)(t - KAMI_SHOCKWAVE_STARTTIME) / (float)(KAMI_SHOCKWAVE_ENDTIME - KAMI_SHOCKWAVE_STARTTIME); - VectorScale( axis[0], c * KAMI_SHOCKWAVE_MAXRADIUS / KAMI_SHOCKWAVEMODEL_RADIUS, shockwave.axis[0] ); - VectorScale( axis[1], c * KAMI_SHOCKWAVE_MAXRADIUS / KAMI_SHOCKWAVEMODEL_RADIUS, shockwave.axis[1] ); - VectorScale( axis[2], c * KAMI_SHOCKWAVE_MAXRADIUS / KAMI_SHOCKWAVEMODEL_RADIUS, shockwave.axis[2] ); - shockwave.nonNormalizedAxes = qtrue; - - if (t > KAMI_SHOCKWAVEFADE_STARTTIME) { - c = (float)(t - KAMI_SHOCKWAVEFADE_STARTTIME) / (float)(KAMI_SHOCKWAVE_ENDTIME - KAMI_SHOCKWAVEFADE_STARTTIME); - } - else { - c = 0; - } - c *= 0xff; - shockwave.shaderRGBA[0] = 0xff - c; - shockwave.shaderRGBA[1] = 0xff - c; - shockwave.shaderRGBA[2] = 0xff - c; - shockwave.shaderRGBA[3] = 0xff - c; - - trap_R_AddRefEntityToScene( &shockwave ); - } - - if (t > KAMI_EXPLODE_STARTTIME && t < KAMI_IMPLODE_ENDTIME) { - // explosion and implosion - c = ( le->endTime - cg.time ) * le->lifeRate; - c *= 0xff; - re->shaderRGBA[0] = le->color[0] * c; - re->shaderRGBA[1] = le->color[1] * c; - re->shaderRGBA[2] = le->color[2] * c; - re->shaderRGBA[3] = le->color[3] * c; - - if( t < KAMI_IMPLODE_STARTTIME ) { - c = (float)(t - KAMI_EXPLODE_STARTTIME) / (float)(KAMI_IMPLODE_STARTTIME - KAMI_EXPLODE_STARTTIME); - } - else { - if (!(le->leFlags & LEF_SOUND2)) { -// trap_S_StartSound (re->origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.kamikazeImplodeSound ); - trap_S_StartLocalSound(cgs.media.kamikazeImplodeSound, CHAN_AUTO); - le->leFlags |= LEF_SOUND2; - } - c = (float)(KAMI_IMPLODE_ENDTIME - t) / (float) (KAMI_IMPLODE_ENDTIME - KAMI_IMPLODE_STARTTIME); - } - VectorScale( axis[0], c * KAMI_BOOMSPHERE_MAXRADIUS / KAMI_BOOMSPHEREMODEL_RADIUS, re->axis[0] ); - VectorScale( axis[1], c * KAMI_BOOMSPHERE_MAXRADIUS / KAMI_BOOMSPHEREMODEL_RADIUS, re->axis[1] ); - VectorScale( axis[2], c * KAMI_BOOMSPHERE_MAXRADIUS / KAMI_BOOMSPHEREMODEL_RADIUS, re->axis[2] ); - re->nonNormalizedAxes = qtrue; - - trap_R_AddRefEntityToScene( re ); - // add the dlight - trap_R_AddLightToScene( re->origin, c * 1000.0, 1.0, 1.0, c ); - } - - if (t > KAMI_SHOCKWAVE2_STARTTIME && t < KAMI_SHOCKWAVE2_ENDTIME) { - // 2nd kamikaze shockwave - if (le->angles.trBase[0] == 0 && - le->angles.trBase[1] == 0 && - le->angles.trBase[2] == 0) { - le->angles.trBase[0] = random() * 360; - le->angles.trBase[1] = random() * 360; - le->angles.trBase[2] = random() * 360; - } - else { - c = 0; - } - memset(&shockwave, 0, sizeof(shockwave)); - shockwave.hModel = cgs.media.kamikazeShockWave; - shockwave.reType = RT_MODEL; - shockwave.shaderTime = re->shaderTime; - VectorCopy(re->origin, shockwave.origin); - - test[0] = le->angles.trBase[0]; - test[1] = le->angles.trBase[1]; - test[2] = le->angles.trBase[2]; - AnglesToAxis( test, axis ); - - c = (float)(t - KAMI_SHOCKWAVE2_STARTTIME) / (float)(KAMI_SHOCKWAVE2_ENDTIME - KAMI_SHOCKWAVE2_STARTTIME); - VectorScale( axis[0], c * KAMI_SHOCKWAVE2_MAXRADIUS / KAMI_SHOCKWAVEMODEL_RADIUS, shockwave.axis[0] ); - VectorScale( axis[1], c * KAMI_SHOCKWAVE2_MAXRADIUS / KAMI_SHOCKWAVEMODEL_RADIUS, shockwave.axis[1] ); - VectorScale( axis[2], c * KAMI_SHOCKWAVE2_MAXRADIUS / KAMI_SHOCKWAVEMODEL_RADIUS, shockwave.axis[2] ); - shockwave.nonNormalizedAxes = qtrue; - - if (t > KAMI_SHOCKWAVE2FADE_STARTTIME) { - c = (float)(t - KAMI_SHOCKWAVE2FADE_STARTTIME) / (float)(KAMI_SHOCKWAVE2_ENDTIME - KAMI_SHOCKWAVE2FADE_STARTTIME); - } - else { - c = 0; - } - c *= 0xff; - shockwave.shaderRGBA[0] = 0xff - c; - shockwave.shaderRGBA[1] = 0xff - c; - shockwave.shaderRGBA[2] = 0xff - c; - shockwave.shaderRGBA[3] = 0xff - c; - - trap_R_AddRefEntityToScene( &shockwave ); - } -} - -/* -=================== -CG_AddInvulnerabilityImpact -=================== -*/ -void CG_AddInvulnerabilityImpact( localEntity_t *le ) { - trap_R_AddRefEntityToScene( &le->refEntity ); -} - -/* -=================== -CG_AddInvulnerabilityJuiced -=================== -*/ -void CG_AddInvulnerabilityJuiced( localEntity_t *le ) { - int t; - - t = cg.time - le->startTime; - if ( t > 3000 ) { - le->refEntity.axis[0][0] = (float) 1.0 + 0.3 * (t - 3000) / 2000; - le->refEntity.axis[1][1] = (float) 1.0 + 0.3 * (t - 3000) / 2000; - le->refEntity.axis[2][2] = (float) 0.7 + 0.3 * (2000 - (t - 3000)) / 2000; - } - if ( t > 5000 ) { - le->endTime = 0; - CG_GibPlayer( le->refEntity.origin ); - } - else { - trap_R_AddRefEntityToScene( &le->refEntity ); - } -} - -/* -=================== -CG_AddRefEntity -=================== -*/ -void CG_AddRefEntity( localEntity_t *le ) { - if (le->endTime < cg.time) { - CG_FreeLocalEntity( le ); - return; - } - trap_R_AddRefEntityToScene( &le->refEntity ); -} - -#endif -/* -=================== -CG_AddScorePlum -=================== -*/ -#define NUMBER_SIZE 8 - -void CG_AddScorePlum( localEntity_t *le ) { - refEntity_t *re; - vec3_t origin, delta, dir, vec, up = {0, 0, 1}; - float c, len; - int i, score, digits[10], numdigits, negative; - - re = &le->refEntity; - - c = ( le->endTime - cg.time ) * le->lifeRate; - - score = le->radius; - if (score < 0) { - re->shaderRGBA[0] = 0xff; - re->shaderRGBA[1] = 0x11; - re->shaderRGBA[2] = 0x11; - } - else { - re->shaderRGBA[0] = 0xff; - re->shaderRGBA[1] = 0xff; - re->shaderRGBA[2] = 0xff; - if (score >= 50) { - re->shaderRGBA[1] = 0; - } else if (score >= 20) { - re->shaderRGBA[0] = re->shaderRGBA[1] = 0; - } else if (score >= 10) { - re->shaderRGBA[2] = 0; - } else if (score >= 2) { - re->shaderRGBA[0] = re->shaderRGBA[2] = 0; - } - - } - if (c < 0.25) - re->shaderRGBA[3] = 0xff * 4 * c; - else - re->shaderRGBA[3] = 0xff; - - re->radius = NUMBER_SIZE / 2; - - VectorCopy(le->pos.trBase, origin); - origin[2] += 110 - c * 100; - - VectorSubtract(cg.refdef.vieworg, origin, dir); - CrossProduct(dir, up, vec); - VectorNormalize(vec); - - VectorMA(origin, -10 + 20 * sin(c * 2 * M_PI), vec, origin); - - // if the view would be "inside" the sprite, kill the sprite - // so it doesn't add too much overdraw - VectorSubtract( origin, cg.refdef.vieworg, delta ); - len = VectorLength( delta ); - if ( len < 20 ) { - CG_FreeLocalEntity( le ); - return; - } - - negative = qfalse; - if (score < 0) { - negative = qtrue; - score = -score; - } - - for (numdigits = 0; !(numdigits && !score); numdigits++) { - digits[numdigits] = score % 10; - score = score / 10; - } - - if (negative) { - digits[numdigits] = 10; - numdigits++; - } - - for (i = 0; i < numdigits; i++) { - VectorMA(origin, (float) (((float) numdigits / 2) - i) * NUMBER_SIZE, vec, re->origin); - re->customShader = cgs.media.numberShaders[digits[numdigits-1-i]]; - trap_R_AddRefEntityToScene( re ); - } -} - - - - -//============================================================================== - -/* -=================== -CG_AddLocalEntities - -=================== -*/ -void CG_AddLocalEntities( void ) { - localEntity_t *le, *next; - - // walk the list backwards, so any new local entities generated - // (trails, marks, etc) will be present this frame - le = cg_activeLocalEntities.prev; - for ( ; le != &cg_activeLocalEntities ; le = next ) { - // grab next now, so if the local entity is freed we - // still have it - next = le->prev; - - if ( cg.time >= le->endTime ) { - CG_FreeLocalEntity( le ); - continue; - } - switch ( le->leType ) { - default: - CG_Error( "Bad leType: %i", le->leType ); - break; - - case LE_MARK: - break; - - case LE_SPRITE_EXPLOSION: - CG_AddSpriteExplosion( le ); - break; - - case LE_EXPLOSION: - CG_AddExplosion( le ); - break; - - case LE_FRAGMENT: // gibs and brass - CG_AddFragment( le ); - break; - - case LE_MOVE_SCALE_FADE: // water bubbles - CG_AddMoveScaleFade( le ); - break; - - case LE_FADE_RGB: // teleporters, railtrails - CG_AddFadeRGB( le ); - break; - - case LE_FALL_SCALE_FADE: // gib blood trails - CG_AddFallScaleFade( le ); - break; - - case LE_SCALE_FADE: // rocket trails - CG_AddScaleFade( le ); - break; - - case LE_SCOREPLUM: - CG_AddScorePlum( le ); - break; - -#ifdef MISSIONPACK - case LE_KAMIKAZE: - CG_AddKamikaze( le ); - break; - case LE_INVULIMPACT: - CG_AddInvulnerabilityImpact( le ); - break; - case LE_INVULJUICED: - CG_AddInvulnerabilityJuiced( le ); - break; - case LE_SHOWREFENTITY: - CG_AddRefEntity( le ); - break; -#endif - } - } -} - - - - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +// cg_localents.c -- every frame, generate renderer commands for locally +// processed entities, like smoke puffs, gibs, shells, etc. + +#include "cg_local.h" + +#define MAX_LOCAL_ENTITIES 512 +localEntity_t cg_localEntities[MAX_LOCAL_ENTITIES]; +localEntity_t cg_activeLocalEntities; // double linked list +localEntity_t *cg_freeLocalEntities; // single linked list + +/* +=================== +CG_InitLocalEntities + +This is called at startup and for tournement restarts +=================== +*/ +void CG_InitLocalEntities( void ) { + int i; + + memset( cg_localEntities, 0, sizeof( cg_localEntities ) ); + cg_activeLocalEntities.next = &cg_activeLocalEntities; + cg_activeLocalEntities.prev = &cg_activeLocalEntities; + cg_freeLocalEntities = cg_localEntities; + for ( i = 0 ; i < MAX_LOCAL_ENTITIES - 1 ; i++ ) { + cg_localEntities[i].next = &cg_localEntities[i+1]; + } +} + + +/* +================== +CG_FreeLocalEntity +================== +*/ +void CG_FreeLocalEntity( localEntity_t *le ) { + if ( !le->prev ) { + CG_Error( "CG_FreeLocalEntity: not active" ); + } + + // remove from the doubly linked active list + le->prev->next = le->next; + le->next->prev = le->prev; + + // the free list is only singly linked + le->next = cg_freeLocalEntities; + cg_freeLocalEntities = le; +} + +/* +=================== +CG_AllocLocalEntity + +Will allways succeed, even if it requires freeing an old active entity +=================== +*/ +localEntity_t *CG_AllocLocalEntity( void ) { + localEntity_t *le; + + if ( !cg_freeLocalEntities ) { + // no free entities, so free the one at the end of the chain + // remove the oldest active entity + CG_FreeLocalEntity( cg_activeLocalEntities.prev ); + } + + le = cg_freeLocalEntities; + cg_freeLocalEntities = cg_freeLocalEntities->next; + + memset( le, 0, sizeof( *le ) ); + + // link into the active list + le->next = cg_activeLocalEntities.next; + le->prev = &cg_activeLocalEntities; + cg_activeLocalEntities.next->prev = le; + cg_activeLocalEntities.next = le; + return le; +} + + +/* +==================================================================================== + +FRAGMENT PROCESSING + +A fragment localentity interacts with the environment in some way (hitting walls), +or generates more localentities along a trail. + +==================================================================================== +*/ + +/* +================ +CG_BloodTrail + +Leave expanding blood puffs behind gibs +================ +*/ +void CG_BloodTrail( localEntity_t *le ) { + int t; + int t2; + int step; + vec3_t newOrigin; + localEntity_t *blood; + + step = 150; + t = step * ( (cg.time - cg.frametime + step ) / step ); + t2 = step * ( cg.time / step ); + + for ( ; t <= t2; t += step ) { + BG_EvaluateTrajectory( &le->pos, t, newOrigin ); + + blood = CG_SmokePuff( newOrigin, vec3_origin, + 20, // radius + 1, 1, 1, 1, // color + 2000, // trailTime + t, // startTime + 0, // fadeInTime + 0, // flags + cgs.media.bloodTrailShader ); + // use the optimized version + blood->leType = LE_FALL_SCALE_FADE; + // drop a total of 40 units over its lifetime + blood->pos.trDelta[2] = 40; + } +} + + +/* +================ +CG_FragmentBounceMark +================ +*/ +void CG_FragmentBounceMark( localEntity_t *le, trace_t *trace ) { + int radius; + + if ( le->leMarkType == LEMT_BLOOD ) { + + radius = 16 + (rand()&31); + CG_ImpactMark( cgs.media.bloodMarkShader, trace->endpos, trace->plane.normal, random()*360, + 1,1,1,1, qtrue, radius, qfalse ); + } else if ( le->leMarkType == LEMT_BURN ) { + + radius = 8 + (rand()&15); + CG_ImpactMark( cgs.media.burnMarkShader, trace->endpos, trace->plane.normal, random()*360, + 1,1,1,1, qtrue, radius, qfalse ); + } + + + // don't allow a fragment to make multiple marks, or they + // pile up while settling + le->leMarkType = LEMT_NONE; +} + +/* +================ +CG_FragmentBounceSound +================ +*/ +void CG_FragmentBounceSound( localEntity_t *le, trace_t *trace ) { + if ( le->leBounceSoundType == LEBS_BLOOD ) { + // half the gibs will make splat sounds + if ( rand() & 1 ) { + int r = rand()&3; + sfxHandle_t s; + + if ( r == 0 ) { + s = cgs.media.gibBounce1Sound; + } else if ( r == 1 ) { + s = cgs.media.gibBounce2Sound; + } else { + s = cgs.media.gibBounce3Sound; + } + trap_S_StartSound( trace->endpos, ENTITYNUM_WORLD, CHAN_AUTO, s ); + } + } else if ( le->leBounceSoundType == LEBS_BRASS ) { + + } + + // don't allow a fragment to make multiple bounce sounds, + // or it gets too noisy as they settle + le->leBounceSoundType = LEBS_NONE; +} + + +/* +================ +CG_ReflectVelocity +================ +*/ +void CG_ReflectVelocity( localEntity_t *le, trace_t *trace ) { + vec3_t velocity; + float dot; + int hitTime; + + // reflect the velocity on the trace plane + hitTime = cg.time - cg.frametime + cg.frametime * trace->fraction; + BG_EvaluateTrajectoryDelta( &le->pos, hitTime, velocity ); + dot = DotProduct( velocity, trace->plane.normal ); + VectorMA( velocity, -2*dot, trace->plane.normal, le->pos.trDelta ); + + VectorScale( le->pos.trDelta, le->bounceFactor, le->pos.trDelta ); + + VectorCopy( trace->endpos, le->pos.trBase ); + le->pos.trTime = cg.time; + + + // check for stop, making sure that even on low FPS systems it doesn't bobble + if ( trace->allsolid || + ( trace->plane.normal[2] > 0 && + ( le->pos.trDelta[2] < 40 || le->pos.trDelta[2] < -cg.frametime * le->pos.trDelta[2] ) ) ) { + le->pos.trType = TR_STATIONARY; + } else { + + } +} + +/* +================ +CG_AddFragment +================ +*/ +void CG_AddFragment( localEntity_t *le ) { + vec3_t newOrigin; + trace_t trace; + + if ( le->pos.trType == TR_STATIONARY ) { + // sink into the ground if near the removal time + int t; + float oldZ; + + t = le->endTime - cg.time; + if ( t < SINK_TIME ) { + // we must use an explicit lighting origin, otherwise the + // lighting would be lost as soon as the origin went + // into the ground + VectorCopy( le->refEntity.origin, le->refEntity.lightingOrigin ); + le->refEntity.renderfx |= RF_LIGHTING_ORIGIN; + oldZ = le->refEntity.origin[2]; + le->refEntity.origin[2] -= 16 * ( 1.0 - (float)t / SINK_TIME ); + trap_R_AddRefEntityToScene( &le->refEntity ); + le->refEntity.origin[2] = oldZ; + } else { + trap_R_AddRefEntityToScene( &le->refEntity ); + } + + return; + } + + // calculate new position + BG_EvaluateTrajectory( &le->pos, cg.time, newOrigin ); + + // trace a line from previous position to new position + CG_Trace( &trace, le->refEntity.origin, NULL, NULL, newOrigin, -1, CONTENTS_SOLID ); + if ( trace.fraction == 1.0 ) { + // still in free fall + VectorCopy( newOrigin, le->refEntity.origin ); + + if ( le->leFlags & LEF_TUMBLE ) { + vec3_t angles; + + BG_EvaluateTrajectory( &le->angles, cg.time, angles ); + AnglesToAxis( angles, le->refEntity.axis ); + } + + trap_R_AddRefEntityToScene( &le->refEntity ); + + // add a blood trail + if ( le->leBounceSoundType == LEBS_BLOOD ) { + CG_BloodTrail( le ); + } + + return; + } + + // if it is in a nodrop zone, remove it + // this keeps gibs from waiting at the bottom of pits of death + // and floating levels + if ( trap_CM_PointContents( trace.endpos, 0 ) & CONTENTS_NODROP ) { + CG_FreeLocalEntity( le ); + return; + } + + // leave a mark + CG_FragmentBounceMark( le, &trace ); + + // do a bouncy sound + CG_FragmentBounceSound( le, &trace ); + + // reflect the velocity on the trace plane + CG_ReflectVelocity( le, &trace ); + + trap_R_AddRefEntityToScene( &le->refEntity ); +} + +/* +===================================================================== + +TRIVIAL LOCAL ENTITIES + +These only do simple scaling or modulation before passing to the renderer +===================================================================== +*/ + +/* +==================== +CG_AddFadeRGB +==================== +*/ +void CG_AddFadeRGB( localEntity_t *le ) { + refEntity_t *re; + float c; + + re = &le->refEntity; + + c = ( le->endTime - cg.time ) * le->lifeRate; + c *= 0xff; + + re->shaderRGBA[0] = le->color[0] * c; + re->shaderRGBA[1] = le->color[1] * c; + re->shaderRGBA[2] = le->color[2] * c; + re->shaderRGBA[3] = le->color[3] * c; + + trap_R_AddRefEntityToScene( re ); +} + +/* +================== +CG_AddMoveScaleFade +================== +*/ +static void CG_AddMoveScaleFade( localEntity_t *le ) { + refEntity_t *re; + float c; + vec3_t delta; + float len; + + re = &le->refEntity; + + if ( le->fadeInTime > le->startTime && cg.time < le->fadeInTime ) { + // fade / grow time + c = 1.0 - (float) ( le->fadeInTime - cg.time ) / ( le->fadeInTime - le->startTime ); + } + else { + // fade / grow time + c = ( le->endTime - cg.time ) * le->lifeRate; + } + + re->shaderRGBA[3] = 0xff * c * le->color[3]; + + if ( !( le->leFlags & LEF_PUFF_DONT_SCALE ) ) { + re->radius = le->radius * ( 1.0 - c ) + 8; + } + + BG_EvaluateTrajectory( &le->pos, cg.time, re->origin ); + + // if the view would be "inside" the sprite, kill the sprite + // so it doesn't add too much overdraw + VectorSubtract( re->origin, cg.refdef.vieworg, delta ); + len = VectorLength( delta ); + if ( len < le->radius ) { + CG_FreeLocalEntity( le ); + return; + } + + trap_R_AddRefEntityToScene( re ); +} + + +/* +=================== +CG_AddScaleFade + +For rocket smokes that hang in place, fade out, and are +removed if the view passes through them. +There are often many of these, so it needs to be simple. +=================== +*/ +static void CG_AddScaleFade( localEntity_t *le ) { + refEntity_t *re; + float c; + vec3_t delta; + float len; + + re = &le->refEntity; + + // fade / grow time + c = ( le->endTime - cg.time ) * le->lifeRate; + + re->shaderRGBA[3] = 0xff * c * le->color[3]; + re->radius = le->radius * ( 1.0 - c ) + 8; + + // if the view would be "inside" the sprite, kill the sprite + // so it doesn't add too much overdraw + VectorSubtract( re->origin, cg.refdef.vieworg, delta ); + len = VectorLength( delta ); + if ( len < le->radius ) { + CG_FreeLocalEntity( le ); + return; + } + + trap_R_AddRefEntityToScene( re ); +} + + +/* +================= +CG_AddFallScaleFade + +This is just an optimized CG_AddMoveScaleFade +For blood mists that drift down, fade out, and are +removed if the view passes through them. +There are often 100+ of these, so it needs to be simple. +================= +*/ +static void CG_AddFallScaleFade( localEntity_t *le ) { + refEntity_t *re; + float c; + vec3_t delta; + float len; + + re = &le->refEntity; + + // fade time + c = ( le->endTime - cg.time ) * le->lifeRate; + + re->shaderRGBA[3] = 0xff * c * le->color[3]; + + re->origin[2] = le->pos.trBase[2] - ( 1.0 - c ) * le->pos.trDelta[2]; + + re->radius = le->radius * ( 1.0 - c ) + 16; + + // if the view would be "inside" the sprite, kill the sprite + // so it doesn't add too much overdraw + VectorSubtract( re->origin, cg.refdef.vieworg, delta ); + len = VectorLength( delta ); + if ( len < le->radius ) { + CG_FreeLocalEntity( le ); + return; + } + + trap_R_AddRefEntityToScene( re ); +} + + + +/* +================ +CG_AddExplosion +================ +*/ +static void CG_AddExplosion( localEntity_t *ex ) { + refEntity_t *ent; + + ent = &ex->refEntity; + + // add the entity + trap_R_AddRefEntityToScene(ent); + + // add the dlight + if ( ex->light ) { + float light; + + light = (float)( cg.time - ex->startTime ) / ( ex->endTime - ex->startTime ); + if ( light < 0.5 ) { + light = 1.0; + } else { + light = 1.0 - ( light - 0.5 ) * 2; + } + light = ex->light * light; + trap_R_AddLightToScene(ent->origin, light, ex->lightColor[0], ex->lightColor[1], ex->lightColor[2] ); + } +} + +/* +================ +CG_AddSpriteExplosion +================ +*/ +static void CG_AddSpriteExplosion( localEntity_t *le ) { + refEntity_t re; + float c; + + re = le->refEntity; + + c = ( le->endTime - cg.time ) / ( float ) ( le->endTime - le->startTime ); + if ( c > 1 ) { + c = 1.0; // can happen during connection problems + } + + re.shaderRGBA[0] = 0xff; + re.shaderRGBA[1] = 0xff; + re.shaderRGBA[2] = 0xff; + re.shaderRGBA[3] = 0xff * c * 0.33; + + re.reType = RT_SPRITE; + re.radius = 42 * ( 1.0 - c ) + 30; + + trap_R_AddRefEntityToScene( &re ); + + // add the dlight + if ( le->light ) { + float light; + + light = (float)( cg.time - le->startTime ) / ( le->endTime - le->startTime ); + if ( light < 0.5 ) { + light = 1.0; + } else { + light = 1.0 - ( light - 0.5 ) * 2; + } + light = le->light * light; + trap_R_AddLightToScene(re.origin, light, le->lightColor[0], le->lightColor[1], le->lightColor[2] ); + } +} + + +#ifdef MISSIONPACK +/* +==================== +CG_AddKamikaze +==================== +*/ +void CG_AddKamikaze( localEntity_t *le ) { + refEntity_t *re; + refEntity_t shockwave; + float c; + vec3_t test, axis[3]; + int t; + + re = &le->refEntity; + + t = cg.time - le->startTime; + VectorClear( test ); + AnglesToAxis( test, axis ); + + if (t > KAMI_SHOCKWAVE_STARTTIME && t < KAMI_SHOCKWAVE_ENDTIME) { + + if (!(le->leFlags & LEF_SOUND1)) { +// trap_S_StartSound (re->origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.kamikazeExplodeSound ); + trap_S_StartLocalSound(cgs.media.kamikazeExplodeSound, CHAN_AUTO); + le->leFlags |= LEF_SOUND1; + } + // 1st kamikaze shockwave + memset(&shockwave, 0, sizeof(shockwave)); + shockwave.hModel = cgs.media.kamikazeShockWave; + shockwave.reType = RT_MODEL; + shockwave.shaderTime = re->shaderTime; + VectorCopy(re->origin, shockwave.origin); + + c = (float)(t - KAMI_SHOCKWAVE_STARTTIME) / (float)(KAMI_SHOCKWAVE_ENDTIME - KAMI_SHOCKWAVE_STARTTIME); + VectorScale( axis[0], c * KAMI_SHOCKWAVE_MAXRADIUS / KAMI_SHOCKWAVEMODEL_RADIUS, shockwave.axis[0] ); + VectorScale( axis[1], c * KAMI_SHOCKWAVE_MAXRADIUS / KAMI_SHOCKWAVEMODEL_RADIUS, shockwave.axis[1] ); + VectorScale( axis[2], c * KAMI_SHOCKWAVE_MAXRADIUS / KAMI_SHOCKWAVEMODEL_RADIUS, shockwave.axis[2] ); + shockwave.nonNormalizedAxes = qtrue; + + if (t > KAMI_SHOCKWAVEFADE_STARTTIME) { + c = (float)(t - KAMI_SHOCKWAVEFADE_STARTTIME) / (float)(KAMI_SHOCKWAVE_ENDTIME - KAMI_SHOCKWAVEFADE_STARTTIME); + } + else { + c = 0; + } + c *= 0xff; + shockwave.shaderRGBA[0] = 0xff - c; + shockwave.shaderRGBA[1] = 0xff - c; + shockwave.shaderRGBA[2] = 0xff - c; + shockwave.shaderRGBA[3] = 0xff - c; + + trap_R_AddRefEntityToScene( &shockwave ); + } + + if (t > KAMI_EXPLODE_STARTTIME && t < KAMI_IMPLODE_ENDTIME) { + // explosion and implosion + c = ( le->endTime - cg.time ) * le->lifeRate; + c *= 0xff; + re->shaderRGBA[0] = le->color[0] * c; + re->shaderRGBA[1] = le->color[1] * c; + re->shaderRGBA[2] = le->color[2] * c; + re->shaderRGBA[3] = le->color[3] * c; + + if( t < KAMI_IMPLODE_STARTTIME ) { + c = (float)(t - KAMI_EXPLODE_STARTTIME) / (float)(KAMI_IMPLODE_STARTTIME - KAMI_EXPLODE_STARTTIME); + } + else { + if (!(le->leFlags & LEF_SOUND2)) { +// trap_S_StartSound (re->origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.kamikazeImplodeSound ); + trap_S_StartLocalSound(cgs.media.kamikazeImplodeSound, CHAN_AUTO); + le->leFlags |= LEF_SOUND2; + } + c = (float)(KAMI_IMPLODE_ENDTIME - t) / (float) (KAMI_IMPLODE_ENDTIME - KAMI_IMPLODE_STARTTIME); + } + VectorScale( axis[0], c * KAMI_BOOMSPHERE_MAXRADIUS / KAMI_BOOMSPHEREMODEL_RADIUS, re->axis[0] ); + VectorScale( axis[1], c * KAMI_BOOMSPHERE_MAXRADIUS / KAMI_BOOMSPHEREMODEL_RADIUS, re->axis[1] ); + VectorScale( axis[2], c * KAMI_BOOMSPHERE_MAXRADIUS / KAMI_BOOMSPHEREMODEL_RADIUS, re->axis[2] ); + re->nonNormalizedAxes = qtrue; + + trap_R_AddRefEntityToScene( re ); + // add the dlight + trap_R_AddLightToScene( re->origin, c * 1000.0, 1.0, 1.0, c ); + } + + if (t > KAMI_SHOCKWAVE2_STARTTIME && t < KAMI_SHOCKWAVE2_ENDTIME) { + // 2nd kamikaze shockwave + if (le->angles.trBase[0] == 0 && + le->angles.trBase[1] == 0 && + le->angles.trBase[2] == 0) { + le->angles.trBase[0] = random() * 360; + le->angles.trBase[1] = random() * 360; + le->angles.trBase[2] = random() * 360; + } + else { + c = 0; + } + memset(&shockwave, 0, sizeof(shockwave)); + shockwave.hModel = cgs.media.kamikazeShockWave; + shockwave.reType = RT_MODEL; + shockwave.shaderTime = re->shaderTime; + VectorCopy(re->origin, shockwave.origin); + + test[0] = le->angles.trBase[0]; + test[1] = le->angles.trBase[1]; + test[2] = le->angles.trBase[2]; + AnglesToAxis( test, axis ); + + c = (float)(t - KAMI_SHOCKWAVE2_STARTTIME) / (float)(KAMI_SHOCKWAVE2_ENDTIME - KAMI_SHOCKWAVE2_STARTTIME); + VectorScale( axis[0], c * KAMI_SHOCKWAVE2_MAXRADIUS / KAMI_SHOCKWAVEMODEL_RADIUS, shockwave.axis[0] ); + VectorScale( axis[1], c * KAMI_SHOCKWAVE2_MAXRADIUS / KAMI_SHOCKWAVEMODEL_RADIUS, shockwave.axis[1] ); + VectorScale( axis[2], c * KAMI_SHOCKWAVE2_MAXRADIUS / KAMI_SHOCKWAVEMODEL_RADIUS, shockwave.axis[2] ); + shockwave.nonNormalizedAxes = qtrue; + + if (t > KAMI_SHOCKWAVE2FADE_STARTTIME) { + c = (float)(t - KAMI_SHOCKWAVE2FADE_STARTTIME) / (float)(KAMI_SHOCKWAVE2_ENDTIME - KAMI_SHOCKWAVE2FADE_STARTTIME); + } + else { + c = 0; + } + c *= 0xff; + shockwave.shaderRGBA[0] = 0xff - c; + shockwave.shaderRGBA[1] = 0xff - c; + shockwave.shaderRGBA[2] = 0xff - c; + shockwave.shaderRGBA[3] = 0xff - c; + + trap_R_AddRefEntityToScene( &shockwave ); + } +} + +/* +=================== +CG_AddInvulnerabilityImpact +=================== +*/ +void CG_AddInvulnerabilityImpact( localEntity_t *le ) { + trap_R_AddRefEntityToScene( &le->refEntity ); +} + +/* +=================== +CG_AddInvulnerabilityJuiced +=================== +*/ +void CG_AddInvulnerabilityJuiced( localEntity_t *le ) { + int t; + + t = cg.time - le->startTime; + if ( t > 3000 ) { + le->refEntity.axis[0][0] = (float) 1.0 + 0.3 * (t - 3000) / 2000; + le->refEntity.axis[1][1] = (float) 1.0 + 0.3 * (t - 3000) / 2000; + le->refEntity.axis[2][2] = (float) 0.7 + 0.3 * (2000 - (t - 3000)) / 2000; + } + if ( t > 5000 ) { + le->endTime = 0; + CG_GibPlayer( le->refEntity.origin ); + } + else { + trap_R_AddRefEntityToScene( &le->refEntity ); + } +} + +/* +=================== +CG_AddRefEntity +=================== +*/ +void CG_AddRefEntity( localEntity_t *le ) { + if (le->endTime < cg.time) { + CG_FreeLocalEntity( le ); + return; + } + trap_R_AddRefEntityToScene( &le->refEntity ); +} + +#endif +/* +=================== +CG_AddScorePlum +=================== +*/ +#define NUMBER_SIZE 8 + +void CG_AddScorePlum( localEntity_t *le ) { + refEntity_t *re; + vec3_t origin, delta, dir, vec, up = {0, 0, 1}; + float c, len; + int i, score, digits[10], numdigits, negative; + + re = &le->refEntity; + + c = ( le->endTime - cg.time ) * le->lifeRate; + + score = le->radius; + if (score < 0) { + re->shaderRGBA[0] = 0xff; + re->shaderRGBA[1] = 0x11; + re->shaderRGBA[2] = 0x11; + } + else { + re->shaderRGBA[0] = 0xff; + re->shaderRGBA[1] = 0xff; + re->shaderRGBA[2] = 0xff; + if (score >= 50) { + re->shaderRGBA[1] = 0; + } else if (score >= 20) { + re->shaderRGBA[0] = re->shaderRGBA[1] = 0; + } else if (score >= 10) { + re->shaderRGBA[2] = 0; + } else if (score >= 2) { + re->shaderRGBA[0] = re->shaderRGBA[2] = 0; + } + + } + if (c < 0.25) + re->shaderRGBA[3] = 0xff * 4 * c; + else + re->shaderRGBA[3] = 0xff; + + re->radius = NUMBER_SIZE / 2; + + VectorCopy(le->pos.trBase, origin); + origin[2] += 110 - c * 100; + + VectorSubtract(cg.refdef.vieworg, origin, dir); + CrossProduct(dir, up, vec); + VectorNormalize(vec); + + VectorMA(origin, -10 + 20 * sin(c * 2 * M_PI), vec, origin); + + // if the view would be "inside" the sprite, kill the sprite + // so it doesn't add too much overdraw + VectorSubtract( origin, cg.refdef.vieworg, delta ); + len = VectorLength( delta ); + if ( len < 20 ) { + CG_FreeLocalEntity( le ); + return; + } + + negative = qfalse; + if (score < 0) { + negative = qtrue; + score = -score; + } + + for (numdigits = 0; !(numdigits && !score); numdigits++) { + digits[numdigits] = score % 10; + score = score / 10; + } + + if (negative) { + digits[numdigits] = 10; + numdigits++; + } + + for (i = 0; i < numdigits; i++) { + VectorMA(origin, (float) (((float) numdigits / 2) - i) * NUMBER_SIZE, vec, re->origin); + re->customShader = cgs.media.numberShaders[digits[numdigits-1-i]]; + trap_R_AddRefEntityToScene( re ); + } +} + + + + +//============================================================================== + +/* +=================== +CG_AddLocalEntities + +=================== +*/ +void CG_AddLocalEntities( void ) { + localEntity_t *le, *next; + + // walk the list backwards, so any new local entities generated + // (trails, marks, etc) will be present this frame + le = cg_activeLocalEntities.prev; + for ( ; le != &cg_activeLocalEntities ; le = next ) { + // grab next now, so if the local entity is freed we + // still have it + next = le->prev; + + if ( cg.time >= le->endTime ) { + CG_FreeLocalEntity( le ); + continue; + } + switch ( le->leType ) { + default: + CG_Error( "Bad leType: %i", le->leType ); + break; + + case LE_MARK: + break; + + case LE_SPRITE_EXPLOSION: + CG_AddSpriteExplosion( le ); + break; + + case LE_EXPLOSION: + CG_AddExplosion( le ); + break; + + case LE_FRAGMENT: // gibs and brass + CG_AddFragment( le ); + break; + + case LE_MOVE_SCALE_FADE: // water bubbles + CG_AddMoveScaleFade( le ); + break; + + case LE_FADE_RGB: // teleporters, railtrails + CG_AddFadeRGB( le ); + break; + + case LE_FALL_SCALE_FADE: // gib blood trails + CG_AddFallScaleFade( le ); + break; + + case LE_SCALE_FADE: // rocket trails + CG_AddScaleFade( le ); + break; + + case LE_SCOREPLUM: + CG_AddScorePlum( le ); + break; + +#ifdef MISSIONPACK + case LE_KAMIKAZE: + CG_AddKamikaze( le ); + break; + case LE_INVULIMPACT: + CG_AddInvulnerabilityImpact( le ); + break; + case LE_INVULJUICED: + CG_AddInvulnerabilityJuiced( le ); + break; + case LE_SHOWREFENTITY: + CG_AddRefEntity( le ); + break; +#endif + } + } +} + + + + diff --git a/code/cgame/cg_main.c b/code/cgame/cg_main.c index aa9ec27..7212996 100755 --- a/code/cgame/cg_main.c +++ b/code/cgame/cg_main.c @@ -1,1997 +1,1997 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// cg_main.c -- initialization and primary entry point for cgame -#include "cg_local.h" - -#ifdef MISSIONPACK -#include "../ui/ui_shared.h" -// display context for new ui stuff -displayContextDef_t cgDC; -#endif - -int forceModelModificationCount = -1; - -void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ); -void CG_Shutdown( void ); - - -/* -================ -vmMain - -This is the only way control passes into the module. -This must be the very first function compiled into the .q3vm 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 ) { - - switch ( command ) { - case CG_INIT: - CG_Init( arg0, arg1, arg2 ); - return 0; - case CG_SHUTDOWN: - CG_Shutdown(); - return 0; - case CG_CONSOLE_COMMAND: - return CG_ConsoleCommand(); - case CG_DRAW_ACTIVE_FRAME: - CG_DrawActiveFrame( arg0, arg1, arg2 ); - return 0; - case CG_CROSSHAIR_PLAYER: - return CG_CrosshairPlayer(); - case CG_LAST_ATTACKER: - return CG_LastAttacker(); - case CG_KEY_EVENT: - CG_KeyEvent(arg0, arg1); - return 0; - case CG_MOUSE_EVENT: -#ifdef MISSIONPACK - cgDC.cursorx = cgs.cursorX; - cgDC.cursory = cgs.cursorY; -#endif - CG_MouseEvent(arg0, arg1); - return 0; - case CG_EVENT_HANDLING: - CG_EventHandling(arg0); - return 0; - default: - CG_Error( "vmMain: unknown command %i", command ); - break; - } - return -1; -} - - -cg_t cg; -cgs_t cgs; -centity_t cg_entities[MAX_GENTITIES]; -weaponInfo_t cg_weapons[MAX_WEAPONS]; -itemInfo_t cg_items[MAX_ITEMS]; - - -vmCvar_t cg_railTrailTime; -vmCvar_t cg_centertime; -vmCvar_t cg_runpitch; -vmCvar_t cg_runroll; -vmCvar_t cg_bobup; -vmCvar_t cg_bobpitch; -vmCvar_t cg_bobroll; -vmCvar_t cg_swingSpeed; -vmCvar_t cg_shadows; -vmCvar_t cg_gibs; -vmCvar_t cg_drawTimer; -vmCvar_t cg_drawFPS; -vmCvar_t cg_drawSnapshot; -vmCvar_t cg_draw3dIcons; -vmCvar_t cg_drawIcons; -vmCvar_t cg_drawAmmoWarning; -vmCvar_t cg_drawCrosshair; -vmCvar_t cg_drawCrosshairNames; -vmCvar_t cg_drawRewards; -vmCvar_t cg_crosshairSize; -vmCvar_t cg_crosshairX; -vmCvar_t cg_crosshairY; -vmCvar_t cg_crosshairHealth; -vmCvar_t cg_draw2D; -vmCvar_t cg_drawStatus; -vmCvar_t cg_animSpeed; -vmCvar_t cg_debugAnim; -vmCvar_t cg_debugPosition; -vmCvar_t cg_debugEvents; -vmCvar_t cg_errorDecay; -vmCvar_t cg_nopredict; -vmCvar_t cg_noPlayerAnims; -vmCvar_t cg_showmiss; -vmCvar_t cg_footsteps; -vmCvar_t cg_addMarks; -vmCvar_t cg_brassTime; -vmCvar_t cg_viewsize; -vmCvar_t cg_drawGun; -vmCvar_t cg_gun_frame; -vmCvar_t cg_gun_x; -vmCvar_t cg_gun_y; -vmCvar_t cg_gun_z; -vmCvar_t cg_tracerChance; -vmCvar_t cg_tracerWidth; -vmCvar_t cg_tracerLength; -vmCvar_t cg_autoswitch; -vmCvar_t cg_ignore; -vmCvar_t cg_simpleItems; -vmCvar_t cg_fov; -vmCvar_t cg_zoomFov; -vmCvar_t cg_thirdPerson; -vmCvar_t cg_thirdPersonRange; -vmCvar_t cg_thirdPersonAngle; -vmCvar_t cg_stereoSeparation; -vmCvar_t cg_lagometer; -vmCvar_t cg_drawAttacker; -vmCvar_t cg_synchronousClients; -vmCvar_t cg_teamChatTime; -vmCvar_t cg_teamChatHeight; -vmCvar_t cg_stats; -vmCvar_t cg_buildScript; -vmCvar_t cg_forceModel; -vmCvar_t cg_paused; -vmCvar_t cg_blood; -vmCvar_t cg_predictItems; -vmCvar_t cg_deferPlayers; -vmCvar_t cg_drawTeamOverlay; -vmCvar_t cg_teamOverlayUserinfo; -vmCvar_t cg_drawFriend; -vmCvar_t cg_teamChatsOnly; -vmCvar_t cg_noVoiceChats; -vmCvar_t cg_noVoiceText; -vmCvar_t cg_hudFiles; -vmCvar_t cg_scorePlum; -vmCvar_t cg_smoothClients; -vmCvar_t pmove_fixed; -//vmCvar_t cg_pmove_fixed; -vmCvar_t pmove_msec; -vmCvar_t cg_pmove_msec; -vmCvar_t cg_cameraMode; -vmCvar_t cg_cameraOrbit; -vmCvar_t cg_cameraOrbitDelay; -vmCvar_t cg_timescaleFadeEnd; -vmCvar_t cg_timescaleFadeSpeed; -vmCvar_t cg_timescale; -vmCvar_t cg_smallFont; -vmCvar_t cg_bigFont; -vmCvar_t cg_noTaunt; -vmCvar_t cg_noProjectileTrail; -vmCvar_t cg_oldRail; -vmCvar_t cg_oldRocket; -vmCvar_t cg_oldPlasma; -vmCvar_t cg_trueLightning; - -#ifdef MISSIONPACK -vmCvar_t cg_redTeamName; -vmCvar_t cg_blueTeamName; -vmCvar_t cg_currentSelectedPlayer; -vmCvar_t cg_currentSelectedPlayerName; -vmCvar_t cg_singlePlayer; -vmCvar_t cg_enableDust; -vmCvar_t cg_enableBreath; -vmCvar_t cg_singlePlayerActive; -vmCvar_t cg_recordSPDemo; -vmCvar_t cg_recordSPDemoName; -vmCvar_t cg_obeliskRespawnDelay; -#endif - -typedef struct { - vmCvar_t *vmCvar; - char *cvarName; - char *defaultString; - int cvarFlags; -} cvarTable_t; - -static cvarTable_t cvarTable[] = { // bk001129 - { &cg_ignore, "cg_ignore", "0", 0 }, // used for debugging - { &cg_autoswitch, "cg_autoswitch", "1", CVAR_ARCHIVE }, - { &cg_drawGun, "cg_drawGun", "1", CVAR_ARCHIVE }, - { &cg_zoomFov, "cg_zoomfov", "22.5", CVAR_ARCHIVE }, - { &cg_fov, "cg_fov", "90", CVAR_ARCHIVE }, - { &cg_viewsize, "cg_viewsize", "100", CVAR_ARCHIVE }, - { &cg_stereoSeparation, "cg_stereoSeparation", "0.4", CVAR_ARCHIVE }, - { &cg_shadows, "cg_shadows", "1", CVAR_ARCHIVE }, - { &cg_gibs, "cg_gibs", "1", CVAR_ARCHIVE }, - { &cg_draw2D, "cg_draw2D", "1", CVAR_ARCHIVE }, - { &cg_drawStatus, "cg_drawStatus", "1", CVAR_ARCHIVE }, - { &cg_drawTimer, "cg_drawTimer", "0", CVAR_ARCHIVE }, - { &cg_drawFPS, "cg_drawFPS", "0", CVAR_ARCHIVE }, - { &cg_drawSnapshot, "cg_drawSnapshot", "0", CVAR_ARCHIVE }, - { &cg_draw3dIcons, "cg_draw3dIcons", "1", CVAR_ARCHIVE }, - { &cg_drawIcons, "cg_drawIcons", "1", CVAR_ARCHIVE }, - { &cg_drawAmmoWarning, "cg_drawAmmoWarning", "1", CVAR_ARCHIVE }, - { &cg_drawAttacker, "cg_drawAttacker", "1", CVAR_ARCHIVE }, - { &cg_drawCrosshair, "cg_drawCrosshair", "4", CVAR_ARCHIVE }, - { &cg_drawCrosshairNames, "cg_drawCrosshairNames", "1", CVAR_ARCHIVE }, - { &cg_drawRewards, "cg_drawRewards", "1", CVAR_ARCHIVE }, - { &cg_crosshairSize, "cg_crosshairSize", "24", CVAR_ARCHIVE }, - { &cg_crosshairHealth, "cg_crosshairHealth", "1", CVAR_ARCHIVE }, - { &cg_crosshairX, "cg_crosshairX", "0", CVAR_ARCHIVE }, - { &cg_crosshairY, "cg_crosshairY", "0", CVAR_ARCHIVE }, - { &cg_brassTime, "cg_brassTime", "2500", CVAR_ARCHIVE }, - { &cg_simpleItems, "cg_simpleItems", "0", CVAR_ARCHIVE }, - { &cg_addMarks, "cg_marks", "1", CVAR_ARCHIVE }, - { &cg_lagometer, "cg_lagometer", "1", CVAR_ARCHIVE }, - { &cg_railTrailTime, "cg_railTrailTime", "400", CVAR_ARCHIVE }, - { &cg_gun_x, "cg_gunX", "0", CVAR_CHEAT }, - { &cg_gun_y, "cg_gunY", "0", CVAR_CHEAT }, - { &cg_gun_z, "cg_gunZ", "0", CVAR_CHEAT }, - { &cg_centertime, "cg_centertime", "3", CVAR_CHEAT }, - { &cg_runpitch, "cg_runpitch", "0.002", CVAR_ARCHIVE}, - { &cg_runroll, "cg_runroll", "0.005", CVAR_ARCHIVE }, - { &cg_bobup , "cg_bobup", "0.005", CVAR_CHEAT }, - { &cg_bobpitch, "cg_bobpitch", "0.002", CVAR_ARCHIVE }, - { &cg_bobroll, "cg_bobroll", "0.002", CVAR_ARCHIVE }, - { &cg_swingSpeed, "cg_swingSpeed", "0.3", CVAR_CHEAT }, - { &cg_animSpeed, "cg_animspeed", "1", CVAR_CHEAT }, - { &cg_debugAnim, "cg_debuganim", "0", CVAR_CHEAT }, - { &cg_debugPosition, "cg_debugposition", "0", CVAR_CHEAT }, - { &cg_debugEvents, "cg_debugevents", "0", CVAR_CHEAT }, - { &cg_errorDecay, "cg_errordecay", "100", 0 }, - { &cg_nopredict, "cg_nopredict", "0", 0 }, - { &cg_noPlayerAnims, "cg_noplayeranims", "0", CVAR_CHEAT }, - { &cg_showmiss, "cg_showmiss", "0", 0 }, - { &cg_footsteps, "cg_footsteps", "1", CVAR_CHEAT }, - { &cg_tracerChance, "cg_tracerchance", "0.4", CVAR_CHEAT }, - { &cg_tracerWidth, "cg_tracerwidth", "1", CVAR_CHEAT }, - { &cg_tracerLength, "cg_tracerlength", "100", CVAR_CHEAT }, - { &cg_thirdPersonRange, "cg_thirdPersonRange", "40", CVAR_CHEAT }, - { &cg_thirdPersonAngle, "cg_thirdPersonAngle", "0", CVAR_CHEAT }, - { &cg_thirdPerson, "cg_thirdPerson", "0", 0 }, - { &cg_teamChatTime, "cg_teamChatTime", "3000", CVAR_ARCHIVE }, - { &cg_teamChatHeight, "cg_teamChatHeight", "0", CVAR_ARCHIVE }, - { &cg_forceModel, "cg_forceModel", "0", CVAR_ARCHIVE }, - { &cg_predictItems, "cg_predictItems", "1", CVAR_ARCHIVE }, -#ifdef MISSIONPACK - { &cg_deferPlayers, "cg_deferPlayers", "0", CVAR_ARCHIVE }, -#else - { &cg_deferPlayers, "cg_deferPlayers", "1", CVAR_ARCHIVE }, -#endif - { &cg_drawTeamOverlay, "cg_drawTeamOverlay", "0", CVAR_ARCHIVE }, - { &cg_teamOverlayUserinfo, "teamoverlay", "0", CVAR_ROM | CVAR_USERINFO }, - { &cg_stats, "cg_stats", "0", 0 }, - { &cg_drawFriend, "cg_drawFriend", "1", CVAR_ARCHIVE }, - { &cg_teamChatsOnly, "cg_teamChatsOnly", "0", CVAR_ARCHIVE }, - { &cg_noVoiceChats, "cg_noVoiceChats", "0", CVAR_ARCHIVE }, - { &cg_noVoiceText, "cg_noVoiceText", "0", CVAR_ARCHIVE }, - // the following variables are created in other parts of the system, - // but we also reference them here - { &cg_buildScript, "com_buildScript", "0", 0 }, // force loading of all possible data amd error on failures - { &cg_paused, "cl_paused", "0", CVAR_ROM }, - { &cg_blood, "com_blood", "1", CVAR_ARCHIVE }, - { &cg_synchronousClients, "g_synchronousClients", "0", 0 }, // communicated by systeminfo -#ifdef MISSIONPACK - { &cg_redTeamName, "g_redteam", DEFAULT_REDTEAM_NAME, CVAR_ARCHIVE | CVAR_SERVERINFO | CVAR_USERINFO }, - { &cg_blueTeamName, "g_blueteam", DEFAULT_BLUETEAM_NAME, CVAR_ARCHIVE | CVAR_SERVERINFO | CVAR_USERINFO }, - { &cg_currentSelectedPlayer, "cg_currentSelectedPlayer", "0", CVAR_ARCHIVE}, - { &cg_currentSelectedPlayerName, "cg_currentSelectedPlayerName", "", CVAR_ARCHIVE}, - { &cg_singlePlayer, "ui_singlePlayerActive", "0", CVAR_USERINFO}, - { &cg_enableDust, "g_enableDust", "0", CVAR_SERVERINFO}, - { &cg_enableBreath, "g_enableBreath", "0", CVAR_SERVERINFO}, - { &cg_singlePlayerActive, "ui_singlePlayerActive", "0", CVAR_USERINFO}, - { &cg_recordSPDemo, "ui_recordSPDemo", "0", CVAR_ARCHIVE}, - { &cg_recordSPDemoName, "ui_recordSPDemoName", "", CVAR_ARCHIVE}, - { &cg_obeliskRespawnDelay, "g_obeliskRespawnDelay", "10", CVAR_SERVERINFO}, - { &cg_hudFiles, "cg_hudFiles", "ui/hud.txt", CVAR_ARCHIVE}, -#endif - { &cg_cameraOrbit, "cg_cameraOrbit", "0", CVAR_CHEAT}, - { &cg_cameraOrbitDelay, "cg_cameraOrbitDelay", "50", CVAR_ARCHIVE}, - { &cg_timescaleFadeEnd, "cg_timescaleFadeEnd", "1", 0}, - { &cg_timescaleFadeSpeed, "cg_timescaleFadeSpeed", "0", 0}, - { &cg_timescale, "timescale", "1", 0}, - { &cg_scorePlum, "cg_scorePlums", "1", CVAR_USERINFO | CVAR_ARCHIVE}, - { &cg_smoothClients, "cg_smoothClients", "0", CVAR_USERINFO | CVAR_ARCHIVE}, - { &cg_cameraMode, "com_cameraMode", "0", CVAR_CHEAT}, - - { &pmove_fixed, "pmove_fixed", "0", 0}, - { &pmove_msec, "pmove_msec", "8", 0}, - { &cg_noTaunt, "cg_noTaunt", "0", CVAR_ARCHIVE}, - { &cg_noProjectileTrail, "cg_noProjectileTrail", "0", CVAR_ARCHIVE}, - { &cg_smallFont, "ui_smallFont", "0.25", CVAR_ARCHIVE}, - { &cg_bigFont, "ui_bigFont", "0.4", CVAR_ARCHIVE}, - { &cg_oldRail, "cg_oldRail", "1", CVAR_ARCHIVE}, - { &cg_oldRocket, "cg_oldRocket", "1", CVAR_ARCHIVE}, - { &cg_oldPlasma, "cg_oldPlasma", "1", CVAR_ARCHIVE}, - { &cg_trueLightning, "cg_trueLightning", "0.0", CVAR_ARCHIVE} -// { &cg_pmove_fixed, "cg_pmove_fixed", "0", CVAR_USERINFO | CVAR_ARCHIVE } -}; - -static int cvarTableSize = sizeof( cvarTable ) / sizeof( cvarTable[0] ); - -/* -================= -CG_RegisterCvars -================= -*/ -void CG_RegisterCvars( void ) { - int i; - cvarTable_t *cv; - char var[MAX_TOKEN_CHARS]; - - for ( i = 0, cv = cvarTable ; i < cvarTableSize ; i++, cv++ ) { - trap_Cvar_Register( cv->vmCvar, cv->cvarName, - cv->defaultString, cv->cvarFlags ); - } - - // see if we are also running the server on this machine - trap_Cvar_VariableStringBuffer( "sv_running", var, sizeof( var ) ); - cgs.localServer = atoi( var ); - - forceModelModificationCount = cg_forceModel.modificationCount; - - trap_Cvar_Register(NULL, "model", DEFAULT_MODEL, CVAR_USERINFO | CVAR_ARCHIVE ); - trap_Cvar_Register(NULL, "headmodel", DEFAULT_MODEL, CVAR_USERINFO | CVAR_ARCHIVE ); - trap_Cvar_Register(NULL, "team_model", DEFAULT_TEAM_MODEL, CVAR_USERINFO | CVAR_ARCHIVE ); - trap_Cvar_Register(NULL, "team_headmodel", DEFAULT_TEAM_HEAD, CVAR_USERINFO | CVAR_ARCHIVE ); -} - -/* -=================== -CG_ForceModelChange -=================== -*/ -static void CG_ForceModelChange( void ) { - int i; - - for (i=0 ; ivmCvar ); - } - - // check for modications here - - // If team overlay is on, ask for updates from the server. If its off, - // let the server know so we don't receive it - if ( drawTeamOverlayModificationCount != cg_drawTeamOverlay.modificationCount ) { - drawTeamOverlayModificationCount = cg_drawTeamOverlay.modificationCount; - - if ( cg_drawTeamOverlay.integer > 0 ) { - trap_Cvar_Set( "teamoverlay", "1" ); - } else { - trap_Cvar_Set( "teamoverlay", "0" ); - } - // FIXME E3 HACK - trap_Cvar_Set( "teamoverlay", "1" ); - } - - // if force model changed - if ( forceModelModificationCount != cg_forceModel.modificationCount ) { - forceModelModificationCount = cg_forceModel.modificationCount; - CG_ForceModelChange(); - } -} - -int CG_CrosshairPlayer( void ) { - if ( cg.time > ( cg.crosshairClientTime + 1000 ) ) { - return -1; - } - return cg.crosshairClientNum; -} - -int CG_LastAttacker( void ) { - if ( !cg.attackerTime ) { - return -1; - } - return cg.snap->ps.persistant[PERS_ATTACKER]; -} - -void QDECL CG_Printf( const char *msg, ... ) { - va_list argptr; - char text[1024]; - - va_start (argptr, msg); - vsprintf (text, msg, argptr); - va_end (argptr); - - trap_Print( text ); -} - -void QDECL CG_Error( const char *msg, ... ) { - va_list argptr; - char text[1024]; - - va_start (argptr, msg); - vsprintf (text, msg, argptr); - va_end (argptr); - - trap_Error( text ); -} - -#ifndef CGAME_HARD_LINKED -// this is only here so the functions in q_shared.c and bg_*.c can link (FIXME) - -void QDECL Com_Error( int level, const char *error, ... ) { - va_list argptr; - char text[1024]; - - va_start (argptr, error); - vsprintf (text, error, argptr); - va_end (argptr); - - CG_Error( "%s", text); -} - -void QDECL Com_Printf( const char *msg, ... ) { - va_list argptr; - char text[1024]; - - va_start (argptr, msg); - vsprintf (text, msg, argptr); - va_end (argptr); - - CG_Printf ("%s", text); -} - -#endif - -/* -================ -CG_Argv -================ -*/ -const char *CG_Argv( int arg ) { - static char buffer[MAX_STRING_CHARS]; - - trap_Argv( arg, buffer, sizeof( buffer ) ); - - return buffer; -} - - -//======================================================================== - -/* -================= -CG_RegisterItemSounds - -The server says this item is used on this level -================= -*/ -static void CG_RegisterItemSounds( int itemNum ) { - gitem_t *item; - char data[MAX_QPATH]; - char *s, *start; - int len; - - item = &bg_itemlist[ itemNum ]; - - if( item->pickup_sound ) { - trap_S_RegisterSound( item->pickup_sound, qfalse ); - } - - // parse the space seperated precache string for other media - s = item->sounds; - if (!s || !s[0]) - return; - - while (*s) { - start = s; - while (*s && *s != ' ') { - s++; - } - - len = s-start; - if (len >= MAX_QPATH || len < 5) { - CG_Error( "PrecacheItem: %s has bad precache string", - item->classname); - return; - } - memcpy (data, start, len); - data[len] = 0; - if ( *s ) { - s++; - } - - if ( !strcmp(data+len-3, "wav" )) { - trap_S_RegisterSound( data, qfalse ); - } - } -} - - -/* -================= -CG_RegisterSounds - -called during a precache command -================= -*/ -static void CG_RegisterSounds( void ) { - int i; - char items[MAX_ITEMS+1]; - char name[MAX_QPATH]; - const char *soundName; - - // voice commands -#ifdef MISSIONPACK - CG_LoadVoiceChats(); -#endif - - cgs.media.oneMinuteSound = trap_S_RegisterSound( "sound/feedback/1_minute.wav", qtrue ); - cgs.media.fiveMinuteSound = trap_S_RegisterSound( "sound/feedback/5_minute.wav", qtrue ); - cgs.media.suddenDeathSound = trap_S_RegisterSound( "sound/feedback/sudden_death.wav", qtrue ); - cgs.media.oneFragSound = trap_S_RegisterSound( "sound/feedback/1_frag.wav", qtrue ); - cgs.media.twoFragSound = trap_S_RegisterSound( "sound/feedback/2_frags.wav", qtrue ); - cgs.media.threeFragSound = trap_S_RegisterSound( "sound/feedback/3_frags.wav", qtrue ); - cgs.media.count3Sound = trap_S_RegisterSound( "sound/feedback/three.wav", qtrue ); - cgs.media.count2Sound = trap_S_RegisterSound( "sound/feedback/two.wav", qtrue ); - cgs.media.count1Sound = trap_S_RegisterSound( "sound/feedback/one.wav", qtrue ); - cgs.media.countFightSound = trap_S_RegisterSound( "sound/feedback/fight.wav", qtrue ); - cgs.media.countPrepareSound = trap_S_RegisterSound( "sound/feedback/prepare.wav", qtrue ); -#ifdef MISSIONPACK - cgs.media.countPrepareTeamSound = trap_S_RegisterSound( "sound/feedback/prepare_team.wav", qtrue ); -#endif - - if ( cgs.gametype >= GT_TEAM || cg_buildScript.integer ) { - - cgs.media.captureAwardSound = trap_S_RegisterSound( "sound/teamplay/flagcapture_yourteam.wav", qtrue ); - cgs.media.redLeadsSound = trap_S_RegisterSound( "sound/feedback/redleads.wav", qtrue ); - cgs.media.blueLeadsSound = trap_S_RegisterSound( "sound/feedback/blueleads.wav", qtrue ); - cgs.media.teamsTiedSound = trap_S_RegisterSound( "sound/feedback/teamstied.wav", qtrue ); - cgs.media.hitTeamSound = trap_S_RegisterSound( "sound/feedback/hit_teammate.wav", qtrue ); - - cgs.media.redScoredSound = trap_S_RegisterSound( "sound/teamplay/voc_red_scores.wav", qtrue ); - cgs.media.blueScoredSound = trap_S_RegisterSound( "sound/teamplay/voc_blue_scores.wav", qtrue ); - - cgs.media.captureYourTeamSound = trap_S_RegisterSound( "sound/teamplay/flagcapture_yourteam.wav", qtrue ); - cgs.media.captureOpponentSound = trap_S_RegisterSound( "sound/teamplay/flagcapture_opponent.wav", qtrue ); - - cgs.media.returnYourTeamSound = trap_S_RegisterSound( "sound/teamplay/flagreturn_yourteam.wav", qtrue ); - cgs.media.returnOpponentSound = trap_S_RegisterSound( "sound/teamplay/flagreturn_opponent.wav", qtrue ); - - cgs.media.takenYourTeamSound = trap_S_RegisterSound( "sound/teamplay/flagtaken_yourteam.wav", qtrue ); - cgs.media.takenOpponentSound = trap_S_RegisterSound( "sound/teamplay/flagtaken_opponent.wav", qtrue ); - - if ( cgs.gametype == GT_CTF || cg_buildScript.integer ) { - cgs.media.redFlagReturnedSound = trap_S_RegisterSound( "sound/teamplay/voc_red_returned.wav", qtrue ); - cgs.media.blueFlagReturnedSound = trap_S_RegisterSound( "sound/teamplay/voc_blue_returned.wav", qtrue ); - cgs.media.enemyTookYourFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_enemy_flag.wav", qtrue ); - cgs.media.yourTeamTookEnemyFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_team_flag.wav", qtrue ); - } - -#ifdef MISSIONPACK - if ( cgs.gametype == GT_1FCTF || cg_buildScript.integer ) { - // FIXME: get a replacement for this sound ? - cgs.media.neutralFlagReturnedSound = trap_S_RegisterSound( "sound/teamplay/flagreturn_opponent.wav", qtrue ); - cgs.media.yourTeamTookTheFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_team_1flag.wav", qtrue ); - cgs.media.enemyTookTheFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_enemy_1flag.wav", qtrue ); - } - - if ( cgs.gametype == GT_1FCTF || cgs.gametype == GT_CTF || cg_buildScript.integer ) { - cgs.media.youHaveFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_you_flag.wav", qtrue ); - cgs.media.holyShitSound = trap_S_RegisterSound("sound/feedback/voc_holyshit.wav", qtrue); - } - - if ( cgs.gametype == GT_OBELISK || cg_buildScript.integer ) { - cgs.media.yourBaseIsUnderAttackSound = trap_S_RegisterSound( "sound/teamplay/voc_base_attack.wav", qtrue ); - } -#else - cgs.media.youHaveFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_you_flag.wav", qtrue ); - cgs.media.holyShitSound = trap_S_RegisterSound("sound/feedback/voc_holyshit.wav", qtrue); - cgs.media.neutralFlagReturnedSound = trap_S_RegisterSound( "sound/teamplay/flagreturn_opponent.wav", qtrue ); - cgs.media.yourTeamTookTheFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_team_1flag.wav", qtrue ); - cgs.media.enemyTookTheFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_enemy_1flag.wav", qtrue ); -#endif - } - - cgs.media.tracerSound = trap_S_RegisterSound( "sound/weapons/machinegun/buletby1.wav", qfalse ); - cgs.media.selectSound = trap_S_RegisterSound( "sound/weapons/change.wav", qfalse ); - cgs.media.wearOffSound = trap_S_RegisterSound( "sound/items/wearoff.wav", qfalse ); - cgs.media.useNothingSound = trap_S_RegisterSound( "sound/items/use_nothing.wav", qfalse ); - cgs.media.gibSound = trap_S_RegisterSound( "sound/player/gibsplt1.wav", qfalse ); - cgs.media.gibBounce1Sound = trap_S_RegisterSound( "sound/player/gibimp1.wav", qfalse ); - cgs.media.gibBounce2Sound = trap_S_RegisterSound( "sound/player/gibimp2.wav", qfalse ); - cgs.media.gibBounce3Sound = trap_S_RegisterSound( "sound/player/gibimp3.wav", qfalse ); - -#ifdef MISSIONPACK - cgs.media.useInvulnerabilitySound = trap_S_RegisterSound( "sound/items/invul_activate.wav", qfalse ); - cgs.media.invulnerabilityImpactSound1 = trap_S_RegisterSound( "sound/items/invul_impact_01.wav", qfalse ); - cgs.media.invulnerabilityImpactSound2 = trap_S_RegisterSound( "sound/items/invul_impact_02.wav", qfalse ); - cgs.media.invulnerabilityImpactSound3 = trap_S_RegisterSound( "sound/items/invul_impact_03.wav", qfalse ); - cgs.media.invulnerabilityJuicedSound = trap_S_RegisterSound( "sound/items/invul_juiced.wav", qfalse ); - cgs.media.obeliskHitSound1 = trap_S_RegisterSound( "sound/items/obelisk_hit_01.wav", qfalse ); - cgs.media.obeliskHitSound2 = trap_S_RegisterSound( "sound/items/obelisk_hit_02.wav", qfalse ); - cgs.media.obeliskHitSound3 = trap_S_RegisterSound( "sound/items/obelisk_hit_03.wav", qfalse ); - cgs.media.obeliskRespawnSound = trap_S_RegisterSound( "sound/items/obelisk_respawn.wav", qfalse ); - - cgs.media.ammoregenSound = trap_S_RegisterSound("sound/items/cl_ammoregen.wav", qfalse); - cgs.media.doublerSound = trap_S_RegisterSound("sound/items/cl_doubler.wav", qfalse); - cgs.media.guardSound = trap_S_RegisterSound("sound/items/cl_guard.wav", qfalse); - cgs.media.scoutSound = trap_S_RegisterSound("sound/items/cl_scout.wav", qfalse); -#endif - - cgs.media.teleInSound = trap_S_RegisterSound( "sound/world/telein.wav", qfalse ); - cgs.media.teleOutSound = trap_S_RegisterSound( "sound/world/teleout.wav", qfalse ); - cgs.media.respawnSound = trap_S_RegisterSound( "sound/items/respawn1.wav", qfalse ); - - cgs.media.noAmmoSound = trap_S_RegisterSound( "sound/weapons/noammo.wav", qfalse ); - - cgs.media.talkSound = trap_S_RegisterSound( "sound/player/talk.wav", qfalse ); - cgs.media.landSound = trap_S_RegisterSound( "sound/player/land1.wav", qfalse); - - cgs.media.hitSound = trap_S_RegisterSound( "sound/feedback/hit.wav", qfalse ); -#ifdef MISSIONPACK - cgs.media.hitSoundHighArmor = trap_S_RegisterSound( "sound/feedback/hithi.wav", qfalse ); - cgs.media.hitSoundLowArmor = trap_S_RegisterSound( "sound/feedback/hitlo.wav", qfalse ); -#endif - - cgs.media.impressiveSound = trap_S_RegisterSound( "sound/feedback/impressive.wav", qtrue ); - cgs.media.excellentSound = trap_S_RegisterSound( "sound/feedback/excellent.wav", qtrue ); - cgs.media.deniedSound = trap_S_RegisterSound( "sound/feedback/denied.wav", qtrue ); - cgs.media.humiliationSound = trap_S_RegisterSound( "sound/feedback/humiliation.wav", qtrue ); - cgs.media.assistSound = trap_S_RegisterSound( "sound/feedback/assist.wav", qtrue ); - cgs.media.defendSound = trap_S_RegisterSound( "sound/feedback/defense.wav", qtrue ); -#ifdef MISSIONPACK - cgs.media.firstImpressiveSound = trap_S_RegisterSound( "sound/feedback/first_impressive.wav", qtrue ); - cgs.media.firstExcellentSound = trap_S_RegisterSound( "sound/feedback/first_excellent.wav", qtrue ); - cgs.media.firstHumiliationSound = trap_S_RegisterSound( "sound/feedback/first_gauntlet.wav", qtrue ); -#endif - - cgs.media.takenLeadSound = trap_S_RegisterSound( "sound/feedback/takenlead.wav", qtrue); - cgs.media.tiedLeadSound = trap_S_RegisterSound( "sound/feedback/tiedlead.wav", qtrue); - cgs.media.lostLeadSound = trap_S_RegisterSound( "sound/feedback/lostlead.wav", qtrue); - -#ifdef MISSIONPACK - cgs.media.voteNow = trap_S_RegisterSound( "sound/feedback/vote_now.wav", qtrue); - cgs.media.votePassed = trap_S_RegisterSound( "sound/feedback/vote_passed.wav", qtrue); - cgs.media.voteFailed = trap_S_RegisterSound( "sound/feedback/vote_failed.wav", qtrue); -#endif - - cgs.media.watrInSound = trap_S_RegisterSound( "sound/player/watr_in.wav", qfalse); - cgs.media.watrOutSound = trap_S_RegisterSound( "sound/player/watr_out.wav", qfalse); - cgs.media.watrUnSound = trap_S_RegisterSound( "sound/player/watr_un.wav", qfalse); - - cgs.media.jumpPadSound = trap_S_RegisterSound ("sound/world/jumppad.wav", qfalse ); - - for (i=0 ; i<4 ; i++) { - Com_sprintf (name, sizeof(name), "sound/player/footsteps/step%i.wav", i+1); - cgs.media.footsteps[FOOTSTEP_NORMAL][i] = trap_S_RegisterSound (name, qfalse); - - Com_sprintf (name, sizeof(name), "sound/player/footsteps/boot%i.wav", i+1); - cgs.media.footsteps[FOOTSTEP_BOOT][i] = trap_S_RegisterSound (name, qfalse); - - Com_sprintf (name, sizeof(name), "sound/player/footsteps/flesh%i.wav", i+1); - cgs.media.footsteps[FOOTSTEP_FLESH][i] = trap_S_RegisterSound (name, qfalse); - - Com_sprintf (name, sizeof(name), "sound/player/footsteps/mech%i.wav", i+1); - cgs.media.footsteps[FOOTSTEP_MECH][i] = trap_S_RegisterSound (name, qfalse); - - Com_sprintf (name, sizeof(name), "sound/player/footsteps/energy%i.wav", i+1); - cgs.media.footsteps[FOOTSTEP_ENERGY][i] = trap_S_RegisterSound (name, qfalse); - - Com_sprintf (name, sizeof(name), "sound/player/footsteps/splash%i.wav", i+1); - cgs.media.footsteps[FOOTSTEP_SPLASH][i] = trap_S_RegisterSound (name, qfalse); - - Com_sprintf (name, sizeof(name), "sound/player/footsteps/clank%i.wav", i+1); - cgs.media.footsteps[FOOTSTEP_METAL][i] = trap_S_RegisterSound (name, qfalse); - } - - // only register the items that the server says we need - strcpy( items, CG_ConfigString( CS_ITEMS ) ); - - for ( i = 1 ; i < bg_numItems ; i++ ) { -// if ( items[ i ] == '1' || cg_buildScript.integer ) { - CG_RegisterItemSounds( i ); -// } - } - - for ( i = 1 ; i < MAX_SOUNDS ; i++ ) { - soundName = CG_ConfigString( CS_SOUNDS+i ); - if ( !soundName[0] ) { - break; - } - if ( soundName[0] == '*' ) { - continue; // custom sound - } - cgs.gameSounds[i] = trap_S_RegisterSound( soundName, qfalse ); - } - - // FIXME: only needed with item - cgs.media.flightSound = trap_S_RegisterSound( "sound/items/flight.wav", qfalse ); - cgs.media.medkitSound = trap_S_RegisterSound ("sound/items/use_medkit.wav", qfalse); - cgs.media.quadSound = trap_S_RegisterSound("sound/items/damage3.wav", qfalse); - cgs.media.sfx_ric1 = trap_S_RegisterSound ("sound/weapons/machinegun/ric1.wav", qfalse); - cgs.media.sfx_ric2 = trap_S_RegisterSound ("sound/weapons/machinegun/ric2.wav", qfalse); - cgs.media.sfx_ric3 = trap_S_RegisterSound ("sound/weapons/machinegun/ric3.wav", qfalse); - cgs.media.sfx_railg = trap_S_RegisterSound ("sound/weapons/railgun/railgf1a.wav", qfalse); - cgs.media.sfx_rockexp = trap_S_RegisterSound ("sound/weapons/rocket/rocklx1a.wav", qfalse); - cgs.media.sfx_plasmaexp = trap_S_RegisterSound ("sound/weapons/plasma/plasmx1a.wav", qfalse); -#ifdef MISSIONPACK - cgs.media.sfx_proxexp = trap_S_RegisterSound( "sound/weapons/proxmine/wstbexpl.wav" , qfalse); - cgs.media.sfx_nghit = trap_S_RegisterSound( "sound/weapons/nailgun/wnalimpd.wav" , qfalse); - cgs.media.sfx_nghitflesh = trap_S_RegisterSound( "sound/weapons/nailgun/wnalimpl.wav" , qfalse); - cgs.media.sfx_nghitmetal = trap_S_RegisterSound( "sound/weapons/nailgun/wnalimpm.wav", qfalse ); - cgs.media.sfx_chghit = trap_S_RegisterSound( "sound/weapons/vulcan/wvulimpd.wav", qfalse ); - cgs.media.sfx_chghitflesh = trap_S_RegisterSound( "sound/weapons/vulcan/wvulimpl.wav", qfalse ); - cgs.media.sfx_chghitmetal = trap_S_RegisterSound( "sound/weapons/vulcan/wvulimpm.wav", qfalse ); - cgs.media.weaponHoverSound = trap_S_RegisterSound( "sound/weapons/weapon_hover.wav", qfalse ); - cgs.media.kamikazeExplodeSound = trap_S_RegisterSound( "sound/items/kam_explode.wav", qfalse ); - cgs.media.kamikazeImplodeSound = trap_S_RegisterSound( "sound/items/kam_implode.wav", qfalse ); - cgs.media.kamikazeFarSound = trap_S_RegisterSound( "sound/items/kam_explode_far.wav", qfalse ); - cgs.media.winnerSound = trap_S_RegisterSound( "sound/feedback/voc_youwin.wav", qfalse ); - cgs.media.loserSound = trap_S_RegisterSound( "sound/feedback/voc_youlose.wav", qfalse ); - cgs.media.youSuckSound = trap_S_RegisterSound( "sound/misc/yousuck.wav", qfalse ); - - cgs.media.wstbimplSound = trap_S_RegisterSound("sound/weapons/proxmine/wstbimpl.wav", qfalse); - cgs.media.wstbimpmSound = trap_S_RegisterSound("sound/weapons/proxmine/wstbimpm.wav", qfalse); - cgs.media.wstbimpdSound = trap_S_RegisterSound("sound/weapons/proxmine/wstbimpd.wav", qfalse); - cgs.media.wstbactvSound = trap_S_RegisterSound("sound/weapons/proxmine/wstbactv.wav", qfalse); -#endif - - cgs.media.regenSound = trap_S_RegisterSound("sound/items/regen.wav", qfalse); - cgs.media.protectSound = trap_S_RegisterSound("sound/items/protect3.wav", qfalse); - cgs.media.n_healthSound = trap_S_RegisterSound("sound/items/n_health.wav", qfalse ); - cgs.media.hgrenb1aSound = trap_S_RegisterSound("sound/weapons/grenade/hgrenb1a.wav", qfalse); - cgs.media.hgrenb2aSound = trap_S_RegisterSound("sound/weapons/grenade/hgrenb2a.wav", qfalse); - -#ifdef MISSIONPACK - trap_S_RegisterSound("sound/player/james/death1.wav", qfalse ); - trap_S_RegisterSound("sound/player/james/death2.wav", qfalse ); - trap_S_RegisterSound("sound/player/james/death3.wav", qfalse ); - trap_S_RegisterSound("sound/player/james/jump1.wav", qfalse ); - trap_S_RegisterSound("sound/player/james/pain25_1.wav", qfalse ); - trap_S_RegisterSound("sound/player/james/pain75_1.wav", qfalse ); - trap_S_RegisterSound("sound/player/james/pain100_1.wav", qfalse ); - trap_S_RegisterSound("sound/player/james/falling1.wav", qfalse ); - trap_S_RegisterSound("sound/player/james/gasp.wav", qfalse ); - trap_S_RegisterSound("sound/player/james/drown.wav", qfalse ); - trap_S_RegisterSound("sound/player/james/fall1.wav", qfalse ); - trap_S_RegisterSound("sound/player/james/taunt.wav", qfalse ); - - trap_S_RegisterSound("sound/player/janet/death1.wav", qfalse ); - trap_S_RegisterSound("sound/player/janet/death2.wav", qfalse ); - trap_S_RegisterSound("sound/player/janet/death3.wav", qfalse ); - trap_S_RegisterSound("sound/player/janet/jump1.wav", qfalse ); - trap_S_RegisterSound("sound/player/janet/pain25_1.wav", qfalse ); - trap_S_RegisterSound("sound/player/janet/pain75_1.wav", qfalse ); - trap_S_RegisterSound("sound/player/janet/pain100_1.wav", qfalse ); - trap_S_RegisterSound("sound/player/janet/falling1.wav", qfalse ); - trap_S_RegisterSound("sound/player/janet/gasp.wav", qfalse ); - trap_S_RegisterSound("sound/player/janet/drown.wav", qfalse ); - trap_S_RegisterSound("sound/player/janet/fall1.wav", qfalse ); - trap_S_RegisterSound("sound/player/janet/taunt.wav", qfalse ); -#endif - -} - - -//=================================================================================== - - -/* -================= -CG_RegisterGraphics - -This function may execute for a couple of minutes with a slow disk. -================= -*/ -static void CG_RegisterGraphics( void ) { - int i; - char items[MAX_ITEMS+1]; - static char *sb_nums[11] = { - "gfx/2d/numbers/zero_32b", - "gfx/2d/numbers/one_32b", - "gfx/2d/numbers/two_32b", - "gfx/2d/numbers/three_32b", - "gfx/2d/numbers/four_32b", - "gfx/2d/numbers/five_32b", - "gfx/2d/numbers/six_32b", - "gfx/2d/numbers/seven_32b", - "gfx/2d/numbers/eight_32b", - "gfx/2d/numbers/nine_32b", - "gfx/2d/numbers/minus_32b", - }; - - // clear any references to old media - memset( &cg.refdef, 0, sizeof( cg.refdef ) ); - trap_R_ClearScene(); - - CG_LoadingString( cgs.mapname ); - - trap_R_LoadWorldMap( cgs.mapname ); - - // precache status bar pics - CG_LoadingString( "game media" ); - - for ( i=0 ; i<11 ; i++) { - cgs.media.numberShaders[i] = trap_R_RegisterShader( sb_nums[i] ); - } - - cgs.media.botSkillShaders[0] = trap_R_RegisterShader( "menu/art/skill1.tga" ); - cgs.media.botSkillShaders[1] = trap_R_RegisterShader( "menu/art/skill2.tga" ); - cgs.media.botSkillShaders[2] = trap_R_RegisterShader( "menu/art/skill3.tga" ); - cgs.media.botSkillShaders[3] = trap_R_RegisterShader( "menu/art/skill4.tga" ); - cgs.media.botSkillShaders[4] = trap_R_RegisterShader( "menu/art/skill5.tga" ); - - cgs.media.viewBloodShader = trap_R_RegisterShader( "viewBloodBlend" ); - - cgs.media.deferShader = trap_R_RegisterShaderNoMip( "gfx/2d/defer.tga" ); - - cgs.media.scoreboardName = trap_R_RegisterShaderNoMip( "menu/tab/name.tga" ); - cgs.media.scoreboardPing = trap_R_RegisterShaderNoMip( "menu/tab/ping.tga" ); - cgs.media.scoreboardScore = trap_R_RegisterShaderNoMip( "menu/tab/score.tga" ); - cgs.media.scoreboardTime = trap_R_RegisterShaderNoMip( "menu/tab/time.tga" ); - - cgs.media.smokePuffShader = trap_R_RegisterShader( "smokePuff" ); - cgs.media.smokePuffRageProShader = trap_R_RegisterShader( "smokePuffRagePro" ); - cgs.media.shotgunSmokePuffShader = trap_R_RegisterShader( "shotgunSmokePuff" ); -#ifdef MISSIONPACK - cgs.media.nailPuffShader = trap_R_RegisterShader( "nailtrail" ); - cgs.media.blueProxMine = trap_R_RegisterModel( "models/weaphits/proxmineb.md3" ); -#endif - cgs.media.plasmaBallShader = trap_R_RegisterShader( "sprites/plasma1" ); - cgs.media.bloodTrailShader = trap_R_RegisterShader( "bloodTrail" ); - cgs.media.lagometerShader = trap_R_RegisterShader("lagometer" ); - cgs.media.connectionShader = trap_R_RegisterShader( "disconnected" ); - - cgs.media.waterBubbleShader = trap_R_RegisterShader( "waterBubble" ); - - cgs.media.tracerShader = trap_R_RegisterShader( "gfx/misc/tracer" ); - cgs.media.selectShader = trap_R_RegisterShader( "gfx/2d/select" ); - - for ( i = 0 ; i < NUM_CROSSHAIRS ; i++ ) { - cgs.media.crosshairShader[i] = trap_R_RegisterShader( va("gfx/2d/crosshair%c", 'a'+i) ); - } - - cgs.media.backTileShader = trap_R_RegisterShader( "gfx/2d/backtile" ); - cgs.media.noammoShader = trap_R_RegisterShader( "icons/noammo" ); - - // powerup shaders - cgs.media.quadShader = trap_R_RegisterShader("powerups/quad" ); - cgs.media.quadWeaponShader = trap_R_RegisterShader("powerups/quadWeapon" ); - cgs.media.battleSuitShader = trap_R_RegisterShader("powerups/battleSuit" ); - cgs.media.battleWeaponShader = trap_R_RegisterShader("powerups/battleWeapon" ); - cgs.media.invisShader = trap_R_RegisterShader("powerups/invisibility" ); - cgs.media.regenShader = trap_R_RegisterShader("powerups/regen" ); - cgs.media.hastePuffShader = trap_R_RegisterShader("hasteSmokePuff" ); - -#ifdef MISSIONPACK - if ( cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF || cgs.gametype == GT_HARVESTER || cg_buildScript.integer ) { -#else - if ( cgs.gametype == GT_CTF || cg_buildScript.integer ) { -#endif - cgs.media.redCubeModel = trap_R_RegisterModel( "models/powerups/orb/r_orb.md3" ); - cgs.media.blueCubeModel = trap_R_RegisterModel( "models/powerups/orb/b_orb.md3" ); - cgs.media.redCubeIcon = trap_R_RegisterShader( "icons/skull_red" ); - cgs.media.blueCubeIcon = trap_R_RegisterShader( "icons/skull_blue" ); - } - -#ifdef MISSIONPACK - if ( cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF || cgs.gametype == GT_HARVESTER || cg_buildScript.integer ) { -#else - if ( cgs.gametype == GT_CTF || cg_buildScript.integer ) { -#endif - cgs.media.redFlagModel = trap_R_RegisterModel( "models/flags/r_flag.md3" ); - cgs.media.blueFlagModel = trap_R_RegisterModel( "models/flags/b_flag.md3" ); - cgs.media.redFlagShader[0] = trap_R_RegisterShaderNoMip( "icons/iconf_red1" ); - cgs.media.redFlagShader[1] = trap_R_RegisterShaderNoMip( "icons/iconf_red2" ); - cgs.media.redFlagShader[2] = trap_R_RegisterShaderNoMip( "icons/iconf_red3" ); - cgs.media.blueFlagShader[0] = trap_R_RegisterShaderNoMip( "icons/iconf_blu1" ); - cgs.media.blueFlagShader[1] = trap_R_RegisterShaderNoMip( "icons/iconf_blu2" ); - cgs.media.blueFlagShader[2] = trap_R_RegisterShaderNoMip( "icons/iconf_blu3" ); -#ifdef MISSIONPACK - cgs.media.flagPoleModel = trap_R_RegisterModel( "models/flag2/flagpole.md3" ); - cgs.media.flagFlapModel = trap_R_RegisterModel( "models/flag2/flagflap3.md3" ); - - cgs.media.redFlagFlapSkin = trap_R_RegisterSkin( "models/flag2/red.skin" ); - cgs.media.blueFlagFlapSkin = trap_R_RegisterSkin( "models/flag2/blue.skin" ); - cgs.media.neutralFlagFlapSkin = trap_R_RegisterSkin( "models/flag2/white.skin" ); - - cgs.media.redFlagBaseModel = trap_R_RegisterModel( "models/mapobjects/flagbase/red_base.md3" ); - cgs.media.blueFlagBaseModel = trap_R_RegisterModel( "models/mapobjects/flagbase/blue_base.md3" ); - cgs.media.neutralFlagBaseModel = trap_R_RegisterModel( "models/mapobjects/flagbase/ntrl_base.md3" ); -#endif - } - -#ifdef MISSIONPACK - if ( cgs.gametype == GT_1FCTF || cg_buildScript.integer ) { - cgs.media.neutralFlagModel = trap_R_RegisterModel( "models/flags/n_flag.md3" ); - cgs.media.flagShader[0] = trap_R_RegisterShaderNoMip( "icons/iconf_neutral1" ); - cgs.media.flagShader[1] = trap_R_RegisterShaderNoMip( "icons/iconf_red2" ); - cgs.media.flagShader[2] = trap_R_RegisterShaderNoMip( "icons/iconf_blu2" ); - cgs.media.flagShader[3] = trap_R_RegisterShaderNoMip( "icons/iconf_neutral3" ); - } - - if ( cgs.gametype == GT_OBELISK || cg_buildScript.integer ) { - cgs.media.overloadBaseModel = trap_R_RegisterModel( "models/powerups/overload_base.md3" ); - cgs.media.overloadTargetModel = trap_R_RegisterModel( "models/powerups/overload_target.md3" ); - cgs.media.overloadLightsModel = trap_R_RegisterModel( "models/powerups/overload_lights.md3" ); - cgs.media.overloadEnergyModel = trap_R_RegisterModel( "models/powerups/overload_energy.md3" ); - } - - if ( cgs.gametype == GT_HARVESTER || cg_buildScript.integer ) { - cgs.media.harvesterModel = trap_R_RegisterModel( "models/powerups/harvester/harvester.md3" ); - cgs.media.harvesterRedSkin = trap_R_RegisterSkin( "models/powerups/harvester/red.skin" ); - cgs.media.harvesterBlueSkin = trap_R_RegisterSkin( "models/powerups/harvester/blue.skin" ); - cgs.media.harvesterNeutralModel = trap_R_RegisterModel( "models/powerups/obelisk/obelisk.md3" ); - } - - cgs.media.redKamikazeShader = trap_R_RegisterShader( "models/weaphits/kamikred" ); - cgs.media.dustPuffShader = trap_R_RegisterShader("hasteSmokePuff" ); -#endif - - if ( cgs.gametype >= GT_TEAM || cg_buildScript.integer ) { - cgs.media.friendShader = trap_R_RegisterShader( "sprites/foe" ); - cgs.media.redQuadShader = trap_R_RegisterShader("powerups/blueflag" ); - cgs.media.teamStatusBar = trap_R_RegisterShader( "gfx/2d/colorbar.tga" ); -#ifdef MISSIONPACK - cgs.media.blueKamikazeShader = trap_R_RegisterShader( "models/weaphits/kamikblu" ); -#endif - } - - cgs.media.armorModel = trap_R_RegisterModel( "models/powerups/armor/armor_yel.md3" ); - cgs.media.armorIcon = trap_R_RegisterShaderNoMip( "icons/iconr_yellow" ); - - cgs.media.machinegunBrassModel = trap_R_RegisterModel( "models/weapons2/shells/m_shell.md3" ); - cgs.media.shotgunBrassModel = trap_R_RegisterModel( "models/weapons2/shells/s_shell.md3" ); - - cgs.media.gibAbdomen = trap_R_RegisterModel( "models/gibs/abdomen.md3" ); - cgs.media.gibArm = trap_R_RegisterModel( "models/gibs/arm.md3" ); - cgs.media.gibChest = trap_R_RegisterModel( "models/gibs/chest.md3" ); - cgs.media.gibFist = trap_R_RegisterModel( "models/gibs/fist.md3" ); - cgs.media.gibFoot = trap_R_RegisterModel( "models/gibs/foot.md3" ); - cgs.media.gibForearm = trap_R_RegisterModel( "models/gibs/forearm.md3" ); - cgs.media.gibIntestine = trap_R_RegisterModel( "models/gibs/intestine.md3" ); - cgs.media.gibLeg = trap_R_RegisterModel( "models/gibs/leg.md3" ); - cgs.media.gibSkull = trap_R_RegisterModel( "models/gibs/skull.md3" ); - cgs.media.gibBrain = trap_R_RegisterModel( "models/gibs/brain.md3" ); - - cgs.media.smoke2 = trap_R_RegisterModel( "models/weapons2/shells/s_shell.md3" ); - - cgs.media.balloonShader = trap_R_RegisterShader( "sprites/balloon3" ); - - cgs.media.bloodExplosionShader = trap_R_RegisterShader( "bloodExplosion" ); - - cgs.media.bulletFlashModel = trap_R_RegisterModel("models/weaphits/bullet.md3"); - cgs.media.ringFlashModel = trap_R_RegisterModel("models/weaphits/ring02.md3"); - cgs.media.dishFlashModel = trap_R_RegisterModel("models/weaphits/boom01.md3"); -#ifdef MISSIONPACK - cgs.media.teleportEffectModel = trap_R_RegisterModel( "models/powerups/pop.md3" ); -#else - cgs.media.teleportEffectModel = trap_R_RegisterModel( "models/misc/telep.md3" ); - cgs.media.teleportEffectShader = trap_R_RegisterShader( "teleportEffect" ); -#endif -#ifdef MISSIONPACK - cgs.media.kamikazeEffectModel = trap_R_RegisterModel( "models/weaphits/kamboom2.md3" ); - cgs.media.kamikazeShockWave = trap_R_RegisterModel( "models/weaphits/kamwave.md3" ); - cgs.media.kamikazeHeadModel = trap_R_RegisterModel( "models/powerups/kamikazi.md3" ); - cgs.media.kamikazeHeadTrail = trap_R_RegisterModel( "models/powerups/trailtest.md3" ); - cgs.media.guardPowerupModel = trap_R_RegisterModel( "models/powerups/guard_player.md3" ); - cgs.media.scoutPowerupModel = trap_R_RegisterModel( "models/powerups/scout_player.md3" ); - cgs.media.doublerPowerupModel = trap_R_RegisterModel( "models/powerups/doubler_player.md3" ); - cgs.media.ammoRegenPowerupModel = trap_R_RegisterModel( "models/powerups/ammo_player.md3" ); - cgs.media.invulnerabilityImpactModel = trap_R_RegisterModel( "models/powerups/shield/impact.md3" ); - cgs.media.invulnerabilityJuicedModel = trap_R_RegisterModel( "models/powerups/shield/juicer.md3" ); - cgs.media.medkitUsageModel = trap_R_RegisterModel( "models/powerups/regen.md3" ); - cgs.media.heartShader = trap_R_RegisterShaderNoMip( "ui/assets/statusbar/selectedhealth.tga" ); - -#endif - - cgs.media.invulnerabilityPowerupModel = trap_R_RegisterModel( "models/powerups/shield/shield.md3" ); - cgs.media.medalImpressive = trap_R_RegisterShaderNoMip( "medal_impressive" ); - cgs.media.medalExcellent = trap_R_RegisterShaderNoMip( "medal_excellent" ); - cgs.media.medalGauntlet = trap_R_RegisterShaderNoMip( "medal_gauntlet" ); - cgs.media.medalDefend = trap_R_RegisterShaderNoMip( "medal_defend" ); - cgs.media.medalAssist = trap_R_RegisterShaderNoMip( "medal_assist" ); - cgs.media.medalCapture = trap_R_RegisterShaderNoMip( "medal_capture" ); - - - memset( cg_items, 0, sizeof( cg_items ) ); - memset( cg_weapons, 0, sizeof( cg_weapons ) ); - - // only register the items that the server says we need - strcpy( items, CG_ConfigString( CS_ITEMS) ); - - for ( i = 1 ; i < bg_numItems ; i++ ) { - if ( items[ i ] == '1' || cg_buildScript.integer ) { - CG_LoadingItem( i ); - CG_RegisterItemVisuals( i ); - } - } - - // wall marks - cgs.media.bulletMarkShader = trap_R_RegisterShader( "gfx/damage/bullet_mrk" ); - cgs.media.burnMarkShader = trap_R_RegisterShader( "gfx/damage/burn_med_mrk" ); - cgs.media.holeMarkShader = trap_R_RegisterShader( "gfx/damage/hole_lg_mrk" ); - cgs.media.energyMarkShader = trap_R_RegisterShader( "gfx/damage/plasma_mrk" ); - cgs.media.shadowMarkShader = trap_R_RegisterShader( "markShadow" ); - cgs.media.wakeMarkShader = trap_R_RegisterShader( "wake" ); - cgs.media.bloodMarkShader = trap_R_RegisterShader( "bloodMark" ); - - // register the inline models - cgs.numInlineModels = trap_CM_NumInlineModels(); - for ( i = 1 ; i < cgs.numInlineModels ; i++ ) { - char name[10]; - vec3_t mins, maxs; - int j; - - Com_sprintf( name, sizeof(name), "*%i", i ); - cgs.inlineDrawModel[i] = trap_R_RegisterModel( name ); - trap_R_ModelBounds( cgs.inlineDrawModel[i], mins, maxs ); - for ( j = 0 ; j < 3 ; j++ ) { - cgs.inlineModelMidpoints[i][j] = mins[j] + 0.5 * ( maxs[j] - mins[j] ); - } - } - - // register all the server specified models - for (i=1 ; i= MAX_CONFIGSTRINGS ) { - CG_Error( "CG_ConfigString: bad index: %i", index ); - } - return cgs.gameState.stringData + cgs.gameState.stringOffsets[ index ]; -} - -//================================================================== - -/* -====================== -CG_StartMusic - -====================== -*/ -void CG_StartMusic( void ) { - char *s; - char parm1[MAX_QPATH], parm2[MAX_QPATH]; - - // start the background music - s = (char *)CG_ConfigString( CS_MUSIC ); - Q_strncpyz( parm1, COM_Parse( &s ), sizeof( parm1 ) ); - Q_strncpyz( parm2, COM_Parse( &s ), sizeof( parm2 ) ); - - trap_S_StartBackgroundTrack( parm1, parm2 ); -} -#ifdef MISSIONPACK -char *CG_GetMenuBuffer(const char *filename) { - int len; - fileHandle_t f; - static char buf[MAX_MENUFILE]; - - len = trap_FS_FOpenFile( filename, &f, FS_READ ); - if ( !f ) { - trap_Print( va( S_COLOR_RED "menu file not found: %s, using default\n", filename ) ); - return NULL; - } - if ( len >= MAX_MENUFILE ) { - trap_Print( va( S_COLOR_RED "menu file too large: %s is %i, max allowed is %i", filename, len, MAX_MENUFILE ) ); - trap_FS_FCloseFile( f ); - return NULL; - } - - trap_FS_Read( buf, len, f ); - buf[len] = 0; - trap_FS_FCloseFile( f ); - - return buf; -} - -// -// ============================== -// new hud stuff ( mission pack ) -// ============================== -// -qboolean CG_Asset_Parse(int handle) { - pc_token_t token; - const char *tempStr; - - if (!trap_PC_ReadToken(handle, &token)) - return qfalse; - if (Q_stricmp(token.string, "{") != 0) { - return qfalse; - } - - while ( 1 ) { - if (!trap_PC_ReadToken(handle, &token)) - return qfalse; - - if (Q_stricmp(token.string, "}") == 0) { - return qtrue; - } - - // font - if (Q_stricmp(token.string, "font") == 0) { - int pointSize; - if (!PC_String_Parse(handle, &tempStr) || !PC_Int_Parse(handle, &pointSize)) { - return qfalse; - } - cgDC.registerFont(tempStr, pointSize, &cgDC.Assets.textFont); - continue; - } - - // smallFont - if (Q_stricmp(token.string, "smallFont") == 0) { - int pointSize; - if (!PC_String_Parse(handle, &tempStr) || !PC_Int_Parse(handle, &pointSize)) { - return qfalse; - } - cgDC.registerFont(tempStr, pointSize, &cgDC.Assets.smallFont); - continue; - } - - // font - if (Q_stricmp(token.string, "bigfont") == 0) { - int pointSize; - if (!PC_String_Parse(handle, &tempStr) || !PC_Int_Parse(handle, &pointSize)) { - return qfalse; - } - cgDC.registerFont(tempStr, pointSize, &cgDC.Assets.bigFont); - continue; - } - - // gradientbar - if (Q_stricmp(token.string, "gradientbar") == 0) { - if (!PC_String_Parse(handle, &tempStr)) { - return qfalse; - } - cgDC.Assets.gradientBar = trap_R_RegisterShaderNoMip(tempStr); - continue; - } - - // enterMenuSound - if (Q_stricmp(token.string, "menuEnterSound") == 0) { - if (!PC_String_Parse(handle, &tempStr)) { - return qfalse; - } - cgDC.Assets.menuEnterSound = trap_S_RegisterSound( tempStr, qfalse ); - continue; - } - - // exitMenuSound - if (Q_stricmp(token.string, "menuExitSound") == 0) { - if (!PC_String_Parse(handle, &tempStr)) { - return qfalse; - } - cgDC.Assets.menuExitSound = trap_S_RegisterSound( tempStr, qfalse ); - continue; - } - - // itemFocusSound - if (Q_stricmp(token.string, "itemFocusSound") == 0) { - if (!PC_String_Parse(handle, &tempStr)) { - return qfalse; - } - cgDC.Assets.itemFocusSound = trap_S_RegisterSound( tempStr, qfalse ); - continue; - } - - // menuBuzzSound - if (Q_stricmp(token.string, "menuBuzzSound") == 0) { - if (!PC_String_Parse(handle, &tempStr)) { - return qfalse; - } - cgDC.Assets.menuBuzzSound = trap_S_RegisterSound( tempStr, qfalse ); - continue; - } - - if (Q_stricmp(token.string, "cursor") == 0) { - if (!PC_String_Parse(handle, &cgDC.Assets.cursorStr)) { - return qfalse; - } - cgDC.Assets.cursor = trap_R_RegisterShaderNoMip( cgDC.Assets.cursorStr); - continue; - } - - if (Q_stricmp(token.string, "fadeClamp") == 0) { - if (!PC_Float_Parse(handle, &cgDC.Assets.fadeClamp)) { - return qfalse; - } - continue; - } - - if (Q_stricmp(token.string, "fadeCycle") == 0) { - if (!PC_Int_Parse(handle, &cgDC.Assets.fadeCycle)) { - return qfalse; - } - continue; - } - - if (Q_stricmp(token.string, "fadeAmount") == 0) { - if (!PC_Float_Parse(handle, &cgDC.Assets.fadeAmount)) { - return qfalse; - } - continue; - } - - if (Q_stricmp(token.string, "shadowX") == 0) { - if (!PC_Float_Parse(handle, &cgDC.Assets.shadowX)) { - return qfalse; - } - continue; - } - - if (Q_stricmp(token.string, "shadowY") == 0) { - if (!PC_Float_Parse(handle, &cgDC.Assets.shadowY)) { - return qfalse; - } - continue; - } - - if (Q_stricmp(token.string, "shadowColor") == 0) { - if (!PC_Color_Parse(handle, &cgDC.Assets.shadowColor)) { - return qfalse; - } - cgDC.Assets.shadowFadeClamp = cgDC.Assets.shadowColor[3]; - continue; - } - } - return qfalse; // bk001204 - why not? -} - -void CG_ParseMenu(const char *menuFile) { - pc_token_t token; - int handle; - - handle = trap_PC_LoadSource(menuFile); - if (!handle) - handle = trap_PC_LoadSource("ui/testhud.menu"); - if (!handle) - return; - - while ( 1 ) { - if (!trap_PC_ReadToken( handle, &token )) { - break; - } - - //if ( Q_stricmp( token, "{" ) ) { - // Com_Printf( "Missing { in menu file\n" ); - // break; - //} - - //if ( menuCount == MAX_MENUS ) { - // Com_Printf( "Too many menus!\n" ); - // break; - //} - - if ( token.string[0] == '}' ) { - break; - } - - if (Q_stricmp(token.string, "assetGlobalDef") == 0) { - if (CG_Asset_Parse(handle)) { - continue; - } else { - break; - } - } - - - if (Q_stricmp(token.string, "menudef") == 0) { - // start a new menu - Menu_New(handle); - } - } - trap_PC_FreeSource(handle); -} - -qboolean CG_Load_Menu(char **p) { - char *token; - - token = COM_ParseExt(p, qtrue); - - if (token[0] != '{') { - return qfalse; - } - - while ( 1 ) { - - token = COM_ParseExt(p, qtrue); - - if (Q_stricmp(token, "}") == 0) { - return qtrue; - } - - if ( !token || token[0] == 0 ) { - return qfalse; - } - - CG_ParseMenu(token); - } - return qfalse; -} - - - -void CG_LoadMenus(const char *menuFile) { - char *token; - char *p; - int len, start; - fileHandle_t f; - static char buf[MAX_MENUDEFFILE]; - - start = trap_Milliseconds(); - - len = trap_FS_FOpenFile( menuFile, &f, FS_READ ); - if ( !f ) { - trap_Error( va( S_COLOR_YELLOW "menu file not found: %s, using default\n", menuFile ) ); - len = trap_FS_FOpenFile( "ui/hud.txt", &f, FS_READ ); - if (!f) { - trap_Error( va( S_COLOR_RED "default menu file not found: ui/hud.txt, unable to continue!\n", menuFile ) ); - } - } - - if ( len >= MAX_MENUDEFFILE ) { - trap_Error( va( S_COLOR_RED "menu file too large: %s is %i, max allowed is %i", menuFile, len, MAX_MENUDEFFILE ) ); - trap_FS_FCloseFile( f ); - return; - } - - trap_FS_Read( buf, len, f ); - buf[len] = 0; - trap_FS_FCloseFile( f ); - - COM_Compress(buf); - - Menu_Reset(); - - p = buf; - - while ( 1 ) { - token = COM_ParseExt( &p, qtrue ); - if( !token || token[0] == 0 || token[0] == '}') { - 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 ( Q_stricmp( token, "}" ) == 0 ) { - break; - } - - if (Q_stricmp(token, "loadmenu") == 0) { - if (CG_Load_Menu(&p)) { - continue; - } else { - break; - } - } - } - - Com_Printf("UI menu load time = %d milli seconds\n", trap_Milliseconds() - start); - -} - - - -static qboolean CG_OwnerDrawHandleKey(int ownerDraw, int flags, float *special, int key) { - return qfalse; -} - - -static int CG_FeederCount(float feederID) { - int i, count; - count = 0; - if (feederID == FEEDER_REDTEAM_LIST) { - for (i = 0; i < cg.numScores; i++) { - if (cg.scores[i].team == TEAM_RED) { - count++; - } - } - } else if (feederID == FEEDER_BLUETEAM_LIST) { - for (i = 0; i < cg.numScores; i++) { - if (cg.scores[i].team == TEAM_BLUE) { - count++; - } - } - } else if (feederID == FEEDER_SCOREBOARD) { - return cg.numScores; - } - return count; -} - - -void CG_SetScoreSelection(void *p) { - menuDef_t *menu = (menuDef_t*)p; - playerState_t *ps = &cg.snap->ps; - int i, red, blue; - red = blue = 0; - for (i = 0; i < cg.numScores; i++) { - if (cg.scores[i].team == TEAM_RED) { - red++; - } else if (cg.scores[i].team == TEAM_BLUE) { - blue++; - } - if (ps->clientNum == cg.scores[i].client) { - cg.selectedScore = i; - } - } - - if (menu == NULL) { - // just interested in setting the selected score - return; - } - - if ( cgs.gametype >= GT_TEAM ) { - int feeder = FEEDER_REDTEAM_LIST; - i = red; - if (cg.scores[cg.selectedScore].team == TEAM_BLUE) { - feeder = FEEDER_BLUETEAM_LIST; - i = blue; - } - Menu_SetFeederSelection(menu, feeder, i, NULL); - } else { - Menu_SetFeederSelection(menu, FEEDER_SCOREBOARD, cg.selectedScore, NULL); - } -} - -// FIXME: might need to cache this info -static clientInfo_t * CG_InfoFromScoreIndex(int index, int team, int *scoreIndex) { - int i, count; - if ( cgs.gametype >= GT_TEAM ) { - count = 0; - for (i = 0; i < cg.numScores; i++) { - if (cg.scores[i].team == team) { - if (count == index) { - *scoreIndex = i; - return &cgs.clientinfo[cg.scores[i].client]; - } - count++; - } - } - } - *scoreIndex = index; - return &cgs.clientinfo[ cg.scores[index].client ]; -} - -static const char *CG_FeederItemText(float feederID, int index, int column, qhandle_t *handle) { - gitem_t *item; - int scoreIndex = 0; - clientInfo_t *info = NULL; - int team = -1; - score_t *sp = NULL; - - *handle = -1; - - if (feederID == FEEDER_REDTEAM_LIST) { - team = TEAM_RED; - } else if (feederID == FEEDER_BLUETEAM_LIST) { - team = TEAM_BLUE; - } - - info = CG_InfoFromScoreIndex(index, team, &scoreIndex); - sp = &cg.scores[scoreIndex]; - - if (info && info->infoValid) { - switch (column) { - case 0: - if ( info->powerups & ( 1 << PW_NEUTRALFLAG ) ) { - item = BG_FindItemForPowerup( PW_NEUTRALFLAG ); - *handle = cg_items[ ITEM_INDEX(item) ].icon; - } else if ( info->powerups & ( 1 << PW_REDFLAG ) ) { - item = BG_FindItemForPowerup( PW_REDFLAG ); - *handle = cg_items[ ITEM_INDEX(item) ].icon; - } else if ( info->powerups & ( 1 << PW_BLUEFLAG ) ) { - item = BG_FindItemForPowerup( PW_BLUEFLAG ); - *handle = cg_items[ ITEM_INDEX(item) ].icon; - } else { - if ( info->botSkill > 0 && info->botSkill <= 5 ) { - *handle = cgs.media.botSkillShaders[ info->botSkill - 1 ]; - } else if ( info->handicap < 100 ) { - return va("%i", info->handicap ); - } - } - break; - case 1: - if (team == -1) { - return ""; - } else { - *handle = CG_StatusHandle(info->teamTask); - } - break; - case 2: - if ( cg.snap->ps.stats[ STAT_CLIENTS_READY ] & ( 1 << sp->client ) ) { - return "Ready"; - } - if (team == -1) { - if (cgs.gametype == GT_TOURNAMENT) { - return va("%i/%i", info->wins, info->losses); - } else if (info->infoValid && info->team == TEAM_SPECTATOR ) { - return "Spectator"; - } else { - return ""; - } - } else { - if (info->teamLeader) { - return "Leader"; - } - } - break; - case 3: - return info->name; - break; - case 4: - return va("%i", info->score); - break; - case 5: - return va("%4i", sp->time); - break; - case 6: - if ( sp->ping == -1 ) { - return "connecting"; - } - return va("%4i", sp->ping); - break; - } - } - - return ""; -} - -static qhandle_t CG_FeederItemImage(float feederID, int index) { - return 0; -} - -static void CG_FeederSelection(float feederID, int index) { - if ( cgs.gametype >= GT_TEAM ) { - int i, count; - int team = (feederID == FEEDER_REDTEAM_LIST) ? TEAM_RED : TEAM_BLUE; - count = 0; - for (i = 0; i < cg.numScores; i++) { - if (cg.scores[i].team == team) { - if (index == count) { - cg.selectedScore = i; - } - count++; - } - } - } else { - cg.selectedScore = index; - } -} -#endif - -#ifdef MISSIONPACK // bk001204 - only needed there -static float CG_Cvar_Get(const char *cvar) { - char buff[128]; - memset(buff, 0, sizeof(buff)); - trap_Cvar_VariableStringBuffer(cvar, buff, sizeof(buff)); - return atof(buff); -} -#endif - -#ifdef MISSIONPACK -void CG_Text_PaintWithCursor(float x, float y, float scale, vec4_t color, const char *text, int cursorPos, char cursor, int limit, int style) { - CG_Text_Paint(x, y, scale, color, text, 0, limit, style); -} - -static int CG_OwnerDrawWidth(int ownerDraw, float scale) { - switch (ownerDraw) { - case CG_GAME_TYPE: - return CG_Text_Width(CG_GameTypeString(), scale, 0); - case CG_GAME_STATUS: - return CG_Text_Width(CG_GetGameStatusText(), scale, 0); - break; - case CG_KILLER: - return CG_Text_Width(CG_GetKillerText(), scale, 0); - break; - case CG_RED_NAME: - return CG_Text_Width(cg_redTeamName.string, scale, 0); - break; - case CG_BLUE_NAME: - return CG_Text_Width(cg_blueTeamName.string, scale, 0); - break; - - - } - return 0; -} - -static int CG_PlayCinematic(const char *name, float x, float y, float w, float h) { - return trap_CIN_PlayCinematic(name, x, y, w, h, CIN_loop); -} - -static void CG_StopCinematic(int handle) { - trap_CIN_StopCinematic(handle); -} - -static void CG_DrawCinematic(int handle, float x, float y, float w, float h) { - trap_CIN_SetExtents(handle, x, y, w, h); - trap_CIN_DrawCinematic(handle); -} - -static void CG_RunCinematicFrame(int handle) { - trap_CIN_RunCinematic(handle); -} - -/* -================= -CG_LoadHudMenu(); - -================= -*/ -void CG_LoadHudMenu() { - char buff[1024]; - const char *hudSet; - - cgDC.registerShaderNoMip = &trap_R_RegisterShaderNoMip; - cgDC.setColor = &trap_R_SetColor; - cgDC.drawHandlePic = &CG_DrawPic; - cgDC.drawStretchPic = &trap_R_DrawStretchPic; - cgDC.drawText = &CG_Text_Paint; - cgDC.textWidth = &CG_Text_Width; - cgDC.textHeight = &CG_Text_Height; - cgDC.registerModel = &trap_R_RegisterModel; - cgDC.modelBounds = &trap_R_ModelBounds; - cgDC.fillRect = &CG_FillRect; - cgDC.drawRect = &CG_DrawRect; - cgDC.drawSides = &CG_DrawSides; - cgDC.drawTopBottom = &CG_DrawTopBottom; - cgDC.clearScene = &trap_R_ClearScene; - cgDC.addRefEntityToScene = &trap_R_AddRefEntityToScene; - cgDC.renderScene = &trap_R_RenderScene; - cgDC.registerFont = &trap_R_RegisterFont; - cgDC.ownerDrawItem = &CG_OwnerDraw; - cgDC.getValue = &CG_GetValue; - cgDC.ownerDrawVisible = &CG_OwnerDrawVisible; - cgDC.runScript = &CG_RunMenuScript; - cgDC.getTeamColor = &CG_GetTeamColor; - cgDC.setCVar = trap_Cvar_Set; - cgDC.getCVarString = trap_Cvar_VariableStringBuffer; - cgDC.getCVarValue = CG_Cvar_Get; - cgDC.drawTextWithCursor = &CG_Text_PaintWithCursor; - //cgDC.setOverstrikeMode = &trap_Key_SetOverstrikeMode; - //cgDC.getOverstrikeMode = &trap_Key_GetOverstrikeMode; - cgDC.startLocalSound = &trap_S_StartLocalSound; - cgDC.ownerDrawHandleKey = &CG_OwnerDrawHandleKey; - cgDC.feederCount = &CG_FeederCount; - cgDC.feederItemImage = &CG_FeederItemImage; - cgDC.feederItemText = &CG_FeederItemText; - cgDC.feederSelection = &CG_FeederSelection; - //cgDC.setBinding = &trap_Key_SetBinding; - //cgDC.getBindingBuf = &trap_Key_GetBindingBuf; - //cgDC.keynumToStringBuf = &trap_Key_KeynumToStringBuf; - //cgDC.executeText = &trap_Cmd_ExecuteText; - cgDC.Error = &Com_Error; - cgDC.Print = &Com_Printf; - cgDC.ownerDrawWidth = &CG_OwnerDrawWidth; - //cgDC.Pause = &CG_Pause; - cgDC.registerSound = &trap_S_RegisterSound; - cgDC.startBackgroundTrack = &trap_S_StartBackgroundTrack; - cgDC.stopBackgroundTrack = &trap_S_StopBackgroundTrack; - cgDC.playCinematic = &CG_PlayCinematic; - cgDC.stopCinematic = &CG_StopCinematic; - cgDC.drawCinematic = &CG_DrawCinematic; - cgDC.runCinematicFrame = &CG_RunCinematicFrame; - - Init_Display(&cgDC); - - Menu_Reset(); - - trap_Cvar_VariableStringBuffer("cg_hudFiles", buff, sizeof(buff)); - hudSet = buff; - if (hudSet[0] == '\0') { - hudSet = "ui/hud.txt"; - } - - CG_LoadMenus(hudSet); -} - -void CG_AssetCache() { - //if (Assets.textFont == NULL) { - // trap_R_RegisterFont("fonts/arial.ttf", 72, &Assets.textFont); - //} - //Assets.background = trap_R_RegisterShaderNoMip( ASSET_BACKGROUND ); - //Com_Printf("Menu Size: %i bytes\n", sizeof(Menus)); - cgDC.Assets.gradientBar = trap_R_RegisterShaderNoMip( ASSET_GRADIENTBAR ); - cgDC.Assets.fxBasePic = trap_R_RegisterShaderNoMip( ART_FX_BASE ); - cgDC.Assets.fxPic[0] = trap_R_RegisterShaderNoMip( ART_FX_RED ); - cgDC.Assets.fxPic[1] = trap_R_RegisterShaderNoMip( ART_FX_YELLOW ); - cgDC.Assets.fxPic[2] = trap_R_RegisterShaderNoMip( ART_FX_GREEN ); - cgDC.Assets.fxPic[3] = trap_R_RegisterShaderNoMip( ART_FX_TEAL ); - cgDC.Assets.fxPic[4] = trap_R_RegisterShaderNoMip( ART_FX_BLUE ); - cgDC.Assets.fxPic[5] = trap_R_RegisterShaderNoMip( ART_FX_CYAN ); - cgDC.Assets.fxPic[6] = trap_R_RegisterShaderNoMip( ART_FX_WHITE ); - cgDC.Assets.scrollBar = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR ); - cgDC.Assets.scrollBarArrowDown = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWDOWN ); - cgDC.Assets.scrollBarArrowUp = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWUP ); - cgDC.Assets.scrollBarArrowLeft = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWLEFT ); - cgDC.Assets.scrollBarArrowRight = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWRIGHT ); - cgDC.Assets.scrollBarThumb = trap_R_RegisterShaderNoMip( ASSET_SCROLL_THUMB ); - cgDC.Assets.sliderBar = trap_R_RegisterShaderNoMip( ASSET_SLIDER_BAR ); - cgDC.Assets.sliderThumb = trap_R_RegisterShaderNoMip( ASSET_SLIDER_THUMB ); -} -#endif -/* -================= -CG_Init - -Called after every level change or subsystem restart -Will perform callbacks to make the loading info screen update. -================= -*/ -void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) { - const char *s; - - // clear everything - memset( &cgs, 0, sizeof( cgs ) ); - memset( &cg, 0, sizeof( cg ) ); - memset( cg_entities, 0, sizeof(cg_entities) ); - memset( cg_weapons, 0, sizeof(cg_weapons) ); - memset( cg_items, 0, sizeof(cg_items) ); - - cg.clientNum = clientNum; - - cgs.processedSnapshotNum = serverMessageNum; - cgs.serverCommandSequence = serverCommandSequence; - - // load a few needed things before we do any screen updates - cgs.media.charsetShader = trap_R_RegisterShader( "gfx/2d/bigchars" ); - cgs.media.whiteShader = trap_R_RegisterShader( "white" ); - cgs.media.charsetProp = trap_R_RegisterShaderNoMip( "menu/art/font1_prop.tga" ); - cgs.media.charsetPropGlow = trap_R_RegisterShaderNoMip( "menu/art/font1_prop_glo.tga" ); - cgs.media.charsetPropB = trap_R_RegisterShaderNoMip( "menu/art/font2_prop.tga" ); - - CG_RegisterCvars(); - - CG_InitConsoleCommands(); - - cg.weaponSelect = WP_MACHINEGUN; - - cgs.redflag = cgs.blueflag = -1; // For compatibily, default to unset for - cgs.flagStatus = -1; - // old servers - - // get the rendering configuration from the client system - trap_GetGlconfig( &cgs.glconfig ); - cgs.screenXScale = cgs.glconfig.vidWidth / 640.0; - cgs.screenYScale = cgs.glconfig.vidHeight / 480.0; - - // get the gamestate from the client system - trap_GetGameState( &cgs.gameState ); - - // check version - s = CG_ConfigString( CS_GAME_VERSION ); - if ( strcmp( s, GAME_VERSION ) ) { - CG_Error( "Client/Server game mismatch: %s/%s", GAME_VERSION, s ); - } - - s = CG_ConfigString( CS_LEVEL_START_TIME ); - cgs.levelStartTime = atoi( s ); - - CG_ParseServerinfo(); - - // load the new map - CG_LoadingString( "collision map" ); - - trap_CM_LoadMap( cgs.mapname ); - -#ifdef MISSIONPACK - String_Init(); -#endif - - cg.loading = qtrue; // force players to load instead of defer - - CG_LoadingString( "sounds" ); - - CG_RegisterSounds(); - - CG_LoadingString( "graphics" ); - - CG_RegisterGraphics(); - - CG_LoadingString( "clients" ); - - CG_RegisterClients(); // if low on memory, some clients will be deferred - -#ifdef MISSIONPACK - CG_AssetCache(); - CG_LoadHudMenu(); // load new hud stuff -#endif - - cg.loading = qfalse; // future players will be deferred - - CG_InitLocalEntities(); - - CG_InitMarkPolys(); - - // remove the last loading update - cg.infoScreenText[0] = 0; - - // Make sure we have update values (scores) - CG_SetConfigValues(); - - CG_StartMusic(); - - CG_LoadingString( "" ); - -#ifdef MISSIONPACK - CG_InitTeamChat(); -#endif - - CG_ShaderStateChanged(); - - trap_S_ClearLoopingSounds( qtrue ); -} - -/* -================= -CG_Shutdown - -Called before every level change or subsystem restart -================= -*/ -void CG_Shutdown( void ) { - // some mods may need to do cleanup work here, - // like closing files or archiving session data -} - - -/* -================== -CG_EventHandling -================== - type 0 - no event handling - 1 - team menu - 2 - hud editor - -*/ -#ifndef MISSIONPACK -void CG_EventHandling(int type) { -} - - - -void CG_KeyEvent(int key, qboolean down) { -} - -void CG_MouseEvent(int x, int y) { -} -#endif - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// cg_main.c -- initialization and primary entry point for cgame +#include "cg_local.h" + +#ifdef MISSIONPACK +#include "../ui/ui_shared.h" +// display context for new ui stuff +displayContextDef_t cgDC; +#endif + +int forceModelModificationCount = -1; + +void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ); +void CG_Shutdown( void ); + + +/* +================ +vmMain + +This is the only way control passes into the module. +This must be the very first function compiled into the .q3vm 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 ) { + + switch ( command ) { + case CG_INIT: + CG_Init( arg0, arg1, arg2 ); + return 0; + case CG_SHUTDOWN: + CG_Shutdown(); + return 0; + case CG_CONSOLE_COMMAND: + return CG_ConsoleCommand(); + case CG_DRAW_ACTIVE_FRAME: + CG_DrawActiveFrame( arg0, arg1, arg2 ); + return 0; + case CG_CROSSHAIR_PLAYER: + return CG_CrosshairPlayer(); + case CG_LAST_ATTACKER: + return CG_LastAttacker(); + case CG_KEY_EVENT: + CG_KeyEvent(arg0, arg1); + return 0; + case CG_MOUSE_EVENT: +#ifdef MISSIONPACK + cgDC.cursorx = cgs.cursorX; + cgDC.cursory = cgs.cursorY; +#endif + CG_MouseEvent(arg0, arg1); + return 0; + case CG_EVENT_HANDLING: + CG_EventHandling(arg0); + return 0; + default: + CG_Error( "vmMain: unknown command %i", command ); + break; + } + return -1; +} + + +cg_t cg; +cgs_t cgs; +centity_t cg_entities[MAX_GENTITIES]; +weaponInfo_t cg_weapons[MAX_WEAPONS]; +itemInfo_t cg_items[MAX_ITEMS]; + + +vmCvar_t cg_railTrailTime; +vmCvar_t cg_centertime; +vmCvar_t cg_runpitch; +vmCvar_t cg_runroll; +vmCvar_t cg_bobup; +vmCvar_t cg_bobpitch; +vmCvar_t cg_bobroll; +vmCvar_t cg_swingSpeed; +vmCvar_t cg_shadows; +vmCvar_t cg_gibs; +vmCvar_t cg_drawTimer; +vmCvar_t cg_drawFPS; +vmCvar_t cg_drawSnapshot; +vmCvar_t cg_draw3dIcons; +vmCvar_t cg_drawIcons; +vmCvar_t cg_drawAmmoWarning; +vmCvar_t cg_drawCrosshair; +vmCvar_t cg_drawCrosshairNames; +vmCvar_t cg_drawRewards; +vmCvar_t cg_crosshairSize; +vmCvar_t cg_crosshairX; +vmCvar_t cg_crosshairY; +vmCvar_t cg_crosshairHealth; +vmCvar_t cg_draw2D; +vmCvar_t cg_drawStatus; +vmCvar_t cg_animSpeed; +vmCvar_t cg_debugAnim; +vmCvar_t cg_debugPosition; +vmCvar_t cg_debugEvents; +vmCvar_t cg_errorDecay; +vmCvar_t cg_nopredict; +vmCvar_t cg_noPlayerAnims; +vmCvar_t cg_showmiss; +vmCvar_t cg_footsteps; +vmCvar_t cg_addMarks; +vmCvar_t cg_brassTime; +vmCvar_t cg_viewsize; +vmCvar_t cg_drawGun; +vmCvar_t cg_gun_frame; +vmCvar_t cg_gun_x; +vmCvar_t cg_gun_y; +vmCvar_t cg_gun_z; +vmCvar_t cg_tracerChance; +vmCvar_t cg_tracerWidth; +vmCvar_t cg_tracerLength; +vmCvar_t cg_autoswitch; +vmCvar_t cg_ignore; +vmCvar_t cg_simpleItems; +vmCvar_t cg_fov; +vmCvar_t cg_zoomFov; +vmCvar_t cg_thirdPerson; +vmCvar_t cg_thirdPersonRange; +vmCvar_t cg_thirdPersonAngle; +vmCvar_t cg_stereoSeparation; +vmCvar_t cg_lagometer; +vmCvar_t cg_drawAttacker; +vmCvar_t cg_synchronousClients; +vmCvar_t cg_teamChatTime; +vmCvar_t cg_teamChatHeight; +vmCvar_t cg_stats; +vmCvar_t cg_buildScript; +vmCvar_t cg_forceModel; +vmCvar_t cg_paused; +vmCvar_t cg_blood; +vmCvar_t cg_predictItems; +vmCvar_t cg_deferPlayers; +vmCvar_t cg_drawTeamOverlay; +vmCvar_t cg_teamOverlayUserinfo; +vmCvar_t cg_drawFriend; +vmCvar_t cg_teamChatsOnly; +vmCvar_t cg_noVoiceChats; +vmCvar_t cg_noVoiceText; +vmCvar_t cg_hudFiles; +vmCvar_t cg_scorePlum; +vmCvar_t cg_smoothClients; +vmCvar_t pmove_fixed; +//vmCvar_t cg_pmove_fixed; +vmCvar_t pmove_msec; +vmCvar_t cg_pmove_msec; +vmCvar_t cg_cameraMode; +vmCvar_t cg_cameraOrbit; +vmCvar_t cg_cameraOrbitDelay; +vmCvar_t cg_timescaleFadeEnd; +vmCvar_t cg_timescaleFadeSpeed; +vmCvar_t cg_timescale; +vmCvar_t cg_smallFont; +vmCvar_t cg_bigFont; +vmCvar_t cg_noTaunt; +vmCvar_t cg_noProjectileTrail; +vmCvar_t cg_oldRail; +vmCvar_t cg_oldRocket; +vmCvar_t cg_oldPlasma; +vmCvar_t cg_trueLightning; + +#ifdef MISSIONPACK +vmCvar_t cg_redTeamName; +vmCvar_t cg_blueTeamName; +vmCvar_t cg_currentSelectedPlayer; +vmCvar_t cg_currentSelectedPlayerName; +vmCvar_t cg_singlePlayer; +vmCvar_t cg_enableDust; +vmCvar_t cg_enableBreath; +vmCvar_t cg_singlePlayerActive; +vmCvar_t cg_recordSPDemo; +vmCvar_t cg_recordSPDemoName; +vmCvar_t cg_obeliskRespawnDelay; +#endif + +typedef struct { + vmCvar_t *vmCvar; + char *cvarName; + char *defaultString; + int cvarFlags; +} cvarTable_t; + +static cvarTable_t cvarTable[] = { // bk001129 + { &cg_ignore, "cg_ignore", "0", 0 }, // used for debugging + { &cg_autoswitch, "cg_autoswitch", "1", CVAR_ARCHIVE }, + { &cg_drawGun, "cg_drawGun", "1", CVAR_ARCHIVE }, + { &cg_zoomFov, "cg_zoomfov", "22.5", CVAR_ARCHIVE }, + { &cg_fov, "cg_fov", "90", CVAR_ARCHIVE }, + { &cg_viewsize, "cg_viewsize", "100", CVAR_ARCHIVE }, + { &cg_stereoSeparation, "cg_stereoSeparation", "0.4", CVAR_ARCHIVE }, + { &cg_shadows, "cg_shadows", "1", CVAR_ARCHIVE }, + { &cg_gibs, "cg_gibs", "1", CVAR_ARCHIVE }, + { &cg_draw2D, "cg_draw2D", "1", CVAR_ARCHIVE }, + { &cg_drawStatus, "cg_drawStatus", "1", CVAR_ARCHIVE }, + { &cg_drawTimer, "cg_drawTimer", "0", CVAR_ARCHIVE }, + { &cg_drawFPS, "cg_drawFPS", "0", CVAR_ARCHIVE }, + { &cg_drawSnapshot, "cg_drawSnapshot", "0", CVAR_ARCHIVE }, + { &cg_draw3dIcons, "cg_draw3dIcons", "1", CVAR_ARCHIVE }, + { &cg_drawIcons, "cg_drawIcons", "1", CVAR_ARCHIVE }, + { &cg_drawAmmoWarning, "cg_drawAmmoWarning", "1", CVAR_ARCHIVE }, + { &cg_drawAttacker, "cg_drawAttacker", "1", CVAR_ARCHIVE }, + { &cg_drawCrosshair, "cg_drawCrosshair", "4", CVAR_ARCHIVE }, + { &cg_drawCrosshairNames, "cg_drawCrosshairNames", "1", CVAR_ARCHIVE }, + { &cg_drawRewards, "cg_drawRewards", "1", CVAR_ARCHIVE }, + { &cg_crosshairSize, "cg_crosshairSize", "24", CVAR_ARCHIVE }, + { &cg_crosshairHealth, "cg_crosshairHealth", "1", CVAR_ARCHIVE }, + { &cg_crosshairX, "cg_crosshairX", "0", CVAR_ARCHIVE }, + { &cg_crosshairY, "cg_crosshairY", "0", CVAR_ARCHIVE }, + { &cg_brassTime, "cg_brassTime", "2500", CVAR_ARCHIVE }, + { &cg_simpleItems, "cg_simpleItems", "0", CVAR_ARCHIVE }, + { &cg_addMarks, "cg_marks", "1", CVAR_ARCHIVE }, + { &cg_lagometer, "cg_lagometer", "1", CVAR_ARCHIVE }, + { &cg_railTrailTime, "cg_railTrailTime", "400", CVAR_ARCHIVE }, + { &cg_gun_x, "cg_gunX", "0", CVAR_CHEAT }, + { &cg_gun_y, "cg_gunY", "0", CVAR_CHEAT }, + { &cg_gun_z, "cg_gunZ", "0", CVAR_CHEAT }, + { &cg_centertime, "cg_centertime", "3", CVAR_CHEAT }, + { &cg_runpitch, "cg_runpitch", "0.002", CVAR_ARCHIVE}, + { &cg_runroll, "cg_runroll", "0.005", CVAR_ARCHIVE }, + { &cg_bobup , "cg_bobup", "0.005", CVAR_CHEAT }, + { &cg_bobpitch, "cg_bobpitch", "0.002", CVAR_ARCHIVE }, + { &cg_bobroll, "cg_bobroll", "0.002", CVAR_ARCHIVE }, + { &cg_swingSpeed, "cg_swingSpeed", "0.3", CVAR_CHEAT }, + { &cg_animSpeed, "cg_animspeed", "1", CVAR_CHEAT }, + { &cg_debugAnim, "cg_debuganim", "0", CVAR_CHEAT }, + { &cg_debugPosition, "cg_debugposition", "0", CVAR_CHEAT }, + { &cg_debugEvents, "cg_debugevents", "0", CVAR_CHEAT }, + { &cg_errorDecay, "cg_errordecay", "100", 0 }, + { &cg_nopredict, "cg_nopredict", "0", 0 }, + { &cg_noPlayerAnims, "cg_noplayeranims", "0", CVAR_CHEAT }, + { &cg_showmiss, "cg_showmiss", "0", 0 }, + { &cg_footsteps, "cg_footsteps", "1", CVAR_CHEAT }, + { &cg_tracerChance, "cg_tracerchance", "0.4", CVAR_CHEAT }, + { &cg_tracerWidth, "cg_tracerwidth", "1", CVAR_CHEAT }, + { &cg_tracerLength, "cg_tracerlength", "100", CVAR_CHEAT }, + { &cg_thirdPersonRange, "cg_thirdPersonRange", "40", CVAR_CHEAT }, + { &cg_thirdPersonAngle, "cg_thirdPersonAngle", "0", CVAR_CHEAT }, + { &cg_thirdPerson, "cg_thirdPerson", "0", 0 }, + { &cg_teamChatTime, "cg_teamChatTime", "3000", CVAR_ARCHIVE }, + { &cg_teamChatHeight, "cg_teamChatHeight", "0", CVAR_ARCHIVE }, + { &cg_forceModel, "cg_forceModel", "0", CVAR_ARCHIVE }, + { &cg_predictItems, "cg_predictItems", "1", CVAR_ARCHIVE }, +#ifdef MISSIONPACK + { &cg_deferPlayers, "cg_deferPlayers", "0", CVAR_ARCHIVE }, +#else + { &cg_deferPlayers, "cg_deferPlayers", "1", CVAR_ARCHIVE }, +#endif + { &cg_drawTeamOverlay, "cg_drawTeamOverlay", "0", CVAR_ARCHIVE }, + { &cg_teamOverlayUserinfo, "teamoverlay", "0", CVAR_ROM | CVAR_USERINFO }, + { &cg_stats, "cg_stats", "0", 0 }, + { &cg_drawFriend, "cg_drawFriend", "1", CVAR_ARCHIVE }, + { &cg_teamChatsOnly, "cg_teamChatsOnly", "0", CVAR_ARCHIVE }, + { &cg_noVoiceChats, "cg_noVoiceChats", "0", CVAR_ARCHIVE }, + { &cg_noVoiceText, "cg_noVoiceText", "0", CVAR_ARCHIVE }, + // the following variables are created in other parts of the system, + // but we also reference them here + { &cg_buildScript, "com_buildScript", "0", 0 }, // force loading of all possible data amd error on failures + { &cg_paused, "cl_paused", "0", CVAR_ROM }, + { &cg_blood, "com_blood", "1", CVAR_ARCHIVE }, + { &cg_synchronousClients, "g_synchronousClients", "0", 0 }, // communicated by systeminfo +#ifdef MISSIONPACK + { &cg_redTeamName, "g_redteam", DEFAULT_REDTEAM_NAME, CVAR_ARCHIVE | CVAR_SERVERINFO | CVAR_USERINFO }, + { &cg_blueTeamName, "g_blueteam", DEFAULT_BLUETEAM_NAME, CVAR_ARCHIVE | CVAR_SERVERINFO | CVAR_USERINFO }, + { &cg_currentSelectedPlayer, "cg_currentSelectedPlayer", "0", CVAR_ARCHIVE}, + { &cg_currentSelectedPlayerName, "cg_currentSelectedPlayerName", "", CVAR_ARCHIVE}, + { &cg_singlePlayer, "ui_singlePlayerActive", "0", CVAR_USERINFO}, + { &cg_enableDust, "g_enableDust", "0", CVAR_SERVERINFO}, + { &cg_enableBreath, "g_enableBreath", "0", CVAR_SERVERINFO}, + { &cg_singlePlayerActive, "ui_singlePlayerActive", "0", CVAR_USERINFO}, + { &cg_recordSPDemo, "ui_recordSPDemo", "0", CVAR_ARCHIVE}, + { &cg_recordSPDemoName, "ui_recordSPDemoName", "", CVAR_ARCHIVE}, + { &cg_obeliskRespawnDelay, "g_obeliskRespawnDelay", "10", CVAR_SERVERINFO}, + { &cg_hudFiles, "cg_hudFiles", "ui/hud.txt", CVAR_ARCHIVE}, +#endif + { &cg_cameraOrbit, "cg_cameraOrbit", "0", CVAR_CHEAT}, + { &cg_cameraOrbitDelay, "cg_cameraOrbitDelay", "50", CVAR_ARCHIVE}, + { &cg_timescaleFadeEnd, "cg_timescaleFadeEnd", "1", 0}, + { &cg_timescaleFadeSpeed, "cg_timescaleFadeSpeed", "0", 0}, + { &cg_timescale, "timescale", "1", 0}, + { &cg_scorePlum, "cg_scorePlums", "1", CVAR_USERINFO | CVAR_ARCHIVE}, + { &cg_smoothClients, "cg_smoothClients", "0", CVAR_USERINFO | CVAR_ARCHIVE}, + { &cg_cameraMode, "com_cameraMode", "0", CVAR_CHEAT}, + + { &pmove_fixed, "pmove_fixed", "0", 0}, + { &pmove_msec, "pmove_msec", "8", 0}, + { &cg_noTaunt, "cg_noTaunt", "0", CVAR_ARCHIVE}, + { &cg_noProjectileTrail, "cg_noProjectileTrail", "0", CVAR_ARCHIVE}, + { &cg_smallFont, "ui_smallFont", "0.25", CVAR_ARCHIVE}, + { &cg_bigFont, "ui_bigFont", "0.4", CVAR_ARCHIVE}, + { &cg_oldRail, "cg_oldRail", "1", CVAR_ARCHIVE}, + { &cg_oldRocket, "cg_oldRocket", "1", CVAR_ARCHIVE}, + { &cg_oldPlasma, "cg_oldPlasma", "1", CVAR_ARCHIVE}, + { &cg_trueLightning, "cg_trueLightning", "0.0", CVAR_ARCHIVE} +// { &cg_pmove_fixed, "cg_pmove_fixed", "0", CVAR_USERINFO | CVAR_ARCHIVE } +}; + +static int cvarTableSize = sizeof( cvarTable ) / sizeof( cvarTable[0] ); + +/* +================= +CG_RegisterCvars +================= +*/ +void CG_RegisterCvars( void ) { + int i; + cvarTable_t *cv; + char var[MAX_TOKEN_CHARS]; + + for ( i = 0, cv = cvarTable ; i < cvarTableSize ; i++, cv++ ) { + trap_Cvar_Register( cv->vmCvar, cv->cvarName, + cv->defaultString, cv->cvarFlags ); + } + + // see if we are also running the server on this machine + trap_Cvar_VariableStringBuffer( "sv_running", var, sizeof( var ) ); + cgs.localServer = atoi( var ); + + forceModelModificationCount = cg_forceModel.modificationCount; + + trap_Cvar_Register(NULL, "model", DEFAULT_MODEL, CVAR_USERINFO | CVAR_ARCHIVE ); + trap_Cvar_Register(NULL, "headmodel", DEFAULT_MODEL, CVAR_USERINFO | CVAR_ARCHIVE ); + trap_Cvar_Register(NULL, "team_model", DEFAULT_TEAM_MODEL, CVAR_USERINFO | CVAR_ARCHIVE ); + trap_Cvar_Register(NULL, "team_headmodel", DEFAULT_TEAM_HEAD, CVAR_USERINFO | CVAR_ARCHIVE ); +} + +/* +=================== +CG_ForceModelChange +=================== +*/ +static void CG_ForceModelChange( void ) { + int i; + + for (i=0 ; ivmCvar ); + } + + // check for modications here + + // If team overlay is on, ask for updates from the server. If its off, + // let the server know so we don't receive it + if ( drawTeamOverlayModificationCount != cg_drawTeamOverlay.modificationCount ) { + drawTeamOverlayModificationCount = cg_drawTeamOverlay.modificationCount; + + if ( cg_drawTeamOverlay.integer > 0 ) { + trap_Cvar_Set( "teamoverlay", "1" ); + } else { + trap_Cvar_Set( "teamoverlay", "0" ); + } + // FIXME E3 HACK + trap_Cvar_Set( "teamoverlay", "1" ); + } + + // if force model changed + if ( forceModelModificationCount != cg_forceModel.modificationCount ) { + forceModelModificationCount = cg_forceModel.modificationCount; + CG_ForceModelChange(); + } +} + +int CG_CrosshairPlayer( void ) { + if ( cg.time > ( cg.crosshairClientTime + 1000 ) ) { + return -1; + } + return cg.crosshairClientNum; +} + +int CG_LastAttacker( void ) { + if ( !cg.attackerTime ) { + return -1; + } + return cg.snap->ps.persistant[PERS_ATTACKER]; +} + +void QDECL CG_Printf( const char *msg, ... ) { + va_list argptr; + char text[1024]; + + va_start (argptr, msg); + vsprintf (text, msg, argptr); + va_end (argptr); + + trap_Print( text ); +} + +void QDECL CG_Error( const char *msg, ... ) { + va_list argptr; + char text[1024]; + + va_start (argptr, msg); + vsprintf (text, msg, argptr); + va_end (argptr); + + trap_Error( text ); +} + +#ifndef CGAME_HARD_LINKED +// this is only here so the functions in q_shared.c and bg_*.c can link (FIXME) + +void QDECL Com_Error( int level, const char *error, ... ) { + va_list argptr; + char text[1024]; + + va_start (argptr, error); + vsprintf (text, error, argptr); + va_end (argptr); + + CG_Error( "%s", text); +} + +void QDECL Com_Printf( const char *msg, ... ) { + va_list argptr; + char text[1024]; + + va_start (argptr, msg); + vsprintf (text, msg, argptr); + va_end (argptr); + + CG_Printf ("%s", text); +} + +#endif + +/* +================ +CG_Argv +================ +*/ +const char *CG_Argv( int arg ) { + static char buffer[MAX_STRING_CHARS]; + + trap_Argv( arg, buffer, sizeof( buffer ) ); + + return buffer; +} + + +//======================================================================== + +/* +================= +CG_RegisterItemSounds + +The server says this item is used on this level +================= +*/ +static void CG_RegisterItemSounds( int itemNum ) { + gitem_t *item; + char data[MAX_QPATH]; + char *s, *start; + int len; + + item = &bg_itemlist[ itemNum ]; + + if( item->pickup_sound ) { + trap_S_RegisterSound( item->pickup_sound, qfalse ); + } + + // parse the space seperated precache string for other media + s = item->sounds; + if (!s || !s[0]) + return; + + while (*s) { + start = s; + while (*s && *s != ' ') { + s++; + } + + len = s-start; + if (len >= MAX_QPATH || len < 5) { + CG_Error( "PrecacheItem: %s has bad precache string", + item->classname); + return; + } + memcpy (data, start, len); + data[len] = 0; + if ( *s ) { + s++; + } + + if ( !strcmp(data+len-3, "wav" )) { + trap_S_RegisterSound( data, qfalse ); + } + } +} + + +/* +================= +CG_RegisterSounds + +called during a precache command +================= +*/ +static void CG_RegisterSounds( void ) { + int i; + char items[MAX_ITEMS+1]; + char name[MAX_QPATH]; + const char *soundName; + + // voice commands +#ifdef MISSIONPACK + CG_LoadVoiceChats(); +#endif + + cgs.media.oneMinuteSound = trap_S_RegisterSound( "sound/feedback/1_minute.wav", qtrue ); + cgs.media.fiveMinuteSound = trap_S_RegisterSound( "sound/feedback/5_minute.wav", qtrue ); + cgs.media.suddenDeathSound = trap_S_RegisterSound( "sound/feedback/sudden_death.wav", qtrue ); + cgs.media.oneFragSound = trap_S_RegisterSound( "sound/feedback/1_frag.wav", qtrue ); + cgs.media.twoFragSound = trap_S_RegisterSound( "sound/feedback/2_frags.wav", qtrue ); + cgs.media.threeFragSound = trap_S_RegisterSound( "sound/feedback/3_frags.wav", qtrue ); + cgs.media.count3Sound = trap_S_RegisterSound( "sound/feedback/three.wav", qtrue ); + cgs.media.count2Sound = trap_S_RegisterSound( "sound/feedback/two.wav", qtrue ); + cgs.media.count1Sound = trap_S_RegisterSound( "sound/feedback/one.wav", qtrue ); + cgs.media.countFightSound = trap_S_RegisterSound( "sound/feedback/fight.wav", qtrue ); + cgs.media.countPrepareSound = trap_S_RegisterSound( "sound/feedback/prepare.wav", qtrue ); +#ifdef MISSIONPACK + cgs.media.countPrepareTeamSound = trap_S_RegisterSound( "sound/feedback/prepare_team.wav", qtrue ); +#endif + + if ( cgs.gametype >= GT_TEAM || cg_buildScript.integer ) { + + cgs.media.captureAwardSound = trap_S_RegisterSound( "sound/teamplay/flagcapture_yourteam.wav", qtrue ); + cgs.media.redLeadsSound = trap_S_RegisterSound( "sound/feedback/redleads.wav", qtrue ); + cgs.media.blueLeadsSound = trap_S_RegisterSound( "sound/feedback/blueleads.wav", qtrue ); + cgs.media.teamsTiedSound = trap_S_RegisterSound( "sound/feedback/teamstied.wav", qtrue ); + cgs.media.hitTeamSound = trap_S_RegisterSound( "sound/feedback/hit_teammate.wav", qtrue ); + + cgs.media.redScoredSound = trap_S_RegisterSound( "sound/teamplay/voc_red_scores.wav", qtrue ); + cgs.media.blueScoredSound = trap_S_RegisterSound( "sound/teamplay/voc_blue_scores.wav", qtrue ); + + cgs.media.captureYourTeamSound = trap_S_RegisterSound( "sound/teamplay/flagcapture_yourteam.wav", qtrue ); + cgs.media.captureOpponentSound = trap_S_RegisterSound( "sound/teamplay/flagcapture_opponent.wav", qtrue ); + + cgs.media.returnYourTeamSound = trap_S_RegisterSound( "sound/teamplay/flagreturn_yourteam.wav", qtrue ); + cgs.media.returnOpponentSound = trap_S_RegisterSound( "sound/teamplay/flagreturn_opponent.wav", qtrue ); + + cgs.media.takenYourTeamSound = trap_S_RegisterSound( "sound/teamplay/flagtaken_yourteam.wav", qtrue ); + cgs.media.takenOpponentSound = trap_S_RegisterSound( "sound/teamplay/flagtaken_opponent.wav", qtrue ); + + if ( cgs.gametype == GT_CTF || cg_buildScript.integer ) { + cgs.media.redFlagReturnedSound = trap_S_RegisterSound( "sound/teamplay/voc_red_returned.wav", qtrue ); + cgs.media.blueFlagReturnedSound = trap_S_RegisterSound( "sound/teamplay/voc_blue_returned.wav", qtrue ); + cgs.media.enemyTookYourFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_enemy_flag.wav", qtrue ); + cgs.media.yourTeamTookEnemyFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_team_flag.wav", qtrue ); + } + +#ifdef MISSIONPACK + if ( cgs.gametype == GT_1FCTF || cg_buildScript.integer ) { + // FIXME: get a replacement for this sound ? + cgs.media.neutralFlagReturnedSound = trap_S_RegisterSound( "sound/teamplay/flagreturn_opponent.wav", qtrue ); + cgs.media.yourTeamTookTheFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_team_1flag.wav", qtrue ); + cgs.media.enemyTookTheFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_enemy_1flag.wav", qtrue ); + } + + if ( cgs.gametype == GT_1FCTF || cgs.gametype == GT_CTF || cg_buildScript.integer ) { + cgs.media.youHaveFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_you_flag.wav", qtrue ); + cgs.media.holyShitSound = trap_S_RegisterSound("sound/feedback/voc_holyshit.wav", qtrue); + } + + if ( cgs.gametype == GT_OBELISK || cg_buildScript.integer ) { + cgs.media.yourBaseIsUnderAttackSound = trap_S_RegisterSound( "sound/teamplay/voc_base_attack.wav", qtrue ); + } +#else + cgs.media.youHaveFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_you_flag.wav", qtrue ); + cgs.media.holyShitSound = trap_S_RegisterSound("sound/feedback/voc_holyshit.wav", qtrue); + cgs.media.neutralFlagReturnedSound = trap_S_RegisterSound( "sound/teamplay/flagreturn_opponent.wav", qtrue ); + cgs.media.yourTeamTookTheFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_team_1flag.wav", qtrue ); + cgs.media.enemyTookTheFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_enemy_1flag.wav", qtrue ); +#endif + } + + cgs.media.tracerSound = trap_S_RegisterSound( "sound/weapons/machinegun/buletby1.wav", qfalse ); + cgs.media.selectSound = trap_S_RegisterSound( "sound/weapons/change.wav", qfalse ); + cgs.media.wearOffSound = trap_S_RegisterSound( "sound/items/wearoff.wav", qfalse ); + cgs.media.useNothingSound = trap_S_RegisterSound( "sound/items/use_nothing.wav", qfalse ); + cgs.media.gibSound = trap_S_RegisterSound( "sound/player/gibsplt1.wav", qfalse ); + cgs.media.gibBounce1Sound = trap_S_RegisterSound( "sound/player/gibimp1.wav", qfalse ); + cgs.media.gibBounce2Sound = trap_S_RegisterSound( "sound/player/gibimp2.wav", qfalse ); + cgs.media.gibBounce3Sound = trap_S_RegisterSound( "sound/player/gibimp3.wav", qfalse ); + +#ifdef MISSIONPACK + cgs.media.useInvulnerabilitySound = trap_S_RegisterSound( "sound/items/invul_activate.wav", qfalse ); + cgs.media.invulnerabilityImpactSound1 = trap_S_RegisterSound( "sound/items/invul_impact_01.wav", qfalse ); + cgs.media.invulnerabilityImpactSound2 = trap_S_RegisterSound( "sound/items/invul_impact_02.wav", qfalse ); + cgs.media.invulnerabilityImpactSound3 = trap_S_RegisterSound( "sound/items/invul_impact_03.wav", qfalse ); + cgs.media.invulnerabilityJuicedSound = trap_S_RegisterSound( "sound/items/invul_juiced.wav", qfalse ); + cgs.media.obeliskHitSound1 = trap_S_RegisterSound( "sound/items/obelisk_hit_01.wav", qfalse ); + cgs.media.obeliskHitSound2 = trap_S_RegisterSound( "sound/items/obelisk_hit_02.wav", qfalse ); + cgs.media.obeliskHitSound3 = trap_S_RegisterSound( "sound/items/obelisk_hit_03.wav", qfalse ); + cgs.media.obeliskRespawnSound = trap_S_RegisterSound( "sound/items/obelisk_respawn.wav", qfalse ); + + cgs.media.ammoregenSound = trap_S_RegisterSound("sound/items/cl_ammoregen.wav", qfalse); + cgs.media.doublerSound = trap_S_RegisterSound("sound/items/cl_doubler.wav", qfalse); + cgs.media.guardSound = trap_S_RegisterSound("sound/items/cl_guard.wav", qfalse); + cgs.media.scoutSound = trap_S_RegisterSound("sound/items/cl_scout.wav", qfalse); +#endif + + cgs.media.teleInSound = trap_S_RegisterSound( "sound/world/telein.wav", qfalse ); + cgs.media.teleOutSound = trap_S_RegisterSound( "sound/world/teleout.wav", qfalse ); + cgs.media.respawnSound = trap_S_RegisterSound( "sound/items/respawn1.wav", qfalse ); + + cgs.media.noAmmoSound = trap_S_RegisterSound( "sound/weapons/noammo.wav", qfalse ); + + cgs.media.talkSound = trap_S_RegisterSound( "sound/player/talk.wav", qfalse ); + cgs.media.landSound = trap_S_RegisterSound( "sound/player/land1.wav", qfalse); + + cgs.media.hitSound = trap_S_RegisterSound( "sound/feedback/hit.wav", qfalse ); +#ifdef MISSIONPACK + cgs.media.hitSoundHighArmor = trap_S_RegisterSound( "sound/feedback/hithi.wav", qfalse ); + cgs.media.hitSoundLowArmor = trap_S_RegisterSound( "sound/feedback/hitlo.wav", qfalse ); +#endif + + cgs.media.impressiveSound = trap_S_RegisterSound( "sound/feedback/impressive.wav", qtrue ); + cgs.media.excellentSound = trap_S_RegisterSound( "sound/feedback/excellent.wav", qtrue ); + cgs.media.deniedSound = trap_S_RegisterSound( "sound/feedback/denied.wav", qtrue ); + cgs.media.humiliationSound = trap_S_RegisterSound( "sound/feedback/humiliation.wav", qtrue ); + cgs.media.assistSound = trap_S_RegisterSound( "sound/feedback/assist.wav", qtrue ); + cgs.media.defendSound = trap_S_RegisterSound( "sound/feedback/defense.wav", qtrue ); +#ifdef MISSIONPACK + cgs.media.firstImpressiveSound = trap_S_RegisterSound( "sound/feedback/first_impressive.wav", qtrue ); + cgs.media.firstExcellentSound = trap_S_RegisterSound( "sound/feedback/first_excellent.wav", qtrue ); + cgs.media.firstHumiliationSound = trap_S_RegisterSound( "sound/feedback/first_gauntlet.wav", qtrue ); +#endif + + cgs.media.takenLeadSound = trap_S_RegisterSound( "sound/feedback/takenlead.wav", qtrue); + cgs.media.tiedLeadSound = trap_S_RegisterSound( "sound/feedback/tiedlead.wav", qtrue); + cgs.media.lostLeadSound = trap_S_RegisterSound( "sound/feedback/lostlead.wav", qtrue); + +#ifdef MISSIONPACK + cgs.media.voteNow = trap_S_RegisterSound( "sound/feedback/vote_now.wav", qtrue); + cgs.media.votePassed = trap_S_RegisterSound( "sound/feedback/vote_passed.wav", qtrue); + cgs.media.voteFailed = trap_S_RegisterSound( "sound/feedback/vote_failed.wav", qtrue); +#endif + + cgs.media.watrInSound = trap_S_RegisterSound( "sound/player/watr_in.wav", qfalse); + cgs.media.watrOutSound = trap_S_RegisterSound( "sound/player/watr_out.wav", qfalse); + cgs.media.watrUnSound = trap_S_RegisterSound( "sound/player/watr_un.wav", qfalse); + + cgs.media.jumpPadSound = trap_S_RegisterSound ("sound/world/jumppad.wav", qfalse ); + + for (i=0 ; i<4 ; i++) { + Com_sprintf (name, sizeof(name), "sound/player/footsteps/step%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_NORMAL][i] = trap_S_RegisterSound (name, qfalse); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/boot%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_BOOT][i] = trap_S_RegisterSound (name, qfalse); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/flesh%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_FLESH][i] = trap_S_RegisterSound (name, qfalse); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/mech%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_MECH][i] = trap_S_RegisterSound (name, qfalse); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/energy%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_ENERGY][i] = trap_S_RegisterSound (name, qfalse); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/splash%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_SPLASH][i] = trap_S_RegisterSound (name, qfalse); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/clank%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_METAL][i] = trap_S_RegisterSound (name, qfalse); + } + + // only register the items that the server says we need + strcpy( items, CG_ConfigString( CS_ITEMS ) ); + + for ( i = 1 ; i < bg_numItems ; i++ ) { +// if ( items[ i ] == '1' || cg_buildScript.integer ) { + CG_RegisterItemSounds( i ); +// } + } + + for ( i = 1 ; i < MAX_SOUNDS ; i++ ) { + soundName = CG_ConfigString( CS_SOUNDS+i ); + if ( !soundName[0] ) { + break; + } + if ( soundName[0] == '*' ) { + continue; // custom sound + } + cgs.gameSounds[i] = trap_S_RegisterSound( soundName, qfalse ); + } + + // FIXME: only needed with item + cgs.media.flightSound = trap_S_RegisterSound( "sound/items/flight.wav", qfalse ); + cgs.media.medkitSound = trap_S_RegisterSound ("sound/items/use_medkit.wav", qfalse); + cgs.media.quadSound = trap_S_RegisterSound("sound/items/damage3.wav", qfalse); + cgs.media.sfx_ric1 = trap_S_RegisterSound ("sound/weapons/machinegun/ric1.wav", qfalse); + cgs.media.sfx_ric2 = trap_S_RegisterSound ("sound/weapons/machinegun/ric2.wav", qfalse); + cgs.media.sfx_ric3 = trap_S_RegisterSound ("sound/weapons/machinegun/ric3.wav", qfalse); + cgs.media.sfx_railg = trap_S_RegisterSound ("sound/weapons/railgun/railgf1a.wav", qfalse); + cgs.media.sfx_rockexp = trap_S_RegisterSound ("sound/weapons/rocket/rocklx1a.wav", qfalse); + cgs.media.sfx_plasmaexp = trap_S_RegisterSound ("sound/weapons/plasma/plasmx1a.wav", qfalse); +#ifdef MISSIONPACK + cgs.media.sfx_proxexp = trap_S_RegisterSound( "sound/weapons/proxmine/wstbexpl.wav" , qfalse); + cgs.media.sfx_nghit = trap_S_RegisterSound( "sound/weapons/nailgun/wnalimpd.wav" , qfalse); + cgs.media.sfx_nghitflesh = trap_S_RegisterSound( "sound/weapons/nailgun/wnalimpl.wav" , qfalse); + cgs.media.sfx_nghitmetal = trap_S_RegisterSound( "sound/weapons/nailgun/wnalimpm.wav", qfalse ); + cgs.media.sfx_chghit = trap_S_RegisterSound( "sound/weapons/vulcan/wvulimpd.wav", qfalse ); + cgs.media.sfx_chghitflesh = trap_S_RegisterSound( "sound/weapons/vulcan/wvulimpl.wav", qfalse ); + cgs.media.sfx_chghitmetal = trap_S_RegisterSound( "sound/weapons/vulcan/wvulimpm.wav", qfalse ); + cgs.media.weaponHoverSound = trap_S_RegisterSound( "sound/weapons/weapon_hover.wav", qfalse ); + cgs.media.kamikazeExplodeSound = trap_S_RegisterSound( "sound/items/kam_explode.wav", qfalse ); + cgs.media.kamikazeImplodeSound = trap_S_RegisterSound( "sound/items/kam_implode.wav", qfalse ); + cgs.media.kamikazeFarSound = trap_S_RegisterSound( "sound/items/kam_explode_far.wav", qfalse ); + cgs.media.winnerSound = trap_S_RegisterSound( "sound/feedback/voc_youwin.wav", qfalse ); + cgs.media.loserSound = trap_S_RegisterSound( "sound/feedback/voc_youlose.wav", qfalse ); + cgs.media.youSuckSound = trap_S_RegisterSound( "sound/misc/yousuck.wav", qfalse ); + + cgs.media.wstbimplSound = trap_S_RegisterSound("sound/weapons/proxmine/wstbimpl.wav", qfalse); + cgs.media.wstbimpmSound = trap_S_RegisterSound("sound/weapons/proxmine/wstbimpm.wav", qfalse); + cgs.media.wstbimpdSound = trap_S_RegisterSound("sound/weapons/proxmine/wstbimpd.wav", qfalse); + cgs.media.wstbactvSound = trap_S_RegisterSound("sound/weapons/proxmine/wstbactv.wav", qfalse); +#endif + + cgs.media.regenSound = trap_S_RegisterSound("sound/items/regen.wav", qfalse); + cgs.media.protectSound = trap_S_RegisterSound("sound/items/protect3.wav", qfalse); + cgs.media.n_healthSound = trap_S_RegisterSound("sound/items/n_health.wav", qfalse ); + cgs.media.hgrenb1aSound = trap_S_RegisterSound("sound/weapons/grenade/hgrenb1a.wav", qfalse); + cgs.media.hgrenb2aSound = trap_S_RegisterSound("sound/weapons/grenade/hgrenb2a.wav", qfalse); + +#ifdef MISSIONPACK + trap_S_RegisterSound("sound/player/james/death1.wav", qfalse ); + trap_S_RegisterSound("sound/player/james/death2.wav", qfalse ); + trap_S_RegisterSound("sound/player/james/death3.wav", qfalse ); + trap_S_RegisterSound("sound/player/james/jump1.wav", qfalse ); + trap_S_RegisterSound("sound/player/james/pain25_1.wav", qfalse ); + trap_S_RegisterSound("sound/player/james/pain75_1.wav", qfalse ); + trap_S_RegisterSound("sound/player/james/pain100_1.wav", qfalse ); + trap_S_RegisterSound("sound/player/james/falling1.wav", qfalse ); + trap_S_RegisterSound("sound/player/james/gasp.wav", qfalse ); + trap_S_RegisterSound("sound/player/james/drown.wav", qfalse ); + trap_S_RegisterSound("sound/player/james/fall1.wav", qfalse ); + trap_S_RegisterSound("sound/player/james/taunt.wav", qfalse ); + + trap_S_RegisterSound("sound/player/janet/death1.wav", qfalse ); + trap_S_RegisterSound("sound/player/janet/death2.wav", qfalse ); + trap_S_RegisterSound("sound/player/janet/death3.wav", qfalse ); + trap_S_RegisterSound("sound/player/janet/jump1.wav", qfalse ); + trap_S_RegisterSound("sound/player/janet/pain25_1.wav", qfalse ); + trap_S_RegisterSound("sound/player/janet/pain75_1.wav", qfalse ); + trap_S_RegisterSound("sound/player/janet/pain100_1.wav", qfalse ); + trap_S_RegisterSound("sound/player/janet/falling1.wav", qfalse ); + trap_S_RegisterSound("sound/player/janet/gasp.wav", qfalse ); + trap_S_RegisterSound("sound/player/janet/drown.wav", qfalse ); + trap_S_RegisterSound("sound/player/janet/fall1.wav", qfalse ); + trap_S_RegisterSound("sound/player/janet/taunt.wav", qfalse ); +#endif + +} + + +//=================================================================================== + + +/* +================= +CG_RegisterGraphics + +This function may execute for a couple of minutes with a slow disk. +================= +*/ +static void CG_RegisterGraphics( void ) { + int i; + char items[MAX_ITEMS+1]; + static char *sb_nums[11] = { + "gfx/2d/numbers/zero_32b", + "gfx/2d/numbers/one_32b", + "gfx/2d/numbers/two_32b", + "gfx/2d/numbers/three_32b", + "gfx/2d/numbers/four_32b", + "gfx/2d/numbers/five_32b", + "gfx/2d/numbers/six_32b", + "gfx/2d/numbers/seven_32b", + "gfx/2d/numbers/eight_32b", + "gfx/2d/numbers/nine_32b", + "gfx/2d/numbers/minus_32b", + }; + + // clear any references to old media + memset( &cg.refdef, 0, sizeof( cg.refdef ) ); + trap_R_ClearScene(); + + CG_LoadingString( cgs.mapname ); + + trap_R_LoadWorldMap( cgs.mapname ); + + // precache status bar pics + CG_LoadingString( "game media" ); + + for ( i=0 ; i<11 ; i++) { + cgs.media.numberShaders[i] = trap_R_RegisterShader( sb_nums[i] ); + } + + cgs.media.botSkillShaders[0] = trap_R_RegisterShader( "menu/art/skill1.tga" ); + cgs.media.botSkillShaders[1] = trap_R_RegisterShader( "menu/art/skill2.tga" ); + cgs.media.botSkillShaders[2] = trap_R_RegisterShader( "menu/art/skill3.tga" ); + cgs.media.botSkillShaders[3] = trap_R_RegisterShader( "menu/art/skill4.tga" ); + cgs.media.botSkillShaders[4] = trap_R_RegisterShader( "menu/art/skill5.tga" ); + + cgs.media.viewBloodShader = trap_R_RegisterShader( "viewBloodBlend" ); + + cgs.media.deferShader = trap_R_RegisterShaderNoMip( "gfx/2d/defer.tga" ); + + cgs.media.scoreboardName = trap_R_RegisterShaderNoMip( "menu/tab/name.tga" ); + cgs.media.scoreboardPing = trap_R_RegisterShaderNoMip( "menu/tab/ping.tga" ); + cgs.media.scoreboardScore = trap_R_RegisterShaderNoMip( "menu/tab/score.tga" ); + cgs.media.scoreboardTime = trap_R_RegisterShaderNoMip( "menu/tab/time.tga" ); + + cgs.media.smokePuffShader = trap_R_RegisterShader( "smokePuff" ); + cgs.media.smokePuffRageProShader = trap_R_RegisterShader( "smokePuffRagePro" ); + cgs.media.shotgunSmokePuffShader = trap_R_RegisterShader( "shotgunSmokePuff" ); +#ifdef MISSIONPACK + cgs.media.nailPuffShader = trap_R_RegisterShader( "nailtrail" ); + cgs.media.blueProxMine = trap_R_RegisterModel( "models/weaphits/proxmineb.md3" ); +#endif + cgs.media.plasmaBallShader = trap_R_RegisterShader( "sprites/plasma1" ); + cgs.media.bloodTrailShader = trap_R_RegisterShader( "bloodTrail" ); + cgs.media.lagometerShader = trap_R_RegisterShader("lagometer" ); + cgs.media.connectionShader = trap_R_RegisterShader( "disconnected" ); + + cgs.media.waterBubbleShader = trap_R_RegisterShader( "waterBubble" ); + + cgs.media.tracerShader = trap_R_RegisterShader( "gfx/misc/tracer" ); + cgs.media.selectShader = trap_R_RegisterShader( "gfx/2d/select" ); + + for ( i = 0 ; i < NUM_CROSSHAIRS ; i++ ) { + cgs.media.crosshairShader[i] = trap_R_RegisterShader( va("gfx/2d/crosshair%c", 'a'+i) ); + } + + cgs.media.backTileShader = trap_R_RegisterShader( "gfx/2d/backtile" ); + cgs.media.noammoShader = trap_R_RegisterShader( "icons/noammo" ); + + // powerup shaders + cgs.media.quadShader = trap_R_RegisterShader("powerups/quad" ); + cgs.media.quadWeaponShader = trap_R_RegisterShader("powerups/quadWeapon" ); + cgs.media.battleSuitShader = trap_R_RegisterShader("powerups/battleSuit" ); + cgs.media.battleWeaponShader = trap_R_RegisterShader("powerups/battleWeapon" ); + cgs.media.invisShader = trap_R_RegisterShader("powerups/invisibility" ); + cgs.media.regenShader = trap_R_RegisterShader("powerups/regen" ); + cgs.media.hastePuffShader = trap_R_RegisterShader("hasteSmokePuff" ); + +#ifdef MISSIONPACK + if ( cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF || cgs.gametype == GT_HARVESTER || cg_buildScript.integer ) { +#else + if ( cgs.gametype == GT_CTF || cg_buildScript.integer ) { +#endif + cgs.media.redCubeModel = trap_R_RegisterModel( "models/powerups/orb/r_orb.md3" ); + cgs.media.blueCubeModel = trap_R_RegisterModel( "models/powerups/orb/b_orb.md3" ); + cgs.media.redCubeIcon = trap_R_RegisterShader( "icons/skull_red" ); + cgs.media.blueCubeIcon = trap_R_RegisterShader( "icons/skull_blue" ); + } + +#ifdef MISSIONPACK + if ( cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF || cgs.gametype == GT_HARVESTER || cg_buildScript.integer ) { +#else + if ( cgs.gametype == GT_CTF || cg_buildScript.integer ) { +#endif + cgs.media.redFlagModel = trap_R_RegisterModel( "models/flags/r_flag.md3" ); + cgs.media.blueFlagModel = trap_R_RegisterModel( "models/flags/b_flag.md3" ); + cgs.media.redFlagShader[0] = trap_R_RegisterShaderNoMip( "icons/iconf_red1" ); + cgs.media.redFlagShader[1] = trap_R_RegisterShaderNoMip( "icons/iconf_red2" ); + cgs.media.redFlagShader[2] = trap_R_RegisterShaderNoMip( "icons/iconf_red3" ); + cgs.media.blueFlagShader[0] = trap_R_RegisterShaderNoMip( "icons/iconf_blu1" ); + cgs.media.blueFlagShader[1] = trap_R_RegisterShaderNoMip( "icons/iconf_blu2" ); + cgs.media.blueFlagShader[2] = trap_R_RegisterShaderNoMip( "icons/iconf_blu3" ); +#ifdef MISSIONPACK + cgs.media.flagPoleModel = trap_R_RegisterModel( "models/flag2/flagpole.md3" ); + cgs.media.flagFlapModel = trap_R_RegisterModel( "models/flag2/flagflap3.md3" ); + + cgs.media.redFlagFlapSkin = trap_R_RegisterSkin( "models/flag2/red.skin" ); + cgs.media.blueFlagFlapSkin = trap_R_RegisterSkin( "models/flag2/blue.skin" ); + cgs.media.neutralFlagFlapSkin = trap_R_RegisterSkin( "models/flag2/white.skin" ); + + cgs.media.redFlagBaseModel = trap_R_RegisterModel( "models/mapobjects/flagbase/red_base.md3" ); + cgs.media.blueFlagBaseModel = trap_R_RegisterModel( "models/mapobjects/flagbase/blue_base.md3" ); + cgs.media.neutralFlagBaseModel = trap_R_RegisterModel( "models/mapobjects/flagbase/ntrl_base.md3" ); +#endif + } + +#ifdef MISSIONPACK + if ( cgs.gametype == GT_1FCTF || cg_buildScript.integer ) { + cgs.media.neutralFlagModel = trap_R_RegisterModel( "models/flags/n_flag.md3" ); + cgs.media.flagShader[0] = trap_R_RegisterShaderNoMip( "icons/iconf_neutral1" ); + cgs.media.flagShader[1] = trap_R_RegisterShaderNoMip( "icons/iconf_red2" ); + cgs.media.flagShader[2] = trap_R_RegisterShaderNoMip( "icons/iconf_blu2" ); + cgs.media.flagShader[3] = trap_R_RegisterShaderNoMip( "icons/iconf_neutral3" ); + } + + if ( cgs.gametype == GT_OBELISK || cg_buildScript.integer ) { + cgs.media.overloadBaseModel = trap_R_RegisterModel( "models/powerups/overload_base.md3" ); + cgs.media.overloadTargetModel = trap_R_RegisterModel( "models/powerups/overload_target.md3" ); + cgs.media.overloadLightsModel = trap_R_RegisterModel( "models/powerups/overload_lights.md3" ); + cgs.media.overloadEnergyModel = trap_R_RegisterModel( "models/powerups/overload_energy.md3" ); + } + + if ( cgs.gametype == GT_HARVESTER || cg_buildScript.integer ) { + cgs.media.harvesterModel = trap_R_RegisterModel( "models/powerups/harvester/harvester.md3" ); + cgs.media.harvesterRedSkin = trap_R_RegisterSkin( "models/powerups/harvester/red.skin" ); + cgs.media.harvesterBlueSkin = trap_R_RegisterSkin( "models/powerups/harvester/blue.skin" ); + cgs.media.harvesterNeutralModel = trap_R_RegisterModel( "models/powerups/obelisk/obelisk.md3" ); + } + + cgs.media.redKamikazeShader = trap_R_RegisterShader( "models/weaphits/kamikred" ); + cgs.media.dustPuffShader = trap_R_RegisterShader("hasteSmokePuff" ); +#endif + + if ( cgs.gametype >= GT_TEAM || cg_buildScript.integer ) { + cgs.media.friendShader = trap_R_RegisterShader( "sprites/foe" ); + cgs.media.redQuadShader = trap_R_RegisterShader("powerups/blueflag" ); + cgs.media.teamStatusBar = trap_R_RegisterShader( "gfx/2d/colorbar.tga" ); +#ifdef MISSIONPACK + cgs.media.blueKamikazeShader = trap_R_RegisterShader( "models/weaphits/kamikblu" ); +#endif + } + + cgs.media.armorModel = trap_R_RegisterModel( "models/powerups/armor/armor_yel.md3" ); + cgs.media.armorIcon = trap_R_RegisterShaderNoMip( "icons/iconr_yellow" ); + + cgs.media.machinegunBrassModel = trap_R_RegisterModel( "models/weapons2/shells/m_shell.md3" ); + cgs.media.shotgunBrassModel = trap_R_RegisterModel( "models/weapons2/shells/s_shell.md3" ); + + cgs.media.gibAbdomen = trap_R_RegisterModel( "models/gibs/abdomen.md3" ); + cgs.media.gibArm = trap_R_RegisterModel( "models/gibs/arm.md3" ); + cgs.media.gibChest = trap_R_RegisterModel( "models/gibs/chest.md3" ); + cgs.media.gibFist = trap_R_RegisterModel( "models/gibs/fist.md3" ); + cgs.media.gibFoot = trap_R_RegisterModel( "models/gibs/foot.md3" ); + cgs.media.gibForearm = trap_R_RegisterModel( "models/gibs/forearm.md3" ); + cgs.media.gibIntestine = trap_R_RegisterModel( "models/gibs/intestine.md3" ); + cgs.media.gibLeg = trap_R_RegisterModel( "models/gibs/leg.md3" ); + cgs.media.gibSkull = trap_R_RegisterModel( "models/gibs/skull.md3" ); + cgs.media.gibBrain = trap_R_RegisterModel( "models/gibs/brain.md3" ); + + cgs.media.smoke2 = trap_R_RegisterModel( "models/weapons2/shells/s_shell.md3" ); + + cgs.media.balloonShader = trap_R_RegisterShader( "sprites/balloon3" ); + + cgs.media.bloodExplosionShader = trap_R_RegisterShader( "bloodExplosion" ); + + cgs.media.bulletFlashModel = trap_R_RegisterModel("models/weaphits/bullet.md3"); + cgs.media.ringFlashModel = trap_R_RegisterModel("models/weaphits/ring02.md3"); + cgs.media.dishFlashModel = trap_R_RegisterModel("models/weaphits/boom01.md3"); +#ifdef MISSIONPACK + cgs.media.teleportEffectModel = trap_R_RegisterModel( "models/powerups/pop.md3" ); +#else + cgs.media.teleportEffectModel = trap_R_RegisterModel( "models/misc/telep.md3" ); + cgs.media.teleportEffectShader = trap_R_RegisterShader( "teleportEffect" ); +#endif +#ifdef MISSIONPACK + cgs.media.kamikazeEffectModel = trap_R_RegisterModel( "models/weaphits/kamboom2.md3" ); + cgs.media.kamikazeShockWave = trap_R_RegisterModel( "models/weaphits/kamwave.md3" ); + cgs.media.kamikazeHeadModel = trap_R_RegisterModel( "models/powerups/kamikazi.md3" ); + cgs.media.kamikazeHeadTrail = trap_R_RegisterModel( "models/powerups/trailtest.md3" ); + cgs.media.guardPowerupModel = trap_R_RegisterModel( "models/powerups/guard_player.md3" ); + cgs.media.scoutPowerupModel = trap_R_RegisterModel( "models/powerups/scout_player.md3" ); + cgs.media.doublerPowerupModel = trap_R_RegisterModel( "models/powerups/doubler_player.md3" ); + cgs.media.ammoRegenPowerupModel = trap_R_RegisterModel( "models/powerups/ammo_player.md3" ); + cgs.media.invulnerabilityImpactModel = trap_R_RegisterModel( "models/powerups/shield/impact.md3" ); + cgs.media.invulnerabilityJuicedModel = trap_R_RegisterModel( "models/powerups/shield/juicer.md3" ); + cgs.media.medkitUsageModel = trap_R_RegisterModel( "models/powerups/regen.md3" ); + cgs.media.heartShader = trap_R_RegisterShaderNoMip( "ui/assets/statusbar/selectedhealth.tga" ); + +#endif + + cgs.media.invulnerabilityPowerupModel = trap_R_RegisterModel( "models/powerups/shield/shield.md3" ); + cgs.media.medalImpressive = trap_R_RegisterShaderNoMip( "medal_impressive" ); + cgs.media.medalExcellent = trap_R_RegisterShaderNoMip( "medal_excellent" ); + cgs.media.medalGauntlet = trap_R_RegisterShaderNoMip( "medal_gauntlet" ); + cgs.media.medalDefend = trap_R_RegisterShaderNoMip( "medal_defend" ); + cgs.media.medalAssist = trap_R_RegisterShaderNoMip( "medal_assist" ); + cgs.media.medalCapture = trap_R_RegisterShaderNoMip( "medal_capture" ); + + + memset( cg_items, 0, sizeof( cg_items ) ); + memset( cg_weapons, 0, sizeof( cg_weapons ) ); + + // only register the items that the server says we need + strcpy( items, CG_ConfigString( CS_ITEMS) ); + + for ( i = 1 ; i < bg_numItems ; i++ ) { + if ( items[ i ] == '1' || cg_buildScript.integer ) { + CG_LoadingItem( i ); + CG_RegisterItemVisuals( i ); + } + } + + // wall marks + cgs.media.bulletMarkShader = trap_R_RegisterShader( "gfx/damage/bullet_mrk" ); + cgs.media.burnMarkShader = trap_R_RegisterShader( "gfx/damage/burn_med_mrk" ); + cgs.media.holeMarkShader = trap_R_RegisterShader( "gfx/damage/hole_lg_mrk" ); + cgs.media.energyMarkShader = trap_R_RegisterShader( "gfx/damage/plasma_mrk" ); + cgs.media.shadowMarkShader = trap_R_RegisterShader( "markShadow" ); + cgs.media.wakeMarkShader = trap_R_RegisterShader( "wake" ); + cgs.media.bloodMarkShader = trap_R_RegisterShader( "bloodMark" ); + + // register the inline models + cgs.numInlineModels = trap_CM_NumInlineModels(); + for ( i = 1 ; i < cgs.numInlineModels ; i++ ) { + char name[10]; + vec3_t mins, maxs; + int j; + + Com_sprintf( name, sizeof(name), "*%i", i ); + cgs.inlineDrawModel[i] = trap_R_RegisterModel( name ); + trap_R_ModelBounds( cgs.inlineDrawModel[i], mins, maxs ); + for ( j = 0 ; j < 3 ; j++ ) { + cgs.inlineModelMidpoints[i][j] = mins[j] + 0.5 * ( maxs[j] - mins[j] ); + } + } + + // register all the server specified models + for (i=1 ; i= MAX_CONFIGSTRINGS ) { + CG_Error( "CG_ConfigString: bad index: %i", index ); + } + return cgs.gameState.stringData + cgs.gameState.stringOffsets[ index ]; +} + +//================================================================== + +/* +====================== +CG_StartMusic + +====================== +*/ +void CG_StartMusic( void ) { + char *s; + char parm1[MAX_QPATH], parm2[MAX_QPATH]; + + // start the background music + s = (char *)CG_ConfigString( CS_MUSIC ); + Q_strncpyz( parm1, COM_Parse( &s ), sizeof( parm1 ) ); + Q_strncpyz( parm2, COM_Parse( &s ), sizeof( parm2 ) ); + + trap_S_StartBackgroundTrack( parm1, parm2 ); +} +#ifdef MISSIONPACK +char *CG_GetMenuBuffer(const char *filename) { + int len; + fileHandle_t f; + static char buf[MAX_MENUFILE]; + + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( !f ) { + trap_Print( va( S_COLOR_RED "menu file not found: %s, using default\n", filename ) ); + return NULL; + } + if ( len >= MAX_MENUFILE ) { + trap_Print( va( S_COLOR_RED "menu file too large: %s is %i, max allowed is %i", filename, len, MAX_MENUFILE ) ); + trap_FS_FCloseFile( f ); + return NULL; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + + return buf; +} + +// +// ============================== +// new hud stuff ( mission pack ) +// ============================== +// +qboolean CG_Asset_Parse(int handle) { + pc_token_t token; + const char *tempStr; + + if (!trap_PC_ReadToken(handle, &token)) + return qfalse; + if (Q_stricmp(token.string, "{") != 0) { + return qfalse; + } + + while ( 1 ) { + if (!trap_PC_ReadToken(handle, &token)) + return qfalse; + + if (Q_stricmp(token.string, "}") == 0) { + return qtrue; + } + + // font + if (Q_stricmp(token.string, "font") == 0) { + int pointSize; + if (!PC_String_Parse(handle, &tempStr) || !PC_Int_Parse(handle, &pointSize)) { + return qfalse; + } + cgDC.registerFont(tempStr, pointSize, &cgDC.Assets.textFont); + continue; + } + + // smallFont + if (Q_stricmp(token.string, "smallFont") == 0) { + int pointSize; + if (!PC_String_Parse(handle, &tempStr) || !PC_Int_Parse(handle, &pointSize)) { + return qfalse; + } + cgDC.registerFont(tempStr, pointSize, &cgDC.Assets.smallFont); + continue; + } + + // font + if (Q_stricmp(token.string, "bigfont") == 0) { + int pointSize; + if (!PC_String_Parse(handle, &tempStr) || !PC_Int_Parse(handle, &pointSize)) { + return qfalse; + } + cgDC.registerFont(tempStr, pointSize, &cgDC.Assets.bigFont); + continue; + } + + // gradientbar + if (Q_stricmp(token.string, "gradientbar") == 0) { + if (!PC_String_Parse(handle, &tempStr)) { + return qfalse; + } + cgDC.Assets.gradientBar = trap_R_RegisterShaderNoMip(tempStr); + continue; + } + + // enterMenuSound + if (Q_stricmp(token.string, "menuEnterSound") == 0) { + if (!PC_String_Parse(handle, &tempStr)) { + return qfalse; + } + cgDC.Assets.menuEnterSound = trap_S_RegisterSound( tempStr, qfalse ); + continue; + } + + // exitMenuSound + if (Q_stricmp(token.string, "menuExitSound") == 0) { + if (!PC_String_Parse(handle, &tempStr)) { + return qfalse; + } + cgDC.Assets.menuExitSound = trap_S_RegisterSound( tempStr, qfalse ); + continue; + } + + // itemFocusSound + if (Q_stricmp(token.string, "itemFocusSound") == 0) { + if (!PC_String_Parse(handle, &tempStr)) { + return qfalse; + } + cgDC.Assets.itemFocusSound = trap_S_RegisterSound( tempStr, qfalse ); + continue; + } + + // menuBuzzSound + if (Q_stricmp(token.string, "menuBuzzSound") == 0) { + if (!PC_String_Parse(handle, &tempStr)) { + return qfalse; + } + cgDC.Assets.menuBuzzSound = trap_S_RegisterSound( tempStr, qfalse ); + continue; + } + + if (Q_stricmp(token.string, "cursor") == 0) { + if (!PC_String_Parse(handle, &cgDC.Assets.cursorStr)) { + return qfalse; + } + cgDC.Assets.cursor = trap_R_RegisterShaderNoMip( cgDC.Assets.cursorStr); + continue; + } + + if (Q_stricmp(token.string, "fadeClamp") == 0) { + if (!PC_Float_Parse(handle, &cgDC.Assets.fadeClamp)) { + return qfalse; + } + continue; + } + + if (Q_stricmp(token.string, "fadeCycle") == 0) { + if (!PC_Int_Parse(handle, &cgDC.Assets.fadeCycle)) { + return qfalse; + } + continue; + } + + if (Q_stricmp(token.string, "fadeAmount") == 0) { + if (!PC_Float_Parse(handle, &cgDC.Assets.fadeAmount)) { + return qfalse; + } + continue; + } + + if (Q_stricmp(token.string, "shadowX") == 0) { + if (!PC_Float_Parse(handle, &cgDC.Assets.shadowX)) { + return qfalse; + } + continue; + } + + if (Q_stricmp(token.string, "shadowY") == 0) { + if (!PC_Float_Parse(handle, &cgDC.Assets.shadowY)) { + return qfalse; + } + continue; + } + + if (Q_stricmp(token.string, "shadowColor") == 0) { + if (!PC_Color_Parse(handle, &cgDC.Assets.shadowColor)) { + return qfalse; + } + cgDC.Assets.shadowFadeClamp = cgDC.Assets.shadowColor[3]; + continue; + } + } + return qfalse; // bk001204 - why not? +} + +void CG_ParseMenu(const char *menuFile) { + pc_token_t token; + int handle; + + handle = trap_PC_LoadSource(menuFile); + if (!handle) + handle = trap_PC_LoadSource("ui/testhud.menu"); + if (!handle) + return; + + while ( 1 ) { + if (!trap_PC_ReadToken( handle, &token )) { + break; + } + + //if ( Q_stricmp( token, "{" ) ) { + // Com_Printf( "Missing { in menu file\n" ); + // break; + //} + + //if ( menuCount == MAX_MENUS ) { + // Com_Printf( "Too many menus!\n" ); + // break; + //} + + if ( token.string[0] == '}' ) { + break; + } + + if (Q_stricmp(token.string, "assetGlobalDef") == 0) { + if (CG_Asset_Parse(handle)) { + continue; + } else { + break; + } + } + + + if (Q_stricmp(token.string, "menudef") == 0) { + // start a new menu + Menu_New(handle); + } + } + trap_PC_FreeSource(handle); +} + +qboolean CG_Load_Menu(char **p) { + char *token; + + token = COM_ParseExt(p, qtrue); + + if (token[0] != '{') { + return qfalse; + } + + while ( 1 ) { + + token = COM_ParseExt(p, qtrue); + + if (Q_stricmp(token, "}") == 0) { + return qtrue; + } + + if ( !token || token[0] == 0 ) { + return qfalse; + } + + CG_ParseMenu(token); + } + return qfalse; +} + + + +void CG_LoadMenus(const char *menuFile) { + char *token; + char *p; + int len, start; + fileHandle_t f; + static char buf[MAX_MENUDEFFILE]; + + start = trap_Milliseconds(); + + len = trap_FS_FOpenFile( menuFile, &f, FS_READ ); + if ( !f ) { + trap_Error( va( S_COLOR_YELLOW "menu file not found: %s, using default\n", menuFile ) ); + len = trap_FS_FOpenFile( "ui/hud.txt", &f, FS_READ ); + if (!f) { + trap_Error( va( S_COLOR_RED "default menu file not found: ui/hud.txt, unable to continue!\n", menuFile ) ); + } + } + + if ( len >= MAX_MENUDEFFILE ) { + trap_Error( va( S_COLOR_RED "menu file too large: %s is %i, max allowed is %i", menuFile, len, MAX_MENUDEFFILE ) ); + trap_FS_FCloseFile( f ); + return; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + + COM_Compress(buf); + + Menu_Reset(); + + p = buf; + + while ( 1 ) { + token = COM_ParseExt( &p, qtrue ); + if( !token || token[0] == 0 || token[0] == '}') { + 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 ( Q_stricmp( token, "}" ) == 0 ) { + break; + } + + if (Q_stricmp(token, "loadmenu") == 0) { + if (CG_Load_Menu(&p)) { + continue; + } else { + break; + } + } + } + + Com_Printf("UI menu load time = %d milli seconds\n", trap_Milliseconds() - start); + +} + + + +static qboolean CG_OwnerDrawHandleKey(int ownerDraw, int flags, float *special, int key) { + return qfalse; +} + + +static int CG_FeederCount(float feederID) { + int i, count; + count = 0; + if (feederID == FEEDER_REDTEAM_LIST) { + for (i = 0; i < cg.numScores; i++) { + if (cg.scores[i].team == TEAM_RED) { + count++; + } + } + } else if (feederID == FEEDER_BLUETEAM_LIST) { + for (i = 0; i < cg.numScores; i++) { + if (cg.scores[i].team == TEAM_BLUE) { + count++; + } + } + } else if (feederID == FEEDER_SCOREBOARD) { + return cg.numScores; + } + return count; +} + + +void CG_SetScoreSelection(void *p) { + menuDef_t *menu = (menuDef_t*)p; + playerState_t *ps = &cg.snap->ps; + int i, red, blue; + red = blue = 0; + for (i = 0; i < cg.numScores; i++) { + if (cg.scores[i].team == TEAM_RED) { + red++; + } else if (cg.scores[i].team == TEAM_BLUE) { + blue++; + } + if (ps->clientNum == cg.scores[i].client) { + cg.selectedScore = i; + } + } + + if (menu == NULL) { + // just interested in setting the selected score + return; + } + + if ( cgs.gametype >= GT_TEAM ) { + int feeder = FEEDER_REDTEAM_LIST; + i = red; + if (cg.scores[cg.selectedScore].team == TEAM_BLUE) { + feeder = FEEDER_BLUETEAM_LIST; + i = blue; + } + Menu_SetFeederSelection(menu, feeder, i, NULL); + } else { + Menu_SetFeederSelection(menu, FEEDER_SCOREBOARD, cg.selectedScore, NULL); + } +} + +// FIXME: might need to cache this info +static clientInfo_t * CG_InfoFromScoreIndex(int index, int team, int *scoreIndex) { + int i, count; + if ( cgs.gametype >= GT_TEAM ) { + count = 0; + for (i = 0; i < cg.numScores; i++) { + if (cg.scores[i].team == team) { + if (count == index) { + *scoreIndex = i; + return &cgs.clientinfo[cg.scores[i].client]; + } + count++; + } + } + } + *scoreIndex = index; + return &cgs.clientinfo[ cg.scores[index].client ]; +} + +static const char *CG_FeederItemText(float feederID, int index, int column, qhandle_t *handle) { + gitem_t *item; + int scoreIndex = 0; + clientInfo_t *info = NULL; + int team = -1; + score_t *sp = NULL; + + *handle = -1; + + if (feederID == FEEDER_REDTEAM_LIST) { + team = TEAM_RED; + } else if (feederID == FEEDER_BLUETEAM_LIST) { + team = TEAM_BLUE; + } + + info = CG_InfoFromScoreIndex(index, team, &scoreIndex); + sp = &cg.scores[scoreIndex]; + + if (info && info->infoValid) { + switch (column) { + case 0: + if ( info->powerups & ( 1 << PW_NEUTRALFLAG ) ) { + item = BG_FindItemForPowerup( PW_NEUTRALFLAG ); + *handle = cg_items[ ITEM_INDEX(item) ].icon; + } else if ( info->powerups & ( 1 << PW_REDFLAG ) ) { + item = BG_FindItemForPowerup( PW_REDFLAG ); + *handle = cg_items[ ITEM_INDEX(item) ].icon; + } else if ( info->powerups & ( 1 << PW_BLUEFLAG ) ) { + item = BG_FindItemForPowerup( PW_BLUEFLAG ); + *handle = cg_items[ ITEM_INDEX(item) ].icon; + } else { + if ( info->botSkill > 0 && info->botSkill <= 5 ) { + *handle = cgs.media.botSkillShaders[ info->botSkill - 1 ]; + } else if ( info->handicap < 100 ) { + return va("%i", info->handicap ); + } + } + break; + case 1: + if (team == -1) { + return ""; + } else { + *handle = CG_StatusHandle(info->teamTask); + } + break; + case 2: + if ( cg.snap->ps.stats[ STAT_CLIENTS_READY ] & ( 1 << sp->client ) ) { + return "Ready"; + } + if (team == -1) { + if (cgs.gametype == GT_TOURNAMENT) { + return va("%i/%i", info->wins, info->losses); + } else if (info->infoValid && info->team == TEAM_SPECTATOR ) { + return "Spectator"; + } else { + return ""; + } + } else { + if (info->teamLeader) { + return "Leader"; + } + } + break; + case 3: + return info->name; + break; + case 4: + return va("%i", info->score); + break; + case 5: + return va("%4i", sp->time); + break; + case 6: + if ( sp->ping == -1 ) { + return "connecting"; + } + return va("%4i", sp->ping); + break; + } + } + + return ""; +} + +static qhandle_t CG_FeederItemImage(float feederID, int index) { + return 0; +} + +static void CG_FeederSelection(float feederID, int index) { + if ( cgs.gametype >= GT_TEAM ) { + int i, count; + int team = (feederID == FEEDER_REDTEAM_LIST) ? TEAM_RED : TEAM_BLUE; + count = 0; + for (i = 0; i < cg.numScores; i++) { + if (cg.scores[i].team == team) { + if (index == count) { + cg.selectedScore = i; + } + count++; + } + } + } else { + cg.selectedScore = index; + } +} +#endif + +#ifdef MISSIONPACK // bk001204 - only needed there +static float CG_Cvar_Get(const char *cvar) { + char buff[128]; + memset(buff, 0, sizeof(buff)); + trap_Cvar_VariableStringBuffer(cvar, buff, sizeof(buff)); + return atof(buff); +} +#endif + +#ifdef MISSIONPACK +void CG_Text_PaintWithCursor(float x, float y, float scale, vec4_t color, const char *text, int cursorPos, char cursor, int limit, int style) { + CG_Text_Paint(x, y, scale, color, text, 0, limit, style); +} + +static int CG_OwnerDrawWidth(int ownerDraw, float scale) { + switch (ownerDraw) { + case CG_GAME_TYPE: + return CG_Text_Width(CG_GameTypeString(), scale, 0); + case CG_GAME_STATUS: + return CG_Text_Width(CG_GetGameStatusText(), scale, 0); + break; + case CG_KILLER: + return CG_Text_Width(CG_GetKillerText(), scale, 0); + break; + case CG_RED_NAME: + return CG_Text_Width(cg_redTeamName.string, scale, 0); + break; + case CG_BLUE_NAME: + return CG_Text_Width(cg_blueTeamName.string, scale, 0); + break; + + + } + return 0; +} + +static int CG_PlayCinematic(const char *name, float x, float y, float w, float h) { + return trap_CIN_PlayCinematic(name, x, y, w, h, CIN_loop); +} + +static void CG_StopCinematic(int handle) { + trap_CIN_StopCinematic(handle); +} + +static void CG_DrawCinematic(int handle, float x, float y, float w, float h) { + trap_CIN_SetExtents(handle, x, y, w, h); + trap_CIN_DrawCinematic(handle); +} + +static void CG_RunCinematicFrame(int handle) { + trap_CIN_RunCinematic(handle); +} + +/* +================= +CG_LoadHudMenu(); + +================= +*/ +void CG_LoadHudMenu() { + char buff[1024]; + const char *hudSet; + + cgDC.registerShaderNoMip = &trap_R_RegisterShaderNoMip; + cgDC.setColor = &trap_R_SetColor; + cgDC.drawHandlePic = &CG_DrawPic; + cgDC.drawStretchPic = &trap_R_DrawStretchPic; + cgDC.drawText = &CG_Text_Paint; + cgDC.textWidth = &CG_Text_Width; + cgDC.textHeight = &CG_Text_Height; + cgDC.registerModel = &trap_R_RegisterModel; + cgDC.modelBounds = &trap_R_ModelBounds; + cgDC.fillRect = &CG_FillRect; + cgDC.drawRect = &CG_DrawRect; + cgDC.drawSides = &CG_DrawSides; + cgDC.drawTopBottom = &CG_DrawTopBottom; + cgDC.clearScene = &trap_R_ClearScene; + cgDC.addRefEntityToScene = &trap_R_AddRefEntityToScene; + cgDC.renderScene = &trap_R_RenderScene; + cgDC.registerFont = &trap_R_RegisterFont; + cgDC.ownerDrawItem = &CG_OwnerDraw; + cgDC.getValue = &CG_GetValue; + cgDC.ownerDrawVisible = &CG_OwnerDrawVisible; + cgDC.runScript = &CG_RunMenuScript; + cgDC.getTeamColor = &CG_GetTeamColor; + cgDC.setCVar = trap_Cvar_Set; + cgDC.getCVarString = trap_Cvar_VariableStringBuffer; + cgDC.getCVarValue = CG_Cvar_Get; + cgDC.drawTextWithCursor = &CG_Text_PaintWithCursor; + //cgDC.setOverstrikeMode = &trap_Key_SetOverstrikeMode; + //cgDC.getOverstrikeMode = &trap_Key_GetOverstrikeMode; + cgDC.startLocalSound = &trap_S_StartLocalSound; + cgDC.ownerDrawHandleKey = &CG_OwnerDrawHandleKey; + cgDC.feederCount = &CG_FeederCount; + cgDC.feederItemImage = &CG_FeederItemImage; + cgDC.feederItemText = &CG_FeederItemText; + cgDC.feederSelection = &CG_FeederSelection; + //cgDC.setBinding = &trap_Key_SetBinding; + //cgDC.getBindingBuf = &trap_Key_GetBindingBuf; + //cgDC.keynumToStringBuf = &trap_Key_KeynumToStringBuf; + //cgDC.executeText = &trap_Cmd_ExecuteText; + cgDC.Error = &Com_Error; + cgDC.Print = &Com_Printf; + cgDC.ownerDrawWidth = &CG_OwnerDrawWidth; + //cgDC.Pause = &CG_Pause; + cgDC.registerSound = &trap_S_RegisterSound; + cgDC.startBackgroundTrack = &trap_S_StartBackgroundTrack; + cgDC.stopBackgroundTrack = &trap_S_StopBackgroundTrack; + cgDC.playCinematic = &CG_PlayCinematic; + cgDC.stopCinematic = &CG_StopCinematic; + cgDC.drawCinematic = &CG_DrawCinematic; + cgDC.runCinematicFrame = &CG_RunCinematicFrame; + + Init_Display(&cgDC); + + Menu_Reset(); + + trap_Cvar_VariableStringBuffer("cg_hudFiles", buff, sizeof(buff)); + hudSet = buff; + if (hudSet[0] == '\0') { + hudSet = "ui/hud.txt"; + } + + CG_LoadMenus(hudSet); +} + +void CG_AssetCache() { + //if (Assets.textFont == NULL) { + // trap_R_RegisterFont("fonts/arial.ttf", 72, &Assets.textFont); + //} + //Assets.background = trap_R_RegisterShaderNoMip( ASSET_BACKGROUND ); + //Com_Printf("Menu Size: %i bytes\n", sizeof(Menus)); + cgDC.Assets.gradientBar = trap_R_RegisterShaderNoMip( ASSET_GRADIENTBAR ); + cgDC.Assets.fxBasePic = trap_R_RegisterShaderNoMip( ART_FX_BASE ); + cgDC.Assets.fxPic[0] = trap_R_RegisterShaderNoMip( ART_FX_RED ); + cgDC.Assets.fxPic[1] = trap_R_RegisterShaderNoMip( ART_FX_YELLOW ); + cgDC.Assets.fxPic[2] = trap_R_RegisterShaderNoMip( ART_FX_GREEN ); + cgDC.Assets.fxPic[3] = trap_R_RegisterShaderNoMip( ART_FX_TEAL ); + cgDC.Assets.fxPic[4] = trap_R_RegisterShaderNoMip( ART_FX_BLUE ); + cgDC.Assets.fxPic[5] = trap_R_RegisterShaderNoMip( ART_FX_CYAN ); + cgDC.Assets.fxPic[6] = trap_R_RegisterShaderNoMip( ART_FX_WHITE ); + cgDC.Assets.scrollBar = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR ); + cgDC.Assets.scrollBarArrowDown = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWDOWN ); + cgDC.Assets.scrollBarArrowUp = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWUP ); + cgDC.Assets.scrollBarArrowLeft = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWLEFT ); + cgDC.Assets.scrollBarArrowRight = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWRIGHT ); + cgDC.Assets.scrollBarThumb = trap_R_RegisterShaderNoMip( ASSET_SCROLL_THUMB ); + cgDC.Assets.sliderBar = trap_R_RegisterShaderNoMip( ASSET_SLIDER_BAR ); + cgDC.Assets.sliderThumb = trap_R_RegisterShaderNoMip( ASSET_SLIDER_THUMB ); +} +#endif +/* +================= +CG_Init + +Called after every level change or subsystem restart +Will perform callbacks to make the loading info screen update. +================= +*/ +void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) { + const char *s; + + // clear everything + memset( &cgs, 0, sizeof( cgs ) ); + memset( &cg, 0, sizeof( cg ) ); + memset( cg_entities, 0, sizeof(cg_entities) ); + memset( cg_weapons, 0, sizeof(cg_weapons) ); + memset( cg_items, 0, sizeof(cg_items) ); + + cg.clientNum = clientNum; + + cgs.processedSnapshotNum = serverMessageNum; + cgs.serverCommandSequence = serverCommandSequence; + + // load a few needed things before we do any screen updates + cgs.media.charsetShader = trap_R_RegisterShader( "gfx/2d/bigchars" ); + cgs.media.whiteShader = trap_R_RegisterShader( "white" ); + cgs.media.charsetProp = trap_R_RegisterShaderNoMip( "menu/art/font1_prop.tga" ); + cgs.media.charsetPropGlow = trap_R_RegisterShaderNoMip( "menu/art/font1_prop_glo.tga" ); + cgs.media.charsetPropB = trap_R_RegisterShaderNoMip( "menu/art/font2_prop.tga" ); + + CG_RegisterCvars(); + + CG_InitConsoleCommands(); + + cg.weaponSelect = WP_MACHINEGUN; + + cgs.redflag = cgs.blueflag = -1; // For compatibily, default to unset for + cgs.flagStatus = -1; + // old servers + + // get the rendering configuration from the client system + trap_GetGlconfig( &cgs.glconfig ); + cgs.screenXScale = cgs.glconfig.vidWidth / 640.0; + cgs.screenYScale = cgs.glconfig.vidHeight / 480.0; + + // get the gamestate from the client system + trap_GetGameState( &cgs.gameState ); + + // check version + s = CG_ConfigString( CS_GAME_VERSION ); + if ( strcmp( s, GAME_VERSION ) ) { + CG_Error( "Client/Server game mismatch: %s/%s", GAME_VERSION, s ); + } + + s = CG_ConfigString( CS_LEVEL_START_TIME ); + cgs.levelStartTime = atoi( s ); + + CG_ParseServerinfo(); + + // load the new map + CG_LoadingString( "collision map" ); + + trap_CM_LoadMap( cgs.mapname ); + +#ifdef MISSIONPACK + String_Init(); +#endif + + cg.loading = qtrue; // force players to load instead of defer + + CG_LoadingString( "sounds" ); + + CG_RegisterSounds(); + + CG_LoadingString( "graphics" ); + + CG_RegisterGraphics(); + + CG_LoadingString( "clients" ); + + CG_RegisterClients(); // if low on memory, some clients will be deferred + +#ifdef MISSIONPACK + CG_AssetCache(); + CG_LoadHudMenu(); // load new hud stuff +#endif + + cg.loading = qfalse; // future players will be deferred + + CG_InitLocalEntities(); + + CG_InitMarkPolys(); + + // remove the last loading update + cg.infoScreenText[0] = 0; + + // Make sure we have update values (scores) + CG_SetConfigValues(); + + CG_StartMusic(); + + CG_LoadingString( "" ); + +#ifdef MISSIONPACK + CG_InitTeamChat(); +#endif + + CG_ShaderStateChanged(); + + trap_S_ClearLoopingSounds( qtrue ); +} + +/* +================= +CG_Shutdown + +Called before every level change or subsystem restart +================= +*/ +void CG_Shutdown( void ) { + // some mods may need to do cleanup work here, + // like closing files or archiving session data +} + + +/* +================== +CG_EventHandling +================== + type 0 - no event handling + 1 - team menu + 2 - hud editor + +*/ +#ifndef MISSIONPACK +void CG_EventHandling(int type) { +} + + + +void CG_KeyEvent(int key, qboolean down) { +} + +void CG_MouseEvent(int x, int y) { +} +#endif + diff --git a/code/cgame/cg_marks.c b/code/cgame/cg_marks.c index 4c648d2..d0fecaa 100755 --- a/code/cgame/cg_marks.c +++ b/code/cgame/cg_marks.c @@ -1,2274 +1,2274 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// cg_marks.c -- wall marks - -#include "cg_local.h" - -/* -=================================================================== - -MARK POLYS - -=================================================================== -*/ - - -markPoly_t cg_activeMarkPolys; // double linked list -markPoly_t *cg_freeMarkPolys; // single linked list -markPoly_t cg_markPolys[MAX_MARK_POLYS]; -static int markTotal; - -/* -=================== -CG_InitMarkPolys - -This is called at startup and for tournement restarts -=================== -*/ -void CG_InitMarkPolys( void ) { - int i; - - memset( cg_markPolys, 0, sizeof(cg_markPolys) ); - - cg_activeMarkPolys.nextMark = &cg_activeMarkPolys; - cg_activeMarkPolys.prevMark = &cg_activeMarkPolys; - cg_freeMarkPolys = cg_markPolys; - for ( i = 0 ; i < MAX_MARK_POLYS - 1 ; i++ ) { - cg_markPolys[i].nextMark = &cg_markPolys[i+1]; - } -} - - -/* -================== -CG_FreeMarkPoly -================== -*/ -void CG_FreeMarkPoly( markPoly_t *le ) { - if ( !le->prevMark ) { - CG_Error( "CG_FreeLocalEntity: not active" ); - } - - // remove from the doubly linked active list - le->prevMark->nextMark = le->nextMark; - le->nextMark->prevMark = le->prevMark; - - // the free list is only singly linked - le->nextMark = cg_freeMarkPolys; - cg_freeMarkPolys = le; -} - -/* -=================== -CG_AllocMark - -Will allways succeed, even if it requires freeing an old active mark -=================== -*/ -markPoly_t *CG_AllocMark( void ) { - markPoly_t *le; - int time; - - if ( !cg_freeMarkPolys ) { - // no free entities, so free the one at the end of the chain - // remove the oldest active entity - time = cg_activeMarkPolys.prevMark->time; - while (cg_activeMarkPolys.prevMark && time == cg_activeMarkPolys.prevMark->time) { - CG_FreeMarkPoly( cg_activeMarkPolys.prevMark ); - } - } - - le = cg_freeMarkPolys; - cg_freeMarkPolys = cg_freeMarkPolys->nextMark; - - memset( le, 0, sizeof( *le ) ); - - // link into the active list - le->nextMark = cg_activeMarkPolys.nextMark; - le->prevMark = &cg_activeMarkPolys; - cg_activeMarkPolys.nextMark->prevMark = le; - cg_activeMarkPolys.nextMark = le; - return le; -} - - - -/* -================= -CG_ImpactMark - -origin should be a point within a unit of the plane -dir should be the plane normal - -temporary marks will not be stored or randomly oriented, but immediately -passed to the renderer. -================= -*/ -#define MAX_MARK_FRAGMENTS 128 -#define MAX_MARK_POINTS 384 - -void CG_ImpactMark( qhandle_t markShader, const vec3_t origin, const vec3_t dir, - float orientation, float red, float green, float blue, float alpha, - qboolean alphaFade, float radius, qboolean temporary ) { - vec3_t axis[3]; - float texCoordScale; - vec3_t originalPoints[4]; - byte colors[4]; - int i, j; - int numFragments; - markFragment_t markFragments[MAX_MARK_FRAGMENTS], *mf; - vec3_t markPoints[MAX_MARK_POINTS]; - vec3_t projection; - - if ( !cg_addMarks.integer ) { - return; - } - - if ( radius <= 0 ) { - CG_Error( "CG_ImpactMark called with <= 0 radius" ); - } - - //if ( markTotal >= MAX_MARK_POLYS ) { - // return; - //} - - // create the texture axis - VectorNormalize2( dir, axis[0] ); - PerpendicularVector( axis[1], axis[0] ); - RotatePointAroundVector( axis[2], axis[0], axis[1], orientation ); - CrossProduct( axis[0], axis[2], axis[1] ); - - texCoordScale = 0.5 * 1.0 / radius; - - // create the full polygon - for ( i = 0 ; i < 3 ; i++ ) { - originalPoints[0][i] = origin[i] - radius * axis[1][i] - radius * axis[2][i]; - originalPoints[1][i] = origin[i] + radius * axis[1][i] - radius * axis[2][i]; - originalPoints[2][i] = origin[i] + radius * axis[1][i] + radius * axis[2][i]; - originalPoints[3][i] = origin[i] - radius * axis[1][i] + radius * axis[2][i]; - } - - // get the fragments - VectorScale( dir, -20, projection ); - numFragments = trap_CM_MarkFragments( 4, (void *)originalPoints, - projection, MAX_MARK_POINTS, markPoints[0], - MAX_MARK_FRAGMENTS, markFragments ); - - colors[0] = red * 255; - colors[1] = green * 255; - colors[2] = blue * 255; - colors[3] = alpha * 255; - - for ( i = 0, mf = markFragments ; i < numFragments ; i++, mf++ ) { - polyVert_t *v; - polyVert_t verts[MAX_VERTS_ON_POLY]; - markPoly_t *mark; - - // we have an upper limit on the complexity of polygons - // that we store persistantly - if ( mf->numPoints > MAX_VERTS_ON_POLY ) { - mf->numPoints = MAX_VERTS_ON_POLY; - } - for ( j = 0, v = verts ; j < mf->numPoints ; j++, v++ ) { - vec3_t delta; - - VectorCopy( markPoints[mf->firstPoint + j], v->xyz ); - - VectorSubtract( v->xyz, origin, delta ); - v->st[0] = 0.5 + DotProduct( delta, axis[1] ) * texCoordScale; - v->st[1] = 0.5 + DotProduct( delta, axis[2] ) * texCoordScale; - *(int *)v->modulate = *(int *)colors; - } - - // if it is a temporary (shadow) mark, add it immediately and forget about it - if ( temporary ) { - trap_R_AddPolyToScene( markShader, mf->numPoints, verts ); - continue; - } - - // otherwise save it persistantly - mark = CG_AllocMark(); - mark->time = cg.time; - mark->alphaFade = alphaFade; - mark->markShader = markShader; - mark->poly.numVerts = mf->numPoints; - mark->color[0] = red; - mark->color[1] = green; - mark->color[2] = blue; - mark->color[3] = alpha; - memcpy( mark->verts, verts, mf->numPoints * sizeof( verts[0] ) ); - markTotal++; - } -} - - -/* -=============== -CG_AddMarks -=============== -*/ -#define MARK_TOTAL_TIME 10000 -#define MARK_FADE_TIME 1000 - -void CG_AddMarks( void ) { - int j; - markPoly_t *mp, *next; - int t; - int fade; - - if ( !cg_addMarks.integer ) { - return; - } - - mp = cg_activeMarkPolys.nextMark; - for ( ; mp != &cg_activeMarkPolys ; mp = next ) { - // grab next now, so if the local entity is freed we - // still have it - next = mp->nextMark; - - // see if it is time to completely remove it - if ( cg.time > mp->time + MARK_TOTAL_TIME ) { - CG_FreeMarkPoly( mp ); - continue; - } - - // fade out the energy bursts - if ( mp->markShader == cgs.media.energyMarkShader ) { - - fade = 450 - 450 * ( (cg.time - mp->time ) / 3000.0 ); - if ( fade < 255 ) { - if ( fade < 0 ) { - fade = 0; - } - if ( mp->verts[0].modulate[0] != 0 ) { - for ( j = 0 ; j < mp->poly.numVerts ; j++ ) { - mp->verts[j].modulate[0] = mp->color[0] * fade; - mp->verts[j].modulate[1] = mp->color[1] * fade; - mp->verts[j].modulate[2] = mp->color[2] * fade; - } - } - } - } - - // fade all marks out with time - t = mp->time + MARK_TOTAL_TIME - cg.time; - if ( t < MARK_FADE_TIME ) { - fade = 255 * t / MARK_FADE_TIME; - if ( mp->alphaFade ) { - for ( j = 0 ; j < mp->poly.numVerts ; j++ ) { - mp->verts[j].modulate[3] = fade; - } - } else { - for ( j = 0 ; j < mp->poly.numVerts ; j++ ) { - mp->verts[j].modulate[0] = mp->color[0] * fade; - mp->verts[j].modulate[1] = mp->color[1] * fade; - mp->verts[j].modulate[2] = mp->color[2] * fade; - } - } - } - - - trap_R_AddPolyToScene( mp->markShader, mp->poly.numVerts, mp->verts ); - } -} - -// cg_particles.c - -#define BLOODRED 2 -#define EMISIVEFADE 3 -#define GREY75 4 - -typedef struct particle_s -{ - struct particle_s *next; - - float time; - float endtime; - - vec3_t org; - vec3_t vel; - vec3_t accel; - int color; - float colorvel; - float alpha; - float alphavel; - int type; - qhandle_t pshader; - - float height; - float width; - - float endheight; - float endwidth; - - float start; - float end; - - float startfade; - qboolean rotate; - int snum; - - qboolean link; - - // Ridah - int shaderAnim; - int roll; - - int accumroll; - -} cparticle_t; - -typedef enum -{ - P_NONE, - P_WEATHER, - P_FLAT, - P_SMOKE, - P_ROTATE, - P_WEATHER_TURBULENT, - P_ANIM, // Ridah - P_BAT, - P_BLEED, - P_FLAT_SCALEUP, - P_FLAT_SCALEUP_FADE, - P_WEATHER_FLURRY, - P_SMOKE_IMPACT, - P_BUBBLE, - P_BUBBLE_TURBULENT, - P_SPRITE -} particle_type_t; - -#define MAX_SHADER_ANIMS 32 -#define MAX_SHADER_ANIM_FRAMES 64 - -static char *shaderAnimNames[MAX_SHADER_ANIMS] = { - "explode1", - NULL -}; -static qhandle_t shaderAnims[MAX_SHADER_ANIMS][MAX_SHADER_ANIM_FRAMES]; -static int shaderAnimCounts[MAX_SHADER_ANIMS] = { - 23 -}; -static float shaderAnimSTRatio[MAX_SHADER_ANIMS] = { - 1.0f -}; -static int numShaderAnims; -// done. - -#define PARTICLE_GRAVITY 40 -#define MAX_PARTICLES 1024 - -cparticle_t *active_particles, *free_particles; -cparticle_t particles[MAX_PARTICLES]; -int cl_numparticles = MAX_PARTICLES; - -qboolean initparticles = qfalse; -vec3_t pvforward, pvright, pvup; -vec3_t rforward, rright, rup; - -float oldtime; - -/* -=============== -CL_ClearParticles -=============== -*/ -void CG_ClearParticles (void) -{ - int i; - - memset( particles, 0, sizeof(particles) ); - - free_particles = &particles[0]; - active_particles = NULL; - - for (i=0 ;itype == P_WEATHER || p->type == P_WEATHER_TURBULENT || p->type == P_WEATHER_FLURRY - || p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) - {// create a front facing polygon - - if (p->type != P_WEATHER_FLURRY) - { - if (p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) - { - if (org[2] > p->end) - { - p->time = cg.time; - VectorCopy (org, p->org); // Ridah, fixes rare snow flakes that flicker on the ground - - p->org[2] = ( p->start + crandom () * 4 ); - - - if (p->type == P_BUBBLE_TURBULENT) - { - p->vel[0] = crandom() * 4; - p->vel[1] = crandom() * 4; - } - - } - } - else - { - if (org[2] < p->end) - { - p->time = cg.time; - VectorCopy (org, p->org); // Ridah, fixes rare snow flakes that flicker on the ground - - while (p->org[2] < p->end) - { - p->org[2] += (p->start - p->end); - } - - - if (p->type == P_WEATHER_TURBULENT) - { - p->vel[0] = crandom() * 16; - p->vel[1] = crandom() * 16; - } - - } - } - - - // Rafael snow pvs check - if (!p->link) - return; - - p->alpha = 1; - } - - // Ridah, had to do this or MAX_POLYS is being exceeded in village1.bsp - if (Distance( cg.snap->ps.origin, org ) > 1024) { - return; - } - // done. - - if (p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) - { - VectorMA (org, -p->height, pvup, point); - VectorMA (point, -p->width, pvright, point); - VectorCopy (point, verts[0].xyz); - verts[0].st[0] = 0; - verts[0].st[1] = 0; - verts[0].modulate[0] = 255; - verts[0].modulate[1] = 255; - verts[0].modulate[2] = 255; - verts[0].modulate[3] = 255 * p->alpha; - - VectorMA (org, -p->height, pvup, point); - VectorMA (point, p->width, pvright, point); - VectorCopy (point, verts[1].xyz); - verts[1].st[0] = 0; - verts[1].st[1] = 1; - verts[1].modulate[0] = 255; - verts[1].modulate[1] = 255; - verts[1].modulate[2] = 255; - verts[1].modulate[3] = 255 * p->alpha; - - VectorMA (org, p->height, pvup, point); - VectorMA (point, p->width, pvright, point); - VectorCopy (point, verts[2].xyz); - verts[2].st[0] = 1; - verts[2].st[1] = 1; - verts[2].modulate[0] = 255; - verts[2].modulate[1] = 255; - verts[2].modulate[2] = 255; - verts[2].modulate[3] = 255 * p->alpha; - - VectorMA (org, p->height, pvup, point); - VectorMA (point, -p->width, pvright, point); - VectorCopy (point, verts[3].xyz); - verts[3].st[0] = 1; - verts[3].st[1] = 0; - verts[3].modulate[0] = 255; - verts[3].modulate[1] = 255; - verts[3].modulate[2] = 255; - verts[3].modulate[3] = 255 * p->alpha; - } - else - { - VectorMA (org, -p->height, pvup, point); - VectorMA (point, -p->width, pvright, point); - VectorCopy( point, TRIverts[0].xyz ); - TRIverts[0].st[0] = 1; - TRIverts[0].st[1] = 0; - TRIverts[0].modulate[0] = 255; - TRIverts[0].modulate[1] = 255; - TRIverts[0].modulate[2] = 255; - TRIverts[0].modulate[3] = 255 * p->alpha; - - VectorMA (org, p->height, pvup, point); - VectorMA (point, -p->width, pvright, point); - VectorCopy (point, TRIverts[1].xyz); - TRIverts[1].st[0] = 0; - TRIverts[1].st[1] = 0; - TRIverts[1].modulate[0] = 255; - TRIverts[1].modulate[1] = 255; - TRIverts[1].modulate[2] = 255; - TRIverts[1].modulate[3] = 255 * p->alpha; - - VectorMA (org, p->height, pvup, point); - VectorMA (point, p->width, pvright, point); - VectorCopy (point, TRIverts[2].xyz); - TRIverts[2].st[0] = 0; - TRIverts[2].st[1] = 1; - TRIverts[2].modulate[0] = 255; - TRIverts[2].modulate[1] = 255; - TRIverts[2].modulate[2] = 255; - TRIverts[2].modulate[3] = 255 * p->alpha; - } - - } - else if (p->type == P_SPRITE) - { - vec3_t rr, ru; - vec3_t rotate_ang; - - VectorSet (color, 1.0, 1.0, 0.5); - time = cg.time - p->time; - time2 = p->endtime - p->time; - ratio = time / time2; - - width = p->width + ( ratio * ( p->endwidth - p->width) ); - height = p->height + ( ratio * ( p->endheight - p->height) ); - - if (p->roll) { - vectoangles( cg.refdef.viewaxis[0], rotate_ang ); - rotate_ang[ROLL] += p->roll; - AngleVectors ( rotate_ang, NULL, rr, ru); - } - - if (p->roll) { - VectorMA (org, -height, ru, point); - VectorMA (point, -width, rr, point); - } else { - VectorMA (org, -height, pvup, point); - VectorMA (point, -width, pvright, point); - } - VectorCopy (point, verts[0].xyz); - verts[0].st[0] = 0; - verts[0].st[1] = 0; - verts[0].modulate[0] = 255; - verts[0].modulate[1] = 255; - verts[0].modulate[2] = 255; - verts[0].modulate[3] = 255; - - if (p->roll) { - VectorMA (point, 2*height, ru, point); - } else { - VectorMA (point, 2*height, pvup, point); - } - VectorCopy (point, verts[1].xyz); - verts[1].st[0] = 0; - verts[1].st[1] = 1; - verts[1].modulate[0] = 255; - verts[1].modulate[1] = 255; - verts[1].modulate[2] = 255; - verts[1].modulate[3] = 255; - - if (p->roll) { - VectorMA (point, 2*width, rr, point); - } else { - VectorMA (point, 2*width, pvright, point); - } - VectorCopy (point, verts[2].xyz); - verts[2].st[0] = 1; - verts[2].st[1] = 1; - verts[2].modulate[0] = 255; - verts[2].modulate[1] = 255; - verts[2].modulate[2] = 255; - verts[2].modulate[3] = 255; - - if (p->roll) { - VectorMA (point, -2*height, ru, point); - } else { - VectorMA (point, -2*height, pvup, point); - } - VectorCopy (point, verts[3].xyz); - verts[3].st[0] = 1; - verts[3].st[1] = 0; - verts[3].modulate[0] = 255; - verts[3].modulate[1] = 255; - verts[3].modulate[2] = 255; - verts[3].modulate[3] = 255; - } - else if (p->type == P_SMOKE || p->type == P_SMOKE_IMPACT) - {// create a front rotating facing polygon - - if ( p->type == P_SMOKE_IMPACT && Distance( cg.snap->ps.origin, org ) > 1024) { - return; - } - - if (p->color == BLOODRED) - VectorSet (color, 0.22f, 0.0f, 0.0f); - else if (p->color == GREY75) - { - float len; - float greyit; - float val; - len = Distance (cg.snap->ps.origin, org); - if (!len) - len = 1; - - val = 4096/len; - greyit = 0.25 * val; - if (greyit > 0.5) - greyit = 0.5; - - VectorSet (color, greyit, greyit, greyit); - } - else - VectorSet (color, 1.0, 1.0, 1.0); - - time = cg.time - p->time; - time2 = p->endtime - p->time; - ratio = time / time2; - - if (cg.time > p->startfade) - { - invratio = 1 - ( (cg.time - p->startfade) / (p->endtime - p->startfade) ); - - if (p->color == EMISIVEFADE) - { - float fval; - fval = (invratio * invratio); - if (fval < 0) - fval = 0; - VectorSet (color, fval , fval , fval ); - } - invratio *= p->alpha; - } - else - invratio = 1 * p->alpha; - - if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) - invratio = 1; - - if (invratio > 1) - invratio = 1; - - width = p->width + ( ratio * ( p->endwidth - p->width) ); - height = p->height + ( ratio * ( p->endheight - p->height) ); - - if (p->type != P_SMOKE_IMPACT) - { - vec3_t temp; - - vectoangles (rforward, temp); - p->accumroll += p->roll; - temp[ROLL] += p->accumroll * 0.1; - AngleVectors ( temp, NULL, rright2, rup2); - } - else - { - VectorCopy (rright, rright2); - VectorCopy (rup, rup2); - } - - if (p->rotate) - { - VectorMA (org, -height, rup2, point); - VectorMA (point, -width, rright2, point); - } - else - { - VectorMA (org, -p->height, pvup, point); - VectorMA (point, -p->width, pvright, point); - } - VectorCopy (point, verts[0].xyz); - verts[0].st[0] = 0; - verts[0].st[1] = 0; - verts[0].modulate[0] = 255 * color[0]; - verts[0].modulate[1] = 255 * color[1]; - verts[0].modulate[2] = 255 * color[2]; - verts[0].modulate[3] = 255 * invratio; - - if (p->rotate) - { - VectorMA (org, -height, rup2, point); - VectorMA (point, width, rright2, point); - } - else - { - VectorMA (org, -p->height, pvup, point); - VectorMA (point, p->width, pvright, point); - } - VectorCopy (point, verts[1].xyz); - verts[1].st[0] = 0; - verts[1].st[1] = 1; - verts[1].modulate[0] = 255 * color[0]; - verts[1].modulate[1] = 255 * color[1]; - verts[1].modulate[2] = 255 * color[2]; - verts[1].modulate[3] = 255 * invratio; - - if (p->rotate) - { - VectorMA (org, height, rup2, point); - VectorMA (point, width, rright2, point); - } - else - { - VectorMA (org, p->height, pvup, point); - VectorMA (point, p->width, pvright, point); - } - VectorCopy (point, verts[2].xyz); - verts[2].st[0] = 1; - verts[2].st[1] = 1; - verts[2].modulate[0] = 255 * color[0]; - verts[2].modulate[1] = 255 * color[1]; - verts[2].modulate[2] = 255 * color[2]; - verts[2].modulate[3] = 255 * invratio; - - if (p->rotate) - { - VectorMA (org, height, rup2, point); - VectorMA (point, -width, rright2, point); - } - else - { - VectorMA (org, p->height, pvup, point); - VectorMA (point, -p->width, pvright, point); - } - VectorCopy (point, verts[3].xyz); - verts[3].st[0] = 1; - verts[3].st[1] = 0; - verts[3].modulate[0] = 255 * color[0]; - verts[3].modulate[1] = 255 * color[1]; - verts[3].modulate[2] = 255 * color[2]; - verts[3].modulate[3] = 255 * invratio; - - } - else if (p->type == P_BLEED) - { - vec3_t rr, ru; - vec3_t rotate_ang; - float alpha; - - alpha = p->alpha; - - if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) - alpha = 1; - - if (p->roll) - { - vectoangles( cg.refdef.viewaxis[0], rotate_ang ); - rotate_ang[ROLL] += p->roll; - AngleVectors ( rotate_ang, NULL, rr, ru); - } - else - { - VectorCopy (pvup, ru); - VectorCopy (pvright, rr); - } - - VectorMA (org, -p->height, ru, point); - VectorMA (point, -p->width, rr, point); - VectorCopy (point, verts[0].xyz); - verts[0].st[0] = 0; - verts[0].st[1] = 0; - verts[0].modulate[0] = 111; - verts[0].modulate[1] = 19; - verts[0].modulate[2] = 9; - verts[0].modulate[3] = 255 * alpha; - - VectorMA (org, -p->height, ru, point); - VectorMA (point, p->width, rr, point); - VectorCopy (point, verts[1].xyz); - verts[1].st[0] = 0; - verts[1].st[1] = 1; - verts[1].modulate[0] = 111; - verts[1].modulate[1] = 19; - verts[1].modulate[2] = 9; - verts[1].modulate[3] = 255 * alpha; - - VectorMA (org, p->height, ru, point); - VectorMA (point, p->width, rr, point); - VectorCopy (point, verts[2].xyz); - verts[2].st[0] = 1; - verts[2].st[1] = 1; - verts[2].modulate[0] = 111; - verts[2].modulate[1] = 19; - verts[2].modulate[2] = 9; - verts[2].modulate[3] = 255 * alpha; - - VectorMA (org, p->height, ru, point); - VectorMA (point, -p->width, rr, point); - VectorCopy (point, verts[3].xyz); - verts[3].st[0] = 1; - verts[3].st[1] = 0; - verts[3].modulate[0] = 111; - verts[3].modulate[1] = 19; - verts[3].modulate[2] = 9; - verts[3].modulate[3] = 255 * alpha; - - } - else if (p->type == P_FLAT_SCALEUP) - { - float width, height; - float sinR, cosR; - - if (p->color == BLOODRED) - VectorSet (color, 1, 1, 1); - else - VectorSet (color, 0.5, 0.5, 0.5); - - time = cg.time - p->time; - time2 = p->endtime - p->time; - ratio = time / time2; - - width = p->width + ( ratio * ( p->endwidth - p->width) ); - height = p->height + ( ratio * ( p->endheight - p->height) ); - - if (width > p->endwidth) - width = p->endwidth; - - if (height > p->endheight) - height = p->endheight; - - sinR = height * sin(DEG2RAD(p->roll)) * sqrt(2); - cosR = width * cos(DEG2RAD(p->roll)) * sqrt(2); - - VectorCopy (org, verts[0].xyz); - verts[0].xyz[0] -= sinR; - verts[0].xyz[1] -= cosR; - verts[0].st[0] = 0; - verts[0].st[1] = 0; - verts[0].modulate[0] = 255 * color[0]; - verts[0].modulate[1] = 255 * color[1]; - verts[0].modulate[2] = 255 * color[2]; - verts[0].modulate[3] = 255; - - VectorCopy (org, verts[1].xyz); - verts[1].xyz[0] -= cosR; - verts[1].xyz[1] += sinR; - verts[1].st[0] = 0; - verts[1].st[1] = 1; - verts[1].modulate[0] = 255 * color[0]; - verts[1].modulate[1] = 255 * color[1]; - verts[1].modulate[2] = 255 * color[2]; - verts[1].modulate[3] = 255; - - VectorCopy (org, verts[2].xyz); - verts[2].xyz[0] += sinR; - verts[2].xyz[1] += cosR; - verts[2].st[0] = 1; - verts[2].st[1] = 1; - verts[2].modulate[0] = 255 * color[0]; - verts[2].modulate[1] = 255 * color[1]; - verts[2].modulate[2] = 255 * color[2]; - verts[2].modulate[3] = 255; - - VectorCopy (org, verts[3].xyz); - verts[3].xyz[0] += cosR; - verts[3].xyz[1] -= sinR; - verts[3].st[0] = 1; - verts[3].st[1] = 0; - verts[3].modulate[0] = 255 * color[0]; - verts[3].modulate[1] = 255 * color[1]; - verts[3].modulate[2] = 255 * color[2]; - verts[3].modulate[3] = 255; - } - else if (p->type == P_FLAT) - { - - VectorCopy (org, verts[0].xyz); - verts[0].xyz[0] -= p->height; - verts[0].xyz[1] -= p->width; - verts[0].st[0] = 0; - verts[0].st[1] = 0; - verts[0].modulate[0] = 255; - verts[0].modulate[1] = 255; - verts[0].modulate[2] = 255; - verts[0].modulate[3] = 255; - - VectorCopy (org, verts[1].xyz); - verts[1].xyz[0] -= p->height; - verts[1].xyz[1] += p->width; - verts[1].st[0] = 0; - verts[1].st[1] = 1; - verts[1].modulate[0] = 255; - verts[1].modulate[1] = 255; - verts[1].modulate[2] = 255; - verts[1].modulate[3] = 255; - - VectorCopy (org, verts[2].xyz); - verts[2].xyz[0] += p->height; - verts[2].xyz[1] += p->width; - verts[2].st[0] = 1; - verts[2].st[1] = 1; - verts[2].modulate[0] = 255; - verts[2].modulate[1] = 255; - verts[2].modulate[2] = 255; - verts[2].modulate[3] = 255; - - VectorCopy (org, verts[3].xyz); - verts[3].xyz[0] += p->height; - verts[3].xyz[1] -= p->width; - verts[3].st[0] = 1; - verts[3].st[1] = 0; - verts[3].modulate[0] = 255; - verts[3].modulate[1] = 255; - verts[3].modulate[2] = 255; - verts[3].modulate[3] = 255; - - } - // Ridah - else if (p->type == P_ANIM) { - vec3_t rr, ru; - vec3_t rotate_ang; - int i, j; - - time = cg.time - p->time; - time2 = p->endtime - p->time; - ratio = time / time2; - if (ratio >= 1.0f) { - ratio = 0.9999f; - } - - width = p->width + ( ratio * ( p->endwidth - p->width) ); - height = p->height + ( ratio * ( p->endheight - p->height) ); - - // if we are "inside" this sprite, don't draw - if (Distance( cg.snap->ps.origin, org ) < width/1.5) { - return; - } - - i = p->shaderAnim; - j = (int)floor(ratio * shaderAnimCounts[p->shaderAnim]); - p->pshader = shaderAnims[i][j]; - - if (p->roll) { - vectoangles( cg.refdef.viewaxis[0], rotate_ang ); - rotate_ang[ROLL] += p->roll; - AngleVectors ( rotate_ang, NULL, rr, ru); - } - - if (p->roll) { - VectorMA (org, -height, ru, point); - VectorMA (point, -width, rr, point); - } else { - VectorMA (org, -height, pvup, point); - VectorMA (point, -width, pvright, point); - } - VectorCopy (point, verts[0].xyz); - verts[0].st[0] = 0; - verts[0].st[1] = 0; - verts[0].modulate[0] = 255; - verts[0].modulate[1] = 255; - verts[0].modulate[2] = 255; - verts[0].modulate[3] = 255; - - if (p->roll) { - VectorMA (point, 2*height, ru, point); - } else { - VectorMA (point, 2*height, pvup, point); - } - VectorCopy (point, verts[1].xyz); - verts[1].st[0] = 0; - verts[1].st[1] = 1; - verts[1].modulate[0] = 255; - verts[1].modulate[1] = 255; - verts[1].modulate[2] = 255; - verts[1].modulate[3] = 255; - - if (p->roll) { - VectorMA (point, 2*width, rr, point); - } else { - VectorMA (point, 2*width, pvright, point); - } - VectorCopy (point, verts[2].xyz); - verts[2].st[0] = 1; - verts[2].st[1] = 1; - verts[2].modulate[0] = 255; - verts[2].modulate[1] = 255; - verts[2].modulate[2] = 255; - verts[2].modulate[3] = 255; - - if (p->roll) { - VectorMA (point, -2*height, ru, point); - } else { - VectorMA (point, -2*height, pvup, point); - } - VectorCopy (point, verts[3].xyz); - verts[3].st[0] = 1; - verts[3].st[1] = 0; - verts[3].modulate[0] = 255; - verts[3].modulate[1] = 255; - verts[3].modulate[2] = 255; - verts[3].modulate[3] = 255; - } - // done. - - if (!p->pshader) { -// (SA) temp commented out for DM -// CG_Printf ("CG_AddParticleToScene type %d p->pshader == ZERO\n", p->type); - return; - } - - if (p->type == P_WEATHER || p->type == P_WEATHER_TURBULENT || p->type == P_WEATHER_FLURRY) - trap_R_AddPolyToScene( p->pshader, 3, TRIverts ); - else - trap_R_AddPolyToScene( p->pshader, 4, verts ); - -} - -// Ridah, made this static so it doesn't interfere with other files -static float roll = 0.0; - -/* -=============== -CG_AddParticles -=============== -*/ -void CG_AddParticles (void) -{ - cparticle_t *p, *next; - float alpha; - float time, time2; - vec3_t org; - int color; - cparticle_t *active, *tail; - int type; - vec3_t rotate_ang; - - if (!initparticles) - CG_ClearParticles (); - - VectorCopy( cg.refdef.viewaxis[0], pvforward ); - VectorCopy( cg.refdef.viewaxis[1], pvright ); - VectorCopy( cg.refdef.viewaxis[2], pvup ); - - vectoangles( cg.refdef.viewaxis[0], rotate_ang ); - roll += ((cg.time - oldtime) * 0.1) ; - rotate_ang[ROLL] += (roll*0.9); - AngleVectors ( rotate_ang, rforward, rright, rup); - - oldtime = cg.time; - - active = NULL; - tail = NULL; - - for (p=active_particles ; p ; p=next) - { - - next = p->next; - - time = (cg.time - p->time)*0.001; - - alpha = p->alpha + time*p->alphavel; - if (alpha <= 0) - { // faded out - p->next = free_particles; - free_particles = p; - p->type = 0; - p->color = 0; - p->alpha = 0; - continue; - } - - if (p->type == P_SMOKE || p->type == P_ANIM || p->type == P_BLEED || p->type == P_SMOKE_IMPACT) - { - if (cg.time > p->endtime) - { - p->next = free_particles; - free_particles = p; - p->type = 0; - p->color = 0; - p->alpha = 0; - - continue; - } - - } - - if (p->type == P_WEATHER_FLURRY) - { - if (cg.time > p->endtime) - { - p->next = free_particles; - free_particles = p; - p->type = 0; - p->color = 0; - p->alpha = 0; - - continue; - } - } - - - if (p->type == P_FLAT_SCALEUP_FADE) - { - if (cg.time > p->endtime) - { - p->next = free_particles; - free_particles = p; - p->type = 0; - p->color = 0; - p->alpha = 0; - continue; - } - - } - - if ((p->type == P_BAT || p->type == P_SPRITE) && p->endtime < 0) { - // temporary sprite - CG_AddParticleToScene (p, p->org, alpha); - p->next = free_particles; - free_particles = p; - p->type = 0; - p->color = 0; - p->alpha = 0; - continue; - } - - p->next = NULL; - if (!tail) - active = tail = p; - else - { - tail->next = p; - tail = p; - } - - if (alpha > 1.0) - alpha = 1; - - color = p->color; - - time2 = time*time; - - org[0] = p->org[0] + p->vel[0]*time + p->accel[0]*time2; - org[1] = p->org[1] + p->vel[1]*time + p->accel[1]*time2; - org[2] = p->org[2] + p->vel[2]*time + p->accel[2]*time2; - - type = p->type; - - CG_AddParticleToScene (p, org, alpha); - } - - active_particles = active; -} - -/* -====================== -CG_AddParticles -====================== -*/ -void CG_ParticleSnowFlurry (qhandle_t pshader, centity_t *cent) -{ - cparticle_t *p; - qboolean turb = qtrue; - - if (!pshader) - CG_Printf ("CG_ParticleSnowFlurry pshader == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - p->color = 0; - p->alpha = 0.90f; - p->alphavel = 0; - - p->start = cent->currentState.origin2[0]; - p->end = cent->currentState.origin2[1]; - - p->endtime = cg.time + cent->currentState.time; - p->startfade = cg.time + cent->currentState.time2; - - p->pshader = pshader; - - if (rand()%100 > 90) - { - p->height = 32; - p->width = 32; - p->alpha = 0.10f; - } - else - { - p->height = 1; - p->width = 1; - } - - p->vel[2] = -20; - - p->type = P_WEATHER_FLURRY; - - if (turb) - p->vel[2] = -10; - - VectorCopy(cent->currentState.origin, p->org); - - p->org[0] = p->org[0]; - p->org[1] = p->org[1]; - p->org[2] = p->org[2]; - - p->vel[0] = p->vel[1] = 0; - - p->accel[0] = p->accel[1] = p->accel[2] = 0; - - p->vel[0] += cent->currentState.angles[0] * 32 + (crandom() * 16); - p->vel[1] += cent->currentState.angles[1] * 32 + (crandom() * 16); - p->vel[2] += cent->currentState.angles[2]; - - if (turb) - { - p->accel[0] = crandom () * 16; - p->accel[1] = crandom () * 16; - } - -} - -void CG_ParticleSnow (qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum) -{ - cparticle_t *p; - - if (!pshader) - CG_Printf ("CG_ParticleSnow pshader == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - p->color = 0; - p->alpha = 0.40f; - p->alphavel = 0; - p->start = origin[2]; - p->end = origin2[2]; - p->pshader = pshader; - p->height = 1; - p->width = 1; - - p->vel[2] = -50; - - if (turb) - { - p->type = P_WEATHER_TURBULENT; - p->vel[2] = -50 * 1.3; - } - else - { - p->type = P_WEATHER; - } - - VectorCopy(origin, p->org); - - p->org[0] = p->org[0] + ( crandom() * range); - p->org[1] = p->org[1] + ( crandom() * range); - p->org[2] = p->org[2] + ( crandom() * (p->start - p->end)); - - p->vel[0] = p->vel[1] = 0; - - p->accel[0] = p->accel[1] = p->accel[2] = 0; - - if (turb) - { - p->vel[0] = crandom() * 16; - p->vel[1] = crandom() * 16; - } - - // Rafael snow pvs check - p->snum = snum; - p->link = qtrue; - -} - -void CG_ParticleBubble (qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum) -{ - cparticle_t *p; - float randsize; - - if (!pshader) - CG_Printf ("CG_ParticleSnow pshader == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - p->color = 0; - p->alpha = 0.40f; - p->alphavel = 0; - p->start = origin[2]; - p->end = origin2[2]; - p->pshader = pshader; - - randsize = 1 + (crandom() * 0.5); - - p->height = randsize; - p->width = randsize; - - p->vel[2] = 50 + ( crandom() * 10 ); - - if (turb) - { - p->type = P_BUBBLE_TURBULENT; - p->vel[2] = 50 * 1.3; - } - else - { - p->type = P_BUBBLE; - } - - VectorCopy(origin, p->org); - - p->org[0] = p->org[0] + ( crandom() * range); - p->org[1] = p->org[1] + ( crandom() * range); - p->org[2] = p->org[2] + ( crandom() * (p->start - p->end)); - - p->vel[0] = p->vel[1] = 0; - - p->accel[0] = p->accel[1] = p->accel[2] = 0; - - if (turb) - { - p->vel[0] = crandom() * 4; - p->vel[1] = crandom() * 4; - } - - // Rafael snow pvs check - p->snum = snum; - p->link = qtrue; - -} - -void CG_ParticleSmoke (qhandle_t pshader, centity_t *cent) -{ - - // using cent->density = enttime - // cent->frame = startfade - cparticle_t *p; - - if (!pshader) - CG_Printf ("CG_ParticleSmoke == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - - p->endtime = cg.time + cent->currentState.time; - p->startfade = cg.time + cent->currentState.time2; - - p->color = 0; - p->alpha = 1.0; - p->alphavel = 0; - p->start = cent->currentState.origin[2]; - p->end = cent->currentState.origin2[2]; - p->pshader = pshader; - p->rotate = qfalse; - p->height = 8; - p->width = 8; - p->endheight = 32; - p->endwidth = 32; - p->type = P_SMOKE; - - VectorCopy(cent->currentState.origin, p->org); - - p->vel[0] = p->vel[1] = 0; - p->accel[0] = p->accel[1] = p->accel[2] = 0; - - p->vel[2] = 5; - - if (cent->currentState.frame == 1)// reverse gravity - p->vel[2] *= -1; - - p->roll = 8 + (crandom() * 4); -} - - -void CG_ParticleBulletDebris (vec3_t org, vec3_t vel, int duration) -{ - - cparticle_t *p; - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - - p->endtime = cg.time + duration; - p->startfade = cg.time + duration/2; - - p->color = EMISIVEFADE; - p->alpha = 1.0; - p->alphavel = 0; - - p->height = 0.5; - p->width = 0.5; - p->endheight = 0.5; - p->endwidth = 0.5; - - p->pshader = cgs.media.tracerShader; - - p->type = P_SMOKE; - - VectorCopy(org, p->org); - - p->vel[0] = vel[0]; - p->vel[1] = vel[1]; - p->vel[2] = vel[2]; - p->accel[0] = p->accel[1] = p->accel[2] = 0; - - p->accel[2] = -60; - p->vel[2] += -20; - -} - -/* -====================== -CG_ParticleExplosion -====================== -*/ - -void CG_ParticleExplosion (char *animStr, vec3_t origin, vec3_t vel, int duration, int sizeStart, int sizeEnd) -{ - cparticle_t *p; - int anim; - - if (animStr < (char *)10) - CG_Error( "CG_ParticleExplosion: animStr is probably an index rather than a string" ); - - // find the animation string - for (anim=0; shaderAnimNames[anim]; anim++) { - if (!Q_stricmp( animStr, shaderAnimNames[anim] )) - break; - } - if (!shaderAnimNames[anim]) { - CG_Error("CG_ParticleExplosion: unknown animation string: %s\n", animStr); - return; - } - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - p->alpha = 0.5; - p->alphavel = 0; - - if (duration < 0) { - duration *= -1; - p->roll = 0; - } else { - p->roll = crandom()*179; - } - - p->shaderAnim = anim; - - p->width = sizeStart; - p->height = sizeStart*shaderAnimSTRatio[anim]; // for sprites that are stretch in either direction - - p->endheight = sizeEnd; - p->endwidth = sizeEnd*shaderAnimSTRatio[anim]; - - p->endtime = cg.time + duration; - - p->type = P_ANIM; - - VectorCopy( origin, p->org ); - VectorCopy( vel, p->vel ); - VectorClear( p->accel ); - -} - -// Rafael Shrapnel -void CG_AddParticleShrapnel (localEntity_t *le) -{ - return; -} -// done. - -int CG_NewParticleArea (int num) -{ - // const char *str; - char *str; - char *token; - int type; - vec3_t origin, origin2; - int i; - float range = 0; - int turb; - int numparticles; - int snum; - - str = (char *) CG_ConfigString (num); - if (!str[0]) - return (0); - - // returns type 128 64 or 32 - token = COM_Parse (&str); - type = atoi (token); - - if (type == 1) - range = 128; - else if (type == 2) - range = 64; - else if (type == 3) - range = 32; - else if (type == 0) - range = 256; - else if (type == 4) - range = 8; - else if (type == 5) - range = 16; - else if (type == 6) - range = 32; - else if (type == 7) - range = 64; - - - for (i=0; i<3; i++) - { - token = COM_Parse (&str); - origin[i] = atof (token); - } - - for (i=0; i<3; i++) - { - token = COM_Parse (&str); - origin2[i] = atof (token); - } - - token = COM_Parse (&str); - numparticles = atoi (token); - - token = COM_Parse (&str); - turb = atoi (token); - - token = COM_Parse (&str); - snum = atoi (token); - - for (i=0; i= 4) - CG_ParticleBubble (cgs.media.waterBubbleShader, origin, origin2, turb, range, snum); - else - CG_ParticleSnow (cgs.media.waterBubbleShader, origin, origin2, turb, range, snum); - } - - return (1); -} - -void CG_SnowLink (centity_t *cent, qboolean particleOn) -{ - cparticle_t *p, *next; - int id; - - id = cent->currentState.frame; - - for (p=active_particles ; p ; p=next) - { - next = p->next; - - if (p->type == P_WEATHER || p->type == P_WEATHER_TURBULENT) - { - if (p->snum == id) - { - if (particleOn) - p->link = qtrue; - else - p->link = qfalse; - } - } - - } -} - -void CG_ParticleImpactSmokePuff (qhandle_t pshader, vec3_t origin) -{ - cparticle_t *p; - - if (!pshader) - CG_Printf ("CG_ParticleImpactSmokePuff pshader == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - p->alpha = 0.25; - p->alphavel = 0; - p->roll = crandom()*179; - - p->pshader = pshader; - - p->endtime = cg.time + 1000; - p->startfade = cg.time + 100; - - p->width = rand()%4 + 8; - p->height = rand()%4 + 8; - - p->endheight = p->height *2; - p->endwidth = p->width * 2; - - p->endtime = cg.time + 500; - - p->type = P_SMOKE_IMPACT; - - VectorCopy( origin, p->org ); - VectorSet(p->vel, 0, 0, 20); - VectorSet(p->accel, 0, 0, 20); - - p->rotate = qtrue; -} - -void CG_Particle_Bleed (qhandle_t pshader, vec3_t start, vec3_t dir, int fleshEntityNum, int duration) -{ - cparticle_t *p; - - if (!pshader) - CG_Printf ("CG_Particle_Bleed pshader == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - p->alpha = 1.0; - p->alphavel = 0; - p->roll = 0; - - p->pshader = pshader; - - p->endtime = cg.time + duration; - - if (fleshEntityNum) - p->startfade = cg.time; - else - p->startfade = cg.time + 100; - - p->width = 4; - p->height = 4; - - p->endheight = 4+rand()%3; - p->endwidth = p->endheight; - - p->type = P_SMOKE; - - VectorCopy( start, p->org ); - p->vel[0] = 0; - p->vel[1] = 0; - p->vel[2] = -20; - VectorClear( p->accel ); - - p->rotate = qfalse; - - p->roll = rand()%179; - - p->color = BLOODRED; - p->alpha = 0.75; - -} - -void CG_Particle_OilParticle (qhandle_t pshader, centity_t *cent) -{ - cparticle_t *p; - - int time; - int time2; - float ratio; - - float duration = 1500; - - time = cg.time; - time2 = cg.time + cent->currentState.time; - - ratio =(float)1 - ((float)time / (float)time2); - - if (!pshader) - CG_Printf ("CG_Particle_OilParticle == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - p->alpha = 1.0; - p->alphavel = 0; - p->roll = 0; - - p->pshader = pshader; - - p->endtime = cg.time + duration; - - p->startfade = p->endtime; - - p->width = 1; - p->height = 3; - - p->endheight = 3; - p->endwidth = 1; - - p->type = P_SMOKE; - - VectorCopy(cent->currentState.origin, p->org ); - - p->vel[0] = (cent->currentState.origin2[0] * (16 * ratio)); - p->vel[1] = (cent->currentState.origin2[1] * (16 * ratio)); - p->vel[2] = (cent->currentState.origin2[2]); - - p->snum = 1.0f; - - VectorClear( p->accel ); - - p->accel[2] = -20; - - p->rotate = qfalse; - - p->roll = rand()%179; - - p->alpha = 0.75; - -} - - -void CG_Particle_OilSlick (qhandle_t pshader, centity_t *cent) -{ - cparticle_t *p; - - if (!pshader) - CG_Printf ("CG_Particle_OilSlick == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - - if (cent->currentState.angles2[2]) - p->endtime = cg.time + cent->currentState.angles2[2]; - else - p->endtime = cg.time + 60000; - - p->startfade = p->endtime; - - p->alpha = 1.0; - p->alphavel = 0; - p->roll = 0; - - p->pshader = pshader; - - if (cent->currentState.angles2[0] || cent->currentState.angles2[1]) - { - p->width = cent->currentState.angles2[0]; - p->height = cent->currentState.angles2[0]; - - p->endheight = cent->currentState.angles2[1]; - p->endwidth = cent->currentState.angles2[1]; - } - else - { - p->width = 8; - p->height = 8; - - p->endheight = 16; - p->endwidth = 16; - } - - p->type = P_FLAT_SCALEUP; - - p->snum = 1.0; - - VectorCopy(cent->currentState.origin, p->org ); - - p->org[2]+= 0.55 + (crandom() * 0.5); - - p->vel[0] = 0; - p->vel[1] = 0; - p->vel[2] = 0; - VectorClear( p->accel ); - - p->rotate = qfalse; - - p->roll = rand()%179; - - p->alpha = 0.75; - -} - -void CG_OilSlickRemove (centity_t *cent) -{ - cparticle_t *p, *next; - int id; - - id = 1.0f; - - if (!id) - CG_Printf ("CG_OilSlickRevove NULL id\n"); - - for (p=active_particles ; p ; p=next) - { - next = p->next; - - if (p->type == P_FLAT_SCALEUP) - { - if (p->snum == id) - { - p->endtime = cg.time + 100; - p->startfade = p->endtime; - p->type = P_FLAT_SCALEUP_FADE; - - } - } - - } -} - -qboolean ValidBloodPool (vec3_t start) -{ -#define EXTRUDE_DIST 0.5 - - vec3_t angles; - vec3_t right, up; - vec3_t this_pos, x_pos, center_pos, end_pos; - float x, y; - float fwidth, fheight; - trace_t trace; - vec3_t normal; - - fwidth = 16; - fheight = 16; - - VectorSet (normal, 0, 0, 1); - - vectoangles (normal, angles); - AngleVectors (angles, NULL, right, up); - - VectorMA (start, EXTRUDE_DIST, normal, center_pos); - - for (x= -fwidth/2; xendpos, start); - legit = ValidBloodPool (start); - - if (!legit) - return; - - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - - p->endtime = cg.time + 3000; - p->startfade = p->endtime; - - p->alpha = 1.0; - p->alphavel = 0; - p->roll = 0; - - p->pshader = pshader; - - rndSize = 0.4 + random()*0.6; - - p->width = 8*rndSize; - p->height = 8*rndSize; - - p->endheight = 16*rndSize; - p->endwidth = 16*rndSize; - - p->type = P_FLAT_SCALEUP; - - VectorCopy(start, p->org ); - - p->vel[0] = 0; - p->vel[1] = 0; - p->vel[2] = 0; - VectorClear( p->accel ); - - p->rotate = qfalse; - - p->roll = rand()%179; - - p->alpha = 0.75; - - p->color = BLOODRED; -} - -#define NORMALSIZE 16 -#define LARGESIZE 32 - -void CG_ParticleBloodCloud (centity_t *cent, vec3_t origin, vec3_t dir) -{ - float length; - float dist; - float crittersize; - vec3_t angles, forward; - vec3_t point; - cparticle_t *p; - int i; - - dist = 0; - - length = VectorLength (dir); - vectoangles (dir, angles); - AngleVectors (angles, forward, NULL, NULL); - - crittersize = LARGESIZE; - - if (length) - dist = length / crittersize; - - if (dist < 1) - dist = 1; - - VectorCopy (origin, point); - - for (i=0; inext; - p->next = active_particles; - active_particles = p; - - p->time = cg.time; - p->alpha = 1.0; - p->alphavel = 0; - p->roll = 0; - - p->pshader = cgs.media.smokePuffShader; - - p->endtime = cg.time + 350 + (crandom() * 100); - - p->startfade = cg.time; - - p->width = LARGESIZE; - p->height = LARGESIZE; - p->endheight = LARGESIZE; - p->endwidth = LARGESIZE; - - p->type = P_SMOKE; - - VectorCopy( origin, p->org ); - - p->vel[0] = 0; - p->vel[1] = 0; - p->vel[2] = -1; - - VectorClear( p->accel ); - - p->rotate = qfalse; - - p->roll = rand()%179; - - p->color = BLOODRED; - - p->alpha = 0.75; - - } - - -} - -void CG_ParticleSparks (vec3_t org, vec3_t vel, int duration, float x, float y, float speed) -{ - cparticle_t *p; - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - - p->endtime = cg.time + duration; - p->startfade = cg.time + duration/2; - - p->color = EMISIVEFADE; - p->alpha = 0.4f; - p->alphavel = 0; - - p->height = 0.5; - p->width = 0.5; - p->endheight = 0.5; - p->endwidth = 0.5; - - p->pshader = cgs.media.tracerShader; - - p->type = P_SMOKE; - - VectorCopy(org, p->org); - - p->org[0] += (crandom() * x); - p->org[1] += (crandom() * y); - - p->vel[0] = vel[0]; - p->vel[1] = vel[1]; - p->vel[2] = vel[2]; - - p->accel[0] = p->accel[1] = p->accel[2] = 0; - - p->vel[0] += (crandom() * 4); - p->vel[1] += (crandom() * 4); - p->vel[2] += (20 + (crandom() * 10)) * speed; - - p->accel[0] = crandom () * 4; - p->accel[1] = crandom () * 4; - -} - -void CG_ParticleDust (centity_t *cent, vec3_t origin, vec3_t dir) -{ - float length; - float dist; - float crittersize; - vec3_t angles, forward; - vec3_t point; - cparticle_t *p; - int i; - - dist = 0; - - VectorNegate (dir, dir); - length = VectorLength (dir); - vectoangles (dir, angles); - AngleVectors (angles, forward, NULL, NULL); - - crittersize = LARGESIZE; - - if (length) - dist = length / crittersize; - - if (dist < 1) - dist = 1; - - VectorCopy (origin, point); - - for (i=0; inext; - p->next = active_particles; - active_particles = p; - - p->time = cg.time; - p->alpha = 5.0; - p->alphavel = 0; - p->roll = 0; - - p->pshader = cgs.media.smokePuffShader; - - // RF, stay around for long enough to expand and dissipate naturally - if (length) - p->endtime = cg.time + 4500 + (crandom() * 3500); - else - p->endtime = cg.time + 750 + (crandom() * 500); - - p->startfade = cg.time; - - p->width = LARGESIZE; - p->height = LARGESIZE; - - // RF, expand while falling - p->endheight = LARGESIZE*3.0; - p->endwidth = LARGESIZE*3.0; - - if (!length) - { - p->width *= 0.2f; - p->height *= 0.2f; - - p->endheight = NORMALSIZE; - p->endwidth = NORMALSIZE; - } - - p->type = P_SMOKE; - - VectorCopy( point, p->org ); - - p->vel[0] = crandom()*6; - p->vel[1] = crandom()*6; - p->vel[2] = random()*20; - - // RF, add some gravity/randomness - p->accel[0] = crandom()*3; - p->accel[1] = crandom()*3; - p->accel[2] = -PARTICLE_GRAVITY*0.4; - - VectorClear( p->accel ); - - p->rotate = qfalse; - - p->roll = rand()%179; - - p->alpha = 0.75; - - } - - -} - -void CG_ParticleMisc (qhandle_t pshader, vec3_t origin, int size, int duration, float alpha) -{ - cparticle_t *p; - - if (!pshader) - CG_Printf ("CG_ParticleImpactSmokePuff pshader == ZERO!\n"); - - if (!free_particles) - return; - - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - p->alpha = 1.0; - p->alphavel = 0; - p->roll = rand()%179; - - p->pshader = pshader; - - if (duration > 0) - p->endtime = cg.time + duration; - else - p->endtime = duration; - - p->startfade = cg.time; - - p->width = size; - p->height = size; - - p->endheight = size; - p->endwidth = size; - - p->type = P_SPRITE; - - VectorCopy( origin, p->org ); - - p->rotate = qfalse; -} - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// cg_marks.c -- wall marks + +#include "cg_local.h" + +/* +=================================================================== + +MARK POLYS + +=================================================================== +*/ + + +markPoly_t cg_activeMarkPolys; // double linked list +markPoly_t *cg_freeMarkPolys; // single linked list +markPoly_t cg_markPolys[MAX_MARK_POLYS]; +static int markTotal; + +/* +=================== +CG_InitMarkPolys + +This is called at startup and for tournement restarts +=================== +*/ +void CG_InitMarkPolys( void ) { + int i; + + memset( cg_markPolys, 0, sizeof(cg_markPolys) ); + + cg_activeMarkPolys.nextMark = &cg_activeMarkPolys; + cg_activeMarkPolys.prevMark = &cg_activeMarkPolys; + cg_freeMarkPolys = cg_markPolys; + for ( i = 0 ; i < MAX_MARK_POLYS - 1 ; i++ ) { + cg_markPolys[i].nextMark = &cg_markPolys[i+1]; + } +} + + +/* +================== +CG_FreeMarkPoly +================== +*/ +void CG_FreeMarkPoly( markPoly_t *le ) { + if ( !le->prevMark ) { + CG_Error( "CG_FreeLocalEntity: not active" ); + } + + // remove from the doubly linked active list + le->prevMark->nextMark = le->nextMark; + le->nextMark->prevMark = le->prevMark; + + // the free list is only singly linked + le->nextMark = cg_freeMarkPolys; + cg_freeMarkPolys = le; +} + +/* +=================== +CG_AllocMark + +Will allways succeed, even if it requires freeing an old active mark +=================== +*/ +markPoly_t *CG_AllocMark( void ) { + markPoly_t *le; + int time; + + if ( !cg_freeMarkPolys ) { + // no free entities, so free the one at the end of the chain + // remove the oldest active entity + time = cg_activeMarkPolys.prevMark->time; + while (cg_activeMarkPolys.prevMark && time == cg_activeMarkPolys.prevMark->time) { + CG_FreeMarkPoly( cg_activeMarkPolys.prevMark ); + } + } + + le = cg_freeMarkPolys; + cg_freeMarkPolys = cg_freeMarkPolys->nextMark; + + memset( le, 0, sizeof( *le ) ); + + // link into the active list + le->nextMark = cg_activeMarkPolys.nextMark; + le->prevMark = &cg_activeMarkPolys; + cg_activeMarkPolys.nextMark->prevMark = le; + cg_activeMarkPolys.nextMark = le; + return le; +} + + + +/* +================= +CG_ImpactMark + +origin should be a point within a unit of the plane +dir should be the plane normal + +temporary marks will not be stored or randomly oriented, but immediately +passed to the renderer. +================= +*/ +#define MAX_MARK_FRAGMENTS 128 +#define MAX_MARK_POINTS 384 + +void CG_ImpactMark( qhandle_t markShader, const vec3_t origin, const vec3_t dir, + float orientation, float red, float green, float blue, float alpha, + qboolean alphaFade, float radius, qboolean temporary ) { + vec3_t axis[3]; + float texCoordScale; + vec3_t originalPoints[4]; + byte colors[4]; + int i, j; + int numFragments; + markFragment_t markFragments[MAX_MARK_FRAGMENTS], *mf; + vec3_t markPoints[MAX_MARK_POINTS]; + vec3_t projection; + + if ( !cg_addMarks.integer ) { + return; + } + + if ( radius <= 0 ) { + CG_Error( "CG_ImpactMark called with <= 0 radius" ); + } + + //if ( markTotal >= MAX_MARK_POLYS ) { + // return; + //} + + // create the texture axis + VectorNormalize2( dir, axis[0] ); + PerpendicularVector( axis[1], axis[0] ); + RotatePointAroundVector( axis[2], axis[0], axis[1], orientation ); + CrossProduct( axis[0], axis[2], axis[1] ); + + texCoordScale = 0.5 * 1.0 / radius; + + // create the full polygon + for ( i = 0 ; i < 3 ; i++ ) { + originalPoints[0][i] = origin[i] - radius * axis[1][i] - radius * axis[2][i]; + originalPoints[1][i] = origin[i] + radius * axis[1][i] - radius * axis[2][i]; + originalPoints[2][i] = origin[i] + radius * axis[1][i] + radius * axis[2][i]; + originalPoints[3][i] = origin[i] - radius * axis[1][i] + radius * axis[2][i]; + } + + // get the fragments + VectorScale( dir, -20, projection ); + numFragments = trap_CM_MarkFragments( 4, (void *)originalPoints, + projection, MAX_MARK_POINTS, markPoints[0], + MAX_MARK_FRAGMENTS, markFragments ); + + colors[0] = red * 255; + colors[1] = green * 255; + colors[2] = blue * 255; + colors[3] = alpha * 255; + + for ( i = 0, mf = markFragments ; i < numFragments ; i++, mf++ ) { + polyVert_t *v; + polyVert_t verts[MAX_VERTS_ON_POLY]; + markPoly_t *mark; + + // we have an upper limit on the complexity of polygons + // that we store persistantly + if ( mf->numPoints > MAX_VERTS_ON_POLY ) { + mf->numPoints = MAX_VERTS_ON_POLY; + } + for ( j = 0, v = verts ; j < mf->numPoints ; j++, v++ ) { + vec3_t delta; + + VectorCopy( markPoints[mf->firstPoint + j], v->xyz ); + + VectorSubtract( v->xyz, origin, delta ); + v->st[0] = 0.5 + DotProduct( delta, axis[1] ) * texCoordScale; + v->st[1] = 0.5 + DotProduct( delta, axis[2] ) * texCoordScale; + *(int *)v->modulate = *(int *)colors; + } + + // if it is a temporary (shadow) mark, add it immediately and forget about it + if ( temporary ) { + trap_R_AddPolyToScene( markShader, mf->numPoints, verts ); + continue; + } + + // otherwise save it persistantly + mark = CG_AllocMark(); + mark->time = cg.time; + mark->alphaFade = alphaFade; + mark->markShader = markShader; + mark->poly.numVerts = mf->numPoints; + mark->color[0] = red; + mark->color[1] = green; + mark->color[2] = blue; + mark->color[3] = alpha; + memcpy( mark->verts, verts, mf->numPoints * sizeof( verts[0] ) ); + markTotal++; + } +} + + +/* +=============== +CG_AddMarks +=============== +*/ +#define MARK_TOTAL_TIME 10000 +#define MARK_FADE_TIME 1000 + +void CG_AddMarks( void ) { + int j; + markPoly_t *mp, *next; + int t; + int fade; + + if ( !cg_addMarks.integer ) { + return; + } + + mp = cg_activeMarkPolys.nextMark; + for ( ; mp != &cg_activeMarkPolys ; mp = next ) { + // grab next now, so if the local entity is freed we + // still have it + next = mp->nextMark; + + // see if it is time to completely remove it + if ( cg.time > mp->time + MARK_TOTAL_TIME ) { + CG_FreeMarkPoly( mp ); + continue; + } + + // fade out the energy bursts + if ( mp->markShader == cgs.media.energyMarkShader ) { + + fade = 450 - 450 * ( (cg.time - mp->time ) / 3000.0 ); + if ( fade < 255 ) { + if ( fade < 0 ) { + fade = 0; + } + if ( mp->verts[0].modulate[0] != 0 ) { + for ( j = 0 ; j < mp->poly.numVerts ; j++ ) { + mp->verts[j].modulate[0] = mp->color[0] * fade; + mp->verts[j].modulate[1] = mp->color[1] * fade; + mp->verts[j].modulate[2] = mp->color[2] * fade; + } + } + } + } + + // fade all marks out with time + t = mp->time + MARK_TOTAL_TIME - cg.time; + if ( t < MARK_FADE_TIME ) { + fade = 255 * t / MARK_FADE_TIME; + if ( mp->alphaFade ) { + for ( j = 0 ; j < mp->poly.numVerts ; j++ ) { + mp->verts[j].modulate[3] = fade; + } + } else { + for ( j = 0 ; j < mp->poly.numVerts ; j++ ) { + mp->verts[j].modulate[0] = mp->color[0] * fade; + mp->verts[j].modulate[1] = mp->color[1] * fade; + mp->verts[j].modulate[2] = mp->color[2] * fade; + } + } + } + + + trap_R_AddPolyToScene( mp->markShader, mp->poly.numVerts, mp->verts ); + } +} + +// cg_particles.c + +#define BLOODRED 2 +#define EMISIVEFADE 3 +#define GREY75 4 + +typedef struct particle_s +{ + struct particle_s *next; + + float time; + float endtime; + + vec3_t org; + vec3_t vel; + vec3_t accel; + int color; + float colorvel; + float alpha; + float alphavel; + int type; + qhandle_t pshader; + + float height; + float width; + + float endheight; + float endwidth; + + float start; + float end; + + float startfade; + qboolean rotate; + int snum; + + qboolean link; + + // Ridah + int shaderAnim; + int roll; + + int accumroll; + +} cparticle_t; + +typedef enum +{ + P_NONE, + P_WEATHER, + P_FLAT, + P_SMOKE, + P_ROTATE, + P_WEATHER_TURBULENT, + P_ANIM, // Ridah + P_BAT, + P_BLEED, + P_FLAT_SCALEUP, + P_FLAT_SCALEUP_FADE, + P_WEATHER_FLURRY, + P_SMOKE_IMPACT, + P_BUBBLE, + P_BUBBLE_TURBULENT, + P_SPRITE +} particle_type_t; + +#define MAX_SHADER_ANIMS 32 +#define MAX_SHADER_ANIM_FRAMES 64 + +static char *shaderAnimNames[MAX_SHADER_ANIMS] = { + "explode1", + NULL +}; +static qhandle_t shaderAnims[MAX_SHADER_ANIMS][MAX_SHADER_ANIM_FRAMES]; +static int shaderAnimCounts[MAX_SHADER_ANIMS] = { + 23 +}; +static float shaderAnimSTRatio[MAX_SHADER_ANIMS] = { + 1.0f +}; +static int numShaderAnims; +// done. + +#define PARTICLE_GRAVITY 40 +#define MAX_PARTICLES 1024 + +cparticle_t *active_particles, *free_particles; +cparticle_t particles[MAX_PARTICLES]; +int cl_numparticles = MAX_PARTICLES; + +qboolean initparticles = qfalse; +vec3_t pvforward, pvright, pvup; +vec3_t rforward, rright, rup; + +float oldtime; + +/* +=============== +CL_ClearParticles +=============== +*/ +void CG_ClearParticles (void) +{ + int i; + + memset( particles, 0, sizeof(particles) ); + + free_particles = &particles[0]; + active_particles = NULL; + + for (i=0 ;itype == P_WEATHER || p->type == P_WEATHER_TURBULENT || p->type == P_WEATHER_FLURRY + || p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) + {// create a front facing polygon + + if (p->type != P_WEATHER_FLURRY) + { + if (p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) + { + if (org[2] > p->end) + { + p->time = cg.time; + VectorCopy (org, p->org); // Ridah, fixes rare snow flakes that flicker on the ground + + p->org[2] = ( p->start + crandom () * 4 ); + + + if (p->type == P_BUBBLE_TURBULENT) + { + p->vel[0] = crandom() * 4; + p->vel[1] = crandom() * 4; + } + + } + } + else + { + if (org[2] < p->end) + { + p->time = cg.time; + VectorCopy (org, p->org); // Ridah, fixes rare snow flakes that flicker on the ground + + while (p->org[2] < p->end) + { + p->org[2] += (p->start - p->end); + } + + + if (p->type == P_WEATHER_TURBULENT) + { + p->vel[0] = crandom() * 16; + p->vel[1] = crandom() * 16; + } + + } + } + + + // Rafael snow pvs check + if (!p->link) + return; + + p->alpha = 1; + } + + // Ridah, had to do this or MAX_POLYS is being exceeded in village1.bsp + if (Distance( cg.snap->ps.origin, org ) > 1024) { + return; + } + // done. + + if (p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) + { + VectorMA (org, -p->height, pvup, point); + VectorMA (point, -p->width, pvright, point); + VectorCopy (point, verts[0].xyz); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255 * p->alpha; + + VectorMA (org, -p->height, pvup, point); + VectorMA (point, p->width, pvright, point); + VectorCopy (point, verts[1].xyz); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255 * p->alpha; + + VectorMA (org, p->height, pvup, point); + VectorMA (point, p->width, pvright, point); + VectorCopy (point, verts[2].xyz); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255 * p->alpha; + + VectorMA (org, p->height, pvup, point); + VectorMA (point, -p->width, pvright, point); + VectorCopy (point, verts[3].xyz); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255 * p->alpha; + } + else + { + VectorMA (org, -p->height, pvup, point); + VectorMA (point, -p->width, pvright, point); + VectorCopy( point, TRIverts[0].xyz ); + TRIverts[0].st[0] = 1; + TRIverts[0].st[1] = 0; + TRIverts[0].modulate[0] = 255; + TRIverts[0].modulate[1] = 255; + TRIverts[0].modulate[2] = 255; + TRIverts[0].modulate[3] = 255 * p->alpha; + + VectorMA (org, p->height, pvup, point); + VectorMA (point, -p->width, pvright, point); + VectorCopy (point, TRIverts[1].xyz); + TRIverts[1].st[0] = 0; + TRIverts[1].st[1] = 0; + TRIverts[1].modulate[0] = 255; + TRIverts[1].modulate[1] = 255; + TRIverts[1].modulate[2] = 255; + TRIverts[1].modulate[3] = 255 * p->alpha; + + VectorMA (org, p->height, pvup, point); + VectorMA (point, p->width, pvright, point); + VectorCopy (point, TRIverts[2].xyz); + TRIverts[2].st[0] = 0; + TRIverts[2].st[1] = 1; + TRIverts[2].modulate[0] = 255; + TRIverts[2].modulate[1] = 255; + TRIverts[2].modulate[2] = 255; + TRIverts[2].modulate[3] = 255 * p->alpha; + } + + } + else if (p->type == P_SPRITE) + { + vec3_t rr, ru; + vec3_t rotate_ang; + + VectorSet (color, 1.0, 1.0, 0.5); + time = cg.time - p->time; + time2 = p->endtime - p->time; + ratio = time / time2; + + width = p->width + ( ratio * ( p->endwidth - p->width) ); + height = p->height + ( ratio * ( p->endheight - p->height) ); + + if (p->roll) { + vectoangles( cg.refdef.viewaxis[0], rotate_ang ); + rotate_ang[ROLL] += p->roll; + AngleVectors ( rotate_ang, NULL, rr, ru); + } + + if (p->roll) { + VectorMA (org, -height, ru, point); + VectorMA (point, -width, rr, point); + } else { + VectorMA (org, -height, pvup, point); + VectorMA (point, -width, pvright, point); + } + VectorCopy (point, verts[0].xyz); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, 2*height, ru, point); + } else { + VectorMA (point, 2*height, pvup, point); + } + VectorCopy (point, verts[1].xyz); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, 2*width, rr, point); + } else { + VectorMA (point, 2*width, pvright, point); + } + VectorCopy (point, verts[2].xyz); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, -2*height, ru, point); + } else { + VectorMA (point, -2*height, pvup, point); + } + VectorCopy (point, verts[3].xyz); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255; + } + else if (p->type == P_SMOKE || p->type == P_SMOKE_IMPACT) + {// create a front rotating facing polygon + + if ( p->type == P_SMOKE_IMPACT && Distance( cg.snap->ps.origin, org ) > 1024) { + return; + } + + if (p->color == BLOODRED) + VectorSet (color, 0.22f, 0.0f, 0.0f); + else if (p->color == GREY75) + { + float len; + float greyit; + float val; + len = Distance (cg.snap->ps.origin, org); + if (!len) + len = 1; + + val = 4096/len; + greyit = 0.25 * val; + if (greyit > 0.5) + greyit = 0.5; + + VectorSet (color, greyit, greyit, greyit); + } + else + VectorSet (color, 1.0, 1.0, 1.0); + + time = cg.time - p->time; + time2 = p->endtime - p->time; + ratio = time / time2; + + if (cg.time > p->startfade) + { + invratio = 1 - ( (cg.time - p->startfade) / (p->endtime - p->startfade) ); + + if (p->color == EMISIVEFADE) + { + float fval; + fval = (invratio * invratio); + if (fval < 0) + fval = 0; + VectorSet (color, fval , fval , fval ); + } + invratio *= p->alpha; + } + else + invratio = 1 * p->alpha; + + if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) + invratio = 1; + + if (invratio > 1) + invratio = 1; + + width = p->width + ( ratio * ( p->endwidth - p->width) ); + height = p->height + ( ratio * ( p->endheight - p->height) ); + + if (p->type != P_SMOKE_IMPACT) + { + vec3_t temp; + + vectoangles (rforward, temp); + p->accumroll += p->roll; + temp[ROLL] += p->accumroll * 0.1; + AngleVectors ( temp, NULL, rright2, rup2); + } + else + { + VectorCopy (rright, rright2); + VectorCopy (rup, rup2); + } + + if (p->rotate) + { + VectorMA (org, -height, rup2, point); + VectorMA (point, -width, rright2, point); + } + else + { + VectorMA (org, -p->height, pvup, point); + VectorMA (point, -p->width, pvright, point); + } + VectorCopy (point, verts[0].xyz); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255 * color[0]; + verts[0].modulate[1] = 255 * color[1]; + verts[0].modulate[2] = 255 * color[2]; + verts[0].modulate[3] = 255 * invratio; + + if (p->rotate) + { + VectorMA (org, -height, rup2, point); + VectorMA (point, width, rright2, point); + } + else + { + VectorMA (org, -p->height, pvup, point); + VectorMA (point, p->width, pvright, point); + } + VectorCopy (point, verts[1].xyz); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255 * color[0]; + verts[1].modulate[1] = 255 * color[1]; + verts[1].modulate[2] = 255 * color[2]; + verts[1].modulate[3] = 255 * invratio; + + if (p->rotate) + { + VectorMA (org, height, rup2, point); + VectorMA (point, width, rright2, point); + } + else + { + VectorMA (org, p->height, pvup, point); + VectorMA (point, p->width, pvright, point); + } + VectorCopy (point, verts[2].xyz); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255 * color[0]; + verts[2].modulate[1] = 255 * color[1]; + verts[2].modulate[2] = 255 * color[2]; + verts[2].modulate[3] = 255 * invratio; + + if (p->rotate) + { + VectorMA (org, height, rup2, point); + VectorMA (point, -width, rright2, point); + } + else + { + VectorMA (org, p->height, pvup, point); + VectorMA (point, -p->width, pvright, point); + } + VectorCopy (point, verts[3].xyz); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255 * color[0]; + verts[3].modulate[1] = 255 * color[1]; + verts[3].modulate[2] = 255 * color[2]; + verts[3].modulate[3] = 255 * invratio; + + } + else if (p->type == P_BLEED) + { + vec3_t rr, ru; + vec3_t rotate_ang; + float alpha; + + alpha = p->alpha; + + if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) + alpha = 1; + + if (p->roll) + { + vectoangles( cg.refdef.viewaxis[0], rotate_ang ); + rotate_ang[ROLL] += p->roll; + AngleVectors ( rotate_ang, NULL, rr, ru); + } + else + { + VectorCopy (pvup, ru); + VectorCopy (pvright, rr); + } + + VectorMA (org, -p->height, ru, point); + VectorMA (point, -p->width, rr, point); + VectorCopy (point, verts[0].xyz); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 111; + verts[0].modulate[1] = 19; + verts[0].modulate[2] = 9; + verts[0].modulate[3] = 255 * alpha; + + VectorMA (org, -p->height, ru, point); + VectorMA (point, p->width, rr, point); + VectorCopy (point, verts[1].xyz); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 111; + verts[1].modulate[1] = 19; + verts[1].modulate[2] = 9; + verts[1].modulate[3] = 255 * alpha; + + VectorMA (org, p->height, ru, point); + VectorMA (point, p->width, rr, point); + VectorCopy (point, verts[2].xyz); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 111; + verts[2].modulate[1] = 19; + verts[2].modulate[2] = 9; + verts[2].modulate[3] = 255 * alpha; + + VectorMA (org, p->height, ru, point); + VectorMA (point, -p->width, rr, point); + VectorCopy (point, verts[3].xyz); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 111; + verts[3].modulate[1] = 19; + verts[3].modulate[2] = 9; + verts[3].modulate[3] = 255 * alpha; + + } + else if (p->type == P_FLAT_SCALEUP) + { + float width, height; + float sinR, cosR; + + if (p->color == BLOODRED) + VectorSet (color, 1, 1, 1); + else + VectorSet (color, 0.5, 0.5, 0.5); + + time = cg.time - p->time; + time2 = p->endtime - p->time; + ratio = time / time2; + + width = p->width + ( ratio * ( p->endwidth - p->width) ); + height = p->height + ( ratio * ( p->endheight - p->height) ); + + if (width > p->endwidth) + width = p->endwidth; + + if (height > p->endheight) + height = p->endheight; + + sinR = height * sin(DEG2RAD(p->roll)) * sqrt(2); + cosR = width * cos(DEG2RAD(p->roll)) * sqrt(2); + + VectorCopy (org, verts[0].xyz); + verts[0].xyz[0] -= sinR; + verts[0].xyz[1] -= cosR; + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255 * color[0]; + verts[0].modulate[1] = 255 * color[1]; + verts[0].modulate[2] = 255 * color[2]; + verts[0].modulate[3] = 255; + + VectorCopy (org, verts[1].xyz); + verts[1].xyz[0] -= cosR; + verts[1].xyz[1] += sinR; + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255 * color[0]; + verts[1].modulate[1] = 255 * color[1]; + verts[1].modulate[2] = 255 * color[2]; + verts[1].modulate[3] = 255; + + VectorCopy (org, verts[2].xyz); + verts[2].xyz[0] += sinR; + verts[2].xyz[1] += cosR; + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255 * color[0]; + verts[2].modulate[1] = 255 * color[1]; + verts[2].modulate[2] = 255 * color[2]; + verts[2].modulate[3] = 255; + + VectorCopy (org, verts[3].xyz); + verts[3].xyz[0] += cosR; + verts[3].xyz[1] -= sinR; + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255 * color[0]; + verts[3].modulate[1] = 255 * color[1]; + verts[3].modulate[2] = 255 * color[2]; + verts[3].modulate[3] = 255; + } + else if (p->type == P_FLAT) + { + + VectorCopy (org, verts[0].xyz); + verts[0].xyz[0] -= p->height; + verts[0].xyz[1] -= p->width; + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255; + + VectorCopy (org, verts[1].xyz); + verts[1].xyz[0] -= p->height; + verts[1].xyz[1] += p->width; + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255; + + VectorCopy (org, verts[2].xyz); + verts[2].xyz[0] += p->height; + verts[2].xyz[1] += p->width; + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255; + + VectorCopy (org, verts[3].xyz); + verts[3].xyz[0] += p->height; + verts[3].xyz[1] -= p->width; + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255; + + } + // Ridah + else if (p->type == P_ANIM) { + vec3_t rr, ru; + vec3_t rotate_ang; + int i, j; + + time = cg.time - p->time; + time2 = p->endtime - p->time; + ratio = time / time2; + if (ratio >= 1.0f) { + ratio = 0.9999f; + } + + width = p->width + ( ratio * ( p->endwidth - p->width) ); + height = p->height + ( ratio * ( p->endheight - p->height) ); + + // if we are "inside" this sprite, don't draw + if (Distance( cg.snap->ps.origin, org ) < width/1.5) { + return; + } + + i = p->shaderAnim; + j = (int)floor(ratio * shaderAnimCounts[p->shaderAnim]); + p->pshader = shaderAnims[i][j]; + + if (p->roll) { + vectoangles( cg.refdef.viewaxis[0], rotate_ang ); + rotate_ang[ROLL] += p->roll; + AngleVectors ( rotate_ang, NULL, rr, ru); + } + + if (p->roll) { + VectorMA (org, -height, ru, point); + VectorMA (point, -width, rr, point); + } else { + VectorMA (org, -height, pvup, point); + VectorMA (point, -width, pvright, point); + } + VectorCopy (point, verts[0].xyz); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, 2*height, ru, point); + } else { + VectorMA (point, 2*height, pvup, point); + } + VectorCopy (point, verts[1].xyz); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, 2*width, rr, point); + } else { + VectorMA (point, 2*width, pvright, point); + } + VectorCopy (point, verts[2].xyz); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, -2*height, ru, point); + } else { + VectorMA (point, -2*height, pvup, point); + } + VectorCopy (point, verts[3].xyz); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255; + } + // done. + + if (!p->pshader) { +// (SA) temp commented out for DM +// CG_Printf ("CG_AddParticleToScene type %d p->pshader == ZERO\n", p->type); + return; + } + + if (p->type == P_WEATHER || p->type == P_WEATHER_TURBULENT || p->type == P_WEATHER_FLURRY) + trap_R_AddPolyToScene( p->pshader, 3, TRIverts ); + else + trap_R_AddPolyToScene( p->pshader, 4, verts ); + +} + +// Ridah, made this static so it doesn't interfere with other files +static float roll = 0.0; + +/* +=============== +CG_AddParticles +=============== +*/ +void CG_AddParticles (void) +{ + cparticle_t *p, *next; + float alpha; + float time, time2; + vec3_t org; + int color; + cparticle_t *active, *tail; + int type; + vec3_t rotate_ang; + + if (!initparticles) + CG_ClearParticles (); + + VectorCopy( cg.refdef.viewaxis[0], pvforward ); + VectorCopy( cg.refdef.viewaxis[1], pvright ); + VectorCopy( cg.refdef.viewaxis[2], pvup ); + + vectoangles( cg.refdef.viewaxis[0], rotate_ang ); + roll += ((cg.time - oldtime) * 0.1) ; + rotate_ang[ROLL] += (roll*0.9); + AngleVectors ( rotate_ang, rforward, rright, rup); + + oldtime = cg.time; + + active = NULL; + tail = NULL; + + for (p=active_particles ; p ; p=next) + { + + next = p->next; + + time = (cg.time - p->time)*0.001; + + alpha = p->alpha + time*p->alphavel; + if (alpha <= 0) + { // faded out + p->next = free_particles; + free_particles = p; + p->type = 0; + p->color = 0; + p->alpha = 0; + continue; + } + + if (p->type == P_SMOKE || p->type == P_ANIM || p->type == P_BLEED || p->type == P_SMOKE_IMPACT) + { + if (cg.time > p->endtime) + { + p->next = free_particles; + free_particles = p; + p->type = 0; + p->color = 0; + p->alpha = 0; + + continue; + } + + } + + if (p->type == P_WEATHER_FLURRY) + { + if (cg.time > p->endtime) + { + p->next = free_particles; + free_particles = p; + p->type = 0; + p->color = 0; + p->alpha = 0; + + continue; + } + } + + + if (p->type == P_FLAT_SCALEUP_FADE) + { + if (cg.time > p->endtime) + { + p->next = free_particles; + free_particles = p; + p->type = 0; + p->color = 0; + p->alpha = 0; + continue; + } + + } + + if ((p->type == P_BAT || p->type == P_SPRITE) && p->endtime < 0) { + // temporary sprite + CG_AddParticleToScene (p, p->org, alpha); + p->next = free_particles; + free_particles = p; + p->type = 0; + p->color = 0; + p->alpha = 0; + continue; + } + + p->next = NULL; + if (!tail) + active = tail = p; + else + { + tail->next = p; + tail = p; + } + + if (alpha > 1.0) + alpha = 1; + + color = p->color; + + time2 = time*time; + + org[0] = p->org[0] + p->vel[0]*time + p->accel[0]*time2; + org[1] = p->org[1] + p->vel[1]*time + p->accel[1]*time2; + org[2] = p->org[2] + p->vel[2]*time + p->accel[2]*time2; + + type = p->type; + + CG_AddParticleToScene (p, org, alpha); + } + + active_particles = active; +} + +/* +====================== +CG_AddParticles +====================== +*/ +void CG_ParticleSnowFlurry (qhandle_t pshader, centity_t *cent) +{ + cparticle_t *p; + qboolean turb = qtrue; + + if (!pshader) + CG_Printf ("CG_ParticleSnowFlurry pshader == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->color = 0; + p->alpha = 0.90f; + p->alphavel = 0; + + p->start = cent->currentState.origin2[0]; + p->end = cent->currentState.origin2[1]; + + p->endtime = cg.time + cent->currentState.time; + p->startfade = cg.time + cent->currentState.time2; + + p->pshader = pshader; + + if (rand()%100 > 90) + { + p->height = 32; + p->width = 32; + p->alpha = 0.10f; + } + else + { + p->height = 1; + p->width = 1; + } + + p->vel[2] = -20; + + p->type = P_WEATHER_FLURRY; + + if (turb) + p->vel[2] = -10; + + VectorCopy(cent->currentState.origin, p->org); + + p->org[0] = p->org[0]; + p->org[1] = p->org[1]; + p->org[2] = p->org[2]; + + p->vel[0] = p->vel[1] = 0; + + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + p->vel[0] += cent->currentState.angles[0] * 32 + (crandom() * 16); + p->vel[1] += cent->currentState.angles[1] * 32 + (crandom() * 16); + p->vel[2] += cent->currentState.angles[2]; + + if (turb) + { + p->accel[0] = crandom () * 16; + p->accel[1] = crandom () * 16; + } + +} + +void CG_ParticleSnow (qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum) +{ + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_ParticleSnow pshader == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->color = 0; + p->alpha = 0.40f; + p->alphavel = 0; + p->start = origin[2]; + p->end = origin2[2]; + p->pshader = pshader; + p->height = 1; + p->width = 1; + + p->vel[2] = -50; + + if (turb) + { + p->type = P_WEATHER_TURBULENT; + p->vel[2] = -50 * 1.3; + } + else + { + p->type = P_WEATHER; + } + + VectorCopy(origin, p->org); + + p->org[0] = p->org[0] + ( crandom() * range); + p->org[1] = p->org[1] + ( crandom() * range); + p->org[2] = p->org[2] + ( crandom() * (p->start - p->end)); + + p->vel[0] = p->vel[1] = 0; + + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + if (turb) + { + p->vel[0] = crandom() * 16; + p->vel[1] = crandom() * 16; + } + + // Rafael snow pvs check + p->snum = snum; + p->link = qtrue; + +} + +void CG_ParticleBubble (qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum) +{ + cparticle_t *p; + float randsize; + + if (!pshader) + CG_Printf ("CG_ParticleSnow pshader == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->color = 0; + p->alpha = 0.40f; + p->alphavel = 0; + p->start = origin[2]; + p->end = origin2[2]; + p->pshader = pshader; + + randsize = 1 + (crandom() * 0.5); + + p->height = randsize; + p->width = randsize; + + p->vel[2] = 50 + ( crandom() * 10 ); + + if (turb) + { + p->type = P_BUBBLE_TURBULENT; + p->vel[2] = 50 * 1.3; + } + else + { + p->type = P_BUBBLE; + } + + VectorCopy(origin, p->org); + + p->org[0] = p->org[0] + ( crandom() * range); + p->org[1] = p->org[1] + ( crandom() * range); + p->org[2] = p->org[2] + ( crandom() * (p->start - p->end)); + + p->vel[0] = p->vel[1] = 0; + + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + if (turb) + { + p->vel[0] = crandom() * 4; + p->vel[1] = crandom() * 4; + } + + // Rafael snow pvs check + p->snum = snum; + p->link = qtrue; + +} + +void CG_ParticleSmoke (qhandle_t pshader, centity_t *cent) +{ + + // using cent->density = enttime + // cent->frame = startfade + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_ParticleSmoke == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + + p->endtime = cg.time + cent->currentState.time; + p->startfade = cg.time + cent->currentState.time2; + + p->color = 0; + p->alpha = 1.0; + p->alphavel = 0; + p->start = cent->currentState.origin[2]; + p->end = cent->currentState.origin2[2]; + p->pshader = pshader; + p->rotate = qfalse; + p->height = 8; + p->width = 8; + p->endheight = 32; + p->endwidth = 32; + p->type = P_SMOKE; + + VectorCopy(cent->currentState.origin, p->org); + + p->vel[0] = p->vel[1] = 0; + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + p->vel[2] = 5; + + if (cent->currentState.frame == 1)// reverse gravity + p->vel[2] *= -1; + + p->roll = 8 + (crandom() * 4); +} + + +void CG_ParticleBulletDebris (vec3_t org, vec3_t vel, int duration) +{ + + cparticle_t *p; + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + + p->endtime = cg.time + duration; + p->startfade = cg.time + duration/2; + + p->color = EMISIVEFADE; + p->alpha = 1.0; + p->alphavel = 0; + + p->height = 0.5; + p->width = 0.5; + p->endheight = 0.5; + p->endwidth = 0.5; + + p->pshader = cgs.media.tracerShader; + + p->type = P_SMOKE; + + VectorCopy(org, p->org); + + p->vel[0] = vel[0]; + p->vel[1] = vel[1]; + p->vel[2] = vel[2]; + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + p->accel[2] = -60; + p->vel[2] += -20; + +} + +/* +====================== +CG_ParticleExplosion +====================== +*/ + +void CG_ParticleExplosion (char *animStr, vec3_t origin, vec3_t vel, int duration, int sizeStart, int sizeEnd) +{ + cparticle_t *p; + int anim; + + if (animStr < (char *)10) + CG_Error( "CG_ParticleExplosion: animStr is probably an index rather than a string" ); + + // find the animation string + for (anim=0; shaderAnimNames[anim]; anim++) { + if (!Q_stricmp( animStr, shaderAnimNames[anim] )) + break; + } + if (!shaderAnimNames[anim]) { + CG_Error("CG_ParticleExplosion: unknown animation string: %s\n", animStr); + return; + } + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->alpha = 0.5; + p->alphavel = 0; + + if (duration < 0) { + duration *= -1; + p->roll = 0; + } else { + p->roll = crandom()*179; + } + + p->shaderAnim = anim; + + p->width = sizeStart; + p->height = sizeStart*shaderAnimSTRatio[anim]; // for sprites that are stretch in either direction + + p->endheight = sizeEnd; + p->endwidth = sizeEnd*shaderAnimSTRatio[anim]; + + p->endtime = cg.time + duration; + + p->type = P_ANIM; + + VectorCopy( origin, p->org ); + VectorCopy( vel, p->vel ); + VectorClear( p->accel ); + +} + +// Rafael Shrapnel +void CG_AddParticleShrapnel (localEntity_t *le) +{ + return; +} +// done. + +int CG_NewParticleArea (int num) +{ + // const char *str; + char *str; + char *token; + int type; + vec3_t origin, origin2; + int i; + float range = 0; + int turb; + int numparticles; + int snum; + + str = (char *) CG_ConfigString (num); + if (!str[0]) + return (0); + + // returns type 128 64 or 32 + token = COM_Parse (&str); + type = atoi (token); + + if (type == 1) + range = 128; + else if (type == 2) + range = 64; + else if (type == 3) + range = 32; + else if (type == 0) + range = 256; + else if (type == 4) + range = 8; + else if (type == 5) + range = 16; + else if (type == 6) + range = 32; + else if (type == 7) + range = 64; + + + for (i=0; i<3; i++) + { + token = COM_Parse (&str); + origin[i] = atof (token); + } + + for (i=0; i<3; i++) + { + token = COM_Parse (&str); + origin2[i] = atof (token); + } + + token = COM_Parse (&str); + numparticles = atoi (token); + + token = COM_Parse (&str); + turb = atoi (token); + + token = COM_Parse (&str); + snum = atoi (token); + + for (i=0; i= 4) + CG_ParticleBubble (cgs.media.waterBubbleShader, origin, origin2, turb, range, snum); + else + CG_ParticleSnow (cgs.media.waterBubbleShader, origin, origin2, turb, range, snum); + } + + return (1); +} + +void CG_SnowLink (centity_t *cent, qboolean particleOn) +{ + cparticle_t *p, *next; + int id; + + id = cent->currentState.frame; + + for (p=active_particles ; p ; p=next) + { + next = p->next; + + if (p->type == P_WEATHER || p->type == P_WEATHER_TURBULENT) + { + if (p->snum == id) + { + if (particleOn) + p->link = qtrue; + else + p->link = qfalse; + } + } + + } +} + +void CG_ParticleImpactSmokePuff (qhandle_t pshader, vec3_t origin) +{ + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_ParticleImpactSmokePuff pshader == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->alpha = 0.25; + p->alphavel = 0; + p->roll = crandom()*179; + + p->pshader = pshader; + + p->endtime = cg.time + 1000; + p->startfade = cg.time + 100; + + p->width = rand()%4 + 8; + p->height = rand()%4 + 8; + + p->endheight = p->height *2; + p->endwidth = p->width * 2; + + p->endtime = cg.time + 500; + + p->type = P_SMOKE_IMPACT; + + VectorCopy( origin, p->org ); + VectorSet(p->vel, 0, 0, 20); + VectorSet(p->accel, 0, 0, 20); + + p->rotate = qtrue; +} + +void CG_Particle_Bleed (qhandle_t pshader, vec3_t start, vec3_t dir, int fleshEntityNum, int duration) +{ + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_Particle_Bleed pshader == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->alpha = 1.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = pshader; + + p->endtime = cg.time + duration; + + if (fleshEntityNum) + p->startfade = cg.time; + else + p->startfade = cg.time + 100; + + p->width = 4; + p->height = 4; + + p->endheight = 4+rand()%3; + p->endwidth = p->endheight; + + p->type = P_SMOKE; + + VectorCopy( start, p->org ); + p->vel[0] = 0; + p->vel[1] = 0; + p->vel[2] = -20; + VectorClear( p->accel ); + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->color = BLOODRED; + p->alpha = 0.75; + +} + +void CG_Particle_OilParticle (qhandle_t pshader, centity_t *cent) +{ + cparticle_t *p; + + int time; + int time2; + float ratio; + + float duration = 1500; + + time = cg.time; + time2 = cg.time + cent->currentState.time; + + ratio =(float)1 - ((float)time / (float)time2); + + if (!pshader) + CG_Printf ("CG_Particle_OilParticle == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->alpha = 1.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = pshader; + + p->endtime = cg.time + duration; + + p->startfade = p->endtime; + + p->width = 1; + p->height = 3; + + p->endheight = 3; + p->endwidth = 1; + + p->type = P_SMOKE; + + VectorCopy(cent->currentState.origin, p->org ); + + p->vel[0] = (cent->currentState.origin2[0] * (16 * ratio)); + p->vel[1] = (cent->currentState.origin2[1] * (16 * ratio)); + p->vel[2] = (cent->currentState.origin2[2]); + + p->snum = 1.0f; + + VectorClear( p->accel ); + + p->accel[2] = -20; + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->alpha = 0.75; + +} + + +void CG_Particle_OilSlick (qhandle_t pshader, centity_t *cent) +{ + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_Particle_OilSlick == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + + if (cent->currentState.angles2[2]) + p->endtime = cg.time + cent->currentState.angles2[2]; + else + p->endtime = cg.time + 60000; + + p->startfade = p->endtime; + + p->alpha = 1.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = pshader; + + if (cent->currentState.angles2[0] || cent->currentState.angles2[1]) + { + p->width = cent->currentState.angles2[0]; + p->height = cent->currentState.angles2[0]; + + p->endheight = cent->currentState.angles2[1]; + p->endwidth = cent->currentState.angles2[1]; + } + else + { + p->width = 8; + p->height = 8; + + p->endheight = 16; + p->endwidth = 16; + } + + p->type = P_FLAT_SCALEUP; + + p->snum = 1.0; + + VectorCopy(cent->currentState.origin, p->org ); + + p->org[2]+= 0.55 + (crandom() * 0.5); + + p->vel[0] = 0; + p->vel[1] = 0; + p->vel[2] = 0; + VectorClear( p->accel ); + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->alpha = 0.75; + +} + +void CG_OilSlickRemove (centity_t *cent) +{ + cparticle_t *p, *next; + int id; + + id = 1.0f; + + if (!id) + CG_Printf ("CG_OilSlickRevove NULL id\n"); + + for (p=active_particles ; p ; p=next) + { + next = p->next; + + if (p->type == P_FLAT_SCALEUP) + { + if (p->snum == id) + { + p->endtime = cg.time + 100; + p->startfade = p->endtime; + p->type = P_FLAT_SCALEUP_FADE; + + } + } + + } +} + +qboolean ValidBloodPool (vec3_t start) +{ +#define EXTRUDE_DIST 0.5 + + vec3_t angles; + vec3_t right, up; + vec3_t this_pos, x_pos, center_pos, end_pos; + float x, y; + float fwidth, fheight; + trace_t trace; + vec3_t normal; + + fwidth = 16; + fheight = 16; + + VectorSet (normal, 0, 0, 1); + + vectoangles (normal, angles); + AngleVectors (angles, NULL, right, up); + + VectorMA (start, EXTRUDE_DIST, normal, center_pos); + + for (x= -fwidth/2; xendpos, start); + legit = ValidBloodPool (start); + + if (!legit) + return; + + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + + p->endtime = cg.time + 3000; + p->startfade = p->endtime; + + p->alpha = 1.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = pshader; + + rndSize = 0.4 + random()*0.6; + + p->width = 8*rndSize; + p->height = 8*rndSize; + + p->endheight = 16*rndSize; + p->endwidth = 16*rndSize; + + p->type = P_FLAT_SCALEUP; + + VectorCopy(start, p->org ); + + p->vel[0] = 0; + p->vel[1] = 0; + p->vel[2] = 0; + VectorClear( p->accel ); + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->alpha = 0.75; + + p->color = BLOODRED; +} + +#define NORMALSIZE 16 +#define LARGESIZE 32 + +void CG_ParticleBloodCloud (centity_t *cent, vec3_t origin, vec3_t dir) +{ + float length; + float dist; + float crittersize; + vec3_t angles, forward; + vec3_t point; + cparticle_t *p; + int i; + + dist = 0; + + length = VectorLength (dir); + vectoangles (dir, angles); + AngleVectors (angles, forward, NULL, NULL); + + crittersize = LARGESIZE; + + if (length) + dist = length / crittersize; + + if (dist < 1) + dist = 1; + + VectorCopy (origin, point); + + for (i=0; inext; + p->next = active_particles; + active_particles = p; + + p->time = cg.time; + p->alpha = 1.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = cgs.media.smokePuffShader; + + p->endtime = cg.time + 350 + (crandom() * 100); + + p->startfade = cg.time; + + p->width = LARGESIZE; + p->height = LARGESIZE; + p->endheight = LARGESIZE; + p->endwidth = LARGESIZE; + + p->type = P_SMOKE; + + VectorCopy( origin, p->org ); + + p->vel[0] = 0; + p->vel[1] = 0; + p->vel[2] = -1; + + VectorClear( p->accel ); + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->color = BLOODRED; + + p->alpha = 0.75; + + } + + +} + +void CG_ParticleSparks (vec3_t org, vec3_t vel, int duration, float x, float y, float speed) +{ + cparticle_t *p; + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + + p->endtime = cg.time + duration; + p->startfade = cg.time + duration/2; + + p->color = EMISIVEFADE; + p->alpha = 0.4f; + p->alphavel = 0; + + p->height = 0.5; + p->width = 0.5; + p->endheight = 0.5; + p->endwidth = 0.5; + + p->pshader = cgs.media.tracerShader; + + p->type = P_SMOKE; + + VectorCopy(org, p->org); + + p->org[0] += (crandom() * x); + p->org[1] += (crandom() * y); + + p->vel[0] = vel[0]; + p->vel[1] = vel[1]; + p->vel[2] = vel[2]; + + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + p->vel[0] += (crandom() * 4); + p->vel[1] += (crandom() * 4); + p->vel[2] += (20 + (crandom() * 10)) * speed; + + p->accel[0] = crandom () * 4; + p->accel[1] = crandom () * 4; + +} + +void CG_ParticleDust (centity_t *cent, vec3_t origin, vec3_t dir) +{ + float length; + float dist; + float crittersize; + vec3_t angles, forward; + vec3_t point; + cparticle_t *p; + int i; + + dist = 0; + + VectorNegate (dir, dir); + length = VectorLength (dir); + vectoangles (dir, angles); + AngleVectors (angles, forward, NULL, NULL); + + crittersize = LARGESIZE; + + if (length) + dist = length / crittersize; + + if (dist < 1) + dist = 1; + + VectorCopy (origin, point); + + for (i=0; inext; + p->next = active_particles; + active_particles = p; + + p->time = cg.time; + p->alpha = 5.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = cgs.media.smokePuffShader; + + // RF, stay around for long enough to expand and dissipate naturally + if (length) + p->endtime = cg.time + 4500 + (crandom() * 3500); + else + p->endtime = cg.time + 750 + (crandom() * 500); + + p->startfade = cg.time; + + p->width = LARGESIZE; + p->height = LARGESIZE; + + // RF, expand while falling + p->endheight = LARGESIZE*3.0; + p->endwidth = LARGESIZE*3.0; + + if (!length) + { + p->width *= 0.2f; + p->height *= 0.2f; + + p->endheight = NORMALSIZE; + p->endwidth = NORMALSIZE; + } + + p->type = P_SMOKE; + + VectorCopy( point, p->org ); + + p->vel[0] = crandom()*6; + p->vel[1] = crandom()*6; + p->vel[2] = random()*20; + + // RF, add some gravity/randomness + p->accel[0] = crandom()*3; + p->accel[1] = crandom()*3; + p->accel[2] = -PARTICLE_GRAVITY*0.4; + + VectorClear( p->accel ); + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->alpha = 0.75; + + } + + +} + +void CG_ParticleMisc (qhandle_t pshader, vec3_t origin, int size, int duration, float alpha) +{ + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_ParticleImpactSmokePuff pshader == ZERO!\n"); + + if (!free_particles) + return; + + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->alpha = 1.0; + p->alphavel = 0; + p->roll = rand()%179; + + p->pshader = pshader; + + if (duration > 0) + p->endtime = cg.time + duration; + else + p->endtime = duration; + + p->startfade = cg.time; + + p->width = size; + p->height = size; + + p->endheight = size; + p->endwidth = size; + + p->type = P_SPRITE; + + VectorCopy( origin, p->org ); + + p->rotate = qfalse; +} + diff --git a/code/cgame/cg_newdraw.c b/code/cgame/cg_newdraw.c index 28335fe..c78fff9 100755 --- a/code/cgame/cg_newdraw.c +++ b/code/cgame/cg_newdraw.c @@ -1,1852 +1,1852 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#ifndef MISSIONPACK // bk001204 -#error This file not be used for classic Q3A. -#endif - -#include "cg_local.h" -#include "../ui/ui_shared.h" - -extern displayContextDef_t cgDC; - - -// set in CG_ParseTeamInfo - -//static int sortedTeamPlayers[TEAM_MAXOVERLAY]; -//static int numSortedTeamPlayers; -int drawTeamOverlayModificationCount = -1; - -//static char systemChat[256]; -//static char teamChat1[256]; -//static char teamChat2[256]; - -void CG_InitTeamChat() { - memset(teamChat1, 0, sizeof(teamChat1)); - memset(teamChat2, 0, sizeof(teamChat2)); - memset(systemChat, 0, sizeof(systemChat)); -} - -void CG_SetPrintString(int type, const char *p) { - if (type == SYSTEM_PRINT) { - strcpy(systemChat, p); - } else { - strcpy(teamChat2, teamChat1); - strcpy(teamChat1, p); - } -} - -void CG_CheckOrderPending() { - if (cgs.gametype < GT_CTF) { - return; - } - if (cgs.orderPending) { - //clientInfo_t *ci = cgs.clientinfo + sortedTeamPlayers[cg_currentSelectedPlayer.integer]; - const char *p1, *p2, *b; - p1 = p2 = b = NULL; - switch (cgs.currentOrder) { - case TEAMTASK_OFFENSE: - p1 = VOICECHAT_ONOFFENSE; - p2 = VOICECHAT_OFFENSE; - b = "+button7; wait; -button7"; - break; - case TEAMTASK_DEFENSE: - p1 = VOICECHAT_ONDEFENSE; - p2 = VOICECHAT_DEFEND; - b = "+button8; wait; -button8"; - break; - case TEAMTASK_PATROL: - p1 = VOICECHAT_ONPATROL; - p2 = VOICECHAT_PATROL; - b = "+button9; wait; -button9"; - break; - case TEAMTASK_FOLLOW: - p1 = VOICECHAT_ONFOLLOW; - p2 = VOICECHAT_FOLLOWME; - b = "+button10; wait; -button10"; - break; - case TEAMTASK_CAMP: - p1 = VOICECHAT_ONCAMPING; - p2 = VOICECHAT_CAMP; - break; - case TEAMTASK_RETRIEVE: - p1 = VOICECHAT_ONGETFLAG; - p2 = VOICECHAT_RETURNFLAG; - break; - case TEAMTASK_ESCORT: - p1 = VOICECHAT_ONFOLLOWCARRIER; - p2 = VOICECHAT_FOLLOWFLAGCARRIER; - break; - } - - if (cg_currentSelectedPlayer.integer == numSortedTeamPlayers) { - // to everyone - trap_SendConsoleCommand(va("cmd vsay_team %s\n", p2)); - } else { - // for the player self - if (sortedTeamPlayers[cg_currentSelectedPlayer.integer] == cg.snap->ps.clientNum && p1) { - trap_SendConsoleCommand(va("teamtask %i\n", cgs.currentOrder)); - //trap_SendConsoleCommand(va("cmd say_team %s\n", p2)); - trap_SendConsoleCommand(va("cmd vsay_team %s\n", p1)); - } else if (p2) { - //trap_SendConsoleCommand(va("cmd say_team %s, %s\n", ci->name,p)); - trap_SendConsoleCommand(va("cmd vtell %d %s\n", sortedTeamPlayers[cg_currentSelectedPlayer.integer], p2)); - } - } - if (b) { - trap_SendConsoleCommand(b); - } - cgs.orderPending = qfalse; - } -} - -static void CG_SetSelectedPlayerName() { - if (cg_currentSelectedPlayer.integer >= 0 && cg_currentSelectedPlayer.integer < numSortedTeamPlayers) { - clientInfo_t *ci = cgs.clientinfo + sortedTeamPlayers[cg_currentSelectedPlayer.integer]; - if (ci) { - trap_Cvar_Set("cg_selectedPlayerName", ci->name); - trap_Cvar_Set("cg_selectedPlayer", va("%d", sortedTeamPlayers[cg_currentSelectedPlayer.integer])); - cgs.currentOrder = ci->teamTask; - } - } else { - trap_Cvar_Set("cg_selectedPlayerName", "Everyone"); - } -} -int CG_GetSelectedPlayer() { - if (cg_currentSelectedPlayer.integer < 0 || cg_currentSelectedPlayer.integer >= numSortedTeamPlayers) { - cg_currentSelectedPlayer.integer = 0; - } - return cg_currentSelectedPlayer.integer; -} - -void CG_SelectNextPlayer() { - CG_CheckOrderPending(); - if (cg_currentSelectedPlayer.integer >= 0 && cg_currentSelectedPlayer.integer < numSortedTeamPlayers) { - cg_currentSelectedPlayer.integer++; - } else { - cg_currentSelectedPlayer.integer = 0; - } - CG_SetSelectedPlayerName(); -} - -void CG_SelectPrevPlayer() { - CG_CheckOrderPending(); - if (cg_currentSelectedPlayer.integer > 0 && cg_currentSelectedPlayer.integer < numSortedTeamPlayers) { - cg_currentSelectedPlayer.integer--; - } else { - cg_currentSelectedPlayer.integer = numSortedTeamPlayers; - } - CG_SetSelectedPlayerName(); -} - - -static void CG_DrawPlayerArmorIcon( rectDef_t *rect, qboolean draw2D ) { - centity_t *cent; - playerState_t *ps; - vec3_t angles; - vec3_t origin; - - if ( cg_drawStatus.integer == 0 ) { - return; - } - - cent = &cg_entities[cg.snap->ps.clientNum]; - ps = &cg.snap->ps; - - if ( draw2D || ( !cg_draw3dIcons.integer && cg_drawIcons.integer) ) { // bk001206 - parentheses - CG_DrawPic( rect->x, rect->y + rect->h/2 + 1, rect->w, rect->h, cgs.media.armorIcon ); - } else if (cg_draw3dIcons.integer) { - VectorClear( angles ); - origin[0] = 90; - origin[1] = 0; - origin[2] = -10; - angles[YAW] = ( cg.time & 2047 ) * 360 / 2048.0; - - CG_Draw3DModel( rect->x, rect->y, rect->w, rect->h, cgs.media.armorModel, 0, origin, angles ); - } - -} - -static void CG_DrawPlayerArmorValue(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle) { - char num[16]; - int value; - centity_t *cent; - playerState_t *ps; - - cent = &cg_entities[cg.snap->ps.clientNum]; - ps = &cg.snap->ps; - - value = ps->stats[STAT_ARMOR]; - - - if (shader) { - trap_R_SetColor( color ); - CG_DrawPic(rect->x, rect->y, rect->w, rect->h, shader); - trap_R_SetColor( NULL ); - } else { - Com_sprintf (num, sizeof(num), "%i", value); - value = CG_Text_Width(num, scale, 0); - CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h, scale, color, num, 0, 0, textStyle); - } -} - -#ifndef MISSIONPACK // bk001206 -static float healthColors[4][4] = { -// { 0.2, 1.0, 0.2, 1.0 } , { 1.0, 0.2, 0.2, 1.0 }, {0.5, 0.5, 0.5, 1} }; - // bk0101016 - float const - { 1.0f, 0.69f, 0.0f, 1.0f } , // normal - { 1.0f, 0.2f, 0.2f, 1.0f }, // low health - { 0.5f, 0.5f, 0.5f, 1.0f}, // weapon firing - { 1.0f, 1.0f, 1.0f, 1.0f } }; // health > 100 -#endif - -static void CG_DrawPlayerAmmoIcon( rectDef_t *rect, qboolean draw2D ) { - centity_t *cent; - playerState_t *ps; - vec3_t angles; - vec3_t origin; - - cent = &cg_entities[cg.snap->ps.clientNum]; - ps = &cg.snap->ps; - - if ( draw2D || (!cg_draw3dIcons.integer && cg_drawIcons.integer) ) { // bk001206 - parentheses - qhandle_t icon; - icon = cg_weapons[ cg.predictedPlayerState.weapon ].ammoIcon; - if ( icon ) { - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, icon ); - } - } else if (cg_draw3dIcons.integer) { - if ( cent->currentState.weapon && cg_weapons[ cent->currentState.weapon ].ammoModel ) { - VectorClear( angles ); - origin[0] = 70; - origin[1] = 0; - origin[2] = 0; - angles[YAW] = 90 + 20 * sin( cg.time / 1000.0 ); - CG_Draw3DModel( rect->x, rect->y, rect->w, rect->h, cg_weapons[ cent->currentState.weapon ].ammoModel, 0, origin, angles ); - } - } -} - -static void CG_DrawPlayerAmmoValue(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle) { - char num[16]; - int value; - centity_t *cent; - playerState_t *ps; - - cent = &cg_entities[cg.snap->ps.clientNum]; - ps = &cg.snap->ps; - - if ( cent->currentState.weapon ) { - value = ps->ammo[cent->currentState.weapon]; - if ( value > -1 ) { - if (shader) { - trap_R_SetColor( color ); - CG_DrawPic(rect->x, rect->y, rect->w, rect->h, shader); - trap_R_SetColor( NULL ); - } else { - Com_sprintf (num, sizeof(num), "%i", value); - value = CG_Text_Width(num, scale, 0); - CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h, scale, color, num, 0, 0, textStyle); - } - } - } - -} - - - -static void CG_DrawPlayerHead(rectDef_t *rect, qboolean draw2D) { - vec3_t angles; - float size, stretch; - float frac; - float x = rect->x; - - VectorClear( angles ); - - if ( cg.damageTime && cg.time - cg.damageTime < DAMAGE_TIME ) { - frac = (float)(cg.time - cg.damageTime ) / DAMAGE_TIME; - size = rect->w * 1.25 * ( 1.5 - frac * 0.5 ); - - stretch = size - rect->w * 1.25; - // kick in the direction of damage - x -= stretch * 0.5 + cg.damageX * stretch * 0.5; - - cg.headStartYaw = 180 + cg.damageX * 45; - - cg.headEndYaw = 180 + 20 * cos( crandom()*M_PI ); - cg.headEndPitch = 5 * cos( crandom()*M_PI ); - - cg.headStartTime = cg.time; - cg.headEndTime = cg.time + 100 + random() * 2000; - } else { - if ( cg.time >= cg.headEndTime ) { - // select a new head angle - cg.headStartYaw = cg.headEndYaw; - cg.headStartPitch = cg.headEndPitch; - cg.headStartTime = cg.headEndTime; - cg.headEndTime = cg.time + 100 + random() * 2000; - - cg.headEndYaw = 180 + 20 * cos( crandom()*M_PI ); - cg.headEndPitch = 5 * cos( crandom()*M_PI ); - } - - size = rect->w * 1.25; - } - - // if the server was frozen for a while we may have a bad head start time - if ( cg.headStartTime > cg.time ) { - cg.headStartTime = cg.time; - } - - frac = ( cg.time - cg.headStartTime ) / (float)( cg.headEndTime - cg.headStartTime ); - frac = frac * frac * ( 3 - 2 * frac ); - angles[YAW] = cg.headStartYaw + ( cg.headEndYaw - cg.headStartYaw ) * frac; - angles[PITCH] = cg.headStartPitch + ( cg.headEndPitch - cg.headStartPitch ) * frac; - - CG_DrawHead( x, rect->y, rect->w, rect->h, cg.snap->ps.clientNum, angles ); -} - -static void CG_DrawSelectedPlayerHealth( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { - clientInfo_t *ci; - int value; - char num[16]; - - ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; - if (ci) { - if (shader) { - trap_R_SetColor( color ); - CG_DrawPic(rect->x, rect->y, rect->w, rect->h, shader); - trap_R_SetColor( NULL ); - } else { - Com_sprintf (num, sizeof(num), "%i", ci->health); - value = CG_Text_Width(num, scale, 0); - CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h, scale, color, num, 0, 0, textStyle); - } - } -} - -static void CG_DrawSelectedPlayerArmor( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { - clientInfo_t *ci; - int value; - char num[16]; - ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; - if (ci) { - if (ci->armor > 0) { - if (shader) { - trap_R_SetColor( color ); - CG_DrawPic(rect->x, rect->y, rect->w, rect->h, shader); - trap_R_SetColor( NULL ); - } else { - Com_sprintf (num, sizeof(num), "%i", ci->armor); - value = CG_Text_Width(num, scale, 0); - CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h, scale, color, num, 0, 0, textStyle); - } - } - } -} - -qhandle_t CG_StatusHandle(int task) { - qhandle_t h = cgs.media.assaultShader; - switch (task) { - case TEAMTASK_OFFENSE : - h = cgs.media.assaultShader; - break; - case TEAMTASK_DEFENSE : - h = cgs.media.defendShader; - break; - case TEAMTASK_PATROL : - h = cgs.media.patrolShader; - break; - case TEAMTASK_FOLLOW : - h = cgs.media.followShader; - break; - case TEAMTASK_CAMP : - h = cgs.media.campShader; - break; - case TEAMTASK_RETRIEVE : - h = cgs.media.retrieveShader; - break; - case TEAMTASK_ESCORT : - h = cgs.media.escortShader; - break; - default : - h = cgs.media.assaultShader; - break; - } - return h; -} - -static void CG_DrawSelectedPlayerStatus( rectDef_t *rect ) { - clientInfo_t *ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; - if (ci) { - qhandle_t h; - if (cgs.orderPending) { - // blink the icon - if ( cg.time > cgs.orderTime - 2500 && (cg.time >> 9 ) & 1 ) { - return; - } - h = CG_StatusHandle(cgs.currentOrder); - } else { - h = CG_StatusHandle(ci->teamTask); - } - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, h ); - } -} - - -static void CG_DrawPlayerStatus( rectDef_t *rect ) { - clientInfo_t *ci = &cgs.clientinfo[cg.snap->ps.clientNum]; - if (ci) { - qhandle_t h = CG_StatusHandle(ci->teamTask); - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, h); - } -} - - -static void CG_DrawSelectedPlayerName( rectDef_t *rect, float scale, vec4_t color, qboolean voice, int textStyle) { - clientInfo_t *ci; - ci = cgs.clientinfo + ((voice) ? cgs.currentVoiceClient : sortedTeamPlayers[CG_GetSelectedPlayer()]); - if (ci) { - CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, ci->name, 0, 0, textStyle); - } -} - -static void CG_DrawSelectedPlayerLocation( rectDef_t *rect, float scale, vec4_t color, int textStyle ) { - clientInfo_t *ci; - ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; - if (ci) { - const char *p = CG_ConfigString(CS_LOCATIONS + ci->location); - if (!p || !*p) { - p = "unknown"; - } - CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, p, 0, 0, textStyle); - } -} - -static void CG_DrawPlayerLocation( rectDef_t *rect, float scale, vec4_t color, int textStyle ) { - clientInfo_t *ci = &cgs.clientinfo[cg.snap->ps.clientNum]; - if (ci) { - const char *p = CG_ConfigString(CS_LOCATIONS + ci->location); - if (!p || !*p) { - p = "unknown"; - } - CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, p, 0, 0, textStyle); - } -} - - - -static void CG_DrawSelectedPlayerWeapon( rectDef_t *rect ) { - clientInfo_t *ci; - - ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; - if (ci) { - if ( cg_weapons[ci->curWeapon].weaponIcon ) { - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cg_weapons[ci->curWeapon].weaponIcon ); - } else { - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.deferShader); - } - } -} - -static void CG_DrawPlayerScore( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { - char num[16]; - int value = cg.snap->ps.persistant[PERS_SCORE]; - - if (shader) { - trap_R_SetColor( color ); - CG_DrawPic(rect->x, rect->y, rect->w, rect->h, shader); - trap_R_SetColor( NULL ); - } else { - Com_sprintf (num, sizeof(num), "%i", value); - value = CG_Text_Width(num, scale, 0); - CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h, scale, color, num, 0, 0, textStyle); - } -} - -static void CG_DrawPlayerItem( rectDef_t *rect, float scale, qboolean draw2D) { - int value; - vec3_t origin, angles; - - value = cg.snap->ps.stats[STAT_HOLDABLE_ITEM]; - if ( value ) { - CG_RegisterItemVisuals( value ); - - if (qtrue) { - CG_RegisterItemVisuals( value ); - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cg_items[ value ].icon ); - } else { - VectorClear( angles ); - origin[0] = 90; - origin[1] = 0; - origin[2] = -10; - angles[YAW] = ( cg.time & 2047 ) * 360 / 2048.0; - CG_Draw3DModel(rect->x, rect->y, rect->w, rect->h, cg_items[ value ].models[0], 0, origin, angles ); - } - } - -} - - -static void CG_DrawSelectedPlayerPowerup( rectDef_t *rect, qboolean draw2D ) { - clientInfo_t *ci; - int j; - float x, y; - - ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; - if (ci) { - x = rect->x; - y = rect->y; - - for (j = 0; j < PW_NUM_POWERUPS; j++) { - if (ci->powerups & (1 << j)) { - gitem_t *item; - item = BG_FindItemForPowerup( j ); - if (item) { - CG_DrawPic( x, y, rect->w, rect->h, trap_R_RegisterShader( item->icon ) ); - x += 3; - y += 3; - return; - } - } - } - - } -} - - -static void CG_DrawSelectedPlayerHead( rectDef_t *rect, qboolean draw2D, qboolean voice ) { - clipHandle_t cm; - clientInfo_t *ci; - float len; - vec3_t origin; - vec3_t mins, maxs, angles; - - - ci = cgs.clientinfo + ((voice) ? cgs.currentVoiceClient : sortedTeamPlayers[CG_GetSelectedPlayer()]); - - if (ci) { - if ( cg_draw3dIcons.integer ) { - cm = ci->headModel; - if ( !cm ) { - return; - } - - // offset the origin y and z to center the head - trap_R_ModelBounds( cm, mins, maxs ); - - origin[2] = -0.5 * ( mins[2] + maxs[2] ); - origin[1] = 0.5 * ( mins[1] + maxs[1] ); - - // calculate distance so the head nearly fills the box - // assume heads are taller than wide - len = 0.7 * ( maxs[2] - mins[2] ); - origin[0] = len / 0.268; // len / tan( fov/2 ) - - // allow per-model tweaking - VectorAdd( origin, ci->headOffset, origin ); - - angles[PITCH] = 0; - angles[YAW] = 180; - angles[ROLL] = 0; - - CG_Draw3DModel( rect->x, rect->y, rect->w, rect->h, ci->headModel, ci->headSkin, origin, angles ); - } else if ( cg_drawIcons.integer ) { - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, ci->modelIcon ); - } - - // if they are deferred, draw a cross out - if ( ci->deferred ) { - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.deferShader ); - } - } - -} - - -static void CG_DrawPlayerHealth(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { - playerState_t *ps; - int value; - char num[16]; - - ps = &cg.snap->ps; - - value = ps->stats[STAT_HEALTH]; - - if (shader) { - trap_R_SetColor( color ); - CG_DrawPic(rect->x, rect->y, rect->w, rect->h, shader); - trap_R_SetColor( NULL ); - } else { - Com_sprintf (num, sizeof(num), "%i", value); - value = CG_Text_Width(num, scale, 0); - CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h, scale, color, num, 0, 0, textStyle); - } -} - - -static void CG_DrawRedScore(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { - int value; - char num[16]; - if ( cgs.scores1 == SCORE_NOT_PRESENT ) { - Com_sprintf (num, sizeof(num), "-"); - } - else { - Com_sprintf (num, sizeof(num), "%i", cgs.scores1); - } - value = CG_Text_Width(num, scale, 0); - CG_Text_Paint(rect->x + rect->w - value, rect->y + rect->h, scale, color, num, 0, 0, textStyle); -} - -static void CG_DrawBlueScore(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { - int value; - char num[16]; - - if ( cgs.scores2 == SCORE_NOT_PRESENT ) { - Com_sprintf (num, sizeof(num), "-"); - } - else { - Com_sprintf (num, sizeof(num), "%i", cgs.scores2); - } - value = CG_Text_Width(num, scale, 0); - CG_Text_Paint(rect->x + rect->w - value, rect->y + rect->h, scale, color, num, 0, 0, textStyle); -} - -// FIXME: team name support -static void CG_DrawRedName(rectDef_t *rect, float scale, vec4_t color, int textStyle ) { - CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, cg_redTeamName.string , 0, 0, textStyle); -} - -static void CG_DrawBlueName(rectDef_t *rect, float scale, vec4_t color, int textStyle ) { - CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, cg_blueTeamName.string, 0, 0, textStyle); -} - -static void CG_DrawBlueFlagName(rectDef_t *rect, float scale, vec4_t color, int textStyle ) { - int i; - for ( i = 0 ; i < cgs.maxclients ; i++ ) { - if ( cgs.clientinfo[i].infoValid && cgs.clientinfo[i].team == TEAM_RED && cgs.clientinfo[i].powerups & ( 1<< PW_BLUEFLAG )) { - CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, cgs.clientinfo[i].name, 0, 0, textStyle); - return; - } - } -} - -static void CG_DrawBlueFlagStatus(rectDef_t *rect, qhandle_t shader) { - if (cgs.gametype != GT_CTF && cgs.gametype != GT_1FCTF) { - if (cgs.gametype == GT_HARVESTER) { - vec4_t color = {0, 0, 1, 1}; - trap_R_SetColor(color); - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.blueCubeIcon ); - trap_R_SetColor(NULL); - } - return; - } - if (shader) { - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); - } else { - gitem_t *item = BG_FindItemForPowerup( PW_BLUEFLAG ); - if (item) { - vec4_t color = {0, 0, 1, 1}; - trap_R_SetColor(color); - if( cgs.blueflag >= 0 && cgs.blueflag <= 2 ) { - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.flagShaders[cgs.blueflag] ); - } else { - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.flagShaders[0] ); - } - trap_R_SetColor(NULL); - } - } -} - -static void CG_DrawBlueFlagHead(rectDef_t *rect) { - int i; - for ( i = 0 ; i < cgs.maxclients ; i++ ) { - if ( cgs.clientinfo[i].infoValid && cgs.clientinfo[i].team == TEAM_RED && cgs.clientinfo[i].powerups & ( 1<< PW_BLUEFLAG )) { - vec3_t angles; - VectorClear( angles ); - angles[YAW] = 180 + 20 * sin( cg.time / 650.0 );; - CG_DrawHead( rect->x, rect->y, rect->w, rect->h, 0,angles ); - return; - } - } -} - -static void CG_DrawRedFlagName(rectDef_t *rect, float scale, vec4_t color, int textStyle ) { - int i; - for ( i = 0 ; i < cgs.maxclients ; i++ ) { - if ( cgs.clientinfo[i].infoValid && cgs.clientinfo[i].team == TEAM_BLUE && cgs.clientinfo[i].powerups & ( 1<< PW_REDFLAG )) { - CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, cgs.clientinfo[i].name, 0, 0, textStyle); - return; - } - } -} - -static void CG_DrawRedFlagStatus(rectDef_t *rect, qhandle_t shader) { - if (cgs.gametype != GT_CTF && cgs.gametype != GT_1FCTF) { - if (cgs.gametype == GT_HARVESTER) { - vec4_t color = {1, 0, 0, 1}; - trap_R_SetColor(color); - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.redCubeIcon ); - trap_R_SetColor(NULL); - } - return; - } - if (shader) { - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); - } else { - gitem_t *item = BG_FindItemForPowerup( PW_REDFLAG ); - if (item) { - vec4_t color = {1, 0, 0, 1}; - trap_R_SetColor(color); - if( cgs.redflag >= 0 && cgs.redflag <= 2) { - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.flagShaders[cgs.redflag] ); - } else { - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.flagShaders[0] ); - } - trap_R_SetColor(NULL); - } - } -} - -static void CG_DrawRedFlagHead(rectDef_t *rect) { - int i; - for ( i = 0 ; i < cgs.maxclients ; i++ ) { - if ( cgs.clientinfo[i].infoValid && cgs.clientinfo[i].team == TEAM_BLUE && cgs.clientinfo[i].powerups & ( 1<< PW_REDFLAG )) { - vec3_t angles; - VectorClear( angles ); - angles[YAW] = 180 + 20 * sin( cg.time / 650.0 );; - CG_DrawHead( rect->x, rect->y, rect->w, rect->h, 0,angles ); - return; - } - } -} - -static void CG_HarvesterSkulls(rectDef_t *rect, float scale, vec4_t color, qboolean force2D, int textStyle ) { - char num[16]; - vec3_t origin, angles; - qhandle_t handle; - int value = cg.snap->ps.generic1; - - if (cgs.gametype != GT_HARVESTER) { - return; - } - - if( value > 99 ) { - value = 99; - } - - Com_sprintf (num, sizeof(num), "%i", value); - value = CG_Text_Width(num, scale, 0); - CG_Text_Paint(rect->x + (rect->w - value), rect->y + rect->h, scale, color, num, 0, 0, textStyle); - - if (cg_drawIcons.integer) { - if (!force2D && cg_draw3dIcons.integer) { - VectorClear(angles); - origin[0] = 90; - origin[1] = 0; - origin[2] = -10; - angles[YAW] = ( cg.time & 2047 ) * 360 / 2048.0; - if( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { - handle = cgs.media.redCubeModel; - } else { - handle = cgs.media.blueCubeModel; - } - CG_Draw3DModel( rect->x, rect->y, 35, 35, handle, 0, origin, angles ); - } else { - if( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { - handle = cgs.media.redCubeIcon; - } else { - handle = cgs.media.blueCubeIcon; - } - CG_DrawPic( rect->x + 3, rect->y + 16, 20, 20, handle ); - } - } -} - -static void CG_OneFlagStatus(rectDef_t *rect) { - if (cgs.gametype != GT_1FCTF) { - return; - } else { - gitem_t *item = BG_FindItemForPowerup( PW_NEUTRALFLAG ); - if (item) { - if( cgs.flagStatus >= 0 && cgs.flagStatus <= 4 ) { - vec4_t color = {1, 1, 1, 1}; - int index = 0; - if (cgs.flagStatus == FLAG_TAKEN_RED) { - color[1] = color[2] = 0; - index = 1; - } else if (cgs.flagStatus == FLAG_TAKEN_BLUE) { - color[0] = color[1] = 0; - index = 1; - } else if (cgs.flagStatus == FLAG_DROPPED) { - index = 2; - } - trap_R_SetColor(color); - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.flagShaders[index] ); - } - } - } -} - - -static void CG_DrawCTFPowerUp(rectDef_t *rect) { - int value; - - if (cgs.gametype < GT_CTF) { - return; - } - value = cg.snap->ps.stats[STAT_PERSISTANT_POWERUP]; - if ( value ) { - CG_RegisterItemVisuals( value ); - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cg_items[ value ].icon ); - } -} - - - -static void CG_DrawTeamColor(rectDef_t *rect, vec4_t color) { - CG_DrawTeamBackground(rect->x, rect->y, rect->w, rect->h, color[3], cg.snap->ps.persistant[PERS_TEAM]); -} - -static void CG_DrawAreaPowerUp(rectDef_t *rect, int align, float special, float scale, vec4_t color) { - char num[16]; - int sorted[MAX_POWERUPS]; - int sortedTime[MAX_POWERUPS]; - int i, j, k; - int active; - playerState_t *ps; - int t; - gitem_t *item; - float f; - rectDef_t r2; - float *inc; - r2.x = rect->x; - r2.y = rect->y; - r2.w = rect->w; - r2.h = rect->h; - - inc = (align == HUD_VERTICAL) ? &r2.y : &r2.x; - - ps = &cg.snap->ps; - - if ( ps->stats[STAT_HEALTH] <= 0 ) { - return; - } - - // sort the list by time remaining - active = 0; - for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { - if ( !ps->powerups[ i ] ) { - continue; - } - t = ps->powerups[ i ] - cg.time; - // ZOID--don't draw if the power up has unlimited time (999 seconds) - // This is true of the CTF flags - if ( t <= 0 || t >= 999000) { - continue; - } - - // insert into the list - for ( j = 0 ; j < active ; j++ ) { - if ( sortedTime[j] >= t ) { - for ( k = active - 1 ; k >= j ; k-- ) { - sorted[k+1] = sorted[k]; - sortedTime[k+1] = sortedTime[k]; - } - break; - } - } - sorted[j] = i; - sortedTime[j] = t; - active++; - } - - // draw the icons and timers - for ( i = 0 ; i < active ; i++ ) { - item = BG_FindItemForPowerup( sorted[i] ); - - if (item) { - t = ps->powerups[ sorted[i] ]; - if ( t - cg.time >= POWERUP_BLINKS * POWERUP_BLINK_TIME ) { - trap_R_SetColor( NULL ); - } else { - vec4_t modulate; - - f = (float)( t - cg.time ) / POWERUP_BLINK_TIME; - f -= (int)f; - modulate[0] = modulate[1] = modulate[2] = modulate[3] = f; - trap_R_SetColor( modulate ); - } - - CG_DrawPic( r2.x, r2.y, r2.w * .75, r2.h, trap_R_RegisterShader( item->icon ) ); - - Com_sprintf (num, sizeof(num), "%i", sortedTime[i] / 1000); - CG_Text_Paint(r2.x + (r2.w * .75) + 3 , r2.y + r2.h, scale, color, num, 0, 0, 0); - *inc += r2.w + special; - } - - } - trap_R_SetColor( NULL ); - -} - -float CG_GetValue(int ownerDraw) { - centity_t *cent; - clientInfo_t *ci; - playerState_t *ps; - - cent = &cg_entities[cg.snap->ps.clientNum]; - ps = &cg.snap->ps; - - switch (ownerDraw) { - case CG_SELECTEDPLAYER_ARMOR: - ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; - return ci->armor; - break; - case CG_SELECTEDPLAYER_HEALTH: - ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; - return ci->health; - break; - case CG_PLAYER_ARMOR_VALUE: - return ps->stats[STAT_ARMOR]; - break; - case CG_PLAYER_AMMO_VALUE: - if ( cent->currentState.weapon ) { - return ps->ammo[cent->currentState.weapon]; - } - break; - case CG_PLAYER_SCORE: - return cg.snap->ps.persistant[PERS_SCORE]; - break; - case CG_PLAYER_HEALTH: - return ps->stats[STAT_HEALTH]; - break; - case CG_RED_SCORE: - return cgs.scores1; - break; - case CG_BLUE_SCORE: - return cgs.scores2; - break; - default: - break; - } - return -1; -} - -qboolean CG_OtherTeamHasFlag() { - if (cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF) { - int team = cg.snap->ps.persistant[PERS_TEAM]; - if (cgs.gametype == GT_1FCTF) { - if (team == TEAM_RED && cgs.flagStatus == FLAG_TAKEN_BLUE) { - return qtrue; - } else if (team == TEAM_BLUE && cgs.flagStatus == FLAG_TAKEN_RED) { - return qtrue; - } else { - return qfalse; - } - } else { - if (team == TEAM_RED && cgs.redflag == FLAG_TAKEN) { - return qtrue; - } else if (team == TEAM_BLUE && cgs.blueflag == FLAG_TAKEN) { - return qtrue; - } else { - return qfalse; - } - } - } - return qfalse; -} - -qboolean CG_YourTeamHasFlag() { - if (cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF) { - int team = cg.snap->ps.persistant[PERS_TEAM]; - if (cgs.gametype == GT_1FCTF) { - if (team == TEAM_RED && cgs.flagStatus == FLAG_TAKEN_RED) { - return qtrue; - } else if (team == TEAM_BLUE && cgs.flagStatus == FLAG_TAKEN_BLUE) { - return qtrue; - } else { - return qfalse; - } - } else { - if (team == TEAM_RED && cgs.blueflag == FLAG_TAKEN) { - return qtrue; - } else if (team == TEAM_BLUE && cgs.redflag == FLAG_TAKEN) { - return qtrue; - } else { - return qfalse; - } - } - } - return qfalse; -} - -// THINKABOUTME: should these be exclusive or inclusive.. -// -qboolean CG_OwnerDrawVisible(int flags) { - - if (flags & CG_SHOW_TEAMINFO) { - return (cg_currentSelectedPlayer.integer == numSortedTeamPlayers); - } - - if (flags & CG_SHOW_NOTEAMINFO) { - return !(cg_currentSelectedPlayer.integer == numSortedTeamPlayers); - } - - if (flags & CG_SHOW_OTHERTEAMHASFLAG) { - return CG_OtherTeamHasFlag(); - } - - if (flags & CG_SHOW_YOURTEAMHASENEMYFLAG) { - return CG_YourTeamHasFlag(); - } - - if (flags & (CG_SHOW_BLUE_TEAM_HAS_REDFLAG | CG_SHOW_RED_TEAM_HAS_BLUEFLAG)) { - if (flags & CG_SHOW_BLUE_TEAM_HAS_REDFLAG && (cgs.redflag == FLAG_TAKEN || cgs.flagStatus == FLAG_TAKEN_RED)) { - return qtrue; - } else if (flags & CG_SHOW_RED_TEAM_HAS_BLUEFLAG && (cgs.blueflag == FLAG_TAKEN || cgs.flagStatus == FLAG_TAKEN_BLUE)) { - return qtrue; - } - return qfalse; - } - - if (flags & CG_SHOW_ANYTEAMGAME) { - if( cgs.gametype >= GT_TEAM) { - return qtrue; - } - } - - if (flags & CG_SHOW_ANYNONTEAMGAME) { - if( cgs.gametype < GT_TEAM) { - return qtrue; - } - } - - if (flags & CG_SHOW_HARVESTER) { - if( cgs.gametype == GT_HARVESTER ) { - return qtrue; - } else { - return qfalse; - } - } - - if (flags & CG_SHOW_ONEFLAG) { - if( cgs.gametype == GT_1FCTF ) { - return qtrue; - } else { - return qfalse; - } - } - - if (flags & CG_SHOW_CTF) { - if( cgs.gametype == GT_CTF ) { - return qtrue; - } - } - - if (flags & CG_SHOW_OBELISK) { - if( cgs.gametype == GT_OBELISK ) { - return qtrue; - } else { - return qfalse; - } - } - - if (flags & CG_SHOW_HEALTHCRITICAL) { - if (cg.snap->ps.stats[STAT_HEALTH] < 25) { - return qtrue; - } - } - - if (flags & CG_SHOW_HEALTHOK) { - if (cg.snap->ps.stats[STAT_HEALTH] >= 25) { - return qtrue; - } - } - - if (flags & CG_SHOW_SINGLEPLAYER) { - if( cgs.gametype == GT_SINGLE_PLAYER ) { - return qtrue; - } - } - - if (flags & CG_SHOW_TOURNAMENT) { - if( cgs.gametype == GT_TOURNAMENT ) { - return qtrue; - } - } - - if (flags & CG_SHOW_DURINGINCOMINGVOICE) { - } - - if (flags & CG_SHOW_IF_PLAYER_HAS_FLAG) { - if (cg.snap->ps.powerups[PW_REDFLAG] || cg.snap->ps.powerups[PW_BLUEFLAG] || cg.snap->ps.powerups[PW_NEUTRALFLAG]) { - return qtrue; - } - } - return qfalse; -} - - - -static void CG_DrawPlayerHasFlag(rectDef_t *rect, qboolean force2D) { - int adj = (force2D) ? 0 : 2; - if( cg.predictedPlayerState.powerups[PW_REDFLAG] ) { - CG_DrawFlagModel( rect->x + adj, rect->y + adj, rect->w - adj, rect->h - adj, TEAM_RED, force2D); - } else if( cg.predictedPlayerState.powerups[PW_BLUEFLAG] ) { - CG_DrawFlagModel( rect->x + adj, rect->y + adj, rect->w - adj, rect->h - adj, TEAM_BLUE, force2D); - } else if( cg.predictedPlayerState.powerups[PW_NEUTRALFLAG] ) { - CG_DrawFlagModel( rect->x + adj, rect->y + adj, rect->w - adj, rect->h - adj, TEAM_FREE, force2D); - } -} - -static void CG_DrawAreaSystemChat(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader) { - CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, systemChat, 0, 0, 0); -} - -static void CG_DrawAreaTeamChat(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader) { - CG_Text_Paint(rect->x, rect->y + rect->h, scale, color,teamChat1, 0, 0, 0); -} - -static void CG_DrawAreaChat(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader) { - CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, teamChat2, 0, 0, 0); -} - -const char *CG_GetKillerText() { - const char *s = ""; - if ( cg.killerName[0] ) { - s = va("Fragged by %s", cg.killerName ); - } - return s; -} - - -static void CG_DrawKiller(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { - // fragged by ... line - if ( cg.killerName[0] ) { - int x = rect->x + rect->w / 2; - CG_Text_Paint(x - CG_Text_Width(CG_GetKillerText(), scale, 0) / 2, rect->y + rect->h, scale, color, CG_GetKillerText(), 0, 0, textStyle); - } - -} - - -static void CG_DrawCapFragLimit(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle) { - int limit = (cgs.gametype >= GT_CTF) ? cgs.capturelimit : cgs.fraglimit; - CG_Text_Paint(rect->x, rect->y, scale, color, va("%2i", limit),0, 0, textStyle); -} - -static void CG_Draw1stPlace(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle) { - if (cgs.scores1 != SCORE_NOT_PRESENT) { - CG_Text_Paint(rect->x, rect->y, scale, color, va("%2i", cgs.scores1),0, 0, textStyle); - } -} - -static void CG_Draw2ndPlace(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle) { - if (cgs.scores2 != SCORE_NOT_PRESENT) { - CG_Text_Paint(rect->x, rect->y, scale, color, va("%2i", cgs.scores2),0, 0, textStyle); - } -} - -const char *CG_GetGameStatusText() { - const char *s = ""; - if ( cgs.gametype < GT_TEAM) { - if (cg.snap->ps.persistant[PERS_TEAM] != TEAM_SPECTATOR ) { - s = va("%s place with %i",CG_PlaceString( cg.snap->ps.persistant[PERS_RANK] + 1 ),cg.snap->ps.persistant[PERS_SCORE] ); - } - } else { - if ( cg.teamScores[0] == cg.teamScores[1] ) { - s = va("Teams are tied at %i", cg.teamScores[0] ); - } else if ( cg.teamScores[0] >= cg.teamScores[1] ) { - s = va("Red leads Blue, %i to %i", cg.teamScores[0], cg.teamScores[1] ); - } else { - s = va("Blue leads Red, %i to %i", cg.teamScores[1], cg.teamScores[0] ); - } - } - return s; -} - -static void CG_DrawGameStatus(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { - CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, CG_GetGameStatusText(), 0, 0, textStyle); -} - -const char *CG_GameTypeString() { - if ( cgs.gametype == GT_FFA ) { - return "Free For All"; - } else if ( cgs.gametype == GT_TEAM ) { - return "Team Deathmatch"; - } else if ( cgs.gametype == GT_CTF ) { - return "Capture the Flag"; - } else if ( cgs.gametype == GT_1FCTF ) { - return "One Flag CTF"; - } else if ( cgs.gametype == GT_OBELISK ) { - return "Overload"; - } else if ( cgs.gametype == GT_HARVESTER ) { - return "Harvester"; - } - return ""; -} -static void CG_DrawGameType(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { - CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, CG_GameTypeString(), 0, 0, textStyle); -} - -static void CG_Text_Paint_Limit(float *maxX, float x, float y, float scale, vec4_t color, const char* text, float adjust, int limit) { - int len, count; - vec4_t newColor; - glyphInfo_t *glyph; - if (text) { -// TTimo: FIXME -// const unsigned char *s = text; // bk001206 - unsigned - const char *s = text; - float max = *maxX; - float useScale; - fontInfo_t *font = &cgDC.Assets.textFont; - if (scale <= cg_smallFont.value) { - font = &cgDC.Assets.smallFont; - } else if (scale > cg_bigFont.value) { - font = &cgDC.Assets.bigFont; - } - useScale = scale * font->glyphScale; - trap_R_SetColor( color ); - len = strlen(text); - if (limit > 0 && len > limit) { - len = limit; - } - count = 0; - while (s && *s && count < len) { - glyph = &font->glyphs[(int)*s]; // TTimo: FIXME: getting nasty warnings without the cast, hopefully this doesn't break the VM build - if ( Q_IsColorString( s ) ) { - memcpy( newColor, g_color_table[ColorIndex(*(s+1))], sizeof( newColor ) ); - newColor[3] = color[3]; - trap_R_SetColor( newColor ); - s += 2; - continue; - } else { - float yadj = useScale * glyph->top; - if (CG_Text_Width(s, useScale, 1) + x > max) { - *maxX = 0; - break; - } - CG_Text_PaintChar(x, y - yadj, - glyph->imageWidth, - glyph->imageHeight, - useScale, - glyph->s, - glyph->t, - glyph->s2, - glyph->t2, - glyph->glyph); - x += (glyph->xSkip * useScale) + adjust; - *maxX = x; - count++; - s++; - } - } - trap_R_SetColor( NULL ); - } - -} - - - -#define PIC_WIDTH 12 - -void CG_DrawNewTeamInfo(rectDef_t *rect, float text_x, float text_y, float scale, vec4_t color, qhandle_t shader) { - int xx; - float y; - int i, j, len, count; - const char *p; - vec4_t hcolor; - float pwidth, lwidth, maxx, leftOver; - clientInfo_t *ci; - gitem_t *item; - qhandle_t h; - - // max player name width - pwidth = 0; - count = (numSortedTeamPlayers > 8) ? 8 : numSortedTeamPlayers; - for (i = 0; i < count; i++) { - ci = cgs.clientinfo + sortedTeamPlayers[i]; - if ( ci->infoValid && ci->team == cg.snap->ps.persistant[PERS_TEAM]) { - len = CG_Text_Width( ci->name, scale, 0); - if (len > pwidth) - pwidth = len; - } - } - - // max location name width - lwidth = 0; - for (i = 1; i < MAX_LOCATIONS; i++) { - p = CG_ConfigString(CS_LOCATIONS + i); - if (p && *p) { - len = CG_Text_Width(p, scale, 0); - if (len > lwidth) - lwidth = len; - } - } - - y = rect->y; - - for (i = 0; i < count; i++) { - ci = cgs.clientinfo + sortedTeamPlayers[i]; - if ( ci->infoValid && ci->team == cg.snap->ps.persistant[PERS_TEAM]) { - - xx = rect->x + 1; - for (j = 0; j <= PW_NUM_POWERUPS; j++) { - if (ci->powerups & (1 << j)) { - - item = BG_FindItemForPowerup( j ); - - if (item) { - CG_DrawPic( xx, y, PIC_WIDTH, PIC_WIDTH, trap_R_RegisterShader( item->icon ) ); - xx += PIC_WIDTH; - } - } - } - - // FIXME: max of 3 powerups shown properly - xx = rect->x + (PIC_WIDTH * 3) + 2; - - CG_GetColorForHealth( ci->health, ci->armor, hcolor ); - trap_R_SetColor(hcolor); - CG_DrawPic( xx, y + 1, PIC_WIDTH - 2, PIC_WIDTH - 2, cgs.media.heartShader ); - - //Com_sprintf (st, sizeof(st), "%3i %3i", ci->health, ci->armor); - //CG_Text_Paint(xx, y + text_y, scale, hcolor, st, 0, 0); - - // draw weapon icon - xx += PIC_WIDTH + 1; - -// weapon used is not that useful, use the space for task -#if 0 - if ( cg_weapons[ci->curWeapon].weaponIcon ) { - CG_DrawPic( xx, y, PIC_WIDTH, PIC_WIDTH, cg_weapons[ci->curWeapon].weaponIcon ); - } else { - CG_DrawPic( xx, y, PIC_WIDTH, PIC_WIDTH, cgs.media.deferShader ); - } -#endif - - trap_R_SetColor(NULL); - if (cgs.orderPending) { - // blink the icon - if ( cg.time > cgs.orderTime - 2500 && (cg.time >> 9 ) & 1 ) { - h = 0; - } else { - h = CG_StatusHandle(cgs.currentOrder); - } - } else { - h = CG_StatusHandle(ci->teamTask); - } - - if (h) { - CG_DrawPic( xx, y, PIC_WIDTH, PIC_WIDTH, h); - } - - xx += PIC_WIDTH + 1; - - leftOver = rect->w - xx; - maxx = xx + leftOver / 3; - - - - CG_Text_Paint_Limit(&maxx, xx, y + text_y, scale, color, ci->name, 0, 0); - - p = CG_ConfigString(CS_LOCATIONS + ci->location); - if (!p || !*p) { - p = "unknown"; - } - - xx += leftOver / 3 + 2; - maxx = rect->w - 4; - - CG_Text_Paint_Limit(&maxx, xx, y + text_y, scale, color, p, 0, 0); - y += text_y + 2; - if ( y + text_y + 2 > rect->y + rect->h ) { - break; - } - - } - } -} - - -void CG_DrawTeamSpectators(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader) { - if (cg.spectatorLen) { - float maxX; - - if (cg.spectatorWidth == -1) { - cg.spectatorWidth = 0; - cg.spectatorPaintX = rect->x + 1; - cg.spectatorPaintX2 = -1; - } - - if (cg.spectatorOffset > cg.spectatorLen) { - cg.spectatorOffset = 0; - cg.spectatorPaintX = rect->x + 1; - cg.spectatorPaintX2 = -1; - } - - if (cg.time > cg.spectatorTime) { - cg.spectatorTime = cg.time + 10; - if (cg.spectatorPaintX <= rect->x + 2) { - if (cg.spectatorOffset < cg.spectatorLen) { - cg.spectatorPaintX += CG_Text_Width(&cg.spectatorList[cg.spectatorOffset], scale, 1) - 1; - cg.spectatorOffset++; - } else { - cg.spectatorOffset = 0; - if (cg.spectatorPaintX2 >= 0) { - cg.spectatorPaintX = cg.spectatorPaintX2; - } else { - cg.spectatorPaintX = rect->x + rect->w - 2; - } - cg.spectatorPaintX2 = -1; - } - } else { - cg.spectatorPaintX--; - if (cg.spectatorPaintX2 >= 0) { - cg.spectatorPaintX2--; - } - } - } - - maxX = rect->x + rect->w - 2; - CG_Text_Paint_Limit(&maxX, cg.spectatorPaintX, rect->y + rect->h - 3, scale, color, &cg.spectatorList[cg.spectatorOffset], 0, 0); - if (cg.spectatorPaintX2 >= 0) { - float maxX2 = rect->x + rect->w - 2; - CG_Text_Paint_Limit(&maxX2, cg.spectatorPaintX2, rect->y + rect->h - 3, scale, color, cg.spectatorList, 0, cg.spectatorOffset); - } - if (cg.spectatorOffset && maxX > 0) { - // if we have an offset ( we are skipping the first part of the string ) and we fit the string - if (cg.spectatorPaintX2 == -1) { - cg.spectatorPaintX2 = rect->x + rect->w - 2; - } - } else { - cg.spectatorPaintX2 = -1; - } - - } -} - - - -void CG_DrawMedal(int ownerDraw, rectDef_t *rect, float scale, vec4_t color, qhandle_t shader) { - score_t *score = &cg.scores[cg.selectedScore]; - float value = 0; - char *text = NULL; - color[3] = 0.25; - - switch (ownerDraw) { - case CG_ACCURACY: - value = score->accuracy; - break; - case CG_ASSISTS: - value = score->assistCount; - break; - case CG_DEFEND: - value = score->defendCount; - break; - case CG_EXCELLENT: - value = score->excellentCount; - break; - case CG_IMPRESSIVE: - value = score->impressiveCount; - break; - case CG_PERFECT: - value = score->perfect; - break; - case CG_GAUNTLET: - value = score->guantletCount; - break; - case CG_CAPTURES: - value = score->captures; - break; - } - - if (value > 0) { - if (ownerDraw != CG_PERFECT) { - if (ownerDraw == CG_ACCURACY) { - text = va("%i%%", (int)value); - if (value > 50) { - color[3] = 1.0; - } - } else { - text = va("%i", (int)value); - color[3] = 1.0; - } - } else { - if (value) { - color[3] = 1.0; - } - text = "Wow"; - } - } - - trap_R_SetColor(color); - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); - - if (text) { - color[3] = 1.0; - value = CG_Text_Width(text, scale, 0); - CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h + 10 , scale, color, text, 0, 0, 0); - } - trap_R_SetColor(NULL); - -} - - -// -void CG_OwnerDraw(float x, float y, float w, float h, float text_x, float text_y, int ownerDraw, int ownerDrawFlags, int align, float special, float scale, vec4_t color, qhandle_t shader, int textStyle) { - rectDef_t rect; - - if ( cg_drawStatus.integer == 0 ) { - return; - } - - //if (ownerDrawFlags != 0 && !CG_OwnerDrawVisible(ownerDrawFlags)) { - // return; - //} - - rect.x = x; - rect.y = y; - rect.w = w; - rect.h = h; - - switch (ownerDraw) { - case CG_PLAYER_ARMOR_ICON: - CG_DrawPlayerArmorIcon(&rect, ownerDrawFlags & CG_SHOW_2DONLY); - break; - case CG_PLAYER_ARMOR_ICON2D: - CG_DrawPlayerArmorIcon(&rect, qtrue); - break; - case CG_PLAYER_ARMOR_VALUE: - CG_DrawPlayerArmorValue(&rect, scale, color, shader, textStyle); - break; - case CG_PLAYER_AMMO_ICON: - CG_DrawPlayerAmmoIcon(&rect, ownerDrawFlags & CG_SHOW_2DONLY); - break; - case CG_PLAYER_AMMO_ICON2D: - CG_DrawPlayerAmmoIcon(&rect, qtrue); - break; - case CG_PLAYER_AMMO_VALUE: - CG_DrawPlayerAmmoValue(&rect, scale, color, shader, textStyle); - break; - case CG_SELECTEDPLAYER_HEAD: - CG_DrawSelectedPlayerHead(&rect, ownerDrawFlags & CG_SHOW_2DONLY, qfalse); - break; - case CG_VOICE_HEAD: - CG_DrawSelectedPlayerHead(&rect, ownerDrawFlags & CG_SHOW_2DONLY, qtrue); - break; - case CG_VOICE_NAME: - CG_DrawSelectedPlayerName(&rect, scale, color, qtrue, textStyle); - break; - case CG_SELECTEDPLAYER_STATUS: - CG_DrawSelectedPlayerStatus(&rect); - break; - case CG_SELECTEDPLAYER_ARMOR: - CG_DrawSelectedPlayerArmor(&rect, scale, color, shader, textStyle); - break; - case CG_SELECTEDPLAYER_HEALTH: - CG_DrawSelectedPlayerHealth(&rect, scale, color, shader, textStyle); - break; - case CG_SELECTEDPLAYER_NAME: - CG_DrawSelectedPlayerName(&rect, scale, color, qfalse, textStyle); - break; - case CG_SELECTEDPLAYER_LOCATION: - CG_DrawSelectedPlayerLocation(&rect, scale, color, textStyle); - break; - case CG_SELECTEDPLAYER_WEAPON: - CG_DrawSelectedPlayerWeapon(&rect); - break; - case CG_SELECTEDPLAYER_POWERUP: - CG_DrawSelectedPlayerPowerup(&rect, ownerDrawFlags & CG_SHOW_2DONLY); - break; - case CG_PLAYER_HEAD: - CG_DrawPlayerHead(&rect, ownerDrawFlags & CG_SHOW_2DONLY); - break; - case CG_PLAYER_ITEM: - CG_DrawPlayerItem(&rect, scale, ownerDrawFlags & CG_SHOW_2DONLY); - break; - case CG_PLAYER_SCORE: - CG_DrawPlayerScore(&rect, scale, color, shader, textStyle); - break; - case CG_PLAYER_HEALTH: - CG_DrawPlayerHealth(&rect, scale, color, shader, textStyle); - break; - case CG_RED_SCORE: - CG_DrawRedScore(&rect, scale, color, shader, textStyle); - break; - case CG_BLUE_SCORE: - CG_DrawBlueScore(&rect, scale, color, shader, textStyle); - break; - case CG_RED_NAME: - CG_DrawRedName(&rect, scale, color, textStyle); - break; - case CG_BLUE_NAME: - CG_DrawBlueName(&rect, scale, color, textStyle); - break; - case CG_BLUE_FLAGHEAD: - CG_DrawBlueFlagHead(&rect); - break; - case CG_BLUE_FLAGSTATUS: - CG_DrawBlueFlagStatus(&rect, shader); - break; - case CG_BLUE_FLAGNAME: - CG_DrawBlueFlagName(&rect, scale, color, textStyle); - break; - case CG_RED_FLAGHEAD: - CG_DrawRedFlagHead(&rect); - break; - case CG_RED_FLAGSTATUS: - CG_DrawRedFlagStatus(&rect, shader); - break; - case CG_RED_FLAGNAME: - CG_DrawRedFlagName(&rect, scale, color, textStyle); - break; - case CG_HARVESTER_SKULLS: - CG_HarvesterSkulls(&rect, scale, color, qfalse, textStyle); - break; - case CG_HARVESTER_SKULLS2D: - CG_HarvesterSkulls(&rect, scale, color, qtrue, textStyle); - break; - case CG_ONEFLAG_STATUS: - CG_OneFlagStatus(&rect); - break; - case CG_PLAYER_LOCATION: - CG_DrawPlayerLocation(&rect, scale, color, textStyle); - break; - case CG_TEAM_COLOR: - CG_DrawTeamColor(&rect, color); - break; - case CG_CTF_POWERUP: - CG_DrawCTFPowerUp(&rect); - break; - case CG_AREA_POWERUP: - CG_DrawAreaPowerUp(&rect, align, special, scale, color); - break; - case CG_PLAYER_STATUS: - CG_DrawPlayerStatus(&rect); - break; - case CG_PLAYER_HASFLAG: - CG_DrawPlayerHasFlag(&rect, qfalse); - break; - case CG_PLAYER_HASFLAG2D: - CG_DrawPlayerHasFlag(&rect, qtrue); - break; - case CG_AREA_SYSTEMCHAT: - CG_DrawAreaSystemChat(&rect, scale, color, shader); - break; - case CG_AREA_TEAMCHAT: - CG_DrawAreaTeamChat(&rect, scale, color, shader); - break; - case CG_AREA_CHAT: - CG_DrawAreaChat(&rect, scale, color, shader); - break; - case CG_GAME_TYPE: - CG_DrawGameType(&rect, scale, color, shader, textStyle); - break; - case CG_GAME_STATUS: - CG_DrawGameStatus(&rect, scale, color, shader, textStyle); - break; - case CG_KILLER: - CG_DrawKiller(&rect, scale, color, shader, textStyle); - break; - case CG_ACCURACY: - case CG_ASSISTS: - case CG_DEFEND: - case CG_EXCELLENT: - case CG_IMPRESSIVE: - case CG_PERFECT: - case CG_GAUNTLET: - case CG_CAPTURES: - CG_DrawMedal(ownerDraw, &rect, scale, color, shader); - break; - case CG_SPECTATORS: - CG_DrawTeamSpectators(&rect, scale, color, shader); - break; - case CG_TEAMINFO: - if (cg_currentSelectedPlayer.integer == numSortedTeamPlayers) { - CG_DrawNewTeamInfo(&rect, text_x, text_y, scale, color, shader); - } - break; - case CG_CAPFRAGLIMIT: - CG_DrawCapFragLimit(&rect, scale, color, shader, textStyle); - break; - case CG_1STPLACE: - CG_Draw1stPlace(&rect, scale, color, shader, textStyle); - break; - case CG_2NDPLACE: - CG_Draw2ndPlace(&rect, scale, color, shader, textStyle); - break; - default: - break; - } -} - -void CG_MouseEvent(int x, int y) { - int n; - - if ( (cg.predictedPlayerState.pm_type == PM_NORMAL || cg.predictedPlayerState.pm_type == PM_SPECTATOR) && cg.showScores == qfalse) { - trap_Key_SetCatcher(0); - return; - } - - cgs.cursorX+= x; - if (cgs.cursorX < 0) - cgs.cursorX = 0; - else if (cgs.cursorX > 640) - cgs.cursorX = 640; - - cgs.cursorY += y; - if (cgs.cursorY < 0) - cgs.cursorY = 0; - else if (cgs.cursorY > 480) - cgs.cursorY = 480; - - n = Display_CursorType(cgs.cursorX, cgs.cursorY); - cgs.activeCursor = 0; - if (n == CURSOR_ARROW) { - cgs.activeCursor = cgs.media.selectCursor; - } else if (n == CURSOR_SIZER) { - cgs.activeCursor = cgs.media.sizeCursor; - } - - if (cgs.capturedItem) { - Display_MouseMove(cgs.capturedItem, x, y); - } else { - Display_MouseMove(NULL, cgs.cursorX, cgs.cursorY); - } - -} - -/* -================== -CG_HideTeamMenus -================== - -*/ -void CG_HideTeamMenu() { - Menus_CloseByName("teamMenu"); - Menus_CloseByName("getMenu"); -} - -/* -================== -CG_ShowTeamMenus -================== - -*/ -void CG_ShowTeamMenu() { - Menus_OpenByName("teamMenu"); -} - - - - -/* -================== -CG_EventHandling -================== - type 0 - no event handling - 1 - team menu - 2 - hud editor - -*/ -void CG_EventHandling(int type) { - cgs.eventHandling = type; - if (type == CGAME_EVENT_NONE) { - CG_HideTeamMenu(); - } else if (type == CGAME_EVENT_TEAMMENU) { - //CG_ShowTeamMenu(); - } else if (type == CGAME_EVENT_SCOREBOARD) { - } - -} - - - -void CG_KeyEvent(int key, qboolean down) { - - if (!down) { - return; - } - - if ( cg.predictedPlayerState.pm_type == PM_NORMAL || (cg.predictedPlayerState.pm_type == PM_SPECTATOR && cg.showScores == qfalse)) { - CG_EventHandling(CGAME_EVENT_NONE); - trap_Key_SetCatcher(0); - return; - } - - //if (key == trap_Key_GetKey("teamMenu") || !Display_CaptureItem(cgs.cursorX, cgs.cursorY)) { - // if we see this then we should always be visible - // CG_EventHandling(CGAME_EVENT_NONE); - // trap_Key_SetCatcher(0); - //} - - - - Display_HandleKey(key, down, cgs.cursorX, cgs.cursorY); - - if (cgs.capturedItem) { - cgs.capturedItem = NULL; - } else { - if (key == K_MOUSE2 && down) { - cgs.capturedItem = Display_CaptureItem(cgs.cursorX, cgs.cursorY); - } - } -} - -int CG_ClientNumFromName(const char *p) { - int i; - for (i = 0; i < cgs.maxclients; i++) { - if (cgs.clientinfo[i].infoValid && Q_stricmp(cgs.clientinfo[i].name, p) == 0) { - return i; - } - } - return -1; -} - -void CG_ShowResponseHead() { - Menus_OpenByName("voiceMenu"); - trap_Cvar_Set("cl_conXOffset", "72"); - cg.voiceTime = cg.time; -} - -void CG_RunMenuScript(char **args) { -} - - -void CG_GetTeamColor(vec4_t *color) { - if (cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED) { - (*color)[0] = 1.0f; - (*color)[3] = 0.25f; - (*color)[1] = (*color)[2] = 0.0f; - } else if (cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE) { - (*color)[0] = (*color)[1] = 0.0f; - (*color)[2] = 1.0f; - (*color)[3] = 0.25f; - } else { - (*color)[0] = (*color)[2] = 0.0f; - (*color)[1] = 0.17f; - (*color)[3] = 0.25f; - } -} - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#ifndef MISSIONPACK // bk001204 +#error This file not be used for classic Q3A. +#endif + +#include "cg_local.h" +#include "../ui/ui_shared.h" + +extern displayContextDef_t cgDC; + + +// set in CG_ParseTeamInfo + +//static int sortedTeamPlayers[TEAM_MAXOVERLAY]; +//static int numSortedTeamPlayers; +int drawTeamOverlayModificationCount = -1; + +//static char systemChat[256]; +//static char teamChat1[256]; +//static char teamChat2[256]; + +void CG_InitTeamChat() { + memset(teamChat1, 0, sizeof(teamChat1)); + memset(teamChat2, 0, sizeof(teamChat2)); + memset(systemChat, 0, sizeof(systemChat)); +} + +void CG_SetPrintString(int type, const char *p) { + if (type == SYSTEM_PRINT) { + strcpy(systemChat, p); + } else { + strcpy(teamChat2, teamChat1); + strcpy(teamChat1, p); + } +} + +void CG_CheckOrderPending() { + if (cgs.gametype < GT_CTF) { + return; + } + if (cgs.orderPending) { + //clientInfo_t *ci = cgs.clientinfo + sortedTeamPlayers[cg_currentSelectedPlayer.integer]; + const char *p1, *p2, *b; + p1 = p2 = b = NULL; + switch (cgs.currentOrder) { + case TEAMTASK_OFFENSE: + p1 = VOICECHAT_ONOFFENSE; + p2 = VOICECHAT_OFFENSE; + b = "+button7; wait; -button7"; + break; + case TEAMTASK_DEFENSE: + p1 = VOICECHAT_ONDEFENSE; + p2 = VOICECHAT_DEFEND; + b = "+button8; wait; -button8"; + break; + case TEAMTASK_PATROL: + p1 = VOICECHAT_ONPATROL; + p2 = VOICECHAT_PATROL; + b = "+button9; wait; -button9"; + break; + case TEAMTASK_FOLLOW: + p1 = VOICECHAT_ONFOLLOW; + p2 = VOICECHAT_FOLLOWME; + b = "+button10; wait; -button10"; + break; + case TEAMTASK_CAMP: + p1 = VOICECHAT_ONCAMPING; + p2 = VOICECHAT_CAMP; + break; + case TEAMTASK_RETRIEVE: + p1 = VOICECHAT_ONGETFLAG; + p2 = VOICECHAT_RETURNFLAG; + break; + case TEAMTASK_ESCORT: + p1 = VOICECHAT_ONFOLLOWCARRIER; + p2 = VOICECHAT_FOLLOWFLAGCARRIER; + break; + } + + if (cg_currentSelectedPlayer.integer == numSortedTeamPlayers) { + // to everyone + trap_SendConsoleCommand(va("cmd vsay_team %s\n", p2)); + } else { + // for the player self + if (sortedTeamPlayers[cg_currentSelectedPlayer.integer] == cg.snap->ps.clientNum && p1) { + trap_SendConsoleCommand(va("teamtask %i\n", cgs.currentOrder)); + //trap_SendConsoleCommand(va("cmd say_team %s\n", p2)); + trap_SendConsoleCommand(va("cmd vsay_team %s\n", p1)); + } else if (p2) { + //trap_SendConsoleCommand(va("cmd say_team %s, %s\n", ci->name,p)); + trap_SendConsoleCommand(va("cmd vtell %d %s\n", sortedTeamPlayers[cg_currentSelectedPlayer.integer], p2)); + } + } + if (b) { + trap_SendConsoleCommand(b); + } + cgs.orderPending = qfalse; + } +} + +static void CG_SetSelectedPlayerName() { + if (cg_currentSelectedPlayer.integer >= 0 && cg_currentSelectedPlayer.integer < numSortedTeamPlayers) { + clientInfo_t *ci = cgs.clientinfo + sortedTeamPlayers[cg_currentSelectedPlayer.integer]; + if (ci) { + trap_Cvar_Set("cg_selectedPlayerName", ci->name); + trap_Cvar_Set("cg_selectedPlayer", va("%d", sortedTeamPlayers[cg_currentSelectedPlayer.integer])); + cgs.currentOrder = ci->teamTask; + } + } else { + trap_Cvar_Set("cg_selectedPlayerName", "Everyone"); + } +} +int CG_GetSelectedPlayer() { + if (cg_currentSelectedPlayer.integer < 0 || cg_currentSelectedPlayer.integer >= numSortedTeamPlayers) { + cg_currentSelectedPlayer.integer = 0; + } + return cg_currentSelectedPlayer.integer; +} + +void CG_SelectNextPlayer() { + CG_CheckOrderPending(); + if (cg_currentSelectedPlayer.integer >= 0 && cg_currentSelectedPlayer.integer < numSortedTeamPlayers) { + cg_currentSelectedPlayer.integer++; + } else { + cg_currentSelectedPlayer.integer = 0; + } + CG_SetSelectedPlayerName(); +} + +void CG_SelectPrevPlayer() { + CG_CheckOrderPending(); + if (cg_currentSelectedPlayer.integer > 0 && cg_currentSelectedPlayer.integer < numSortedTeamPlayers) { + cg_currentSelectedPlayer.integer--; + } else { + cg_currentSelectedPlayer.integer = numSortedTeamPlayers; + } + CG_SetSelectedPlayerName(); +} + + +static void CG_DrawPlayerArmorIcon( rectDef_t *rect, qboolean draw2D ) { + centity_t *cent; + playerState_t *ps; + vec3_t angles; + vec3_t origin; + + if ( cg_drawStatus.integer == 0 ) { + return; + } + + cent = &cg_entities[cg.snap->ps.clientNum]; + ps = &cg.snap->ps; + + if ( draw2D || ( !cg_draw3dIcons.integer && cg_drawIcons.integer) ) { // bk001206 - parentheses + CG_DrawPic( rect->x, rect->y + rect->h/2 + 1, rect->w, rect->h, cgs.media.armorIcon ); + } else if (cg_draw3dIcons.integer) { + VectorClear( angles ); + origin[0] = 90; + origin[1] = 0; + origin[2] = -10; + angles[YAW] = ( cg.time & 2047 ) * 360 / 2048.0; + + CG_Draw3DModel( rect->x, rect->y, rect->w, rect->h, cgs.media.armorModel, 0, origin, angles ); + } + +} + +static void CG_DrawPlayerArmorValue(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle) { + char num[16]; + int value; + centity_t *cent; + playerState_t *ps; + + cent = &cg_entities[cg.snap->ps.clientNum]; + ps = &cg.snap->ps; + + value = ps->stats[STAT_ARMOR]; + + + if (shader) { + trap_R_SetColor( color ); + CG_DrawPic(rect->x, rect->y, rect->w, rect->h, shader); + trap_R_SetColor( NULL ); + } else { + Com_sprintf (num, sizeof(num), "%i", value); + value = CG_Text_Width(num, scale, 0); + CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h, scale, color, num, 0, 0, textStyle); + } +} + +#ifndef MISSIONPACK // bk001206 +static float healthColors[4][4] = { +// { 0.2, 1.0, 0.2, 1.0 } , { 1.0, 0.2, 0.2, 1.0 }, {0.5, 0.5, 0.5, 1} }; + // bk0101016 - float const + { 1.0f, 0.69f, 0.0f, 1.0f } , // normal + { 1.0f, 0.2f, 0.2f, 1.0f }, // low health + { 0.5f, 0.5f, 0.5f, 1.0f}, // weapon firing + { 1.0f, 1.0f, 1.0f, 1.0f } }; // health > 100 +#endif + +static void CG_DrawPlayerAmmoIcon( rectDef_t *rect, qboolean draw2D ) { + centity_t *cent; + playerState_t *ps; + vec3_t angles; + vec3_t origin; + + cent = &cg_entities[cg.snap->ps.clientNum]; + ps = &cg.snap->ps; + + if ( draw2D || (!cg_draw3dIcons.integer && cg_drawIcons.integer) ) { // bk001206 - parentheses + qhandle_t icon; + icon = cg_weapons[ cg.predictedPlayerState.weapon ].ammoIcon; + if ( icon ) { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, icon ); + } + } else if (cg_draw3dIcons.integer) { + if ( cent->currentState.weapon && cg_weapons[ cent->currentState.weapon ].ammoModel ) { + VectorClear( angles ); + origin[0] = 70; + origin[1] = 0; + origin[2] = 0; + angles[YAW] = 90 + 20 * sin( cg.time / 1000.0 ); + CG_Draw3DModel( rect->x, rect->y, rect->w, rect->h, cg_weapons[ cent->currentState.weapon ].ammoModel, 0, origin, angles ); + } + } +} + +static void CG_DrawPlayerAmmoValue(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle) { + char num[16]; + int value; + centity_t *cent; + playerState_t *ps; + + cent = &cg_entities[cg.snap->ps.clientNum]; + ps = &cg.snap->ps; + + if ( cent->currentState.weapon ) { + value = ps->ammo[cent->currentState.weapon]; + if ( value > -1 ) { + if (shader) { + trap_R_SetColor( color ); + CG_DrawPic(rect->x, rect->y, rect->w, rect->h, shader); + trap_R_SetColor( NULL ); + } else { + Com_sprintf (num, sizeof(num), "%i", value); + value = CG_Text_Width(num, scale, 0); + CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h, scale, color, num, 0, 0, textStyle); + } + } + } + +} + + + +static void CG_DrawPlayerHead(rectDef_t *rect, qboolean draw2D) { + vec3_t angles; + float size, stretch; + float frac; + float x = rect->x; + + VectorClear( angles ); + + if ( cg.damageTime && cg.time - cg.damageTime < DAMAGE_TIME ) { + frac = (float)(cg.time - cg.damageTime ) / DAMAGE_TIME; + size = rect->w * 1.25 * ( 1.5 - frac * 0.5 ); + + stretch = size - rect->w * 1.25; + // kick in the direction of damage + x -= stretch * 0.5 + cg.damageX * stretch * 0.5; + + cg.headStartYaw = 180 + cg.damageX * 45; + + cg.headEndYaw = 180 + 20 * cos( crandom()*M_PI ); + cg.headEndPitch = 5 * cos( crandom()*M_PI ); + + cg.headStartTime = cg.time; + cg.headEndTime = cg.time + 100 + random() * 2000; + } else { + if ( cg.time >= cg.headEndTime ) { + // select a new head angle + cg.headStartYaw = cg.headEndYaw; + cg.headStartPitch = cg.headEndPitch; + cg.headStartTime = cg.headEndTime; + cg.headEndTime = cg.time + 100 + random() * 2000; + + cg.headEndYaw = 180 + 20 * cos( crandom()*M_PI ); + cg.headEndPitch = 5 * cos( crandom()*M_PI ); + } + + size = rect->w * 1.25; + } + + // if the server was frozen for a while we may have a bad head start time + if ( cg.headStartTime > cg.time ) { + cg.headStartTime = cg.time; + } + + frac = ( cg.time - cg.headStartTime ) / (float)( cg.headEndTime - cg.headStartTime ); + frac = frac * frac * ( 3 - 2 * frac ); + angles[YAW] = cg.headStartYaw + ( cg.headEndYaw - cg.headStartYaw ) * frac; + angles[PITCH] = cg.headStartPitch + ( cg.headEndPitch - cg.headStartPitch ) * frac; + + CG_DrawHead( x, rect->y, rect->w, rect->h, cg.snap->ps.clientNum, angles ); +} + +static void CG_DrawSelectedPlayerHealth( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + clientInfo_t *ci; + int value; + char num[16]; + + ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; + if (ci) { + if (shader) { + trap_R_SetColor( color ); + CG_DrawPic(rect->x, rect->y, rect->w, rect->h, shader); + trap_R_SetColor( NULL ); + } else { + Com_sprintf (num, sizeof(num), "%i", ci->health); + value = CG_Text_Width(num, scale, 0); + CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h, scale, color, num, 0, 0, textStyle); + } + } +} + +static void CG_DrawSelectedPlayerArmor( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + clientInfo_t *ci; + int value; + char num[16]; + ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; + if (ci) { + if (ci->armor > 0) { + if (shader) { + trap_R_SetColor( color ); + CG_DrawPic(rect->x, rect->y, rect->w, rect->h, shader); + trap_R_SetColor( NULL ); + } else { + Com_sprintf (num, sizeof(num), "%i", ci->armor); + value = CG_Text_Width(num, scale, 0); + CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h, scale, color, num, 0, 0, textStyle); + } + } + } +} + +qhandle_t CG_StatusHandle(int task) { + qhandle_t h = cgs.media.assaultShader; + switch (task) { + case TEAMTASK_OFFENSE : + h = cgs.media.assaultShader; + break; + case TEAMTASK_DEFENSE : + h = cgs.media.defendShader; + break; + case TEAMTASK_PATROL : + h = cgs.media.patrolShader; + break; + case TEAMTASK_FOLLOW : + h = cgs.media.followShader; + break; + case TEAMTASK_CAMP : + h = cgs.media.campShader; + break; + case TEAMTASK_RETRIEVE : + h = cgs.media.retrieveShader; + break; + case TEAMTASK_ESCORT : + h = cgs.media.escortShader; + break; + default : + h = cgs.media.assaultShader; + break; + } + return h; +} + +static void CG_DrawSelectedPlayerStatus( rectDef_t *rect ) { + clientInfo_t *ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; + if (ci) { + qhandle_t h; + if (cgs.orderPending) { + // blink the icon + if ( cg.time > cgs.orderTime - 2500 && (cg.time >> 9 ) & 1 ) { + return; + } + h = CG_StatusHandle(cgs.currentOrder); + } else { + h = CG_StatusHandle(ci->teamTask); + } + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, h ); + } +} + + +static void CG_DrawPlayerStatus( rectDef_t *rect ) { + clientInfo_t *ci = &cgs.clientinfo[cg.snap->ps.clientNum]; + if (ci) { + qhandle_t h = CG_StatusHandle(ci->teamTask); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, h); + } +} + + +static void CG_DrawSelectedPlayerName( rectDef_t *rect, float scale, vec4_t color, qboolean voice, int textStyle) { + clientInfo_t *ci; + ci = cgs.clientinfo + ((voice) ? cgs.currentVoiceClient : sortedTeamPlayers[CG_GetSelectedPlayer()]); + if (ci) { + CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, ci->name, 0, 0, textStyle); + } +} + +static void CG_DrawSelectedPlayerLocation( rectDef_t *rect, float scale, vec4_t color, int textStyle ) { + clientInfo_t *ci; + ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; + if (ci) { + const char *p = CG_ConfigString(CS_LOCATIONS + ci->location); + if (!p || !*p) { + p = "unknown"; + } + CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, p, 0, 0, textStyle); + } +} + +static void CG_DrawPlayerLocation( rectDef_t *rect, float scale, vec4_t color, int textStyle ) { + clientInfo_t *ci = &cgs.clientinfo[cg.snap->ps.clientNum]; + if (ci) { + const char *p = CG_ConfigString(CS_LOCATIONS + ci->location); + if (!p || !*p) { + p = "unknown"; + } + CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, p, 0, 0, textStyle); + } +} + + + +static void CG_DrawSelectedPlayerWeapon( rectDef_t *rect ) { + clientInfo_t *ci; + + ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; + if (ci) { + if ( cg_weapons[ci->curWeapon].weaponIcon ) { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cg_weapons[ci->curWeapon].weaponIcon ); + } else { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.deferShader); + } + } +} + +static void CG_DrawPlayerScore( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + char num[16]; + int value = cg.snap->ps.persistant[PERS_SCORE]; + + if (shader) { + trap_R_SetColor( color ); + CG_DrawPic(rect->x, rect->y, rect->w, rect->h, shader); + trap_R_SetColor( NULL ); + } else { + Com_sprintf (num, sizeof(num), "%i", value); + value = CG_Text_Width(num, scale, 0); + CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h, scale, color, num, 0, 0, textStyle); + } +} + +static void CG_DrawPlayerItem( rectDef_t *rect, float scale, qboolean draw2D) { + int value; + vec3_t origin, angles; + + value = cg.snap->ps.stats[STAT_HOLDABLE_ITEM]; + if ( value ) { + CG_RegisterItemVisuals( value ); + + if (qtrue) { + CG_RegisterItemVisuals( value ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cg_items[ value ].icon ); + } else { + VectorClear( angles ); + origin[0] = 90; + origin[1] = 0; + origin[2] = -10; + angles[YAW] = ( cg.time & 2047 ) * 360 / 2048.0; + CG_Draw3DModel(rect->x, rect->y, rect->w, rect->h, cg_items[ value ].models[0], 0, origin, angles ); + } + } + +} + + +static void CG_DrawSelectedPlayerPowerup( rectDef_t *rect, qboolean draw2D ) { + clientInfo_t *ci; + int j; + float x, y; + + ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; + if (ci) { + x = rect->x; + y = rect->y; + + for (j = 0; j < PW_NUM_POWERUPS; j++) { + if (ci->powerups & (1 << j)) { + gitem_t *item; + item = BG_FindItemForPowerup( j ); + if (item) { + CG_DrawPic( x, y, rect->w, rect->h, trap_R_RegisterShader( item->icon ) ); + x += 3; + y += 3; + return; + } + } + } + + } +} + + +static void CG_DrawSelectedPlayerHead( rectDef_t *rect, qboolean draw2D, qboolean voice ) { + clipHandle_t cm; + clientInfo_t *ci; + float len; + vec3_t origin; + vec3_t mins, maxs, angles; + + + ci = cgs.clientinfo + ((voice) ? cgs.currentVoiceClient : sortedTeamPlayers[CG_GetSelectedPlayer()]); + + if (ci) { + if ( cg_draw3dIcons.integer ) { + cm = ci->headModel; + if ( !cm ) { + return; + } + + // offset the origin y and z to center the head + trap_R_ModelBounds( cm, mins, maxs ); + + origin[2] = -0.5 * ( mins[2] + maxs[2] ); + origin[1] = 0.5 * ( mins[1] + maxs[1] ); + + // calculate distance so the head nearly fills the box + // assume heads are taller than wide + len = 0.7 * ( maxs[2] - mins[2] ); + origin[0] = len / 0.268; // len / tan( fov/2 ) + + // allow per-model tweaking + VectorAdd( origin, ci->headOffset, origin ); + + angles[PITCH] = 0; + angles[YAW] = 180; + angles[ROLL] = 0; + + CG_Draw3DModel( rect->x, rect->y, rect->w, rect->h, ci->headModel, ci->headSkin, origin, angles ); + } else if ( cg_drawIcons.integer ) { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, ci->modelIcon ); + } + + // if they are deferred, draw a cross out + if ( ci->deferred ) { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.deferShader ); + } + } + +} + + +static void CG_DrawPlayerHealth(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + playerState_t *ps; + int value; + char num[16]; + + ps = &cg.snap->ps; + + value = ps->stats[STAT_HEALTH]; + + if (shader) { + trap_R_SetColor( color ); + CG_DrawPic(rect->x, rect->y, rect->w, rect->h, shader); + trap_R_SetColor( NULL ); + } else { + Com_sprintf (num, sizeof(num), "%i", value); + value = CG_Text_Width(num, scale, 0); + CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h, scale, color, num, 0, 0, textStyle); + } +} + + +static void CG_DrawRedScore(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + int value; + char num[16]; + if ( cgs.scores1 == SCORE_NOT_PRESENT ) { + Com_sprintf (num, sizeof(num), "-"); + } + else { + Com_sprintf (num, sizeof(num), "%i", cgs.scores1); + } + value = CG_Text_Width(num, scale, 0); + CG_Text_Paint(rect->x + rect->w - value, rect->y + rect->h, scale, color, num, 0, 0, textStyle); +} + +static void CG_DrawBlueScore(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + int value; + char num[16]; + + if ( cgs.scores2 == SCORE_NOT_PRESENT ) { + Com_sprintf (num, sizeof(num), "-"); + } + else { + Com_sprintf (num, sizeof(num), "%i", cgs.scores2); + } + value = CG_Text_Width(num, scale, 0); + CG_Text_Paint(rect->x + rect->w - value, rect->y + rect->h, scale, color, num, 0, 0, textStyle); +} + +// FIXME: team name support +static void CG_DrawRedName(rectDef_t *rect, float scale, vec4_t color, int textStyle ) { + CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, cg_redTeamName.string , 0, 0, textStyle); +} + +static void CG_DrawBlueName(rectDef_t *rect, float scale, vec4_t color, int textStyle ) { + CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, cg_blueTeamName.string, 0, 0, textStyle); +} + +static void CG_DrawBlueFlagName(rectDef_t *rect, float scale, vec4_t color, int textStyle ) { + int i; + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + if ( cgs.clientinfo[i].infoValid && cgs.clientinfo[i].team == TEAM_RED && cgs.clientinfo[i].powerups & ( 1<< PW_BLUEFLAG )) { + CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, cgs.clientinfo[i].name, 0, 0, textStyle); + return; + } + } +} + +static void CG_DrawBlueFlagStatus(rectDef_t *rect, qhandle_t shader) { + if (cgs.gametype != GT_CTF && cgs.gametype != GT_1FCTF) { + if (cgs.gametype == GT_HARVESTER) { + vec4_t color = {0, 0, 1, 1}; + trap_R_SetColor(color); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.blueCubeIcon ); + trap_R_SetColor(NULL); + } + return; + } + if (shader) { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); + } else { + gitem_t *item = BG_FindItemForPowerup( PW_BLUEFLAG ); + if (item) { + vec4_t color = {0, 0, 1, 1}; + trap_R_SetColor(color); + if( cgs.blueflag >= 0 && cgs.blueflag <= 2 ) { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.flagShaders[cgs.blueflag] ); + } else { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.flagShaders[0] ); + } + trap_R_SetColor(NULL); + } + } +} + +static void CG_DrawBlueFlagHead(rectDef_t *rect) { + int i; + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + if ( cgs.clientinfo[i].infoValid && cgs.clientinfo[i].team == TEAM_RED && cgs.clientinfo[i].powerups & ( 1<< PW_BLUEFLAG )) { + vec3_t angles; + VectorClear( angles ); + angles[YAW] = 180 + 20 * sin( cg.time / 650.0 );; + CG_DrawHead( rect->x, rect->y, rect->w, rect->h, 0,angles ); + return; + } + } +} + +static void CG_DrawRedFlagName(rectDef_t *rect, float scale, vec4_t color, int textStyle ) { + int i; + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + if ( cgs.clientinfo[i].infoValid && cgs.clientinfo[i].team == TEAM_BLUE && cgs.clientinfo[i].powerups & ( 1<< PW_REDFLAG )) { + CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, cgs.clientinfo[i].name, 0, 0, textStyle); + return; + } + } +} + +static void CG_DrawRedFlagStatus(rectDef_t *rect, qhandle_t shader) { + if (cgs.gametype != GT_CTF && cgs.gametype != GT_1FCTF) { + if (cgs.gametype == GT_HARVESTER) { + vec4_t color = {1, 0, 0, 1}; + trap_R_SetColor(color); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.redCubeIcon ); + trap_R_SetColor(NULL); + } + return; + } + if (shader) { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); + } else { + gitem_t *item = BG_FindItemForPowerup( PW_REDFLAG ); + if (item) { + vec4_t color = {1, 0, 0, 1}; + trap_R_SetColor(color); + if( cgs.redflag >= 0 && cgs.redflag <= 2) { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.flagShaders[cgs.redflag] ); + } else { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.flagShaders[0] ); + } + trap_R_SetColor(NULL); + } + } +} + +static void CG_DrawRedFlagHead(rectDef_t *rect) { + int i; + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + if ( cgs.clientinfo[i].infoValid && cgs.clientinfo[i].team == TEAM_BLUE && cgs.clientinfo[i].powerups & ( 1<< PW_REDFLAG )) { + vec3_t angles; + VectorClear( angles ); + angles[YAW] = 180 + 20 * sin( cg.time / 650.0 );; + CG_DrawHead( rect->x, rect->y, rect->w, rect->h, 0,angles ); + return; + } + } +} + +static void CG_HarvesterSkulls(rectDef_t *rect, float scale, vec4_t color, qboolean force2D, int textStyle ) { + char num[16]; + vec3_t origin, angles; + qhandle_t handle; + int value = cg.snap->ps.generic1; + + if (cgs.gametype != GT_HARVESTER) { + return; + } + + if( value > 99 ) { + value = 99; + } + + Com_sprintf (num, sizeof(num), "%i", value); + value = CG_Text_Width(num, scale, 0); + CG_Text_Paint(rect->x + (rect->w - value), rect->y + rect->h, scale, color, num, 0, 0, textStyle); + + if (cg_drawIcons.integer) { + if (!force2D && cg_draw3dIcons.integer) { + VectorClear(angles); + origin[0] = 90; + origin[1] = 0; + origin[2] = -10; + angles[YAW] = ( cg.time & 2047 ) * 360 / 2048.0; + if( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { + handle = cgs.media.redCubeModel; + } else { + handle = cgs.media.blueCubeModel; + } + CG_Draw3DModel( rect->x, rect->y, 35, 35, handle, 0, origin, angles ); + } else { + if( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { + handle = cgs.media.redCubeIcon; + } else { + handle = cgs.media.blueCubeIcon; + } + CG_DrawPic( rect->x + 3, rect->y + 16, 20, 20, handle ); + } + } +} + +static void CG_OneFlagStatus(rectDef_t *rect) { + if (cgs.gametype != GT_1FCTF) { + return; + } else { + gitem_t *item = BG_FindItemForPowerup( PW_NEUTRALFLAG ); + if (item) { + if( cgs.flagStatus >= 0 && cgs.flagStatus <= 4 ) { + vec4_t color = {1, 1, 1, 1}; + int index = 0; + if (cgs.flagStatus == FLAG_TAKEN_RED) { + color[1] = color[2] = 0; + index = 1; + } else if (cgs.flagStatus == FLAG_TAKEN_BLUE) { + color[0] = color[1] = 0; + index = 1; + } else if (cgs.flagStatus == FLAG_DROPPED) { + index = 2; + } + trap_R_SetColor(color); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.flagShaders[index] ); + } + } + } +} + + +static void CG_DrawCTFPowerUp(rectDef_t *rect) { + int value; + + if (cgs.gametype < GT_CTF) { + return; + } + value = cg.snap->ps.stats[STAT_PERSISTANT_POWERUP]; + if ( value ) { + CG_RegisterItemVisuals( value ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cg_items[ value ].icon ); + } +} + + + +static void CG_DrawTeamColor(rectDef_t *rect, vec4_t color) { + CG_DrawTeamBackground(rect->x, rect->y, rect->w, rect->h, color[3], cg.snap->ps.persistant[PERS_TEAM]); +} + +static void CG_DrawAreaPowerUp(rectDef_t *rect, int align, float special, float scale, vec4_t color) { + char num[16]; + int sorted[MAX_POWERUPS]; + int sortedTime[MAX_POWERUPS]; + int i, j, k; + int active; + playerState_t *ps; + int t; + gitem_t *item; + float f; + rectDef_t r2; + float *inc; + r2.x = rect->x; + r2.y = rect->y; + r2.w = rect->w; + r2.h = rect->h; + + inc = (align == HUD_VERTICAL) ? &r2.y : &r2.x; + + ps = &cg.snap->ps; + + if ( ps->stats[STAT_HEALTH] <= 0 ) { + return; + } + + // sort the list by time remaining + active = 0; + for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { + if ( !ps->powerups[ i ] ) { + continue; + } + t = ps->powerups[ i ] - cg.time; + // ZOID--don't draw if the power up has unlimited time (999 seconds) + // This is true of the CTF flags + if ( t <= 0 || t >= 999000) { + continue; + } + + // insert into the list + for ( j = 0 ; j < active ; j++ ) { + if ( sortedTime[j] >= t ) { + for ( k = active - 1 ; k >= j ; k-- ) { + sorted[k+1] = sorted[k]; + sortedTime[k+1] = sortedTime[k]; + } + break; + } + } + sorted[j] = i; + sortedTime[j] = t; + active++; + } + + // draw the icons and timers + for ( i = 0 ; i < active ; i++ ) { + item = BG_FindItemForPowerup( sorted[i] ); + + if (item) { + t = ps->powerups[ sorted[i] ]; + if ( t - cg.time >= POWERUP_BLINKS * POWERUP_BLINK_TIME ) { + trap_R_SetColor( NULL ); + } else { + vec4_t modulate; + + f = (float)( t - cg.time ) / POWERUP_BLINK_TIME; + f -= (int)f; + modulate[0] = modulate[1] = modulate[2] = modulate[3] = f; + trap_R_SetColor( modulate ); + } + + CG_DrawPic( r2.x, r2.y, r2.w * .75, r2.h, trap_R_RegisterShader( item->icon ) ); + + Com_sprintf (num, sizeof(num), "%i", sortedTime[i] / 1000); + CG_Text_Paint(r2.x + (r2.w * .75) + 3 , r2.y + r2.h, scale, color, num, 0, 0, 0); + *inc += r2.w + special; + } + + } + trap_R_SetColor( NULL ); + +} + +float CG_GetValue(int ownerDraw) { + centity_t *cent; + clientInfo_t *ci; + playerState_t *ps; + + cent = &cg_entities[cg.snap->ps.clientNum]; + ps = &cg.snap->ps; + + switch (ownerDraw) { + case CG_SELECTEDPLAYER_ARMOR: + ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; + return ci->armor; + break; + case CG_SELECTEDPLAYER_HEALTH: + ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; + return ci->health; + break; + case CG_PLAYER_ARMOR_VALUE: + return ps->stats[STAT_ARMOR]; + break; + case CG_PLAYER_AMMO_VALUE: + if ( cent->currentState.weapon ) { + return ps->ammo[cent->currentState.weapon]; + } + break; + case CG_PLAYER_SCORE: + return cg.snap->ps.persistant[PERS_SCORE]; + break; + case CG_PLAYER_HEALTH: + return ps->stats[STAT_HEALTH]; + break; + case CG_RED_SCORE: + return cgs.scores1; + break; + case CG_BLUE_SCORE: + return cgs.scores2; + break; + default: + break; + } + return -1; +} + +qboolean CG_OtherTeamHasFlag() { + if (cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF) { + int team = cg.snap->ps.persistant[PERS_TEAM]; + if (cgs.gametype == GT_1FCTF) { + if (team == TEAM_RED && cgs.flagStatus == FLAG_TAKEN_BLUE) { + return qtrue; + } else if (team == TEAM_BLUE && cgs.flagStatus == FLAG_TAKEN_RED) { + return qtrue; + } else { + return qfalse; + } + } else { + if (team == TEAM_RED && cgs.redflag == FLAG_TAKEN) { + return qtrue; + } else if (team == TEAM_BLUE && cgs.blueflag == FLAG_TAKEN) { + return qtrue; + } else { + return qfalse; + } + } + } + return qfalse; +} + +qboolean CG_YourTeamHasFlag() { + if (cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF) { + int team = cg.snap->ps.persistant[PERS_TEAM]; + if (cgs.gametype == GT_1FCTF) { + if (team == TEAM_RED && cgs.flagStatus == FLAG_TAKEN_RED) { + return qtrue; + } else if (team == TEAM_BLUE && cgs.flagStatus == FLAG_TAKEN_BLUE) { + return qtrue; + } else { + return qfalse; + } + } else { + if (team == TEAM_RED && cgs.blueflag == FLAG_TAKEN) { + return qtrue; + } else if (team == TEAM_BLUE && cgs.redflag == FLAG_TAKEN) { + return qtrue; + } else { + return qfalse; + } + } + } + return qfalse; +} + +// THINKABOUTME: should these be exclusive or inclusive.. +// +qboolean CG_OwnerDrawVisible(int flags) { + + if (flags & CG_SHOW_TEAMINFO) { + return (cg_currentSelectedPlayer.integer == numSortedTeamPlayers); + } + + if (flags & CG_SHOW_NOTEAMINFO) { + return !(cg_currentSelectedPlayer.integer == numSortedTeamPlayers); + } + + if (flags & CG_SHOW_OTHERTEAMHASFLAG) { + return CG_OtherTeamHasFlag(); + } + + if (flags & CG_SHOW_YOURTEAMHASENEMYFLAG) { + return CG_YourTeamHasFlag(); + } + + if (flags & (CG_SHOW_BLUE_TEAM_HAS_REDFLAG | CG_SHOW_RED_TEAM_HAS_BLUEFLAG)) { + if (flags & CG_SHOW_BLUE_TEAM_HAS_REDFLAG && (cgs.redflag == FLAG_TAKEN || cgs.flagStatus == FLAG_TAKEN_RED)) { + return qtrue; + } else if (flags & CG_SHOW_RED_TEAM_HAS_BLUEFLAG && (cgs.blueflag == FLAG_TAKEN || cgs.flagStatus == FLAG_TAKEN_BLUE)) { + return qtrue; + } + return qfalse; + } + + if (flags & CG_SHOW_ANYTEAMGAME) { + if( cgs.gametype >= GT_TEAM) { + return qtrue; + } + } + + if (flags & CG_SHOW_ANYNONTEAMGAME) { + if( cgs.gametype < GT_TEAM) { + return qtrue; + } + } + + if (flags & CG_SHOW_HARVESTER) { + if( cgs.gametype == GT_HARVESTER ) { + return qtrue; + } else { + return qfalse; + } + } + + if (flags & CG_SHOW_ONEFLAG) { + if( cgs.gametype == GT_1FCTF ) { + return qtrue; + } else { + return qfalse; + } + } + + if (flags & CG_SHOW_CTF) { + if( cgs.gametype == GT_CTF ) { + return qtrue; + } + } + + if (flags & CG_SHOW_OBELISK) { + if( cgs.gametype == GT_OBELISK ) { + return qtrue; + } else { + return qfalse; + } + } + + if (flags & CG_SHOW_HEALTHCRITICAL) { + if (cg.snap->ps.stats[STAT_HEALTH] < 25) { + return qtrue; + } + } + + if (flags & CG_SHOW_HEALTHOK) { + if (cg.snap->ps.stats[STAT_HEALTH] >= 25) { + return qtrue; + } + } + + if (flags & CG_SHOW_SINGLEPLAYER) { + if( cgs.gametype == GT_SINGLE_PLAYER ) { + return qtrue; + } + } + + if (flags & CG_SHOW_TOURNAMENT) { + if( cgs.gametype == GT_TOURNAMENT ) { + return qtrue; + } + } + + if (flags & CG_SHOW_DURINGINCOMINGVOICE) { + } + + if (flags & CG_SHOW_IF_PLAYER_HAS_FLAG) { + if (cg.snap->ps.powerups[PW_REDFLAG] || cg.snap->ps.powerups[PW_BLUEFLAG] || cg.snap->ps.powerups[PW_NEUTRALFLAG]) { + return qtrue; + } + } + return qfalse; +} + + + +static void CG_DrawPlayerHasFlag(rectDef_t *rect, qboolean force2D) { + int adj = (force2D) ? 0 : 2; + if( cg.predictedPlayerState.powerups[PW_REDFLAG] ) { + CG_DrawFlagModel( rect->x + adj, rect->y + adj, rect->w - adj, rect->h - adj, TEAM_RED, force2D); + } else if( cg.predictedPlayerState.powerups[PW_BLUEFLAG] ) { + CG_DrawFlagModel( rect->x + adj, rect->y + adj, rect->w - adj, rect->h - adj, TEAM_BLUE, force2D); + } else if( cg.predictedPlayerState.powerups[PW_NEUTRALFLAG] ) { + CG_DrawFlagModel( rect->x + adj, rect->y + adj, rect->w - adj, rect->h - adj, TEAM_FREE, force2D); + } +} + +static void CG_DrawAreaSystemChat(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader) { + CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, systemChat, 0, 0, 0); +} + +static void CG_DrawAreaTeamChat(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader) { + CG_Text_Paint(rect->x, rect->y + rect->h, scale, color,teamChat1, 0, 0, 0); +} + +static void CG_DrawAreaChat(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader) { + CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, teamChat2, 0, 0, 0); +} + +const char *CG_GetKillerText() { + const char *s = ""; + if ( cg.killerName[0] ) { + s = va("Fragged by %s", cg.killerName ); + } + return s; +} + + +static void CG_DrawKiller(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + // fragged by ... line + if ( cg.killerName[0] ) { + int x = rect->x + rect->w / 2; + CG_Text_Paint(x - CG_Text_Width(CG_GetKillerText(), scale, 0) / 2, rect->y + rect->h, scale, color, CG_GetKillerText(), 0, 0, textStyle); + } + +} + + +static void CG_DrawCapFragLimit(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle) { + int limit = (cgs.gametype >= GT_CTF) ? cgs.capturelimit : cgs.fraglimit; + CG_Text_Paint(rect->x, rect->y, scale, color, va("%2i", limit),0, 0, textStyle); +} + +static void CG_Draw1stPlace(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle) { + if (cgs.scores1 != SCORE_NOT_PRESENT) { + CG_Text_Paint(rect->x, rect->y, scale, color, va("%2i", cgs.scores1),0, 0, textStyle); + } +} + +static void CG_Draw2ndPlace(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle) { + if (cgs.scores2 != SCORE_NOT_PRESENT) { + CG_Text_Paint(rect->x, rect->y, scale, color, va("%2i", cgs.scores2),0, 0, textStyle); + } +} + +const char *CG_GetGameStatusText() { + const char *s = ""; + if ( cgs.gametype < GT_TEAM) { + if (cg.snap->ps.persistant[PERS_TEAM] != TEAM_SPECTATOR ) { + s = va("%s place with %i",CG_PlaceString( cg.snap->ps.persistant[PERS_RANK] + 1 ),cg.snap->ps.persistant[PERS_SCORE] ); + } + } else { + if ( cg.teamScores[0] == cg.teamScores[1] ) { + s = va("Teams are tied at %i", cg.teamScores[0] ); + } else if ( cg.teamScores[0] >= cg.teamScores[1] ) { + s = va("Red leads Blue, %i to %i", cg.teamScores[0], cg.teamScores[1] ); + } else { + s = va("Blue leads Red, %i to %i", cg.teamScores[1], cg.teamScores[0] ); + } + } + return s; +} + +static void CG_DrawGameStatus(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, CG_GetGameStatusText(), 0, 0, textStyle); +} + +const char *CG_GameTypeString() { + if ( cgs.gametype == GT_FFA ) { + return "Free For All"; + } else if ( cgs.gametype == GT_TEAM ) { + return "Team Deathmatch"; + } else if ( cgs.gametype == GT_CTF ) { + return "Capture the Flag"; + } else if ( cgs.gametype == GT_1FCTF ) { + return "One Flag CTF"; + } else if ( cgs.gametype == GT_OBELISK ) { + return "Overload"; + } else if ( cgs.gametype == GT_HARVESTER ) { + return "Harvester"; + } + return ""; +} +static void CG_DrawGameType(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, CG_GameTypeString(), 0, 0, textStyle); +} + +static void CG_Text_Paint_Limit(float *maxX, float x, float y, float scale, vec4_t color, const char* text, float adjust, int limit) { + int len, count; + vec4_t newColor; + glyphInfo_t *glyph; + if (text) { +// TTimo: FIXME +// const unsigned char *s = text; // bk001206 - unsigned + const char *s = text; + float max = *maxX; + float useScale; + fontInfo_t *font = &cgDC.Assets.textFont; + if (scale <= cg_smallFont.value) { + font = &cgDC.Assets.smallFont; + } else if (scale > cg_bigFont.value) { + font = &cgDC.Assets.bigFont; + } + useScale = scale * font->glyphScale; + trap_R_SetColor( color ); + len = strlen(text); + if (limit > 0 && len > limit) { + len = limit; + } + count = 0; + while (s && *s && count < len) { + glyph = &font->glyphs[(int)*s]; // TTimo: FIXME: getting nasty warnings without the cast, hopefully this doesn't break the VM build + if ( Q_IsColorString( s ) ) { + memcpy( newColor, g_color_table[ColorIndex(*(s+1))], sizeof( newColor ) ); + newColor[3] = color[3]; + trap_R_SetColor( newColor ); + s += 2; + continue; + } else { + float yadj = useScale * glyph->top; + if (CG_Text_Width(s, useScale, 1) + x > max) { + *maxX = 0; + break; + } + CG_Text_PaintChar(x, y - yadj, + glyph->imageWidth, + glyph->imageHeight, + useScale, + glyph->s, + glyph->t, + glyph->s2, + glyph->t2, + glyph->glyph); + x += (glyph->xSkip * useScale) + adjust; + *maxX = x; + count++; + s++; + } + } + trap_R_SetColor( NULL ); + } + +} + + + +#define PIC_WIDTH 12 + +void CG_DrawNewTeamInfo(rectDef_t *rect, float text_x, float text_y, float scale, vec4_t color, qhandle_t shader) { + int xx; + float y; + int i, j, len, count; + const char *p; + vec4_t hcolor; + float pwidth, lwidth, maxx, leftOver; + clientInfo_t *ci; + gitem_t *item; + qhandle_t h; + + // max player name width + pwidth = 0; + count = (numSortedTeamPlayers > 8) ? 8 : numSortedTeamPlayers; + for (i = 0; i < count; i++) { + ci = cgs.clientinfo + sortedTeamPlayers[i]; + if ( ci->infoValid && ci->team == cg.snap->ps.persistant[PERS_TEAM]) { + len = CG_Text_Width( ci->name, scale, 0); + if (len > pwidth) + pwidth = len; + } + } + + // max location name width + lwidth = 0; + for (i = 1; i < MAX_LOCATIONS; i++) { + p = CG_ConfigString(CS_LOCATIONS + i); + if (p && *p) { + len = CG_Text_Width(p, scale, 0); + if (len > lwidth) + lwidth = len; + } + } + + y = rect->y; + + for (i = 0; i < count; i++) { + ci = cgs.clientinfo + sortedTeamPlayers[i]; + if ( ci->infoValid && ci->team == cg.snap->ps.persistant[PERS_TEAM]) { + + xx = rect->x + 1; + for (j = 0; j <= PW_NUM_POWERUPS; j++) { + if (ci->powerups & (1 << j)) { + + item = BG_FindItemForPowerup( j ); + + if (item) { + CG_DrawPic( xx, y, PIC_WIDTH, PIC_WIDTH, trap_R_RegisterShader( item->icon ) ); + xx += PIC_WIDTH; + } + } + } + + // FIXME: max of 3 powerups shown properly + xx = rect->x + (PIC_WIDTH * 3) + 2; + + CG_GetColorForHealth( ci->health, ci->armor, hcolor ); + trap_R_SetColor(hcolor); + CG_DrawPic( xx, y + 1, PIC_WIDTH - 2, PIC_WIDTH - 2, cgs.media.heartShader ); + + //Com_sprintf (st, sizeof(st), "%3i %3i", ci->health, ci->armor); + //CG_Text_Paint(xx, y + text_y, scale, hcolor, st, 0, 0); + + // draw weapon icon + xx += PIC_WIDTH + 1; + +// weapon used is not that useful, use the space for task +#if 0 + if ( cg_weapons[ci->curWeapon].weaponIcon ) { + CG_DrawPic( xx, y, PIC_WIDTH, PIC_WIDTH, cg_weapons[ci->curWeapon].weaponIcon ); + } else { + CG_DrawPic( xx, y, PIC_WIDTH, PIC_WIDTH, cgs.media.deferShader ); + } +#endif + + trap_R_SetColor(NULL); + if (cgs.orderPending) { + // blink the icon + if ( cg.time > cgs.orderTime - 2500 && (cg.time >> 9 ) & 1 ) { + h = 0; + } else { + h = CG_StatusHandle(cgs.currentOrder); + } + } else { + h = CG_StatusHandle(ci->teamTask); + } + + if (h) { + CG_DrawPic( xx, y, PIC_WIDTH, PIC_WIDTH, h); + } + + xx += PIC_WIDTH + 1; + + leftOver = rect->w - xx; + maxx = xx + leftOver / 3; + + + + CG_Text_Paint_Limit(&maxx, xx, y + text_y, scale, color, ci->name, 0, 0); + + p = CG_ConfigString(CS_LOCATIONS + ci->location); + if (!p || !*p) { + p = "unknown"; + } + + xx += leftOver / 3 + 2; + maxx = rect->w - 4; + + CG_Text_Paint_Limit(&maxx, xx, y + text_y, scale, color, p, 0, 0); + y += text_y + 2; + if ( y + text_y + 2 > rect->y + rect->h ) { + break; + } + + } + } +} + + +void CG_DrawTeamSpectators(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader) { + if (cg.spectatorLen) { + float maxX; + + if (cg.spectatorWidth == -1) { + cg.spectatorWidth = 0; + cg.spectatorPaintX = rect->x + 1; + cg.spectatorPaintX2 = -1; + } + + if (cg.spectatorOffset > cg.spectatorLen) { + cg.spectatorOffset = 0; + cg.spectatorPaintX = rect->x + 1; + cg.spectatorPaintX2 = -1; + } + + if (cg.time > cg.spectatorTime) { + cg.spectatorTime = cg.time + 10; + if (cg.spectatorPaintX <= rect->x + 2) { + if (cg.spectatorOffset < cg.spectatorLen) { + cg.spectatorPaintX += CG_Text_Width(&cg.spectatorList[cg.spectatorOffset], scale, 1) - 1; + cg.spectatorOffset++; + } else { + cg.spectatorOffset = 0; + if (cg.spectatorPaintX2 >= 0) { + cg.spectatorPaintX = cg.spectatorPaintX2; + } else { + cg.spectatorPaintX = rect->x + rect->w - 2; + } + cg.spectatorPaintX2 = -1; + } + } else { + cg.spectatorPaintX--; + if (cg.spectatorPaintX2 >= 0) { + cg.spectatorPaintX2--; + } + } + } + + maxX = rect->x + rect->w - 2; + CG_Text_Paint_Limit(&maxX, cg.spectatorPaintX, rect->y + rect->h - 3, scale, color, &cg.spectatorList[cg.spectatorOffset], 0, 0); + if (cg.spectatorPaintX2 >= 0) { + float maxX2 = rect->x + rect->w - 2; + CG_Text_Paint_Limit(&maxX2, cg.spectatorPaintX2, rect->y + rect->h - 3, scale, color, cg.spectatorList, 0, cg.spectatorOffset); + } + if (cg.spectatorOffset && maxX > 0) { + // if we have an offset ( we are skipping the first part of the string ) and we fit the string + if (cg.spectatorPaintX2 == -1) { + cg.spectatorPaintX2 = rect->x + rect->w - 2; + } + } else { + cg.spectatorPaintX2 = -1; + } + + } +} + + + +void CG_DrawMedal(int ownerDraw, rectDef_t *rect, float scale, vec4_t color, qhandle_t shader) { + score_t *score = &cg.scores[cg.selectedScore]; + float value = 0; + char *text = NULL; + color[3] = 0.25; + + switch (ownerDraw) { + case CG_ACCURACY: + value = score->accuracy; + break; + case CG_ASSISTS: + value = score->assistCount; + break; + case CG_DEFEND: + value = score->defendCount; + break; + case CG_EXCELLENT: + value = score->excellentCount; + break; + case CG_IMPRESSIVE: + value = score->impressiveCount; + break; + case CG_PERFECT: + value = score->perfect; + break; + case CG_GAUNTLET: + value = score->guantletCount; + break; + case CG_CAPTURES: + value = score->captures; + break; + } + + if (value > 0) { + if (ownerDraw != CG_PERFECT) { + if (ownerDraw == CG_ACCURACY) { + text = va("%i%%", (int)value); + if (value > 50) { + color[3] = 1.0; + } + } else { + text = va("%i", (int)value); + color[3] = 1.0; + } + } else { + if (value) { + color[3] = 1.0; + } + text = "Wow"; + } + } + + trap_R_SetColor(color); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); + + if (text) { + color[3] = 1.0; + value = CG_Text_Width(text, scale, 0); + CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h + 10 , scale, color, text, 0, 0, 0); + } + trap_R_SetColor(NULL); + +} + + +// +void CG_OwnerDraw(float x, float y, float w, float h, float text_x, float text_y, int ownerDraw, int ownerDrawFlags, int align, float special, float scale, vec4_t color, qhandle_t shader, int textStyle) { + rectDef_t rect; + + if ( cg_drawStatus.integer == 0 ) { + return; + } + + //if (ownerDrawFlags != 0 && !CG_OwnerDrawVisible(ownerDrawFlags)) { + // return; + //} + + rect.x = x; + rect.y = y; + rect.w = w; + rect.h = h; + + switch (ownerDraw) { + case CG_PLAYER_ARMOR_ICON: + CG_DrawPlayerArmorIcon(&rect, ownerDrawFlags & CG_SHOW_2DONLY); + break; + case CG_PLAYER_ARMOR_ICON2D: + CG_DrawPlayerArmorIcon(&rect, qtrue); + break; + case CG_PLAYER_ARMOR_VALUE: + CG_DrawPlayerArmorValue(&rect, scale, color, shader, textStyle); + break; + case CG_PLAYER_AMMO_ICON: + CG_DrawPlayerAmmoIcon(&rect, ownerDrawFlags & CG_SHOW_2DONLY); + break; + case CG_PLAYER_AMMO_ICON2D: + CG_DrawPlayerAmmoIcon(&rect, qtrue); + break; + case CG_PLAYER_AMMO_VALUE: + CG_DrawPlayerAmmoValue(&rect, scale, color, shader, textStyle); + break; + case CG_SELECTEDPLAYER_HEAD: + CG_DrawSelectedPlayerHead(&rect, ownerDrawFlags & CG_SHOW_2DONLY, qfalse); + break; + case CG_VOICE_HEAD: + CG_DrawSelectedPlayerHead(&rect, ownerDrawFlags & CG_SHOW_2DONLY, qtrue); + break; + case CG_VOICE_NAME: + CG_DrawSelectedPlayerName(&rect, scale, color, qtrue, textStyle); + break; + case CG_SELECTEDPLAYER_STATUS: + CG_DrawSelectedPlayerStatus(&rect); + break; + case CG_SELECTEDPLAYER_ARMOR: + CG_DrawSelectedPlayerArmor(&rect, scale, color, shader, textStyle); + break; + case CG_SELECTEDPLAYER_HEALTH: + CG_DrawSelectedPlayerHealth(&rect, scale, color, shader, textStyle); + break; + case CG_SELECTEDPLAYER_NAME: + CG_DrawSelectedPlayerName(&rect, scale, color, qfalse, textStyle); + break; + case CG_SELECTEDPLAYER_LOCATION: + CG_DrawSelectedPlayerLocation(&rect, scale, color, textStyle); + break; + case CG_SELECTEDPLAYER_WEAPON: + CG_DrawSelectedPlayerWeapon(&rect); + break; + case CG_SELECTEDPLAYER_POWERUP: + CG_DrawSelectedPlayerPowerup(&rect, ownerDrawFlags & CG_SHOW_2DONLY); + break; + case CG_PLAYER_HEAD: + CG_DrawPlayerHead(&rect, ownerDrawFlags & CG_SHOW_2DONLY); + break; + case CG_PLAYER_ITEM: + CG_DrawPlayerItem(&rect, scale, ownerDrawFlags & CG_SHOW_2DONLY); + break; + case CG_PLAYER_SCORE: + CG_DrawPlayerScore(&rect, scale, color, shader, textStyle); + break; + case CG_PLAYER_HEALTH: + CG_DrawPlayerHealth(&rect, scale, color, shader, textStyle); + break; + case CG_RED_SCORE: + CG_DrawRedScore(&rect, scale, color, shader, textStyle); + break; + case CG_BLUE_SCORE: + CG_DrawBlueScore(&rect, scale, color, shader, textStyle); + break; + case CG_RED_NAME: + CG_DrawRedName(&rect, scale, color, textStyle); + break; + case CG_BLUE_NAME: + CG_DrawBlueName(&rect, scale, color, textStyle); + break; + case CG_BLUE_FLAGHEAD: + CG_DrawBlueFlagHead(&rect); + break; + case CG_BLUE_FLAGSTATUS: + CG_DrawBlueFlagStatus(&rect, shader); + break; + case CG_BLUE_FLAGNAME: + CG_DrawBlueFlagName(&rect, scale, color, textStyle); + break; + case CG_RED_FLAGHEAD: + CG_DrawRedFlagHead(&rect); + break; + case CG_RED_FLAGSTATUS: + CG_DrawRedFlagStatus(&rect, shader); + break; + case CG_RED_FLAGNAME: + CG_DrawRedFlagName(&rect, scale, color, textStyle); + break; + case CG_HARVESTER_SKULLS: + CG_HarvesterSkulls(&rect, scale, color, qfalse, textStyle); + break; + case CG_HARVESTER_SKULLS2D: + CG_HarvesterSkulls(&rect, scale, color, qtrue, textStyle); + break; + case CG_ONEFLAG_STATUS: + CG_OneFlagStatus(&rect); + break; + case CG_PLAYER_LOCATION: + CG_DrawPlayerLocation(&rect, scale, color, textStyle); + break; + case CG_TEAM_COLOR: + CG_DrawTeamColor(&rect, color); + break; + case CG_CTF_POWERUP: + CG_DrawCTFPowerUp(&rect); + break; + case CG_AREA_POWERUP: + CG_DrawAreaPowerUp(&rect, align, special, scale, color); + break; + case CG_PLAYER_STATUS: + CG_DrawPlayerStatus(&rect); + break; + case CG_PLAYER_HASFLAG: + CG_DrawPlayerHasFlag(&rect, qfalse); + break; + case CG_PLAYER_HASFLAG2D: + CG_DrawPlayerHasFlag(&rect, qtrue); + break; + case CG_AREA_SYSTEMCHAT: + CG_DrawAreaSystemChat(&rect, scale, color, shader); + break; + case CG_AREA_TEAMCHAT: + CG_DrawAreaTeamChat(&rect, scale, color, shader); + break; + case CG_AREA_CHAT: + CG_DrawAreaChat(&rect, scale, color, shader); + break; + case CG_GAME_TYPE: + CG_DrawGameType(&rect, scale, color, shader, textStyle); + break; + case CG_GAME_STATUS: + CG_DrawGameStatus(&rect, scale, color, shader, textStyle); + break; + case CG_KILLER: + CG_DrawKiller(&rect, scale, color, shader, textStyle); + break; + case CG_ACCURACY: + case CG_ASSISTS: + case CG_DEFEND: + case CG_EXCELLENT: + case CG_IMPRESSIVE: + case CG_PERFECT: + case CG_GAUNTLET: + case CG_CAPTURES: + CG_DrawMedal(ownerDraw, &rect, scale, color, shader); + break; + case CG_SPECTATORS: + CG_DrawTeamSpectators(&rect, scale, color, shader); + break; + case CG_TEAMINFO: + if (cg_currentSelectedPlayer.integer == numSortedTeamPlayers) { + CG_DrawNewTeamInfo(&rect, text_x, text_y, scale, color, shader); + } + break; + case CG_CAPFRAGLIMIT: + CG_DrawCapFragLimit(&rect, scale, color, shader, textStyle); + break; + case CG_1STPLACE: + CG_Draw1stPlace(&rect, scale, color, shader, textStyle); + break; + case CG_2NDPLACE: + CG_Draw2ndPlace(&rect, scale, color, shader, textStyle); + break; + default: + break; + } +} + +void CG_MouseEvent(int x, int y) { + int n; + + if ( (cg.predictedPlayerState.pm_type == PM_NORMAL || cg.predictedPlayerState.pm_type == PM_SPECTATOR) && cg.showScores == qfalse) { + trap_Key_SetCatcher(0); + return; + } + + cgs.cursorX+= x; + if (cgs.cursorX < 0) + cgs.cursorX = 0; + else if (cgs.cursorX > 640) + cgs.cursorX = 640; + + cgs.cursorY += y; + if (cgs.cursorY < 0) + cgs.cursorY = 0; + else if (cgs.cursorY > 480) + cgs.cursorY = 480; + + n = Display_CursorType(cgs.cursorX, cgs.cursorY); + cgs.activeCursor = 0; + if (n == CURSOR_ARROW) { + cgs.activeCursor = cgs.media.selectCursor; + } else if (n == CURSOR_SIZER) { + cgs.activeCursor = cgs.media.sizeCursor; + } + + if (cgs.capturedItem) { + Display_MouseMove(cgs.capturedItem, x, y); + } else { + Display_MouseMove(NULL, cgs.cursorX, cgs.cursorY); + } + +} + +/* +================== +CG_HideTeamMenus +================== + +*/ +void CG_HideTeamMenu() { + Menus_CloseByName("teamMenu"); + Menus_CloseByName("getMenu"); +} + +/* +================== +CG_ShowTeamMenus +================== + +*/ +void CG_ShowTeamMenu() { + Menus_OpenByName("teamMenu"); +} + + + + +/* +================== +CG_EventHandling +================== + type 0 - no event handling + 1 - team menu + 2 - hud editor + +*/ +void CG_EventHandling(int type) { + cgs.eventHandling = type; + if (type == CGAME_EVENT_NONE) { + CG_HideTeamMenu(); + } else if (type == CGAME_EVENT_TEAMMENU) { + //CG_ShowTeamMenu(); + } else if (type == CGAME_EVENT_SCOREBOARD) { + } + +} + + + +void CG_KeyEvent(int key, qboolean down) { + + if (!down) { + return; + } + + if ( cg.predictedPlayerState.pm_type == PM_NORMAL || (cg.predictedPlayerState.pm_type == PM_SPECTATOR && cg.showScores == qfalse)) { + CG_EventHandling(CGAME_EVENT_NONE); + trap_Key_SetCatcher(0); + return; + } + + //if (key == trap_Key_GetKey("teamMenu") || !Display_CaptureItem(cgs.cursorX, cgs.cursorY)) { + // if we see this then we should always be visible + // CG_EventHandling(CGAME_EVENT_NONE); + // trap_Key_SetCatcher(0); + //} + + + + Display_HandleKey(key, down, cgs.cursorX, cgs.cursorY); + + if (cgs.capturedItem) { + cgs.capturedItem = NULL; + } else { + if (key == K_MOUSE2 && down) { + cgs.capturedItem = Display_CaptureItem(cgs.cursorX, cgs.cursorY); + } + } +} + +int CG_ClientNumFromName(const char *p) { + int i; + for (i = 0; i < cgs.maxclients; i++) { + if (cgs.clientinfo[i].infoValid && Q_stricmp(cgs.clientinfo[i].name, p) == 0) { + return i; + } + } + return -1; +} + +void CG_ShowResponseHead() { + Menus_OpenByName("voiceMenu"); + trap_Cvar_Set("cl_conXOffset", "72"); + cg.voiceTime = cg.time; +} + +void CG_RunMenuScript(char **args) { +} + + +void CG_GetTeamColor(vec4_t *color) { + if (cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED) { + (*color)[0] = 1.0f; + (*color)[3] = 0.25f; + (*color)[1] = (*color)[2] = 0.0f; + } else if (cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE) { + (*color)[0] = (*color)[1] = 0.0f; + (*color)[2] = 1.0f; + (*color)[3] = 0.25f; + } else { + (*color)[0] = (*color)[2] = 0.0f; + (*color)[1] = 0.17f; + (*color)[3] = 0.25f; + } +} + diff --git a/code/cgame/cg_particles.c b/code/cgame/cg_particles.c index b3ce322..d9c3e7e 100755 --- a/code/cgame/cg_particles.c +++ b/code/cgame/cg_particles.c @@ -1,2018 +1,2018 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// Rafael particles -// cg_particles.c - -#include "cg_local.h" - -#define BLOODRED 2 -#define EMISIVEFADE 3 -#define GREY75 4 - -typedef struct particle_s -{ - struct particle_s *next; - - float time; - float endtime; - - vec3_t org; - vec3_t vel; - vec3_t accel; - int color; - float colorvel; - float alpha; - float alphavel; - int type; - qhandle_t pshader; - - float height; - float width; - - float endheight; - float endwidth; - - float start; - float end; - - float startfade; - qboolean rotate; - int snum; - - qboolean link; - - // Ridah - int shaderAnim; - int roll; - - int accumroll; - -} cparticle_t; - -typedef enum -{ - P_NONE, - P_WEATHER, - P_FLAT, - P_SMOKE, - P_ROTATE, - P_WEATHER_TURBULENT, - P_ANIM, // Ridah - P_BAT, - P_BLEED, - P_FLAT_SCALEUP, - P_FLAT_SCALEUP_FADE, - P_WEATHER_FLURRY, - P_SMOKE_IMPACT, - P_BUBBLE, - P_BUBBLE_TURBULENT, - P_SPRITE -} particle_type_t; - -#define MAX_SHADER_ANIMS 32 -#define MAX_SHADER_ANIM_FRAMES 64 - -static char *shaderAnimNames[MAX_SHADER_ANIMS] = { - "explode1", - "blacksmokeanim", - "twiltb2", - "expblue", - "blacksmokeanimb", // uses 'explode1' sequence - "blood", - NULL -}; -static qhandle_t shaderAnims[MAX_SHADER_ANIMS][MAX_SHADER_ANIM_FRAMES]; -static int shaderAnimCounts[MAX_SHADER_ANIMS] = { - 23, - 25, - 45, - 25, - 23, - 5, -}; -static float shaderAnimSTRatio[MAX_SHADER_ANIMS] = { - 1.405f, - 1.0f, - 1.0f, - 1.0f, - 1.0f, - 1.0f, -}; -static int numShaderAnims; -// done. - -#define PARTICLE_GRAVITY 40 -#define MAX_PARTICLES 1024 * 8 - -cparticle_t *active_particles, *free_particles; -cparticle_t particles[MAX_PARTICLES]; -int cl_numparticles = MAX_PARTICLES; - -qboolean initparticles = qfalse; -vec3_t vforward, vright, vup; -vec3_t rforward, rright, rup; - -float oldtime; - -/* -=============== -CL_ClearParticles -=============== -*/ -void CG_ClearParticles (void) -{ - int i; - - memset( particles, 0, sizeof(particles) ); - - free_particles = &particles[0]; - active_particles = NULL; - - for (i=0 ;itype == P_WEATHER || p->type == P_WEATHER_TURBULENT || p->type == P_WEATHER_FLURRY - || p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) - {// create a front facing polygon - - if (p->type != P_WEATHER_FLURRY) - { - if (p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) - { - if (org[2] > p->end) - { - p->time = cg.time; - VectorCopy (org, p->org); // Ridah, fixes rare snow flakes that flicker on the ground - - p->org[2] = ( p->start + crandom () * 4 ); - - - if (p->type == P_BUBBLE_TURBULENT) - { - p->vel[0] = crandom() * 4; - p->vel[1] = crandom() * 4; - } - - } - } - else - { - if (org[2] < p->end) - { - p->time = cg.time; - VectorCopy (org, p->org); // Ridah, fixes rare snow flakes that flicker on the ground - - while (p->org[2] < p->end) - { - p->org[2] += (p->start - p->end); - } - - - if (p->type == P_WEATHER_TURBULENT) - { - p->vel[0] = crandom() * 16; - p->vel[1] = crandom() * 16; - } - - } - } - - - // Rafael snow pvs check - if (!p->link) - return; - - p->alpha = 1; - } - - // Ridah, had to do this or MAX_POLYS is being exceeded in village1.bsp - if (Distance( cg.snap->ps.origin, org ) > 1024) { - return; - } - // done. - - if (p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) - { - VectorMA (org, -p->height, vup, point); - VectorMA (point, -p->width, vright, point); - VectorCopy (point, verts[0].xyz); - verts[0].st[0] = 0; - verts[0].st[1] = 0; - verts[0].modulate[0] = 255; - verts[0].modulate[1] = 255; - verts[0].modulate[2] = 255; - verts[0].modulate[3] = 255 * p->alpha; - - VectorMA (org, -p->height, vup, point); - VectorMA (point, p->width, vright, point); - VectorCopy (point, verts[1].xyz); - verts[1].st[0] = 0; - verts[1].st[1] = 1; - verts[1].modulate[0] = 255; - verts[1].modulate[1] = 255; - verts[1].modulate[2] = 255; - verts[1].modulate[3] = 255 * p->alpha; - - VectorMA (org, p->height, vup, point); - VectorMA (point, p->width, vright, point); - VectorCopy (point, verts[2].xyz); - verts[2].st[0] = 1; - verts[2].st[1] = 1; - verts[2].modulate[0] = 255; - verts[2].modulate[1] = 255; - verts[2].modulate[2] = 255; - verts[2].modulate[3] = 255 * p->alpha; - - VectorMA (org, p->height, vup, point); - VectorMA (point, -p->width, vright, point); - VectorCopy (point, verts[3].xyz); - verts[3].st[0] = 1; - verts[3].st[1] = 0; - verts[3].modulate[0] = 255; - verts[3].modulate[1] = 255; - verts[3].modulate[2] = 255; - verts[3].modulate[3] = 255 * p->alpha; - } - else - { - VectorMA (org, -p->height, vup, point); - VectorMA (point, -p->width, vright, point); - VectorCopy( point, TRIverts[0].xyz ); - TRIverts[0].st[0] = 1; - TRIverts[0].st[1] = 0; - TRIverts[0].modulate[0] = 255; - TRIverts[0].modulate[1] = 255; - TRIverts[0].modulate[2] = 255; - TRIverts[0].modulate[3] = 255 * p->alpha; - - VectorMA (org, p->height, vup, point); - VectorMA (point, -p->width, vright, point); - VectorCopy (point, TRIverts[1].xyz); - TRIverts[1].st[0] = 0; - TRIverts[1].st[1] = 0; - TRIverts[1].modulate[0] = 255; - TRIverts[1].modulate[1] = 255; - TRIverts[1].modulate[2] = 255; - TRIverts[1].modulate[3] = 255 * p->alpha; - - VectorMA (org, p->height, vup, point); - VectorMA (point, p->width, vright, point); - VectorCopy (point, TRIverts[2].xyz); - TRIverts[2].st[0] = 0; - TRIverts[2].st[1] = 1; - TRIverts[2].modulate[0] = 255; - TRIverts[2].modulate[1] = 255; - TRIverts[2].modulate[2] = 255; - TRIverts[2].modulate[3] = 255 * p->alpha; - } - - } - else if (p->type == P_SPRITE) - { - vec3_t rr, ru; - vec3_t rotate_ang; - - VectorSet (color, 1.0, 1.0, 1.0); - time = cg.time - p->time; - time2 = p->endtime - p->time; - ratio = time / time2; - - width = p->width + ( ratio * ( p->endwidth - p->width) ); - height = p->height + ( ratio * ( p->endheight - p->height) ); - - if (p->roll) { - vectoangles( cg.refdef.viewaxis[0], rotate_ang ); - rotate_ang[ROLL] += p->roll; - AngleVectors ( rotate_ang, NULL, rr, ru); - } - - if (p->roll) { - VectorMA (org, -height, ru, point); - VectorMA (point, -width, rr, point); - } else { - VectorMA (org, -height, vup, point); - VectorMA (point, -width, vright, point); - } - VectorCopy (point, verts[0].xyz); - verts[0].st[0] = 0; - verts[0].st[1] = 0; - verts[0].modulate[0] = 255; - verts[0].modulate[1] = 255; - verts[0].modulate[2] = 255; - verts[0].modulate[3] = 255; - - if (p->roll) { - VectorMA (point, 2*height, ru, point); - } else { - VectorMA (point, 2*height, vup, point); - } - VectorCopy (point, verts[1].xyz); - verts[1].st[0] = 0; - verts[1].st[1] = 1; - verts[1].modulate[0] = 255; - verts[1].modulate[1] = 255; - verts[1].modulate[2] = 255; - verts[1].modulate[3] = 255; - - if (p->roll) { - VectorMA (point, 2*width, rr, point); - } else { - VectorMA (point, 2*width, vright, point); - } - VectorCopy (point, verts[2].xyz); - verts[2].st[0] = 1; - verts[2].st[1] = 1; - verts[2].modulate[0] = 255; - verts[2].modulate[1] = 255; - verts[2].modulate[2] = 255; - verts[2].modulate[3] = 255; - - if (p->roll) { - VectorMA (point, -2*height, ru, point); - } else { - VectorMA (point, -2*height, vup, point); - } - VectorCopy (point, verts[3].xyz); - verts[3].st[0] = 1; - verts[3].st[1] = 0; - verts[3].modulate[0] = 255; - verts[3].modulate[1] = 255; - verts[3].modulate[2] = 255; - verts[3].modulate[3] = 255; - } - else if (p->type == P_SMOKE || p->type == P_SMOKE_IMPACT) - {// create a front rotating facing polygon - - if ( p->type == P_SMOKE_IMPACT && Distance( cg.snap->ps.origin, org ) > 1024) { - return; - } - - if (p->color == BLOODRED) - VectorSet (color, 0.22f, 0.0f, 0.0f); - else if (p->color == GREY75) - { - float len; - float greyit; - float val; - len = Distance (cg.snap->ps.origin, org); - if (!len) - len = 1; - - val = 4096/len; - greyit = 0.25 * val; - if (greyit > 0.5) - greyit = 0.5; - - VectorSet (color, greyit, greyit, greyit); - } - else - VectorSet (color, 1.0, 1.0, 1.0); - - time = cg.time - p->time; - time2 = p->endtime - p->time; - ratio = time / time2; - - if (cg.time > p->startfade) - { - invratio = 1 - ( (cg.time - p->startfade) / (p->endtime - p->startfade) ); - - if (p->color == EMISIVEFADE) - { - float fval; - fval = (invratio * invratio); - if (fval < 0) - fval = 0; - VectorSet (color, fval , fval , fval ); - } - invratio *= p->alpha; - } - else - invratio = 1 * p->alpha; - - if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) - invratio = 1; - - if (invratio > 1) - invratio = 1; - - width = p->width + ( ratio * ( p->endwidth - p->width) ); - height = p->height + ( ratio * ( p->endheight - p->height) ); - - if (p->type != P_SMOKE_IMPACT) - { - vec3_t temp; - - vectoangles (rforward, temp); - p->accumroll += p->roll; - temp[ROLL] += p->accumroll * 0.1; - AngleVectors ( temp, NULL, rright2, rup2); - } - else - { - VectorCopy (rright, rright2); - VectorCopy (rup, rup2); - } - - if (p->rotate) - { - VectorMA (org, -height, rup2, point); - VectorMA (point, -width, rright2, point); - } - else - { - VectorMA (org, -p->height, vup, point); - VectorMA (point, -p->width, vright, point); - } - VectorCopy (point, verts[0].xyz); - verts[0].st[0] = 0; - verts[0].st[1] = 0; - verts[0].modulate[0] = 255 * color[0]; - verts[0].modulate[1] = 255 * color[1]; - verts[0].modulate[2] = 255 * color[2]; - verts[0].modulate[3] = 255 * invratio; - - if (p->rotate) - { - VectorMA (org, -height, rup2, point); - VectorMA (point, width, rright2, point); - } - else - { - VectorMA (org, -p->height, vup, point); - VectorMA (point, p->width, vright, point); - } - VectorCopy (point, verts[1].xyz); - verts[1].st[0] = 0; - verts[1].st[1] = 1; - verts[1].modulate[0] = 255 * color[0]; - verts[1].modulate[1] = 255 * color[1]; - verts[1].modulate[2] = 255 * color[2]; - verts[1].modulate[3] = 255 * invratio; - - if (p->rotate) - { - VectorMA (org, height, rup2, point); - VectorMA (point, width, rright2, point); - } - else - { - VectorMA (org, p->height, vup, point); - VectorMA (point, p->width, vright, point); - } - VectorCopy (point, verts[2].xyz); - verts[2].st[0] = 1; - verts[2].st[1] = 1; - verts[2].modulate[0] = 255 * color[0]; - verts[2].modulate[1] = 255 * color[1]; - verts[2].modulate[2] = 255 * color[2]; - verts[2].modulate[3] = 255 * invratio; - - if (p->rotate) - { - VectorMA (org, height, rup2, point); - VectorMA (point, -width, rright2, point); - } - else - { - VectorMA (org, p->height, vup, point); - VectorMA (point, -p->width, vright, point); - } - VectorCopy (point, verts[3].xyz); - verts[3].st[0] = 1; - verts[3].st[1] = 0; - verts[3].modulate[0] = 255 * color[0]; - verts[3].modulate[1] = 255 * color[1]; - verts[3].modulate[2] = 255 * color[2]; - verts[3].modulate[3] = 255 * invratio; - - } - else if (p->type == P_BLEED) - { - vec3_t rr, ru; - vec3_t rotate_ang; - float alpha; - - alpha = p->alpha; - - if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) - alpha = 1; - - if (p->roll) - { - vectoangles( cg.refdef.viewaxis[0], rotate_ang ); - rotate_ang[ROLL] += p->roll; - AngleVectors ( rotate_ang, NULL, rr, ru); - } - else - { - VectorCopy (vup, ru); - VectorCopy (vright, rr); - } - - VectorMA (org, -p->height, ru, point); - VectorMA (point, -p->width, rr, point); - VectorCopy (point, verts[0].xyz); - verts[0].st[0] = 0; - verts[0].st[1] = 0; - verts[0].modulate[0] = 111; - verts[0].modulate[1] = 19; - verts[0].modulate[2] = 9; - verts[0].modulate[3] = 255 * alpha; - - VectorMA (org, -p->height, ru, point); - VectorMA (point, p->width, rr, point); - VectorCopy (point, verts[1].xyz); - verts[1].st[0] = 0; - verts[1].st[1] = 1; - verts[1].modulate[0] = 111; - verts[1].modulate[1] = 19; - verts[1].modulate[2] = 9; - verts[1].modulate[3] = 255 * alpha; - - VectorMA (org, p->height, ru, point); - VectorMA (point, p->width, rr, point); - VectorCopy (point, verts[2].xyz); - verts[2].st[0] = 1; - verts[2].st[1] = 1; - verts[2].modulate[0] = 111; - verts[2].modulate[1] = 19; - verts[2].modulate[2] = 9; - verts[2].modulate[3] = 255 * alpha; - - VectorMA (org, p->height, ru, point); - VectorMA (point, -p->width, rr, point); - VectorCopy (point, verts[3].xyz); - verts[3].st[0] = 1; - verts[3].st[1] = 0; - verts[3].modulate[0] = 111; - verts[3].modulate[1] = 19; - verts[3].modulate[2] = 9; - verts[3].modulate[3] = 255 * alpha; - - } - else if (p->type == P_FLAT_SCALEUP) - { - float width, height; - float sinR, cosR; - - if (p->color == BLOODRED) - VectorSet (color, 1, 1, 1); - else - VectorSet (color, 0.5, 0.5, 0.5); - - time = cg.time - p->time; - time2 = p->endtime - p->time; - ratio = time / time2; - - width = p->width + ( ratio * ( p->endwidth - p->width) ); - height = p->height + ( ratio * ( p->endheight - p->height) ); - - if (width > p->endwidth) - width = p->endwidth; - - if (height > p->endheight) - height = p->endheight; - - sinR = height * sin(DEG2RAD(p->roll)) * sqrt(2); - cosR = width * cos(DEG2RAD(p->roll)) * sqrt(2); - - VectorCopy (org, verts[0].xyz); - verts[0].xyz[0] -= sinR; - verts[0].xyz[1] -= cosR; - verts[0].st[0] = 0; - verts[0].st[1] = 0; - verts[0].modulate[0] = 255 * color[0]; - verts[0].modulate[1] = 255 * color[1]; - verts[0].modulate[2] = 255 * color[2]; - verts[0].modulate[3] = 255; - - VectorCopy (org, verts[1].xyz); - verts[1].xyz[0] -= cosR; - verts[1].xyz[1] += sinR; - verts[1].st[0] = 0; - verts[1].st[1] = 1; - verts[1].modulate[0] = 255 * color[0]; - verts[1].modulate[1] = 255 * color[1]; - verts[1].modulate[2] = 255 * color[2]; - verts[1].modulate[3] = 255; - - VectorCopy (org, verts[2].xyz); - verts[2].xyz[0] += sinR; - verts[2].xyz[1] += cosR; - verts[2].st[0] = 1; - verts[2].st[1] = 1; - verts[2].modulate[0] = 255 * color[0]; - verts[2].modulate[1] = 255 * color[1]; - verts[2].modulate[2] = 255 * color[2]; - verts[2].modulate[3] = 255; - - VectorCopy (org, verts[3].xyz); - verts[3].xyz[0] += cosR; - verts[3].xyz[1] -= sinR; - verts[3].st[0] = 1; - verts[3].st[1] = 0; - verts[3].modulate[0] = 255 * color[0]; - verts[3].modulate[1] = 255 * color[1]; - verts[3].modulate[2] = 255 * color[2]; - verts[3].modulate[3] = 255; - } - else if (p->type == P_FLAT) - { - - VectorCopy (org, verts[0].xyz); - verts[0].xyz[0] -= p->height; - verts[0].xyz[1] -= p->width; - verts[0].st[0] = 0; - verts[0].st[1] = 0; - verts[0].modulate[0] = 255; - verts[0].modulate[1] = 255; - verts[0].modulate[2] = 255; - verts[0].modulate[3] = 255; - - VectorCopy (org, verts[1].xyz); - verts[1].xyz[0] -= p->height; - verts[1].xyz[1] += p->width; - verts[1].st[0] = 0; - verts[1].st[1] = 1; - verts[1].modulate[0] = 255; - verts[1].modulate[1] = 255; - verts[1].modulate[2] = 255; - verts[1].modulate[3] = 255; - - VectorCopy (org, verts[2].xyz); - verts[2].xyz[0] += p->height; - verts[2].xyz[1] += p->width; - verts[2].st[0] = 1; - verts[2].st[1] = 1; - verts[2].modulate[0] = 255; - verts[2].modulate[1] = 255; - verts[2].modulate[2] = 255; - verts[2].modulate[3] = 255; - - VectorCopy (org, verts[3].xyz); - verts[3].xyz[0] += p->height; - verts[3].xyz[1] -= p->width; - verts[3].st[0] = 1; - verts[3].st[1] = 0; - verts[3].modulate[0] = 255; - verts[3].modulate[1] = 255; - verts[3].modulate[2] = 255; - verts[3].modulate[3] = 255; - - } - // Ridah - else if (p->type == P_ANIM) { - vec3_t rr, ru; - vec3_t rotate_ang; - int i, j; - - time = cg.time - p->time; - time2 = p->endtime - p->time; - ratio = time / time2; - if (ratio >= 1.0f) { - ratio = 0.9999f; - } - - width = p->width + ( ratio * ( p->endwidth - p->width) ); - height = p->height + ( ratio * ( p->endheight - p->height) ); - - // if we are "inside" this sprite, don't draw - if (Distance( cg.snap->ps.origin, org ) < width/1.5) { - return; - } - - i = p->shaderAnim; - j = (int)floor(ratio * shaderAnimCounts[p->shaderAnim]); - p->pshader = shaderAnims[i][j]; - - if (p->roll) { - vectoangles( cg.refdef.viewaxis[0], rotate_ang ); - rotate_ang[ROLL] += p->roll; - AngleVectors ( rotate_ang, NULL, rr, ru); - } - - if (p->roll) { - VectorMA (org, -height, ru, point); - VectorMA (point, -width, rr, point); - } else { - VectorMA (org, -height, vup, point); - VectorMA (point, -width, vright, point); - } - VectorCopy (point, verts[0].xyz); - verts[0].st[0] = 0; - verts[0].st[1] = 0; - verts[0].modulate[0] = 255; - verts[0].modulate[1] = 255; - verts[0].modulate[2] = 255; - verts[0].modulate[3] = 255; - - if (p->roll) { - VectorMA (point, 2*height, ru, point); - } else { - VectorMA (point, 2*height, vup, point); - } - VectorCopy (point, verts[1].xyz); - verts[1].st[0] = 0; - verts[1].st[1] = 1; - verts[1].modulate[0] = 255; - verts[1].modulate[1] = 255; - verts[1].modulate[2] = 255; - verts[1].modulate[3] = 255; - - if (p->roll) { - VectorMA (point, 2*width, rr, point); - } else { - VectorMA (point, 2*width, vright, point); - } - VectorCopy (point, verts[2].xyz); - verts[2].st[0] = 1; - verts[2].st[1] = 1; - verts[2].modulate[0] = 255; - verts[2].modulate[1] = 255; - verts[2].modulate[2] = 255; - verts[2].modulate[3] = 255; - - if (p->roll) { - VectorMA (point, -2*height, ru, point); - } else { - VectorMA (point, -2*height, vup, point); - } - VectorCopy (point, verts[3].xyz); - verts[3].st[0] = 1; - verts[3].st[1] = 0; - verts[3].modulate[0] = 255; - verts[3].modulate[1] = 255; - verts[3].modulate[2] = 255; - verts[3].modulate[3] = 255; - } - // done. - - if (!p->pshader) { -// (SA) temp commented out for DM -// CG_Printf ("CG_AddParticleToScene type %d p->pshader == ZERO\n", p->type); - return; - } - - if (p->type == P_WEATHER || p->type == P_WEATHER_TURBULENT || p->type == P_WEATHER_FLURRY) - trap_R_AddPolyToScene( p->pshader, 3, TRIverts ); - else - trap_R_AddPolyToScene( p->pshader, 4, verts ); - -} - -// Ridah, made this static so it doesn't interfere with other files -static float roll = 0.0; - -/* -=============== -CG_AddParticles -=============== -*/ -void CG_AddParticles (void) -{ - cparticle_t *p, *next; - float alpha; - float time, time2; - vec3_t org; - int color; - cparticle_t *active, *tail; - int type; - vec3_t rotate_ang; - - if (!initparticles) - CG_ClearParticles (); - - VectorCopy( cg.refdef.viewaxis[0], vforward ); - VectorCopy( cg.refdef.viewaxis[1], vright ); - VectorCopy( cg.refdef.viewaxis[2], vup ); - - vectoangles( cg.refdef.viewaxis[0], rotate_ang ); - roll += ((cg.time - oldtime) * 0.1) ; - rotate_ang[ROLL] += (roll*0.9); - AngleVectors ( rotate_ang, rforward, rright, rup); - - oldtime = cg.time; - - active = NULL; - tail = NULL; - - for (p=active_particles ; p ; p=next) - { - - next = p->next; - - time = (cg.time - p->time)*0.001; - - alpha = p->alpha + time*p->alphavel; - if (alpha <= 0) - { // faded out - p->next = free_particles; - free_particles = p; - p->type = 0; - p->color = 0; - p->alpha = 0; - continue; - } - - if (p->type == P_SMOKE || p->type == P_ANIM || p->type == P_BLEED || p->type == P_SMOKE_IMPACT) - { - if (cg.time > p->endtime) - { - p->next = free_particles; - free_particles = p; - p->type = 0; - p->color = 0; - p->alpha = 0; - - continue; - } - - } - - if (p->type == P_WEATHER_FLURRY) - { - if (cg.time > p->endtime) - { - p->next = free_particles; - free_particles = p; - p->type = 0; - p->color = 0; - p->alpha = 0; - - continue; - } - } - - - if (p->type == P_FLAT_SCALEUP_FADE) - { - if (cg.time > p->endtime) - { - p->next = free_particles; - free_particles = p; - p->type = 0; - p->color = 0; - p->alpha = 0; - continue; - } - - } - - if ((p->type == P_BAT || p->type == P_SPRITE) && p->endtime < 0) { - // temporary sprite - CG_AddParticleToScene (p, p->org, alpha); - p->next = free_particles; - free_particles = p; - p->type = 0; - p->color = 0; - p->alpha = 0; - continue; - } - - p->next = NULL; - if (!tail) - active = tail = p; - else - { - tail->next = p; - tail = p; - } - - if (alpha > 1.0) - alpha = 1; - - color = p->color; - - time2 = time*time; - - org[0] = p->org[0] + p->vel[0]*time + p->accel[0]*time2; - org[1] = p->org[1] + p->vel[1]*time + p->accel[1]*time2; - org[2] = p->org[2] + p->vel[2]*time + p->accel[2]*time2; - - type = p->type; - - CG_AddParticleToScene (p, org, alpha); - } - - active_particles = active; -} - -/* -====================== -CG_AddParticles -====================== -*/ -void CG_ParticleSnowFlurry (qhandle_t pshader, centity_t *cent) -{ - cparticle_t *p; - qboolean turb = qtrue; - - if (!pshader) - CG_Printf ("CG_ParticleSnowFlurry pshader == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - p->color = 0; - p->alpha = 0.90f; - p->alphavel = 0; - - p->start = cent->currentState.origin2[0]; - p->end = cent->currentState.origin2[1]; - - p->endtime = cg.time + cent->currentState.time; - p->startfade = cg.time + cent->currentState.time2; - - p->pshader = pshader; - - if (rand()%100 > 90) - { - p->height = 32; - p->width = 32; - p->alpha = 0.10f; - } - else - { - p->height = 1; - p->width = 1; - } - - p->vel[2] = -20; - - p->type = P_WEATHER_FLURRY; - - if (turb) - p->vel[2] = -10; - - VectorCopy(cent->currentState.origin, p->org); - - p->org[0] = p->org[0]; - p->org[1] = p->org[1]; - p->org[2] = p->org[2]; - - p->vel[0] = p->vel[1] = 0; - - p->accel[0] = p->accel[1] = p->accel[2] = 0; - - p->vel[0] += cent->currentState.angles[0] * 32 + (crandom() * 16); - p->vel[1] += cent->currentState.angles[1] * 32 + (crandom() * 16); - p->vel[2] += cent->currentState.angles[2]; - - if (turb) - { - p->accel[0] = crandom () * 16; - p->accel[1] = crandom () * 16; - } - -} - -void CG_ParticleSnow (qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum) -{ - cparticle_t *p; - - if (!pshader) - CG_Printf ("CG_ParticleSnow pshader == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - p->color = 0; - p->alpha = 0.40f; - p->alphavel = 0; - p->start = origin[2]; - p->end = origin2[2]; - p->pshader = pshader; - p->height = 1; - p->width = 1; - - p->vel[2] = -50; - - if (turb) - { - p->type = P_WEATHER_TURBULENT; - p->vel[2] = -50 * 1.3; - } - else - { - p->type = P_WEATHER; - } - - VectorCopy(origin, p->org); - - p->org[0] = p->org[0] + ( crandom() * range); - p->org[1] = p->org[1] + ( crandom() * range); - p->org[2] = p->org[2] + ( crandom() * (p->start - p->end)); - - p->vel[0] = p->vel[1] = 0; - - p->accel[0] = p->accel[1] = p->accel[2] = 0; - - if (turb) - { - p->vel[0] = crandom() * 16; - p->vel[1] = crandom() * 16; - } - - // Rafael snow pvs check - p->snum = snum; - p->link = qtrue; - -} - -void CG_ParticleBubble (qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum) -{ - cparticle_t *p; - float randsize; - - if (!pshader) - CG_Printf ("CG_ParticleSnow pshader == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - p->color = 0; - p->alpha = 0.40f; - p->alphavel = 0; - p->start = origin[2]; - p->end = origin2[2]; - p->pshader = pshader; - - randsize = 1 + (crandom() * 0.5); - - p->height = randsize; - p->width = randsize; - - p->vel[2] = 50 + ( crandom() * 10 ); - - if (turb) - { - p->type = P_BUBBLE_TURBULENT; - p->vel[2] = 50 * 1.3; - } - else - { - p->type = P_BUBBLE; - } - - VectorCopy(origin, p->org); - - p->org[0] = p->org[0] + ( crandom() * range); - p->org[1] = p->org[1] + ( crandom() * range); - p->org[2] = p->org[2] + ( crandom() * (p->start - p->end)); - - p->vel[0] = p->vel[1] = 0; - - p->accel[0] = p->accel[1] = p->accel[2] = 0; - - if (turb) - { - p->vel[0] = crandom() * 4; - p->vel[1] = crandom() * 4; - } - - // Rafael snow pvs check - p->snum = snum; - p->link = qtrue; - -} - -void CG_ParticleSmoke (qhandle_t pshader, centity_t *cent) -{ - - // using cent->density = enttime - // cent->frame = startfade - cparticle_t *p; - - if (!pshader) - CG_Printf ("CG_ParticleSmoke == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - - p->endtime = cg.time + cent->currentState.time; - p->startfade = cg.time + cent->currentState.time2; - - p->color = 0; - p->alpha = 1.0; - p->alphavel = 0; - p->start = cent->currentState.origin[2]; - p->end = cent->currentState.origin2[2]; - p->pshader = pshader; - p->rotate = qfalse; - p->height = 8; - p->width = 8; - p->endheight = 32; - p->endwidth = 32; - p->type = P_SMOKE; - - VectorCopy(cent->currentState.origin, p->org); - - p->vel[0] = p->vel[1] = 0; - p->accel[0] = p->accel[1] = p->accel[2] = 0; - - p->vel[2] = 5; - - if (cent->currentState.frame == 1)// reverse gravity - p->vel[2] *= -1; - - p->roll = 8 + (crandom() * 4); -} - - -void CG_ParticleBulletDebris (vec3_t org, vec3_t vel, int duration) -{ - - cparticle_t *p; - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - - p->endtime = cg.time + duration; - p->startfade = cg.time + duration/2; - - p->color = EMISIVEFADE; - p->alpha = 1.0; - p->alphavel = 0; - - p->height = 0.5; - p->width = 0.5; - p->endheight = 0.5; - p->endwidth = 0.5; - - p->pshader = cgs.media.tracerShader; - - p->type = P_SMOKE; - - VectorCopy(org, p->org); - - p->vel[0] = vel[0]; - p->vel[1] = vel[1]; - p->vel[2] = vel[2]; - p->accel[0] = p->accel[1] = p->accel[2] = 0; - - p->accel[2] = -60; - p->vel[2] += -20; - -} - -/* -====================== -CG_ParticleExplosion -====================== -*/ - -void CG_ParticleExplosion (char *animStr, vec3_t origin, vec3_t vel, int duration, int sizeStart, int sizeEnd) -{ - cparticle_t *p; - int anim; - - if (animStr < (char *)10) - CG_Error( "CG_ParticleExplosion: animStr is probably an index rather than a string" ); - - // find the animation string - for (anim=0; shaderAnimNames[anim]; anim++) { - if (!stricmp( animStr, shaderAnimNames[anim] )) - break; - } - if (!shaderAnimNames[anim]) { - CG_Error("CG_ParticleExplosion: unknown animation string: %s\n", animStr); - return; - } - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - p->alpha = 1.0; - p->alphavel = 0; - - if (duration < 0) { - duration *= -1; - p->roll = 0; - } else { - p->roll = crandom()*179; - } - - p->shaderAnim = anim; - - p->width = sizeStart; - p->height = sizeStart*shaderAnimSTRatio[anim]; // for sprites that are stretch in either direction - - p->endheight = sizeEnd; - p->endwidth = sizeEnd*shaderAnimSTRatio[anim]; - - p->endtime = cg.time + duration; - - p->type = P_ANIM; - - VectorCopy( origin, p->org ); - VectorCopy( vel, p->vel ); - VectorClear( p->accel ); - -} - -// Rafael Shrapnel -void CG_AddParticleShrapnel (localEntity_t *le) -{ - return; -} -// done. - -int CG_NewParticleArea (int num) -{ - // const char *str; - char *str; - char *token; - int type; - vec3_t origin, origin2; - int i; - float range = 0; - int turb; - int numparticles; - int snum; - - str = (char *) CG_ConfigString (num); - if (!str[0]) - return (0); - - // returns type 128 64 or 32 - token = COM_Parse (&str); - type = atoi (token); - - if (type == 1) - range = 128; - else if (type == 2) - range = 64; - else if (type == 3) - range = 32; - else if (type == 0) - range = 256; - else if (type == 4) - range = 8; - else if (type == 5) - range = 16; - else if (type == 6) - range = 32; - else if (type == 7) - range = 64; - - - for (i=0; i<3; i++) - { - token = COM_Parse (&str); - origin[i] = atof (token); - } - - for (i=0; i<3; i++) - { - token = COM_Parse (&str); - origin2[i] = atof (token); - } - - token = COM_Parse (&str); - numparticles = atoi (token); - - token = COM_Parse (&str); - turb = atoi (token); - - token = COM_Parse (&str); - snum = atoi (token); - - for (i=0; i= 4) - CG_ParticleBubble (cgs.media.waterBubbleShader, origin, origin2, turb, range, snum); - else - CG_ParticleSnow (cgs.media.waterBubbleShader, origin, origin2, turb, range, snum); - } - - return (1); -} - -void CG_SnowLink (centity_t *cent, qboolean particleOn) -{ - cparticle_t *p, *next; - int id; - - id = cent->currentState.frame; - - for (p=active_particles ; p ; p=next) - { - next = p->next; - - if (p->type == P_WEATHER || p->type == P_WEATHER_TURBULENT) - { - if (p->snum == id) - { - if (particleOn) - p->link = qtrue; - else - p->link = qfalse; - } - } - - } -} - -void CG_ParticleImpactSmokePuff (qhandle_t pshader, vec3_t origin) -{ - cparticle_t *p; - - if (!pshader) - CG_Printf ("CG_ParticleImpactSmokePuff pshader == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - p->alpha = 0.25; - p->alphavel = 0; - p->roll = crandom()*179; - - p->pshader = pshader; - - p->endtime = cg.time + 1000; - p->startfade = cg.time + 100; - - p->width = rand()%4 + 8; - p->height = rand()%4 + 8; - - p->endheight = p->height *2; - p->endwidth = p->width * 2; - - p->endtime = cg.time + 500; - - p->type = P_SMOKE_IMPACT; - - VectorCopy( origin, p->org ); - VectorSet(p->vel, 0, 0, 20); - VectorSet(p->accel, 0, 0, 20); - - p->rotate = qtrue; -} - -void CG_Particle_Bleed (qhandle_t pshader, vec3_t start, vec3_t dir, int fleshEntityNum, int duration) -{ - cparticle_t *p; - - if (!pshader) - CG_Printf ("CG_Particle_Bleed pshader == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - p->alpha = 1.0; - p->alphavel = 0; - p->roll = 0; - - p->pshader = pshader; - - p->endtime = cg.time + duration; - - if (fleshEntityNum) - p->startfade = cg.time; - else - p->startfade = cg.time + 100; - - p->width = 4; - p->height = 4; - - p->endheight = 4+rand()%3; - p->endwidth = p->endheight; - - p->type = P_SMOKE; - - VectorCopy( start, p->org ); - p->vel[0] = 0; - p->vel[1] = 0; - p->vel[2] = -20; - VectorClear( p->accel ); - - p->rotate = qfalse; - - p->roll = rand()%179; - - p->color = BLOODRED; - p->alpha = 0.75; - -} - -void CG_Particle_OilParticle (qhandle_t pshader, centity_t *cent) -{ - cparticle_t *p; - - int time; - int time2; - float ratio; - - float duration = 1500; - - time = cg.time; - time2 = cg.time + cent->currentState.time; - - ratio =(float)1 - ((float)time / (float)time2); - - if (!pshader) - CG_Printf ("CG_Particle_OilParticle == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - p->alpha = 1.0; - p->alphavel = 0; - p->roll = 0; - - p->pshader = pshader; - - p->endtime = cg.time + duration; - - p->startfade = p->endtime; - - p->width = 1; - p->height = 3; - - p->endheight = 3; - p->endwidth = 1; - - p->type = P_SMOKE; - - VectorCopy(cent->currentState.origin, p->org ); - - p->vel[0] = (cent->currentState.origin2[0] * (16 * ratio)); - p->vel[1] = (cent->currentState.origin2[1] * (16 * ratio)); - p->vel[2] = (cent->currentState.origin2[2]); - - p->snum = 1.0f; - - VectorClear( p->accel ); - - p->accel[2] = -20; - - p->rotate = qfalse; - - p->roll = rand()%179; - - p->alpha = 0.75; - -} - - -void CG_Particle_OilSlick (qhandle_t pshader, centity_t *cent) -{ - cparticle_t *p; - - if (!pshader) - CG_Printf ("CG_Particle_OilSlick == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - - if (cent->currentState.angles2[2]) - p->endtime = cg.time + cent->currentState.angles2[2]; - else - p->endtime = cg.time + 60000; - - p->startfade = p->endtime; - - p->alpha = 1.0; - p->alphavel = 0; - p->roll = 0; - - p->pshader = pshader; - - if (cent->currentState.angles2[0] || cent->currentState.angles2[1]) - { - p->width = cent->currentState.angles2[0]; - p->height = cent->currentState.angles2[0]; - - p->endheight = cent->currentState.angles2[1]; - p->endwidth = cent->currentState.angles2[1]; - } - else - { - p->width = 8; - p->height = 8; - - p->endheight = 16; - p->endwidth = 16; - } - - p->type = P_FLAT_SCALEUP; - - p->snum = 1.0; - - VectorCopy(cent->currentState.origin, p->org ); - - p->org[2]+= 0.55 + (crandom() * 0.5); - - p->vel[0] = 0; - p->vel[1] = 0; - p->vel[2] = 0; - VectorClear( p->accel ); - - p->rotate = qfalse; - - p->roll = rand()%179; - - p->alpha = 0.75; - -} - -void CG_OilSlickRemove (centity_t *cent) -{ - cparticle_t *p, *next; - int id; - - id = 1.0f; - - if (!id) - CG_Printf ("CG_OilSlickRevove NULL id\n"); - - for (p=active_particles ; p ; p=next) - { - next = p->next; - - if (p->type == P_FLAT_SCALEUP) - { - if (p->snum == id) - { - p->endtime = cg.time + 100; - p->startfade = p->endtime; - p->type = P_FLAT_SCALEUP_FADE; - - } - } - - } -} - -qboolean ValidBloodPool (vec3_t start) -{ -#define EXTRUDE_DIST 0.5 - - vec3_t angles; - vec3_t right, up; - vec3_t this_pos, x_pos, center_pos, end_pos; - float x, y; - float fwidth, fheight; - trace_t trace; - vec3_t normal; - - fwidth = 16; - fheight = 16; - - VectorSet (normal, 0, 0, 1); - - vectoangles (normal, angles); - AngleVectors (angles, NULL, right, up); - - VectorMA (start, EXTRUDE_DIST, normal, center_pos); - - for (x= -fwidth/2; xendpos, start); - legit = ValidBloodPool (start); - - if (!legit) - return; - - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - - p->endtime = cg.time + 3000; - p->startfade = p->endtime; - - p->alpha = 1.0; - p->alphavel = 0; - p->roll = 0; - - p->pshader = pshader; - - rndSize = 0.4 + random()*0.6; - - p->width = 8*rndSize; - p->height = 8*rndSize; - - p->endheight = 16*rndSize; - p->endwidth = 16*rndSize; - - p->type = P_FLAT_SCALEUP; - - VectorCopy(start, p->org ); - - p->vel[0] = 0; - p->vel[1] = 0; - p->vel[2] = 0; - VectorClear( p->accel ); - - p->rotate = qfalse; - - p->roll = rand()%179; - - p->alpha = 0.75; - - p->color = BLOODRED; -} - -#define NORMALSIZE 16 -#define LARGESIZE 32 - -void CG_ParticleBloodCloud (centity_t *cent, vec3_t origin, vec3_t dir) -{ - float length; - float dist; - float crittersize; - vec3_t angles, forward; - vec3_t point; - cparticle_t *p; - int i; - - dist = 0; - - length = VectorLength (dir); - vectoangles (dir, angles); - AngleVectors (angles, forward, NULL, NULL); - - crittersize = LARGESIZE; - - if (length) - dist = length / crittersize; - - if (dist < 1) - dist = 1; - - VectorCopy (origin, point); - - for (i=0; inext; - p->next = active_particles; - active_particles = p; - - p->time = cg.time; - p->alpha = 1.0; - p->alphavel = 0; - p->roll = 0; - - p->pshader = cgs.media.smokePuffShader; - - p->endtime = cg.time + 350 + (crandom() * 100); - - p->startfade = cg.time; - - p->width = LARGESIZE; - p->height = LARGESIZE; - p->endheight = LARGESIZE; - p->endwidth = LARGESIZE; - - p->type = P_SMOKE; - - VectorCopy( origin, p->org ); - - p->vel[0] = 0; - p->vel[1] = 0; - p->vel[2] = -1; - - VectorClear( p->accel ); - - p->rotate = qfalse; - - p->roll = rand()%179; - - p->color = BLOODRED; - - p->alpha = 0.75; - - } - - -} - -void CG_ParticleSparks (vec3_t org, vec3_t vel, int duration, float x, float y, float speed) -{ - cparticle_t *p; - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - - p->endtime = cg.time + duration; - p->startfade = cg.time + duration/2; - - p->color = EMISIVEFADE; - p->alpha = 0.4f; - p->alphavel = 0; - - p->height = 0.5; - p->width = 0.5; - p->endheight = 0.5; - p->endwidth = 0.5; - - p->pshader = cgs.media.tracerShader; - - p->type = P_SMOKE; - - VectorCopy(org, p->org); - - p->org[0] += (crandom() * x); - p->org[1] += (crandom() * y); - - p->vel[0] = vel[0]; - p->vel[1] = vel[1]; - p->vel[2] = vel[2]; - - p->accel[0] = p->accel[1] = p->accel[2] = 0; - - p->vel[0] += (crandom() * 4); - p->vel[1] += (crandom() * 4); - p->vel[2] += (20 + (crandom() * 10)) * speed; - - p->accel[0] = crandom () * 4; - p->accel[1] = crandom () * 4; - -} - -void CG_ParticleDust (centity_t *cent, vec3_t origin, vec3_t dir) -{ - float length; - float dist; - float crittersize; - vec3_t angles, forward; - vec3_t point; - cparticle_t *p; - int i; - - dist = 0; - - VectorNegate (dir, dir); - length = VectorLength (dir); - vectoangles (dir, angles); - AngleVectors (angles, forward, NULL, NULL); - - crittersize = LARGESIZE; - - if (length) - dist = length / crittersize; - - if (dist < 1) - dist = 1; - - VectorCopy (origin, point); - - for (i=0; inext; - p->next = active_particles; - active_particles = p; - - p->time = cg.time; - p->alpha = 5.0; - p->alphavel = 0; - p->roll = 0; - - p->pshader = cgs.media.smokePuffShader; - - // RF, stay around for long enough to expand and dissipate naturally - if (length) - p->endtime = cg.time + 4500 + (crandom() * 3500); - else - p->endtime = cg.time + 750 + (crandom() * 500); - - p->startfade = cg.time; - - p->width = LARGESIZE; - p->height = LARGESIZE; - - // RF, expand while falling - p->endheight = LARGESIZE*3.0; - p->endwidth = LARGESIZE*3.0; - - if (!length) - { - p->width *= 0.2f; - p->height *= 0.2f; - - p->endheight = NORMALSIZE; - p->endwidth = NORMALSIZE; - } - - p->type = P_SMOKE; - - VectorCopy( point, p->org ); - - p->vel[0] = crandom()*6; - p->vel[1] = crandom()*6; - p->vel[2] = random()*20; - - // RF, add some gravity/randomness - p->accel[0] = crandom()*3; - p->accel[1] = crandom()*3; - p->accel[2] = -PARTICLE_GRAVITY*0.4; - - VectorClear( p->accel ); - - p->rotate = qfalse; - - p->roll = rand()%179; - - p->alpha = 0.75; - - } - - -} - -void CG_ParticleMisc (qhandle_t pshader, vec3_t origin, int size, int duration, float alpha) -{ - cparticle_t *p; - - if (!pshader) - CG_Printf ("CG_ParticleImpactSmokePuff pshader == ZERO!\n"); - - if (!free_particles) - return; - - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - p->alpha = 1.0; - p->alphavel = 0; - p->roll = rand()%179; - - p->pshader = pshader; - - if (duration > 0) - p->endtime = cg.time + duration; - else - p->endtime = duration; - - p->startfade = cg.time; - - p->width = size; - p->height = size; - - p->endheight = size; - p->endwidth = size; - - p->type = P_SPRITE; - - VectorCopy( origin, p->org ); - - p->rotate = qfalse; -} +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// Rafael particles +// cg_particles.c + +#include "cg_local.h" + +#define BLOODRED 2 +#define EMISIVEFADE 3 +#define GREY75 4 + +typedef struct particle_s +{ + struct particle_s *next; + + float time; + float endtime; + + vec3_t org; + vec3_t vel; + vec3_t accel; + int color; + float colorvel; + float alpha; + float alphavel; + int type; + qhandle_t pshader; + + float height; + float width; + + float endheight; + float endwidth; + + float start; + float end; + + float startfade; + qboolean rotate; + int snum; + + qboolean link; + + // Ridah + int shaderAnim; + int roll; + + int accumroll; + +} cparticle_t; + +typedef enum +{ + P_NONE, + P_WEATHER, + P_FLAT, + P_SMOKE, + P_ROTATE, + P_WEATHER_TURBULENT, + P_ANIM, // Ridah + P_BAT, + P_BLEED, + P_FLAT_SCALEUP, + P_FLAT_SCALEUP_FADE, + P_WEATHER_FLURRY, + P_SMOKE_IMPACT, + P_BUBBLE, + P_BUBBLE_TURBULENT, + P_SPRITE +} particle_type_t; + +#define MAX_SHADER_ANIMS 32 +#define MAX_SHADER_ANIM_FRAMES 64 + +static char *shaderAnimNames[MAX_SHADER_ANIMS] = { + "explode1", + "blacksmokeanim", + "twiltb2", + "expblue", + "blacksmokeanimb", // uses 'explode1' sequence + "blood", + NULL +}; +static qhandle_t shaderAnims[MAX_SHADER_ANIMS][MAX_SHADER_ANIM_FRAMES]; +static int shaderAnimCounts[MAX_SHADER_ANIMS] = { + 23, + 25, + 45, + 25, + 23, + 5, +}; +static float shaderAnimSTRatio[MAX_SHADER_ANIMS] = { + 1.405f, + 1.0f, + 1.0f, + 1.0f, + 1.0f, + 1.0f, +}; +static int numShaderAnims; +// done. + +#define PARTICLE_GRAVITY 40 +#define MAX_PARTICLES 1024 * 8 + +cparticle_t *active_particles, *free_particles; +cparticle_t particles[MAX_PARTICLES]; +int cl_numparticles = MAX_PARTICLES; + +qboolean initparticles = qfalse; +vec3_t vforward, vright, vup; +vec3_t rforward, rright, rup; + +float oldtime; + +/* +=============== +CL_ClearParticles +=============== +*/ +void CG_ClearParticles (void) +{ + int i; + + memset( particles, 0, sizeof(particles) ); + + free_particles = &particles[0]; + active_particles = NULL; + + for (i=0 ;itype == P_WEATHER || p->type == P_WEATHER_TURBULENT || p->type == P_WEATHER_FLURRY + || p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) + {// create a front facing polygon + + if (p->type != P_WEATHER_FLURRY) + { + if (p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) + { + if (org[2] > p->end) + { + p->time = cg.time; + VectorCopy (org, p->org); // Ridah, fixes rare snow flakes that flicker on the ground + + p->org[2] = ( p->start + crandom () * 4 ); + + + if (p->type == P_BUBBLE_TURBULENT) + { + p->vel[0] = crandom() * 4; + p->vel[1] = crandom() * 4; + } + + } + } + else + { + if (org[2] < p->end) + { + p->time = cg.time; + VectorCopy (org, p->org); // Ridah, fixes rare snow flakes that flicker on the ground + + while (p->org[2] < p->end) + { + p->org[2] += (p->start - p->end); + } + + + if (p->type == P_WEATHER_TURBULENT) + { + p->vel[0] = crandom() * 16; + p->vel[1] = crandom() * 16; + } + + } + } + + + // Rafael snow pvs check + if (!p->link) + return; + + p->alpha = 1; + } + + // Ridah, had to do this or MAX_POLYS is being exceeded in village1.bsp + if (Distance( cg.snap->ps.origin, org ) > 1024) { + return; + } + // done. + + if (p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) + { + VectorMA (org, -p->height, vup, point); + VectorMA (point, -p->width, vright, point); + VectorCopy (point, verts[0].xyz); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255 * p->alpha; + + VectorMA (org, -p->height, vup, point); + VectorMA (point, p->width, vright, point); + VectorCopy (point, verts[1].xyz); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255 * p->alpha; + + VectorMA (org, p->height, vup, point); + VectorMA (point, p->width, vright, point); + VectorCopy (point, verts[2].xyz); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255 * p->alpha; + + VectorMA (org, p->height, vup, point); + VectorMA (point, -p->width, vright, point); + VectorCopy (point, verts[3].xyz); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255 * p->alpha; + } + else + { + VectorMA (org, -p->height, vup, point); + VectorMA (point, -p->width, vright, point); + VectorCopy( point, TRIverts[0].xyz ); + TRIverts[0].st[0] = 1; + TRIverts[0].st[1] = 0; + TRIverts[0].modulate[0] = 255; + TRIverts[0].modulate[1] = 255; + TRIverts[0].modulate[2] = 255; + TRIverts[0].modulate[3] = 255 * p->alpha; + + VectorMA (org, p->height, vup, point); + VectorMA (point, -p->width, vright, point); + VectorCopy (point, TRIverts[1].xyz); + TRIverts[1].st[0] = 0; + TRIverts[1].st[1] = 0; + TRIverts[1].modulate[0] = 255; + TRIverts[1].modulate[1] = 255; + TRIverts[1].modulate[2] = 255; + TRIverts[1].modulate[3] = 255 * p->alpha; + + VectorMA (org, p->height, vup, point); + VectorMA (point, p->width, vright, point); + VectorCopy (point, TRIverts[2].xyz); + TRIverts[2].st[0] = 0; + TRIverts[2].st[1] = 1; + TRIverts[2].modulate[0] = 255; + TRIverts[2].modulate[1] = 255; + TRIverts[2].modulate[2] = 255; + TRIverts[2].modulate[3] = 255 * p->alpha; + } + + } + else if (p->type == P_SPRITE) + { + vec3_t rr, ru; + vec3_t rotate_ang; + + VectorSet (color, 1.0, 1.0, 1.0); + time = cg.time - p->time; + time2 = p->endtime - p->time; + ratio = time / time2; + + width = p->width + ( ratio * ( p->endwidth - p->width) ); + height = p->height + ( ratio * ( p->endheight - p->height) ); + + if (p->roll) { + vectoangles( cg.refdef.viewaxis[0], rotate_ang ); + rotate_ang[ROLL] += p->roll; + AngleVectors ( rotate_ang, NULL, rr, ru); + } + + if (p->roll) { + VectorMA (org, -height, ru, point); + VectorMA (point, -width, rr, point); + } else { + VectorMA (org, -height, vup, point); + VectorMA (point, -width, vright, point); + } + VectorCopy (point, verts[0].xyz); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, 2*height, ru, point); + } else { + VectorMA (point, 2*height, vup, point); + } + VectorCopy (point, verts[1].xyz); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, 2*width, rr, point); + } else { + VectorMA (point, 2*width, vright, point); + } + VectorCopy (point, verts[2].xyz); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, -2*height, ru, point); + } else { + VectorMA (point, -2*height, vup, point); + } + VectorCopy (point, verts[3].xyz); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255; + } + else if (p->type == P_SMOKE || p->type == P_SMOKE_IMPACT) + {// create a front rotating facing polygon + + if ( p->type == P_SMOKE_IMPACT && Distance( cg.snap->ps.origin, org ) > 1024) { + return; + } + + if (p->color == BLOODRED) + VectorSet (color, 0.22f, 0.0f, 0.0f); + else if (p->color == GREY75) + { + float len; + float greyit; + float val; + len = Distance (cg.snap->ps.origin, org); + if (!len) + len = 1; + + val = 4096/len; + greyit = 0.25 * val; + if (greyit > 0.5) + greyit = 0.5; + + VectorSet (color, greyit, greyit, greyit); + } + else + VectorSet (color, 1.0, 1.0, 1.0); + + time = cg.time - p->time; + time2 = p->endtime - p->time; + ratio = time / time2; + + if (cg.time > p->startfade) + { + invratio = 1 - ( (cg.time - p->startfade) / (p->endtime - p->startfade) ); + + if (p->color == EMISIVEFADE) + { + float fval; + fval = (invratio * invratio); + if (fval < 0) + fval = 0; + VectorSet (color, fval , fval , fval ); + } + invratio *= p->alpha; + } + else + invratio = 1 * p->alpha; + + if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) + invratio = 1; + + if (invratio > 1) + invratio = 1; + + width = p->width + ( ratio * ( p->endwidth - p->width) ); + height = p->height + ( ratio * ( p->endheight - p->height) ); + + if (p->type != P_SMOKE_IMPACT) + { + vec3_t temp; + + vectoangles (rforward, temp); + p->accumroll += p->roll; + temp[ROLL] += p->accumroll * 0.1; + AngleVectors ( temp, NULL, rright2, rup2); + } + else + { + VectorCopy (rright, rright2); + VectorCopy (rup, rup2); + } + + if (p->rotate) + { + VectorMA (org, -height, rup2, point); + VectorMA (point, -width, rright2, point); + } + else + { + VectorMA (org, -p->height, vup, point); + VectorMA (point, -p->width, vright, point); + } + VectorCopy (point, verts[0].xyz); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255 * color[0]; + verts[0].modulate[1] = 255 * color[1]; + verts[0].modulate[2] = 255 * color[2]; + verts[0].modulate[3] = 255 * invratio; + + if (p->rotate) + { + VectorMA (org, -height, rup2, point); + VectorMA (point, width, rright2, point); + } + else + { + VectorMA (org, -p->height, vup, point); + VectorMA (point, p->width, vright, point); + } + VectorCopy (point, verts[1].xyz); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255 * color[0]; + verts[1].modulate[1] = 255 * color[1]; + verts[1].modulate[2] = 255 * color[2]; + verts[1].modulate[3] = 255 * invratio; + + if (p->rotate) + { + VectorMA (org, height, rup2, point); + VectorMA (point, width, rright2, point); + } + else + { + VectorMA (org, p->height, vup, point); + VectorMA (point, p->width, vright, point); + } + VectorCopy (point, verts[2].xyz); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255 * color[0]; + verts[2].modulate[1] = 255 * color[1]; + verts[2].modulate[2] = 255 * color[2]; + verts[2].modulate[3] = 255 * invratio; + + if (p->rotate) + { + VectorMA (org, height, rup2, point); + VectorMA (point, -width, rright2, point); + } + else + { + VectorMA (org, p->height, vup, point); + VectorMA (point, -p->width, vright, point); + } + VectorCopy (point, verts[3].xyz); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255 * color[0]; + verts[3].modulate[1] = 255 * color[1]; + verts[3].modulate[2] = 255 * color[2]; + verts[3].modulate[3] = 255 * invratio; + + } + else if (p->type == P_BLEED) + { + vec3_t rr, ru; + vec3_t rotate_ang; + float alpha; + + alpha = p->alpha; + + if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) + alpha = 1; + + if (p->roll) + { + vectoangles( cg.refdef.viewaxis[0], rotate_ang ); + rotate_ang[ROLL] += p->roll; + AngleVectors ( rotate_ang, NULL, rr, ru); + } + else + { + VectorCopy (vup, ru); + VectorCopy (vright, rr); + } + + VectorMA (org, -p->height, ru, point); + VectorMA (point, -p->width, rr, point); + VectorCopy (point, verts[0].xyz); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 111; + verts[0].modulate[1] = 19; + verts[0].modulate[2] = 9; + verts[0].modulate[3] = 255 * alpha; + + VectorMA (org, -p->height, ru, point); + VectorMA (point, p->width, rr, point); + VectorCopy (point, verts[1].xyz); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 111; + verts[1].modulate[1] = 19; + verts[1].modulate[2] = 9; + verts[1].modulate[3] = 255 * alpha; + + VectorMA (org, p->height, ru, point); + VectorMA (point, p->width, rr, point); + VectorCopy (point, verts[2].xyz); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 111; + verts[2].modulate[1] = 19; + verts[2].modulate[2] = 9; + verts[2].modulate[3] = 255 * alpha; + + VectorMA (org, p->height, ru, point); + VectorMA (point, -p->width, rr, point); + VectorCopy (point, verts[3].xyz); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 111; + verts[3].modulate[1] = 19; + verts[3].modulate[2] = 9; + verts[3].modulate[3] = 255 * alpha; + + } + else if (p->type == P_FLAT_SCALEUP) + { + float width, height; + float sinR, cosR; + + if (p->color == BLOODRED) + VectorSet (color, 1, 1, 1); + else + VectorSet (color, 0.5, 0.5, 0.5); + + time = cg.time - p->time; + time2 = p->endtime - p->time; + ratio = time / time2; + + width = p->width + ( ratio * ( p->endwidth - p->width) ); + height = p->height + ( ratio * ( p->endheight - p->height) ); + + if (width > p->endwidth) + width = p->endwidth; + + if (height > p->endheight) + height = p->endheight; + + sinR = height * sin(DEG2RAD(p->roll)) * sqrt(2); + cosR = width * cos(DEG2RAD(p->roll)) * sqrt(2); + + VectorCopy (org, verts[0].xyz); + verts[0].xyz[0] -= sinR; + verts[0].xyz[1] -= cosR; + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255 * color[0]; + verts[0].modulate[1] = 255 * color[1]; + verts[0].modulate[2] = 255 * color[2]; + verts[0].modulate[3] = 255; + + VectorCopy (org, verts[1].xyz); + verts[1].xyz[0] -= cosR; + verts[1].xyz[1] += sinR; + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255 * color[0]; + verts[1].modulate[1] = 255 * color[1]; + verts[1].modulate[2] = 255 * color[2]; + verts[1].modulate[3] = 255; + + VectorCopy (org, verts[2].xyz); + verts[2].xyz[0] += sinR; + verts[2].xyz[1] += cosR; + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255 * color[0]; + verts[2].modulate[1] = 255 * color[1]; + verts[2].modulate[2] = 255 * color[2]; + verts[2].modulate[3] = 255; + + VectorCopy (org, verts[3].xyz); + verts[3].xyz[0] += cosR; + verts[3].xyz[1] -= sinR; + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255 * color[0]; + verts[3].modulate[1] = 255 * color[1]; + verts[3].modulate[2] = 255 * color[2]; + verts[3].modulate[3] = 255; + } + else if (p->type == P_FLAT) + { + + VectorCopy (org, verts[0].xyz); + verts[0].xyz[0] -= p->height; + verts[0].xyz[1] -= p->width; + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255; + + VectorCopy (org, verts[1].xyz); + verts[1].xyz[0] -= p->height; + verts[1].xyz[1] += p->width; + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255; + + VectorCopy (org, verts[2].xyz); + verts[2].xyz[0] += p->height; + verts[2].xyz[1] += p->width; + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255; + + VectorCopy (org, verts[3].xyz); + verts[3].xyz[0] += p->height; + verts[3].xyz[1] -= p->width; + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255; + + } + // Ridah + else if (p->type == P_ANIM) { + vec3_t rr, ru; + vec3_t rotate_ang; + int i, j; + + time = cg.time - p->time; + time2 = p->endtime - p->time; + ratio = time / time2; + if (ratio >= 1.0f) { + ratio = 0.9999f; + } + + width = p->width + ( ratio * ( p->endwidth - p->width) ); + height = p->height + ( ratio * ( p->endheight - p->height) ); + + // if we are "inside" this sprite, don't draw + if (Distance( cg.snap->ps.origin, org ) < width/1.5) { + return; + } + + i = p->shaderAnim; + j = (int)floor(ratio * shaderAnimCounts[p->shaderAnim]); + p->pshader = shaderAnims[i][j]; + + if (p->roll) { + vectoangles( cg.refdef.viewaxis[0], rotate_ang ); + rotate_ang[ROLL] += p->roll; + AngleVectors ( rotate_ang, NULL, rr, ru); + } + + if (p->roll) { + VectorMA (org, -height, ru, point); + VectorMA (point, -width, rr, point); + } else { + VectorMA (org, -height, vup, point); + VectorMA (point, -width, vright, point); + } + VectorCopy (point, verts[0].xyz); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, 2*height, ru, point); + } else { + VectorMA (point, 2*height, vup, point); + } + VectorCopy (point, verts[1].xyz); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, 2*width, rr, point); + } else { + VectorMA (point, 2*width, vright, point); + } + VectorCopy (point, verts[2].xyz); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, -2*height, ru, point); + } else { + VectorMA (point, -2*height, vup, point); + } + VectorCopy (point, verts[3].xyz); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255; + } + // done. + + if (!p->pshader) { +// (SA) temp commented out for DM +// CG_Printf ("CG_AddParticleToScene type %d p->pshader == ZERO\n", p->type); + return; + } + + if (p->type == P_WEATHER || p->type == P_WEATHER_TURBULENT || p->type == P_WEATHER_FLURRY) + trap_R_AddPolyToScene( p->pshader, 3, TRIverts ); + else + trap_R_AddPolyToScene( p->pshader, 4, verts ); + +} + +// Ridah, made this static so it doesn't interfere with other files +static float roll = 0.0; + +/* +=============== +CG_AddParticles +=============== +*/ +void CG_AddParticles (void) +{ + cparticle_t *p, *next; + float alpha; + float time, time2; + vec3_t org; + int color; + cparticle_t *active, *tail; + int type; + vec3_t rotate_ang; + + if (!initparticles) + CG_ClearParticles (); + + VectorCopy( cg.refdef.viewaxis[0], vforward ); + VectorCopy( cg.refdef.viewaxis[1], vright ); + VectorCopy( cg.refdef.viewaxis[2], vup ); + + vectoangles( cg.refdef.viewaxis[0], rotate_ang ); + roll += ((cg.time - oldtime) * 0.1) ; + rotate_ang[ROLL] += (roll*0.9); + AngleVectors ( rotate_ang, rforward, rright, rup); + + oldtime = cg.time; + + active = NULL; + tail = NULL; + + for (p=active_particles ; p ; p=next) + { + + next = p->next; + + time = (cg.time - p->time)*0.001; + + alpha = p->alpha + time*p->alphavel; + if (alpha <= 0) + { // faded out + p->next = free_particles; + free_particles = p; + p->type = 0; + p->color = 0; + p->alpha = 0; + continue; + } + + if (p->type == P_SMOKE || p->type == P_ANIM || p->type == P_BLEED || p->type == P_SMOKE_IMPACT) + { + if (cg.time > p->endtime) + { + p->next = free_particles; + free_particles = p; + p->type = 0; + p->color = 0; + p->alpha = 0; + + continue; + } + + } + + if (p->type == P_WEATHER_FLURRY) + { + if (cg.time > p->endtime) + { + p->next = free_particles; + free_particles = p; + p->type = 0; + p->color = 0; + p->alpha = 0; + + continue; + } + } + + + if (p->type == P_FLAT_SCALEUP_FADE) + { + if (cg.time > p->endtime) + { + p->next = free_particles; + free_particles = p; + p->type = 0; + p->color = 0; + p->alpha = 0; + continue; + } + + } + + if ((p->type == P_BAT || p->type == P_SPRITE) && p->endtime < 0) { + // temporary sprite + CG_AddParticleToScene (p, p->org, alpha); + p->next = free_particles; + free_particles = p; + p->type = 0; + p->color = 0; + p->alpha = 0; + continue; + } + + p->next = NULL; + if (!tail) + active = tail = p; + else + { + tail->next = p; + tail = p; + } + + if (alpha > 1.0) + alpha = 1; + + color = p->color; + + time2 = time*time; + + org[0] = p->org[0] + p->vel[0]*time + p->accel[0]*time2; + org[1] = p->org[1] + p->vel[1]*time + p->accel[1]*time2; + org[2] = p->org[2] + p->vel[2]*time + p->accel[2]*time2; + + type = p->type; + + CG_AddParticleToScene (p, org, alpha); + } + + active_particles = active; +} + +/* +====================== +CG_AddParticles +====================== +*/ +void CG_ParticleSnowFlurry (qhandle_t pshader, centity_t *cent) +{ + cparticle_t *p; + qboolean turb = qtrue; + + if (!pshader) + CG_Printf ("CG_ParticleSnowFlurry pshader == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->color = 0; + p->alpha = 0.90f; + p->alphavel = 0; + + p->start = cent->currentState.origin2[0]; + p->end = cent->currentState.origin2[1]; + + p->endtime = cg.time + cent->currentState.time; + p->startfade = cg.time + cent->currentState.time2; + + p->pshader = pshader; + + if (rand()%100 > 90) + { + p->height = 32; + p->width = 32; + p->alpha = 0.10f; + } + else + { + p->height = 1; + p->width = 1; + } + + p->vel[2] = -20; + + p->type = P_WEATHER_FLURRY; + + if (turb) + p->vel[2] = -10; + + VectorCopy(cent->currentState.origin, p->org); + + p->org[0] = p->org[0]; + p->org[1] = p->org[1]; + p->org[2] = p->org[2]; + + p->vel[0] = p->vel[1] = 0; + + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + p->vel[0] += cent->currentState.angles[0] * 32 + (crandom() * 16); + p->vel[1] += cent->currentState.angles[1] * 32 + (crandom() * 16); + p->vel[2] += cent->currentState.angles[2]; + + if (turb) + { + p->accel[0] = crandom () * 16; + p->accel[1] = crandom () * 16; + } + +} + +void CG_ParticleSnow (qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum) +{ + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_ParticleSnow pshader == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->color = 0; + p->alpha = 0.40f; + p->alphavel = 0; + p->start = origin[2]; + p->end = origin2[2]; + p->pshader = pshader; + p->height = 1; + p->width = 1; + + p->vel[2] = -50; + + if (turb) + { + p->type = P_WEATHER_TURBULENT; + p->vel[2] = -50 * 1.3; + } + else + { + p->type = P_WEATHER; + } + + VectorCopy(origin, p->org); + + p->org[0] = p->org[0] + ( crandom() * range); + p->org[1] = p->org[1] + ( crandom() * range); + p->org[2] = p->org[2] + ( crandom() * (p->start - p->end)); + + p->vel[0] = p->vel[1] = 0; + + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + if (turb) + { + p->vel[0] = crandom() * 16; + p->vel[1] = crandom() * 16; + } + + // Rafael snow pvs check + p->snum = snum; + p->link = qtrue; + +} + +void CG_ParticleBubble (qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum) +{ + cparticle_t *p; + float randsize; + + if (!pshader) + CG_Printf ("CG_ParticleSnow pshader == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->color = 0; + p->alpha = 0.40f; + p->alphavel = 0; + p->start = origin[2]; + p->end = origin2[2]; + p->pshader = pshader; + + randsize = 1 + (crandom() * 0.5); + + p->height = randsize; + p->width = randsize; + + p->vel[2] = 50 + ( crandom() * 10 ); + + if (turb) + { + p->type = P_BUBBLE_TURBULENT; + p->vel[2] = 50 * 1.3; + } + else + { + p->type = P_BUBBLE; + } + + VectorCopy(origin, p->org); + + p->org[0] = p->org[0] + ( crandom() * range); + p->org[1] = p->org[1] + ( crandom() * range); + p->org[2] = p->org[2] + ( crandom() * (p->start - p->end)); + + p->vel[0] = p->vel[1] = 0; + + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + if (turb) + { + p->vel[0] = crandom() * 4; + p->vel[1] = crandom() * 4; + } + + // Rafael snow pvs check + p->snum = snum; + p->link = qtrue; + +} + +void CG_ParticleSmoke (qhandle_t pshader, centity_t *cent) +{ + + // using cent->density = enttime + // cent->frame = startfade + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_ParticleSmoke == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + + p->endtime = cg.time + cent->currentState.time; + p->startfade = cg.time + cent->currentState.time2; + + p->color = 0; + p->alpha = 1.0; + p->alphavel = 0; + p->start = cent->currentState.origin[2]; + p->end = cent->currentState.origin2[2]; + p->pshader = pshader; + p->rotate = qfalse; + p->height = 8; + p->width = 8; + p->endheight = 32; + p->endwidth = 32; + p->type = P_SMOKE; + + VectorCopy(cent->currentState.origin, p->org); + + p->vel[0] = p->vel[1] = 0; + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + p->vel[2] = 5; + + if (cent->currentState.frame == 1)// reverse gravity + p->vel[2] *= -1; + + p->roll = 8 + (crandom() * 4); +} + + +void CG_ParticleBulletDebris (vec3_t org, vec3_t vel, int duration) +{ + + cparticle_t *p; + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + + p->endtime = cg.time + duration; + p->startfade = cg.time + duration/2; + + p->color = EMISIVEFADE; + p->alpha = 1.0; + p->alphavel = 0; + + p->height = 0.5; + p->width = 0.5; + p->endheight = 0.5; + p->endwidth = 0.5; + + p->pshader = cgs.media.tracerShader; + + p->type = P_SMOKE; + + VectorCopy(org, p->org); + + p->vel[0] = vel[0]; + p->vel[1] = vel[1]; + p->vel[2] = vel[2]; + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + p->accel[2] = -60; + p->vel[2] += -20; + +} + +/* +====================== +CG_ParticleExplosion +====================== +*/ + +void CG_ParticleExplosion (char *animStr, vec3_t origin, vec3_t vel, int duration, int sizeStart, int sizeEnd) +{ + cparticle_t *p; + int anim; + + if (animStr < (char *)10) + CG_Error( "CG_ParticleExplosion: animStr is probably an index rather than a string" ); + + // find the animation string + for (anim=0; shaderAnimNames[anim]; anim++) { + if (!stricmp( animStr, shaderAnimNames[anim] )) + break; + } + if (!shaderAnimNames[anim]) { + CG_Error("CG_ParticleExplosion: unknown animation string: %s\n", animStr); + return; + } + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->alpha = 1.0; + p->alphavel = 0; + + if (duration < 0) { + duration *= -1; + p->roll = 0; + } else { + p->roll = crandom()*179; + } + + p->shaderAnim = anim; + + p->width = sizeStart; + p->height = sizeStart*shaderAnimSTRatio[anim]; // for sprites that are stretch in either direction + + p->endheight = sizeEnd; + p->endwidth = sizeEnd*shaderAnimSTRatio[anim]; + + p->endtime = cg.time + duration; + + p->type = P_ANIM; + + VectorCopy( origin, p->org ); + VectorCopy( vel, p->vel ); + VectorClear( p->accel ); + +} + +// Rafael Shrapnel +void CG_AddParticleShrapnel (localEntity_t *le) +{ + return; +} +// done. + +int CG_NewParticleArea (int num) +{ + // const char *str; + char *str; + char *token; + int type; + vec3_t origin, origin2; + int i; + float range = 0; + int turb; + int numparticles; + int snum; + + str = (char *) CG_ConfigString (num); + if (!str[0]) + return (0); + + // returns type 128 64 or 32 + token = COM_Parse (&str); + type = atoi (token); + + if (type == 1) + range = 128; + else if (type == 2) + range = 64; + else if (type == 3) + range = 32; + else if (type == 0) + range = 256; + else if (type == 4) + range = 8; + else if (type == 5) + range = 16; + else if (type == 6) + range = 32; + else if (type == 7) + range = 64; + + + for (i=0; i<3; i++) + { + token = COM_Parse (&str); + origin[i] = atof (token); + } + + for (i=0; i<3; i++) + { + token = COM_Parse (&str); + origin2[i] = atof (token); + } + + token = COM_Parse (&str); + numparticles = atoi (token); + + token = COM_Parse (&str); + turb = atoi (token); + + token = COM_Parse (&str); + snum = atoi (token); + + for (i=0; i= 4) + CG_ParticleBubble (cgs.media.waterBubbleShader, origin, origin2, turb, range, snum); + else + CG_ParticleSnow (cgs.media.waterBubbleShader, origin, origin2, turb, range, snum); + } + + return (1); +} + +void CG_SnowLink (centity_t *cent, qboolean particleOn) +{ + cparticle_t *p, *next; + int id; + + id = cent->currentState.frame; + + for (p=active_particles ; p ; p=next) + { + next = p->next; + + if (p->type == P_WEATHER || p->type == P_WEATHER_TURBULENT) + { + if (p->snum == id) + { + if (particleOn) + p->link = qtrue; + else + p->link = qfalse; + } + } + + } +} + +void CG_ParticleImpactSmokePuff (qhandle_t pshader, vec3_t origin) +{ + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_ParticleImpactSmokePuff pshader == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->alpha = 0.25; + p->alphavel = 0; + p->roll = crandom()*179; + + p->pshader = pshader; + + p->endtime = cg.time + 1000; + p->startfade = cg.time + 100; + + p->width = rand()%4 + 8; + p->height = rand()%4 + 8; + + p->endheight = p->height *2; + p->endwidth = p->width * 2; + + p->endtime = cg.time + 500; + + p->type = P_SMOKE_IMPACT; + + VectorCopy( origin, p->org ); + VectorSet(p->vel, 0, 0, 20); + VectorSet(p->accel, 0, 0, 20); + + p->rotate = qtrue; +} + +void CG_Particle_Bleed (qhandle_t pshader, vec3_t start, vec3_t dir, int fleshEntityNum, int duration) +{ + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_Particle_Bleed pshader == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->alpha = 1.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = pshader; + + p->endtime = cg.time + duration; + + if (fleshEntityNum) + p->startfade = cg.time; + else + p->startfade = cg.time + 100; + + p->width = 4; + p->height = 4; + + p->endheight = 4+rand()%3; + p->endwidth = p->endheight; + + p->type = P_SMOKE; + + VectorCopy( start, p->org ); + p->vel[0] = 0; + p->vel[1] = 0; + p->vel[2] = -20; + VectorClear( p->accel ); + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->color = BLOODRED; + p->alpha = 0.75; + +} + +void CG_Particle_OilParticle (qhandle_t pshader, centity_t *cent) +{ + cparticle_t *p; + + int time; + int time2; + float ratio; + + float duration = 1500; + + time = cg.time; + time2 = cg.time + cent->currentState.time; + + ratio =(float)1 - ((float)time / (float)time2); + + if (!pshader) + CG_Printf ("CG_Particle_OilParticle == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->alpha = 1.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = pshader; + + p->endtime = cg.time + duration; + + p->startfade = p->endtime; + + p->width = 1; + p->height = 3; + + p->endheight = 3; + p->endwidth = 1; + + p->type = P_SMOKE; + + VectorCopy(cent->currentState.origin, p->org ); + + p->vel[0] = (cent->currentState.origin2[0] * (16 * ratio)); + p->vel[1] = (cent->currentState.origin2[1] * (16 * ratio)); + p->vel[2] = (cent->currentState.origin2[2]); + + p->snum = 1.0f; + + VectorClear( p->accel ); + + p->accel[2] = -20; + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->alpha = 0.75; + +} + + +void CG_Particle_OilSlick (qhandle_t pshader, centity_t *cent) +{ + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_Particle_OilSlick == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + + if (cent->currentState.angles2[2]) + p->endtime = cg.time + cent->currentState.angles2[2]; + else + p->endtime = cg.time + 60000; + + p->startfade = p->endtime; + + p->alpha = 1.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = pshader; + + if (cent->currentState.angles2[0] || cent->currentState.angles2[1]) + { + p->width = cent->currentState.angles2[0]; + p->height = cent->currentState.angles2[0]; + + p->endheight = cent->currentState.angles2[1]; + p->endwidth = cent->currentState.angles2[1]; + } + else + { + p->width = 8; + p->height = 8; + + p->endheight = 16; + p->endwidth = 16; + } + + p->type = P_FLAT_SCALEUP; + + p->snum = 1.0; + + VectorCopy(cent->currentState.origin, p->org ); + + p->org[2]+= 0.55 + (crandom() * 0.5); + + p->vel[0] = 0; + p->vel[1] = 0; + p->vel[2] = 0; + VectorClear( p->accel ); + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->alpha = 0.75; + +} + +void CG_OilSlickRemove (centity_t *cent) +{ + cparticle_t *p, *next; + int id; + + id = 1.0f; + + if (!id) + CG_Printf ("CG_OilSlickRevove NULL id\n"); + + for (p=active_particles ; p ; p=next) + { + next = p->next; + + if (p->type == P_FLAT_SCALEUP) + { + if (p->snum == id) + { + p->endtime = cg.time + 100; + p->startfade = p->endtime; + p->type = P_FLAT_SCALEUP_FADE; + + } + } + + } +} + +qboolean ValidBloodPool (vec3_t start) +{ +#define EXTRUDE_DIST 0.5 + + vec3_t angles; + vec3_t right, up; + vec3_t this_pos, x_pos, center_pos, end_pos; + float x, y; + float fwidth, fheight; + trace_t trace; + vec3_t normal; + + fwidth = 16; + fheight = 16; + + VectorSet (normal, 0, 0, 1); + + vectoangles (normal, angles); + AngleVectors (angles, NULL, right, up); + + VectorMA (start, EXTRUDE_DIST, normal, center_pos); + + for (x= -fwidth/2; xendpos, start); + legit = ValidBloodPool (start); + + if (!legit) + return; + + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + + p->endtime = cg.time + 3000; + p->startfade = p->endtime; + + p->alpha = 1.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = pshader; + + rndSize = 0.4 + random()*0.6; + + p->width = 8*rndSize; + p->height = 8*rndSize; + + p->endheight = 16*rndSize; + p->endwidth = 16*rndSize; + + p->type = P_FLAT_SCALEUP; + + VectorCopy(start, p->org ); + + p->vel[0] = 0; + p->vel[1] = 0; + p->vel[2] = 0; + VectorClear( p->accel ); + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->alpha = 0.75; + + p->color = BLOODRED; +} + +#define NORMALSIZE 16 +#define LARGESIZE 32 + +void CG_ParticleBloodCloud (centity_t *cent, vec3_t origin, vec3_t dir) +{ + float length; + float dist; + float crittersize; + vec3_t angles, forward; + vec3_t point; + cparticle_t *p; + int i; + + dist = 0; + + length = VectorLength (dir); + vectoangles (dir, angles); + AngleVectors (angles, forward, NULL, NULL); + + crittersize = LARGESIZE; + + if (length) + dist = length / crittersize; + + if (dist < 1) + dist = 1; + + VectorCopy (origin, point); + + for (i=0; inext; + p->next = active_particles; + active_particles = p; + + p->time = cg.time; + p->alpha = 1.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = cgs.media.smokePuffShader; + + p->endtime = cg.time + 350 + (crandom() * 100); + + p->startfade = cg.time; + + p->width = LARGESIZE; + p->height = LARGESIZE; + p->endheight = LARGESIZE; + p->endwidth = LARGESIZE; + + p->type = P_SMOKE; + + VectorCopy( origin, p->org ); + + p->vel[0] = 0; + p->vel[1] = 0; + p->vel[2] = -1; + + VectorClear( p->accel ); + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->color = BLOODRED; + + p->alpha = 0.75; + + } + + +} + +void CG_ParticleSparks (vec3_t org, vec3_t vel, int duration, float x, float y, float speed) +{ + cparticle_t *p; + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + + p->endtime = cg.time + duration; + p->startfade = cg.time + duration/2; + + p->color = EMISIVEFADE; + p->alpha = 0.4f; + p->alphavel = 0; + + p->height = 0.5; + p->width = 0.5; + p->endheight = 0.5; + p->endwidth = 0.5; + + p->pshader = cgs.media.tracerShader; + + p->type = P_SMOKE; + + VectorCopy(org, p->org); + + p->org[0] += (crandom() * x); + p->org[1] += (crandom() * y); + + p->vel[0] = vel[0]; + p->vel[1] = vel[1]; + p->vel[2] = vel[2]; + + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + p->vel[0] += (crandom() * 4); + p->vel[1] += (crandom() * 4); + p->vel[2] += (20 + (crandom() * 10)) * speed; + + p->accel[0] = crandom () * 4; + p->accel[1] = crandom () * 4; + +} + +void CG_ParticleDust (centity_t *cent, vec3_t origin, vec3_t dir) +{ + float length; + float dist; + float crittersize; + vec3_t angles, forward; + vec3_t point; + cparticle_t *p; + int i; + + dist = 0; + + VectorNegate (dir, dir); + length = VectorLength (dir); + vectoangles (dir, angles); + AngleVectors (angles, forward, NULL, NULL); + + crittersize = LARGESIZE; + + if (length) + dist = length / crittersize; + + if (dist < 1) + dist = 1; + + VectorCopy (origin, point); + + for (i=0; inext; + p->next = active_particles; + active_particles = p; + + p->time = cg.time; + p->alpha = 5.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = cgs.media.smokePuffShader; + + // RF, stay around for long enough to expand and dissipate naturally + if (length) + p->endtime = cg.time + 4500 + (crandom() * 3500); + else + p->endtime = cg.time + 750 + (crandom() * 500); + + p->startfade = cg.time; + + p->width = LARGESIZE; + p->height = LARGESIZE; + + // RF, expand while falling + p->endheight = LARGESIZE*3.0; + p->endwidth = LARGESIZE*3.0; + + if (!length) + { + p->width *= 0.2f; + p->height *= 0.2f; + + p->endheight = NORMALSIZE; + p->endwidth = NORMALSIZE; + } + + p->type = P_SMOKE; + + VectorCopy( point, p->org ); + + p->vel[0] = crandom()*6; + p->vel[1] = crandom()*6; + p->vel[2] = random()*20; + + // RF, add some gravity/randomness + p->accel[0] = crandom()*3; + p->accel[1] = crandom()*3; + p->accel[2] = -PARTICLE_GRAVITY*0.4; + + VectorClear( p->accel ); + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->alpha = 0.75; + + } + + +} + +void CG_ParticleMisc (qhandle_t pshader, vec3_t origin, int size, int duration, float alpha) +{ + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_ParticleImpactSmokePuff pshader == ZERO!\n"); + + if (!free_particles) + return; + + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->alpha = 1.0; + p->alphavel = 0; + p->roll = rand()%179; + + p->pshader = pshader; + + if (duration > 0) + p->endtime = cg.time + duration; + else + p->endtime = duration; + + p->startfade = cg.time; + + p->width = size; + p->height = size; + + p->endheight = size; + p->endwidth = size; + + p->type = P_SPRITE; + + VectorCopy( origin, p->org ); + + p->rotate = qfalse; +} diff --git a/code/cgame/cg_players.c b/code/cgame/cg_players.c index 2fd7b73..6987442 100755 --- a/code/cgame/cg_players.c +++ b/code/cgame/cg_players.c @@ -1,2619 +1,2619 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// cg_players.c -- handle the media and animation for player entities -#include "cg_local.h" - -char *cg_customSoundNames[MAX_CUSTOM_SOUNDS] = { - "*death1.wav", - "*death2.wav", - "*death3.wav", - "*jump1.wav", - "*pain25_1.wav", - "*pain50_1.wav", - "*pain75_1.wav", - "*pain100_1.wav", - "*falling1.wav", - "*gasp.wav", - "*drown.wav", - "*fall1.wav", - "*taunt.wav" -}; - - -/* -================ -CG_CustomSound - -================ -*/ -sfxHandle_t CG_CustomSound( int clientNum, const char *soundName ) { - clientInfo_t *ci; - int i; - - if ( soundName[0] != '*' ) { - return trap_S_RegisterSound( soundName, qfalse ); - } - - if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { - clientNum = 0; - } - ci = &cgs.clientinfo[ clientNum ]; - - for ( i = 0 ; i < MAX_CUSTOM_SOUNDS && cg_customSoundNames[i] ; i++ ) { - if ( !strcmp( soundName, cg_customSoundNames[i] ) ) { - return ci->sounds[i]; - } - } - - CG_Error( "Unknown custom sound: %s", soundName ); - return 0; -} - - - -/* -============================================================================= - -CLIENT INFO - -============================================================================= -*/ - -/* -====================== -CG_ParseAnimationFile - -Read a configuration file containing animation coutns and rates -models/players/visor/animation.cfg, etc -====================== -*/ -static qboolean CG_ParseAnimationFile( const char *filename, clientInfo_t *ci ) { - char *text_p, *prev; - int len; - int i; - char *token; - float fps; - int skip; - char text[20000]; - fileHandle_t f; - animation_t *animations; - - animations = ci->animations; - - // load the file - len = trap_FS_FOpenFile( filename, &f, FS_READ ); - if ( len <= 0 ) { - return qfalse; - } - if ( len >= sizeof( text ) - 1 ) { - CG_Printf( "File %s too long\n", filename ); - return qfalse; - } - trap_FS_Read( text, len, f ); - text[len] = 0; - trap_FS_FCloseFile( f ); - - // parse the text - text_p = text; - skip = 0; // quite the compiler warning - - ci->footsteps = FOOTSTEP_NORMAL; - VectorClear( ci->headOffset ); - ci->gender = GENDER_MALE; - ci->fixedlegs = qfalse; - ci->fixedtorso = qfalse; - - // read optional parameters - while ( 1 ) { - prev = text_p; // so we can unget - token = COM_Parse( &text_p ); - if ( !token ) { - break; - } - if ( !Q_stricmp( token, "footsteps" ) ) { - token = COM_Parse( &text_p ); - if ( !token ) { - break; - } - if ( !Q_stricmp( token, "default" ) || !Q_stricmp( token, "normal" ) ) { - ci->footsteps = FOOTSTEP_NORMAL; - } else if ( !Q_stricmp( token, "boot" ) ) { - ci->footsteps = FOOTSTEP_BOOT; - } else if ( !Q_stricmp( token, "flesh" ) ) { - ci->footsteps = FOOTSTEP_FLESH; - } else if ( !Q_stricmp( token, "mech" ) ) { - ci->footsteps = FOOTSTEP_MECH; - } else if ( !Q_stricmp( token, "energy" ) ) { - ci->footsteps = FOOTSTEP_ENERGY; - } else { - CG_Printf( "Bad footsteps parm in %s: %s\n", filename, token ); - } - continue; - } else if ( !Q_stricmp( token, "headoffset" ) ) { - for ( i = 0 ; i < 3 ; i++ ) { - token = COM_Parse( &text_p ); - if ( !token ) { - break; - } - ci->headOffset[i] = atof( token ); - } - continue; - } else if ( !Q_stricmp( token, "sex" ) ) { - token = COM_Parse( &text_p ); - if ( !token ) { - break; - } - if ( token[0] == 'f' || token[0] == 'F' ) { - ci->gender = GENDER_FEMALE; - } else if ( token[0] == 'n' || token[0] == 'N' ) { - ci->gender = GENDER_NEUTER; - } else { - ci->gender = GENDER_MALE; - } - continue; - } else if ( !Q_stricmp( token, "fixedlegs" ) ) { - ci->fixedlegs = qtrue; - continue; - } else if ( !Q_stricmp( token, "fixedtorso" ) ) { - ci->fixedtorso = qtrue; - 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( &text_p ); - if ( !*token ) { - if( i >= TORSO_GETFLAG && i <= TORSO_NEGATIVE ) { - animations[i].firstFrame = animations[TORSO_GESTURE].firstFrame; - animations[i].frameLerp = animations[TORSO_GESTURE].frameLerp; - animations[i].initialLerp = animations[TORSO_GESTURE].initialLerp; - animations[i].loopFrames = animations[TORSO_GESTURE].loopFrames; - animations[i].numFrames = animations[TORSO_GESTURE].numFrames; - animations[i].reversed = qfalse; - animations[i].flipflop = qfalse; - continue; - } - break; - } - animations[i].firstFrame = atoi( token ); - // leg only frames are adjusted to not count the upper body only frames - if ( i == LEGS_WALKCR ) { - skip = animations[LEGS_WALKCR].firstFrame - animations[TORSO_GESTURE].firstFrame; - } - if ( i >= LEGS_WALKCR && i0) { - return qtrue; - } - return qfalse; -} - -/* -========================== -CG_FindClientModelFile -========================== -*/ -static qboolean CG_FindClientModelFile( char *filename, int length, clientInfo_t *ci, const char *teamName, const char *modelName, const char *skinName, const char *base, const char *ext ) { - char *team, *charactersFolder; - int i; - - if ( cgs.gametype >= GT_TEAM ) { - switch ( ci->team ) { - case TEAM_BLUE: { - team = "blue"; - break; - } - default: { - team = "red"; - break; - } - } - } - else { - team = "default"; - } - charactersFolder = ""; - while(1) { - for ( i = 0; i < 2; i++ ) { - if ( i == 0 && teamName && *teamName ) { - // "models/players/characters/james/stroggs/lower_lily_red.skin" - Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s_%s.%s", charactersFolder, modelName, teamName, base, skinName, team, ext ); - } - else { - // "models/players/characters/james/lower_lily_red.skin" - Com_sprintf( filename, length, "models/players/%s%s/%s_%s_%s.%s", charactersFolder, modelName, base, skinName, team, ext ); - } - if ( CG_FileExists( filename ) ) { - return qtrue; - } - if ( cgs.gametype >= GT_TEAM ) { - if ( i == 0 && teamName && *teamName ) { - // "models/players/characters/james/stroggs/lower_red.skin" - Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", charactersFolder, modelName, teamName, base, team, ext ); - } - else { - // "models/players/characters/james/lower_red.skin" - Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", charactersFolder, modelName, base, team, ext ); - } - } - else { - if ( i == 0 && teamName && *teamName ) { - // "models/players/characters/james/stroggs/lower_lily.skin" - Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", charactersFolder, modelName, teamName, base, skinName, ext ); - } - else { - // "models/players/characters/james/lower_lily.skin" - Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", charactersFolder, modelName, base, skinName, ext ); - } - } - if ( CG_FileExists( filename ) ) { - return qtrue; - } - if ( !teamName || !*teamName ) { - break; - } - } - // if tried the heads folder first - if ( charactersFolder[0] ) { - break; - } - charactersFolder = "characters/"; - } - - return qfalse; -} - -/* -========================== -CG_FindClientHeadFile -========================== -*/ -static qboolean CG_FindClientHeadFile( char *filename, int length, clientInfo_t *ci, const char *teamName, const char *headModelName, const char *headSkinName, const char *base, const char *ext ) { - char *team, *headsFolder; - int i; - - if ( cgs.gametype >= GT_TEAM ) { - switch ( ci->team ) { - case TEAM_BLUE: { - team = "blue"; - break; - } - default: { - team = "red"; - break; - } - } - } - else { - 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 ( CG_FileExists( filename ) ) { - return qtrue; - } - if ( cgs.gametype >= GT_TEAM ) { - if ( i == 0 && teamName && *teamName ) { - Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", headsFolder, headModelName, teamName, base, team, ext ); - } - else { - Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", headsFolder, headModelName, base, team, ext ); - } - } - else { - 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 ( CG_FileExists( filename ) ) { - return qtrue; - } - if ( !teamName || !*teamName ) { - break; - } - } - // if tried the heads folder first - if ( headsFolder[0] ) { - break; - } - headsFolder = "heads/"; - } - - return qfalse; -} - -/* -========================== -CG_RegisterClientSkin -========================== -*/ -static qboolean CG_RegisterClientSkin( clientInfo_t *ci, const char *teamName, const char *modelName, const char *skinName, const char *headModelName, const char *headSkinName ) { - char filename[MAX_QPATH]; - - /* - Com_sprintf( filename, sizeof( filename ), "models/players/%s/%slower_%s.skin", modelName, teamName, skinName ); - ci->legsSkin = trap_R_RegisterSkin( filename ); - if (!ci->legsSkin) { - Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/%slower_%s.skin", modelName, teamName, skinName ); - ci->legsSkin = trap_R_RegisterSkin( filename ); - if (!ci->legsSkin) { - Com_Printf( "Leg skin load failure: %s\n", filename ); - } - } - - - Com_sprintf( filename, sizeof( filename ), "models/players/%s/%supper_%s.skin", modelName, teamName, skinName ); - ci->torsoSkin = trap_R_RegisterSkin( filename ); - if (!ci->torsoSkin) { - Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/%supper_%s.skin", modelName, teamName, skinName ); - ci->torsoSkin = trap_R_RegisterSkin( filename ); - if (!ci->torsoSkin) { - Com_Printf( "Torso skin load failure: %s\n", filename ); - } - } - */ - if ( CG_FindClientModelFile( filename, sizeof(filename), ci, teamName, modelName, skinName, "lower", "skin" ) ) { - ci->legsSkin = trap_R_RegisterSkin( filename ); - } - if (!ci->legsSkin) { - Com_Printf( "Leg skin load failure: %s\n", filename ); - } - - if ( CG_FindClientModelFile( filename, sizeof(filename), ci, teamName, modelName, skinName, "upper", "skin" ) ) { - ci->torsoSkin = trap_R_RegisterSkin( filename ); - } - if (!ci->torsoSkin) { - Com_Printf( "Torso skin load failure: %s\n", filename ); - } - - if ( CG_FindClientHeadFile( filename, sizeof(filename), ci, teamName, headModelName, headSkinName, "head", "skin" ) ) { - ci->headSkin = trap_R_RegisterSkin( filename ); - } - if (!ci->headSkin) { - Com_Printf( "Head skin load failure: %s\n", filename ); - } - - // if any skins failed to load - if ( !ci->legsSkin || !ci->torsoSkin || !ci->headSkin ) { - return qfalse; - } - return qtrue; -} - -/* -========================== -CG_RegisterClientModelname -========================== -*/ -static qboolean CG_RegisterClientModelname( clientInfo_t *ci, const char *modelName, const char *skinName, const char *headModelName, const char *headSkinName, const char *teamName ) { - char filename[MAX_QPATH*2]; - const char *headName; - char newTeamName[MAX_QPATH*2]; - - if ( headModelName[0] == '\0' ) { - headName = modelName; - } - else { - headName = headModelName; - } - Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower.md3", modelName ); - ci->legsModel = trap_R_RegisterModel( filename ); - if ( !ci->legsModel ) { - Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/lower.md3", modelName ); - ci->legsModel = trap_R_RegisterModel( filename ); - if ( !ci->legsModel ) { - Com_Printf( "Failed to load model file %s\n", filename ); - return qfalse; - } - } - - Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper.md3", modelName ); - ci->torsoModel = trap_R_RegisterModel( filename ); - if ( !ci->torsoModel ) { - Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/upper.md3", modelName ); - ci->torsoModel = trap_R_RegisterModel( filename ); - if ( !ci->torsoModel ) { - Com_Printf( "Failed to load model file %s\n", filename ); - return qfalse; - } - } - - if( headName[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", headName ); - } - ci->headModel = trap_R_RegisterModel( filename ); - // if the head model could not be found and we didn't load from the heads folder try to load from there - if ( !ci->headModel && headName[0] != '*' ) { - Com_sprintf( filename, sizeof( filename ), "models/players/heads/%s/%s.md3", headModelName, headModelName ); - ci->headModel = trap_R_RegisterModel( filename ); - } - if ( !ci->headModel ) { - Com_Printf( "Failed to load model file %s\n", filename ); - return qfalse; - } - - // if any skins failed to load, return failure - if ( !CG_RegisterClientSkin( ci, teamName, modelName, skinName, headName, headSkinName ) ) { - if ( teamName && *teamName) { - Com_Printf( "Failed to load skin file: %s : %s : %s, %s : %s\n", teamName, modelName, skinName, headName, headSkinName ); - if( ci->team == TEAM_BLUE ) { - Com_sprintf(newTeamName, sizeof(newTeamName), "%s/", DEFAULT_BLUETEAM_NAME); - } - else { - Com_sprintf(newTeamName, sizeof(newTeamName), "%s/", DEFAULT_REDTEAM_NAME); - } - if ( !CG_RegisterClientSkin( ci, newTeamName, modelName, skinName, headName, headSkinName ) ) { - Com_Printf( "Failed to load skin file: %s : %s : %s, %s : %s\n", newTeamName, modelName, skinName, headName, headSkinName ); - return qfalse; - } - } else { - Com_Printf( "Failed to load skin file: %s : %s, %s : %s\n", modelName, skinName, headName, headSkinName ); - return qfalse; - } - } - - // load the animations - Com_sprintf( filename, sizeof( filename ), "models/players/%s/animation.cfg", modelName ); - if ( !CG_ParseAnimationFile( filename, ci ) ) { - Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/animation.cfg", modelName ); - if ( !CG_ParseAnimationFile( filename, ci ) ) { - Com_Printf( "Failed to load animation file %s\n", filename ); - return qfalse; - } - } - - if ( CG_FindClientHeadFile( filename, sizeof(filename), ci, teamName, headName, headSkinName, "icon", "skin" ) ) { - ci->modelIcon = trap_R_RegisterShaderNoMip( filename ); - } - else if ( CG_FindClientHeadFile( filename, sizeof(filename), ci, teamName, headName, headSkinName, "icon", "tga" ) ) { - ci->modelIcon = trap_R_RegisterShaderNoMip( filename ); - } - - if ( !ci->modelIcon ) { - return qfalse; - } - - return qtrue; -} - -/* -==================== -CG_ColorFromString -==================== -*/ -static void CG_ColorFromString( const char *v, vec3_t color ) { - int val; - - VectorClear( color ); - - val = atoi( v ); - - if ( val < 1 || val > 7 ) { - VectorSet( color, 1, 1, 1 ); - return; - } - - if ( val & 1 ) { - color[2] = 1.0f; - } - if ( val & 2 ) { - color[1] = 1.0f; - } - if ( val & 4 ) { - color[0] = 1.0f; - } -} - -/* -=================== -CG_LoadClientInfo - -Load it now, taking the disk hits. -This will usually be deferred to a safe time -=================== -*/ -static void CG_LoadClientInfo( clientInfo_t *ci ) { - const char *dir, *fallback; - int i, modelloaded; - const char *s; - int clientNum; - char teamname[MAX_QPATH]; - - teamname[0] = 0; -#ifdef MISSIONPACK - if( cgs.gametype >= GT_TEAM) { - if( ci->team == TEAM_BLUE ) { - Q_strncpyz(teamname, cg_blueTeamName.string, sizeof(teamname) ); - } else { - Q_strncpyz(teamname, cg_redTeamName.string, sizeof(teamname) ); - } - } - if( teamname[0] ) { - strcat( teamname, "/" ); - } -#endif - modelloaded = qtrue; - if ( !CG_RegisterClientModelname( ci, ci->modelName, ci->skinName, ci->headModelName, ci->headSkinName, teamname ) ) { - if ( cg_buildScript.integer ) { - CG_Error( "CG_RegisterClientModelname( %s, %s, %s, %s %s ) failed", ci->modelName, ci->skinName, ci->headModelName, ci->headSkinName, teamname ); - } - - // fall back to default team name - if( cgs.gametype >= GT_TEAM) { - // keep skin name - if( ci->team == TEAM_BLUE ) { - Q_strncpyz(teamname, DEFAULT_BLUETEAM_NAME, sizeof(teamname) ); - } else { - Q_strncpyz(teamname, DEFAULT_REDTEAM_NAME, sizeof(teamname) ); - } - if ( !CG_RegisterClientModelname( ci, DEFAULT_TEAM_MODEL, ci->skinName, DEFAULT_TEAM_HEAD, ci->skinName, teamname ) ) { - CG_Error( "DEFAULT_TEAM_MODEL / skin (%s/%s) failed to register", DEFAULT_TEAM_MODEL, ci->skinName ); - } - } else { - if ( !CG_RegisterClientModelname( ci, DEFAULT_MODEL, "default", DEFAULT_MODEL, "default", teamname ) ) { - CG_Error( "DEFAULT_MODEL (%s) failed to register", DEFAULT_MODEL ); - } - } - modelloaded = qfalse; - } - - ci->newAnims = qfalse; - if ( ci->torsoModel ) { - orientation_t tag; - // if the torso model has the "tag_flag" - if ( trap_R_LerpTag( &tag, ci->torsoModel, 0, 0, 1, "tag_flag" ) ) { - ci->newAnims = qtrue; - } - } - - // sounds - dir = ci->modelName; - fallback = (cgs.gametype >= GT_TEAM) ? DEFAULT_TEAM_MODEL : DEFAULT_MODEL; - - for ( i = 0 ; i < MAX_CUSTOM_SOUNDS ; i++ ) { - s = cg_customSoundNames[i]; - if ( !s ) { - break; - } - ci->sounds[i] = 0; - // if the model didn't load use the sounds of the default model - if (modelloaded) { - ci->sounds[i] = trap_S_RegisterSound( va("sound/player/%s/%s", dir, s + 1), qfalse ); - } - if ( !ci->sounds[i] ) { - ci->sounds[i] = trap_S_RegisterSound( va("sound/player/%s/%s", fallback, s + 1), qfalse ); - } - } - - ci->deferred = qfalse; - - // reset any existing players and bodies, because they might be in bad - // frames for this new model - clientNum = ci - cgs.clientinfo; - for ( i = 0 ; i < MAX_GENTITIES ; i++ ) { - if ( cg_entities[i].currentState.clientNum == clientNum - && cg_entities[i].currentState.eType == ET_PLAYER ) { - CG_ResetPlayerEntity( &cg_entities[i] ); - } - } -} - -/* -====================== -CG_CopyClientInfoModel -====================== -*/ -static void CG_CopyClientInfoModel( clientInfo_t *from, clientInfo_t *to ) { - VectorCopy( from->headOffset, to->headOffset ); - to->footsteps = from->footsteps; - to->gender = from->gender; - - to->legsModel = from->legsModel; - to->legsSkin = from->legsSkin; - to->torsoModel = from->torsoModel; - to->torsoSkin = from->torsoSkin; - to->headModel = from->headModel; - to->headSkin = from->headSkin; - to->modelIcon = from->modelIcon; - - to->newAnims = from->newAnims; - - memcpy( to->animations, from->animations, sizeof( to->animations ) ); - memcpy( to->sounds, from->sounds, sizeof( to->sounds ) ); -} - -/* -====================== -CG_ScanForExistingClientInfo -====================== -*/ -static qboolean CG_ScanForExistingClientInfo( clientInfo_t *ci ) { - int i; - clientInfo_t *match; - - for ( i = 0 ; i < cgs.maxclients ; i++ ) { - match = &cgs.clientinfo[ i ]; - if ( !match->infoValid ) { - continue; - } - if ( match->deferred ) { - continue; - } - if ( !Q_stricmp( ci->modelName, match->modelName ) - && !Q_stricmp( ci->skinName, match->skinName ) - && !Q_stricmp( ci->headModelName, match->headModelName ) - && !Q_stricmp( ci->headSkinName, match->headSkinName ) - && !Q_stricmp( ci->blueTeam, match->blueTeam ) - && !Q_stricmp( ci->redTeam, match->redTeam ) - && (cgs.gametype < GT_TEAM || ci->team == match->team) ) { - // this clientinfo is identical, so use it's handles - - ci->deferred = qfalse; - - CG_CopyClientInfoModel( match, ci ); - - return qtrue; - } - } - - // nothing matches, so defer the load - return qfalse; -} - -/* -====================== -CG_SetDeferredClientInfo - -We aren't going to load it now, so grab some other -client's info to use until we have some spare time. -====================== -*/ -static void CG_SetDeferredClientInfo( clientInfo_t *ci ) { - int i; - clientInfo_t *match; - - // if someone else is already the same models and skins we - // can just load the client info - for ( i = 0 ; i < cgs.maxclients ; i++ ) { - match = &cgs.clientinfo[ i ]; - if ( !match->infoValid || match->deferred ) { - continue; - } - if ( Q_stricmp( ci->skinName, match->skinName ) || - Q_stricmp( ci->modelName, match->modelName ) || -// Q_stricmp( ci->headModelName, match->headModelName ) || -// Q_stricmp( ci->headSkinName, match->headSkinName ) || - (cgs.gametype >= GT_TEAM && ci->team != match->team) ) { - continue; - } - // just load the real info cause it uses the same models and skins - CG_LoadClientInfo( ci ); - return; - } - - // if we are in teamplay, only grab a model if the skin is correct - if ( cgs.gametype >= GT_TEAM ) { - for ( i = 0 ; i < cgs.maxclients ; i++ ) { - match = &cgs.clientinfo[ i ]; - if ( !match->infoValid || match->deferred ) { - continue; - } - if ( Q_stricmp( ci->skinName, match->skinName ) || - (cgs.gametype >= GT_TEAM && ci->team != match->team) ) { - continue; - } - ci->deferred = qtrue; - CG_CopyClientInfoModel( match, ci ); - return; - } - // load the full model, because we don't ever want to show - // an improper team skin. This will cause a hitch for the first - // player, when the second enters. Combat shouldn't be going on - // yet, so it shouldn't matter - CG_LoadClientInfo( ci ); - return; - } - - // find the first valid clientinfo and grab its stuff - for ( i = 0 ; i < cgs.maxclients ; i++ ) { - match = &cgs.clientinfo[ i ]; - if ( !match->infoValid ) { - continue; - } - - ci->deferred = qtrue; - CG_CopyClientInfoModel( match, ci ); - return; - } - - // we should never get here... - CG_Printf( "CG_SetDeferredClientInfo: no valid clients!\n" ); - - CG_LoadClientInfo( ci ); -} - - -/* -====================== -CG_NewClientInfo -====================== -*/ -void CG_NewClientInfo( int clientNum ) { - clientInfo_t *ci; - clientInfo_t newInfo; - const char *configstring; - const char *v; - char *slash; - - ci = &cgs.clientinfo[clientNum]; - - configstring = CG_ConfigString( clientNum + CS_PLAYERS ); - if ( !configstring[0] ) { - memset( ci, 0, sizeof( *ci ) ); - return; // player just left - } - - // build into a temp buffer so the defer checks can use - // the old value - memset( &newInfo, 0, sizeof( newInfo ) ); - - // isolate the player's name - v = Info_ValueForKey(configstring, "n"); - Q_strncpyz( newInfo.name, v, sizeof( newInfo.name ) ); - - // colors - v = Info_ValueForKey( configstring, "c1" ); - CG_ColorFromString( v, newInfo.color1 ); - - v = Info_ValueForKey( configstring, "c2" ); - CG_ColorFromString( v, newInfo.color2 ); - - // bot skill - v = Info_ValueForKey( configstring, "skill" ); - newInfo.botSkill = atoi( v ); - - // handicap - v = Info_ValueForKey( configstring, "hc" ); - newInfo.handicap = atoi( v ); - - // wins - v = Info_ValueForKey( configstring, "w" ); - newInfo.wins = atoi( v ); - - // losses - v = Info_ValueForKey( configstring, "l" ); - newInfo.losses = atoi( v ); - - // team - v = Info_ValueForKey( configstring, "t" ); - newInfo.team = atoi( v ); - - // team task - v = Info_ValueForKey( configstring, "tt" ); - newInfo.teamTask = atoi(v); - - // team leader - v = Info_ValueForKey( configstring, "tl" ); - newInfo.teamLeader = atoi(v); - - v = Info_ValueForKey( configstring, "g_redteam" ); - Q_strncpyz(newInfo.redTeam, v, MAX_TEAMNAME); - - v = Info_ValueForKey( configstring, "g_blueteam" ); - Q_strncpyz(newInfo.blueTeam, v, MAX_TEAMNAME); - - // model - v = Info_ValueForKey( configstring, "model" ); - if ( cg_forceModel.integer ) { - // forcemodel makes everyone use a single model - // to prevent load hitches - char modelStr[MAX_QPATH]; - char *skin; - - if( cgs.gametype >= GT_TEAM ) { - Q_strncpyz( newInfo.modelName, DEFAULT_TEAM_MODEL, sizeof( newInfo.modelName ) ); - Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) ); - } else { - trap_Cvar_VariableStringBuffer( "model", modelStr, sizeof( modelStr ) ); - if ( ( skin = strchr( modelStr, '/' ) ) == NULL) { - skin = "default"; - } else { - *skin++ = 0; - } - - Q_strncpyz( newInfo.skinName, skin, sizeof( newInfo.skinName ) ); - Q_strncpyz( newInfo.modelName, modelStr, sizeof( newInfo.modelName ) ); - } - - if ( cgs.gametype >= GT_TEAM ) { - // keep skin name - slash = strchr( v, '/' ); - if ( slash ) { - Q_strncpyz( newInfo.skinName, slash + 1, sizeof( newInfo.skinName ) ); - } - } - } else { - Q_strncpyz( newInfo.modelName, v, sizeof( newInfo.modelName ) ); - - slash = strchr( newInfo.modelName, '/' ); - if ( !slash ) { - // modelName didn not include a skin name - Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) ); - } else { - Q_strncpyz( newInfo.skinName, slash + 1, sizeof( newInfo.skinName ) ); - // truncate modelName - *slash = 0; - } - } - - // head model - v = Info_ValueForKey( configstring, "hmodel" ); - if ( cg_forceModel.integer ) { - // forcemodel makes everyone use a single model - // to prevent load hitches - char modelStr[MAX_QPATH]; - char *skin; - - if( cgs.gametype >= GT_TEAM ) { - Q_strncpyz( newInfo.headModelName, DEFAULT_TEAM_MODEL, sizeof( newInfo.headModelName ) ); - Q_strncpyz( newInfo.headSkinName, "default", sizeof( newInfo.headSkinName ) ); - } else { - trap_Cvar_VariableStringBuffer( "headmodel", modelStr, sizeof( modelStr ) ); - if ( ( skin = strchr( modelStr, '/' ) ) == NULL) { - skin = "default"; - } else { - *skin++ = 0; - } - - Q_strncpyz( newInfo.headSkinName, skin, sizeof( newInfo.headSkinName ) ); - Q_strncpyz( newInfo.headModelName, modelStr, sizeof( newInfo.headModelName ) ); - } - - if ( cgs.gametype >= GT_TEAM ) { - // keep skin name - slash = strchr( v, '/' ); - if ( slash ) { - Q_strncpyz( newInfo.headSkinName, slash + 1, sizeof( newInfo.headSkinName ) ); - } - } - } else { - Q_strncpyz( newInfo.headModelName, v, sizeof( newInfo.headModelName ) ); - - slash = strchr( newInfo.headModelName, '/' ); - if ( !slash ) { - // modelName didn not include a skin name - Q_strncpyz( newInfo.headSkinName, "default", sizeof( newInfo.headSkinName ) ); - } else { - Q_strncpyz( newInfo.headSkinName, slash + 1, sizeof( newInfo.headSkinName ) ); - // truncate modelName - *slash = 0; - } - } - - // scan for an existing clientinfo that matches this modelname - // so we can avoid loading checks if possible - if ( !CG_ScanForExistingClientInfo( &newInfo ) ) { - qboolean forceDefer; - - forceDefer = trap_MemoryRemaining() < 4000000; - - // if we are defering loads, just have it pick the first valid - if ( forceDefer || (cg_deferPlayers.integer && !cg_buildScript.integer && !cg.loading ) ) { - // keep whatever they had if it won't violate team skins - CG_SetDeferredClientInfo( &newInfo ); - // if we are low on memory, leave them with this model - if ( forceDefer ) { - CG_Printf( "Memory is low. Using deferred model.\n" ); - newInfo.deferred = qfalse; - } - } else { - CG_LoadClientInfo( &newInfo ); - } - } - - // replace whatever was there with the new one - newInfo.infoValid = qtrue; - *ci = newInfo; -} - - - -/* -====================== -CG_LoadDeferredPlayers - -Called each frame when a player is dead -and the scoreboard is up -so deferred players can be loaded -====================== -*/ -void CG_LoadDeferredPlayers( void ) { - int i; - clientInfo_t *ci; - - // scan for a deferred player to load - for ( i = 0, ci = cgs.clientinfo ; i < cgs.maxclients ; i++, ci++ ) { - if ( ci->infoValid && ci->deferred ) { - // if we are low on memory, leave it deferred - if ( trap_MemoryRemaining() < 4000000 ) { - CG_Printf( "Memory is low. Using deferred model.\n" ); - ci->deferred = qfalse; - continue; - } - CG_LoadClientInfo( ci ); -// break; - } - } -} - -/* -============================================================================= - -PLAYER ANIMATION - -============================================================================= -*/ - - -/* -=============== -CG_SetLerpFrameAnimation - -may include ANIM_TOGGLEBIT -=============== -*/ -static void CG_SetLerpFrameAnimation( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation ) { - animation_t *anim; - - lf->animationNumber = newAnimation; - newAnimation &= ~ANIM_TOGGLEBIT; - - if ( newAnimation < 0 || newAnimation >= MAX_TOTALANIMATIONS ) { - CG_Error( "Bad animation number: %i", newAnimation ); - } - - anim = &ci->animations[ newAnimation ]; - - lf->animation = anim; - lf->animationTime = lf->frameTime + anim->initialLerp; - - if ( cg_debugAnim.integer ) { - CG_Printf( "Anim: %i\n", newAnimation ); - } -} - -/* -=============== -CG_RunLerpFrame - -Sets cg.snap, cg.oldFrame, and cg.backlerp -cg.time should be between oldFrameTime and frameTime after exit -=============== -*/ -static void CG_RunLerpFrame( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation, float speedScale ) { - int f, numFrames; - animation_t *anim; - - // debugging tool to get no animations - if ( cg_animSpeed.integer == 0 ) { - lf->oldFrame = lf->frame = lf->backlerp = 0; - return; - } - - // see if the animation sequence is switching - if ( newAnimation != lf->animationNumber || !lf->animation ) { - CG_SetLerpFrameAnimation( ci, lf, newAnimation ); - } - - // if we have passed the current frame, move it to - // oldFrame and calculate a new frame - if ( cg.time >= lf->frameTime ) { - lf->oldFrame = lf->frame; - lf->oldFrameTime = lf->frameTime; - - // get the next frame based on the animation - anim = lf->animation; - if ( !anim->frameLerp ) { - return; // shouldn't happen - } - if ( cg.time < lf->animationTime ) { - lf->frameTime = lf->animationTime; // initial lerp - } else { - lf->frameTime = lf->oldFrameTime + anim->frameLerp; - } - f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp; - f *= speedScale; // adjust for haste, etc - - numFrames = anim->numFrames; - if (anim->flipflop) { - numFrames *= 2; - } - if ( f >= numFrames ) { - f -= numFrames; - if ( anim->loopFrames ) { - f %= anim->loopFrames; - f += anim->numFrames - anim->loopFrames; - } else { - f = numFrames - 1; - // the animation is stuck at the end, so it - // can immediately transition to another sequence - lf->frameTime = cg.time; - } - } - if ( anim->reversed ) { - lf->frame = anim->firstFrame + anim->numFrames - 1 - f; - } - else if (anim->flipflop && f>=anim->numFrames) { - lf->frame = anim->firstFrame + anim->numFrames - 1 - (f%anim->numFrames); - } - else { - lf->frame = anim->firstFrame + f; - } - if ( cg.time > lf->frameTime ) { - lf->frameTime = cg.time; - if ( cg_debugAnim.integer ) { - CG_Printf( "Clamp lf->frameTime\n"); - } - } - } - - if ( lf->frameTime > cg.time + 200 ) { - lf->frameTime = cg.time; - } - - if ( lf->oldFrameTime > cg.time ) { - lf->oldFrameTime = cg.time; - } - // calculate current lerp value - if ( lf->frameTime == lf->oldFrameTime ) { - lf->backlerp = 0; - } else { - lf->backlerp = 1.0 - (float)( cg.time - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime ); - } -} - - -/* -=============== -CG_ClearLerpFrame -=============== -*/ -static void CG_ClearLerpFrame( clientInfo_t *ci, lerpFrame_t *lf, int animationNumber ) { - lf->frameTime = lf->oldFrameTime = cg.time; - CG_SetLerpFrameAnimation( ci, lf, animationNumber ); - lf->oldFrame = lf->frame = lf->animation->firstFrame; -} - - -/* -=============== -CG_PlayerAnimation -=============== -*/ -static void CG_PlayerAnimation( centity_t *cent, int *legsOld, int *legs, float *legsBackLerp, - int *torsoOld, int *torso, float *torsoBackLerp ) { - clientInfo_t *ci; - int clientNum; - float speedScale; - - clientNum = cent->currentState.clientNum; - - if ( cg_noPlayerAnims.integer ) { - *legsOld = *legs = *torsoOld = *torso = 0; - return; - } - - if ( cent->currentState.powerups & ( 1 << PW_HASTE ) ) { - speedScale = 1.5; - } else { - speedScale = 1; - } - - ci = &cgs.clientinfo[ clientNum ]; - - // do the shuffle turn frames locally - if ( cent->pe.legs.yawing && ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_IDLE ) { - CG_RunLerpFrame( ci, ¢->pe.legs, LEGS_TURN, speedScale ); - } else { - CG_RunLerpFrame( ci, ¢->pe.legs, cent->currentState.legsAnim, speedScale ); - } - - *legsOld = cent->pe.legs.oldFrame; - *legs = cent->pe.legs.frame; - *legsBackLerp = cent->pe.legs.backlerp; - - CG_RunLerpFrame( ci, ¢->pe.torso, cent->currentState.torsoAnim, speedScale ); - - *torsoOld = cent->pe.torso.oldFrame; - *torso = cent->pe.torso.frame; - *torsoBackLerp = cent->pe.torso.backlerp; -} - -/* -============================================================================= - -PLAYER ANGLES - -============================================================================= -*/ - -/* -================== -CG_SwingAngles -================== -*/ -static void CG_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 = cg.frametime * scale * speed; - if ( move >= swing ) { - move = swing; - *swinging = qfalse; - } - *angle = AngleMod( *angle + move ); - } else if ( swing < 0 ) { - move = cg.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) ); - } -} - -/* -================= -CG_AddPainTwitch -================= -*/ -static void CG_AddPainTwitch( centity_t *cent, vec3_t torsoAngles ) { - int t; - float f; - - t = cg.time - cent->pe.painTime; - if ( t >= PAIN_TWITCH_TIME ) { - return; - } - - f = 1.0 - (float)t / PAIN_TWITCH_TIME; - - if ( cent->pe.painDirection ) { - torsoAngles[ROLL] += 20 * f; - } else { - torsoAngles[ROLL] -= 20 * f; - } -} - - -/* -=============== -CG_PlayerAngles - -Handles seperate torso motion - - legs pivot based on direction of movement - - head always looks exactly at cent->lerpAngles - - if motion < 20 degrees, show in head only - if < 45 degrees, also show in torso -=============== -*/ -static void CG_PlayerAngles( centity_t *cent, vec3_t legs[3], vec3_t torso[3], vec3_t head[3] ) { - vec3_t legsAngles, torsoAngles, headAngles; - float dest; - static int movementOffsets[8] = { 0, 22, 45, -22, 0, 22, -45, -22 }; - vec3_t velocity; - float speed; - int dir, clientNum; - clientInfo_t *ci; - - VectorCopy( cent->lerpAngles, headAngles ); - headAngles[YAW] = AngleMod( headAngles[YAW] ); - VectorClear( legsAngles ); - VectorClear( torsoAngles ); - - // --------- yaw ------------- - - // allow yaw to drift a bit - if ( ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != LEGS_IDLE - || ( cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ) != TORSO_STAND ) { - // if not standing still, always point all in the same direction - cent->pe.torso.yawing = qtrue; // always center - cent->pe.torso.pitching = qtrue; // always center - cent->pe.legs.yawing = qtrue; // always center - } - - // adjust legs for movement dir - if ( cent->currentState.eFlags & EF_DEAD ) { - // don't let dead bodies twitch - dir = 0; - } else { - dir = cent->currentState.angles2[YAW]; - if ( dir < 0 || dir > 7 ) { - CG_Error( "Bad player movement angle" ); - } - } - legsAngles[YAW] = headAngles[YAW] + movementOffsets[ dir ]; - torsoAngles[YAW] = headAngles[YAW] + 0.25 * movementOffsets[ dir ]; - - // torso - CG_SwingAngles( torsoAngles[YAW], 25, 90, cg_swingSpeed.value, ¢->pe.torso.yawAngle, ¢->pe.torso.yawing ); - CG_SwingAngles( legsAngles[YAW], 40, 90, cg_swingSpeed.value, ¢->pe.legs.yawAngle, ¢->pe.legs.yawing ); - - torsoAngles[YAW] = cent->pe.torso.yawAngle; - legsAngles[YAW] = cent->pe.legs.yawAngle; - - - // --------- pitch ------------- - - // only show a fraction of the pitch angle in the torso - if ( headAngles[PITCH] > 180 ) { - dest = (-360 + headAngles[PITCH]) * 0.75f; - } else { - dest = headAngles[PITCH] * 0.75f; - } - CG_SwingAngles( dest, 15, 30, 0.1f, ¢->pe.torso.pitchAngle, ¢->pe.torso.pitching ); - torsoAngles[PITCH] = cent->pe.torso.pitchAngle; - - // - clientNum = cent->currentState.clientNum; - if ( clientNum >= 0 && clientNum < MAX_CLIENTS ) { - ci = &cgs.clientinfo[ clientNum ]; - if ( ci->fixedtorso ) { - torsoAngles[PITCH] = 0.0f; - } - } - - // --------- roll ------------- - - - // lean towards the direction of travel - VectorCopy( cent->currentState.pos.trDelta, velocity ); - speed = VectorNormalize( velocity ); - if ( speed ) { - vec3_t axis[3]; - float side; - - speed *= 0.05f; - - AnglesToAxis( legsAngles, axis ); - side = speed * DotProduct( velocity, axis[1] ); - legsAngles[ROLL] -= side; - - side = speed * DotProduct( velocity, axis[0] ); - legsAngles[PITCH] += side; - } - - // - clientNum = cent->currentState.clientNum; - if ( clientNum >= 0 && clientNum < MAX_CLIENTS ) { - ci = &cgs.clientinfo[ clientNum ]; - if ( ci->fixedlegs ) { - legsAngles[YAW] = torsoAngles[YAW]; - legsAngles[PITCH] = 0.0f; - legsAngles[ROLL] = 0.0f; - } - } - - // pain twitch - CG_AddPainTwitch( cent, torsoAngles ); - - // 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 ); -} - - -//========================================================================== - -/* -=============== -CG_HasteTrail -=============== -*/ -static void CG_HasteTrail( centity_t *cent ) { - localEntity_t *smoke; - vec3_t origin; - int anim; - - if ( cent->trailTime > cg.time ) { - return; - } - anim = cent->pe.legs.animationNumber & ~ANIM_TOGGLEBIT; - if ( anim != LEGS_RUN && anim != LEGS_BACK ) { - return; - } - - cent->trailTime += 100; - if ( cent->trailTime < cg.time ) { - cent->trailTime = cg.time; - } - - VectorCopy( cent->lerpOrigin, origin ); - origin[2] -= 16; - - smoke = CG_SmokePuff( origin, vec3_origin, - 8, - 1, 1, 1, 1, - 500, - cg.time, - 0, - 0, - cgs.media.hastePuffShader ); - - // use the optimized local entity add - smoke->leType = LE_SCALE_FADE; -} - -#ifdef MISSIONPACK -/* -=============== -CG_BreathPuffs -=============== -*/ -static void CG_BreathPuffs( centity_t *cent, refEntity_t *head) { - clientInfo_t *ci; - vec3_t up, origin; - int contents; - - ci = &cgs.clientinfo[ cent->currentState.number ]; - - if (!cg_enableBreath.integer) { - return; - } - if ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson) { - return; - } - if ( cent->currentState.eFlags & EF_DEAD ) { - return; - } - contents = trap_CM_PointContents( head->origin, 0 ); - if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { - return; - } - if ( ci->breathPuffTime > cg.time ) { - return; - } - - VectorSet( up, 0, 0, 8 ); - VectorMA(head->origin, 8, head->axis[0], origin); - VectorMA(origin, -4, head->axis[2], origin); - CG_SmokePuff( origin, up, 16, 1, 1, 1, 0.66f, 1500, cg.time, cg.time + 400, LEF_PUFF_DONT_SCALE, cgs.media.shotgunSmokePuffShader ); - ci->breathPuffTime = cg.time + 2000; -} - -/* -=============== -CG_DustTrail -=============== -*/ -static void CG_DustTrail( centity_t *cent ) { - int anim; - localEntity_t *dust; - vec3_t end, vel; - trace_t tr; - - if (!cg_enableDust.integer) - return; - - if ( cent->dustTrailTime > cg.time ) { - return; - } - - anim = cent->pe.legs.animationNumber & ~ANIM_TOGGLEBIT; - if ( anim != LEGS_LANDB && anim != LEGS_LAND ) { - return; - } - - cent->dustTrailTime += 40; - if ( cent->dustTrailTime < cg.time ) { - cent->dustTrailTime = cg.time; - } - - VectorCopy(cent->currentState.pos.trBase, end); - end[2] -= 64; - CG_Trace( &tr, cent->currentState.pos.trBase, NULL, NULL, end, cent->currentState.number, MASK_PLAYERSOLID ); - - if ( !(tr.surfaceFlags & SURF_DUST) ) - return; - - VectorCopy( cent->currentState.pos.trBase, end ); - end[2] -= 16; - - VectorSet(vel, 0, 0, -30); - dust = CG_SmokePuff( end, vel, - 24, - .8f, .8f, 0.7f, 0.33f, - 500, - cg.time, - 0, - 0, - cgs.media.dustPuffShader ); -} - -#endif - -/* -=============== -CG_TrailItem -=============== -*/ -static void CG_TrailItem( centity_t *cent, qhandle_t hModel ) { - refEntity_t ent; - vec3_t angles; - vec3_t axis[3]; - - VectorCopy( cent->lerpAngles, angles ); - angles[PITCH] = 0; - angles[ROLL] = 0; - AnglesToAxis( angles, axis ); - - memset( &ent, 0, sizeof( ent ) ); - VectorMA( cent->lerpOrigin, -16, axis[0], ent.origin ); - ent.origin[2] += 16; - angles[YAW] += 90; - AnglesToAxis( angles, ent.axis ); - - ent.hModel = hModel; - trap_R_AddRefEntityToScene( &ent ); -} - - -/* -=============== -CG_PlayerFlag -=============== -*/ -static void CG_PlayerFlag( centity_t *cent, qhandle_t hSkin, refEntity_t *torso ) { - clientInfo_t *ci; - refEntity_t pole; - refEntity_t flag; - vec3_t angles, dir; - int legsAnim, flagAnim, updateangles; - float angle, d; - - // show the flag pole model - memset( &pole, 0, sizeof(pole) ); - pole.hModel = cgs.media.flagPoleModel; - VectorCopy( torso->lightingOrigin, pole.lightingOrigin ); - pole.shadowPlane = torso->shadowPlane; - pole.renderfx = torso->renderfx; - CG_PositionEntityOnTag( &pole, torso, torso->hModel, "tag_flag" ); - trap_R_AddRefEntityToScene( &pole ); - - // show the flag model - memset( &flag, 0, sizeof(flag) ); - flag.hModel = cgs.media.flagFlapModel; - flag.customSkin = hSkin; - VectorCopy( torso->lightingOrigin, flag.lightingOrigin ); - flag.shadowPlane = torso->shadowPlane; - flag.renderfx = torso->renderfx; - - VectorClear(angles); - - updateangles = qfalse; - legsAnim = cent->currentState.legsAnim & ~ANIM_TOGGLEBIT; - if( legsAnim == LEGS_IDLE || legsAnim == LEGS_IDLECR ) { - flagAnim = FLAG_STAND; - } else if ( legsAnim == LEGS_WALK || legsAnim == LEGS_WALKCR ) { - flagAnim = FLAG_STAND; - updateangles = qtrue; - } else { - flagAnim = FLAG_RUN; - updateangles = qtrue; - } - - if ( updateangles ) { - - VectorCopy( cent->currentState.pos.trDelta, dir ); - // add gravity - dir[2] += 100; - VectorNormalize( dir ); - d = DotProduct(pole.axis[2], dir); - // if there is anough movement orthogonal to the flag pole - if (fabs(d) < 0.9) { - // - d = DotProduct(pole.axis[0], dir); - if (d > 1.0f) { - d = 1.0f; - } - else if (d < -1.0f) { - d = -1.0f; - } - angle = acos(d); - - d = DotProduct(pole.axis[1], dir); - if (d < 0) { - angles[YAW] = 360 - angle * 180 / M_PI; - } - else { - angles[YAW] = angle * 180 / M_PI; - } - if (angles[YAW] < 0) - angles[YAW] += 360; - if (angles[YAW] > 360) - angles[YAW] -= 360; - - //vectoangles( cent->currentState.pos.trDelta, tmpangles ); - //angles[YAW] = tmpangles[YAW] + 45 - cent->pe.torso.yawAngle; - // change the yaw angle - CG_SwingAngles( angles[YAW], 25, 90, 0.15f, ¢->pe.flag.yawAngle, ¢->pe.flag.yawing ); - } - - /* - d = DotProduct(pole.axis[2], dir); - angle = Q_acos(d); - - d = DotProduct(pole.axis[1], dir); - if (d < 0) { - angle = 360 - angle * 180 / M_PI; - } - else { - angle = angle * 180 / M_PI; - } - if (angle > 340 && angle < 20) { - flagAnim = FLAG_RUNUP; - } - if (angle > 160 && angle < 200) { - flagAnim = FLAG_RUNDOWN; - } - */ - } - - // set the yaw angle - angles[YAW] = cent->pe.flag.yawAngle; - // lerp the flag animation frames - ci = &cgs.clientinfo[ cent->currentState.clientNum ]; - CG_RunLerpFrame( ci, ¢->pe.flag, flagAnim, 1 ); - flag.oldframe = cent->pe.flag.oldFrame; - flag.frame = cent->pe.flag.frame; - flag.backlerp = cent->pe.flag.backlerp; - - AnglesToAxis( angles, flag.axis ); - CG_PositionRotatedEntityOnTag( &flag, &pole, pole.hModel, "tag_flag" ); - - trap_R_AddRefEntityToScene( &flag ); -} - - -#ifdef MISSIONPACK // bk001204 -/* -=============== -CG_PlayerTokens -=============== -*/ -static void CG_PlayerTokens( centity_t *cent, int renderfx ) { - int tokens, i, j; - float angle; - refEntity_t ent; - vec3_t dir, origin; - skulltrail_t *trail; - trail = &cg.skulltrails[cent->currentState.number]; - tokens = cent->currentState.generic1; - if ( !tokens ) { - trail->numpositions = 0; - return; - } - - if ( tokens > MAX_SKULLTRAIL ) { - tokens = MAX_SKULLTRAIL; - } - - // add skulls if there are more than last time - for (i = 0; i < tokens - trail->numpositions; i++) { - for (j = trail->numpositions; j > 0; j--) { - VectorCopy(trail->positions[j-1], trail->positions[j]); - } - VectorCopy(cent->lerpOrigin, trail->positions[0]); - } - trail->numpositions = tokens; - - // move all the skulls along the trail - VectorCopy(cent->lerpOrigin, origin); - for (i = 0; i < trail->numpositions; i++) { - VectorSubtract(trail->positions[i], origin, dir); - if (VectorNormalize(dir) > 30) { - VectorMA(origin, 30, dir, trail->positions[i]); - } - VectorCopy(trail->positions[i], origin); - } - - memset( &ent, 0, sizeof( ent ) ); - if( cgs.clientinfo[ cent->currentState.clientNum ].team == TEAM_BLUE ) { - ent.hModel = cgs.media.redCubeModel; - } else { - ent.hModel = cgs.media.blueCubeModel; - } - ent.renderfx = renderfx; - - VectorCopy(cent->lerpOrigin, origin); - for (i = 0; i < trail->numpositions; i++) { - VectorSubtract(origin, trail->positions[i], ent.axis[0]); - ent.axis[0][2] = 0; - VectorNormalize(ent.axis[0]); - VectorSet(ent.axis[2], 0, 0, 1); - CrossProduct(ent.axis[0], ent.axis[2], ent.axis[1]); - - VectorCopy(trail->positions[i], ent.origin); - angle = (((cg.time + 500 * MAX_SKULLTRAIL - 500 * i) / 16) & 255) * (M_PI * 2) / 255; - ent.origin[2] += sin(angle) * 10; - trap_R_AddRefEntityToScene( &ent ); - VectorCopy(trail->positions[i], origin); - } -} -#endif - - -/* -=============== -CG_PlayerPowerups -=============== -*/ -static void CG_PlayerPowerups( centity_t *cent, refEntity_t *torso ) { - int powerups; - clientInfo_t *ci; - - powerups = cent->currentState.powerups; - if ( !powerups ) { - return; - } - - // quad gives a dlight - if ( powerups & ( 1 << PW_QUAD ) ) { - trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.2f, 0.2f, 1 ); - } - - // flight plays a looped sound - if ( powerups & ( 1 << PW_FLIGHT ) ) { - trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.media.flightSound ); - } - - ci = &cgs.clientinfo[ cent->currentState.clientNum ]; - // redflag - if ( powerups & ( 1 << PW_REDFLAG ) ) { - if (ci->newAnims) { - CG_PlayerFlag( cent, cgs.media.redFlagFlapSkin, torso ); - } - else { - CG_TrailItem( cent, cgs.media.redFlagModel ); - } - trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 1.0, 0.2f, 0.2f ); - } - - // blueflag - if ( powerups & ( 1 << PW_BLUEFLAG ) ) { - if (ci->newAnims){ - CG_PlayerFlag( cent, cgs.media.blueFlagFlapSkin, torso ); - } - else { - CG_TrailItem( cent, cgs.media.blueFlagModel ); - } - trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.2f, 0.2f, 1.0 ); - } - - // neutralflag - if ( powerups & ( 1 << PW_NEUTRALFLAG ) ) { - if (ci->newAnims) { - CG_PlayerFlag( cent, cgs.media.neutralFlagFlapSkin, torso ); - } - else { - CG_TrailItem( cent, cgs.media.neutralFlagModel ); - } - trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 1.0, 1.0, 1.0 ); - } - - // haste leaves smoke trails - if ( powerups & ( 1 << PW_HASTE ) ) { - CG_HasteTrail( cent ); - } -} - - -/* -=============== -CG_PlayerFloatSprite - -Float a sprite over the player's head -=============== -*/ -static void CG_PlayerFloatSprite( centity_t *cent, qhandle_t shader ) { - int rf; - refEntity_t ent; - - if ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson ) { - rf = RF_THIRD_PERSON; // only show in mirrors - } else { - rf = 0; - } - - memset( &ent, 0, sizeof( ent ) ); - VectorCopy( cent->lerpOrigin, ent.origin ); - ent.origin[2] += 48; - ent.reType = RT_SPRITE; - ent.customShader = shader; - ent.radius = 10; - ent.renderfx = rf; - ent.shaderRGBA[0] = 255; - ent.shaderRGBA[1] = 255; - ent.shaderRGBA[2] = 255; - ent.shaderRGBA[3] = 255; - trap_R_AddRefEntityToScene( &ent ); -} - - - -/* -=============== -CG_PlayerSprites - -Float sprites over the player's head -=============== -*/ -static void CG_PlayerSprites( centity_t *cent ) { - int team; - - if ( cent->currentState.eFlags & EF_CONNECTION ) { - CG_PlayerFloatSprite( cent, cgs.media.connectionShader ); - return; - } - - if ( cent->currentState.eFlags & EF_TALK ) { - CG_PlayerFloatSprite( cent, cgs.media.balloonShader ); - return; - } - - if ( cent->currentState.eFlags & EF_AWARD_IMPRESSIVE ) { - CG_PlayerFloatSprite( cent, cgs.media.medalImpressive ); - return; - } - - if ( cent->currentState.eFlags & EF_AWARD_EXCELLENT ) { - CG_PlayerFloatSprite( cent, cgs.media.medalExcellent ); - return; - } - - if ( cent->currentState.eFlags & EF_AWARD_GAUNTLET ) { - CG_PlayerFloatSprite( cent, cgs.media.medalGauntlet ); - return; - } - - if ( cent->currentState.eFlags & EF_AWARD_DEFEND ) { - CG_PlayerFloatSprite( cent, cgs.media.medalDefend ); - return; - } - - if ( cent->currentState.eFlags & EF_AWARD_ASSIST ) { - CG_PlayerFloatSprite( cent, cgs.media.medalAssist ); - return; - } - - if ( cent->currentState.eFlags & EF_AWARD_CAP ) { - CG_PlayerFloatSprite( cent, cgs.media.medalCapture ); - return; - } - - team = cgs.clientinfo[ cent->currentState.clientNum ].team; - if ( !(cent->currentState.eFlags & EF_DEAD) && - cg.snap->ps.persistant[PERS_TEAM] == team && - cgs.gametype >= GT_TEAM) { - if (cg_drawFriend.integer) { - CG_PlayerFloatSprite( cent, cgs.media.friendShader ); - } - return; - } -} - -/* -=============== -CG_PlayerShadow - -Returns the Z component of the surface being shadowed - - should it return a full plane instead of a Z? -=============== -*/ -#define SHADOW_DISTANCE 128 -static qboolean CG_PlayerShadow( centity_t *cent, float *shadowPlane ) { - vec3_t end, mins = {-15, -15, 0}, maxs = {15, 15, 2}; - trace_t trace; - float alpha; - - *shadowPlane = 0; - - if ( cg_shadows.integer == 0 ) { - return qfalse; - } - - // no shadows when invisible - if ( cent->currentState.powerups & ( 1 << PW_INVIS ) ) { - return qfalse; - } - - // send a trace down from the player to the ground - VectorCopy( cent->lerpOrigin, end ); - end[2] -= SHADOW_DISTANCE; - - trap_CM_BoxTrace( &trace, cent->lerpOrigin, end, mins, maxs, 0, MASK_PLAYERSOLID ); - - // no shadow if too high - if ( trace.fraction == 1.0 || trace.startsolid || trace.allsolid ) { - return qfalse; - } - - *shadowPlane = trace.endpos[2] + 1; - - if ( cg_shadows.integer != 1 ) { // no mark for stencil or projection shadows - return qtrue; - } - - // fade the shadow out with height - alpha = 1.0 - trace.fraction; - - // bk0101022 - hack / FPE - bogus planes? - //assert( DotProduct( trace.plane.normal, trace.plane.normal ) != 0.0f ) - - // add the mark as a temporary, so it goes directly to the renderer - // without taking a spot in the cg_marks array - CG_ImpactMark( cgs.media.shadowMarkShader, trace.endpos, trace.plane.normal, - cent->pe.legs.yawAngle, alpha,alpha,alpha,1, qfalse, 24, qtrue ); - - return qtrue; -} - - -/* -=============== -CG_PlayerSplash - -Draw a mark at the water surface -=============== -*/ -static void CG_PlayerSplash( centity_t *cent ) { - vec3_t start, end; - trace_t trace; - int contents; - polyVert_t verts[4]; - - if ( !cg_shadows.integer ) { - return; - } - - VectorCopy( cent->lerpOrigin, end ); - end[2] -= 24; - - // if the feet aren't in liquid, don't make a mark - // this won't handle moving water brushes, but they wouldn't draw right anyway... - contents = trap_CM_PointContents( end, 0 ); - if ( !( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) ) { - return; - } - - VectorCopy( cent->lerpOrigin, start ); - start[2] += 32; - - // if the head isn't out of liquid, don't make a mark - contents = trap_CM_PointContents( start, 0 ); - if ( contents & ( CONTENTS_SOLID | CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { - return; - } - - // trace down to find the surface - trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ); - - if ( trace.fraction == 1.0 ) { - return; - } - - // create a mark polygon - VectorCopy( trace.endpos, verts[0].xyz ); - verts[0].xyz[0] -= 32; - verts[0].xyz[1] -= 32; - verts[0].st[0] = 0; - verts[0].st[1] = 0; - verts[0].modulate[0] = 255; - verts[0].modulate[1] = 255; - verts[0].modulate[2] = 255; - verts[0].modulate[3] = 255; - - VectorCopy( trace.endpos, verts[1].xyz ); - verts[1].xyz[0] -= 32; - verts[1].xyz[1] += 32; - verts[1].st[0] = 0; - verts[1].st[1] = 1; - verts[1].modulate[0] = 255; - verts[1].modulate[1] = 255; - verts[1].modulate[2] = 255; - verts[1].modulate[3] = 255; - - VectorCopy( trace.endpos, verts[2].xyz ); - verts[2].xyz[0] += 32; - verts[2].xyz[1] += 32; - verts[2].st[0] = 1; - verts[2].st[1] = 1; - verts[2].modulate[0] = 255; - verts[2].modulate[1] = 255; - verts[2].modulate[2] = 255; - verts[2].modulate[3] = 255; - - VectorCopy( trace.endpos, verts[3].xyz ); - verts[3].xyz[0] += 32; - verts[3].xyz[1] -= 32; - verts[3].st[0] = 1; - verts[3].st[1] = 0; - verts[3].modulate[0] = 255; - verts[3].modulate[1] = 255; - verts[3].modulate[2] = 255; - verts[3].modulate[3] = 255; - - trap_R_AddPolyToScene( cgs.media.wakeMarkShader, 4, verts ); -} - - - -/* -=============== -CG_AddRefEntityWithPowerups - -Adds a piece with modifications or duplications for powerups -Also called by CG_Missile for quad rockets, but nobody can tell... -=============== -*/ -void CG_AddRefEntityWithPowerups( refEntity_t *ent, entityState_t *state, int team ) { - - if ( state->powerups & ( 1 << PW_INVIS ) ) { - ent->customShader = cgs.media.invisShader; - trap_R_AddRefEntityToScene( ent ); - } else { - /* - if ( state->eFlags & EF_KAMIKAZE ) { - if (team == TEAM_BLUE) - ent->customShader = cgs.media.blueKamikazeShader; - else - ent->customShader = cgs.media.redKamikazeShader; - trap_R_AddRefEntityToScene( ent ); - } - else {*/ - trap_R_AddRefEntityToScene( ent ); - //} - - if ( state->powerups & ( 1 << PW_QUAD ) ) - { - if (team == TEAM_RED) - ent->customShader = cgs.media.redQuadShader; - else - ent->customShader = cgs.media.quadShader; - trap_R_AddRefEntityToScene( ent ); - } - if ( state->powerups & ( 1 << PW_REGEN ) ) { - if ( ( ( cg.time / 100 ) % 10 ) == 1 ) { - ent->customShader = cgs.media.regenShader; - trap_R_AddRefEntityToScene( ent ); - } - } - if ( state->powerups & ( 1 << PW_BATTLESUIT ) ) { - ent->customShader = cgs.media.battleSuitShader; - trap_R_AddRefEntityToScene( ent ); - } - } -} - -/* -================= -CG_LightVerts -================= -*/ -int CG_LightVerts( vec3_t normal, int numVerts, polyVert_t *verts ) -{ - int i, j; - float incoming; - vec3_t ambientLight; - vec3_t lightDir; - vec3_t directedLight; - - trap_R_LightForPoint( verts[0].xyz, ambientLight, directedLight, lightDir ); - - for (i = 0; i < numVerts; i++) { - incoming = DotProduct (normal, lightDir); - if ( incoming <= 0 ) { - verts[i].modulate[0] = ambientLight[0]; - verts[i].modulate[1] = ambientLight[1]; - verts[i].modulate[2] = ambientLight[2]; - verts[i].modulate[3] = 255; - continue; - } - j = ( ambientLight[0] + incoming * directedLight[0] ); - if ( j > 255 ) { - j = 255; - } - verts[i].modulate[0] = j; - - j = ( ambientLight[1] + incoming * directedLight[1] ); - if ( j > 255 ) { - j = 255; - } - verts[i].modulate[1] = j; - - j = ( ambientLight[2] + incoming * directedLight[2] ); - if ( j > 255 ) { - j = 255; - } - verts[i].modulate[2] = j; - - verts[i].modulate[3] = 255; - } - return qtrue; -} - -/* -=============== -CG_Player -=============== -*/ -void CG_Player( centity_t *cent ) { - clientInfo_t *ci; - refEntity_t legs; - refEntity_t torso; - refEntity_t head; - int clientNum; - int renderfx; - qboolean shadow; - float shadowPlane; -#ifdef MISSIONPACK - refEntity_t skull; - refEntity_t powerup; - int t; - float c; - float angle; - vec3_t dir, angles; -#endif - - // the client number is stored in clientNum. It can't be derived - // from the entity number, because a single client may have - // multiple corpses on the level using the same clientinfo - clientNum = cent->currentState.clientNum; - if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { - CG_Error( "Bad clientNum on player entity"); - } - ci = &cgs.clientinfo[ clientNum ]; - - // it is possible to see corpses from disconnected players that may - // not have valid clientinfo - if ( !ci->infoValid ) { - return; - } - - // get the player model information - renderfx = 0; - if ( cent->currentState.number == cg.snap->ps.clientNum) { - if (!cg.renderingThirdPerson) { - renderfx = RF_THIRD_PERSON; // only draw in mirrors - } else { - if (cg_cameraMode.integer) { - return; - } - } - } - - - memset( &legs, 0, sizeof(legs) ); - memset( &torso, 0, sizeof(torso) ); - memset( &head, 0, sizeof(head) ); - - // get the rotation information - CG_PlayerAngles( cent, legs.axis, torso.axis, head.axis ); - - // get the animation state (after rotation, to allow feet shuffle) - CG_PlayerAnimation( cent, &legs.oldframe, &legs.frame, &legs.backlerp, - &torso.oldframe, &torso.frame, &torso.backlerp ); - - // add the talk baloon or disconnect icon - CG_PlayerSprites( cent ); - - // add the shadow - shadow = CG_PlayerShadow( cent, &shadowPlane ); - - // add a water splash if partially in and out of water - CG_PlayerSplash( cent ); - - if ( cg_shadows.integer == 3 && shadow ) { - renderfx |= RF_SHADOW_PLANE; - } - renderfx |= RF_LIGHTING_ORIGIN; // use the same origin for all -#ifdef MISSIONPACK - if( cgs.gametype == GT_HARVESTER ) { - CG_PlayerTokens( cent, renderfx ); - } -#endif - // - // add the legs - // - legs.hModel = ci->legsModel; - legs.customSkin = ci->legsSkin; - - VectorCopy( cent->lerpOrigin, legs.origin ); - - VectorCopy( cent->lerpOrigin, legs.lightingOrigin ); - legs.shadowPlane = shadowPlane; - legs.renderfx = renderfx; - VectorCopy (legs.origin, legs.oldorigin); // don't positionally lerp at all - - CG_AddRefEntityWithPowerups( &legs, ¢->currentState, ci->team ); - - // if the model failed, allow the default nullmodel to be displayed - if (!legs.hModel) { - return; - } - - // - // add the torso - // - torso.hModel = ci->torsoModel; - if (!torso.hModel) { - return; - } - - torso.customSkin = ci->torsoSkin; - - VectorCopy( cent->lerpOrigin, torso.lightingOrigin ); - - CG_PositionRotatedEntityOnTag( &torso, &legs, ci->legsModel, "tag_torso"); - - torso.shadowPlane = shadowPlane; - torso.renderfx = renderfx; - - CG_AddRefEntityWithPowerups( &torso, ¢->currentState, ci->team ); - -#ifdef MISSIONPACK - if ( cent->currentState.eFlags & EF_KAMIKAZE ) { - - memset( &skull, 0, sizeof(skull) ); - - VectorCopy( cent->lerpOrigin, skull.lightingOrigin ); - skull.shadowPlane = shadowPlane; - skull.renderfx = renderfx; - - if ( cent->currentState.eFlags & EF_DEAD ) { - // one skull bobbing above the dead body - angle = ((cg.time / 7) & 255) * (M_PI * 2) / 255; - if (angle > M_PI * 2) - angle -= (float)M_PI * 2; - dir[0] = sin(angle) * 20; - dir[1] = cos(angle) * 20; - angle = ((cg.time / 4) & 255) * (M_PI * 2) / 255; - dir[2] = 15 + sin(angle) * 8; - VectorAdd(torso.origin, dir, skull.origin); - - dir[2] = 0; - VectorCopy(dir, skull.axis[1]); - VectorNormalize(skull.axis[1]); - VectorSet(skull.axis[2], 0, 0, 1); - CrossProduct(skull.axis[1], skull.axis[2], skull.axis[0]); - - skull.hModel = cgs.media.kamikazeHeadModel; - trap_R_AddRefEntityToScene( &skull ); - skull.hModel = cgs.media.kamikazeHeadTrail; - trap_R_AddRefEntityToScene( &skull ); - } - else { - // three skulls spinning around the player - angle = ((cg.time / 4) & 255) * (M_PI * 2) / 255; - dir[0] = cos(angle) * 20; - dir[1] = sin(angle) * 20; - dir[2] = cos(angle) * 20; - VectorAdd(torso.origin, dir, skull.origin); - - angles[0] = sin(angle) * 30; - angles[1] = (angle * 180 / M_PI) + 90; - if (angles[1] > 360) - angles[1] -= 360; - angles[2] = 0; - AnglesToAxis( angles, skull.axis ); - - /* - dir[2] = 0; - VectorInverse(dir); - VectorCopy(dir, skull.axis[1]); - VectorNormalize(skull.axis[1]); - VectorSet(skull.axis[2], 0, 0, 1); - CrossProduct(skull.axis[1], skull.axis[2], skull.axis[0]); - */ - - skull.hModel = cgs.media.kamikazeHeadModel; - trap_R_AddRefEntityToScene( &skull ); - // flip the trail because this skull is spinning in the other direction - VectorInverse(skull.axis[1]); - skull.hModel = cgs.media.kamikazeHeadTrail; - trap_R_AddRefEntityToScene( &skull ); - - angle = ((cg.time / 4) & 255) * (M_PI * 2) / 255 + M_PI; - if (angle > M_PI * 2) - angle -= (float)M_PI * 2; - dir[0] = sin(angle) * 20; - dir[1] = cos(angle) * 20; - dir[2] = cos(angle) * 20; - VectorAdd(torso.origin, dir, skull.origin); - - angles[0] = cos(angle - 0.5 * M_PI) * 30; - angles[1] = 360 - (angle * 180 / M_PI); - if (angles[1] > 360) - angles[1] -= 360; - angles[2] = 0; - AnglesToAxis( angles, skull.axis ); - - /* - dir[2] = 0; - VectorCopy(dir, skull.axis[1]); - VectorNormalize(skull.axis[1]); - VectorSet(skull.axis[2], 0, 0, 1); - CrossProduct(skull.axis[1], skull.axis[2], skull.axis[0]); - */ - - skull.hModel = cgs.media.kamikazeHeadModel; - trap_R_AddRefEntityToScene( &skull ); - skull.hModel = cgs.media.kamikazeHeadTrail; - trap_R_AddRefEntityToScene( &skull ); - - angle = ((cg.time / 3) & 255) * (M_PI * 2) / 255 + 0.5 * M_PI; - if (angle > M_PI * 2) - angle -= (float)M_PI * 2; - dir[0] = sin(angle) * 20; - dir[1] = cos(angle) * 20; - dir[2] = 0; - VectorAdd(torso.origin, dir, skull.origin); - - VectorCopy(dir, skull.axis[1]); - VectorNormalize(skull.axis[1]); - VectorSet(skull.axis[2], 0, 0, 1); - CrossProduct(skull.axis[1], skull.axis[2], skull.axis[0]); - - skull.hModel = cgs.media.kamikazeHeadModel; - trap_R_AddRefEntityToScene( &skull ); - skull.hModel = cgs.media.kamikazeHeadTrail; - trap_R_AddRefEntityToScene( &skull ); - } - } - - if ( cent->currentState.powerups & ( 1 << PW_GUARD ) ) { - memcpy(&powerup, &torso, sizeof(torso)); - powerup.hModel = cgs.media.guardPowerupModel; - powerup.frame = 0; - powerup.oldframe = 0; - powerup.customSkin = 0; - trap_R_AddRefEntityToScene( &powerup ); - } - if ( cent->currentState.powerups & ( 1 << PW_SCOUT ) ) { - memcpy(&powerup, &torso, sizeof(torso)); - powerup.hModel = cgs.media.scoutPowerupModel; - powerup.frame = 0; - powerup.oldframe = 0; - powerup.customSkin = 0; - trap_R_AddRefEntityToScene( &powerup ); - } - if ( cent->currentState.powerups & ( 1 << PW_DOUBLER ) ) { - memcpy(&powerup, &torso, sizeof(torso)); - powerup.hModel = cgs.media.doublerPowerupModel; - powerup.frame = 0; - powerup.oldframe = 0; - powerup.customSkin = 0; - trap_R_AddRefEntityToScene( &powerup ); - } - if ( cent->currentState.powerups & ( 1 << PW_AMMOREGEN ) ) { - memcpy(&powerup, &torso, sizeof(torso)); - powerup.hModel = cgs.media.ammoRegenPowerupModel; - powerup.frame = 0; - powerup.oldframe = 0; - powerup.customSkin = 0; - trap_R_AddRefEntityToScene( &powerup ); - } - if ( cent->currentState.powerups & ( 1 << PW_INVULNERABILITY ) ) { - if ( !ci->invulnerabilityStartTime ) { - ci->invulnerabilityStartTime = cg.time; - } - ci->invulnerabilityStopTime = cg.time; - } - else { - ci->invulnerabilityStartTime = 0; - } - if ( (cent->currentState.powerups & ( 1 << PW_INVULNERABILITY ) ) || - cg.time - ci->invulnerabilityStopTime < 250 ) { - - memcpy(&powerup, &torso, sizeof(torso)); - powerup.hModel = cgs.media.invulnerabilityPowerupModel; - powerup.customSkin = 0; - // always draw - powerup.renderfx &= ~RF_THIRD_PERSON; - VectorCopy(cent->lerpOrigin, powerup.origin); - - if ( cg.time - ci->invulnerabilityStartTime < 250 ) { - c = (float) (cg.time - ci->invulnerabilityStartTime) / 250; - } - else if (cg.time - ci->invulnerabilityStopTime < 250 ) { - c = (float) (250 - (cg.time - ci->invulnerabilityStopTime)) / 250; - } - else { - c = 1; - } - VectorSet( powerup.axis[0], c, 0, 0 ); - VectorSet( powerup.axis[1], 0, c, 0 ); - VectorSet( powerup.axis[2], 0, 0, c ); - trap_R_AddRefEntityToScene( &powerup ); - } - - t = cg.time - ci->medkitUsageTime; - if ( ci->medkitUsageTime && t < 500 ) { - memcpy(&powerup, &torso, sizeof(torso)); - powerup.hModel = cgs.media.medkitUsageModel; - powerup.customSkin = 0; - // always draw - powerup.renderfx &= ~RF_THIRD_PERSON; - VectorClear(angles); - AnglesToAxis(angles, powerup.axis); - VectorCopy(cent->lerpOrigin, powerup.origin); - powerup.origin[2] += -24 + (float) t * 80 / 500; - if ( t > 400 ) { - c = (float) (t - 1000) * 0xff / 100; - powerup.shaderRGBA[0] = 0xff - c; - powerup.shaderRGBA[1] = 0xff - c; - powerup.shaderRGBA[2] = 0xff - c; - powerup.shaderRGBA[3] = 0xff - c; - } - else { - powerup.shaderRGBA[0] = 0xff; - powerup.shaderRGBA[1] = 0xff; - powerup.shaderRGBA[2] = 0xff; - powerup.shaderRGBA[3] = 0xff; - } - trap_R_AddRefEntityToScene( &powerup ); - } -#endif // MISSIONPACK - - // - // add the head - // - head.hModel = ci->headModel; - if (!head.hModel) { - return; - } - head.customSkin = ci->headSkin; - - VectorCopy( cent->lerpOrigin, head.lightingOrigin ); - - CG_PositionRotatedEntityOnTag( &head, &torso, ci->torsoModel, "tag_head"); - - head.shadowPlane = shadowPlane; - head.renderfx = renderfx; - - CG_AddRefEntityWithPowerups( &head, ¢->currentState, ci->team ); - -#ifdef MISSIONPACK - CG_BreathPuffs(cent, &head); - - CG_DustTrail(cent); -#endif - - // - // add the gun / barrel / flash - // - CG_AddPlayerWeapon( &torso, NULL, cent, ci->team ); - - // add powerups floating behind the player - CG_PlayerPowerups( cent, &torso ); -} - - -//===================================================================== - -/* -=============== -CG_ResetPlayerEntity - -A player just came into view or teleported, so reset all animation info -=============== -*/ -void CG_ResetPlayerEntity( centity_t *cent ) { - cent->errorTime = -99999; // guarantee no error decay added - cent->extrapolated = qfalse; - - CG_ClearLerpFrame( &cgs.clientinfo[ cent->currentState.clientNum ], ¢->pe.legs, cent->currentState.legsAnim ); - CG_ClearLerpFrame( &cgs.clientinfo[ cent->currentState.clientNum ], ¢->pe.torso, cent->currentState.torsoAnim ); - - BG_EvaluateTrajectory( ¢->currentState.pos, cg.time, cent->lerpOrigin ); - BG_EvaluateTrajectory( ¢->currentState.apos, cg.time, cent->lerpAngles ); - - VectorCopy( cent->lerpOrigin, cent->rawOrigin ); - VectorCopy( cent->lerpAngles, cent->rawAngles ); - - memset( ¢->pe.legs, 0, sizeof( cent->pe.legs ) ); - cent->pe.legs.yawAngle = cent->rawAngles[YAW]; - cent->pe.legs.yawing = qfalse; - cent->pe.legs.pitchAngle = 0; - cent->pe.legs.pitching = qfalse; - - memset( ¢->pe.torso, 0, sizeof( cent->pe.legs ) ); - cent->pe.torso.yawAngle = cent->rawAngles[YAW]; - cent->pe.torso.yawing = qfalse; - cent->pe.torso.pitchAngle = cent->rawAngles[PITCH]; - cent->pe.torso.pitching = qfalse; - - if ( cg_debugPosition.integer ) { - CG_Printf("%i ResetPlayerEntity yaw=%i\n", cent->currentState.number, cent->pe.torso.yawAngle ); - } -} - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// cg_players.c -- handle the media and animation for player entities +#include "cg_local.h" + +char *cg_customSoundNames[MAX_CUSTOM_SOUNDS] = { + "*death1.wav", + "*death2.wav", + "*death3.wav", + "*jump1.wav", + "*pain25_1.wav", + "*pain50_1.wav", + "*pain75_1.wav", + "*pain100_1.wav", + "*falling1.wav", + "*gasp.wav", + "*drown.wav", + "*fall1.wav", + "*taunt.wav" +}; + + +/* +================ +CG_CustomSound + +================ +*/ +sfxHandle_t CG_CustomSound( int clientNum, const char *soundName ) { + clientInfo_t *ci; + int i; + + if ( soundName[0] != '*' ) { + return trap_S_RegisterSound( soundName, qfalse ); + } + + if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { + clientNum = 0; + } + ci = &cgs.clientinfo[ clientNum ]; + + for ( i = 0 ; i < MAX_CUSTOM_SOUNDS && cg_customSoundNames[i] ; i++ ) { + if ( !strcmp( soundName, cg_customSoundNames[i] ) ) { + return ci->sounds[i]; + } + } + + CG_Error( "Unknown custom sound: %s", soundName ); + return 0; +} + + + +/* +============================================================================= + +CLIENT INFO + +============================================================================= +*/ + +/* +====================== +CG_ParseAnimationFile + +Read a configuration file containing animation coutns and rates +models/players/visor/animation.cfg, etc +====================== +*/ +static qboolean CG_ParseAnimationFile( const char *filename, clientInfo_t *ci ) { + char *text_p, *prev; + int len; + int i; + char *token; + float fps; + int skip; + char text[20000]; + fileHandle_t f; + animation_t *animations; + + animations = ci->animations; + + // load the file + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( len <= 0 ) { + return qfalse; + } + if ( len >= sizeof( text ) - 1 ) { + CG_Printf( "File %s too long\n", filename ); + return qfalse; + } + trap_FS_Read( text, len, f ); + text[len] = 0; + trap_FS_FCloseFile( f ); + + // parse the text + text_p = text; + skip = 0; // quite the compiler warning + + ci->footsteps = FOOTSTEP_NORMAL; + VectorClear( ci->headOffset ); + ci->gender = GENDER_MALE; + ci->fixedlegs = qfalse; + ci->fixedtorso = qfalse; + + // read optional parameters + while ( 1 ) { + prev = text_p; // so we can unget + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + if ( !Q_stricmp( token, "footsteps" ) ) { + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + if ( !Q_stricmp( token, "default" ) || !Q_stricmp( token, "normal" ) ) { + ci->footsteps = FOOTSTEP_NORMAL; + } else if ( !Q_stricmp( token, "boot" ) ) { + ci->footsteps = FOOTSTEP_BOOT; + } else if ( !Q_stricmp( token, "flesh" ) ) { + ci->footsteps = FOOTSTEP_FLESH; + } else if ( !Q_stricmp( token, "mech" ) ) { + ci->footsteps = FOOTSTEP_MECH; + } else if ( !Q_stricmp( token, "energy" ) ) { + ci->footsteps = FOOTSTEP_ENERGY; + } else { + CG_Printf( "Bad footsteps parm in %s: %s\n", filename, token ); + } + continue; + } else if ( !Q_stricmp( token, "headoffset" ) ) { + for ( i = 0 ; i < 3 ; i++ ) { + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + ci->headOffset[i] = atof( token ); + } + continue; + } else if ( !Q_stricmp( token, "sex" ) ) { + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + if ( token[0] == 'f' || token[0] == 'F' ) { + ci->gender = GENDER_FEMALE; + } else if ( token[0] == 'n' || token[0] == 'N' ) { + ci->gender = GENDER_NEUTER; + } else { + ci->gender = GENDER_MALE; + } + continue; + } else if ( !Q_stricmp( token, "fixedlegs" ) ) { + ci->fixedlegs = qtrue; + continue; + } else if ( !Q_stricmp( token, "fixedtorso" ) ) { + ci->fixedtorso = qtrue; + 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( &text_p ); + if ( !*token ) { + if( i >= TORSO_GETFLAG && i <= TORSO_NEGATIVE ) { + animations[i].firstFrame = animations[TORSO_GESTURE].firstFrame; + animations[i].frameLerp = animations[TORSO_GESTURE].frameLerp; + animations[i].initialLerp = animations[TORSO_GESTURE].initialLerp; + animations[i].loopFrames = animations[TORSO_GESTURE].loopFrames; + animations[i].numFrames = animations[TORSO_GESTURE].numFrames; + animations[i].reversed = qfalse; + animations[i].flipflop = qfalse; + continue; + } + break; + } + animations[i].firstFrame = atoi( token ); + // leg only frames are adjusted to not count the upper body only frames + if ( i == LEGS_WALKCR ) { + skip = animations[LEGS_WALKCR].firstFrame - animations[TORSO_GESTURE].firstFrame; + } + if ( i >= LEGS_WALKCR && i0) { + return qtrue; + } + return qfalse; +} + +/* +========================== +CG_FindClientModelFile +========================== +*/ +static qboolean CG_FindClientModelFile( char *filename, int length, clientInfo_t *ci, const char *teamName, const char *modelName, const char *skinName, const char *base, const char *ext ) { + char *team, *charactersFolder; + int i; + + if ( cgs.gametype >= GT_TEAM ) { + switch ( ci->team ) { + case TEAM_BLUE: { + team = "blue"; + break; + } + default: { + team = "red"; + break; + } + } + } + else { + team = "default"; + } + charactersFolder = ""; + while(1) { + for ( i = 0; i < 2; i++ ) { + if ( i == 0 && teamName && *teamName ) { + // "models/players/characters/james/stroggs/lower_lily_red.skin" + Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s_%s.%s", charactersFolder, modelName, teamName, base, skinName, team, ext ); + } + else { + // "models/players/characters/james/lower_lily_red.skin" + Com_sprintf( filename, length, "models/players/%s%s/%s_%s_%s.%s", charactersFolder, modelName, base, skinName, team, ext ); + } + if ( CG_FileExists( filename ) ) { + return qtrue; + } + if ( cgs.gametype >= GT_TEAM ) { + if ( i == 0 && teamName && *teamName ) { + // "models/players/characters/james/stroggs/lower_red.skin" + Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", charactersFolder, modelName, teamName, base, team, ext ); + } + else { + // "models/players/characters/james/lower_red.skin" + Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", charactersFolder, modelName, base, team, ext ); + } + } + else { + if ( i == 0 && teamName && *teamName ) { + // "models/players/characters/james/stroggs/lower_lily.skin" + Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", charactersFolder, modelName, teamName, base, skinName, ext ); + } + else { + // "models/players/characters/james/lower_lily.skin" + Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", charactersFolder, modelName, base, skinName, ext ); + } + } + if ( CG_FileExists( filename ) ) { + return qtrue; + } + if ( !teamName || !*teamName ) { + break; + } + } + // if tried the heads folder first + if ( charactersFolder[0] ) { + break; + } + charactersFolder = "characters/"; + } + + return qfalse; +} + +/* +========================== +CG_FindClientHeadFile +========================== +*/ +static qboolean CG_FindClientHeadFile( char *filename, int length, clientInfo_t *ci, const char *teamName, const char *headModelName, const char *headSkinName, const char *base, const char *ext ) { + char *team, *headsFolder; + int i; + + if ( cgs.gametype >= GT_TEAM ) { + switch ( ci->team ) { + case TEAM_BLUE: { + team = "blue"; + break; + } + default: { + team = "red"; + break; + } + } + } + else { + 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 ( CG_FileExists( filename ) ) { + return qtrue; + } + if ( cgs.gametype >= GT_TEAM ) { + if ( i == 0 && teamName && *teamName ) { + Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", headsFolder, headModelName, teamName, base, team, ext ); + } + else { + Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", headsFolder, headModelName, base, team, ext ); + } + } + else { + 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 ( CG_FileExists( filename ) ) { + return qtrue; + } + if ( !teamName || !*teamName ) { + break; + } + } + // if tried the heads folder first + if ( headsFolder[0] ) { + break; + } + headsFolder = "heads/"; + } + + return qfalse; +} + +/* +========================== +CG_RegisterClientSkin +========================== +*/ +static qboolean CG_RegisterClientSkin( clientInfo_t *ci, const char *teamName, const char *modelName, const char *skinName, const char *headModelName, const char *headSkinName ) { + char filename[MAX_QPATH]; + + /* + Com_sprintf( filename, sizeof( filename ), "models/players/%s/%slower_%s.skin", modelName, teamName, skinName ); + ci->legsSkin = trap_R_RegisterSkin( filename ); + if (!ci->legsSkin) { + Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/%slower_%s.skin", modelName, teamName, skinName ); + ci->legsSkin = trap_R_RegisterSkin( filename ); + if (!ci->legsSkin) { + Com_Printf( "Leg skin load failure: %s\n", filename ); + } + } + + + Com_sprintf( filename, sizeof( filename ), "models/players/%s/%supper_%s.skin", modelName, teamName, skinName ); + ci->torsoSkin = trap_R_RegisterSkin( filename ); + if (!ci->torsoSkin) { + Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/%supper_%s.skin", modelName, teamName, skinName ); + ci->torsoSkin = trap_R_RegisterSkin( filename ); + if (!ci->torsoSkin) { + Com_Printf( "Torso skin load failure: %s\n", filename ); + } + } + */ + if ( CG_FindClientModelFile( filename, sizeof(filename), ci, teamName, modelName, skinName, "lower", "skin" ) ) { + ci->legsSkin = trap_R_RegisterSkin( filename ); + } + if (!ci->legsSkin) { + Com_Printf( "Leg skin load failure: %s\n", filename ); + } + + if ( CG_FindClientModelFile( filename, sizeof(filename), ci, teamName, modelName, skinName, "upper", "skin" ) ) { + ci->torsoSkin = trap_R_RegisterSkin( filename ); + } + if (!ci->torsoSkin) { + Com_Printf( "Torso skin load failure: %s\n", filename ); + } + + if ( CG_FindClientHeadFile( filename, sizeof(filename), ci, teamName, headModelName, headSkinName, "head", "skin" ) ) { + ci->headSkin = trap_R_RegisterSkin( filename ); + } + if (!ci->headSkin) { + Com_Printf( "Head skin load failure: %s\n", filename ); + } + + // if any skins failed to load + if ( !ci->legsSkin || !ci->torsoSkin || !ci->headSkin ) { + return qfalse; + } + return qtrue; +} + +/* +========================== +CG_RegisterClientModelname +========================== +*/ +static qboolean CG_RegisterClientModelname( clientInfo_t *ci, const char *modelName, const char *skinName, const char *headModelName, const char *headSkinName, const char *teamName ) { + char filename[MAX_QPATH*2]; + const char *headName; + char newTeamName[MAX_QPATH*2]; + + if ( headModelName[0] == '\0' ) { + headName = modelName; + } + else { + headName = headModelName; + } + Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower.md3", modelName ); + ci->legsModel = trap_R_RegisterModel( filename ); + if ( !ci->legsModel ) { + Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/lower.md3", modelName ); + ci->legsModel = trap_R_RegisterModel( filename ); + if ( !ci->legsModel ) { + Com_Printf( "Failed to load model file %s\n", filename ); + return qfalse; + } + } + + Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper.md3", modelName ); + ci->torsoModel = trap_R_RegisterModel( filename ); + if ( !ci->torsoModel ) { + Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/upper.md3", modelName ); + ci->torsoModel = trap_R_RegisterModel( filename ); + if ( !ci->torsoModel ) { + Com_Printf( "Failed to load model file %s\n", filename ); + return qfalse; + } + } + + if( headName[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", headName ); + } + ci->headModel = trap_R_RegisterModel( filename ); + // if the head model could not be found and we didn't load from the heads folder try to load from there + if ( !ci->headModel && headName[0] != '*' ) { + Com_sprintf( filename, sizeof( filename ), "models/players/heads/%s/%s.md3", headModelName, headModelName ); + ci->headModel = trap_R_RegisterModel( filename ); + } + if ( !ci->headModel ) { + Com_Printf( "Failed to load model file %s\n", filename ); + return qfalse; + } + + // if any skins failed to load, return failure + if ( !CG_RegisterClientSkin( ci, teamName, modelName, skinName, headName, headSkinName ) ) { + if ( teamName && *teamName) { + Com_Printf( "Failed to load skin file: %s : %s : %s, %s : %s\n", teamName, modelName, skinName, headName, headSkinName ); + if( ci->team == TEAM_BLUE ) { + Com_sprintf(newTeamName, sizeof(newTeamName), "%s/", DEFAULT_BLUETEAM_NAME); + } + else { + Com_sprintf(newTeamName, sizeof(newTeamName), "%s/", DEFAULT_REDTEAM_NAME); + } + if ( !CG_RegisterClientSkin( ci, newTeamName, modelName, skinName, headName, headSkinName ) ) { + Com_Printf( "Failed to load skin file: %s : %s : %s, %s : %s\n", newTeamName, modelName, skinName, headName, headSkinName ); + return qfalse; + } + } else { + Com_Printf( "Failed to load skin file: %s : %s, %s : %s\n", modelName, skinName, headName, headSkinName ); + return qfalse; + } + } + + // load the animations + Com_sprintf( filename, sizeof( filename ), "models/players/%s/animation.cfg", modelName ); + if ( !CG_ParseAnimationFile( filename, ci ) ) { + Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/animation.cfg", modelName ); + if ( !CG_ParseAnimationFile( filename, ci ) ) { + Com_Printf( "Failed to load animation file %s\n", filename ); + return qfalse; + } + } + + if ( CG_FindClientHeadFile( filename, sizeof(filename), ci, teamName, headName, headSkinName, "icon", "skin" ) ) { + ci->modelIcon = trap_R_RegisterShaderNoMip( filename ); + } + else if ( CG_FindClientHeadFile( filename, sizeof(filename), ci, teamName, headName, headSkinName, "icon", "tga" ) ) { + ci->modelIcon = trap_R_RegisterShaderNoMip( filename ); + } + + if ( !ci->modelIcon ) { + return qfalse; + } + + return qtrue; +} + +/* +==================== +CG_ColorFromString +==================== +*/ +static void CG_ColorFromString( const char *v, vec3_t color ) { + int val; + + VectorClear( color ); + + val = atoi( v ); + + if ( val < 1 || val > 7 ) { + VectorSet( color, 1, 1, 1 ); + return; + } + + if ( val & 1 ) { + color[2] = 1.0f; + } + if ( val & 2 ) { + color[1] = 1.0f; + } + if ( val & 4 ) { + color[0] = 1.0f; + } +} + +/* +=================== +CG_LoadClientInfo + +Load it now, taking the disk hits. +This will usually be deferred to a safe time +=================== +*/ +static void CG_LoadClientInfo( clientInfo_t *ci ) { + const char *dir, *fallback; + int i, modelloaded; + const char *s; + int clientNum; + char teamname[MAX_QPATH]; + + teamname[0] = 0; +#ifdef MISSIONPACK + if( cgs.gametype >= GT_TEAM) { + if( ci->team == TEAM_BLUE ) { + Q_strncpyz(teamname, cg_blueTeamName.string, sizeof(teamname) ); + } else { + Q_strncpyz(teamname, cg_redTeamName.string, sizeof(teamname) ); + } + } + if( teamname[0] ) { + strcat( teamname, "/" ); + } +#endif + modelloaded = qtrue; + if ( !CG_RegisterClientModelname( ci, ci->modelName, ci->skinName, ci->headModelName, ci->headSkinName, teamname ) ) { + if ( cg_buildScript.integer ) { + CG_Error( "CG_RegisterClientModelname( %s, %s, %s, %s %s ) failed", ci->modelName, ci->skinName, ci->headModelName, ci->headSkinName, teamname ); + } + + // fall back to default team name + if( cgs.gametype >= GT_TEAM) { + // keep skin name + if( ci->team == TEAM_BLUE ) { + Q_strncpyz(teamname, DEFAULT_BLUETEAM_NAME, sizeof(teamname) ); + } else { + Q_strncpyz(teamname, DEFAULT_REDTEAM_NAME, sizeof(teamname) ); + } + if ( !CG_RegisterClientModelname( ci, DEFAULT_TEAM_MODEL, ci->skinName, DEFAULT_TEAM_HEAD, ci->skinName, teamname ) ) { + CG_Error( "DEFAULT_TEAM_MODEL / skin (%s/%s) failed to register", DEFAULT_TEAM_MODEL, ci->skinName ); + } + } else { + if ( !CG_RegisterClientModelname( ci, DEFAULT_MODEL, "default", DEFAULT_MODEL, "default", teamname ) ) { + CG_Error( "DEFAULT_MODEL (%s) failed to register", DEFAULT_MODEL ); + } + } + modelloaded = qfalse; + } + + ci->newAnims = qfalse; + if ( ci->torsoModel ) { + orientation_t tag; + // if the torso model has the "tag_flag" + if ( trap_R_LerpTag( &tag, ci->torsoModel, 0, 0, 1, "tag_flag" ) ) { + ci->newAnims = qtrue; + } + } + + // sounds + dir = ci->modelName; + fallback = (cgs.gametype >= GT_TEAM) ? DEFAULT_TEAM_MODEL : DEFAULT_MODEL; + + for ( i = 0 ; i < MAX_CUSTOM_SOUNDS ; i++ ) { + s = cg_customSoundNames[i]; + if ( !s ) { + break; + } + ci->sounds[i] = 0; + // if the model didn't load use the sounds of the default model + if (modelloaded) { + ci->sounds[i] = trap_S_RegisterSound( va("sound/player/%s/%s", dir, s + 1), qfalse ); + } + if ( !ci->sounds[i] ) { + ci->sounds[i] = trap_S_RegisterSound( va("sound/player/%s/%s", fallback, s + 1), qfalse ); + } + } + + ci->deferred = qfalse; + + // reset any existing players and bodies, because they might be in bad + // frames for this new model + clientNum = ci - cgs.clientinfo; + for ( i = 0 ; i < MAX_GENTITIES ; i++ ) { + if ( cg_entities[i].currentState.clientNum == clientNum + && cg_entities[i].currentState.eType == ET_PLAYER ) { + CG_ResetPlayerEntity( &cg_entities[i] ); + } + } +} + +/* +====================== +CG_CopyClientInfoModel +====================== +*/ +static void CG_CopyClientInfoModel( clientInfo_t *from, clientInfo_t *to ) { + VectorCopy( from->headOffset, to->headOffset ); + to->footsteps = from->footsteps; + to->gender = from->gender; + + to->legsModel = from->legsModel; + to->legsSkin = from->legsSkin; + to->torsoModel = from->torsoModel; + to->torsoSkin = from->torsoSkin; + to->headModel = from->headModel; + to->headSkin = from->headSkin; + to->modelIcon = from->modelIcon; + + to->newAnims = from->newAnims; + + memcpy( to->animations, from->animations, sizeof( to->animations ) ); + memcpy( to->sounds, from->sounds, sizeof( to->sounds ) ); +} + +/* +====================== +CG_ScanForExistingClientInfo +====================== +*/ +static qboolean CG_ScanForExistingClientInfo( clientInfo_t *ci ) { + int i; + clientInfo_t *match; + + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + match = &cgs.clientinfo[ i ]; + if ( !match->infoValid ) { + continue; + } + if ( match->deferred ) { + continue; + } + if ( !Q_stricmp( ci->modelName, match->modelName ) + && !Q_stricmp( ci->skinName, match->skinName ) + && !Q_stricmp( ci->headModelName, match->headModelName ) + && !Q_stricmp( ci->headSkinName, match->headSkinName ) + && !Q_stricmp( ci->blueTeam, match->blueTeam ) + && !Q_stricmp( ci->redTeam, match->redTeam ) + && (cgs.gametype < GT_TEAM || ci->team == match->team) ) { + // this clientinfo is identical, so use it's handles + + ci->deferred = qfalse; + + CG_CopyClientInfoModel( match, ci ); + + return qtrue; + } + } + + // nothing matches, so defer the load + return qfalse; +} + +/* +====================== +CG_SetDeferredClientInfo + +We aren't going to load it now, so grab some other +client's info to use until we have some spare time. +====================== +*/ +static void CG_SetDeferredClientInfo( clientInfo_t *ci ) { + int i; + clientInfo_t *match; + + // if someone else is already the same models and skins we + // can just load the client info + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + match = &cgs.clientinfo[ i ]; + if ( !match->infoValid || match->deferred ) { + continue; + } + if ( Q_stricmp( ci->skinName, match->skinName ) || + Q_stricmp( ci->modelName, match->modelName ) || +// Q_stricmp( ci->headModelName, match->headModelName ) || +// Q_stricmp( ci->headSkinName, match->headSkinName ) || + (cgs.gametype >= GT_TEAM && ci->team != match->team) ) { + continue; + } + // just load the real info cause it uses the same models and skins + CG_LoadClientInfo( ci ); + return; + } + + // if we are in teamplay, only grab a model if the skin is correct + if ( cgs.gametype >= GT_TEAM ) { + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + match = &cgs.clientinfo[ i ]; + if ( !match->infoValid || match->deferred ) { + continue; + } + if ( Q_stricmp( ci->skinName, match->skinName ) || + (cgs.gametype >= GT_TEAM && ci->team != match->team) ) { + continue; + } + ci->deferred = qtrue; + CG_CopyClientInfoModel( match, ci ); + return; + } + // load the full model, because we don't ever want to show + // an improper team skin. This will cause a hitch for the first + // player, when the second enters. Combat shouldn't be going on + // yet, so it shouldn't matter + CG_LoadClientInfo( ci ); + return; + } + + // find the first valid clientinfo and grab its stuff + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + match = &cgs.clientinfo[ i ]; + if ( !match->infoValid ) { + continue; + } + + ci->deferred = qtrue; + CG_CopyClientInfoModel( match, ci ); + return; + } + + // we should never get here... + CG_Printf( "CG_SetDeferredClientInfo: no valid clients!\n" ); + + CG_LoadClientInfo( ci ); +} + + +/* +====================== +CG_NewClientInfo +====================== +*/ +void CG_NewClientInfo( int clientNum ) { + clientInfo_t *ci; + clientInfo_t newInfo; + const char *configstring; + const char *v; + char *slash; + + ci = &cgs.clientinfo[clientNum]; + + configstring = CG_ConfigString( clientNum + CS_PLAYERS ); + if ( !configstring[0] ) { + memset( ci, 0, sizeof( *ci ) ); + return; // player just left + } + + // build into a temp buffer so the defer checks can use + // the old value + memset( &newInfo, 0, sizeof( newInfo ) ); + + // isolate the player's name + v = Info_ValueForKey(configstring, "n"); + Q_strncpyz( newInfo.name, v, sizeof( newInfo.name ) ); + + // colors + v = Info_ValueForKey( configstring, "c1" ); + CG_ColorFromString( v, newInfo.color1 ); + + v = Info_ValueForKey( configstring, "c2" ); + CG_ColorFromString( v, newInfo.color2 ); + + // bot skill + v = Info_ValueForKey( configstring, "skill" ); + newInfo.botSkill = atoi( v ); + + // handicap + v = Info_ValueForKey( configstring, "hc" ); + newInfo.handicap = atoi( v ); + + // wins + v = Info_ValueForKey( configstring, "w" ); + newInfo.wins = atoi( v ); + + // losses + v = Info_ValueForKey( configstring, "l" ); + newInfo.losses = atoi( v ); + + // team + v = Info_ValueForKey( configstring, "t" ); + newInfo.team = atoi( v ); + + // team task + v = Info_ValueForKey( configstring, "tt" ); + newInfo.teamTask = atoi(v); + + // team leader + v = Info_ValueForKey( configstring, "tl" ); + newInfo.teamLeader = atoi(v); + + v = Info_ValueForKey( configstring, "g_redteam" ); + Q_strncpyz(newInfo.redTeam, v, MAX_TEAMNAME); + + v = Info_ValueForKey( configstring, "g_blueteam" ); + Q_strncpyz(newInfo.blueTeam, v, MAX_TEAMNAME); + + // model + v = Info_ValueForKey( configstring, "model" ); + if ( cg_forceModel.integer ) { + // forcemodel makes everyone use a single model + // to prevent load hitches + char modelStr[MAX_QPATH]; + char *skin; + + if( cgs.gametype >= GT_TEAM ) { + Q_strncpyz( newInfo.modelName, DEFAULT_TEAM_MODEL, sizeof( newInfo.modelName ) ); + Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) ); + } else { + trap_Cvar_VariableStringBuffer( "model", modelStr, sizeof( modelStr ) ); + if ( ( skin = strchr( modelStr, '/' ) ) == NULL) { + skin = "default"; + } else { + *skin++ = 0; + } + + Q_strncpyz( newInfo.skinName, skin, sizeof( newInfo.skinName ) ); + Q_strncpyz( newInfo.modelName, modelStr, sizeof( newInfo.modelName ) ); + } + + if ( cgs.gametype >= GT_TEAM ) { + // keep skin name + slash = strchr( v, '/' ); + if ( slash ) { + Q_strncpyz( newInfo.skinName, slash + 1, sizeof( newInfo.skinName ) ); + } + } + } else { + Q_strncpyz( newInfo.modelName, v, sizeof( newInfo.modelName ) ); + + slash = strchr( newInfo.modelName, '/' ); + if ( !slash ) { + // modelName didn not include a skin name + Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) ); + } else { + Q_strncpyz( newInfo.skinName, slash + 1, sizeof( newInfo.skinName ) ); + // truncate modelName + *slash = 0; + } + } + + // head model + v = Info_ValueForKey( configstring, "hmodel" ); + if ( cg_forceModel.integer ) { + // forcemodel makes everyone use a single model + // to prevent load hitches + char modelStr[MAX_QPATH]; + char *skin; + + if( cgs.gametype >= GT_TEAM ) { + Q_strncpyz( newInfo.headModelName, DEFAULT_TEAM_MODEL, sizeof( newInfo.headModelName ) ); + Q_strncpyz( newInfo.headSkinName, "default", sizeof( newInfo.headSkinName ) ); + } else { + trap_Cvar_VariableStringBuffer( "headmodel", modelStr, sizeof( modelStr ) ); + if ( ( skin = strchr( modelStr, '/' ) ) == NULL) { + skin = "default"; + } else { + *skin++ = 0; + } + + Q_strncpyz( newInfo.headSkinName, skin, sizeof( newInfo.headSkinName ) ); + Q_strncpyz( newInfo.headModelName, modelStr, sizeof( newInfo.headModelName ) ); + } + + if ( cgs.gametype >= GT_TEAM ) { + // keep skin name + slash = strchr( v, '/' ); + if ( slash ) { + Q_strncpyz( newInfo.headSkinName, slash + 1, sizeof( newInfo.headSkinName ) ); + } + } + } else { + Q_strncpyz( newInfo.headModelName, v, sizeof( newInfo.headModelName ) ); + + slash = strchr( newInfo.headModelName, '/' ); + if ( !slash ) { + // modelName didn not include a skin name + Q_strncpyz( newInfo.headSkinName, "default", sizeof( newInfo.headSkinName ) ); + } else { + Q_strncpyz( newInfo.headSkinName, slash + 1, sizeof( newInfo.headSkinName ) ); + // truncate modelName + *slash = 0; + } + } + + // scan for an existing clientinfo that matches this modelname + // so we can avoid loading checks if possible + if ( !CG_ScanForExistingClientInfo( &newInfo ) ) { + qboolean forceDefer; + + forceDefer = trap_MemoryRemaining() < 4000000; + + // if we are defering loads, just have it pick the first valid + if ( forceDefer || (cg_deferPlayers.integer && !cg_buildScript.integer && !cg.loading ) ) { + // keep whatever they had if it won't violate team skins + CG_SetDeferredClientInfo( &newInfo ); + // if we are low on memory, leave them with this model + if ( forceDefer ) { + CG_Printf( "Memory is low. Using deferred model.\n" ); + newInfo.deferred = qfalse; + } + } else { + CG_LoadClientInfo( &newInfo ); + } + } + + // replace whatever was there with the new one + newInfo.infoValid = qtrue; + *ci = newInfo; +} + + + +/* +====================== +CG_LoadDeferredPlayers + +Called each frame when a player is dead +and the scoreboard is up +so deferred players can be loaded +====================== +*/ +void CG_LoadDeferredPlayers( void ) { + int i; + clientInfo_t *ci; + + // scan for a deferred player to load + for ( i = 0, ci = cgs.clientinfo ; i < cgs.maxclients ; i++, ci++ ) { + if ( ci->infoValid && ci->deferred ) { + // if we are low on memory, leave it deferred + if ( trap_MemoryRemaining() < 4000000 ) { + CG_Printf( "Memory is low. Using deferred model.\n" ); + ci->deferred = qfalse; + continue; + } + CG_LoadClientInfo( ci ); +// break; + } + } +} + +/* +============================================================================= + +PLAYER ANIMATION + +============================================================================= +*/ + + +/* +=============== +CG_SetLerpFrameAnimation + +may include ANIM_TOGGLEBIT +=============== +*/ +static void CG_SetLerpFrameAnimation( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation ) { + animation_t *anim; + + lf->animationNumber = newAnimation; + newAnimation &= ~ANIM_TOGGLEBIT; + + if ( newAnimation < 0 || newAnimation >= MAX_TOTALANIMATIONS ) { + CG_Error( "Bad animation number: %i", newAnimation ); + } + + anim = &ci->animations[ newAnimation ]; + + lf->animation = anim; + lf->animationTime = lf->frameTime + anim->initialLerp; + + if ( cg_debugAnim.integer ) { + CG_Printf( "Anim: %i\n", newAnimation ); + } +} + +/* +=============== +CG_RunLerpFrame + +Sets cg.snap, cg.oldFrame, and cg.backlerp +cg.time should be between oldFrameTime and frameTime after exit +=============== +*/ +static void CG_RunLerpFrame( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation, float speedScale ) { + int f, numFrames; + animation_t *anim; + + // debugging tool to get no animations + if ( cg_animSpeed.integer == 0 ) { + lf->oldFrame = lf->frame = lf->backlerp = 0; + return; + } + + // see if the animation sequence is switching + if ( newAnimation != lf->animationNumber || !lf->animation ) { + CG_SetLerpFrameAnimation( ci, lf, newAnimation ); + } + + // if we have passed the current frame, move it to + // oldFrame and calculate a new frame + if ( cg.time >= lf->frameTime ) { + lf->oldFrame = lf->frame; + lf->oldFrameTime = lf->frameTime; + + // get the next frame based on the animation + anim = lf->animation; + if ( !anim->frameLerp ) { + return; // shouldn't happen + } + if ( cg.time < lf->animationTime ) { + lf->frameTime = lf->animationTime; // initial lerp + } else { + lf->frameTime = lf->oldFrameTime + anim->frameLerp; + } + f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp; + f *= speedScale; // adjust for haste, etc + + numFrames = anim->numFrames; + if (anim->flipflop) { + numFrames *= 2; + } + if ( f >= numFrames ) { + f -= numFrames; + if ( anim->loopFrames ) { + f %= anim->loopFrames; + f += anim->numFrames - anim->loopFrames; + } else { + f = numFrames - 1; + // the animation is stuck at the end, so it + // can immediately transition to another sequence + lf->frameTime = cg.time; + } + } + if ( anim->reversed ) { + lf->frame = anim->firstFrame + anim->numFrames - 1 - f; + } + else if (anim->flipflop && f>=anim->numFrames) { + lf->frame = anim->firstFrame + anim->numFrames - 1 - (f%anim->numFrames); + } + else { + lf->frame = anim->firstFrame + f; + } + if ( cg.time > lf->frameTime ) { + lf->frameTime = cg.time; + if ( cg_debugAnim.integer ) { + CG_Printf( "Clamp lf->frameTime\n"); + } + } + } + + if ( lf->frameTime > cg.time + 200 ) { + lf->frameTime = cg.time; + } + + if ( lf->oldFrameTime > cg.time ) { + lf->oldFrameTime = cg.time; + } + // calculate current lerp value + if ( lf->frameTime == lf->oldFrameTime ) { + lf->backlerp = 0; + } else { + lf->backlerp = 1.0 - (float)( cg.time - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime ); + } +} + + +/* +=============== +CG_ClearLerpFrame +=============== +*/ +static void CG_ClearLerpFrame( clientInfo_t *ci, lerpFrame_t *lf, int animationNumber ) { + lf->frameTime = lf->oldFrameTime = cg.time; + CG_SetLerpFrameAnimation( ci, lf, animationNumber ); + lf->oldFrame = lf->frame = lf->animation->firstFrame; +} + + +/* +=============== +CG_PlayerAnimation +=============== +*/ +static void CG_PlayerAnimation( centity_t *cent, int *legsOld, int *legs, float *legsBackLerp, + int *torsoOld, int *torso, float *torsoBackLerp ) { + clientInfo_t *ci; + int clientNum; + float speedScale; + + clientNum = cent->currentState.clientNum; + + if ( cg_noPlayerAnims.integer ) { + *legsOld = *legs = *torsoOld = *torso = 0; + return; + } + + if ( cent->currentState.powerups & ( 1 << PW_HASTE ) ) { + speedScale = 1.5; + } else { + speedScale = 1; + } + + ci = &cgs.clientinfo[ clientNum ]; + + // do the shuffle turn frames locally + if ( cent->pe.legs.yawing && ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_IDLE ) { + CG_RunLerpFrame( ci, ¢->pe.legs, LEGS_TURN, speedScale ); + } else { + CG_RunLerpFrame( ci, ¢->pe.legs, cent->currentState.legsAnim, speedScale ); + } + + *legsOld = cent->pe.legs.oldFrame; + *legs = cent->pe.legs.frame; + *legsBackLerp = cent->pe.legs.backlerp; + + CG_RunLerpFrame( ci, ¢->pe.torso, cent->currentState.torsoAnim, speedScale ); + + *torsoOld = cent->pe.torso.oldFrame; + *torso = cent->pe.torso.frame; + *torsoBackLerp = cent->pe.torso.backlerp; +} + +/* +============================================================================= + +PLAYER ANGLES + +============================================================================= +*/ + +/* +================== +CG_SwingAngles +================== +*/ +static void CG_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 = cg.frametime * scale * speed; + if ( move >= swing ) { + move = swing; + *swinging = qfalse; + } + *angle = AngleMod( *angle + move ); + } else if ( swing < 0 ) { + move = cg.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) ); + } +} + +/* +================= +CG_AddPainTwitch +================= +*/ +static void CG_AddPainTwitch( centity_t *cent, vec3_t torsoAngles ) { + int t; + float f; + + t = cg.time - cent->pe.painTime; + if ( t >= PAIN_TWITCH_TIME ) { + return; + } + + f = 1.0 - (float)t / PAIN_TWITCH_TIME; + + if ( cent->pe.painDirection ) { + torsoAngles[ROLL] += 20 * f; + } else { + torsoAngles[ROLL] -= 20 * f; + } +} + + +/* +=============== +CG_PlayerAngles + +Handles seperate torso motion + + legs pivot based on direction of movement + + head always looks exactly at cent->lerpAngles + + if motion < 20 degrees, show in head only + if < 45 degrees, also show in torso +=============== +*/ +static void CG_PlayerAngles( centity_t *cent, vec3_t legs[3], vec3_t torso[3], vec3_t head[3] ) { + vec3_t legsAngles, torsoAngles, headAngles; + float dest; + static int movementOffsets[8] = { 0, 22, 45, -22, 0, 22, -45, -22 }; + vec3_t velocity; + float speed; + int dir, clientNum; + clientInfo_t *ci; + + VectorCopy( cent->lerpAngles, headAngles ); + headAngles[YAW] = AngleMod( headAngles[YAW] ); + VectorClear( legsAngles ); + VectorClear( torsoAngles ); + + // --------- yaw ------------- + + // allow yaw to drift a bit + if ( ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != LEGS_IDLE + || ( cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ) != TORSO_STAND ) { + // if not standing still, always point all in the same direction + cent->pe.torso.yawing = qtrue; // always center + cent->pe.torso.pitching = qtrue; // always center + cent->pe.legs.yawing = qtrue; // always center + } + + // adjust legs for movement dir + if ( cent->currentState.eFlags & EF_DEAD ) { + // don't let dead bodies twitch + dir = 0; + } else { + dir = cent->currentState.angles2[YAW]; + if ( dir < 0 || dir > 7 ) { + CG_Error( "Bad player movement angle" ); + } + } + legsAngles[YAW] = headAngles[YAW] + movementOffsets[ dir ]; + torsoAngles[YAW] = headAngles[YAW] + 0.25 * movementOffsets[ dir ]; + + // torso + CG_SwingAngles( torsoAngles[YAW], 25, 90, cg_swingSpeed.value, ¢->pe.torso.yawAngle, ¢->pe.torso.yawing ); + CG_SwingAngles( legsAngles[YAW], 40, 90, cg_swingSpeed.value, ¢->pe.legs.yawAngle, ¢->pe.legs.yawing ); + + torsoAngles[YAW] = cent->pe.torso.yawAngle; + legsAngles[YAW] = cent->pe.legs.yawAngle; + + + // --------- pitch ------------- + + // only show a fraction of the pitch angle in the torso + if ( headAngles[PITCH] > 180 ) { + dest = (-360 + headAngles[PITCH]) * 0.75f; + } else { + dest = headAngles[PITCH] * 0.75f; + } + CG_SwingAngles( dest, 15, 30, 0.1f, ¢->pe.torso.pitchAngle, ¢->pe.torso.pitching ); + torsoAngles[PITCH] = cent->pe.torso.pitchAngle; + + // + clientNum = cent->currentState.clientNum; + if ( clientNum >= 0 && clientNum < MAX_CLIENTS ) { + ci = &cgs.clientinfo[ clientNum ]; + if ( ci->fixedtorso ) { + torsoAngles[PITCH] = 0.0f; + } + } + + // --------- roll ------------- + + + // lean towards the direction of travel + VectorCopy( cent->currentState.pos.trDelta, velocity ); + speed = VectorNormalize( velocity ); + if ( speed ) { + vec3_t axis[3]; + float side; + + speed *= 0.05f; + + AnglesToAxis( legsAngles, axis ); + side = speed * DotProduct( velocity, axis[1] ); + legsAngles[ROLL] -= side; + + side = speed * DotProduct( velocity, axis[0] ); + legsAngles[PITCH] += side; + } + + // + clientNum = cent->currentState.clientNum; + if ( clientNum >= 0 && clientNum < MAX_CLIENTS ) { + ci = &cgs.clientinfo[ clientNum ]; + if ( ci->fixedlegs ) { + legsAngles[YAW] = torsoAngles[YAW]; + legsAngles[PITCH] = 0.0f; + legsAngles[ROLL] = 0.0f; + } + } + + // pain twitch + CG_AddPainTwitch( cent, torsoAngles ); + + // 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 ); +} + + +//========================================================================== + +/* +=============== +CG_HasteTrail +=============== +*/ +static void CG_HasteTrail( centity_t *cent ) { + localEntity_t *smoke; + vec3_t origin; + int anim; + + if ( cent->trailTime > cg.time ) { + return; + } + anim = cent->pe.legs.animationNumber & ~ANIM_TOGGLEBIT; + if ( anim != LEGS_RUN && anim != LEGS_BACK ) { + return; + } + + cent->trailTime += 100; + if ( cent->trailTime < cg.time ) { + cent->trailTime = cg.time; + } + + VectorCopy( cent->lerpOrigin, origin ); + origin[2] -= 16; + + smoke = CG_SmokePuff( origin, vec3_origin, + 8, + 1, 1, 1, 1, + 500, + cg.time, + 0, + 0, + cgs.media.hastePuffShader ); + + // use the optimized local entity add + smoke->leType = LE_SCALE_FADE; +} + +#ifdef MISSIONPACK +/* +=============== +CG_BreathPuffs +=============== +*/ +static void CG_BreathPuffs( centity_t *cent, refEntity_t *head) { + clientInfo_t *ci; + vec3_t up, origin; + int contents; + + ci = &cgs.clientinfo[ cent->currentState.number ]; + + if (!cg_enableBreath.integer) { + return; + } + if ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson) { + return; + } + if ( cent->currentState.eFlags & EF_DEAD ) { + return; + } + contents = trap_CM_PointContents( head->origin, 0 ); + if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { + return; + } + if ( ci->breathPuffTime > cg.time ) { + return; + } + + VectorSet( up, 0, 0, 8 ); + VectorMA(head->origin, 8, head->axis[0], origin); + VectorMA(origin, -4, head->axis[2], origin); + CG_SmokePuff( origin, up, 16, 1, 1, 1, 0.66f, 1500, cg.time, cg.time + 400, LEF_PUFF_DONT_SCALE, cgs.media.shotgunSmokePuffShader ); + ci->breathPuffTime = cg.time + 2000; +} + +/* +=============== +CG_DustTrail +=============== +*/ +static void CG_DustTrail( centity_t *cent ) { + int anim; + localEntity_t *dust; + vec3_t end, vel; + trace_t tr; + + if (!cg_enableDust.integer) + return; + + if ( cent->dustTrailTime > cg.time ) { + return; + } + + anim = cent->pe.legs.animationNumber & ~ANIM_TOGGLEBIT; + if ( anim != LEGS_LANDB && anim != LEGS_LAND ) { + return; + } + + cent->dustTrailTime += 40; + if ( cent->dustTrailTime < cg.time ) { + cent->dustTrailTime = cg.time; + } + + VectorCopy(cent->currentState.pos.trBase, end); + end[2] -= 64; + CG_Trace( &tr, cent->currentState.pos.trBase, NULL, NULL, end, cent->currentState.number, MASK_PLAYERSOLID ); + + if ( !(tr.surfaceFlags & SURF_DUST) ) + return; + + VectorCopy( cent->currentState.pos.trBase, end ); + end[2] -= 16; + + VectorSet(vel, 0, 0, -30); + dust = CG_SmokePuff( end, vel, + 24, + .8f, .8f, 0.7f, 0.33f, + 500, + cg.time, + 0, + 0, + cgs.media.dustPuffShader ); +} + +#endif + +/* +=============== +CG_TrailItem +=============== +*/ +static void CG_TrailItem( centity_t *cent, qhandle_t hModel ) { + refEntity_t ent; + vec3_t angles; + vec3_t axis[3]; + + VectorCopy( cent->lerpAngles, angles ); + angles[PITCH] = 0; + angles[ROLL] = 0; + AnglesToAxis( angles, axis ); + + memset( &ent, 0, sizeof( ent ) ); + VectorMA( cent->lerpOrigin, -16, axis[0], ent.origin ); + ent.origin[2] += 16; + angles[YAW] += 90; + AnglesToAxis( angles, ent.axis ); + + ent.hModel = hModel; + trap_R_AddRefEntityToScene( &ent ); +} + + +/* +=============== +CG_PlayerFlag +=============== +*/ +static void CG_PlayerFlag( centity_t *cent, qhandle_t hSkin, refEntity_t *torso ) { + clientInfo_t *ci; + refEntity_t pole; + refEntity_t flag; + vec3_t angles, dir; + int legsAnim, flagAnim, updateangles; + float angle, d; + + // show the flag pole model + memset( &pole, 0, sizeof(pole) ); + pole.hModel = cgs.media.flagPoleModel; + VectorCopy( torso->lightingOrigin, pole.lightingOrigin ); + pole.shadowPlane = torso->shadowPlane; + pole.renderfx = torso->renderfx; + CG_PositionEntityOnTag( &pole, torso, torso->hModel, "tag_flag" ); + trap_R_AddRefEntityToScene( &pole ); + + // show the flag model + memset( &flag, 0, sizeof(flag) ); + flag.hModel = cgs.media.flagFlapModel; + flag.customSkin = hSkin; + VectorCopy( torso->lightingOrigin, flag.lightingOrigin ); + flag.shadowPlane = torso->shadowPlane; + flag.renderfx = torso->renderfx; + + VectorClear(angles); + + updateangles = qfalse; + legsAnim = cent->currentState.legsAnim & ~ANIM_TOGGLEBIT; + if( legsAnim == LEGS_IDLE || legsAnim == LEGS_IDLECR ) { + flagAnim = FLAG_STAND; + } else if ( legsAnim == LEGS_WALK || legsAnim == LEGS_WALKCR ) { + flagAnim = FLAG_STAND; + updateangles = qtrue; + } else { + flagAnim = FLAG_RUN; + updateangles = qtrue; + } + + if ( updateangles ) { + + VectorCopy( cent->currentState.pos.trDelta, dir ); + // add gravity + dir[2] += 100; + VectorNormalize( dir ); + d = DotProduct(pole.axis[2], dir); + // if there is anough movement orthogonal to the flag pole + if (fabs(d) < 0.9) { + // + d = DotProduct(pole.axis[0], dir); + if (d > 1.0f) { + d = 1.0f; + } + else if (d < -1.0f) { + d = -1.0f; + } + angle = acos(d); + + d = DotProduct(pole.axis[1], dir); + if (d < 0) { + angles[YAW] = 360 - angle * 180 / M_PI; + } + else { + angles[YAW] = angle * 180 / M_PI; + } + if (angles[YAW] < 0) + angles[YAW] += 360; + if (angles[YAW] > 360) + angles[YAW] -= 360; + + //vectoangles( cent->currentState.pos.trDelta, tmpangles ); + //angles[YAW] = tmpangles[YAW] + 45 - cent->pe.torso.yawAngle; + // change the yaw angle + CG_SwingAngles( angles[YAW], 25, 90, 0.15f, ¢->pe.flag.yawAngle, ¢->pe.flag.yawing ); + } + + /* + d = DotProduct(pole.axis[2], dir); + angle = Q_acos(d); + + d = DotProduct(pole.axis[1], dir); + if (d < 0) { + angle = 360 - angle * 180 / M_PI; + } + else { + angle = angle * 180 / M_PI; + } + if (angle > 340 && angle < 20) { + flagAnim = FLAG_RUNUP; + } + if (angle > 160 && angle < 200) { + flagAnim = FLAG_RUNDOWN; + } + */ + } + + // set the yaw angle + angles[YAW] = cent->pe.flag.yawAngle; + // lerp the flag animation frames + ci = &cgs.clientinfo[ cent->currentState.clientNum ]; + CG_RunLerpFrame( ci, ¢->pe.flag, flagAnim, 1 ); + flag.oldframe = cent->pe.flag.oldFrame; + flag.frame = cent->pe.flag.frame; + flag.backlerp = cent->pe.flag.backlerp; + + AnglesToAxis( angles, flag.axis ); + CG_PositionRotatedEntityOnTag( &flag, &pole, pole.hModel, "tag_flag" ); + + trap_R_AddRefEntityToScene( &flag ); +} + + +#ifdef MISSIONPACK // bk001204 +/* +=============== +CG_PlayerTokens +=============== +*/ +static void CG_PlayerTokens( centity_t *cent, int renderfx ) { + int tokens, i, j; + float angle; + refEntity_t ent; + vec3_t dir, origin; + skulltrail_t *trail; + trail = &cg.skulltrails[cent->currentState.number]; + tokens = cent->currentState.generic1; + if ( !tokens ) { + trail->numpositions = 0; + return; + } + + if ( tokens > MAX_SKULLTRAIL ) { + tokens = MAX_SKULLTRAIL; + } + + // add skulls if there are more than last time + for (i = 0; i < tokens - trail->numpositions; i++) { + for (j = trail->numpositions; j > 0; j--) { + VectorCopy(trail->positions[j-1], trail->positions[j]); + } + VectorCopy(cent->lerpOrigin, trail->positions[0]); + } + trail->numpositions = tokens; + + // move all the skulls along the trail + VectorCopy(cent->lerpOrigin, origin); + for (i = 0; i < trail->numpositions; i++) { + VectorSubtract(trail->positions[i], origin, dir); + if (VectorNormalize(dir) > 30) { + VectorMA(origin, 30, dir, trail->positions[i]); + } + VectorCopy(trail->positions[i], origin); + } + + memset( &ent, 0, sizeof( ent ) ); + if( cgs.clientinfo[ cent->currentState.clientNum ].team == TEAM_BLUE ) { + ent.hModel = cgs.media.redCubeModel; + } else { + ent.hModel = cgs.media.blueCubeModel; + } + ent.renderfx = renderfx; + + VectorCopy(cent->lerpOrigin, origin); + for (i = 0; i < trail->numpositions; i++) { + VectorSubtract(origin, trail->positions[i], ent.axis[0]); + ent.axis[0][2] = 0; + VectorNormalize(ent.axis[0]); + VectorSet(ent.axis[2], 0, 0, 1); + CrossProduct(ent.axis[0], ent.axis[2], ent.axis[1]); + + VectorCopy(trail->positions[i], ent.origin); + angle = (((cg.time + 500 * MAX_SKULLTRAIL - 500 * i) / 16) & 255) * (M_PI * 2) / 255; + ent.origin[2] += sin(angle) * 10; + trap_R_AddRefEntityToScene( &ent ); + VectorCopy(trail->positions[i], origin); + } +} +#endif + + +/* +=============== +CG_PlayerPowerups +=============== +*/ +static void CG_PlayerPowerups( centity_t *cent, refEntity_t *torso ) { + int powerups; + clientInfo_t *ci; + + powerups = cent->currentState.powerups; + if ( !powerups ) { + return; + } + + // quad gives a dlight + if ( powerups & ( 1 << PW_QUAD ) ) { + trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.2f, 0.2f, 1 ); + } + + // flight plays a looped sound + if ( powerups & ( 1 << PW_FLIGHT ) ) { + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.media.flightSound ); + } + + ci = &cgs.clientinfo[ cent->currentState.clientNum ]; + // redflag + if ( powerups & ( 1 << PW_REDFLAG ) ) { + if (ci->newAnims) { + CG_PlayerFlag( cent, cgs.media.redFlagFlapSkin, torso ); + } + else { + CG_TrailItem( cent, cgs.media.redFlagModel ); + } + trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 1.0, 0.2f, 0.2f ); + } + + // blueflag + if ( powerups & ( 1 << PW_BLUEFLAG ) ) { + if (ci->newAnims){ + CG_PlayerFlag( cent, cgs.media.blueFlagFlapSkin, torso ); + } + else { + CG_TrailItem( cent, cgs.media.blueFlagModel ); + } + trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.2f, 0.2f, 1.0 ); + } + + // neutralflag + if ( powerups & ( 1 << PW_NEUTRALFLAG ) ) { + if (ci->newAnims) { + CG_PlayerFlag( cent, cgs.media.neutralFlagFlapSkin, torso ); + } + else { + CG_TrailItem( cent, cgs.media.neutralFlagModel ); + } + trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 1.0, 1.0, 1.0 ); + } + + // haste leaves smoke trails + if ( powerups & ( 1 << PW_HASTE ) ) { + CG_HasteTrail( cent ); + } +} + + +/* +=============== +CG_PlayerFloatSprite + +Float a sprite over the player's head +=============== +*/ +static void CG_PlayerFloatSprite( centity_t *cent, qhandle_t shader ) { + int rf; + refEntity_t ent; + + if ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson ) { + rf = RF_THIRD_PERSON; // only show in mirrors + } else { + rf = 0; + } + + memset( &ent, 0, sizeof( ent ) ); + VectorCopy( cent->lerpOrigin, ent.origin ); + ent.origin[2] += 48; + ent.reType = RT_SPRITE; + ent.customShader = shader; + ent.radius = 10; + ent.renderfx = rf; + ent.shaderRGBA[0] = 255; + ent.shaderRGBA[1] = 255; + ent.shaderRGBA[2] = 255; + ent.shaderRGBA[3] = 255; + trap_R_AddRefEntityToScene( &ent ); +} + + + +/* +=============== +CG_PlayerSprites + +Float sprites over the player's head +=============== +*/ +static void CG_PlayerSprites( centity_t *cent ) { + int team; + + if ( cent->currentState.eFlags & EF_CONNECTION ) { + CG_PlayerFloatSprite( cent, cgs.media.connectionShader ); + return; + } + + if ( cent->currentState.eFlags & EF_TALK ) { + CG_PlayerFloatSprite( cent, cgs.media.balloonShader ); + return; + } + + if ( cent->currentState.eFlags & EF_AWARD_IMPRESSIVE ) { + CG_PlayerFloatSprite( cent, cgs.media.medalImpressive ); + return; + } + + if ( cent->currentState.eFlags & EF_AWARD_EXCELLENT ) { + CG_PlayerFloatSprite( cent, cgs.media.medalExcellent ); + return; + } + + if ( cent->currentState.eFlags & EF_AWARD_GAUNTLET ) { + CG_PlayerFloatSprite( cent, cgs.media.medalGauntlet ); + return; + } + + if ( cent->currentState.eFlags & EF_AWARD_DEFEND ) { + CG_PlayerFloatSprite( cent, cgs.media.medalDefend ); + return; + } + + if ( cent->currentState.eFlags & EF_AWARD_ASSIST ) { + CG_PlayerFloatSprite( cent, cgs.media.medalAssist ); + return; + } + + if ( cent->currentState.eFlags & EF_AWARD_CAP ) { + CG_PlayerFloatSprite( cent, cgs.media.medalCapture ); + return; + } + + team = cgs.clientinfo[ cent->currentState.clientNum ].team; + if ( !(cent->currentState.eFlags & EF_DEAD) && + cg.snap->ps.persistant[PERS_TEAM] == team && + cgs.gametype >= GT_TEAM) { + if (cg_drawFriend.integer) { + CG_PlayerFloatSprite( cent, cgs.media.friendShader ); + } + return; + } +} + +/* +=============== +CG_PlayerShadow + +Returns the Z component of the surface being shadowed + + should it return a full plane instead of a Z? +=============== +*/ +#define SHADOW_DISTANCE 128 +static qboolean CG_PlayerShadow( centity_t *cent, float *shadowPlane ) { + vec3_t end, mins = {-15, -15, 0}, maxs = {15, 15, 2}; + trace_t trace; + float alpha; + + *shadowPlane = 0; + + if ( cg_shadows.integer == 0 ) { + return qfalse; + } + + // no shadows when invisible + if ( cent->currentState.powerups & ( 1 << PW_INVIS ) ) { + return qfalse; + } + + // send a trace down from the player to the ground + VectorCopy( cent->lerpOrigin, end ); + end[2] -= SHADOW_DISTANCE; + + trap_CM_BoxTrace( &trace, cent->lerpOrigin, end, mins, maxs, 0, MASK_PLAYERSOLID ); + + // no shadow if too high + if ( trace.fraction == 1.0 || trace.startsolid || trace.allsolid ) { + return qfalse; + } + + *shadowPlane = trace.endpos[2] + 1; + + if ( cg_shadows.integer != 1 ) { // no mark for stencil or projection shadows + return qtrue; + } + + // fade the shadow out with height + alpha = 1.0 - trace.fraction; + + // bk0101022 - hack / FPE - bogus planes? + //assert( DotProduct( trace.plane.normal, trace.plane.normal ) != 0.0f ) + + // add the mark as a temporary, so it goes directly to the renderer + // without taking a spot in the cg_marks array + CG_ImpactMark( cgs.media.shadowMarkShader, trace.endpos, trace.plane.normal, + cent->pe.legs.yawAngle, alpha,alpha,alpha,1, qfalse, 24, qtrue ); + + return qtrue; +} + + +/* +=============== +CG_PlayerSplash + +Draw a mark at the water surface +=============== +*/ +static void CG_PlayerSplash( centity_t *cent ) { + vec3_t start, end; + trace_t trace; + int contents; + polyVert_t verts[4]; + + if ( !cg_shadows.integer ) { + return; + } + + VectorCopy( cent->lerpOrigin, end ); + end[2] -= 24; + + // if the feet aren't in liquid, don't make a mark + // this won't handle moving water brushes, but they wouldn't draw right anyway... + contents = trap_CM_PointContents( end, 0 ); + if ( !( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) ) { + return; + } + + VectorCopy( cent->lerpOrigin, start ); + start[2] += 32; + + // if the head isn't out of liquid, don't make a mark + contents = trap_CM_PointContents( start, 0 ); + if ( contents & ( CONTENTS_SOLID | CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { + return; + } + + // trace down to find the surface + trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ); + + if ( trace.fraction == 1.0 ) { + return; + } + + // create a mark polygon + VectorCopy( trace.endpos, verts[0].xyz ); + verts[0].xyz[0] -= 32; + verts[0].xyz[1] -= 32; + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255; + + VectorCopy( trace.endpos, verts[1].xyz ); + verts[1].xyz[0] -= 32; + verts[1].xyz[1] += 32; + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255; + + VectorCopy( trace.endpos, verts[2].xyz ); + verts[2].xyz[0] += 32; + verts[2].xyz[1] += 32; + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255; + + VectorCopy( trace.endpos, verts[3].xyz ); + verts[3].xyz[0] += 32; + verts[3].xyz[1] -= 32; + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255; + + trap_R_AddPolyToScene( cgs.media.wakeMarkShader, 4, verts ); +} + + + +/* +=============== +CG_AddRefEntityWithPowerups + +Adds a piece with modifications or duplications for powerups +Also called by CG_Missile for quad rockets, but nobody can tell... +=============== +*/ +void CG_AddRefEntityWithPowerups( refEntity_t *ent, entityState_t *state, int team ) { + + if ( state->powerups & ( 1 << PW_INVIS ) ) { + ent->customShader = cgs.media.invisShader; + trap_R_AddRefEntityToScene( ent ); + } else { + /* + if ( state->eFlags & EF_KAMIKAZE ) { + if (team == TEAM_BLUE) + ent->customShader = cgs.media.blueKamikazeShader; + else + ent->customShader = cgs.media.redKamikazeShader; + trap_R_AddRefEntityToScene( ent ); + } + else {*/ + trap_R_AddRefEntityToScene( ent ); + //} + + if ( state->powerups & ( 1 << PW_QUAD ) ) + { + if (team == TEAM_RED) + ent->customShader = cgs.media.redQuadShader; + else + ent->customShader = cgs.media.quadShader; + trap_R_AddRefEntityToScene( ent ); + } + if ( state->powerups & ( 1 << PW_REGEN ) ) { + if ( ( ( cg.time / 100 ) % 10 ) == 1 ) { + ent->customShader = cgs.media.regenShader; + trap_R_AddRefEntityToScene( ent ); + } + } + if ( state->powerups & ( 1 << PW_BATTLESUIT ) ) { + ent->customShader = cgs.media.battleSuitShader; + trap_R_AddRefEntityToScene( ent ); + } + } +} + +/* +================= +CG_LightVerts +================= +*/ +int CG_LightVerts( vec3_t normal, int numVerts, polyVert_t *verts ) +{ + int i, j; + float incoming; + vec3_t ambientLight; + vec3_t lightDir; + vec3_t directedLight; + + trap_R_LightForPoint( verts[0].xyz, ambientLight, directedLight, lightDir ); + + for (i = 0; i < numVerts; i++) { + incoming = DotProduct (normal, lightDir); + if ( incoming <= 0 ) { + verts[i].modulate[0] = ambientLight[0]; + verts[i].modulate[1] = ambientLight[1]; + verts[i].modulate[2] = ambientLight[2]; + verts[i].modulate[3] = 255; + continue; + } + j = ( ambientLight[0] + incoming * directedLight[0] ); + if ( j > 255 ) { + j = 255; + } + verts[i].modulate[0] = j; + + j = ( ambientLight[1] + incoming * directedLight[1] ); + if ( j > 255 ) { + j = 255; + } + verts[i].modulate[1] = j; + + j = ( ambientLight[2] + incoming * directedLight[2] ); + if ( j > 255 ) { + j = 255; + } + verts[i].modulate[2] = j; + + verts[i].modulate[3] = 255; + } + return qtrue; +} + +/* +=============== +CG_Player +=============== +*/ +void CG_Player( centity_t *cent ) { + clientInfo_t *ci; + refEntity_t legs; + refEntity_t torso; + refEntity_t head; + int clientNum; + int renderfx; + qboolean shadow; + float shadowPlane; +#ifdef MISSIONPACK + refEntity_t skull; + refEntity_t powerup; + int t; + float c; + float angle; + vec3_t dir, angles; +#endif + + // the client number is stored in clientNum. It can't be derived + // from the entity number, because a single client may have + // multiple corpses on the level using the same clientinfo + clientNum = cent->currentState.clientNum; + if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { + CG_Error( "Bad clientNum on player entity"); + } + ci = &cgs.clientinfo[ clientNum ]; + + // it is possible to see corpses from disconnected players that may + // not have valid clientinfo + if ( !ci->infoValid ) { + return; + } + + // get the player model information + renderfx = 0; + if ( cent->currentState.number == cg.snap->ps.clientNum) { + if (!cg.renderingThirdPerson) { + renderfx = RF_THIRD_PERSON; // only draw in mirrors + } else { + if (cg_cameraMode.integer) { + return; + } + } + } + + + memset( &legs, 0, sizeof(legs) ); + memset( &torso, 0, sizeof(torso) ); + memset( &head, 0, sizeof(head) ); + + // get the rotation information + CG_PlayerAngles( cent, legs.axis, torso.axis, head.axis ); + + // get the animation state (after rotation, to allow feet shuffle) + CG_PlayerAnimation( cent, &legs.oldframe, &legs.frame, &legs.backlerp, + &torso.oldframe, &torso.frame, &torso.backlerp ); + + // add the talk baloon or disconnect icon + CG_PlayerSprites( cent ); + + // add the shadow + shadow = CG_PlayerShadow( cent, &shadowPlane ); + + // add a water splash if partially in and out of water + CG_PlayerSplash( cent ); + + if ( cg_shadows.integer == 3 && shadow ) { + renderfx |= RF_SHADOW_PLANE; + } + renderfx |= RF_LIGHTING_ORIGIN; // use the same origin for all +#ifdef MISSIONPACK + if( cgs.gametype == GT_HARVESTER ) { + CG_PlayerTokens( cent, renderfx ); + } +#endif + // + // add the legs + // + legs.hModel = ci->legsModel; + legs.customSkin = ci->legsSkin; + + VectorCopy( cent->lerpOrigin, legs.origin ); + + VectorCopy( cent->lerpOrigin, legs.lightingOrigin ); + legs.shadowPlane = shadowPlane; + legs.renderfx = renderfx; + VectorCopy (legs.origin, legs.oldorigin); // don't positionally lerp at all + + CG_AddRefEntityWithPowerups( &legs, ¢->currentState, ci->team ); + + // if the model failed, allow the default nullmodel to be displayed + if (!legs.hModel) { + return; + } + + // + // add the torso + // + torso.hModel = ci->torsoModel; + if (!torso.hModel) { + return; + } + + torso.customSkin = ci->torsoSkin; + + VectorCopy( cent->lerpOrigin, torso.lightingOrigin ); + + CG_PositionRotatedEntityOnTag( &torso, &legs, ci->legsModel, "tag_torso"); + + torso.shadowPlane = shadowPlane; + torso.renderfx = renderfx; + + CG_AddRefEntityWithPowerups( &torso, ¢->currentState, ci->team ); + +#ifdef MISSIONPACK + if ( cent->currentState.eFlags & EF_KAMIKAZE ) { + + memset( &skull, 0, sizeof(skull) ); + + VectorCopy( cent->lerpOrigin, skull.lightingOrigin ); + skull.shadowPlane = shadowPlane; + skull.renderfx = renderfx; + + if ( cent->currentState.eFlags & EF_DEAD ) { + // one skull bobbing above the dead body + angle = ((cg.time / 7) & 255) * (M_PI * 2) / 255; + if (angle > M_PI * 2) + angle -= (float)M_PI * 2; + dir[0] = sin(angle) * 20; + dir[1] = cos(angle) * 20; + angle = ((cg.time / 4) & 255) * (M_PI * 2) / 255; + dir[2] = 15 + sin(angle) * 8; + VectorAdd(torso.origin, dir, skull.origin); + + dir[2] = 0; + VectorCopy(dir, skull.axis[1]); + VectorNormalize(skull.axis[1]); + VectorSet(skull.axis[2], 0, 0, 1); + CrossProduct(skull.axis[1], skull.axis[2], skull.axis[0]); + + skull.hModel = cgs.media.kamikazeHeadModel; + trap_R_AddRefEntityToScene( &skull ); + skull.hModel = cgs.media.kamikazeHeadTrail; + trap_R_AddRefEntityToScene( &skull ); + } + else { + // three skulls spinning around the player + angle = ((cg.time / 4) & 255) * (M_PI * 2) / 255; + dir[0] = cos(angle) * 20; + dir[1] = sin(angle) * 20; + dir[2] = cos(angle) * 20; + VectorAdd(torso.origin, dir, skull.origin); + + angles[0] = sin(angle) * 30; + angles[1] = (angle * 180 / M_PI) + 90; + if (angles[1] > 360) + angles[1] -= 360; + angles[2] = 0; + AnglesToAxis( angles, skull.axis ); + + /* + dir[2] = 0; + VectorInverse(dir); + VectorCopy(dir, skull.axis[1]); + VectorNormalize(skull.axis[1]); + VectorSet(skull.axis[2], 0, 0, 1); + CrossProduct(skull.axis[1], skull.axis[2], skull.axis[0]); + */ + + skull.hModel = cgs.media.kamikazeHeadModel; + trap_R_AddRefEntityToScene( &skull ); + // flip the trail because this skull is spinning in the other direction + VectorInverse(skull.axis[1]); + skull.hModel = cgs.media.kamikazeHeadTrail; + trap_R_AddRefEntityToScene( &skull ); + + angle = ((cg.time / 4) & 255) * (M_PI * 2) / 255 + M_PI; + if (angle > M_PI * 2) + angle -= (float)M_PI * 2; + dir[0] = sin(angle) * 20; + dir[1] = cos(angle) * 20; + dir[2] = cos(angle) * 20; + VectorAdd(torso.origin, dir, skull.origin); + + angles[0] = cos(angle - 0.5 * M_PI) * 30; + angles[1] = 360 - (angle * 180 / M_PI); + if (angles[1] > 360) + angles[1] -= 360; + angles[2] = 0; + AnglesToAxis( angles, skull.axis ); + + /* + dir[2] = 0; + VectorCopy(dir, skull.axis[1]); + VectorNormalize(skull.axis[1]); + VectorSet(skull.axis[2], 0, 0, 1); + CrossProduct(skull.axis[1], skull.axis[2], skull.axis[0]); + */ + + skull.hModel = cgs.media.kamikazeHeadModel; + trap_R_AddRefEntityToScene( &skull ); + skull.hModel = cgs.media.kamikazeHeadTrail; + trap_R_AddRefEntityToScene( &skull ); + + angle = ((cg.time / 3) & 255) * (M_PI * 2) / 255 + 0.5 * M_PI; + if (angle > M_PI * 2) + angle -= (float)M_PI * 2; + dir[0] = sin(angle) * 20; + dir[1] = cos(angle) * 20; + dir[2] = 0; + VectorAdd(torso.origin, dir, skull.origin); + + VectorCopy(dir, skull.axis[1]); + VectorNormalize(skull.axis[1]); + VectorSet(skull.axis[2], 0, 0, 1); + CrossProduct(skull.axis[1], skull.axis[2], skull.axis[0]); + + skull.hModel = cgs.media.kamikazeHeadModel; + trap_R_AddRefEntityToScene( &skull ); + skull.hModel = cgs.media.kamikazeHeadTrail; + trap_R_AddRefEntityToScene( &skull ); + } + } + + if ( cent->currentState.powerups & ( 1 << PW_GUARD ) ) { + memcpy(&powerup, &torso, sizeof(torso)); + powerup.hModel = cgs.media.guardPowerupModel; + powerup.frame = 0; + powerup.oldframe = 0; + powerup.customSkin = 0; + trap_R_AddRefEntityToScene( &powerup ); + } + if ( cent->currentState.powerups & ( 1 << PW_SCOUT ) ) { + memcpy(&powerup, &torso, sizeof(torso)); + powerup.hModel = cgs.media.scoutPowerupModel; + powerup.frame = 0; + powerup.oldframe = 0; + powerup.customSkin = 0; + trap_R_AddRefEntityToScene( &powerup ); + } + if ( cent->currentState.powerups & ( 1 << PW_DOUBLER ) ) { + memcpy(&powerup, &torso, sizeof(torso)); + powerup.hModel = cgs.media.doublerPowerupModel; + powerup.frame = 0; + powerup.oldframe = 0; + powerup.customSkin = 0; + trap_R_AddRefEntityToScene( &powerup ); + } + if ( cent->currentState.powerups & ( 1 << PW_AMMOREGEN ) ) { + memcpy(&powerup, &torso, sizeof(torso)); + powerup.hModel = cgs.media.ammoRegenPowerupModel; + powerup.frame = 0; + powerup.oldframe = 0; + powerup.customSkin = 0; + trap_R_AddRefEntityToScene( &powerup ); + } + if ( cent->currentState.powerups & ( 1 << PW_INVULNERABILITY ) ) { + if ( !ci->invulnerabilityStartTime ) { + ci->invulnerabilityStartTime = cg.time; + } + ci->invulnerabilityStopTime = cg.time; + } + else { + ci->invulnerabilityStartTime = 0; + } + if ( (cent->currentState.powerups & ( 1 << PW_INVULNERABILITY ) ) || + cg.time - ci->invulnerabilityStopTime < 250 ) { + + memcpy(&powerup, &torso, sizeof(torso)); + powerup.hModel = cgs.media.invulnerabilityPowerupModel; + powerup.customSkin = 0; + // always draw + powerup.renderfx &= ~RF_THIRD_PERSON; + VectorCopy(cent->lerpOrigin, powerup.origin); + + if ( cg.time - ci->invulnerabilityStartTime < 250 ) { + c = (float) (cg.time - ci->invulnerabilityStartTime) / 250; + } + else if (cg.time - ci->invulnerabilityStopTime < 250 ) { + c = (float) (250 - (cg.time - ci->invulnerabilityStopTime)) / 250; + } + else { + c = 1; + } + VectorSet( powerup.axis[0], c, 0, 0 ); + VectorSet( powerup.axis[1], 0, c, 0 ); + VectorSet( powerup.axis[2], 0, 0, c ); + trap_R_AddRefEntityToScene( &powerup ); + } + + t = cg.time - ci->medkitUsageTime; + if ( ci->medkitUsageTime && t < 500 ) { + memcpy(&powerup, &torso, sizeof(torso)); + powerup.hModel = cgs.media.medkitUsageModel; + powerup.customSkin = 0; + // always draw + powerup.renderfx &= ~RF_THIRD_PERSON; + VectorClear(angles); + AnglesToAxis(angles, powerup.axis); + VectorCopy(cent->lerpOrigin, powerup.origin); + powerup.origin[2] += -24 + (float) t * 80 / 500; + if ( t > 400 ) { + c = (float) (t - 1000) * 0xff / 100; + powerup.shaderRGBA[0] = 0xff - c; + powerup.shaderRGBA[1] = 0xff - c; + powerup.shaderRGBA[2] = 0xff - c; + powerup.shaderRGBA[3] = 0xff - c; + } + else { + powerup.shaderRGBA[0] = 0xff; + powerup.shaderRGBA[1] = 0xff; + powerup.shaderRGBA[2] = 0xff; + powerup.shaderRGBA[3] = 0xff; + } + trap_R_AddRefEntityToScene( &powerup ); + } +#endif // MISSIONPACK + + // + // add the head + // + head.hModel = ci->headModel; + if (!head.hModel) { + return; + } + head.customSkin = ci->headSkin; + + VectorCopy( cent->lerpOrigin, head.lightingOrigin ); + + CG_PositionRotatedEntityOnTag( &head, &torso, ci->torsoModel, "tag_head"); + + head.shadowPlane = shadowPlane; + head.renderfx = renderfx; + + CG_AddRefEntityWithPowerups( &head, ¢->currentState, ci->team ); + +#ifdef MISSIONPACK + CG_BreathPuffs(cent, &head); + + CG_DustTrail(cent); +#endif + + // + // add the gun / barrel / flash + // + CG_AddPlayerWeapon( &torso, NULL, cent, ci->team ); + + // add powerups floating behind the player + CG_PlayerPowerups( cent, &torso ); +} + + +//===================================================================== + +/* +=============== +CG_ResetPlayerEntity + +A player just came into view or teleported, so reset all animation info +=============== +*/ +void CG_ResetPlayerEntity( centity_t *cent ) { + cent->errorTime = -99999; // guarantee no error decay added + cent->extrapolated = qfalse; + + CG_ClearLerpFrame( &cgs.clientinfo[ cent->currentState.clientNum ], ¢->pe.legs, cent->currentState.legsAnim ); + CG_ClearLerpFrame( &cgs.clientinfo[ cent->currentState.clientNum ], ¢->pe.torso, cent->currentState.torsoAnim ); + + BG_EvaluateTrajectory( ¢->currentState.pos, cg.time, cent->lerpOrigin ); + BG_EvaluateTrajectory( ¢->currentState.apos, cg.time, cent->lerpAngles ); + + VectorCopy( cent->lerpOrigin, cent->rawOrigin ); + VectorCopy( cent->lerpAngles, cent->rawAngles ); + + memset( ¢->pe.legs, 0, sizeof( cent->pe.legs ) ); + cent->pe.legs.yawAngle = cent->rawAngles[YAW]; + cent->pe.legs.yawing = qfalse; + cent->pe.legs.pitchAngle = 0; + cent->pe.legs.pitching = qfalse; + + memset( ¢->pe.torso, 0, sizeof( cent->pe.legs ) ); + cent->pe.torso.yawAngle = cent->rawAngles[YAW]; + cent->pe.torso.yawing = qfalse; + cent->pe.torso.pitchAngle = cent->rawAngles[PITCH]; + cent->pe.torso.pitching = qfalse; + + if ( cg_debugPosition.integer ) { + CG_Printf("%i ResetPlayerEntity yaw=%i\n", cent->currentState.number, cent->pe.torso.yawAngle ); + } +} + diff --git a/code/cgame/cg_playerstate.c b/code/cgame/cg_playerstate.c index 1e0d61a..16f3608 100755 --- a/code/cgame/cg_playerstate.c +++ b/code/cgame/cg_playerstate.c @@ -1,526 +1,526 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// cg_playerstate.c -- this file acts on changes in a new playerState_t -// With normal play, this will be done after local prediction, but when -// following another player or playing back a demo, it will be checked -// when the snapshot transitions like all the other entities - -#include "cg_local.h" - -/* -============== -CG_CheckAmmo - -If the ammo has gone low enough to generate the warning, play a sound -============== -*/ -void CG_CheckAmmo( void ) { - int i; - int total; - int previous; - int weapons; - - // see about how many seconds of ammo we have remaining - weapons = cg.snap->ps.stats[ STAT_WEAPONS ]; - total = 0; - for ( i = WP_MACHINEGUN ; i < WP_NUM_WEAPONS ; i++ ) { - if ( ! ( weapons & ( 1 << i ) ) ) { - continue; - } - switch ( i ) { - case WP_ROCKET_LAUNCHER: - case WP_GRENADE_LAUNCHER: - case WP_RAILGUN: - case WP_SHOTGUN: -#ifdef MISSIONPACK - case WP_PROX_LAUNCHER: -#endif - total += cg.snap->ps.ammo[i] * 1000; - break; - default: - total += cg.snap->ps.ammo[i] * 200; - break; - } - if ( total >= 5000 ) { - cg.lowAmmoWarning = 0; - return; - } - } - - previous = cg.lowAmmoWarning; - - if ( total == 0 ) { - cg.lowAmmoWarning = 2; - } else { - cg.lowAmmoWarning = 1; - } - - // play a sound on transitions - if ( cg.lowAmmoWarning != previous ) { - trap_S_StartLocalSound( cgs.media.noAmmoSound, CHAN_LOCAL_SOUND ); - } -} - -/* -============== -CG_DamageFeedback -============== -*/ -void CG_DamageFeedback( int yawByte, int pitchByte, int damage ) { - float left, front, up; - float kick; - int health; - float scale; - vec3_t dir; - vec3_t angles; - float dist; - float yaw, pitch; - - // show the attacking player's head and name in corner - cg.attackerTime = cg.time; - - // the lower on health you are, the greater the view kick will be - health = cg.snap->ps.stats[STAT_HEALTH]; - if ( health < 40 ) { - scale = 1; - } else { - scale = 40.0 / health; - } - kick = damage * scale; - - if (kick < 5) - kick = 5; - if (kick > 10) - kick = 10; - - // if yaw and pitch are both 255, make the damage always centered (falling, etc) - if ( yawByte == 255 && pitchByte == 255 ) { - cg.damageX = 0; - cg.damageY = 0; - cg.v_dmg_roll = 0; - cg.v_dmg_pitch = -kick; - } else { - // positional - pitch = pitchByte / 255.0 * 360; - yaw = yawByte / 255.0 * 360; - - angles[PITCH] = pitch; - angles[YAW] = yaw; - angles[ROLL] = 0; - - AngleVectors( angles, dir, NULL, NULL ); - VectorSubtract( vec3_origin, dir, dir ); - - front = DotProduct (dir, cg.refdef.viewaxis[0] ); - left = DotProduct (dir, cg.refdef.viewaxis[1] ); - up = DotProduct (dir, cg.refdef.viewaxis[2] ); - - dir[0] = front; - dir[1] = left; - dir[2] = 0; - dist = VectorLength( dir ); - if ( dist < 0.1 ) { - dist = 0.1f; - } - - cg.v_dmg_roll = kick * left; - - cg.v_dmg_pitch = -kick * front; - - if ( front <= 0.1 ) { - front = 0.1f; - } - cg.damageX = -left / front; - cg.damageY = up / dist; - } - - // clamp the position - if ( cg.damageX > 1.0 ) { - cg.damageX = 1.0; - } - if ( cg.damageX < - 1.0 ) { - cg.damageX = -1.0; - } - - if ( cg.damageY > 1.0 ) { - cg.damageY = 1.0; - } - if ( cg.damageY < - 1.0 ) { - cg.damageY = -1.0; - } - - // don't let the screen flashes vary as much - if ( kick > 10 ) { - kick = 10; - } - cg.damageValue = kick; - cg.v_dmg_time = cg.time + DAMAGE_TIME; - cg.damageTime = cg.snap->serverTime; -} - - - - -/* -================ -CG_Respawn - -A respawn happened this snapshot -================ -*/ -void CG_Respawn( void ) { - // no error decay on player movement - cg.thisFrameTeleport = qtrue; - - // display weapons available - cg.weaponSelectTime = cg.time; - - // select the weapon the server says we are using - cg.weaponSelect = cg.snap->ps.weapon; -} - -extern char *eventnames[]; - -/* -============== -CG_CheckPlayerstateEvents -============== -*/ -void CG_CheckPlayerstateEvents( playerState_t *ps, playerState_t *ops ) { - int i; - int event; - centity_t *cent; - - if ( ps->externalEvent && ps->externalEvent != ops->externalEvent ) { - cent = &cg_entities[ ps->clientNum ]; - cent->currentState.event = ps->externalEvent; - cent->currentState.eventParm = ps->externalEventParm; - CG_EntityEvent( cent, cent->lerpOrigin ); - } - - cent = &cg.predictedPlayerEntity; // cg_entities[ ps->clientNum ]; - // go through the predictable events buffer - for ( i = ps->eventSequence - MAX_PS_EVENTS ; i < ps->eventSequence ; i++ ) { - // if we have a new predictable event - if ( i >= ops->eventSequence - // or the server told us to play another event instead of a predicted event we already issued - // or something the server told us changed our prediction causing a different event - || (i > ops->eventSequence - MAX_PS_EVENTS && ps->events[i & (MAX_PS_EVENTS-1)] != ops->events[i & (MAX_PS_EVENTS-1)]) ) { - - event = ps->events[ i & (MAX_PS_EVENTS-1) ]; - cent->currentState.event = event; - cent->currentState.eventParm = ps->eventParms[ i & (MAX_PS_EVENTS-1) ]; - CG_EntityEvent( cent, cent->lerpOrigin ); - - cg.predictableEvents[ i & (MAX_PREDICTED_EVENTS-1) ] = event; - - cg.eventSequence++; - } - } -} - -/* -================== -CG_CheckChangedPredictableEvents -================== -*/ -void CG_CheckChangedPredictableEvents( playerState_t *ps ) { - int i; - int event; - centity_t *cent; - - cent = &cg.predictedPlayerEntity; - for ( i = ps->eventSequence - MAX_PS_EVENTS ; i < ps->eventSequence ; i++ ) { - // - if (i >= cg.eventSequence) { - continue; - } - // if this event is not further back in than the maximum predictable events we remember - if (i > cg.eventSequence - MAX_PREDICTED_EVENTS) { - // if the new playerstate event is different from a previously predicted one - if ( ps->events[i & (MAX_PS_EVENTS-1)] != cg.predictableEvents[i & (MAX_PREDICTED_EVENTS-1) ] ) { - - event = ps->events[ i & (MAX_PS_EVENTS-1) ]; - cent->currentState.event = event; - cent->currentState.eventParm = ps->eventParms[ i & (MAX_PS_EVENTS-1) ]; - CG_EntityEvent( cent, cent->lerpOrigin ); - - cg.predictableEvents[ i & (MAX_PREDICTED_EVENTS-1) ] = event; - - if ( cg_showmiss.integer ) { - CG_Printf("WARNING: changed predicted event\n"); - } - } - } - } -} - -/* -================== -pushReward -================== -*/ -static void pushReward(sfxHandle_t sfx, qhandle_t shader, int rewardCount) { - if (cg.rewardStack < (MAX_REWARDSTACK-1)) { - cg.rewardStack++; - cg.rewardSound[cg.rewardStack] = sfx; - cg.rewardShader[cg.rewardStack] = shader; - cg.rewardCount[cg.rewardStack] = rewardCount; - } -} - -/* -================== -CG_CheckLocalSounds -================== -*/ -void CG_CheckLocalSounds( playerState_t *ps, playerState_t *ops ) { - int highScore, health, armor, reward; - sfxHandle_t sfx; - - // don't play the sounds if the player just changed teams - if ( ps->persistant[PERS_TEAM] != ops->persistant[PERS_TEAM] ) { - return; - } - - // hit changes - if ( ps->persistant[PERS_HITS] > ops->persistant[PERS_HITS] ) { - armor = ps->persistant[PERS_ATTACKEE_ARMOR] & 0xff; - health = ps->persistant[PERS_ATTACKEE_ARMOR] >> 8; -#ifdef MISSIONPACK - if (armor > 50 ) { - trap_S_StartLocalSound( cgs.media.hitSoundHighArmor, CHAN_LOCAL_SOUND ); - } else if (armor || health > 100) { - trap_S_StartLocalSound( cgs.media.hitSoundLowArmor, CHAN_LOCAL_SOUND ); - } else { - trap_S_StartLocalSound( cgs.media.hitSound, CHAN_LOCAL_SOUND ); - } -#else - trap_S_StartLocalSound( cgs.media.hitSound, CHAN_LOCAL_SOUND ); -#endif - } else if ( ps->persistant[PERS_HITS] < ops->persistant[PERS_HITS] ) { - trap_S_StartLocalSound( cgs.media.hitTeamSound, CHAN_LOCAL_SOUND ); - } - - // health changes of more than -1 should make pain sounds - if ( ps->stats[STAT_HEALTH] < ops->stats[STAT_HEALTH] - 1 ) { - if ( ps->stats[STAT_HEALTH] > 0 ) { - CG_PainEvent( &cg.predictedPlayerEntity, ps->stats[STAT_HEALTH] ); - } - } - - - // if we are going into the intermission, don't start any voices - if ( cg.intermissionStarted ) { - return; - } - - // reward sounds - reward = qfalse; - if (ps->persistant[PERS_CAPTURES] != ops->persistant[PERS_CAPTURES]) { - pushReward(cgs.media.captureAwardSound, cgs.media.medalCapture, ps->persistant[PERS_CAPTURES]); - reward = qtrue; - //Com_Printf("capture\n"); - } - if (ps->persistant[PERS_IMPRESSIVE_COUNT] != ops->persistant[PERS_IMPRESSIVE_COUNT]) { -#ifdef MISSIONPACK - if (ps->persistant[PERS_IMPRESSIVE_COUNT] == 1) { - sfx = cgs.media.firstImpressiveSound; - } else { - sfx = cgs.media.impressiveSound; - } -#else - sfx = cgs.media.impressiveSound; -#endif - pushReward(sfx, cgs.media.medalImpressive, ps->persistant[PERS_IMPRESSIVE_COUNT]); - reward = qtrue; - //Com_Printf("impressive\n"); - } - if (ps->persistant[PERS_EXCELLENT_COUNT] != ops->persistant[PERS_EXCELLENT_COUNT]) { -#ifdef MISSIONPACK - if (ps->persistant[PERS_EXCELLENT_COUNT] == 1) { - sfx = cgs.media.firstExcellentSound; - } else { - sfx = cgs.media.excellentSound; - } -#else - sfx = cgs.media.excellentSound; -#endif - pushReward(sfx, cgs.media.medalExcellent, ps->persistant[PERS_EXCELLENT_COUNT]); - reward = qtrue; - //Com_Printf("excellent\n"); - } - if (ps->persistant[PERS_GAUNTLET_FRAG_COUNT] != ops->persistant[PERS_GAUNTLET_FRAG_COUNT]) { -#ifdef MISSIONPACK - if (ops->persistant[PERS_GAUNTLET_FRAG_COUNT] == 1) { - sfx = cgs.media.firstHumiliationSound; - } else { - sfx = cgs.media.humiliationSound; - } -#else - sfx = cgs.media.humiliationSound; -#endif - pushReward(sfx, cgs.media.medalGauntlet, ps->persistant[PERS_GAUNTLET_FRAG_COUNT]); - reward = qtrue; - //Com_Printf("guantlet frag\n"); - } - if (ps->persistant[PERS_DEFEND_COUNT] != ops->persistant[PERS_DEFEND_COUNT]) { - pushReward(cgs.media.defendSound, cgs.media.medalDefend, ps->persistant[PERS_DEFEND_COUNT]); - reward = qtrue; - //Com_Printf("defend\n"); - } - if (ps->persistant[PERS_ASSIST_COUNT] != ops->persistant[PERS_ASSIST_COUNT]) { - pushReward(cgs.media.assistSound, cgs.media.medalAssist, ps->persistant[PERS_ASSIST_COUNT]); - reward = qtrue; - //Com_Printf("assist\n"); - } - // if any of the player event bits changed - if (ps->persistant[PERS_PLAYEREVENTS] != ops->persistant[PERS_PLAYEREVENTS]) { - if ((ps->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_DENIEDREWARD) != - (ops->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_DENIEDREWARD)) { - trap_S_StartLocalSound( cgs.media.deniedSound, CHAN_ANNOUNCER ); - } - else if ((ps->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_GAUNTLETREWARD) != - (ops->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_GAUNTLETREWARD)) { - trap_S_StartLocalSound( cgs.media.humiliationSound, CHAN_ANNOUNCER ); - } - else if ((ps->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_HOLYSHIT) != - (ops->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_HOLYSHIT)) { - trap_S_StartLocalSound( cgs.media.holyShitSound, CHAN_ANNOUNCER ); - } - reward = qtrue; - } - - // check for flag pickup - if ( cgs.gametype >= GT_TEAM ) { - if ((ps->powerups[PW_REDFLAG] != ops->powerups[PW_REDFLAG] && ps->powerups[PW_REDFLAG]) || - (ps->powerups[PW_BLUEFLAG] != ops->powerups[PW_BLUEFLAG] && ps->powerups[PW_BLUEFLAG]) || - (ps->powerups[PW_NEUTRALFLAG] != ops->powerups[PW_NEUTRALFLAG] && ps->powerups[PW_NEUTRALFLAG]) ) - { - trap_S_StartLocalSound( cgs.media.youHaveFlagSound, CHAN_ANNOUNCER ); - } - } - - // lead changes - if (!reward) { - // - if ( !cg.warmup ) { - // never play lead changes during warmup - if ( ps->persistant[PERS_RANK] != ops->persistant[PERS_RANK] ) { - if ( cgs.gametype < GT_TEAM) { - if ( ps->persistant[PERS_RANK] == 0 ) { - CG_AddBufferedSound(cgs.media.takenLeadSound); - } else if ( ps->persistant[PERS_RANK] == RANK_TIED_FLAG ) { - CG_AddBufferedSound(cgs.media.tiedLeadSound); - } else if ( ( ops->persistant[PERS_RANK] & ~RANK_TIED_FLAG ) == 0 ) { - CG_AddBufferedSound(cgs.media.lostLeadSound); - } - } - } - } - } - - // timelimit warnings - if ( cgs.timelimit > 0 ) { - int msec; - - msec = cg.time - cgs.levelStartTime; - if ( !( cg.timelimitWarnings & 4 ) && msec > ( cgs.timelimit * 60 + 2 ) * 1000 ) { - cg.timelimitWarnings |= 1 | 2 | 4; - trap_S_StartLocalSound( cgs.media.suddenDeathSound, CHAN_ANNOUNCER ); - } - else if ( !( cg.timelimitWarnings & 2 ) && msec > (cgs.timelimit - 1) * 60 * 1000 ) { - cg.timelimitWarnings |= 1 | 2; - trap_S_StartLocalSound( cgs.media.oneMinuteSound, CHAN_ANNOUNCER ); - } - else if ( cgs.timelimit > 5 && !( cg.timelimitWarnings & 1 ) && msec > (cgs.timelimit - 5) * 60 * 1000 ) { - cg.timelimitWarnings |= 1; - trap_S_StartLocalSound( cgs.media.fiveMinuteSound, CHAN_ANNOUNCER ); - } - } - - // fraglimit warnings - if ( cgs.fraglimit > 0 && cgs.gametype < GT_CTF) { - highScore = cgs.scores1; - if ( !( cg.fraglimitWarnings & 4 ) && highScore == (cgs.fraglimit - 1) ) { - cg.fraglimitWarnings |= 1 | 2 | 4; - CG_AddBufferedSound(cgs.media.oneFragSound); - } - else if ( cgs.fraglimit > 2 && !( cg.fraglimitWarnings & 2 ) && highScore == (cgs.fraglimit - 2) ) { - cg.fraglimitWarnings |= 1 | 2; - CG_AddBufferedSound(cgs.media.twoFragSound); - } - else if ( cgs.fraglimit > 3 && !( cg.fraglimitWarnings & 1 ) && highScore == (cgs.fraglimit - 3) ) { - cg.fraglimitWarnings |= 1; - CG_AddBufferedSound(cgs.media.threeFragSound); - } - } -} - -/* -=============== -CG_TransitionPlayerState - -=============== -*/ -void CG_TransitionPlayerState( playerState_t *ps, playerState_t *ops ) { - // check for changing follow mode - if ( ps->clientNum != ops->clientNum ) { - cg.thisFrameTeleport = qtrue; - // make sure we don't get any unwanted transition effects - *ops = *ps; - } - - // damage events (player is getting wounded) - if ( ps->damageEvent != ops->damageEvent && ps->damageCount ) { - CG_DamageFeedback( ps->damageYaw, ps->damagePitch, ps->damageCount ); - } - - // respawning - if ( ps->persistant[PERS_SPAWN_COUNT] != ops->persistant[PERS_SPAWN_COUNT] ) { - CG_Respawn(); - } - - if ( cg.mapRestart ) { - CG_Respawn(); - cg.mapRestart = qfalse; - } - - if ( cg.snap->ps.pm_type != PM_INTERMISSION - && ps->persistant[PERS_TEAM] != TEAM_SPECTATOR ) { - CG_CheckLocalSounds( ps, ops ); - } - - // check for going low on ammo - CG_CheckAmmo(); - - // run events - CG_CheckPlayerstateEvents( ps, ops ); - - // smooth the ducking viewheight change - if ( ps->viewheight != ops->viewheight ) { - cg.duckChange = ps->viewheight - ops->viewheight; - cg.duckTime = cg.time; - } -} - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// cg_playerstate.c -- this file acts on changes in a new playerState_t +// With normal play, this will be done after local prediction, but when +// following another player or playing back a demo, it will be checked +// when the snapshot transitions like all the other entities + +#include "cg_local.h" + +/* +============== +CG_CheckAmmo + +If the ammo has gone low enough to generate the warning, play a sound +============== +*/ +void CG_CheckAmmo( void ) { + int i; + int total; + int previous; + int weapons; + + // see about how many seconds of ammo we have remaining + weapons = cg.snap->ps.stats[ STAT_WEAPONS ]; + total = 0; + for ( i = WP_MACHINEGUN ; i < WP_NUM_WEAPONS ; i++ ) { + if ( ! ( weapons & ( 1 << i ) ) ) { + continue; + } + switch ( i ) { + case WP_ROCKET_LAUNCHER: + case WP_GRENADE_LAUNCHER: + case WP_RAILGUN: + case WP_SHOTGUN: +#ifdef MISSIONPACK + case WP_PROX_LAUNCHER: +#endif + total += cg.snap->ps.ammo[i] * 1000; + break; + default: + total += cg.snap->ps.ammo[i] * 200; + break; + } + if ( total >= 5000 ) { + cg.lowAmmoWarning = 0; + return; + } + } + + previous = cg.lowAmmoWarning; + + if ( total == 0 ) { + cg.lowAmmoWarning = 2; + } else { + cg.lowAmmoWarning = 1; + } + + // play a sound on transitions + if ( cg.lowAmmoWarning != previous ) { + trap_S_StartLocalSound( cgs.media.noAmmoSound, CHAN_LOCAL_SOUND ); + } +} + +/* +============== +CG_DamageFeedback +============== +*/ +void CG_DamageFeedback( int yawByte, int pitchByte, int damage ) { + float left, front, up; + float kick; + int health; + float scale; + vec3_t dir; + vec3_t angles; + float dist; + float yaw, pitch; + + // show the attacking player's head and name in corner + cg.attackerTime = cg.time; + + // the lower on health you are, the greater the view kick will be + health = cg.snap->ps.stats[STAT_HEALTH]; + if ( health < 40 ) { + scale = 1; + } else { + scale = 40.0 / health; + } + kick = damage * scale; + + if (kick < 5) + kick = 5; + if (kick > 10) + kick = 10; + + // if yaw and pitch are both 255, make the damage always centered (falling, etc) + if ( yawByte == 255 && pitchByte == 255 ) { + cg.damageX = 0; + cg.damageY = 0; + cg.v_dmg_roll = 0; + cg.v_dmg_pitch = -kick; + } else { + // positional + pitch = pitchByte / 255.0 * 360; + yaw = yawByte / 255.0 * 360; + + angles[PITCH] = pitch; + angles[YAW] = yaw; + angles[ROLL] = 0; + + AngleVectors( angles, dir, NULL, NULL ); + VectorSubtract( vec3_origin, dir, dir ); + + front = DotProduct (dir, cg.refdef.viewaxis[0] ); + left = DotProduct (dir, cg.refdef.viewaxis[1] ); + up = DotProduct (dir, cg.refdef.viewaxis[2] ); + + dir[0] = front; + dir[1] = left; + dir[2] = 0; + dist = VectorLength( dir ); + if ( dist < 0.1 ) { + dist = 0.1f; + } + + cg.v_dmg_roll = kick * left; + + cg.v_dmg_pitch = -kick * front; + + if ( front <= 0.1 ) { + front = 0.1f; + } + cg.damageX = -left / front; + cg.damageY = up / dist; + } + + // clamp the position + if ( cg.damageX > 1.0 ) { + cg.damageX = 1.0; + } + if ( cg.damageX < - 1.0 ) { + cg.damageX = -1.0; + } + + if ( cg.damageY > 1.0 ) { + cg.damageY = 1.0; + } + if ( cg.damageY < - 1.0 ) { + cg.damageY = -1.0; + } + + // don't let the screen flashes vary as much + if ( kick > 10 ) { + kick = 10; + } + cg.damageValue = kick; + cg.v_dmg_time = cg.time + DAMAGE_TIME; + cg.damageTime = cg.snap->serverTime; +} + + + + +/* +================ +CG_Respawn + +A respawn happened this snapshot +================ +*/ +void CG_Respawn( void ) { + // no error decay on player movement + cg.thisFrameTeleport = qtrue; + + // display weapons available + cg.weaponSelectTime = cg.time; + + // select the weapon the server says we are using + cg.weaponSelect = cg.snap->ps.weapon; +} + +extern char *eventnames[]; + +/* +============== +CG_CheckPlayerstateEvents +============== +*/ +void CG_CheckPlayerstateEvents( playerState_t *ps, playerState_t *ops ) { + int i; + int event; + centity_t *cent; + + if ( ps->externalEvent && ps->externalEvent != ops->externalEvent ) { + cent = &cg_entities[ ps->clientNum ]; + cent->currentState.event = ps->externalEvent; + cent->currentState.eventParm = ps->externalEventParm; + CG_EntityEvent( cent, cent->lerpOrigin ); + } + + cent = &cg.predictedPlayerEntity; // cg_entities[ ps->clientNum ]; + // go through the predictable events buffer + for ( i = ps->eventSequence - MAX_PS_EVENTS ; i < ps->eventSequence ; i++ ) { + // if we have a new predictable event + if ( i >= ops->eventSequence + // or the server told us to play another event instead of a predicted event we already issued + // or something the server told us changed our prediction causing a different event + || (i > ops->eventSequence - MAX_PS_EVENTS && ps->events[i & (MAX_PS_EVENTS-1)] != ops->events[i & (MAX_PS_EVENTS-1)]) ) { + + event = ps->events[ i & (MAX_PS_EVENTS-1) ]; + cent->currentState.event = event; + cent->currentState.eventParm = ps->eventParms[ i & (MAX_PS_EVENTS-1) ]; + CG_EntityEvent( cent, cent->lerpOrigin ); + + cg.predictableEvents[ i & (MAX_PREDICTED_EVENTS-1) ] = event; + + cg.eventSequence++; + } + } +} + +/* +================== +CG_CheckChangedPredictableEvents +================== +*/ +void CG_CheckChangedPredictableEvents( playerState_t *ps ) { + int i; + int event; + centity_t *cent; + + cent = &cg.predictedPlayerEntity; + for ( i = ps->eventSequence - MAX_PS_EVENTS ; i < ps->eventSequence ; i++ ) { + // + if (i >= cg.eventSequence) { + continue; + } + // if this event is not further back in than the maximum predictable events we remember + if (i > cg.eventSequence - MAX_PREDICTED_EVENTS) { + // if the new playerstate event is different from a previously predicted one + if ( ps->events[i & (MAX_PS_EVENTS-1)] != cg.predictableEvents[i & (MAX_PREDICTED_EVENTS-1) ] ) { + + event = ps->events[ i & (MAX_PS_EVENTS-1) ]; + cent->currentState.event = event; + cent->currentState.eventParm = ps->eventParms[ i & (MAX_PS_EVENTS-1) ]; + CG_EntityEvent( cent, cent->lerpOrigin ); + + cg.predictableEvents[ i & (MAX_PREDICTED_EVENTS-1) ] = event; + + if ( cg_showmiss.integer ) { + CG_Printf("WARNING: changed predicted event\n"); + } + } + } + } +} + +/* +================== +pushReward +================== +*/ +static void pushReward(sfxHandle_t sfx, qhandle_t shader, int rewardCount) { + if (cg.rewardStack < (MAX_REWARDSTACK-1)) { + cg.rewardStack++; + cg.rewardSound[cg.rewardStack] = sfx; + cg.rewardShader[cg.rewardStack] = shader; + cg.rewardCount[cg.rewardStack] = rewardCount; + } +} + +/* +================== +CG_CheckLocalSounds +================== +*/ +void CG_CheckLocalSounds( playerState_t *ps, playerState_t *ops ) { + int highScore, health, armor, reward; + sfxHandle_t sfx; + + // don't play the sounds if the player just changed teams + if ( ps->persistant[PERS_TEAM] != ops->persistant[PERS_TEAM] ) { + return; + } + + // hit changes + if ( ps->persistant[PERS_HITS] > ops->persistant[PERS_HITS] ) { + armor = ps->persistant[PERS_ATTACKEE_ARMOR] & 0xff; + health = ps->persistant[PERS_ATTACKEE_ARMOR] >> 8; +#ifdef MISSIONPACK + if (armor > 50 ) { + trap_S_StartLocalSound( cgs.media.hitSoundHighArmor, CHAN_LOCAL_SOUND ); + } else if (armor || health > 100) { + trap_S_StartLocalSound( cgs.media.hitSoundLowArmor, CHAN_LOCAL_SOUND ); + } else { + trap_S_StartLocalSound( cgs.media.hitSound, CHAN_LOCAL_SOUND ); + } +#else + trap_S_StartLocalSound( cgs.media.hitSound, CHAN_LOCAL_SOUND ); +#endif + } else if ( ps->persistant[PERS_HITS] < ops->persistant[PERS_HITS] ) { + trap_S_StartLocalSound( cgs.media.hitTeamSound, CHAN_LOCAL_SOUND ); + } + + // health changes of more than -1 should make pain sounds + if ( ps->stats[STAT_HEALTH] < ops->stats[STAT_HEALTH] - 1 ) { + if ( ps->stats[STAT_HEALTH] > 0 ) { + CG_PainEvent( &cg.predictedPlayerEntity, ps->stats[STAT_HEALTH] ); + } + } + + + // if we are going into the intermission, don't start any voices + if ( cg.intermissionStarted ) { + return; + } + + // reward sounds + reward = qfalse; + if (ps->persistant[PERS_CAPTURES] != ops->persistant[PERS_CAPTURES]) { + pushReward(cgs.media.captureAwardSound, cgs.media.medalCapture, ps->persistant[PERS_CAPTURES]); + reward = qtrue; + //Com_Printf("capture\n"); + } + if (ps->persistant[PERS_IMPRESSIVE_COUNT] != ops->persistant[PERS_IMPRESSIVE_COUNT]) { +#ifdef MISSIONPACK + if (ps->persistant[PERS_IMPRESSIVE_COUNT] == 1) { + sfx = cgs.media.firstImpressiveSound; + } else { + sfx = cgs.media.impressiveSound; + } +#else + sfx = cgs.media.impressiveSound; +#endif + pushReward(sfx, cgs.media.medalImpressive, ps->persistant[PERS_IMPRESSIVE_COUNT]); + reward = qtrue; + //Com_Printf("impressive\n"); + } + if (ps->persistant[PERS_EXCELLENT_COUNT] != ops->persistant[PERS_EXCELLENT_COUNT]) { +#ifdef MISSIONPACK + if (ps->persistant[PERS_EXCELLENT_COUNT] == 1) { + sfx = cgs.media.firstExcellentSound; + } else { + sfx = cgs.media.excellentSound; + } +#else + sfx = cgs.media.excellentSound; +#endif + pushReward(sfx, cgs.media.medalExcellent, ps->persistant[PERS_EXCELLENT_COUNT]); + reward = qtrue; + //Com_Printf("excellent\n"); + } + if (ps->persistant[PERS_GAUNTLET_FRAG_COUNT] != ops->persistant[PERS_GAUNTLET_FRAG_COUNT]) { +#ifdef MISSIONPACK + if (ops->persistant[PERS_GAUNTLET_FRAG_COUNT] == 1) { + sfx = cgs.media.firstHumiliationSound; + } else { + sfx = cgs.media.humiliationSound; + } +#else + sfx = cgs.media.humiliationSound; +#endif + pushReward(sfx, cgs.media.medalGauntlet, ps->persistant[PERS_GAUNTLET_FRAG_COUNT]); + reward = qtrue; + //Com_Printf("guantlet frag\n"); + } + if (ps->persistant[PERS_DEFEND_COUNT] != ops->persistant[PERS_DEFEND_COUNT]) { + pushReward(cgs.media.defendSound, cgs.media.medalDefend, ps->persistant[PERS_DEFEND_COUNT]); + reward = qtrue; + //Com_Printf("defend\n"); + } + if (ps->persistant[PERS_ASSIST_COUNT] != ops->persistant[PERS_ASSIST_COUNT]) { + pushReward(cgs.media.assistSound, cgs.media.medalAssist, ps->persistant[PERS_ASSIST_COUNT]); + reward = qtrue; + //Com_Printf("assist\n"); + } + // if any of the player event bits changed + if (ps->persistant[PERS_PLAYEREVENTS] != ops->persistant[PERS_PLAYEREVENTS]) { + if ((ps->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_DENIEDREWARD) != + (ops->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_DENIEDREWARD)) { + trap_S_StartLocalSound( cgs.media.deniedSound, CHAN_ANNOUNCER ); + } + else if ((ps->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_GAUNTLETREWARD) != + (ops->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_GAUNTLETREWARD)) { + trap_S_StartLocalSound( cgs.media.humiliationSound, CHAN_ANNOUNCER ); + } + else if ((ps->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_HOLYSHIT) != + (ops->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_HOLYSHIT)) { + trap_S_StartLocalSound( cgs.media.holyShitSound, CHAN_ANNOUNCER ); + } + reward = qtrue; + } + + // check for flag pickup + if ( cgs.gametype >= GT_TEAM ) { + if ((ps->powerups[PW_REDFLAG] != ops->powerups[PW_REDFLAG] && ps->powerups[PW_REDFLAG]) || + (ps->powerups[PW_BLUEFLAG] != ops->powerups[PW_BLUEFLAG] && ps->powerups[PW_BLUEFLAG]) || + (ps->powerups[PW_NEUTRALFLAG] != ops->powerups[PW_NEUTRALFLAG] && ps->powerups[PW_NEUTRALFLAG]) ) + { + trap_S_StartLocalSound( cgs.media.youHaveFlagSound, CHAN_ANNOUNCER ); + } + } + + // lead changes + if (!reward) { + // + if ( !cg.warmup ) { + // never play lead changes during warmup + if ( ps->persistant[PERS_RANK] != ops->persistant[PERS_RANK] ) { + if ( cgs.gametype < GT_TEAM) { + if ( ps->persistant[PERS_RANK] == 0 ) { + CG_AddBufferedSound(cgs.media.takenLeadSound); + } else if ( ps->persistant[PERS_RANK] == RANK_TIED_FLAG ) { + CG_AddBufferedSound(cgs.media.tiedLeadSound); + } else if ( ( ops->persistant[PERS_RANK] & ~RANK_TIED_FLAG ) == 0 ) { + CG_AddBufferedSound(cgs.media.lostLeadSound); + } + } + } + } + } + + // timelimit warnings + if ( cgs.timelimit > 0 ) { + int msec; + + msec = cg.time - cgs.levelStartTime; + if ( !( cg.timelimitWarnings & 4 ) && msec > ( cgs.timelimit * 60 + 2 ) * 1000 ) { + cg.timelimitWarnings |= 1 | 2 | 4; + trap_S_StartLocalSound( cgs.media.suddenDeathSound, CHAN_ANNOUNCER ); + } + else if ( !( cg.timelimitWarnings & 2 ) && msec > (cgs.timelimit - 1) * 60 * 1000 ) { + cg.timelimitWarnings |= 1 | 2; + trap_S_StartLocalSound( cgs.media.oneMinuteSound, CHAN_ANNOUNCER ); + } + else if ( cgs.timelimit > 5 && !( cg.timelimitWarnings & 1 ) && msec > (cgs.timelimit - 5) * 60 * 1000 ) { + cg.timelimitWarnings |= 1; + trap_S_StartLocalSound( cgs.media.fiveMinuteSound, CHAN_ANNOUNCER ); + } + } + + // fraglimit warnings + if ( cgs.fraglimit > 0 && cgs.gametype < GT_CTF) { + highScore = cgs.scores1; + if ( !( cg.fraglimitWarnings & 4 ) && highScore == (cgs.fraglimit - 1) ) { + cg.fraglimitWarnings |= 1 | 2 | 4; + CG_AddBufferedSound(cgs.media.oneFragSound); + } + else if ( cgs.fraglimit > 2 && !( cg.fraglimitWarnings & 2 ) && highScore == (cgs.fraglimit - 2) ) { + cg.fraglimitWarnings |= 1 | 2; + CG_AddBufferedSound(cgs.media.twoFragSound); + } + else if ( cgs.fraglimit > 3 && !( cg.fraglimitWarnings & 1 ) && highScore == (cgs.fraglimit - 3) ) { + cg.fraglimitWarnings |= 1; + CG_AddBufferedSound(cgs.media.threeFragSound); + } + } +} + +/* +=============== +CG_TransitionPlayerState + +=============== +*/ +void CG_TransitionPlayerState( playerState_t *ps, playerState_t *ops ) { + // check for changing follow mode + if ( ps->clientNum != ops->clientNum ) { + cg.thisFrameTeleport = qtrue; + // make sure we don't get any unwanted transition effects + *ops = *ps; + } + + // damage events (player is getting wounded) + if ( ps->damageEvent != ops->damageEvent && ps->damageCount ) { + CG_DamageFeedback( ps->damageYaw, ps->damagePitch, ps->damageCount ); + } + + // respawning + if ( ps->persistant[PERS_SPAWN_COUNT] != ops->persistant[PERS_SPAWN_COUNT] ) { + CG_Respawn(); + } + + if ( cg.mapRestart ) { + CG_Respawn(); + cg.mapRestart = qfalse; + } + + if ( cg.snap->ps.pm_type != PM_INTERMISSION + && ps->persistant[PERS_TEAM] != TEAM_SPECTATOR ) { + CG_CheckLocalSounds( ps, ops ); + } + + // check for going low on ammo + CG_CheckAmmo(); + + // run events + CG_CheckPlayerstateEvents( ps, ops ); + + // smooth the ducking viewheight change + if ( ps->viewheight != ops->viewheight ) { + cg.duckChange = ps->viewheight - ops->viewheight; + cg.duckTime = cg.time; + } +} + diff --git a/code/cgame/cg_predict.c b/code/cgame/cg_predict.c index fec2cfb..893b439 100755 --- a/code/cgame/cg_predict.c +++ b/code/cgame/cg_predict.c @@ -1,628 +1,628 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// cg_predict.c -- this file generates cg.predictedPlayerState by either -// interpolating between snapshots from the server or locally predicting -// ahead the client's movement. -// It also handles local physics interaction, like fragments bouncing off walls - -#include "cg_local.h" - -static pmove_t cg_pmove; - -static int cg_numSolidEntities; -static centity_t *cg_solidEntities[MAX_ENTITIES_IN_SNAPSHOT]; -static int cg_numTriggerEntities; -static centity_t *cg_triggerEntities[MAX_ENTITIES_IN_SNAPSHOT]; - -/* -==================== -CG_BuildSolidList - -When a new cg.snap has been set, this function builds a sublist -of the entities that are actually solid, to make for more -efficient collision detection -==================== -*/ -void CG_BuildSolidList( void ) { - int i; - centity_t *cent; - snapshot_t *snap; - entityState_t *ent; - - cg_numSolidEntities = 0; - cg_numTriggerEntities = 0; - - if ( cg.nextSnap && !cg.nextFrameTeleport && !cg.thisFrameTeleport ) { - snap = cg.nextSnap; - } else { - snap = cg.snap; - } - - for ( i = 0 ; i < snap->numEntities ; i++ ) { - cent = &cg_entities[ snap->entities[ i ].number ]; - ent = ¢->currentState; - - if ( ent->eType == ET_ITEM || ent->eType == ET_PUSH_TRIGGER || ent->eType == ET_TELEPORT_TRIGGER ) { - cg_triggerEntities[cg_numTriggerEntities] = cent; - cg_numTriggerEntities++; - continue; - } - - if ( cent->nextState.solid ) { - cg_solidEntities[cg_numSolidEntities] = cent; - cg_numSolidEntities++; - continue; - } - } -} - -/* -==================== -CG_ClipMoveToEntities - -==================== -*/ -static void CG_ClipMoveToEntities ( const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, - int skipNumber, int mask, trace_t *tr ) { - int i, x, zd, zu; - trace_t trace; - entityState_t *ent; - clipHandle_t cmodel; - vec3_t bmins, bmaxs; - vec3_t origin, angles; - centity_t *cent; - - for ( i = 0 ; i < cg_numSolidEntities ; i++ ) { - cent = cg_solidEntities[ i ]; - ent = ¢->currentState; - - if ( ent->number == skipNumber ) { - continue; - } - - if ( ent->solid == SOLID_BMODEL ) { - // special value for bmodel - cmodel = trap_CM_InlineModel( ent->modelindex ); - VectorCopy( cent->lerpAngles, angles ); - BG_EvaluateTrajectory( ¢->currentState.pos, cg.physicsTime, origin ); - } else { - // encoded bbox - x = (ent->solid & 255); - zd = ((ent->solid>>8) & 255); - zu = ((ent->solid>>16) & 255) - 32; - - bmins[0] = bmins[1] = -x; - bmaxs[0] = bmaxs[1] = x; - bmins[2] = -zd; - bmaxs[2] = zu; - - cmodel = trap_CM_TempBoxModel( bmins, bmaxs ); - VectorCopy( vec3_origin, angles ); - VectorCopy( cent->lerpOrigin, origin ); - } - - - trap_CM_TransformedBoxTrace ( &trace, start, end, - mins, maxs, cmodel, mask, origin, angles); - - if (trace.allsolid || trace.fraction < tr->fraction) { - trace.entityNum = ent->number; - *tr = trace; - } else if (trace.startsolid) { - tr->startsolid = qtrue; - } - if ( tr->allsolid ) { - return; - } - } -} - -/* -================ -CG_Trace -================ -*/ -void CG_Trace( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, - int skipNumber, int mask ) { - trace_t t; - - trap_CM_BoxTrace ( &t, start, end, mins, maxs, 0, mask); - t.entityNum = t.fraction != 1.0 ? ENTITYNUM_WORLD : ENTITYNUM_NONE; - // check all other solid models - CG_ClipMoveToEntities (start, mins, maxs, end, skipNumber, mask, &t); - - *result = t; -} - -/* -================ -CG_PointContents -================ -*/ -int CG_PointContents( const vec3_t point, int passEntityNum ) { - int i; - entityState_t *ent; - centity_t *cent; - clipHandle_t cmodel; - int contents; - - contents = trap_CM_PointContents (point, 0); - - for ( i = 0 ; i < cg_numSolidEntities ; i++ ) { - cent = cg_solidEntities[ i ]; - - ent = ¢->currentState; - - if ( ent->number == passEntityNum ) { - continue; - } - - if (ent->solid != SOLID_BMODEL) { // special value for bmodel - continue; - } - - cmodel = trap_CM_InlineModel( ent->modelindex ); - if ( !cmodel ) { - continue; - } - - contents |= trap_CM_TransformedPointContents( point, cmodel, ent->origin, ent->angles ); - } - - return contents; -} - - -/* -======================== -CG_InterpolatePlayerState - -Generates cg.predictedPlayerState by interpolating between -cg.snap->player_state and cg.nextFrame->player_state -======================== -*/ -static void CG_InterpolatePlayerState( qboolean grabAngles ) { - float f; - int i; - playerState_t *out; - snapshot_t *prev, *next; - - out = &cg.predictedPlayerState; - prev = cg.snap; - next = cg.nextSnap; - - *out = cg.snap->ps; - - // if we are still allowing local input, short circuit the view angles - if ( grabAngles ) { - usercmd_t cmd; - int cmdNum; - - cmdNum = trap_GetCurrentCmdNumber(); - trap_GetUserCmd( cmdNum, &cmd ); - - PM_UpdateViewAngles( out, &cmd ); - } - - // if the next frame is a teleport, we can't lerp to it - if ( cg.nextFrameTeleport ) { - return; - } - - if ( !next || next->serverTime <= prev->serverTime ) { - return; - } - - f = (float)( cg.time - prev->serverTime ) / ( next->serverTime - prev->serverTime ); - - i = next->ps.bobCycle; - if ( i < prev->ps.bobCycle ) { - i += 256; // handle wraparound - } - out->bobCycle = prev->ps.bobCycle + f * ( i - prev->ps.bobCycle ); - - for ( i = 0 ; i < 3 ; i++ ) { - out->origin[i] = prev->ps.origin[i] + f * (next->ps.origin[i] - prev->ps.origin[i] ); - if ( !grabAngles ) { - out->viewangles[i] = LerpAngle( - prev->ps.viewangles[i], next->ps.viewangles[i], f ); - } - out->velocity[i] = prev->ps.velocity[i] + - f * (next->ps.velocity[i] - prev->ps.velocity[i] ); - } - -} - -/* -=================== -CG_TouchItem -=================== -*/ -static void CG_TouchItem( centity_t *cent ) { - gitem_t *item; - - if ( !cg_predictItems.integer ) { - return; - } - if ( !BG_PlayerTouchesItem( &cg.predictedPlayerState, ¢->currentState, cg.time ) ) { - return; - } - - // never pick an item up twice in a prediction - if ( cent->miscTime == cg.time ) { - return; - } - - if ( !BG_CanItemBeGrabbed( cgs.gametype, ¢->currentState, &cg.predictedPlayerState ) ) { - return; // can't hold it - } - - item = &bg_itemlist[ cent->currentState.modelindex ]; - - // Special case for flags. - // We don't predict touching our own flag -#ifdef MISSIONPACK - if( cgs.gametype == GT_1FCTF ) { - if( item->giTag != PW_NEUTRALFLAG ) { - return; - } - } - if( cgs.gametype == GT_CTF || cgs.gametype == GT_HARVESTER ) { -#else - if( cgs.gametype == GT_CTF ) { -#endif - if (cg.predictedPlayerState.persistant[PERS_TEAM] == TEAM_RED && - item->giTag == PW_REDFLAG) - return; - if (cg.predictedPlayerState.persistant[PERS_TEAM] == TEAM_BLUE && - item->giTag == PW_BLUEFLAG) - return; - } - - // grab it - BG_AddPredictableEventToPlayerstate( EV_ITEM_PICKUP, cent->currentState.modelindex , &cg.predictedPlayerState); - - // remove it from the frame so it won't be drawn - cent->currentState.eFlags |= EF_NODRAW; - - // don't touch it again this prediction - cent->miscTime = cg.time; - - // if its a weapon, give them some predicted ammo so the autoswitch will work - if ( item->giType == IT_WEAPON ) { - cg.predictedPlayerState.stats[ STAT_WEAPONS ] |= 1 << item->giTag; - if ( !cg.predictedPlayerState.ammo[ item->giTag ] ) { - cg.predictedPlayerState.ammo[ item->giTag ] = 1; - } - } -} - - -/* -========================= -CG_TouchTriggerPrediction - -Predict push triggers and items -========================= -*/ -static void CG_TouchTriggerPrediction( void ) { - int i; - trace_t trace; - entityState_t *ent; - clipHandle_t cmodel; - centity_t *cent; - qboolean spectator; - - // dead clients don't activate triggers - if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) { - return; - } - - spectator = ( cg.predictedPlayerState.pm_type == PM_SPECTATOR ); - - if ( cg.predictedPlayerState.pm_type != PM_NORMAL && !spectator ) { - return; - } - - for ( i = 0 ; i < cg_numTriggerEntities ; i++ ) { - cent = cg_triggerEntities[ i ]; - ent = ¢->currentState; - - if ( ent->eType == ET_ITEM && !spectator ) { - CG_TouchItem( cent ); - continue; - } - - if ( ent->solid != SOLID_BMODEL ) { - continue; - } - - cmodel = trap_CM_InlineModel( ent->modelindex ); - if ( !cmodel ) { - continue; - } - - trap_CM_BoxTrace( &trace, cg.predictedPlayerState.origin, cg.predictedPlayerState.origin, - cg_pmove.mins, cg_pmove.maxs, cmodel, -1 ); - - if ( !trace.startsolid ) { - continue; - } - - if ( ent->eType == ET_TELEPORT_TRIGGER ) { - cg.hyperspace = qtrue; - } else if ( ent->eType == ET_PUSH_TRIGGER ) { - BG_TouchJumpPad( &cg.predictedPlayerState, ent ); - } - } - - // if we didn't touch a jump pad this pmove frame - if ( cg.predictedPlayerState.jumppad_frame != cg.predictedPlayerState.pmove_framecount ) { - cg.predictedPlayerState.jumppad_frame = 0; - cg.predictedPlayerState.jumppad_ent = 0; - } -} - - - -/* -================= -CG_PredictPlayerState - -Generates cg.predictedPlayerState for the current cg.time -cg.predictedPlayerState is guaranteed to be valid after exiting. - -For demo playback, this will be an interpolation between two valid -playerState_t. - -For normal gameplay, it will be the result of predicted usercmd_t on -top of the most recent playerState_t received from the server. - -Each new snapshot will usually have one or more new usercmd over the last, -but we simulate all unacknowledged commands each time, not just the new ones. -This means that on an internet connection, quite a few pmoves may be issued -each frame. - -OPTIMIZE: don't re-simulate unless the newly arrived snapshot playerState_t -differs from the predicted one. Would require saving all intermediate -playerState_t during prediction. - -We detect prediction errors and allow them to be decayed off over several frames -to ease the jerk. -================= -*/ -void CG_PredictPlayerState( void ) { - int cmdNum, current; - playerState_t oldPlayerState; - qboolean moved; - usercmd_t oldestCmd; - usercmd_t latestCmd; - - cg.hyperspace = qfalse; // will be set if touching a trigger_teleport - - // if this is the first frame we must guarantee - // predictedPlayerState is valid even if there is some - // other error condition - if ( !cg.validPPS ) { - cg.validPPS = qtrue; - cg.predictedPlayerState = cg.snap->ps; - } - - - // demo playback just copies the moves - if ( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW) ) { - CG_InterpolatePlayerState( qfalse ); - return; - } - - // non-predicting local movement will grab the latest angles - if ( cg_nopredict.integer || cg_synchronousClients.integer ) { - CG_InterpolatePlayerState( qtrue ); - return; - } - - // prepare for pmove - cg_pmove.ps = &cg.predictedPlayerState; - cg_pmove.trace = CG_Trace; - cg_pmove.pointcontents = CG_PointContents; - if ( cg_pmove.ps->pm_type == PM_DEAD ) { - cg_pmove.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; - } - else { - cg_pmove.tracemask = MASK_PLAYERSOLID; - } - if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR ) { - cg_pmove.tracemask &= ~CONTENTS_BODY; // spectators can fly through bodies - } - cg_pmove.noFootsteps = ( cgs.dmflags & DF_NO_FOOTSTEPS ) > 0; - - // save the state before the pmove so we can detect transitions - oldPlayerState = cg.predictedPlayerState; - - current = trap_GetCurrentCmdNumber(); - - // if we don't have the commands right after the snapshot, we - // can't accurately predict a current position, so just freeze at - // the last good position we had - cmdNum = current - CMD_BACKUP + 1; - trap_GetUserCmd( cmdNum, &oldestCmd ); - if ( oldestCmd.serverTime > cg.snap->ps.commandTime - && oldestCmd.serverTime < cg.time ) { // special check for map_restart - if ( cg_showmiss.integer ) { - CG_Printf ("exceeded PACKET_BACKUP on commands\n"); - } - return; - } - - // get the latest command so we can know which commands are from previous map_restarts - trap_GetUserCmd( current, &latestCmd ); - - // get the most recent information we have, even if - // the server time is beyond our current cg.time, - // because predicted player positions are going to - // be ahead of everything else anyway - if ( cg.nextSnap && !cg.nextFrameTeleport && !cg.thisFrameTeleport ) { - cg.predictedPlayerState = cg.nextSnap->ps; - cg.physicsTime = cg.nextSnap->serverTime; - } else { - cg.predictedPlayerState = cg.snap->ps; - cg.physicsTime = cg.snap->serverTime; - } - - if ( pmove_msec.integer < 8 ) { - trap_Cvar_Set("pmove_msec", "8"); - } - else if (pmove_msec.integer > 33) { - trap_Cvar_Set("pmove_msec", "33"); - } - - cg_pmove.pmove_fixed = pmove_fixed.integer;// | cg_pmove_fixed.integer; - cg_pmove.pmove_msec = pmove_msec.integer; - - // run cmds - moved = qfalse; - for ( cmdNum = current - CMD_BACKUP + 1 ; cmdNum <= current ; cmdNum++ ) { - // get the command - trap_GetUserCmd( cmdNum, &cg_pmove.cmd ); - - if ( cg_pmove.pmove_fixed ) { - PM_UpdateViewAngles( cg_pmove.ps, &cg_pmove.cmd ); - } - - // don't do anything if the time is before the snapshot player time - if ( cg_pmove.cmd.serverTime <= cg.predictedPlayerState.commandTime ) { - continue; - } - - // don't do anything if the command was from a previous map_restart - if ( cg_pmove.cmd.serverTime > latestCmd.serverTime ) { - continue; - } - - // check for a prediction error from last frame - // on a lan, this will often be the exact value - // from the snapshot, but on a wan we will have - // to predict several commands to get to the point - // we want to compare - if ( cg.predictedPlayerState.commandTime == oldPlayerState.commandTime ) { - vec3_t delta; - float len; - - if ( cg.thisFrameTeleport ) { - // a teleport will not cause an error decay - VectorClear( cg.predictedError ); - if ( cg_showmiss.integer ) { - CG_Printf( "PredictionTeleport\n" ); - } - cg.thisFrameTeleport = qfalse; - } else { - vec3_t adjusted; - CG_AdjustPositionForMover( cg.predictedPlayerState.origin, - cg.predictedPlayerState.groundEntityNum, cg.physicsTime, cg.oldTime, adjusted ); - - if ( cg_showmiss.integer ) { - if (!VectorCompare( oldPlayerState.origin, adjusted )) { - CG_Printf("prediction error\n"); - } - } - VectorSubtract( oldPlayerState.origin, adjusted, delta ); - len = VectorLength( delta ); - if ( len > 0.1 ) { - if ( cg_showmiss.integer ) { - CG_Printf("Prediction miss: %f\n", len); - } - if ( cg_errorDecay.integer ) { - int t; - float f; - - t = cg.time - cg.predictedErrorTime; - f = ( cg_errorDecay.value - t ) / cg_errorDecay.value; - if ( f < 0 ) { - f = 0; - } - if ( f > 0 && cg_showmiss.integer ) { - CG_Printf("Double prediction decay: %f\n", f); - } - VectorScale( cg.predictedError, f, cg.predictedError ); - } else { - VectorClear( cg.predictedError ); - } - VectorAdd( delta, cg.predictedError, cg.predictedError ); - cg.predictedErrorTime = cg.oldTime; - } - } - } - - // don't predict gauntlet firing, which is only supposed to happen - // when it actually inflicts damage - cg_pmove.gauntletHit = qfalse; - - if ( cg_pmove.pmove_fixed ) { - cg_pmove.cmd.serverTime = ((cg_pmove.cmd.serverTime + pmove_msec.integer-1) / pmove_msec.integer) * pmove_msec.integer; - } - - Pmove (&cg_pmove); - - moved = qtrue; - - // add push trigger movement effects - CG_TouchTriggerPrediction(); - - // check for predictable events that changed from previous predictions - //CG_CheckChangedPredictableEvents(&cg.predictedPlayerState); - } - - if ( cg_showmiss.integer > 1 ) { - CG_Printf( "[%i : %i] ", cg_pmove.cmd.serverTime, cg.time ); - } - - if ( !moved ) { - if ( cg_showmiss.integer ) { - CG_Printf( "not moved\n" ); - } - return; - } - - // adjust for the movement of the groundentity - CG_AdjustPositionForMover( cg.predictedPlayerState.origin, - cg.predictedPlayerState.groundEntityNum, - cg.physicsTime, cg.time, cg.predictedPlayerState.origin ); - - if ( cg_showmiss.integer ) { - if (cg.predictedPlayerState.eventSequence > oldPlayerState.eventSequence + MAX_PS_EVENTS) { - CG_Printf("WARNING: dropped event\n"); - } - } - - // fire events and other transition triggered things - CG_TransitionPlayerState( &cg.predictedPlayerState, &oldPlayerState ); - - if ( cg_showmiss.integer ) { - if (cg.eventSequence > cg.predictedPlayerState.eventSequence) { - CG_Printf("WARNING: double event\n"); - cg.eventSequence = cg.predictedPlayerState.eventSequence; - } - } -} - - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// cg_predict.c -- this file generates cg.predictedPlayerState by either +// interpolating between snapshots from the server or locally predicting +// ahead the client's movement. +// It also handles local physics interaction, like fragments bouncing off walls + +#include "cg_local.h" + +static pmove_t cg_pmove; + +static int cg_numSolidEntities; +static centity_t *cg_solidEntities[MAX_ENTITIES_IN_SNAPSHOT]; +static int cg_numTriggerEntities; +static centity_t *cg_triggerEntities[MAX_ENTITIES_IN_SNAPSHOT]; + +/* +==================== +CG_BuildSolidList + +When a new cg.snap has been set, this function builds a sublist +of the entities that are actually solid, to make for more +efficient collision detection +==================== +*/ +void CG_BuildSolidList( void ) { + int i; + centity_t *cent; + snapshot_t *snap; + entityState_t *ent; + + cg_numSolidEntities = 0; + cg_numTriggerEntities = 0; + + if ( cg.nextSnap && !cg.nextFrameTeleport && !cg.thisFrameTeleport ) { + snap = cg.nextSnap; + } else { + snap = cg.snap; + } + + for ( i = 0 ; i < snap->numEntities ; i++ ) { + cent = &cg_entities[ snap->entities[ i ].number ]; + ent = ¢->currentState; + + if ( ent->eType == ET_ITEM || ent->eType == ET_PUSH_TRIGGER || ent->eType == ET_TELEPORT_TRIGGER ) { + cg_triggerEntities[cg_numTriggerEntities] = cent; + cg_numTriggerEntities++; + continue; + } + + if ( cent->nextState.solid ) { + cg_solidEntities[cg_numSolidEntities] = cent; + cg_numSolidEntities++; + continue; + } + } +} + +/* +==================== +CG_ClipMoveToEntities + +==================== +*/ +static void CG_ClipMoveToEntities ( const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, + int skipNumber, int mask, trace_t *tr ) { + int i, x, zd, zu; + trace_t trace; + entityState_t *ent; + clipHandle_t cmodel; + vec3_t bmins, bmaxs; + vec3_t origin, angles; + centity_t *cent; + + for ( i = 0 ; i < cg_numSolidEntities ; i++ ) { + cent = cg_solidEntities[ i ]; + ent = ¢->currentState; + + if ( ent->number == skipNumber ) { + continue; + } + + if ( ent->solid == SOLID_BMODEL ) { + // special value for bmodel + cmodel = trap_CM_InlineModel( ent->modelindex ); + VectorCopy( cent->lerpAngles, angles ); + BG_EvaluateTrajectory( ¢->currentState.pos, cg.physicsTime, origin ); + } else { + // encoded bbox + x = (ent->solid & 255); + zd = ((ent->solid>>8) & 255); + zu = ((ent->solid>>16) & 255) - 32; + + bmins[0] = bmins[1] = -x; + bmaxs[0] = bmaxs[1] = x; + bmins[2] = -zd; + bmaxs[2] = zu; + + cmodel = trap_CM_TempBoxModel( bmins, bmaxs ); + VectorCopy( vec3_origin, angles ); + VectorCopy( cent->lerpOrigin, origin ); + } + + + trap_CM_TransformedBoxTrace ( &trace, start, end, + mins, maxs, cmodel, mask, origin, angles); + + if (trace.allsolid || trace.fraction < tr->fraction) { + trace.entityNum = ent->number; + *tr = trace; + } else if (trace.startsolid) { + tr->startsolid = qtrue; + } + if ( tr->allsolid ) { + return; + } + } +} + +/* +================ +CG_Trace +================ +*/ +void CG_Trace( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, + int skipNumber, int mask ) { + trace_t t; + + trap_CM_BoxTrace ( &t, start, end, mins, maxs, 0, mask); + t.entityNum = t.fraction != 1.0 ? ENTITYNUM_WORLD : ENTITYNUM_NONE; + // check all other solid models + CG_ClipMoveToEntities (start, mins, maxs, end, skipNumber, mask, &t); + + *result = t; +} + +/* +================ +CG_PointContents +================ +*/ +int CG_PointContents( const vec3_t point, int passEntityNum ) { + int i; + entityState_t *ent; + centity_t *cent; + clipHandle_t cmodel; + int contents; + + contents = trap_CM_PointContents (point, 0); + + for ( i = 0 ; i < cg_numSolidEntities ; i++ ) { + cent = cg_solidEntities[ i ]; + + ent = ¢->currentState; + + if ( ent->number == passEntityNum ) { + continue; + } + + if (ent->solid != SOLID_BMODEL) { // special value for bmodel + continue; + } + + cmodel = trap_CM_InlineModel( ent->modelindex ); + if ( !cmodel ) { + continue; + } + + contents |= trap_CM_TransformedPointContents( point, cmodel, ent->origin, ent->angles ); + } + + return contents; +} + + +/* +======================== +CG_InterpolatePlayerState + +Generates cg.predictedPlayerState by interpolating between +cg.snap->player_state and cg.nextFrame->player_state +======================== +*/ +static void CG_InterpolatePlayerState( qboolean grabAngles ) { + float f; + int i; + playerState_t *out; + snapshot_t *prev, *next; + + out = &cg.predictedPlayerState; + prev = cg.snap; + next = cg.nextSnap; + + *out = cg.snap->ps; + + // if we are still allowing local input, short circuit the view angles + if ( grabAngles ) { + usercmd_t cmd; + int cmdNum; + + cmdNum = trap_GetCurrentCmdNumber(); + trap_GetUserCmd( cmdNum, &cmd ); + + PM_UpdateViewAngles( out, &cmd ); + } + + // if the next frame is a teleport, we can't lerp to it + if ( cg.nextFrameTeleport ) { + return; + } + + if ( !next || next->serverTime <= prev->serverTime ) { + return; + } + + f = (float)( cg.time - prev->serverTime ) / ( next->serverTime - prev->serverTime ); + + i = next->ps.bobCycle; + if ( i < prev->ps.bobCycle ) { + i += 256; // handle wraparound + } + out->bobCycle = prev->ps.bobCycle + f * ( i - prev->ps.bobCycle ); + + for ( i = 0 ; i < 3 ; i++ ) { + out->origin[i] = prev->ps.origin[i] + f * (next->ps.origin[i] - prev->ps.origin[i] ); + if ( !grabAngles ) { + out->viewangles[i] = LerpAngle( + prev->ps.viewangles[i], next->ps.viewangles[i], f ); + } + out->velocity[i] = prev->ps.velocity[i] + + f * (next->ps.velocity[i] - prev->ps.velocity[i] ); + } + +} + +/* +=================== +CG_TouchItem +=================== +*/ +static void CG_TouchItem( centity_t *cent ) { + gitem_t *item; + + if ( !cg_predictItems.integer ) { + return; + } + if ( !BG_PlayerTouchesItem( &cg.predictedPlayerState, ¢->currentState, cg.time ) ) { + return; + } + + // never pick an item up twice in a prediction + if ( cent->miscTime == cg.time ) { + return; + } + + if ( !BG_CanItemBeGrabbed( cgs.gametype, ¢->currentState, &cg.predictedPlayerState ) ) { + return; // can't hold it + } + + item = &bg_itemlist[ cent->currentState.modelindex ]; + + // Special case for flags. + // We don't predict touching our own flag +#ifdef MISSIONPACK + if( cgs.gametype == GT_1FCTF ) { + if( item->giTag != PW_NEUTRALFLAG ) { + return; + } + } + if( cgs.gametype == GT_CTF || cgs.gametype == GT_HARVESTER ) { +#else + if( cgs.gametype == GT_CTF ) { +#endif + if (cg.predictedPlayerState.persistant[PERS_TEAM] == TEAM_RED && + item->giTag == PW_REDFLAG) + return; + if (cg.predictedPlayerState.persistant[PERS_TEAM] == TEAM_BLUE && + item->giTag == PW_BLUEFLAG) + return; + } + + // grab it + BG_AddPredictableEventToPlayerstate( EV_ITEM_PICKUP, cent->currentState.modelindex , &cg.predictedPlayerState); + + // remove it from the frame so it won't be drawn + cent->currentState.eFlags |= EF_NODRAW; + + // don't touch it again this prediction + cent->miscTime = cg.time; + + // if its a weapon, give them some predicted ammo so the autoswitch will work + if ( item->giType == IT_WEAPON ) { + cg.predictedPlayerState.stats[ STAT_WEAPONS ] |= 1 << item->giTag; + if ( !cg.predictedPlayerState.ammo[ item->giTag ] ) { + cg.predictedPlayerState.ammo[ item->giTag ] = 1; + } + } +} + + +/* +========================= +CG_TouchTriggerPrediction + +Predict push triggers and items +========================= +*/ +static void CG_TouchTriggerPrediction( void ) { + int i; + trace_t trace; + entityState_t *ent; + clipHandle_t cmodel; + centity_t *cent; + qboolean spectator; + + // dead clients don't activate triggers + if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) { + return; + } + + spectator = ( cg.predictedPlayerState.pm_type == PM_SPECTATOR ); + + if ( cg.predictedPlayerState.pm_type != PM_NORMAL && !spectator ) { + return; + } + + for ( i = 0 ; i < cg_numTriggerEntities ; i++ ) { + cent = cg_triggerEntities[ i ]; + ent = ¢->currentState; + + if ( ent->eType == ET_ITEM && !spectator ) { + CG_TouchItem( cent ); + continue; + } + + if ( ent->solid != SOLID_BMODEL ) { + continue; + } + + cmodel = trap_CM_InlineModel( ent->modelindex ); + if ( !cmodel ) { + continue; + } + + trap_CM_BoxTrace( &trace, cg.predictedPlayerState.origin, cg.predictedPlayerState.origin, + cg_pmove.mins, cg_pmove.maxs, cmodel, -1 ); + + if ( !trace.startsolid ) { + continue; + } + + if ( ent->eType == ET_TELEPORT_TRIGGER ) { + cg.hyperspace = qtrue; + } else if ( ent->eType == ET_PUSH_TRIGGER ) { + BG_TouchJumpPad( &cg.predictedPlayerState, ent ); + } + } + + // if we didn't touch a jump pad this pmove frame + if ( cg.predictedPlayerState.jumppad_frame != cg.predictedPlayerState.pmove_framecount ) { + cg.predictedPlayerState.jumppad_frame = 0; + cg.predictedPlayerState.jumppad_ent = 0; + } +} + + + +/* +================= +CG_PredictPlayerState + +Generates cg.predictedPlayerState for the current cg.time +cg.predictedPlayerState is guaranteed to be valid after exiting. + +For demo playback, this will be an interpolation between two valid +playerState_t. + +For normal gameplay, it will be the result of predicted usercmd_t on +top of the most recent playerState_t received from the server. + +Each new snapshot will usually have one or more new usercmd over the last, +but we simulate all unacknowledged commands each time, not just the new ones. +This means that on an internet connection, quite a few pmoves may be issued +each frame. + +OPTIMIZE: don't re-simulate unless the newly arrived snapshot playerState_t +differs from the predicted one. Would require saving all intermediate +playerState_t during prediction. + +We detect prediction errors and allow them to be decayed off over several frames +to ease the jerk. +================= +*/ +void CG_PredictPlayerState( void ) { + int cmdNum, current; + playerState_t oldPlayerState; + qboolean moved; + usercmd_t oldestCmd; + usercmd_t latestCmd; + + cg.hyperspace = qfalse; // will be set if touching a trigger_teleport + + // if this is the first frame we must guarantee + // predictedPlayerState is valid even if there is some + // other error condition + if ( !cg.validPPS ) { + cg.validPPS = qtrue; + cg.predictedPlayerState = cg.snap->ps; + } + + + // demo playback just copies the moves + if ( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW) ) { + CG_InterpolatePlayerState( qfalse ); + return; + } + + // non-predicting local movement will grab the latest angles + if ( cg_nopredict.integer || cg_synchronousClients.integer ) { + CG_InterpolatePlayerState( qtrue ); + return; + } + + // prepare for pmove + cg_pmove.ps = &cg.predictedPlayerState; + cg_pmove.trace = CG_Trace; + cg_pmove.pointcontents = CG_PointContents; + if ( cg_pmove.ps->pm_type == PM_DEAD ) { + cg_pmove.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; + } + else { + cg_pmove.tracemask = MASK_PLAYERSOLID; + } + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR ) { + cg_pmove.tracemask &= ~CONTENTS_BODY; // spectators can fly through bodies + } + cg_pmove.noFootsteps = ( cgs.dmflags & DF_NO_FOOTSTEPS ) > 0; + + // save the state before the pmove so we can detect transitions + oldPlayerState = cg.predictedPlayerState; + + current = trap_GetCurrentCmdNumber(); + + // if we don't have the commands right after the snapshot, we + // can't accurately predict a current position, so just freeze at + // the last good position we had + cmdNum = current - CMD_BACKUP + 1; + trap_GetUserCmd( cmdNum, &oldestCmd ); + if ( oldestCmd.serverTime > cg.snap->ps.commandTime + && oldestCmd.serverTime < cg.time ) { // special check for map_restart + if ( cg_showmiss.integer ) { + CG_Printf ("exceeded PACKET_BACKUP on commands\n"); + } + return; + } + + // get the latest command so we can know which commands are from previous map_restarts + trap_GetUserCmd( current, &latestCmd ); + + // get the most recent information we have, even if + // the server time is beyond our current cg.time, + // because predicted player positions are going to + // be ahead of everything else anyway + if ( cg.nextSnap && !cg.nextFrameTeleport && !cg.thisFrameTeleport ) { + cg.predictedPlayerState = cg.nextSnap->ps; + cg.physicsTime = cg.nextSnap->serverTime; + } else { + cg.predictedPlayerState = cg.snap->ps; + cg.physicsTime = cg.snap->serverTime; + } + + if ( pmove_msec.integer < 8 ) { + trap_Cvar_Set("pmove_msec", "8"); + } + else if (pmove_msec.integer > 33) { + trap_Cvar_Set("pmove_msec", "33"); + } + + cg_pmove.pmove_fixed = pmove_fixed.integer;// | cg_pmove_fixed.integer; + cg_pmove.pmove_msec = pmove_msec.integer; + + // run cmds + moved = qfalse; + for ( cmdNum = current - CMD_BACKUP + 1 ; cmdNum <= current ; cmdNum++ ) { + // get the command + trap_GetUserCmd( cmdNum, &cg_pmove.cmd ); + + if ( cg_pmove.pmove_fixed ) { + PM_UpdateViewAngles( cg_pmove.ps, &cg_pmove.cmd ); + } + + // don't do anything if the time is before the snapshot player time + if ( cg_pmove.cmd.serverTime <= cg.predictedPlayerState.commandTime ) { + continue; + } + + // don't do anything if the command was from a previous map_restart + if ( cg_pmove.cmd.serverTime > latestCmd.serverTime ) { + continue; + } + + // check for a prediction error from last frame + // on a lan, this will often be the exact value + // from the snapshot, but on a wan we will have + // to predict several commands to get to the point + // we want to compare + if ( cg.predictedPlayerState.commandTime == oldPlayerState.commandTime ) { + vec3_t delta; + float len; + + if ( cg.thisFrameTeleport ) { + // a teleport will not cause an error decay + VectorClear( cg.predictedError ); + if ( cg_showmiss.integer ) { + CG_Printf( "PredictionTeleport\n" ); + } + cg.thisFrameTeleport = qfalse; + } else { + vec3_t adjusted; + CG_AdjustPositionForMover( cg.predictedPlayerState.origin, + cg.predictedPlayerState.groundEntityNum, cg.physicsTime, cg.oldTime, adjusted ); + + if ( cg_showmiss.integer ) { + if (!VectorCompare( oldPlayerState.origin, adjusted )) { + CG_Printf("prediction error\n"); + } + } + VectorSubtract( oldPlayerState.origin, adjusted, delta ); + len = VectorLength( delta ); + if ( len > 0.1 ) { + if ( cg_showmiss.integer ) { + CG_Printf("Prediction miss: %f\n", len); + } + if ( cg_errorDecay.integer ) { + int t; + float f; + + t = cg.time - cg.predictedErrorTime; + f = ( cg_errorDecay.value - t ) / cg_errorDecay.value; + if ( f < 0 ) { + f = 0; + } + if ( f > 0 && cg_showmiss.integer ) { + CG_Printf("Double prediction decay: %f\n", f); + } + VectorScale( cg.predictedError, f, cg.predictedError ); + } else { + VectorClear( cg.predictedError ); + } + VectorAdd( delta, cg.predictedError, cg.predictedError ); + cg.predictedErrorTime = cg.oldTime; + } + } + } + + // don't predict gauntlet firing, which is only supposed to happen + // when it actually inflicts damage + cg_pmove.gauntletHit = qfalse; + + if ( cg_pmove.pmove_fixed ) { + cg_pmove.cmd.serverTime = ((cg_pmove.cmd.serverTime + pmove_msec.integer-1) / pmove_msec.integer) * pmove_msec.integer; + } + + Pmove (&cg_pmove); + + moved = qtrue; + + // add push trigger movement effects + CG_TouchTriggerPrediction(); + + // check for predictable events that changed from previous predictions + //CG_CheckChangedPredictableEvents(&cg.predictedPlayerState); + } + + if ( cg_showmiss.integer > 1 ) { + CG_Printf( "[%i : %i] ", cg_pmove.cmd.serverTime, cg.time ); + } + + if ( !moved ) { + if ( cg_showmiss.integer ) { + CG_Printf( "not moved\n" ); + } + return; + } + + // adjust for the movement of the groundentity + CG_AdjustPositionForMover( cg.predictedPlayerState.origin, + cg.predictedPlayerState.groundEntityNum, + cg.physicsTime, cg.time, cg.predictedPlayerState.origin ); + + if ( cg_showmiss.integer ) { + if (cg.predictedPlayerState.eventSequence > oldPlayerState.eventSequence + MAX_PS_EVENTS) { + CG_Printf("WARNING: dropped event\n"); + } + } + + // fire events and other transition triggered things + CG_TransitionPlayerState( &cg.predictedPlayerState, &oldPlayerState ); + + if ( cg_showmiss.integer ) { + if (cg.eventSequence > cg.predictedPlayerState.eventSequence) { + CG_Printf("WARNING: double event\n"); + cg.eventSequence = cg.predictedPlayerState.eventSequence; + } + } +} + + diff --git a/code/cgame/cg_public.h b/code/cgame/cg_public.h index 8514143..8f8ea6d 100755 --- a/code/cgame/cg_public.h +++ b/code/cgame/cg_public.h @@ -1,238 +1,238 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - - -#define CMD_BACKUP 64 -#define CMD_MASK (CMD_BACKUP - 1) -// allow a lot of command backups for very fast systems -// multiple commands may be combined into a single packet, so this -// needs to be larger than PACKET_BACKUP - - -#define MAX_ENTITIES_IN_SNAPSHOT 256 - -// snapshots are a view of the server at a given time - -// Snapshots are generated at regular time intervals by the server, -// but they may not be sent if a client's rate level is exceeded, or -// they may be dropped by the network. -typedef struct { - int snapFlags; // SNAPFLAG_RATE_DELAYED, etc - int ping; - - int serverTime; // server time the message is valid for (in msec) - - byte areamask[MAX_MAP_AREA_BYTES]; // portalarea visibility bits - - playerState_t ps; // complete information about the current player at this time - - int numEntities; // all of the entities that need to be presented - entityState_t entities[MAX_ENTITIES_IN_SNAPSHOT]; // at the time of this snapshot - - int numServerCommands; // text based server commands to execute when this - int serverCommandSequence; // snapshot becomes current -} snapshot_t; - -enum { - CGAME_EVENT_NONE, - CGAME_EVENT_TEAMMENU, - CGAME_EVENT_SCOREBOARD, - CGAME_EVENT_EDITHUD -}; - - -/* -================================================================== - -functions imported from the main executable - -================================================================== -*/ - -#define CGAME_IMPORT_API_VERSION 4 - -typedef enum { - CG_PRINT, - CG_ERROR, - CG_MILLISECONDS, - CG_CVAR_REGISTER, - CG_CVAR_UPDATE, - CG_CVAR_SET, - CG_CVAR_VARIABLESTRINGBUFFER, - CG_ARGC, - CG_ARGV, - CG_ARGS, - CG_FS_FOPENFILE, - CG_FS_READ, - CG_FS_WRITE, - CG_FS_FCLOSEFILE, - CG_SENDCONSOLECOMMAND, - CG_ADDCOMMAND, - CG_SENDCLIENTCOMMAND, - CG_UPDATESCREEN, - CG_CM_LOADMAP, - CG_CM_NUMINLINEMODELS, - CG_CM_INLINEMODEL, - CG_CM_LOADMODEL, - CG_CM_TEMPBOXMODEL, - CG_CM_POINTCONTENTS, - CG_CM_TRANSFORMEDPOINTCONTENTS, - CG_CM_BOXTRACE, - CG_CM_TRANSFORMEDBOXTRACE, - CG_CM_MARKFRAGMENTS, - CG_S_STARTSOUND, - CG_S_STARTLOCALSOUND, - CG_S_CLEARLOOPINGSOUNDS, - CG_S_ADDLOOPINGSOUND, - CG_S_UPDATEENTITYPOSITION, - CG_S_RESPATIALIZE, - CG_S_REGISTERSOUND, - CG_S_STARTBACKGROUNDTRACK, - CG_R_LOADWORLDMAP, - CG_R_REGISTERMODEL, - CG_R_REGISTERSKIN, - CG_R_REGISTERSHADER, - CG_R_CLEARSCENE, - CG_R_ADDREFENTITYTOSCENE, - CG_R_ADDPOLYTOSCENE, - CG_R_ADDLIGHTTOSCENE, - CG_R_RENDERSCENE, - CG_R_SETCOLOR, - CG_R_DRAWSTRETCHPIC, - CG_R_MODELBOUNDS, - CG_R_LERPTAG, - CG_GETGLCONFIG, - CG_GETGAMESTATE, - CG_GETCURRENTSNAPSHOTNUMBER, - CG_GETSNAPSHOT, - CG_GETSERVERCOMMAND, - CG_GETCURRENTCMDNUMBER, - CG_GETUSERCMD, - CG_SETUSERCMDVALUE, - CG_R_REGISTERSHADERNOMIP, - CG_MEMORY_REMAINING, - CG_R_REGISTERFONT, - CG_KEY_ISDOWN, - CG_KEY_GETCATCHER, - CG_KEY_SETCATCHER, - CG_KEY_GETKEY, - CG_PC_ADD_GLOBAL_DEFINE, - CG_PC_LOAD_SOURCE, - CG_PC_FREE_SOURCE, - CG_PC_READ_TOKEN, - CG_PC_SOURCE_FILE_AND_LINE, - CG_S_STOPBACKGROUNDTRACK, - CG_REAL_TIME, - CG_SNAPVECTOR, - CG_REMOVECOMMAND, - CG_R_LIGHTFORPOINT, - CG_CIN_PLAYCINEMATIC, - CG_CIN_STOPCINEMATIC, - CG_CIN_RUNCINEMATIC, - CG_CIN_DRAWCINEMATIC, - CG_CIN_SETEXTENTS, - CG_R_REMAP_SHADER, - CG_S_ADDREALLOOPINGSOUND, - CG_S_STOPLOOPINGSOUND, - - CG_CM_TEMPCAPSULEMODEL, - CG_CM_CAPSULETRACE, - CG_CM_TRANSFORMEDCAPSULETRACE, - CG_R_ADDADDITIVELIGHTTOSCENE, - CG_GET_ENTITY_TOKEN, - CG_R_ADDPOLYSTOSCENE, - CG_R_INPVS, - // 1.32 - CG_FS_SEEK, - -/* - CG_LOADCAMERA, - CG_STARTCAMERA, - CG_GETCAMERAINFO, -*/ - - CG_MEMSET = 100, - CG_MEMCPY, - CG_STRNCPY, - CG_SIN, - CG_COS, - CG_ATAN2, - CG_SQRT, - CG_FLOOR, - CG_CEIL, - CG_TESTPRINTINT, - CG_TESTPRINTFLOAT, - CG_ACOS -} cgameImport_t; - - -/* -================================================================== - -functions exported to the main executable - -================================================================== -*/ - -typedef enum { - CG_INIT, -// void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) - // called when the level loads or when the renderer is restarted - // all media should be registered at this time - // cgame will display loading status by calling SCR_Update, which - // will call CG_DrawInformation during the loading process - // reliableCommandSequence will be 0 on fresh loads, but higher for - // demos, tourney restarts, or vid_restarts - - CG_SHUTDOWN, -// void (*CG_Shutdown)( void ); - // oportunity to flush and close any open files - - CG_CONSOLE_COMMAND, -// qboolean (*CG_ConsoleCommand)( void ); - // a console command has been issued locally that is not recognized by the - // main game system. - // use Cmd_Argc() / Cmd_Argv() to read the command, return qfalse if the - // command is not known to the game - - CG_DRAW_ACTIVE_FRAME, -// void (*CG_DrawActiveFrame)( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ); - // Generates and draws a game scene and status information at the given time. - // If demoPlayback is set, local movement prediction will not be enabled - - CG_CROSSHAIR_PLAYER, -// int (*CG_CrosshairPlayer)( void ); - - CG_LAST_ATTACKER, -// int (*CG_LastAttacker)( void ); - - CG_KEY_EVENT, -// void (*CG_KeyEvent)( int key, qboolean down ); - - CG_MOUSE_EVENT, -// void (*CG_MouseEvent)( int dx, int dy ); - CG_EVENT_HANDLING -// void (*CG_EventHandling)(int type); -} cgameExport_t; - -//---------------------------------------------- +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + + +#define CMD_BACKUP 64 +#define CMD_MASK (CMD_BACKUP - 1) +// allow a lot of command backups for very fast systems +// multiple commands may be combined into a single packet, so this +// needs to be larger than PACKET_BACKUP + + +#define MAX_ENTITIES_IN_SNAPSHOT 256 + +// snapshots are a view of the server at a given time + +// Snapshots are generated at regular time intervals by the server, +// but they may not be sent if a client's rate level is exceeded, or +// they may be dropped by the network. +typedef struct { + int snapFlags; // SNAPFLAG_RATE_DELAYED, etc + int ping; + + int serverTime; // server time the message is valid for (in msec) + + byte areamask[MAX_MAP_AREA_BYTES]; // portalarea visibility bits + + playerState_t ps; // complete information about the current player at this time + + int numEntities; // all of the entities that need to be presented + entityState_t entities[MAX_ENTITIES_IN_SNAPSHOT]; // at the time of this snapshot + + int numServerCommands; // text based server commands to execute when this + int serverCommandSequence; // snapshot becomes current +} snapshot_t; + +enum { + CGAME_EVENT_NONE, + CGAME_EVENT_TEAMMENU, + CGAME_EVENT_SCOREBOARD, + CGAME_EVENT_EDITHUD +}; + + +/* +================================================================== + +functions imported from the main executable + +================================================================== +*/ + +#define CGAME_IMPORT_API_VERSION 4 + +typedef enum { + CG_PRINT, + CG_ERROR, + CG_MILLISECONDS, + CG_CVAR_REGISTER, + CG_CVAR_UPDATE, + CG_CVAR_SET, + CG_CVAR_VARIABLESTRINGBUFFER, + CG_ARGC, + CG_ARGV, + CG_ARGS, + CG_FS_FOPENFILE, + CG_FS_READ, + CG_FS_WRITE, + CG_FS_FCLOSEFILE, + CG_SENDCONSOLECOMMAND, + CG_ADDCOMMAND, + CG_SENDCLIENTCOMMAND, + CG_UPDATESCREEN, + CG_CM_LOADMAP, + CG_CM_NUMINLINEMODELS, + CG_CM_INLINEMODEL, + CG_CM_LOADMODEL, + CG_CM_TEMPBOXMODEL, + CG_CM_POINTCONTENTS, + CG_CM_TRANSFORMEDPOINTCONTENTS, + CG_CM_BOXTRACE, + CG_CM_TRANSFORMEDBOXTRACE, + CG_CM_MARKFRAGMENTS, + CG_S_STARTSOUND, + CG_S_STARTLOCALSOUND, + CG_S_CLEARLOOPINGSOUNDS, + CG_S_ADDLOOPINGSOUND, + CG_S_UPDATEENTITYPOSITION, + CG_S_RESPATIALIZE, + CG_S_REGISTERSOUND, + CG_S_STARTBACKGROUNDTRACK, + CG_R_LOADWORLDMAP, + CG_R_REGISTERMODEL, + CG_R_REGISTERSKIN, + CG_R_REGISTERSHADER, + CG_R_CLEARSCENE, + CG_R_ADDREFENTITYTOSCENE, + CG_R_ADDPOLYTOSCENE, + CG_R_ADDLIGHTTOSCENE, + CG_R_RENDERSCENE, + CG_R_SETCOLOR, + CG_R_DRAWSTRETCHPIC, + CG_R_MODELBOUNDS, + CG_R_LERPTAG, + CG_GETGLCONFIG, + CG_GETGAMESTATE, + CG_GETCURRENTSNAPSHOTNUMBER, + CG_GETSNAPSHOT, + CG_GETSERVERCOMMAND, + CG_GETCURRENTCMDNUMBER, + CG_GETUSERCMD, + CG_SETUSERCMDVALUE, + CG_R_REGISTERSHADERNOMIP, + CG_MEMORY_REMAINING, + CG_R_REGISTERFONT, + CG_KEY_ISDOWN, + CG_KEY_GETCATCHER, + CG_KEY_SETCATCHER, + CG_KEY_GETKEY, + CG_PC_ADD_GLOBAL_DEFINE, + CG_PC_LOAD_SOURCE, + CG_PC_FREE_SOURCE, + CG_PC_READ_TOKEN, + CG_PC_SOURCE_FILE_AND_LINE, + CG_S_STOPBACKGROUNDTRACK, + CG_REAL_TIME, + CG_SNAPVECTOR, + CG_REMOVECOMMAND, + CG_R_LIGHTFORPOINT, + CG_CIN_PLAYCINEMATIC, + CG_CIN_STOPCINEMATIC, + CG_CIN_RUNCINEMATIC, + CG_CIN_DRAWCINEMATIC, + CG_CIN_SETEXTENTS, + CG_R_REMAP_SHADER, + CG_S_ADDREALLOOPINGSOUND, + CG_S_STOPLOOPINGSOUND, + + CG_CM_TEMPCAPSULEMODEL, + CG_CM_CAPSULETRACE, + CG_CM_TRANSFORMEDCAPSULETRACE, + CG_R_ADDADDITIVELIGHTTOSCENE, + CG_GET_ENTITY_TOKEN, + CG_R_ADDPOLYSTOSCENE, + CG_R_INPVS, + // 1.32 + CG_FS_SEEK, + +/* + CG_LOADCAMERA, + CG_STARTCAMERA, + CG_GETCAMERAINFO, +*/ + + CG_MEMSET = 100, + CG_MEMCPY, + CG_STRNCPY, + CG_SIN, + CG_COS, + CG_ATAN2, + CG_SQRT, + CG_FLOOR, + CG_CEIL, + CG_TESTPRINTINT, + CG_TESTPRINTFLOAT, + CG_ACOS +} cgameImport_t; + + +/* +================================================================== + +functions exported to the main executable + +================================================================== +*/ + +typedef enum { + CG_INIT, +// void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) + // called when the level loads or when the renderer is restarted + // all media should be registered at this time + // cgame will display loading status by calling SCR_Update, which + // will call CG_DrawInformation during the loading process + // reliableCommandSequence will be 0 on fresh loads, but higher for + // demos, tourney restarts, or vid_restarts + + CG_SHUTDOWN, +// void (*CG_Shutdown)( void ); + // oportunity to flush and close any open files + + CG_CONSOLE_COMMAND, +// qboolean (*CG_ConsoleCommand)( void ); + // a console command has been issued locally that is not recognized by the + // main game system. + // use Cmd_Argc() / Cmd_Argv() to read the command, return qfalse if the + // command is not known to the game + + CG_DRAW_ACTIVE_FRAME, +// void (*CG_DrawActiveFrame)( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ); + // Generates and draws a game scene and status information at the given time. + // If demoPlayback is set, local movement prediction will not be enabled + + CG_CROSSHAIR_PLAYER, +// int (*CG_CrosshairPlayer)( void ); + + CG_LAST_ATTACKER, +// int (*CG_LastAttacker)( void ); + + CG_KEY_EVENT, +// void (*CG_KeyEvent)( int key, qboolean down ); + + CG_MOUSE_EVENT, +// void (*CG_MouseEvent)( int dx, int dy ); + CG_EVENT_HANDLING +// void (*CG_EventHandling)(int type); +} cgameExport_t; + +//---------------------------------------------- diff --git a/code/cgame/cg_scoreboard.c b/code/cgame/cg_scoreboard.c index 25966c9..eb9d40b 100755 --- a/code/cgame/cg_scoreboard.c +++ b/code/cgame/cg_scoreboard.c @@ -1,534 +1,534 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// cg_scoreboard -- draw the scoreboard on top of the game screen -#include "cg_local.h" - - -#define SCOREBOARD_X (0) - -#define SB_HEADER 86 -#define SB_TOP (SB_HEADER+32) - -// Where the status bar starts, so we don't overwrite it -#define SB_STATUSBAR 420 - -#define SB_NORMAL_HEIGHT 40 -#define SB_INTER_HEIGHT 16 // interleaved height - -#define SB_MAXCLIENTS_NORMAL ((SB_STATUSBAR - SB_TOP) / SB_NORMAL_HEIGHT) -#define SB_MAXCLIENTS_INTER ((SB_STATUSBAR - SB_TOP) / SB_INTER_HEIGHT - 1) - -// Used when interleaved - - - -#define SB_LEFT_BOTICON_X (SCOREBOARD_X+0) -#define SB_LEFT_HEAD_X (SCOREBOARD_X+32) -#define SB_RIGHT_BOTICON_X (SCOREBOARD_X+64) -#define SB_RIGHT_HEAD_X (SCOREBOARD_X+96) -// Normal -#define SB_BOTICON_X (SCOREBOARD_X+32) -#define SB_HEAD_X (SCOREBOARD_X+64) - -#define SB_SCORELINE_X 112 - -#define SB_RATING_WIDTH (6 * BIGCHAR_WIDTH) // width 6 -#define SB_SCORE_X (SB_SCORELINE_X + BIGCHAR_WIDTH) // width 6 -#define SB_RATING_X (SB_SCORELINE_X + 6 * BIGCHAR_WIDTH) // width 6 -#define SB_PING_X (SB_SCORELINE_X + 12 * BIGCHAR_WIDTH + 8) // width 5 -#define SB_TIME_X (SB_SCORELINE_X + 17 * BIGCHAR_WIDTH + 8) // width 5 -#define SB_NAME_X (SB_SCORELINE_X + 22 * BIGCHAR_WIDTH) // width 15 - -// The new and improved score board -// -// In cases where the number of clients is high, the score board heads are interleaved -// here's the layout - -// -// 0 32 80 112 144 240 320 400 <-- pixel position -// bot head bot head score ping time name -// -// wins/losses are drawn on bot icon now - -static qboolean localClient; // true if local client has been displayed - - - /* -================= -CG_DrawScoreboard -================= -*/ -static void CG_DrawClientScore( int y, score_t *score, float *color, float fade, qboolean largeFormat ) { - char string[1024]; - vec3_t headAngles; - clientInfo_t *ci; - int iconx, headx; - - if ( score->client < 0 || score->client >= cgs.maxclients ) { - Com_Printf( "Bad score->client: %i\n", score->client ); - return; - } - - ci = &cgs.clientinfo[score->client]; - - iconx = SB_BOTICON_X + (SB_RATING_WIDTH / 2); - headx = SB_HEAD_X + (SB_RATING_WIDTH / 2); - - // draw the handicap or bot skill marker (unless player has flag) - if ( ci->powerups & ( 1 << PW_NEUTRALFLAG ) ) { - if( largeFormat ) { - CG_DrawFlagModel( iconx, y - ( 32 - BIGCHAR_HEIGHT ) / 2, 32, 32, TEAM_FREE, qfalse ); - } - else { - CG_DrawFlagModel( iconx, y, 16, 16, TEAM_FREE, qfalse ); - } - } else if ( ci->powerups & ( 1 << PW_REDFLAG ) ) { - if( largeFormat ) { - CG_DrawFlagModel( iconx, y - ( 32 - BIGCHAR_HEIGHT ) / 2, 32, 32, TEAM_RED, qfalse ); - } - else { - CG_DrawFlagModel( iconx, y, 16, 16, TEAM_RED, qfalse ); - } - } else if ( ci->powerups & ( 1 << PW_BLUEFLAG ) ) { - if( largeFormat ) { - CG_DrawFlagModel( iconx, y - ( 32 - BIGCHAR_HEIGHT ) / 2, 32, 32, TEAM_BLUE, qfalse ); - } - else { - CG_DrawFlagModel( iconx, y, 16, 16, TEAM_BLUE, qfalse ); - } - } else { - if ( ci->botSkill > 0 && ci->botSkill <= 5 ) { - if ( cg_drawIcons.integer ) { - if( largeFormat ) { - CG_DrawPic( iconx, y - ( 32 - BIGCHAR_HEIGHT ) / 2, 32, 32, cgs.media.botSkillShaders[ ci->botSkill - 1 ] ); - } - else { - CG_DrawPic( iconx, y, 16, 16, cgs.media.botSkillShaders[ ci->botSkill - 1 ] ); - } - } - } else if ( ci->handicap < 100 ) { - Com_sprintf( string, sizeof( string ), "%i", ci->handicap ); - if ( cgs.gametype == GT_TOURNAMENT ) - CG_DrawSmallStringColor( iconx, y - SMALLCHAR_HEIGHT/2, string, color ); - else - CG_DrawSmallStringColor( iconx, y, string, color ); - } - - // draw the wins / losses - if ( cgs.gametype == GT_TOURNAMENT ) { - Com_sprintf( string, sizeof( string ), "%i/%i", ci->wins, ci->losses ); - if( ci->handicap < 100 && !ci->botSkill ) { - CG_DrawSmallStringColor( iconx, y + SMALLCHAR_HEIGHT/2, string, color ); - } - else { - CG_DrawSmallStringColor( iconx, y, string, color ); - } - } - } - - // draw the face - VectorClear( headAngles ); - headAngles[YAW] = 180; - if( largeFormat ) { - CG_DrawHead( headx, y - ( ICON_SIZE - BIGCHAR_HEIGHT ) / 2, ICON_SIZE, ICON_SIZE, - score->client, headAngles ); - } - else { - CG_DrawHead( headx, y, 16, 16, score->client, headAngles ); - } - -#ifdef MISSIONPACK - // draw the team task - if ( ci->teamTask != TEAMTASK_NONE ) { - if ( ci->teamTask == TEAMTASK_OFFENSE ) { - CG_DrawPic( headx + 48, y, 16, 16, cgs.media.assaultShader ); - } - else if ( ci->teamTask == TEAMTASK_DEFENSE ) { - CG_DrawPic( headx + 48, y, 16, 16, cgs.media.defendShader ); - } - } -#endif - // draw the score line - if ( score->ping == -1 ) { - Com_sprintf(string, sizeof(string), - " connecting %s", ci->name); - } else if ( ci->team == TEAM_SPECTATOR ) { - Com_sprintf(string, sizeof(string), - " SPECT %3i %4i %s", score->ping, score->time, ci->name); - } else { - Com_sprintf(string, sizeof(string), - "%5i %4i %4i %s", score->score, score->ping, score->time, ci->name); - } - - // highlight your position - if ( score->client == cg.snap->ps.clientNum ) { - float hcolor[4]; - int rank; - - localClient = qtrue; - - if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR - || cgs.gametype >= GT_TEAM ) { - rank = -1; - } else { - rank = cg.snap->ps.persistant[PERS_RANK] & ~RANK_TIED_FLAG; - } - if ( rank == 0 ) { - hcolor[0] = 0; - hcolor[1] = 0; - hcolor[2] = 0.7f; - } else if ( rank == 1 ) { - hcolor[0] = 0.7f; - hcolor[1] = 0; - hcolor[2] = 0; - } else if ( rank == 2 ) { - hcolor[0] = 0.7f; - hcolor[1] = 0.7f; - hcolor[2] = 0; - } else { - hcolor[0] = 0.7f; - hcolor[1] = 0.7f; - hcolor[2] = 0.7f; - } - - hcolor[3] = fade * 0.7; - CG_FillRect( SB_SCORELINE_X + BIGCHAR_WIDTH + (SB_RATING_WIDTH / 2), y, - 640 - SB_SCORELINE_X - BIGCHAR_WIDTH, BIGCHAR_HEIGHT+1, hcolor ); - } - - CG_DrawBigString( SB_SCORELINE_X + (SB_RATING_WIDTH / 2), y, string, fade ); - - // add the "ready" marker for intermission exiting - if ( cg.snap->ps.stats[ STAT_CLIENTS_READY ] & ( 1 << score->client ) ) { - CG_DrawBigStringColor( iconx, y, "READY", color ); - } -} - -/* -================= -CG_TeamScoreboard -================= -*/ -static int CG_TeamScoreboard( int y, team_t team, float fade, int maxClients, int lineHeight ) { - int i; - score_t *score; - float color[4]; - int count; - clientInfo_t *ci; - - color[0] = color[1] = color[2] = 1.0; - color[3] = fade; - - count = 0; - for ( i = 0 ; i < cg.numScores && count < maxClients ; i++ ) { - score = &cg.scores[i]; - ci = &cgs.clientinfo[ score->client ]; - - if ( team != ci->team ) { - continue; - } - - CG_DrawClientScore( y + lineHeight * count, score, color, fade, lineHeight == SB_NORMAL_HEIGHT ); - - count++; - } - - return count; -} - -/* -================= -CG_DrawScoreboard - -Draw the normal in-game scoreboard -================= -*/ -qboolean CG_DrawOldScoreboard( void ) { - int x, y, w, i, n1, n2; - float fade; - float *fadeColor; - char *s; - int maxClients; - int lineHeight; - int topBorderSize, bottomBorderSize; - - // don't draw amuthing if the menu or console is up - if ( cg_paused.integer ) { - cg.deferredPlayerLoading = 0; - return qfalse; - } - - if ( cgs.gametype == GT_SINGLE_PLAYER && cg.predictedPlayerState.pm_type == PM_INTERMISSION ) { - cg.deferredPlayerLoading = 0; - return qfalse; - } - - // don't draw scoreboard during death while warmup up - if ( cg.warmup && !cg.showScores ) { - return qfalse; - } - - if ( cg.showScores || cg.predictedPlayerState.pm_type == PM_DEAD || - cg.predictedPlayerState.pm_type == PM_INTERMISSION ) { - fade = 1.0; - fadeColor = colorWhite; - } else { - fadeColor = CG_FadeColor( cg.scoreFadeTime, FADE_TIME ); - - if ( !fadeColor ) { - // next time scoreboard comes up, don't print killer - cg.deferredPlayerLoading = 0; - cg.killerName[0] = 0; - return qfalse; - } - fade = *fadeColor; - } - - - // fragged by ... line - if ( cg.killerName[0] ) { - s = va("Fragged by %s", cg.killerName ); - w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; - x = ( SCREEN_WIDTH - w ) / 2; - y = 40; - CG_DrawBigString( x, y, s, fade ); - } - - // current rank - if ( cgs.gametype < GT_TEAM) { - if (cg.snap->ps.persistant[PERS_TEAM] != TEAM_SPECTATOR ) { - s = va("%s place with %i", - CG_PlaceString( cg.snap->ps.persistant[PERS_RANK] + 1 ), - cg.snap->ps.persistant[PERS_SCORE] ); - w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; - x = ( SCREEN_WIDTH - w ) / 2; - y = 60; - CG_DrawBigString( x, y, s, fade ); - } - } else { - if ( cg.teamScores[0] == cg.teamScores[1] ) { - s = va("Teams are tied at %i", cg.teamScores[0] ); - } else if ( cg.teamScores[0] >= cg.teamScores[1] ) { - s = va("Red leads %i to %i",cg.teamScores[0], cg.teamScores[1] ); - } else { - s = va("Blue leads %i to %i",cg.teamScores[1], cg.teamScores[0] ); - } - - w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; - x = ( SCREEN_WIDTH - w ) / 2; - y = 60; - CG_DrawBigString( x, y, s, fade ); - } - - // scoreboard - y = SB_HEADER; - - CG_DrawPic( SB_SCORE_X + (SB_RATING_WIDTH / 2), y, 64, 32, cgs.media.scoreboardScore ); - CG_DrawPic( SB_PING_X - (SB_RATING_WIDTH / 2), y, 64, 32, cgs.media.scoreboardPing ); - CG_DrawPic( SB_TIME_X - (SB_RATING_WIDTH / 2), y, 64, 32, cgs.media.scoreboardTime ); - CG_DrawPic( SB_NAME_X - (SB_RATING_WIDTH / 2), y, 64, 32, cgs.media.scoreboardName ); - - y = SB_TOP; - - // If there are more than SB_MAXCLIENTS_NORMAL, use the interleaved scores - if ( cg.numScores > SB_MAXCLIENTS_NORMAL ) { - maxClients = SB_MAXCLIENTS_INTER; - lineHeight = SB_INTER_HEIGHT; - topBorderSize = 8; - bottomBorderSize = 16; - } else { - maxClients = SB_MAXCLIENTS_NORMAL; - lineHeight = SB_NORMAL_HEIGHT; - topBorderSize = 16; - bottomBorderSize = 16; - } - - localClient = qfalse; - - if ( cgs.gametype >= GT_TEAM ) { - // - // teamplay scoreboard - // - y += lineHeight/2; - - if ( cg.teamScores[0] >= cg.teamScores[1] ) { - n1 = CG_TeamScoreboard( y, TEAM_RED, fade, maxClients, lineHeight ); - CG_DrawTeamBackground( 0, y - topBorderSize, 640, n1 * lineHeight + bottomBorderSize, 0.33f, TEAM_RED ); - y += (n1 * lineHeight) + BIGCHAR_HEIGHT; - maxClients -= n1; - n2 = CG_TeamScoreboard( y, TEAM_BLUE, fade, maxClients, lineHeight ); - CG_DrawTeamBackground( 0, y - topBorderSize, 640, n2 * lineHeight + bottomBorderSize, 0.33f, TEAM_BLUE ); - y += (n2 * lineHeight) + BIGCHAR_HEIGHT; - maxClients -= n2; - } else { - n1 = CG_TeamScoreboard( y, TEAM_BLUE, fade, maxClients, lineHeight ); - CG_DrawTeamBackground( 0, y - topBorderSize, 640, n1 * lineHeight + bottomBorderSize, 0.33f, TEAM_BLUE ); - y += (n1 * lineHeight) + BIGCHAR_HEIGHT; - maxClients -= n1; - n2 = CG_TeamScoreboard( y, TEAM_RED, fade, maxClients, lineHeight ); - CG_DrawTeamBackground( 0, y - topBorderSize, 640, n2 * lineHeight + bottomBorderSize, 0.33f, TEAM_RED ); - y += (n2 * lineHeight) + BIGCHAR_HEIGHT; - maxClients -= n2; - } - n1 = CG_TeamScoreboard( y, TEAM_SPECTATOR, fade, maxClients, lineHeight ); - y += (n1 * lineHeight) + BIGCHAR_HEIGHT; - - } else { - // - // free for all scoreboard - // - n1 = CG_TeamScoreboard( y, TEAM_FREE, fade, maxClients, lineHeight ); - y += (n1 * lineHeight) + BIGCHAR_HEIGHT; - n2 = CG_TeamScoreboard( y, TEAM_SPECTATOR, fade, maxClients - n1, lineHeight ); - y += (n2 * lineHeight) + BIGCHAR_HEIGHT; - } - - if (!localClient) { - // draw local client at the bottom - for ( i = 0 ; i < cg.numScores ; i++ ) { - if ( cg.scores[i].client == cg.snap->ps.clientNum ) { - CG_DrawClientScore( y, &cg.scores[i], fadeColor, fade, lineHeight == SB_NORMAL_HEIGHT ); - break; - } - } - } - - // load any models that have been deferred - if ( ++cg.deferredPlayerLoading > 10 ) { - CG_LoadDeferredPlayers(); - } - - return qtrue; -} - -//================================================================================ - -/* -================ -CG_CenterGiantLine -================ -*/ -static void CG_CenterGiantLine( float y, const char *string ) { - float x; - vec4_t color; - - color[0] = 1; - color[1] = 1; - color[2] = 1; - color[3] = 1; - - x = 0.5 * ( 640 - GIANT_WIDTH * CG_DrawStrlen( string ) ); - - CG_DrawStringExt( x, y, string, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); -} - -/* -================= -CG_DrawTourneyScoreboard - -Draw the oversize scoreboard for tournements -================= -*/ -void CG_DrawOldTourneyScoreboard( void ) { - const char *s; - vec4_t color; - int min, tens, ones; - clientInfo_t *ci; - int y; - int i; - - // request more scores regularly - if ( cg.scoresRequestTime + 2000 < cg.time ) { - cg.scoresRequestTime = cg.time; - trap_SendClientCommand( "score" ); - } - - color[0] = 1; - color[1] = 1; - color[2] = 1; - color[3] = 1; - - // draw the dialog background - color[0] = color[1] = color[2] = 0; - color[3] = 1; - CG_FillRect( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, color ); - - // print the mesage of the day - s = CG_ConfigString( CS_MOTD ); - if ( !s[0] ) { - s = "Scoreboard"; - } - - // print optional title - CG_CenterGiantLine( 8, s ); - - // print server time - ones = cg.time / 1000; - min = ones / 60; - ones %= 60; - tens = ones / 10; - ones %= 10; - s = va("%i:%i%i", min, tens, ones ); - - CG_CenterGiantLine( 64, s ); - - - // print the two scores - - y = 160; - if ( cgs.gametype >= GT_TEAM ) { - // - // teamplay scoreboard - // - CG_DrawStringExt( 8, y, "Red Team", color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); - s = va("%i", cg.teamScores[0] ); - CG_DrawStringExt( 632 - GIANT_WIDTH * strlen(s), y, s, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); - - y += 64; - - CG_DrawStringExt( 8, y, "Blue Team", color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); - s = va("%i", cg.teamScores[1] ); - CG_DrawStringExt( 632 - GIANT_WIDTH * strlen(s), y, s, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); - } else { - // - // free for all scoreboard - // - for ( i = 0 ; i < MAX_CLIENTS ; i++ ) { - ci = &cgs.clientinfo[i]; - if ( !ci->infoValid ) { - continue; - } - if ( ci->team != TEAM_FREE ) { - continue; - } - - CG_DrawStringExt( 8, y, ci->name, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); - s = va("%i", ci->score ); - CG_DrawStringExt( 632 - GIANT_WIDTH * strlen(s), y, s, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); - y += 64; - } - } - - -} - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// cg_scoreboard -- draw the scoreboard on top of the game screen +#include "cg_local.h" + + +#define SCOREBOARD_X (0) + +#define SB_HEADER 86 +#define SB_TOP (SB_HEADER+32) + +// Where the status bar starts, so we don't overwrite it +#define SB_STATUSBAR 420 + +#define SB_NORMAL_HEIGHT 40 +#define SB_INTER_HEIGHT 16 // interleaved height + +#define SB_MAXCLIENTS_NORMAL ((SB_STATUSBAR - SB_TOP) / SB_NORMAL_HEIGHT) +#define SB_MAXCLIENTS_INTER ((SB_STATUSBAR - SB_TOP) / SB_INTER_HEIGHT - 1) + +// Used when interleaved + + + +#define SB_LEFT_BOTICON_X (SCOREBOARD_X+0) +#define SB_LEFT_HEAD_X (SCOREBOARD_X+32) +#define SB_RIGHT_BOTICON_X (SCOREBOARD_X+64) +#define SB_RIGHT_HEAD_X (SCOREBOARD_X+96) +// Normal +#define SB_BOTICON_X (SCOREBOARD_X+32) +#define SB_HEAD_X (SCOREBOARD_X+64) + +#define SB_SCORELINE_X 112 + +#define SB_RATING_WIDTH (6 * BIGCHAR_WIDTH) // width 6 +#define SB_SCORE_X (SB_SCORELINE_X + BIGCHAR_WIDTH) // width 6 +#define SB_RATING_X (SB_SCORELINE_X + 6 * BIGCHAR_WIDTH) // width 6 +#define SB_PING_X (SB_SCORELINE_X + 12 * BIGCHAR_WIDTH + 8) // width 5 +#define SB_TIME_X (SB_SCORELINE_X + 17 * BIGCHAR_WIDTH + 8) // width 5 +#define SB_NAME_X (SB_SCORELINE_X + 22 * BIGCHAR_WIDTH) // width 15 + +// The new and improved score board +// +// In cases where the number of clients is high, the score board heads are interleaved +// here's the layout + +// +// 0 32 80 112 144 240 320 400 <-- pixel position +// bot head bot head score ping time name +// +// wins/losses are drawn on bot icon now + +static qboolean localClient; // true if local client has been displayed + + + /* +================= +CG_DrawScoreboard +================= +*/ +static void CG_DrawClientScore( int y, score_t *score, float *color, float fade, qboolean largeFormat ) { + char string[1024]; + vec3_t headAngles; + clientInfo_t *ci; + int iconx, headx; + + if ( score->client < 0 || score->client >= cgs.maxclients ) { + Com_Printf( "Bad score->client: %i\n", score->client ); + return; + } + + ci = &cgs.clientinfo[score->client]; + + iconx = SB_BOTICON_X + (SB_RATING_WIDTH / 2); + headx = SB_HEAD_X + (SB_RATING_WIDTH / 2); + + // draw the handicap or bot skill marker (unless player has flag) + if ( ci->powerups & ( 1 << PW_NEUTRALFLAG ) ) { + if( largeFormat ) { + CG_DrawFlagModel( iconx, y - ( 32 - BIGCHAR_HEIGHT ) / 2, 32, 32, TEAM_FREE, qfalse ); + } + else { + CG_DrawFlagModel( iconx, y, 16, 16, TEAM_FREE, qfalse ); + } + } else if ( ci->powerups & ( 1 << PW_REDFLAG ) ) { + if( largeFormat ) { + CG_DrawFlagModel( iconx, y - ( 32 - BIGCHAR_HEIGHT ) / 2, 32, 32, TEAM_RED, qfalse ); + } + else { + CG_DrawFlagModel( iconx, y, 16, 16, TEAM_RED, qfalse ); + } + } else if ( ci->powerups & ( 1 << PW_BLUEFLAG ) ) { + if( largeFormat ) { + CG_DrawFlagModel( iconx, y - ( 32 - BIGCHAR_HEIGHT ) / 2, 32, 32, TEAM_BLUE, qfalse ); + } + else { + CG_DrawFlagModel( iconx, y, 16, 16, TEAM_BLUE, qfalse ); + } + } else { + if ( ci->botSkill > 0 && ci->botSkill <= 5 ) { + if ( cg_drawIcons.integer ) { + if( largeFormat ) { + CG_DrawPic( iconx, y - ( 32 - BIGCHAR_HEIGHT ) / 2, 32, 32, cgs.media.botSkillShaders[ ci->botSkill - 1 ] ); + } + else { + CG_DrawPic( iconx, y, 16, 16, cgs.media.botSkillShaders[ ci->botSkill - 1 ] ); + } + } + } else if ( ci->handicap < 100 ) { + Com_sprintf( string, sizeof( string ), "%i", ci->handicap ); + if ( cgs.gametype == GT_TOURNAMENT ) + CG_DrawSmallStringColor( iconx, y - SMALLCHAR_HEIGHT/2, string, color ); + else + CG_DrawSmallStringColor( iconx, y, string, color ); + } + + // draw the wins / losses + if ( cgs.gametype == GT_TOURNAMENT ) { + Com_sprintf( string, sizeof( string ), "%i/%i", ci->wins, ci->losses ); + if( ci->handicap < 100 && !ci->botSkill ) { + CG_DrawSmallStringColor( iconx, y + SMALLCHAR_HEIGHT/2, string, color ); + } + else { + CG_DrawSmallStringColor( iconx, y, string, color ); + } + } + } + + // draw the face + VectorClear( headAngles ); + headAngles[YAW] = 180; + if( largeFormat ) { + CG_DrawHead( headx, y - ( ICON_SIZE - BIGCHAR_HEIGHT ) / 2, ICON_SIZE, ICON_SIZE, + score->client, headAngles ); + } + else { + CG_DrawHead( headx, y, 16, 16, score->client, headAngles ); + } + +#ifdef MISSIONPACK + // draw the team task + if ( ci->teamTask != TEAMTASK_NONE ) { + if ( ci->teamTask == TEAMTASK_OFFENSE ) { + CG_DrawPic( headx + 48, y, 16, 16, cgs.media.assaultShader ); + } + else if ( ci->teamTask == TEAMTASK_DEFENSE ) { + CG_DrawPic( headx + 48, y, 16, 16, cgs.media.defendShader ); + } + } +#endif + // draw the score line + if ( score->ping == -1 ) { + Com_sprintf(string, sizeof(string), + " connecting %s", ci->name); + } else if ( ci->team == TEAM_SPECTATOR ) { + Com_sprintf(string, sizeof(string), + " SPECT %3i %4i %s", score->ping, score->time, ci->name); + } else { + Com_sprintf(string, sizeof(string), + "%5i %4i %4i %s", score->score, score->ping, score->time, ci->name); + } + + // highlight your position + if ( score->client == cg.snap->ps.clientNum ) { + float hcolor[4]; + int rank; + + localClient = qtrue; + + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR + || cgs.gametype >= GT_TEAM ) { + rank = -1; + } else { + rank = cg.snap->ps.persistant[PERS_RANK] & ~RANK_TIED_FLAG; + } + if ( rank == 0 ) { + hcolor[0] = 0; + hcolor[1] = 0; + hcolor[2] = 0.7f; + } else if ( rank == 1 ) { + hcolor[0] = 0.7f; + hcolor[1] = 0; + hcolor[2] = 0; + } else if ( rank == 2 ) { + hcolor[0] = 0.7f; + hcolor[1] = 0.7f; + hcolor[2] = 0; + } else { + hcolor[0] = 0.7f; + hcolor[1] = 0.7f; + hcolor[2] = 0.7f; + } + + hcolor[3] = fade * 0.7; + CG_FillRect( SB_SCORELINE_X + BIGCHAR_WIDTH + (SB_RATING_WIDTH / 2), y, + 640 - SB_SCORELINE_X - BIGCHAR_WIDTH, BIGCHAR_HEIGHT+1, hcolor ); + } + + CG_DrawBigString( SB_SCORELINE_X + (SB_RATING_WIDTH / 2), y, string, fade ); + + // add the "ready" marker for intermission exiting + if ( cg.snap->ps.stats[ STAT_CLIENTS_READY ] & ( 1 << score->client ) ) { + CG_DrawBigStringColor( iconx, y, "READY", color ); + } +} + +/* +================= +CG_TeamScoreboard +================= +*/ +static int CG_TeamScoreboard( int y, team_t team, float fade, int maxClients, int lineHeight ) { + int i; + score_t *score; + float color[4]; + int count; + clientInfo_t *ci; + + color[0] = color[1] = color[2] = 1.0; + color[3] = fade; + + count = 0; + for ( i = 0 ; i < cg.numScores && count < maxClients ; i++ ) { + score = &cg.scores[i]; + ci = &cgs.clientinfo[ score->client ]; + + if ( team != ci->team ) { + continue; + } + + CG_DrawClientScore( y + lineHeight * count, score, color, fade, lineHeight == SB_NORMAL_HEIGHT ); + + count++; + } + + return count; +} + +/* +================= +CG_DrawScoreboard + +Draw the normal in-game scoreboard +================= +*/ +qboolean CG_DrawOldScoreboard( void ) { + int x, y, w, i, n1, n2; + float fade; + float *fadeColor; + char *s; + int maxClients; + int lineHeight; + int topBorderSize, bottomBorderSize; + + // don't draw amuthing if the menu or console is up + if ( cg_paused.integer ) { + cg.deferredPlayerLoading = 0; + return qfalse; + } + + if ( cgs.gametype == GT_SINGLE_PLAYER && cg.predictedPlayerState.pm_type == PM_INTERMISSION ) { + cg.deferredPlayerLoading = 0; + return qfalse; + } + + // don't draw scoreboard during death while warmup up + if ( cg.warmup && !cg.showScores ) { + return qfalse; + } + + if ( cg.showScores || cg.predictedPlayerState.pm_type == PM_DEAD || + cg.predictedPlayerState.pm_type == PM_INTERMISSION ) { + fade = 1.0; + fadeColor = colorWhite; + } else { + fadeColor = CG_FadeColor( cg.scoreFadeTime, FADE_TIME ); + + if ( !fadeColor ) { + // next time scoreboard comes up, don't print killer + cg.deferredPlayerLoading = 0; + cg.killerName[0] = 0; + return qfalse; + } + fade = *fadeColor; + } + + + // fragged by ... line + if ( cg.killerName[0] ) { + s = va("Fragged by %s", cg.killerName ); + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + x = ( SCREEN_WIDTH - w ) / 2; + y = 40; + CG_DrawBigString( x, y, s, fade ); + } + + // current rank + if ( cgs.gametype < GT_TEAM) { + if (cg.snap->ps.persistant[PERS_TEAM] != TEAM_SPECTATOR ) { + s = va("%s place with %i", + CG_PlaceString( cg.snap->ps.persistant[PERS_RANK] + 1 ), + cg.snap->ps.persistant[PERS_SCORE] ); + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + x = ( SCREEN_WIDTH - w ) / 2; + y = 60; + CG_DrawBigString( x, y, s, fade ); + } + } else { + if ( cg.teamScores[0] == cg.teamScores[1] ) { + s = va("Teams are tied at %i", cg.teamScores[0] ); + } else if ( cg.teamScores[0] >= cg.teamScores[1] ) { + s = va("Red leads %i to %i",cg.teamScores[0], cg.teamScores[1] ); + } else { + s = va("Blue leads %i to %i",cg.teamScores[1], cg.teamScores[0] ); + } + + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + x = ( SCREEN_WIDTH - w ) / 2; + y = 60; + CG_DrawBigString( x, y, s, fade ); + } + + // scoreboard + y = SB_HEADER; + + CG_DrawPic( SB_SCORE_X + (SB_RATING_WIDTH / 2), y, 64, 32, cgs.media.scoreboardScore ); + CG_DrawPic( SB_PING_X - (SB_RATING_WIDTH / 2), y, 64, 32, cgs.media.scoreboardPing ); + CG_DrawPic( SB_TIME_X - (SB_RATING_WIDTH / 2), y, 64, 32, cgs.media.scoreboardTime ); + CG_DrawPic( SB_NAME_X - (SB_RATING_WIDTH / 2), y, 64, 32, cgs.media.scoreboardName ); + + y = SB_TOP; + + // If there are more than SB_MAXCLIENTS_NORMAL, use the interleaved scores + if ( cg.numScores > SB_MAXCLIENTS_NORMAL ) { + maxClients = SB_MAXCLIENTS_INTER; + lineHeight = SB_INTER_HEIGHT; + topBorderSize = 8; + bottomBorderSize = 16; + } else { + maxClients = SB_MAXCLIENTS_NORMAL; + lineHeight = SB_NORMAL_HEIGHT; + topBorderSize = 16; + bottomBorderSize = 16; + } + + localClient = qfalse; + + if ( cgs.gametype >= GT_TEAM ) { + // + // teamplay scoreboard + // + y += lineHeight/2; + + if ( cg.teamScores[0] >= cg.teamScores[1] ) { + n1 = CG_TeamScoreboard( y, TEAM_RED, fade, maxClients, lineHeight ); + CG_DrawTeamBackground( 0, y - topBorderSize, 640, n1 * lineHeight + bottomBorderSize, 0.33f, TEAM_RED ); + y += (n1 * lineHeight) + BIGCHAR_HEIGHT; + maxClients -= n1; + n2 = CG_TeamScoreboard( y, TEAM_BLUE, fade, maxClients, lineHeight ); + CG_DrawTeamBackground( 0, y - topBorderSize, 640, n2 * lineHeight + bottomBorderSize, 0.33f, TEAM_BLUE ); + y += (n2 * lineHeight) + BIGCHAR_HEIGHT; + maxClients -= n2; + } else { + n1 = CG_TeamScoreboard( y, TEAM_BLUE, fade, maxClients, lineHeight ); + CG_DrawTeamBackground( 0, y - topBorderSize, 640, n1 * lineHeight + bottomBorderSize, 0.33f, TEAM_BLUE ); + y += (n1 * lineHeight) + BIGCHAR_HEIGHT; + maxClients -= n1; + n2 = CG_TeamScoreboard( y, TEAM_RED, fade, maxClients, lineHeight ); + CG_DrawTeamBackground( 0, y - topBorderSize, 640, n2 * lineHeight + bottomBorderSize, 0.33f, TEAM_RED ); + y += (n2 * lineHeight) + BIGCHAR_HEIGHT; + maxClients -= n2; + } + n1 = CG_TeamScoreboard( y, TEAM_SPECTATOR, fade, maxClients, lineHeight ); + y += (n1 * lineHeight) + BIGCHAR_HEIGHT; + + } else { + // + // free for all scoreboard + // + n1 = CG_TeamScoreboard( y, TEAM_FREE, fade, maxClients, lineHeight ); + y += (n1 * lineHeight) + BIGCHAR_HEIGHT; + n2 = CG_TeamScoreboard( y, TEAM_SPECTATOR, fade, maxClients - n1, lineHeight ); + y += (n2 * lineHeight) + BIGCHAR_HEIGHT; + } + + if (!localClient) { + // draw local client at the bottom + for ( i = 0 ; i < cg.numScores ; i++ ) { + if ( cg.scores[i].client == cg.snap->ps.clientNum ) { + CG_DrawClientScore( y, &cg.scores[i], fadeColor, fade, lineHeight == SB_NORMAL_HEIGHT ); + break; + } + } + } + + // load any models that have been deferred + if ( ++cg.deferredPlayerLoading > 10 ) { + CG_LoadDeferredPlayers(); + } + + return qtrue; +} + +//================================================================================ + +/* +================ +CG_CenterGiantLine +================ +*/ +static void CG_CenterGiantLine( float y, const char *string ) { + float x; + vec4_t color; + + color[0] = 1; + color[1] = 1; + color[2] = 1; + color[3] = 1; + + x = 0.5 * ( 640 - GIANT_WIDTH * CG_DrawStrlen( string ) ); + + CG_DrawStringExt( x, y, string, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); +} + +/* +================= +CG_DrawTourneyScoreboard + +Draw the oversize scoreboard for tournements +================= +*/ +void CG_DrawOldTourneyScoreboard( void ) { + const char *s; + vec4_t color; + int min, tens, ones; + clientInfo_t *ci; + int y; + int i; + + // request more scores regularly + if ( cg.scoresRequestTime + 2000 < cg.time ) { + cg.scoresRequestTime = cg.time; + trap_SendClientCommand( "score" ); + } + + color[0] = 1; + color[1] = 1; + color[2] = 1; + color[3] = 1; + + // draw the dialog background + color[0] = color[1] = color[2] = 0; + color[3] = 1; + CG_FillRect( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, color ); + + // print the mesage of the day + s = CG_ConfigString( CS_MOTD ); + if ( !s[0] ) { + s = "Scoreboard"; + } + + // print optional title + CG_CenterGiantLine( 8, s ); + + // print server time + ones = cg.time / 1000; + min = ones / 60; + ones %= 60; + tens = ones / 10; + ones %= 10; + s = va("%i:%i%i", min, tens, ones ); + + CG_CenterGiantLine( 64, s ); + + + // print the two scores + + y = 160; + if ( cgs.gametype >= GT_TEAM ) { + // + // teamplay scoreboard + // + CG_DrawStringExt( 8, y, "Red Team", color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); + s = va("%i", cg.teamScores[0] ); + CG_DrawStringExt( 632 - GIANT_WIDTH * strlen(s), y, s, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); + + y += 64; + + CG_DrawStringExt( 8, y, "Blue Team", color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); + s = va("%i", cg.teamScores[1] ); + CG_DrawStringExt( 632 - GIANT_WIDTH * strlen(s), y, s, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); + } else { + // + // free for all scoreboard + // + for ( i = 0 ; i < MAX_CLIENTS ; i++ ) { + ci = &cgs.clientinfo[i]; + if ( !ci->infoValid ) { + continue; + } + if ( ci->team != TEAM_FREE ) { + continue; + } + + CG_DrawStringExt( 8, y, ci->name, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); + s = va("%i", ci->score ); + CG_DrawStringExt( 632 - GIANT_WIDTH * strlen(s), y, s, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); + y += 64; + } + } + + +} + diff --git a/code/cgame/cg_servercmds.c b/code/cgame/cg_servercmds.c index d12cd97..6558d2f 100755 --- a/code/cgame/cg_servercmds.c +++ b/code/cgame/cg_servercmds.c @@ -1,1098 +1,1098 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// cg_servercmds.c -- reliably sequenced text commands sent by the server -// these are processed at snapshot transition time, so there will definately -// be a valid snapshot this frame - -#include "cg_local.h" -#include "../../ui/menudef.h" // bk001205 - for Q3_ui as well - -typedef struct { - const char *order; - int taskNum; -} orderTask_t; - -static const orderTask_t validOrders[] = { - { VOICECHAT_GETFLAG, TEAMTASK_OFFENSE }, - { VOICECHAT_OFFENSE, TEAMTASK_OFFENSE }, - { VOICECHAT_DEFEND, TEAMTASK_DEFENSE }, - { VOICECHAT_DEFENDFLAG, TEAMTASK_DEFENSE }, - { VOICECHAT_PATROL, TEAMTASK_PATROL }, - { VOICECHAT_CAMP, TEAMTASK_CAMP }, - { VOICECHAT_FOLLOWME, TEAMTASK_FOLLOW }, - { VOICECHAT_RETURNFLAG, TEAMTASK_RETRIEVE }, - { VOICECHAT_FOLLOWFLAGCARRIER, TEAMTASK_ESCORT } -}; - -static const int numValidOrders = sizeof(validOrders) / sizeof(orderTask_t); - -#ifdef MISSIONPACK // bk001204 -static int CG_ValidOrder(const char *p) { - int i; - for (i = 0; i < numValidOrders; i++) { - if (Q_stricmp(p, validOrders[i].order) == 0) { - return validOrders[i].taskNum; - } - } - return -1; -} -#endif - -/* -================= -CG_ParseScores - -================= -*/ -static void CG_ParseScores( void ) { - int i, powerups; - - cg.numScores = atoi( CG_Argv( 1 ) ); - if ( cg.numScores > MAX_CLIENTS ) { - cg.numScores = MAX_CLIENTS; - } - - cg.teamScores[0] = atoi( CG_Argv( 2 ) ); - cg.teamScores[1] = atoi( CG_Argv( 3 ) ); - - memset( cg.scores, 0, sizeof( cg.scores ) ); - for ( i = 0 ; i < cg.numScores ; i++ ) { - // - cg.scores[i].client = atoi( CG_Argv( i * 14 + 4 ) ); - cg.scores[i].score = atoi( CG_Argv( i * 14 + 5 ) ); - cg.scores[i].ping = atoi( CG_Argv( i * 14 + 6 ) ); - cg.scores[i].time = atoi( CG_Argv( i * 14 + 7 ) ); - cg.scores[i].scoreFlags = atoi( CG_Argv( i * 14 + 8 ) ); - powerups = atoi( CG_Argv( i * 14 + 9 ) ); - cg.scores[i].accuracy = atoi(CG_Argv(i * 14 + 10)); - cg.scores[i].impressiveCount = atoi(CG_Argv(i * 14 + 11)); - cg.scores[i].excellentCount = atoi(CG_Argv(i * 14 + 12)); - cg.scores[i].guantletCount = atoi(CG_Argv(i * 14 + 13)); - cg.scores[i].defendCount = atoi(CG_Argv(i * 14 + 14)); - cg.scores[i].assistCount = atoi(CG_Argv(i * 14 + 15)); - cg.scores[i].perfect = atoi(CG_Argv(i * 14 + 16)); - cg.scores[i].captures = atoi(CG_Argv(i * 14 + 17)); - - if ( cg.scores[i].client < 0 || cg.scores[i].client >= MAX_CLIENTS ) { - cg.scores[i].client = 0; - } - cgs.clientinfo[ cg.scores[i].client ].score = cg.scores[i].score; - cgs.clientinfo[ cg.scores[i].client ].powerups = powerups; - - cg.scores[i].team = cgs.clientinfo[cg.scores[i].client].team; - } -#ifdef MISSIONPACK - CG_SetScoreSelection(NULL); -#endif - -} - -/* -================= -CG_ParseTeamInfo - -================= -*/ -static void CG_ParseTeamInfo( void ) { - int i; - int client; - - numSortedTeamPlayers = atoi( CG_Argv( 1 ) ); - - for ( i = 0 ; i < numSortedTeamPlayers ; i++ ) { - client = atoi( CG_Argv( i * 6 + 2 ) ); - - sortedTeamPlayers[i] = client; - - cgs.clientinfo[ client ].location = atoi( CG_Argv( i * 6 + 3 ) ); - cgs.clientinfo[ client ].health = atoi( CG_Argv( i * 6 + 4 ) ); - cgs.clientinfo[ client ].armor = atoi( CG_Argv( i * 6 + 5 ) ); - cgs.clientinfo[ client ].curWeapon = atoi( CG_Argv( i * 6 + 6 ) ); - cgs.clientinfo[ client ].powerups = atoi( CG_Argv( i * 6 + 7 ) ); - } -} - - -/* -================ -CG_ParseServerinfo - -This is called explicitly when the gamestate is first received, -and whenever the server updates any serverinfo flagged cvars -================ -*/ -void CG_ParseServerinfo( void ) { - const char *info; - char *mapname; - - info = CG_ConfigString( CS_SERVERINFO ); - cgs.gametype = atoi( Info_ValueForKey( info, "g_gametype" ) ); - trap_Cvar_Set("g_gametype", va("%i", cgs.gametype)); - cgs.dmflags = atoi( Info_ValueForKey( info, "dmflags" ) ); - cgs.teamflags = atoi( Info_ValueForKey( info, "teamflags" ) ); - cgs.fraglimit = atoi( Info_ValueForKey( info, "fraglimit" ) ); - cgs.capturelimit = atoi( Info_ValueForKey( info, "capturelimit" ) ); - cgs.timelimit = atoi( Info_ValueForKey( info, "timelimit" ) ); - cgs.maxclients = atoi( Info_ValueForKey( info, "sv_maxclients" ) ); - mapname = Info_ValueForKey( info, "mapname" ); - Com_sprintf( cgs.mapname, sizeof( cgs.mapname ), "maps/%s.bsp", mapname ); - Q_strncpyz( cgs.redTeam, Info_ValueForKey( info, "g_redTeam" ), sizeof(cgs.redTeam) ); - trap_Cvar_Set("g_redTeam", cgs.redTeam); - Q_strncpyz( cgs.blueTeam, Info_ValueForKey( info, "g_blueTeam" ), sizeof(cgs.blueTeam) ); - trap_Cvar_Set("g_blueTeam", cgs.blueTeam); -} - -/* -================== -CG_ParseWarmup -================== -*/ -static void CG_ParseWarmup( void ) { - const char *info; - int warmup; - - info = CG_ConfigString( CS_WARMUP ); - - warmup = atoi( info ); - cg.warmupCount = -1; - - if ( warmup == 0 && cg.warmup ) { - - } else if ( warmup > 0 && cg.warmup <= 0 ) { -#ifdef MISSIONPACK - if (cgs.gametype >= GT_CTF && cgs.gametype <= GT_HARVESTER) { - trap_S_StartLocalSound( cgs.media.countPrepareTeamSound, CHAN_ANNOUNCER ); - } else -#endif - { - trap_S_StartLocalSound( cgs.media.countPrepareSound, CHAN_ANNOUNCER ); - } - } - - cg.warmup = warmup; -} - -/* -================ -CG_SetConfigValues - -Called on load to set the initial values from configure strings -================ -*/ -void CG_SetConfigValues( void ) { - const char *s; - - cgs.scores1 = atoi( CG_ConfigString( CS_SCORES1 ) ); - cgs.scores2 = atoi( CG_ConfigString( CS_SCORES2 ) ); - cgs.levelStartTime = atoi( CG_ConfigString( CS_LEVEL_START_TIME ) ); - if( cgs.gametype == GT_CTF ) { - s = CG_ConfigString( CS_FLAGSTATUS ); - cgs.redflag = s[0] - '0'; - cgs.blueflag = s[1] - '0'; - } -#ifdef MISSIONPACK - else if( cgs.gametype == GT_1FCTF ) { - s = CG_ConfigString( CS_FLAGSTATUS ); - cgs.flagStatus = s[0] - '0'; - } -#endif - cg.warmup = atoi( CG_ConfigString( CS_WARMUP ) ); -} - -/* -===================== -CG_ShaderStateChanged -===================== -*/ -void CG_ShaderStateChanged(void) { - char originalShader[MAX_QPATH]; - char newShader[MAX_QPATH]; - char timeOffset[16]; - const char *o; - char *n,*t; - - o = CG_ConfigString( CS_SHADERSTATE ); - while (o && *o) { - n = strstr(o, "="); - if (n && *n) { - strncpy(originalShader, o, n-o); - originalShader[n-o] = 0; - n++; - t = strstr(n, ":"); - if (t && *t) { - strncpy(newShader, n, t-n); - newShader[t-n] = 0; - } else { - break; - } - t++; - o = strstr(t, "@"); - if (o) { - strncpy(timeOffset, t, o-t); - timeOffset[o-t] = 0; - o++; - trap_R_RemapShader( originalShader, newShader, timeOffset ); - } - } else { - break; - } - } -} - -/* -================ -CG_ConfigStringModified - -================ -*/ -static void CG_ConfigStringModified( void ) { - const char *str; - int num; - - num = atoi( CG_Argv( 1 ) ); - - // get the gamestate from the client system, which will have the - // new configstring already integrated - trap_GetGameState( &cgs.gameState ); - - // look up the individual string that was modified - str = CG_ConfigString( num ); - - // do something with it if necessary - if ( num == CS_MUSIC ) { - CG_StartMusic(); - } else if ( num == CS_SERVERINFO ) { - CG_ParseServerinfo(); - } else if ( num == CS_WARMUP ) { - CG_ParseWarmup(); - } else if ( num == CS_SCORES1 ) { - cgs.scores1 = atoi( str ); - } else if ( num == CS_SCORES2 ) { - cgs.scores2 = atoi( str ); - } else if ( num == CS_LEVEL_START_TIME ) { - cgs.levelStartTime = atoi( str ); - } else if ( num == CS_VOTE_TIME ) { - cgs.voteTime = atoi( str ); - cgs.voteModified = qtrue; - } else if ( num == CS_VOTE_YES ) { - cgs.voteYes = atoi( str ); - cgs.voteModified = qtrue; - } else if ( num == CS_VOTE_NO ) { - cgs.voteNo = atoi( str ); - cgs.voteModified = qtrue; - } else if ( num == CS_VOTE_STRING ) { - Q_strncpyz( cgs.voteString, str, sizeof( cgs.voteString ) ); -#ifdef MISSIONPACK - trap_S_StartLocalSound( cgs.media.voteNow, CHAN_ANNOUNCER ); -#endif //MISSIONPACK - } else if ( num >= CS_TEAMVOTE_TIME && num <= CS_TEAMVOTE_TIME + 1) { - cgs.teamVoteTime[num-CS_TEAMVOTE_TIME] = atoi( str ); - cgs.teamVoteModified[num-CS_TEAMVOTE_TIME] = qtrue; - } else if ( num >= CS_TEAMVOTE_YES && num <= CS_TEAMVOTE_YES + 1) { - cgs.teamVoteYes[num-CS_TEAMVOTE_YES] = atoi( str ); - cgs.teamVoteModified[num-CS_TEAMVOTE_YES] = qtrue; - } else if ( num >= CS_TEAMVOTE_NO && num <= CS_TEAMVOTE_NO + 1) { - cgs.teamVoteNo[num-CS_TEAMVOTE_NO] = atoi( str ); - cgs.teamVoteModified[num-CS_TEAMVOTE_NO] = qtrue; - } else if ( num >= CS_TEAMVOTE_STRING && num <= CS_TEAMVOTE_STRING + 1) { - Q_strncpyz( cgs.teamVoteString[num-CS_TEAMVOTE_STRING], str, sizeof( cgs.teamVoteString ) ); -#ifdef MISSIONPACK - trap_S_StartLocalSound( cgs.media.voteNow, CHAN_ANNOUNCER ); -#endif - } else if ( num == CS_INTERMISSION ) { - cg.intermissionStarted = atoi( str ); - } else if ( num >= CS_MODELS && num < CS_MODELS+MAX_MODELS ) { - cgs.gameModels[ num-CS_MODELS ] = trap_R_RegisterModel( str ); - } else if ( num >= CS_SOUNDS && num < CS_SOUNDS+MAX_MODELS ) { - if ( str[0] != '*' ) { // player specific sounds don't register here - cgs.gameSounds[ num-CS_SOUNDS] = trap_S_RegisterSound( str, qfalse ); - } - } else if ( num >= CS_PLAYERS && num < CS_PLAYERS+MAX_CLIENTS ) { - CG_NewClientInfo( num - CS_PLAYERS ); - CG_BuildSpectatorString(); - } else if ( num == CS_FLAGSTATUS ) { - if( cgs.gametype == GT_CTF ) { - // format is rb where its red/blue, 0 is at base, 1 is taken, 2 is dropped - cgs.redflag = str[0] - '0'; - cgs.blueflag = str[1] - '0'; - } -#ifdef MISSIONPACK - else if( cgs.gametype == GT_1FCTF ) { - cgs.flagStatus = str[0] - '0'; - } -#endif - } - else if ( num == CS_SHADERSTATE ) { - CG_ShaderStateChanged(); - } - -} - - -/* -======================= -CG_AddToTeamChat - -======================= -*/ -static void CG_AddToTeamChat( const char *str ) { - int len; - char *p, *ls; - int lastcolor; - int chatHeight; - - if (cg_teamChatHeight.integer < TEAMCHAT_HEIGHT) { - chatHeight = cg_teamChatHeight.integer; - } else { - chatHeight = TEAMCHAT_HEIGHT; - } - - if (chatHeight <= 0 || cg_teamChatTime.integer <= 0) { - // team chat disabled, dump into normal chat - cgs.teamChatPos = cgs.teamLastChatPos = 0; - return; - } - - len = 0; - - p = cgs.teamChatMsgs[cgs.teamChatPos % chatHeight]; - *p = 0; - - lastcolor = '7'; - - ls = NULL; - while (*str) { - if (len > TEAMCHAT_WIDTH - 1) { - if (ls) { - str -= (p - ls); - str++; - p -= (p - ls); - } - *p = 0; - - cgs.teamChatMsgTimes[cgs.teamChatPos % chatHeight] = cg.time; - - cgs.teamChatPos++; - p = cgs.teamChatMsgs[cgs.teamChatPos % chatHeight]; - *p = 0; - *p++ = Q_COLOR_ESCAPE; - *p++ = lastcolor; - len = 0; - ls = NULL; - } - - if ( Q_IsColorString( str ) ) { - *p++ = *str++; - lastcolor = *str; - *p++ = *str++; - continue; - } - if (*str == ' ') { - ls = p; - } - *p++ = *str++; - len++; - } - *p = 0; - - cgs.teamChatMsgTimes[cgs.teamChatPos % chatHeight] = cg.time; - cgs.teamChatPos++; - - if (cgs.teamChatPos - cgs.teamLastChatPos > chatHeight) - cgs.teamLastChatPos = cgs.teamChatPos - chatHeight; -} - -/* -=============== -CG_MapRestart - -The server has issued a map_restart, so the next snapshot -is completely new and should not be interpolated to. - -A tournement restart will clear everything, but doesn't -require a reload of all the media -=============== -*/ -static void CG_MapRestart( void ) { - if ( cg_showmiss.integer ) { - CG_Printf( "CG_MapRestart\n" ); - } - - CG_InitLocalEntities(); - CG_InitMarkPolys(); - CG_ClearParticles (); - - // make sure the "3 frags left" warnings play again - cg.fraglimitWarnings = 0; - - cg.timelimitWarnings = 0; - - cg.intermissionStarted = qfalse; - - cgs.voteTime = 0; - - cg.mapRestart = qtrue; - - CG_StartMusic(); - - trap_S_ClearLoopingSounds(qtrue); - - // we really should clear more parts of cg here and stop sounds - - // play the "fight" sound if this is a restart without warmup - if ( cg.warmup == 0 /* && cgs.gametype == GT_TOURNAMENT */) { - trap_S_StartLocalSound( cgs.media.countFightSound, CHAN_ANNOUNCER ); - CG_CenterPrint( "FIGHT!", 120, GIANTCHAR_WIDTH*2 ); - } -#ifdef MISSIONPACK - if (cg_singlePlayerActive.integer) { - trap_Cvar_Set("ui_matchStartTime", va("%i", cg.time)); - if (cg_recordSPDemo.integer && cg_recordSPDemoName.string && *cg_recordSPDemoName.string) { - trap_SendConsoleCommand(va("set g_synchronousclients 1 ; record %s \n", cg_recordSPDemoName.string)); - } - } -#endif - trap_Cvar_Set("cg_thirdPerson", "0"); -} - -#define MAX_VOICEFILESIZE 16384 -#define MAX_VOICEFILES 8 -#define MAX_VOICECHATS 64 -#define MAX_VOICESOUNDS 64 -#define MAX_CHATSIZE 64 -#define MAX_HEADMODELS 64 - -typedef struct voiceChat_s -{ - char id[64]; - int numSounds; - sfxHandle_t sounds[MAX_VOICESOUNDS]; - char chats[MAX_VOICESOUNDS][MAX_CHATSIZE]; -} voiceChat_t; - -typedef struct voiceChatList_s -{ - char name[64]; - int gender; - int numVoiceChats; - voiceChat_t voiceChats[MAX_VOICECHATS]; -} voiceChatList_t; - -typedef struct headModelVoiceChat_s -{ - char headmodel[64]; - int voiceChatNum; -} headModelVoiceChat_t; - -voiceChatList_t voiceChatLists[MAX_VOICEFILES]; -headModelVoiceChat_t headModelVoiceChat[MAX_HEADMODELS]; - -/* -================= -CG_ParseVoiceChats -================= -*/ -int CG_ParseVoiceChats( const char *filename, voiceChatList_t *voiceChatList, int maxVoiceChats ) { - int len, i; - fileHandle_t f; - char buf[MAX_VOICEFILESIZE]; - char **p, *ptr; - char *token; - voiceChat_t *voiceChats; - qboolean compress; - sfxHandle_t sound; - - compress = qtrue; - if (cg_buildScript.integer) { - compress = qfalse; - } - - len = trap_FS_FOpenFile( filename, &f, FS_READ ); - if ( !f ) { - trap_Print( va( S_COLOR_RED "voice chat file not found: %s\n", filename ) ); - return qfalse; - } - if ( len >= MAX_VOICEFILESIZE ) { - trap_Print( va( S_COLOR_RED "voice chat file too large: %s is %i, max allowed is %i", filename, len, MAX_VOICEFILESIZE ) ); - trap_FS_FCloseFile( f ); - return qfalse; - } - - trap_FS_Read( buf, len, f ); - buf[len] = 0; - trap_FS_FCloseFile( f ); - - ptr = buf; - p = &ptr; - - Com_sprintf(voiceChatList->name, sizeof(voiceChatList->name), "%s", filename); - voiceChats = voiceChatList->voiceChats; - for ( i = 0; i < maxVoiceChats; i++ ) { - voiceChats[i].id[0] = 0; - } - token = COM_ParseExt(p, qtrue); - if (!token || token[0] == 0) { - return qtrue; - } - if (!Q_stricmp(token, "female")) { - voiceChatList->gender = GENDER_FEMALE; - } - else if (!Q_stricmp(token, "male")) { - voiceChatList->gender = GENDER_MALE; - } - else if (!Q_stricmp(token, "neuter")) { - voiceChatList->gender = GENDER_NEUTER; - } - else { - trap_Print( va( S_COLOR_RED "expected gender not found in voice chat file: %s\n", filename ) ); - return qfalse; - } - - voiceChatList->numVoiceChats = 0; - while ( 1 ) { - token = COM_ParseExt(p, qtrue); - if (!token || token[0] == 0) { - return qtrue; - } - Com_sprintf(voiceChats[voiceChatList->numVoiceChats].id, sizeof( voiceChats[voiceChatList->numVoiceChats].id ), "%s", token); - token = COM_ParseExt(p, qtrue); - if (Q_stricmp(token, "{")) { - trap_Print( va( S_COLOR_RED "expected { found %s in voice chat file: %s\n", token, filename ) ); - return qfalse; - } - voiceChats[voiceChatList->numVoiceChats].numSounds = 0; - while(1) { - token = COM_ParseExt(p, qtrue); - if (!token || token[0] == 0) { - return qtrue; - } - if (!Q_stricmp(token, "}")) - break; - sound = trap_S_RegisterSound( token, compress ); - voiceChats[voiceChatList->numVoiceChats].sounds[voiceChats[voiceChatList->numVoiceChats].numSounds] = sound; - token = COM_ParseExt(p, qtrue); - if (!token || token[0] == 0) { - return qtrue; - } - Com_sprintf(voiceChats[voiceChatList->numVoiceChats].chats[ - voiceChats[voiceChatList->numVoiceChats].numSounds], MAX_CHATSIZE, "%s", token); - if (sound) - voiceChats[voiceChatList->numVoiceChats].numSounds++; - if (voiceChats[voiceChatList->numVoiceChats].numSounds >= MAX_VOICESOUNDS) - break; - } - voiceChatList->numVoiceChats++; - if (voiceChatList->numVoiceChats >= maxVoiceChats) - return qtrue; - } - return qtrue; -} - -/* -================= -CG_LoadVoiceChats -================= -*/ -void CG_LoadVoiceChats( void ) { - int size; - - size = trap_MemoryRemaining(); - CG_ParseVoiceChats( "scripts/female1.voice", &voiceChatLists[0], MAX_VOICECHATS ); - CG_ParseVoiceChats( "scripts/female2.voice", &voiceChatLists[1], MAX_VOICECHATS ); - CG_ParseVoiceChats( "scripts/female3.voice", &voiceChatLists[2], MAX_VOICECHATS ); - CG_ParseVoiceChats( "scripts/male1.voice", &voiceChatLists[3], MAX_VOICECHATS ); - CG_ParseVoiceChats( "scripts/male2.voice", &voiceChatLists[4], MAX_VOICECHATS ); - CG_ParseVoiceChats( "scripts/male3.voice", &voiceChatLists[5], MAX_VOICECHATS ); - CG_ParseVoiceChats( "scripts/male4.voice", &voiceChatLists[6], MAX_VOICECHATS ); - CG_ParseVoiceChats( "scripts/male5.voice", &voiceChatLists[7], MAX_VOICECHATS ); - CG_Printf("voice chat memory size = %d\n", size - trap_MemoryRemaining()); -} - -/* -================= -CG_HeadModelVoiceChats -================= -*/ -int CG_HeadModelVoiceChats( char *filename ) { - int len, i; - fileHandle_t f; - char buf[MAX_VOICEFILESIZE]; - char **p, *ptr; - char *token; - - len = trap_FS_FOpenFile( filename, &f, FS_READ ); - if ( !f ) { - //trap_Print( va( "voice chat file not found: %s\n", filename ) ); - return -1; - } - if ( len >= MAX_VOICEFILESIZE ) { - trap_Print( va( S_COLOR_RED "voice chat file too large: %s is %i, max allowed is %i", filename, len, MAX_VOICEFILESIZE ) ); - trap_FS_FCloseFile( f ); - return -1; - } - - trap_FS_Read( buf, len, f ); - buf[len] = 0; - trap_FS_FCloseFile( f ); - - ptr = buf; - p = &ptr; - - token = COM_ParseExt(p, qtrue); - if (!token || token[0] == 0) { - return -1; - } - - for ( i = 0; i < MAX_VOICEFILES; i++ ) { - if ( !Q_stricmp(token, voiceChatLists[i].name) ) { - return i; - } - } - - //FIXME: maybe try to load the .voice file which name is stored in token? - - return -1; -} - - -/* -================= -CG_GetVoiceChat -================= -*/ -int CG_GetVoiceChat( voiceChatList_t *voiceChatList, const char *id, sfxHandle_t *snd, char **chat) { - int i, rnd; - - for ( i = 0; i < voiceChatList->numVoiceChats; i++ ) { - if ( !Q_stricmp( id, voiceChatList->voiceChats[i].id ) ) { - rnd = random() * voiceChatList->voiceChats[i].numSounds; - *snd = voiceChatList->voiceChats[i].sounds[rnd]; - *chat = voiceChatList->voiceChats[i].chats[rnd]; - return qtrue; - } - } - return qfalse; -} - -/* -================= -CG_VoiceChatListForClient -================= -*/ -voiceChatList_t *CG_VoiceChatListForClient( int clientNum ) { - clientInfo_t *ci; - int voiceChatNum, i, j, k, gender; - char filename[MAX_QPATH], headModelName[MAX_QPATH]; - - if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { - clientNum = 0; - } - ci = &cgs.clientinfo[ clientNum ]; - - for ( k = 0; k < 2; k++ ) { - if ( k == 0 ) { - if (ci->headModelName[0] == '*') { - Com_sprintf( headModelName, sizeof(headModelName), "%s/%s", ci->headModelName+1, ci->headSkinName ); - } - else { - Com_sprintf( headModelName, sizeof(headModelName), "%s/%s", ci->headModelName, ci->headSkinName ); - } - } - else { - if (ci->headModelName[0] == '*') { - Com_sprintf( headModelName, sizeof(headModelName), "%s", ci->headModelName+1 ); - } - else { - Com_sprintf( headModelName, sizeof(headModelName), "%s", ci->headModelName ); - } - } - // find the voice file for the head model the client uses - for ( i = 0; i < MAX_HEADMODELS; i++ ) { - if (!Q_stricmp(headModelVoiceChat[i].headmodel, headModelName)) { - break; - } - } - if (i < MAX_HEADMODELS) { - return &voiceChatLists[headModelVoiceChat[i].voiceChatNum]; - } - // find a .vc file - for ( i = 0; i < MAX_HEADMODELS; i++ ) { - if (!strlen(headModelVoiceChat[i].headmodel)) { - Com_sprintf(filename, sizeof(filename), "scripts/%s.vc", headModelName); - voiceChatNum = CG_HeadModelVoiceChats(filename); - if (voiceChatNum == -1) - break; - Com_sprintf(headModelVoiceChat[i].headmodel, sizeof ( headModelVoiceChat[i].headmodel ), - "%s", headModelName); - headModelVoiceChat[i].voiceChatNum = voiceChatNum; - return &voiceChatLists[headModelVoiceChat[i].voiceChatNum]; - } - } - } - gender = ci->gender; - for (k = 0; k < 2; k++) { - // just pick the first with the right gender - for ( i = 0; i < MAX_VOICEFILES; i++ ) { - if (strlen(voiceChatLists[i].name)) { - if (voiceChatLists[i].gender == gender) { - // store this head model with voice chat for future reference - for ( j = 0; j < MAX_HEADMODELS; j++ ) { - if (!strlen(headModelVoiceChat[j].headmodel)) { - Com_sprintf(headModelVoiceChat[j].headmodel, sizeof ( headModelVoiceChat[j].headmodel ), - "%s", headModelName); - headModelVoiceChat[j].voiceChatNum = i; - break; - } - } - return &voiceChatLists[i]; - } - } - } - // fall back to male gender because we don't have neuter in the mission pack - if (gender == GENDER_MALE) - break; - gender = GENDER_MALE; - } - // store this head model with voice chat for future reference - for ( j = 0; j < MAX_HEADMODELS; j++ ) { - if (!strlen(headModelVoiceChat[j].headmodel)) { - Com_sprintf(headModelVoiceChat[j].headmodel, sizeof ( headModelVoiceChat[j].headmodel ), - "%s", headModelName); - headModelVoiceChat[j].voiceChatNum = 0; - break; - } - } - // just return the first voice chat list - return &voiceChatLists[0]; -} - -#define MAX_VOICECHATBUFFER 32 - -typedef struct bufferedVoiceChat_s -{ - int clientNum; - sfxHandle_t snd; - int voiceOnly; - char cmd[MAX_SAY_TEXT]; - char message[MAX_SAY_TEXT]; -} bufferedVoiceChat_t; - -bufferedVoiceChat_t voiceChatBuffer[MAX_VOICECHATBUFFER]; - -/* -================= -CG_PlayVoiceChat -================= -*/ -void CG_PlayVoiceChat( bufferedVoiceChat_t *vchat ) { -#ifdef MISSIONPACK - // if we are going into the intermission, don't start any voices - if ( cg.intermissionStarted ) { - return; - } - - if ( !cg_noVoiceChats.integer ) { - trap_S_StartLocalSound( vchat->snd, CHAN_VOICE); - if (vchat->clientNum != cg.snap->ps.clientNum) { - int orderTask = CG_ValidOrder(vchat->cmd); - if (orderTask > 0) { - cgs.acceptOrderTime = cg.time + 5000; - Q_strncpyz(cgs.acceptVoice, vchat->cmd, sizeof(cgs.acceptVoice)); - cgs.acceptTask = orderTask; - cgs.acceptLeader = vchat->clientNum; - } - // see if this was an order - CG_ShowResponseHead(); - } - } - if (!vchat->voiceOnly && !cg_noVoiceText.integer) { - CG_AddToTeamChat( vchat->message ); - CG_Printf( "%s\n", vchat->message ); - } - voiceChatBuffer[cg.voiceChatBufferOut].snd = 0; -#endif -} - -/* -===================== -CG_PlayBufferedVoieChats -===================== -*/ -void CG_PlayBufferedVoiceChats( void ) { -#ifdef MISSIONPACK - if ( cg.voiceChatTime < cg.time ) { - if (cg.voiceChatBufferOut != cg.voiceChatBufferIn && voiceChatBuffer[cg.voiceChatBufferOut].snd) { - // - CG_PlayVoiceChat(&voiceChatBuffer[cg.voiceChatBufferOut]); - // - cg.voiceChatBufferOut = (cg.voiceChatBufferOut + 1) % MAX_VOICECHATBUFFER; - cg.voiceChatTime = cg.time + 1000; - } - } -#endif -} - -/* -===================== -CG_AddBufferedVoiceChat -===================== -*/ -void CG_AddBufferedVoiceChat( bufferedVoiceChat_t *vchat ) { -#ifdef MISSIONPACK - // if we are going into the intermission, don't start any voices - if ( cg.intermissionStarted ) { - return; - } - - memcpy(&voiceChatBuffer[cg.voiceChatBufferIn], vchat, sizeof(bufferedVoiceChat_t)); - cg.voiceChatBufferIn = (cg.voiceChatBufferIn + 1) % MAX_VOICECHATBUFFER; - if (cg.voiceChatBufferIn == cg.voiceChatBufferOut) { - CG_PlayVoiceChat( &voiceChatBuffer[cg.voiceChatBufferOut] ); - cg.voiceChatBufferOut++; - } -#endif -} - -/* -================= -CG_VoiceChatLocal -================= -*/ -void CG_VoiceChatLocal( int mode, qboolean voiceOnly, int clientNum, int color, const char *cmd ) { -#ifdef MISSIONPACK - char *chat; - voiceChatList_t *voiceChatList; - clientInfo_t *ci; - sfxHandle_t snd; - bufferedVoiceChat_t vchat; - - // if we are going into the intermission, don't start any voices - if ( cg.intermissionStarted ) { - return; - } - - if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { - clientNum = 0; - } - ci = &cgs.clientinfo[ clientNum ]; - - cgs.currentVoiceClient = clientNum; - - voiceChatList = CG_VoiceChatListForClient( clientNum ); - - if ( CG_GetVoiceChat( voiceChatList, cmd, &snd, &chat ) ) { - // - if ( mode == SAY_TEAM || !cg_teamChatsOnly.integer ) { - vchat.clientNum = clientNum; - vchat.snd = snd; - vchat.voiceOnly = voiceOnly; - Q_strncpyz(vchat.cmd, cmd, sizeof(vchat.cmd)); - if ( mode == SAY_TELL ) { - Com_sprintf(vchat.message, sizeof(vchat.message), "[%s]: %c%c%s", ci->name, Q_COLOR_ESCAPE, color, chat); - } - else if ( mode == SAY_TEAM ) { - Com_sprintf(vchat.message, sizeof(vchat.message), "(%s): %c%c%s", ci->name, Q_COLOR_ESCAPE, color, chat); - } - else { - Com_sprintf(vchat.message, sizeof(vchat.message), "%s: %c%c%s", ci->name, Q_COLOR_ESCAPE, color, chat); - } - CG_AddBufferedVoiceChat(&vchat); - } - } -#endif -} - -/* -================= -CG_VoiceChat -================= -*/ -void CG_VoiceChat( int mode ) { -#ifdef MISSIONPACK - const char *cmd; - int clientNum, color; - qboolean voiceOnly; - - voiceOnly = atoi(CG_Argv(1)); - clientNum = atoi(CG_Argv(2)); - color = atoi(CG_Argv(3)); - cmd = CG_Argv(4); - - if (cg_noTaunt.integer != 0) { - if (!strcmp(cmd, VOICECHAT_KILLINSULT) || !strcmp(cmd, VOICECHAT_TAUNT) || \ - !strcmp(cmd, VOICECHAT_DEATHINSULT) || !strcmp(cmd, VOICECHAT_KILLGAUNTLET) || \ - !strcmp(cmd, VOICECHAT_PRAISE)) { - return; - } - } - - CG_VoiceChatLocal( mode, voiceOnly, clientNum, color, cmd ); -#endif -} - -/* -================= -CG_RemoveChatEscapeChar -================= -*/ -static void CG_RemoveChatEscapeChar( char *text ) { - int i, l; - - l = 0; - for ( i = 0; text[i]; i++ ) { - if (text[i] == '\x19') - continue; - text[l++] = text[i]; - } - text[l] = '\0'; -} - -/* -================= -CG_ServerCommand - -The string has been tokenized and can be retrieved with -Cmd_Argc() / Cmd_Argv() -================= -*/ -static void CG_ServerCommand( void ) { - const char *cmd; - char text[MAX_SAY_TEXT]; - - cmd = CG_Argv(0); - - if ( !cmd[0] ) { - // server claimed the command - return; - } - - if ( !strcmp( cmd, "cp" ) ) { - CG_CenterPrint( CG_Argv(1), SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); - return; - } - - if ( !strcmp( cmd, "cs" ) ) { - CG_ConfigStringModified(); - return; - } - - if ( !strcmp( cmd, "print" ) ) { - CG_Printf( "%s", CG_Argv(1) ); -#ifdef MISSIONPACK - cmd = CG_Argv(1); // yes, this is obviously a hack, but so is the way we hear about - // votes passing or failing - if ( !Q_stricmpn( cmd, "vote failed", 11 ) || !Q_stricmpn( cmd, "team vote failed", 16 )) { - trap_S_StartLocalSound( cgs.media.voteFailed, CHAN_ANNOUNCER ); - } else if ( !Q_stricmpn( cmd, "vote passed", 11 ) || !Q_stricmpn( cmd, "team vote passed", 16 ) ) { - trap_S_StartLocalSound( cgs.media.votePassed, CHAN_ANNOUNCER ); - } -#endif - return; - } - - if ( !strcmp( cmd, "chat" ) ) { - if ( !cg_teamChatsOnly.integer ) { - trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); - Q_strncpyz( text, CG_Argv(1), MAX_SAY_TEXT ); - CG_RemoveChatEscapeChar( text ); - CG_Printf( "%s\n", text ); - } - return; - } - - if ( !strcmp( cmd, "tchat" ) ) { - trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); - Q_strncpyz( text, CG_Argv(1), MAX_SAY_TEXT ); - CG_RemoveChatEscapeChar( text ); - CG_AddToTeamChat( text ); - CG_Printf( "%s\n", text ); - return; - } - if ( !strcmp( cmd, "vchat" ) ) { - CG_VoiceChat( SAY_ALL ); - return; - } - - if ( !strcmp( cmd, "vtchat" ) ) { - CG_VoiceChat( SAY_TEAM ); - return; - } - - if ( !strcmp( cmd, "vtell" ) ) { - CG_VoiceChat( SAY_TELL ); - return; - } - - if ( !strcmp( cmd, "scores" ) ) { - CG_ParseScores(); - return; - } - - if ( !strcmp( cmd, "tinfo" ) ) { - CG_ParseTeamInfo(); - return; - } - - if ( !strcmp( cmd, "map_restart" ) ) { - CG_MapRestart(); - return; - } - - if ( Q_stricmp (cmd, "remapShader") == 0 ) { - if (trap_Argc() == 4) { - trap_R_RemapShader(CG_Argv(1), CG_Argv(2), CG_Argv(3)); - } - } - - // loaddeferred can be both a servercmd and a consolecmd - if ( !strcmp( cmd, "loaddefered" ) ) { // FIXME: spelled wrong, but not changing for demo - CG_LoadDeferredPlayers(); - return; - } - - // clientLevelShot is sent before taking a special screenshot for - // the menu system during development - if ( !strcmp( cmd, "clientLevelShot" ) ) { - cg.levelShot = qtrue; - return; - } - - CG_Printf( "Unknown client game command: %s\n", cmd ); -} - - -/* -==================== -CG_ExecuteNewServerCommands - -Execute all of the server commands that were received along -with this this snapshot. -==================== -*/ -void CG_ExecuteNewServerCommands( int latestSequence ) { - while ( cgs.serverCommandSequence < latestSequence ) { - if ( trap_GetServerCommand( ++cgs.serverCommandSequence ) ) { - CG_ServerCommand(); - } - } -} +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// cg_servercmds.c -- reliably sequenced text commands sent by the server +// these are processed at snapshot transition time, so there will definately +// be a valid snapshot this frame + +#include "cg_local.h" +#include "../../ui/menudef.h" // bk001205 - for Q3_ui as well + +typedef struct { + const char *order; + int taskNum; +} orderTask_t; + +static const orderTask_t validOrders[] = { + { VOICECHAT_GETFLAG, TEAMTASK_OFFENSE }, + { VOICECHAT_OFFENSE, TEAMTASK_OFFENSE }, + { VOICECHAT_DEFEND, TEAMTASK_DEFENSE }, + { VOICECHAT_DEFENDFLAG, TEAMTASK_DEFENSE }, + { VOICECHAT_PATROL, TEAMTASK_PATROL }, + { VOICECHAT_CAMP, TEAMTASK_CAMP }, + { VOICECHAT_FOLLOWME, TEAMTASK_FOLLOW }, + { VOICECHAT_RETURNFLAG, TEAMTASK_RETRIEVE }, + { VOICECHAT_FOLLOWFLAGCARRIER, TEAMTASK_ESCORT } +}; + +static const int numValidOrders = sizeof(validOrders) / sizeof(orderTask_t); + +#ifdef MISSIONPACK // bk001204 +static int CG_ValidOrder(const char *p) { + int i; + for (i = 0; i < numValidOrders; i++) { + if (Q_stricmp(p, validOrders[i].order) == 0) { + return validOrders[i].taskNum; + } + } + return -1; +} +#endif + +/* +================= +CG_ParseScores + +================= +*/ +static void CG_ParseScores( void ) { + int i, powerups; + + cg.numScores = atoi( CG_Argv( 1 ) ); + if ( cg.numScores > MAX_CLIENTS ) { + cg.numScores = MAX_CLIENTS; + } + + cg.teamScores[0] = atoi( CG_Argv( 2 ) ); + cg.teamScores[1] = atoi( CG_Argv( 3 ) ); + + memset( cg.scores, 0, sizeof( cg.scores ) ); + for ( i = 0 ; i < cg.numScores ; i++ ) { + // + cg.scores[i].client = atoi( CG_Argv( i * 14 + 4 ) ); + cg.scores[i].score = atoi( CG_Argv( i * 14 + 5 ) ); + cg.scores[i].ping = atoi( CG_Argv( i * 14 + 6 ) ); + cg.scores[i].time = atoi( CG_Argv( i * 14 + 7 ) ); + cg.scores[i].scoreFlags = atoi( CG_Argv( i * 14 + 8 ) ); + powerups = atoi( CG_Argv( i * 14 + 9 ) ); + cg.scores[i].accuracy = atoi(CG_Argv(i * 14 + 10)); + cg.scores[i].impressiveCount = atoi(CG_Argv(i * 14 + 11)); + cg.scores[i].excellentCount = atoi(CG_Argv(i * 14 + 12)); + cg.scores[i].guantletCount = atoi(CG_Argv(i * 14 + 13)); + cg.scores[i].defendCount = atoi(CG_Argv(i * 14 + 14)); + cg.scores[i].assistCount = atoi(CG_Argv(i * 14 + 15)); + cg.scores[i].perfect = atoi(CG_Argv(i * 14 + 16)); + cg.scores[i].captures = atoi(CG_Argv(i * 14 + 17)); + + if ( cg.scores[i].client < 0 || cg.scores[i].client >= MAX_CLIENTS ) { + cg.scores[i].client = 0; + } + cgs.clientinfo[ cg.scores[i].client ].score = cg.scores[i].score; + cgs.clientinfo[ cg.scores[i].client ].powerups = powerups; + + cg.scores[i].team = cgs.clientinfo[cg.scores[i].client].team; + } +#ifdef MISSIONPACK + CG_SetScoreSelection(NULL); +#endif + +} + +/* +================= +CG_ParseTeamInfo + +================= +*/ +static void CG_ParseTeamInfo( void ) { + int i; + int client; + + numSortedTeamPlayers = atoi( CG_Argv( 1 ) ); + + for ( i = 0 ; i < numSortedTeamPlayers ; i++ ) { + client = atoi( CG_Argv( i * 6 + 2 ) ); + + sortedTeamPlayers[i] = client; + + cgs.clientinfo[ client ].location = atoi( CG_Argv( i * 6 + 3 ) ); + cgs.clientinfo[ client ].health = atoi( CG_Argv( i * 6 + 4 ) ); + cgs.clientinfo[ client ].armor = atoi( CG_Argv( i * 6 + 5 ) ); + cgs.clientinfo[ client ].curWeapon = atoi( CG_Argv( i * 6 + 6 ) ); + cgs.clientinfo[ client ].powerups = atoi( CG_Argv( i * 6 + 7 ) ); + } +} + + +/* +================ +CG_ParseServerinfo + +This is called explicitly when the gamestate is first received, +and whenever the server updates any serverinfo flagged cvars +================ +*/ +void CG_ParseServerinfo( void ) { + const char *info; + char *mapname; + + info = CG_ConfigString( CS_SERVERINFO ); + cgs.gametype = atoi( Info_ValueForKey( info, "g_gametype" ) ); + trap_Cvar_Set("g_gametype", va("%i", cgs.gametype)); + cgs.dmflags = atoi( Info_ValueForKey( info, "dmflags" ) ); + cgs.teamflags = atoi( Info_ValueForKey( info, "teamflags" ) ); + cgs.fraglimit = atoi( Info_ValueForKey( info, "fraglimit" ) ); + cgs.capturelimit = atoi( Info_ValueForKey( info, "capturelimit" ) ); + cgs.timelimit = atoi( Info_ValueForKey( info, "timelimit" ) ); + cgs.maxclients = atoi( Info_ValueForKey( info, "sv_maxclients" ) ); + mapname = Info_ValueForKey( info, "mapname" ); + Com_sprintf( cgs.mapname, sizeof( cgs.mapname ), "maps/%s.bsp", mapname ); + Q_strncpyz( cgs.redTeam, Info_ValueForKey( info, "g_redTeam" ), sizeof(cgs.redTeam) ); + trap_Cvar_Set("g_redTeam", cgs.redTeam); + Q_strncpyz( cgs.blueTeam, Info_ValueForKey( info, "g_blueTeam" ), sizeof(cgs.blueTeam) ); + trap_Cvar_Set("g_blueTeam", cgs.blueTeam); +} + +/* +================== +CG_ParseWarmup +================== +*/ +static void CG_ParseWarmup( void ) { + const char *info; + int warmup; + + info = CG_ConfigString( CS_WARMUP ); + + warmup = atoi( info ); + cg.warmupCount = -1; + + if ( warmup == 0 && cg.warmup ) { + + } else if ( warmup > 0 && cg.warmup <= 0 ) { +#ifdef MISSIONPACK + if (cgs.gametype >= GT_CTF && cgs.gametype <= GT_HARVESTER) { + trap_S_StartLocalSound( cgs.media.countPrepareTeamSound, CHAN_ANNOUNCER ); + } else +#endif + { + trap_S_StartLocalSound( cgs.media.countPrepareSound, CHAN_ANNOUNCER ); + } + } + + cg.warmup = warmup; +} + +/* +================ +CG_SetConfigValues + +Called on load to set the initial values from configure strings +================ +*/ +void CG_SetConfigValues( void ) { + const char *s; + + cgs.scores1 = atoi( CG_ConfigString( CS_SCORES1 ) ); + cgs.scores2 = atoi( CG_ConfigString( CS_SCORES2 ) ); + cgs.levelStartTime = atoi( CG_ConfigString( CS_LEVEL_START_TIME ) ); + if( cgs.gametype == GT_CTF ) { + s = CG_ConfigString( CS_FLAGSTATUS ); + cgs.redflag = s[0] - '0'; + cgs.blueflag = s[1] - '0'; + } +#ifdef MISSIONPACK + else if( cgs.gametype == GT_1FCTF ) { + s = CG_ConfigString( CS_FLAGSTATUS ); + cgs.flagStatus = s[0] - '0'; + } +#endif + cg.warmup = atoi( CG_ConfigString( CS_WARMUP ) ); +} + +/* +===================== +CG_ShaderStateChanged +===================== +*/ +void CG_ShaderStateChanged(void) { + char originalShader[MAX_QPATH]; + char newShader[MAX_QPATH]; + char timeOffset[16]; + const char *o; + char *n,*t; + + o = CG_ConfigString( CS_SHADERSTATE ); + while (o && *o) { + n = strstr(o, "="); + if (n && *n) { + strncpy(originalShader, o, n-o); + originalShader[n-o] = 0; + n++; + t = strstr(n, ":"); + if (t && *t) { + strncpy(newShader, n, t-n); + newShader[t-n] = 0; + } else { + break; + } + t++; + o = strstr(t, "@"); + if (o) { + strncpy(timeOffset, t, o-t); + timeOffset[o-t] = 0; + o++; + trap_R_RemapShader( originalShader, newShader, timeOffset ); + } + } else { + break; + } + } +} + +/* +================ +CG_ConfigStringModified + +================ +*/ +static void CG_ConfigStringModified( void ) { + const char *str; + int num; + + num = atoi( CG_Argv( 1 ) ); + + // get the gamestate from the client system, which will have the + // new configstring already integrated + trap_GetGameState( &cgs.gameState ); + + // look up the individual string that was modified + str = CG_ConfigString( num ); + + // do something with it if necessary + if ( num == CS_MUSIC ) { + CG_StartMusic(); + } else if ( num == CS_SERVERINFO ) { + CG_ParseServerinfo(); + } else if ( num == CS_WARMUP ) { + CG_ParseWarmup(); + } else if ( num == CS_SCORES1 ) { + cgs.scores1 = atoi( str ); + } else if ( num == CS_SCORES2 ) { + cgs.scores2 = atoi( str ); + } else if ( num == CS_LEVEL_START_TIME ) { + cgs.levelStartTime = atoi( str ); + } else if ( num == CS_VOTE_TIME ) { + cgs.voteTime = atoi( str ); + cgs.voteModified = qtrue; + } else if ( num == CS_VOTE_YES ) { + cgs.voteYes = atoi( str ); + cgs.voteModified = qtrue; + } else if ( num == CS_VOTE_NO ) { + cgs.voteNo = atoi( str ); + cgs.voteModified = qtrue; + } else if ( num == CS_VOTE_STRING ) { + Q_strncpyz( cgs.voteString, str, sizeof( cgs.voteString ) ); +#ifdef MISSIONPACK + trap_S_StartLocalSound( cgs.media.voteNow, CHAN_ANNOUNCER ); +#endif //MISSIONPACK + } else if ( num >= CS_TEAMVOTE_TIME && num <= CS_TEAMVOTE_TIME + 1) { + cgs.teamVoteTime[num-CS_TEAMVOTE_TIME] = atoi( str ); + cgs.teamVoteModified[num-CS_TEAMVOTE_TIME] = qtrue; + } else if ( num >= CS_TEAMVOTE_YES && num <= CS_TEAMVOTE_YES + 1) { + cgs.teamVoteYes[num-CS_TEAMVOTE_YES] = atoi( str ); + cgs.teamVoteModified[num-CS_TEAMVOTE_YES] = qtrue; + } else if ( num >= CS_TEAMVOTE_NO && num <= CS_TEAMVOTE_NO + 1) { + cgs.teamVoteNo[num-CS_TEAMVOTE_NO] = atoi( str ); + cgs.teamVoteModified[num-CS_TEAMVOTE_NO] = qtrue; + } else if ( num >= CS_TEAMVOTE_STRING && num <= CS_TEAMVOTE_STRING + 1) { + Q_strncpyz( cgs.teamVoteString[num-CS_TEAMVOTE_STRING], str, sizeof( cgs.teamVoteString ) ); +#ifdef MISSIONPACK + trap_S_StartLocalSound( cgs.media.voteNow, CHAN_ANNOUNCER ); +#endif + } else if ( num == CS_INTERMISSION ) { + cg.intermissionStarted = atoi( str ); + } else if ( num >= CS_MODELS && num < CS_MODELS+MAX_MODELS ) { + cgs.gameModels[ num-CS_MODELS ] = trap_R_RegisterModel( str ); + } else if ( num >= CS_SOUNDS && num < CS_SOUNDS+MAX_MODELS ) { + if ( str[0] != '*' ) { // player specific sounds don't register here + cgs.gameSounds[ num-CS_SOUNDS] = trap_S_RegisterSound( str, qfalse ); + } + } else if ( num >= CS_PLAYERS && num < CS_PLAYERS+MAX_CLIENTS ) { + CG_NewClientInfo( num - CS_PLAYERS ); + CG_BuildSpectatorString(); + } else if ( num == CS_FLAGSTATUS ) { + if( cgs.gametype == GT_CTF ) { + // format is rb where its red/blue, 0 is at base, 1 is taken, 2 is dropped + cgs.redflag = str[0] - '0'; + cgs.blueflag = str[1] - '0'; + } +#ifdef MISSIONPACK + else if( cgs.gametype == GT_1FCTF ) { + cgs.flagStatus = str[0] - '0'; + } +#endif + } + else if ( num == CS_SHADERSTATE ) { + CG_ShaderStateChanged(); + } + +} + + +/* +======================= +CG_AddToTeamChat + +======================= +*/ +static void CG_AddToTeamChat( const char *str ) { + int len; + char *p, *ls; + int lastcolor; + int chatHeight; + + if (cg_teamChatHeight.integer < TEAMCHAT_HEIGHT) { + chatHeight = cg_teamChatHeight.integer; + } else { + chatHeight = TEAMCHAT_HEIGHT; + } + + if (chatHeight <= 0 || cg_teamChatTime.integer <= 0) { + // team chat disabled, dump into normal chat + cgs.teamChatPos = cgs.teamLastChatPos = 0; + return; + } + + len = 0; + + p = cgs.teamChatMsgs[cgs.teamChatPos % chatHeight]; + *p = 0; + + lastcolor = '7'; + + ls = NULL; + while (*str) { + if (len > TEAMCHAT_WIDTH - 1) { + if (ls) { + str -= (p - ls); + str++; + p -= (p - ls); + } + *p = 0; + + cgs.teamChatMsgTimes[cgs.teamChatPos % chatHeight] = cg.time; + + cgs.teamChatPos++; + p = cgs.teamChatMsgs[cgs.teamChatPos % chatHeight]; + *p = 0; + *p++ = Q_COLOR_ESCAPE; + *p++ = lastcolor; + len = 0; + ls = NULL; + } + + if ( Q_IsColorString( str ) ) { + *p++ = *str++; + lastcolor = *str; + *p++ = *str++; + continue; + } + if (*str == ' ') { + ls = p; + } + *p++ = *str++; + len++; + } + *p = 0; + + cgs.teamChatMsgTimes[cgs.teamChatPos % chatHeight] = cg.time; + cgs.teamChatPos++; + + if (cgs.teamChatPos - cgs.teamLastChatPos > chatHeight) + cgs.teamLastChatPos = cgs.teamChatPos - chatHeight; +} + +/* +=============== +CG_MapRestart + +The server has issued a map_restart, so the next snapshot +is completely new and should not be interpolated to. + +A tournement restart will clear everything, but doesn't +require a reload of all the media +=============== +*/ +static void CG_MapRestart( void ) { + if ( cg_showmiss.integer ) { + CG_Printf( "CG_MapRestart\n" ); + } + + CG_InitLocalEntities(); + CG_InitMarkPolys(); + CG_ClearParticles (); + + // make sure the "3 frags left" warnings play again + cg.fraglimitWarnings = 0; + + cg.timelimitWarnings = 0; + + cg.intermissionStarted = qfalse; + + cgs.voteTime = 0; + + cg.mapRestart = qtrue; + + CG_StartMusic(); + + trap_S_ClearLoopingSounds(qtrue); + + // we really should clear more parts of cg here and stop sounds + + // play the "fight" sound if this is a restart without warmup + if ( cg.warmup == 0 /* && cgs.gametype == GT_TOURNAMENT */) { + trap_S_StartLocalSound( cgs.media.countFightSound, CHAN_ANNOUNCER ); + CG_CenterPrint( "FIGHT!", 120, GIANTCHAR_WIDTH*2 ); + } +#ifdef MISSIONPACK + if (cg_singlePlayerActive.integer) { + trap_Cvar_Set("ui_matchStartTime", va("%i", cg.time)); + if (cg_recordSPDemo.integer && cg_recordSPDemoName.string && *cg_recordSPDemoName.string) { + trap_SendConsoleCommand(va("set g_synchronousclients 1 ; record %s \n", cg_recordSPDemoName.string)); + } + } +#endif + trap_Cvar_Set("cg_thirdPerson", "0"); +} + +#define MAX_VOICEFILESIZE 16384 +#define MAX_VOICEFILES 8 +#define MAX_VOICECHATS 64 +#define MAX_VOICESOUNDS 64 +#define MAX_CHATSIZE 64 +#define MAX_HEADMODELS 64 + +typedef struct voiceChat_s +{ + char id[64]; + int numSounds; + sfxHandle_t sounds[MAX_VOICESOUNDS]; + char chats[MAX_VOICESOUNDS][MAX_CHATSIZE]; +} voiceChat_t; + +typedef struct voiceChatList_s +{ + char name[64]; + int gender; + int numVoiceChats; + voiceChat_t voiceChats[MAX_VOICECHATS]; +} voiceChatList_t; + +typedef struct headModelVoiceChat_s +{ + char headmodel[64]; + int voiceChatNum; +} headModelVoiceChat_t; + +voiceChatList_t voiceChatLists[MAX_VOICEFILES]; +headModelVoiceChat_t headModelVoiceChat[MAX_HEADMODELS]; + +/* +================= +CG_ParseVoiceChats +================= +*/ +int CG_ParseVoiceChats( const char *filename, voiceChatList_t *voiceChatList, int maxVoiceChats ) { + int len, i; + fileHandle_t f; + char buf[MAX_VOICEFILESIZE]; + char **p, *ptr; + char *token; + voiceChat_t *voiceChats; + qboolean compress; + sfxHandle_t sound; + + compress = qtrue; + if (cg_buildScript.integer) { + compress = qfalse; + } + + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( !f ) { + trap_Print( va( S_COLOR_RED "voice chat file not found: %s\n", filename ) ); + return qfalse; + } + if ( len >= MAX_VOICEFILESIZE ) { + trap_Print( va( S_COLOR_RED "voice chat file too large: %s is %i, max allowed is %i", filename, len, MAX_VOICEFILESIZE ) ); + trap_FS_FCloseFile( f ); + return qfalse; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + + ptr = buf; + p = &ptr; + + Com_sprintf(voiceChatList->name, sizeof(voiceChatList->name), "%s", filename); + voiceChats = voiceChatList->voiceChats; + for ( i = 0; i < maxVoiceChats; i++ ) { + voiceChats[i].id[0] = 0; + } + token = COM_ParseExt(p, qtrue); + if (!token || token[0] == 0) { + return qtrue; + } + if (!Q_stricmp(token, "female")) { + voiceChatList->gender = GENDER_FEMALE; + } + else if (!Q_stricmp(token, "male")) { + voiceChatList->gender = GENDER_MALE; + } + else if (!Q_stricmp(token, "neuter")) { + voiceChatList->gender = GENDER_NEUTER; + } + else { + trap_Print( va( S_COLOR_RED "expected gender not found in voice chat file: %s\n", filename ) ); + return qfalse; + } + + voiceChatList->numVoiceChats = 0; + while ( 1 ) { + token = COM_ParseExt(p, qtrue); + if (!token || token[0] == 0) { + return qtrue; + } + Com_sprintf(voiceChats[voiceChatList->numVoiceChats].id, sizeof( voiceChats[voiceChatList->numVoiceChats].id ), "%s", token); + token = COM_ParseExt(p, qtrue); + if (Q_stricmp(token, "{")) { + trap_Print( va( S_COLOR_RED "expected { found %s in voice chat file: %s\n", token, filename ) ); + return qfalse; + } + voiceChats[voiceChatList->numVoiceChats].numSounds = 0; + while(1) { + token = COM_ParseExt(p, qtrue); + if (!token || token[0] == 0) { + return qtrue; + } + if (!Q_stricmp(token, "}")) + break; + sound = trap_S_RegisterSound( token, compress ); + voiceChats[voiceChatList->numVoiceChats].sounds[voiceChats[voiceChatList->numVoiceChats].numSounds] = sound; + token = COM_ParseExt(p, qtrue); + if (!token || token[0] == 0) { + return qtrue; + } + Com_sprintf(voiceChats[voiceChatList->numVoiceChats].chats[ + voiceChats[voiceChatList->numVoiceChats].numSounds], MAX_CHATSIZE, "%s", token); + if (sound) + voiceChats[voiceChatList->numVoiceChats].numSounds++; + if (voiceChats[voiceChatList->numVoiceChats].numSounds >= MAX_VOICESOUNDS) + break; + } + voiceChatList->numVoiceChats++; + if (voiceChatList->numVoiceChats >= maxVoiceChats) + return qtrue; + } + return qtrue; +} + +/* +================= +CG_LoadVoiceChats +================= +*/ +void CG_LoadVoiceChats( void ) { + int size; + + size = trap_MemoryRemaining(); + CG_ParseVoiceChats( "scripts/female1.voice", &voiceChatLists[0], MAX_VOICECHATS ); + CG_ParseVoiceChats( "scripts/female2.voice", &voiceChatLists[1], MAX_VOICECHATS ); + CG_ParseVoiceChats( "scripts/female3.voice", &voiceChatLists[2], MAX_VOICECHATS ); + CG_ParseVoiceChats( "scripts/male1.voice", &voiceChatLists[3], MAX_VOICECHATS ); + CG_ParseVoiceChats( "scripts/male2.voice", &voiceChatLists[4], MAX_VOICECHATS ); + CG_ParseVoiceChats( "scripts/male3.voice", &voiceChatLists[5], MAX_VOICECHATS ); + CG_ParseVoiceChats( "scripts/male4.voice", &voiceChatLists[6], MAX_VOICECHATS ); + CG_ParseVoiceChats( "scripts/male5.voice", &voiceChatLists[7], MAX_VOICECHATS ); + CG_Printf("voice chat memory size = %d\n", size - trap_MemoryRemaining()); +} + +/* +================= +CG_HeadModelVoiceChats +================= +*/ +int CG_HeadModelVoiceChats( char *filename ) { + int len, i; + fileHandle_t f; + char buf[MAX_VOICEFILESIZE]; + char **p, *ptr; + char *token; + + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( !f ) { + //trap_Print( va( "voice chat file not found: %s\n", filename ) ); + return -1; + } + if ( len >= MAX_VOICEFILESIZE ) { + trap_Print( va( S_COLOR_RED "voice chat file too large: %s is %i, max allowed is %i", filename, len, MAX_VOICEFILESIZE ) ); + trap_FS_FCloseFile( f ); + return -1; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + + ptr = buf; + p = &ptr; + + token = COM_ParseExt(p, qtrue); + if (!token || token[0] == 0) { + return -1; + } + + for ( i = 0; i < MAX_VOICEFILES; i++ ) { + if ( !Q_stricmp(token, voiceChatLists[i].name) ) { + return i; + } + } + + //FIXME: maybe try to load the .voice file which name is stored in token? + + return -1; +} + + +/* +================= +CG_GetVoiceChat +================= +*/ +int CG_GetVoiceChat( voiceChatList_t *voiceChatList, const char *id, sfxHandle_t *snd, char **chat) { + int i, rnd; + + for ( i = 0; i < voiceChatList->numVoiceChats; i++ ) { + if ( !Q_stricmp( id, voiceChatList->voiceChats[i].id ) ) { + rnd = random() * voiceChatList->voiceChats[i].numSounds; + *snd = voiceChatList->voiceChats[i].sounds[rnd]; + *chat = voiceChatList->voiceChats[i].chats[rnd]; + return qtrue; + } + } + return qfalse; +} + +/* +================= +CG_VoiceChatListForClient +================= +*/ +voiceChatList_t *CG_VoiceChatListForClient( int clientNum ) { + clientInfo_t *ci; + int voiceChatNum, i, j, k, gender; + char filename[MAX_QPATH], headModelName[MAX_QPATH]; + + if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { + clientNum = 0; + } + ci = &cgs.clientinfo[ clientNum ]; + + for ( k = 0; k < 2; k++ ) { + if ( k == 0 ) { + if (ci->headModelName[0] == '*') { + Com_sprintf( headModelName, sizeof(headModelName), "%s/%s", ci->headModelName+1, ci->headSkinName ); + } + else { + Com_sprintf( headModelName, sizeof(headModelName), "%s/%s", ci->headModelName, ci->headSkinName ); + } + } + else { + if (ci->headModelName[0] == '*') { + Com_sprintf( headModelName, sizeof(headModelName), "%s", ci->headModelName+1 ); + } + else { + Com_sprintf( headModelName, sizeof(headModelName), "%s", ci->headModelName ); + } + } + // find the voice file for the head model the client uses + for ( i = 0; i < MAX_HEADMODELS; i++ ) { + if (!Q_stricmp(headModelVoiceChat[i].headmodel, headModelName)) { + break; + } + } + if (i < MAX_HEADMODELS) { + return &voiceChatLists[headModelVoiceChat[i].voiceChatNum]; + } + // find a .vc file + for ( i = 0; i < MAX_HEADMODELS; i++ ) { + if (!strlen(headModelVoiceChat[i].headmodel)) { + Com_sprintf(filename, sizeof(filename), "scripts/%s.vc", headModelName); + voiceChatNum = CG_HeadModelVoiceChats(filename); + if (voiceChatNum == -1) + break; + Com_sprintf(headModelVoiceChat[i].headmodel, sizeof ( headModelVoiceChat[i].headmodel ), + "%s", headModelName); + headModelVoiceChat[i].voiceChatNum = voiceChatNum; + return &voiceChatLists[headModelVoiceChat[i].voiceChatNum]; + } + } + } + gender = ci->gender; + for (k = 0; k < 2; k++) { + // just pick the first with the right gender + for ( i = 0; i < MAX_VOICEFILES; i++ ) { + if (strlen(voiceChatLists[i].name)) { + if (voiceChatLists[i].gender == gender) { + // store this head model with voice chat for future reference + for ( j = 0; j < MAX_HEADMODELS; j++ ) { + if (!strlen(headModelVoiceChat[j].headmodel)) { + Com_sprintf(headModelVoiceChat[j].headmodel, sizeof ( headModelVoiceChat[j].headmodel ), + "%s", headModelName); + headModelVoiceChat[j].voiceChatNum = i; + break; + } + } + return &voiceChatLists[i]; + } + } + } + // fall back to male gender because we don't have neuter in the mission pack + if (gender == GENDER_MALE) + break; + gender = GENDER_MALE; + } + // store this head model with voice chat for future reference + for ( j = 0; j < MAX_HEADMODELS; j++ ) { + if (!strlen(headModelVoiceChat[j].headmodel)) { + Com_sprintf(headModelVoiceChat[j].headmodel, sizeof ( headModelVoiceChat[j].headmodel ), + "%s", headModelName); + headModelVoiceChat[j].voiceChatNum = 0; + break; + } + } + // just return the first voice chat list + return &voiceChatLists[0]; +} + +#define MAX_VOICECHATBUFFER 32 + +typedef struct bufferedVoiceChat_s +{ + int clientNum; + sfxHandle_t snd; + int voiceOnly; + char cmd[MAX_SAY_TEXT]; + char message[MAX_SAY_TEXT]; +} bufferedVoiceChat_t; + +bufferedVoiceChat_t voiceChatBuffer[MAX_VOICECHATBUFFER]; + +/* +================= +CG_PlayVoiceChat +================= +*/ +void CG_PlayVoiceChat( bufferedVoiceChat_t *vchat ) { +#ifdef MISSIONPACK + // if we are going into the intermission, don't start any voices + if ( cg.intermissionStarted ) { + return; + } + + if ( !cg_noVoiceChats.integer ) { + trap_S_StartLocalSound( vchat->snd, CHAN_VOICE); + if (vchat->clientNum != cg.snap->ps.clientNum) { + int orderTask = CG_ValidOrder(vchat->cmd); + if (orderTask > 0) { + cgs.acceptOrderTime = cg.time + 5000; + Q_strncpyz(cgs.acceptVoice, vchat->cmd, sizeof(cgs.acceptVoice)); + cgs.acceptTask = orderTask; + cgs.acceptLeader = vchat->clientNum; + } + // see if this was an order + CG_ShowResponseHead(); + } + } + if (!vchat->voiceOnly && !cg_noVoiceText.integer) { + CG_AddToTeamChat( vchat->message ); + CG_Printf( "%s\n", vchat->message ); + } + voiceChatBuffer[cg.voiceChatBufferOut].snd = 0; +#endif +} + +/* +===================== +CG_PlayBufferedVoieChats +===================== +*/ +void CG_PlayBufferedVoiceChats( void ) { +#ifdef MISSIONPACK + if ( cg.voiceChatTime < cg.time ) { + if (cg.voiceChatBufferOut != cg.voiceChatBufferIn && voiceChatBuffer[cg.voiceChatBufferOut].snd) { + // + CG_PlayVoiceChat(&voiceChatBuffer[cg.voiceChatBufferOut]); + // + cg.voiceChatBufferOut = (cg.voiceChatBufferOut + 1) % MAX_VOICECHATBUFFER; + cg.voiceChatTime = cg.time + 1000; + } + } +#endif +} + +/* +===================== +CG_AddBufferedVoiceChat +===================== +*/ +void CG_AddBufferedVoiceChat( bufferedVoiceChat_t *vchat ) { +#ifdef MISSIONPACK + // if we are going into the intermission, don't start any voices + if ( cg.intermissionStarted ) { + return; + } + + memcpy(&voiceChatBuffer[cg.voiceChatBufferIn], vchat, sizeof(bufferedVoiceChat_t)); + cg.voiceChatBufferIn = (cg.voiceChatBufferIn + 1) % MAX_VOICECHATBUFFER; + if (cg.voiceChatBufferIn == cg.voiceChatBufferOut) { + CG_PlayVoiceChat( &voiceChatBuffer[cg.voiceChatBufferOut] ); + cg.voiceChatBufferOut++; + } +#endif +} + +/* +================= +CG_VoiceChatLocal +================= +*/ +void CG_VoiceChatLocal( int mode, qboolean voiceOnly, int clientNum, int color, const char *cmd ) { +#ifdef MISSIONPACK + char *chat; + voiceChatList_t *voiceChatList; + clientInfo_t *ci; + sfxHandle_t snd; + bufferedVoiceChat_t vchat; + + // if we are going into the intermission, don't start any voices + if ( cg.intermissionStarted ) { + return; + } + + if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { + clientNum = 0; + } + ci = &cgs.clientinfo[ clientNum ]; + + cgs.currentVoiceClient = clientNum; + + voiceChatList = CG_VoiceChatListForClient( clientNum ); + + if ( CG_GetVoiceChat( voiceChatList, cmd, &snd, &chat ) ) { + // + if ( mode == SAY_TEAM || !cg_teamChatsOnly.integer ) { + vchat.clientNum = clientNum; + vchat.snd = snd; + vchat.voiceOnly = voiceOnly; + Q_strncpyz(vchat.cmd, cmd, sizeof(vchat.cmd)); + if ( mode == SAY_TELL ) { + Com_sprintf(vchat.message, sizeof(vchat.message), "[%s]: %c%c%s", ci->name, Q_COLOR_ESCAPE, color, chat); + } + else if ( mode == SAY_TEAM ) { + Com_sprintf(vchat.message, sizeof(vchat.message), "(%s): %c%c%s", ci->name, Q_COLOR_ESCAPE, color, chat); + } + else { + Com_sprintf(vchat.message, sizeof(vchat.message), "%s: %c%c%s", ci->name, Q_COLOR_ESCAPE, color, chat); + } + CG_AddBufferedVoiceChat(&vchat); + } + } +#endif +} + +/* +================= +CG_VoiceChat +================= +*/ +void CG_VoiceChat( int mode ) { +#ifdef MISSIONPACK + const char *cmd; + int clientNum, color; + qboolean voiceOnly; + + voiceOnly = atoi(CG_Argv(1)); + clientNum = atoi(CG_Argv(2)); + color = atoi(CG_Argv(3)); + cmd = CG_Argv(4); + + if (cg_noTaunt.integer != 0) { + if (!strcmp(cmd, VOICECHAT_KILLINSULT) || !strcmp(cmd, VOICECHAT_TAUNT) || \ + !strcmp(cmd, VOICECHAT_DEATHINSULT) || !strcmp(cmd, VOICECHAT_KILLGAUNTLET) || \ + !strcmp(cmd, VOICECHAT_PRAISE)) { + return; + } + } + + CG_VoiceChatLocal( mode, voiceOnly, clientNum, color, cmd ); +#endif +} + +/* +================= +CG_RemoveChatEscapeChar +================= +*/ +static void CG_RemoveChatEscapeChar( char *text ) { + int i, l; + + l = 0; + for ( i = 0; text[i]; i++ ) { + if (text[i] == '\x19') + continue; + text[l++] = text[i]; + } + text[l] = '\0'; +} + +/* +================= +CG_ServerCommand + +The string has been tokenized and can be retrieved with +Cmd_Argc() / Cmd_Argv() +================= +*/ +static void CG_ServerCommand( void ) { + const char *cmd; + char text[MAX_SAY_TEXT]; + + cmd = CG_Argv(0); + + if ( !cmd[0] ) { + // server claimed the command + return; + } + + if ( !strcmp( cmd, "cp" ) ) { + CG_CenterPrint( CG_Argv(1), SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); + return; + } + + if ( !strcmp( cmd, "cs" ) ) { + CG_ConfigStringModified(); + return; + } + + if ( !strcmp( cmd, "print" ) ) { + CG_Printf( "%s", CG_Argv(1) ); +#ifdef MISSIONPACK + cmd = CG_Argv(1); // yes, this is obviously a hack, but so is the way we hear about + // votes passing or failing + if ( !Q_stricmpn( cmd, "vote failed", 11 ) || !Q_stricmpn( cmd, "team vote failed", 16 )) { + trap_S_StartLocalSound( cgs.media.voteFailed, CHAN_ANNOUNCER ); + } else if ( !Q_stricmpn( cmd, "vote passed", 11 ) || !Q_stricmpn( cmd, "team vote passed", 16 ) ) { + trap_S_StartLocalSound( cgs.media.votePassed, CHAN_ANNOUNCER ); + } +#endif + return; + } + + if ( !strcmp( cmd, "chat" ) ) { + if ( !cg_teamChatsOnly.integer ) { + trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); + Q_strncpyz( text, CG_Argv(1), MAX_SAY_TEXT ); + CG_RemoveChatEscapeChar( text ); + CG_Printf( "%s\n", text ); + } + return; + } + + if ( !strcmp( cmd, "tchat" ) ) { + trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); + Q_strncpyz( text, CG_Argv(1), MAX_SAY_TEXT ); + CG_RemoveChatEscapeChar( text ); + CG_AddToTeamChat( text ); + CG_Printf( "%s\n", text ); + return; + } + if ( !strcmp( cmd, "vchat" ) ) { + CG_VoiceChat( SAY_ALL ); + return; + } + + if ( !strcmp( cmd, "vtchat" ) ) { + CG_VoiceChat( SAY_TEAM ); + return; + } + + if ( !strcmp( cmd, "vtell" ) ) { + CG_VoiceChat( SAY_TELL ); + return; + } + + if ( !strcmp( cmd, "scores" ) ) { + CG_ParseScores(); + return; + } + + if ( !strcmp( cmd, "tinfo" ) ) { + CG_ParseTeamInfo(); + return; + } + + if ( !strcmp( cmd, "map_restart" ) ) { + CG_MapRestart(); + return; + } + + if ( Q_stricmp (cmd, "remapShader") == 0 ) { + if (trap_Argc() == 4) { + trap_R_RemapShader(CG_Argv(1), CG_Argv(2), CG_Argv(3)); + } + } + + // loaddeferred can be both a servercmd and a consolecmd + if ( !strcmp( cmd, "loaddefered" ) ) { // FIXME: spelled wrong, but not changing for demo + CG_LoadDeferredPlayers(); + return; + } + + // clientLevelShot is sent before taking a special screenshot for + // the menu system during development + if ( !strcmp( cmd, "clientLevelShot" ) ) { + cg.levelShot = qtrue; + return; + } + + CG_Printf( "Unknown client game command: %s\n", cmd ); +} + + +/* +==================== +CG_ExecuteNewServerCommands + +Execute all of the server commands that were received along +with this this snapshot. +==================== +*/ +void CG_ExecuteNewServerCommands( int latestSequence ) { + while ( cgs.serverCommandSequence < latestSequence ) { + if ( trap_GetServerCommand( ++cgs.serverCommandSequence ) ) { + CG_ServerCommand(); + } + } +} diff --git a/code/cgame/cg_snapshot.c b/code/cgame/cg_snapshot.c index add49f0..f337e08 100755 --- a/code/cgame/cg_snapshot.c +++ b/code/cgame/cg_snapshot.c @@ -1,403 +1,403 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// cg_snapshot.c -- things that happen on snapshot transition, -// not necessarily every single rendered frame - -#include "cg_local.h" - - - -/* -================== -CG_ResetEntity -================== -*/ -static void CG_ResetEntity( centity_t *cent ) { - // if the previous snapshot this entity was updated in is at least - // an event window back in time then we can reset the previous event - if ( cent->snapShotTime < cg.time - EVENT_VALID_MSEC ) { - cent->previousEvent = 0; - } - - cent->trailTime = cg.snap->serverTime; - - VectorCopy (cent->currentState.origin, cent->lerpOrigin); - VectorCopy (cent->currentState.angles, cent->lerpAngles); - if ( cent->currentState.eType == ET_PLAYER ) { - CG_ResetPlayerEntity( cent ); - } -} - -/* -=============== -CG_TransitionEntity - -cent->nextState is moved to cent->currentState and events are fired -=============== -*/ -static void CG_TransitionEntity( centity_t *cent ) { - cent->currentState = cent->nextState; - cent->currentValid = qtrue; - - // reset if the entity wasn't in the last frame or was teleported - if ( !cent->interpolate ) { - CG_ResetEntity( cent ); - } - - // clear the next state. if will be set by the next CG_SetNextSnap - cent->interpolate = qfalse; - - // check for events - CG_CheckEvents( cent ); -} - - -/* -================== -CG_SetInitialSnapshot - -This will only happen on the very first snapshot, or -on tourney restarts. All other times will use -CG_TransitionSnapshot instead. - -FIXME: Also called by map_restart? -================== -*/ -void CG_SetInitialSnapshot( snapshot_t *snap ) { - int i; - centity_t *cent; - entityState_t *state; - - cg.snap = snap; - - BG_PlayerStateToEntityState( &snap->ps, &cg_entities[ snap->ps.clientNum ].currentState, qfalse ); - - // sort out solid entities - CG_BuildSolidList(); - - CG_ExecuteNewServerCommands( snap->serverCommandSequence ); - - // set our local weapon selection pointer to - // what the server has indicated the current weapon is - CG_Respawn(); - - for ( i = 0 ; i < cg.snap->numEntities ; i++ ) { - state = &cg.snap->entities[ i ]; - cent = &cg_entities[ state->number ]; - - memcpy(¢->currentState, state, sizeof(entityState_t)); - //cent->currentState = *state; - cent->interpolate = qfalse; - cent->currentValid = qtrue; - - CG_ResetEntity( cent ); - - // check for events - CG_CheckEvents( cent ); - } -} - - -/* -=================== -CG_TransitionSnapshot - -The transition point from snap to nextSnap has passed -=================== -*/ -static void CG_TransitionSnapshot( void ) { - centity_t *cent; - snapshot_t *oldFrame; - int i; - - if ( !cg.snap ) { - CG_Error( "CG_TransitionSnapshot: NULL cg.snap" ); - } - if ( !cg.nextSnap ) { - CG_Error( "CG_TransitionSnapshot: NULL cg.nextSnap" ); - } - - // execute any server string commands before transitioning entities - CG_ExecuteNewServerCommands( cg.nextSnap->serverCommandSequence ); - - // if we had a map_restart, set everthing with initial - if ( !cg.snap ) { - } - - // clear the currentValid flag for all entities in the existing snapshot - for ( i = 0 ; i < cg.snap->numEntities ; i++ ) { - cent = &cg_entities[ cg.snap->entities[ i ].number ]; - cent->currentValid = qfalse; - } - - // move nextSnap to snap and do the transitions - oldFrame = cg.snap; - cg.snap = cg.nextSnap; - - BG_PlayerStateToEntityState( &cg.snap->ps, &cg_entities[ cg.snap->ps.clientNum ].currentState, qfalse ); - cg_entities[ cg.snap->ps.clientNum ].interpolate = qfalse; - - for ( i = 0 ; i < cg.snap->numEntities ; i++ ) { - cent = &cg_entities[ cg.snap->entities[ i ].number ]; - CG_TransitionEntity( cent ); - - // remember time of snapshot this entity was last updated in - cent->snapShotTime = cg.snap->serverTime; - } - - cg.nextSnap = NULL; - - // check for playerstate transition events - if ( oldFrame ) { - playerState_t *ops, *ps; - - ops = &oldFrame->ps; - ps = &cg.snap->ps; - // teleporting checks are irrespective of prediction - if ( ( ps->eFlags ^ ops->eFlags ) & EF_TELEPORT_BIT ) { - cg.thisFrameTeleport = qtrue; // will be cleared by prediction code - } - - // if we are not doing client side movement prediction for any - // reason, then the client events and view changes will be issued now - if ( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW) - || cg_nopredict.integer || cg_synchronousClients.integer ) { - CG_TransitionPlayerState( ps, ops ); - } - } - -} - - -/* -=================== -CG_SetNextSnap - -A new snapshot has just been read in from the client system. -=================== -*/ -static void CG_SetNextSnap( snapshot_t *snap ) { - int num; - entityState_t *es; - centity_t *cent; - - cg.nextSnap = snap; - - BG_PlayerStateToEntityState( &snap->ps, &cg_entities[ snap->ps.clientNum ].nextState, qfalse ); - cg_entities[ cg.snap->ps.clientNum ].interpolate = qtrue; - - // check for extrapolation errors - for ( num = 0 ; num < snap->numEntities ; num++ ) { - es = &snap->entities[num]; - cent = &cg_entities[ es->number ]; - - memcpy(¢->nextState, es, sizeof(entityState_t)); - //cent->nextState = *es; - - // if this frame is a teleport, or the entity wasn't in the - // previous frame, don't interpolate - if ( !cent->currentValid || ( ( cent->currentState.eFlags ^ es->eFlags ) & EF_TELEPORT_BIT ) ) { - cent->interpolate = qfalse; - } else { - cent->interpolate = qtrue; - } - } - - // if the next frame is a teleport for the playerstate, we - // can't interpolate during demos - if ( cg.snap && ( ( snap->ps.eFlags ^ cg.snap->ps.eFlags ) & EF_TELEPORT_BIT ) ) { - cg.nextFrameTeleport = qtrue; - } else { - cg.nextFrameTeleport = qfalse; - } - - // if changing follow mode, don't interpolate - if ( cg.nextSnap->ps.clientNum != cg.snap->ps.clientNum ) { - cg.nextFrameTeleport = qtrue; - } - - // if changing server restarts, don't interpolate - if ( ( cg.nextSnap->snapFlags ^ cg.snap->snapFlags ) & SNAPFLAG_SERVERCOUNT ) { - cg.nextFrameTeleport = qtrue; - } - - // sort out solid entities - CG_BuildSolidList(); -} - - -/* -======================== -CG_ReadNextSnapshot - -This is the only place new snapshots are requested -This may increment cgs.processedSnapshotNum multiple -times if the client system fails to return a -valid snapshot. -======================== -*/ -static snapshot_t *CG_ReadNextSnapshot( void ) { - qboolean r; - snapshot_t *dest; - - if ( cg.latestSnapshotNum > cgs.processedSnapshotNum + 1000 ) { - CG_Printf( "WARNING: CG_ReadNextSnapshot: way out of range, %i > %i", - cg.latestSnapshotNum, cgs.processedSnapshotNum ); - } - - while ( cgs.processedSnapshotNum < cg.latestSnapshotNum ) { - // decide which of the two slots to load it into - if ( cg.snap == &cg.activeSnapshots[0] ) { - dest = &cg.activeSnapshots[1]; - } else { - dest = &cg.activeSnapshots[0]; - } - - // try to read the snapshot from the client system - cgs.processedSnapshotNum++; - r = trap_GetSnapshot( cgs.processedSnapshotNum, dest ); - - // FIXME: why would trap_GetSnapshot return a snapshot with the same server time - if ( cg.snap && r && dest->serverTime == cg.snap->serverTime ) { - //continue; - } - - // if it succeeded, return - if ( r ) { - CG_AddLagometerSnapshotInfo( dest ); - return dest; - } - - // a GetSnapshot will return failure if the snapshot - // never arrived, or is so old that its entities - // have been shoved off the end of the circular - // buffer in the client system. - - // record as a dropped packet - CG_AddLagometerSnapshotInfo( NULL ); - - // If there are additional snapshots, continue trying to - // read them. - } - - // nothing left to read - return NULL; -} - - -/* -============ -CG_ProcessSnapshots - -We are trying to set up a renderable view, so determine -what the simulated time is, and try to get snapshots -both before and after that time if available. - -If we don't have a valid cg.snap after exiting this function, -then a 3D game view cannot be rendered. This should only happen -right after the initial connection. After cg.snap has been valid -once, it will never turn invalid. - -Even if cg.snap is valid, cg.nextSnap may not be, if the snapshot -hasn't arrived yet (it becomes an extrapolating situation instead -of an interpolating one) - -============ -*/ -void CG_ProcessSnapshots( void ) { - snapshot_t *snap; - int n; - - // see what the latest snapshot the client system has is - trap_GetCurrentSnapshotNumber( &n, &cg.latestSnapshotTime ); - if ( n != cg.latestSnapshotNum ) { - if ( n < cg.latestSnapshotNum ) { - // this should never happen - CG_Error( "CG_ProcessSnapshots: n < cg.latestSnapshotNum" ); - } - cg.latestSnapshotNum = n; - } - - // If we have yet to receive a snapshot, check for it. - // Once we have gotten the first snapshot, cg.snap will - // always have valid data for the rest of the game - while ( !cg.snap ) { - snap = CG_ReadNextSnapshot(); - if ( !snap ) { - // we can't continue until we get a snapshot - return; - } - - // set our weapon selection to what - // the playerstate is currently using - if ( !( snap->snapFlags & SNAPFLAG_NOT_ACTIVE ) ) { - CG_SetInitialSnapshot( snap ); - } - } - - // loop until we either have a valid nextSnap with a serverTime - // greater than cg.time to interpolate towards, or we run - // out of available snapshots - do { - // if we don't have a nextframe, try and read a new one in - if ( !cg.nextSnap ) { - snap = CG_ReadNextSnapshot(); - - // if we still don't have a nextframe, we will just have to - // extrapolate - if ( !snap ) { - break; - } - - CG_SetNextSnap( snap ); - - - // if time went backwards, we have a level restart - if ( cg.nextSnap->serverTime < cg.snap->serverTime ) { - CG_Error( "CG_ProcessSnapshots: Server time went backwards" ); - } - } - - // if our time is < nextFrame's, we have a nice interpolating state - if ( cg.time >= cg.snap->serverTime && cg.time < cg.nextSnap->serverTime ) { - break; - } - - // we have passed the transition from nextFrame to frame - CG_TransitionSnapshot(); - } while ( 1 ); - - // assert our valid conditions upon exiting - if ( cg.snap == NULL ) { - CG_Error( "CG_ProcessSnapshots: cg.snap == NULL" ); - } - if ( cg.time < cg.snap->serverTime ) { - // this can happen right after a vid_restart - cg.time = cg.snap->serverTime; - } - if ( cg.nextSnap != NULL && cg.nextSnap->serverTime <= cg.time ) { - CG_Error( "CG_ProcessSnapshots: cg.nextSnap->serverTime <= cg.time" ); - } - -} - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// cg_snapshot.c -- things that happen on snapshot transition, +// not necessarily every single rendered frame + +#include "cg_local.h" + + + +/* +================== +CG_ResetEntity +================== +*/ +static void CG_ResetEntity( centity_t *cent ) { + // if the previous snapshot this entity was updated in is at least + // an event window back in time then we can reset the previous event + if ( cent->snapShotTime < cg.time - EVENT_VALID_MSEC ) { + cent->previousEvent = 0; + } + + cent->trailTime = cg.snap->serverTime; + + VectorCopy (cent->currentState.origin, cent->lerpOrigin); + VectorCopy (cent->currentState.angles, cent->lerpAngles); + if ( cent->currentState.eType == ET_PLAYER ) { + CG_ResetPlayerEntity( cent ); + } +} + +/* +=============== +CG_TransitionEntity + +cent->nextState is moved to cent->currentState and events are fired +=============== +*/ +static void CG_TransitionEntity( centity_t *cent ) { + cent->currentState = cent->nextState; + cent->currentValid = qtrue; + + // reset if the entity wasn't in the last frame or was teleported + if ( !cent->interpolate ) { + CG_ResetEntity( cent ); + } + + // clear the next state. if will be set by the next CG_SetNextSnap + cent->interpolate = qfalse; + + // check for events + CG_CheckEvents( cent ); +} + + +/* +================== +CG_SetInitialSnapshot + +This will only happen on the very first snapshot, or +on tourney restarts. All other times will use +CG_TransitionSnapshot instead. + +FIXME: Also called by map_restart? +================== +*/ +void CG_SetInitialSnapshot( snapshot_t *snap ) { + int i; + centity_t *cent; + entityState_t *state; + + cg.snap = snap; + + BG_PlayerStateToEntityState( &snap->ps, &cg_entities[ snap->ps.clientNum ].currentState, qfalse ); + + // sort out solid entities + CG_BuildSolidList(); + + CG_ExecuteNewServerCommands( snap->serverCommandSequence ); + + // set our local weapon selection pointer to + // what the server has indicated the current weapon is + CG_Respawn(); + + for ( i = 0 ; i < cg.snap->numEntities ; i++ ) { + state = &cg.snap->entities[ i ]; + cent = &cg_entities[ state->number ]; + + memcpy(¢->currentState, state, sizeof(entityState_t)); + //cent->currentState = *state; + cent->interpolate = qfalse; + cent->currentValid = qtrue; + + CG_ResetEntity( cent ); + + // check for events + CG_CheckEvents( cent ); + } +} + + +/* +=================== +CG_TransitionSnapshot + +The transition point from snap to nextSnap has passed +=================== +*/ +static void CG_TransitionSnapshot( void ) { + centity_t *cent; + snapshot_t *oldFrame; + int i; + + if ( !cg.snap ) { + CG_Error( "CG_TransitionSnapshot: NULL cg.snap" ); + } + if ( !cg.nextSnap ) { + CG_Error( "CG_TransitionSnapshot: NULL cg.nextSnap" ); + } + + // execute any server string commands before transitioning entities + CG_ExecuteNewServerCommands( cg.nextSnap->serverCommandSequence ); + + // if we had a map_restart, set everthing with initial + if ( !cg.snap ) { + } + + // clear the currentValid flag for all entities in the existing snapshot + for ( i = 0 ; i < cg.snap->numEntities ; i++ ) { + cent = &cg_entities[ cg.snap->entities[ i ].number ]; + cent->currentValid = qfalse; + } + + // move nextSnap to snap and do the transitions + oldFrame = cg.snap; + cg.snap = cg.nextSnap; + + BG_PlayerStateToEntityState( &cg.snap->ps, &cg_entities[ cg.snap->ps.clientNum ].currentState, qfalse ); + cg_entities[ cg.snap->ps.clientNum ].interpolate = qfalse; + + for ( i = 0 ; i < cg.snap->numEntities ; i++ ) { + cent = &cg_entities[ cg.snap->entities[ i ].number ]; + CG_TransitionEntity( cent ); + + // remember time of snapshot this entity was last updated in + cent->snapShotTime = cg.snap->serverTime; + } + + cg.nextSnap = NULL; + + // check for playerstate transition events + if ( oldFrame ) { + playerState_t *ops, *ps; + + ops = &oldFrame->ps; + ps = &cg.snap->ps; + // teleporting checks are irrespective of prediction + if ( ( ps->eFlags ^ ops->eFlags ) & EF_TELEPORT_BIT ) { + cg.thisFrameTeleport = qtrue; // will be cleared by prediction code + } + + // if we are not doing client side movement prediction for any + // reason, then the client events and view changes will be issued now + if ( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW) + || cg_nopredict.integer || cg_synchronousClients.integer ) { + CG_TransitionPlayerState( ps, ops ); + } + } + +} + + +/* +=================== +CG_SetNextSnap + +A new snapshot has just been read in from the client system. +=================== +*/ +static void CG_SetNextSnap( snapshot_t *snap ) { + int num; + entityState_t *es; + centity_t *cent; + + cg.nextSnap = snap; + + BG_PlayerStateToEntityState( &snap->ps, &cg_entities[ snap->ps.clientNum ].nextState, qfalse ); + cg_entities[ cg.snap->ps.clientNum ].interpolate = qtrue; + + // check for extrapolation errors + for ( num = 0 ; num < snap->numEntities ; num++ ) { + es = &snap->entities[num]; + cent = &cg_entities[ es->number ]; + + memcpy(¢->nextState, es, sizeof(entityState_t)); + //cent->nextState = *es; + + // if this frame is a teleport, or the entity wasn't in the + // previous frame, don't interpolate + if ( !cent->currentValid || ( ( cent->currentState.eFlags ^ es->eFlags ) & EF_TELEPORT_BIT ) ) { + cent->interpolate = qfalse; + } else { + cent->interpolate = qtrue; + } + } + + // if the next frame is a teleport for the playerstate, we + // can't interpolate during demos + if ( cg.snap && ( ( snap->ps.eFlags ^ cg.snap->ps.eFlags ) & EF_TELEPORT_BIT ) ) { + cg.nextFrameTeleport = qtrue; + } else { + cg.nextFrameTeleport = qfalse; + } + + // if changing follow mode, don't interpolate + if ( cg.nextSnap->ps.clientNum != cg.snap->ps.clientNum ) { + cg.nextFrameTeleport = qtrue; + } + + // if changing server restarts, don't interpolate + if ( ( cg.nextSnap->snapFlags ^ cg.snap->snapFlags ) & SNAPFLAG_SERVERCOUNT ) { + cg.nextFrameTeleport = qtrue; + } + + // sort out solid entities + CG_BuildSolidList(); +} + + +/* +======================== +CG_ReadNextSnapshot + +This is the only place new snapshots are requested +This may increment cgs.processedSnapshotNum multiple +times if the client system fails to return a +valid snapshot. +======================== +*/ +static snapshot_t *CG_ReadNextSnapshot( void ) { + qboolean r; + snapshot_t *dest; + + if ( cg.latestSnapshotNum > cgs.processedSnapshotNum + 1000 ) { + CG_Printf( "WARNING: CG_ReadNextSnapshot: way out of range, %i > %i", + cg.latestSnapshotNum, cgs.processedSnapshotNum ); + } + + while ( cgs.processedSnapshotNum < cg.latestSnapshotNum ) { + // decide which of the two slots to load it into + if ( cg.snap == &cg.activeSnapshots[0] ) { + dest = &cg.activeSnapshots[1]; + } else { + dest = &cg.activeSnapshots[0]; + } + + // try to read the snapshot from the client system + cgs.processedSnapshotNum++; + r = trap_GetSnapshot( cgs.processedSnapshotNum, dest ); + + // FIXME: why would trap_GetSnapshot return a snapshot with the same server time + if ( cg.snap && r && dest->serverTime == cg.snap->serverTime ) { + //continue; + } + + // if it succeeded, return + if ( r ) { + CG_AddLagometerSnapshotInfo( dest ); + return dest; + } + + // a GetSnapshot will return failure if the snapshot + // never arrived, or is so old that its entities + // have been shoved off the end of the circular + // buffer in the client system. + + // record as a dropped packet + CG_AddLagometerSnapshotInfo( NULL ); + + // If there are additional snapshots, continue trying to + // read them. + } + + // nothing left to read + return NULL; +} + + +/* +============ +CG_ProcessSnapshots + +We are trying to set up a renderable view, so determine +what the simulated time is, and try to get snapshots +both before and after that time if available. + +If we don't have a valid cg.snap after exiting this function, +then a 3D game view cannot be rendered. This should only happen +right after the initial connection. After cg.snap has been valid +once, it will never turn invalid. + +Even if cg.snap is valid, cg.nextSnap may not be, if the snapshot +hasn't arrived yet (it becomes an extrapolating situation instead +of an interpolating one) + +============ +*/ +void CG_ProcessSnapshots( void ) { + snapshot_t *snap; + int n; + + // see what the latest snapshot the client system has is + trap_GetCurrentSnapshotNumber( &n, &cg.latestSnapshotTime ); + if ( n != cg.latestSnapshotNum ) { + if ( n < cg.latestSnapshotNum ) { + // this should never happen + CG_Error( "CG_ProcessSnapshots: n < cg.latestSnapshotNum" ); + } + cg.latestSnapshotNum = n; + } + + // If we have yet to receive a snapshot, check for it. + // Once we have gotten the first snapshot, cg.snap will + // always have valid data for the rest of the game + while ( !cg.snap ) { + snap = CG_ReadNextSnapshot(); + if ( !snap ) { + // we can't continue until we get a snapshot + return; + } + + // set our weapon selection to what + // the playerstate is currently using + if ( !( snap->snapFlags & SNAPFLAG_NOT_ACTIVE ) ) { + CG_SetInitialSnapshot( snap ); + } + } + + // loop until we either have a valid nextSnap with a serverTime + // greater than cg.time to interpolate towards, or we run + // out of available snapshots + do { + // if we don't have a nextframe, try and read a new one in + if ( !cg.nextSnap ) { + snap = CG_ReadNextSnapshot(); + + // if we still don't have a nextframe, we will just have to + // extrapolate + if ( !snap ) { + break; + } + + CG_SetNextSnap( snap ); + + + // if time went backwards, we have a level restart + if ( cg.nextSnap->serverTime < cg.snap->serverTime ) { + CG_Error( "CG_ProcessSnapshots: Server time went backwards" ); + } + } + + // if our time is < nextFrame's, we have a nice interpolating state + if ( cg.time >= cg.snap->serverTime && cg.time < cg.nextSnap->serverTime ) { + break; + } + + // we have passed the transition from nextFrame to frame + CG_TransitionSnapshot(); + } while ( 1 ); + + // assert our valid conditions upon exiting + if ( cg.snap == NULL ) { + CG_Error( "CG_ProcessSnapshots: cg.snap == NULL" ); + } + if ( cg.time < cg.snap->serverTime ) { + // this can happen right after a vid_restart + cg.time = cg.snap->serverTime; + } + if ( cg.nextSnap != NULL && cg.nextSnap->serverTime <= cg.time ) { + CG_Error( "CG_ProcessSnapshots: cg.nextSnap->serverTime <= cg.time" ); + } + +} + diff --git a/code/cgame/cg_syscalls.asm b/code/cgame/cg_syscalls.asm index 8363fe3..0874b47 100755 --- a/code/cgame/cg_syscalls.asm +++ b/code/cgame/cg_syscalls.asm @@ -1,106 +1,106 @@ -code - -equ trap_Print -1 -equ trap_Error -2 -equ trap_Milliseconds -3 -equ trap_Cvar_Register -4 -equ trap_Cvar_Update -5 -equ trap_Cvar_Set -6 -equ trap_Cvar_VariableStringBuffer -7 -equ trap_Argc -8 -equ trap_Argv -9 -equ trap_Args -10 -equ trap_FS_FOpenFile -11 -equ trap_FS_Read -12 -equ trap_FS_Write -13 -equ trap_FS_FCloseFile -14 -equ trap_SendConsoleCommand -15 -equ trap_AddCommand -16 -equ trap_SendClientCommand -17 -equ trap_UpdateScreen -18 -equ trap_CM_LoadMap -19 -equ trap_CM_NumInlineModels -20 -equ trap_CM_InlineModel -21 -equ trap_CM_LoadModel -22 -equ trap_CM_TempBoxModel -23 -equ trap_CM_PointContents -24 -equ trap_CM_TransformedPointContents -25 -equ trap_CM_BoxTrace -26 -equ trap_CM_TransformedBoxTrace -27 -equ trap_CM_MarkFragments -28 -equ trap_S_StartSound -29 -equ trap_S_StartLocalSound -30 -equ trap_S_ClearLoopingSounds -31 -equ trap_S_AddLoopingSound -32 -equ trap_S_UpdateEntityPosition -33 -equ trap_S_Respatialize -34 -equ trap_S_RegisterSound -35 -equ trap_S_StartBackgroundTrack -36 -equ trap_R_LoadWorldMap -37 -equ trap_R_RegisterModel -38 -equ trap_R_RegisterSkin -39 -equ trap_R_RegisterShader -40 -equ trap_R_ClearScene -41 -equ trap_R_AddRefEntityToScene -42 -equ trap_R_AddPolyToScene -43 -equ trap_R_AddLightToScene -44 -equ trap_R_RenderScene -45 -equ trap_R_SetColor -46 -equ trap_R_DrawStretchPic -47 -equ trap_R_ModelBounds -48 -equ trap_R_LerpTag -49 -equ trap_GetGlconfig -50 -equ trap_GetGameState -51 -equ trap_GetCurrentSnapshotNumber -52 -equ trap_GetSnapshot -53 -equ trap_GetServerCommand -54 -equ trap_GetCurrentCmdNumber -55 -equ trap_GetUserCmd -56 -equ trap_SetUserCmdValue -57 -equ trap_R_RegisterShaderNoMip -58 -equ trap_MemoryRemaining -59 -equ trap_R_RegisterFont -60 -equ trap_Key_IsDown -61 -equ trap_Key_GetCatcher -62 -equ trap_Key_SetCatcher -63 -equ trap_Key_GetKey -64 -equ trap_PC_AddGlobalDefine -65 -equ trap_PC_LoadSource -66 -equ trap_PC_FreeSource -67 -equ trap_PC_ReadToken -68 -equ trap_PC_SourceFileAndLine -69 -equ trap_S_StopBackgroundTrack -70 -equ trap_RealTime -71 -equ trap_SnapVector -72 -equ trap_RemoveCommand -73 -equ trap_R_LightForPoint -74 -equ trap_CIN_PlayCinematic -75 -equ trap_CIN_StopCinematic -76 -equ trap_CIN_RunCinematic -77 -equ trap_CIN_DrawCinematic -78 -equ trap_CIN_SetExtents -79 -equ trap_R_RemapShader -80 -equ trap_S_AddRealLoopingSound -81 -equ trap_S_StopLoopingSound -82 -equ trap_CM_TempCapsuleModel -83 -equ trap_CM_CapsuleTrace -84 -equ trap_CM_TransformedCapsuleTrace -85 -equ trap_R_AddAdditiveLightToScene -86 -equ trap_GetEntityToken -87 -equ trap_R_AddPolysToScene -88 -equ trap_R_inPVS -89 -equ trap_FS_Seek -90 - -equ memset -101 -equ memcpy -102 -equ strncpy -103 -equ sin -104 -equ cos -105 -equ atan2 -106 -equ sqrt -107 -equ floor -108 -equ ceil -109 -equ testPrintInt -110 -equ testPrintFloat -111 -equ acos -112 - +code + +equ trap_Print -1 +equ trap_Error -2 +equ trap_Milliseconds -3 +equ trap_Cvar_Register -4 +equ trap_Cvar_Update -5 +equ trap_Cvar_Set -6 +equ trap_Cvar_VariableStringBuffer -7 +equ trap_Argc -8 +equ trap_Argv -9 +equ trap_Args -10 +equ trap_FS_FOpenFile -11 +equ trap_FS_Read -12 +equ trap_FS_Write -13 +equ trap_FS_FCloseFile -14 +equ trap_SendConsoleCommand -15 +equ trap_AddCommand -16 +equ trap_SendClientCommand -17 +equ trap_UpdateScreen -18 +equ trap_CM_LoadMap -19 +equ trap_CM_NumInlineModels -20 +equ trap_CM_InlineModel -21 +equ trap_CM_LoadModel -22 +equ trap_CM_TempBoxModel -23 +equ trap_CM_PointContents -24 +equ trap_CM_TransformedPointContents -25 +equ trap_CM_BoxTrace -26 +equ trap_CM_TransformedBoxTrace -27 +equ trap_CM_MarkFragments -28 +equ trap_S_StartSound -29 +equ trap_S_StartLocalSound -30 +equ trap_S_ClearLoopingSounds -31 +equ trap_S_AddLoopingSound -32 +equ trap_S_UpdateEntityPosition -33 +equ trap_S_Respatialize -34 +equ trap_S_RegisterSound -35 +equ trap_S_StartBackgroundTrack -36 +equ trap_R_LoadWorldMap -37 +equ trap_R_RegisterModel -38 +equ trap_R_RegisterSkin -39 +equ trap_R_RegisterShader -40 +equ trap_R_ClearScene -41 +equ trap_R_AddRefEntityToScene -42 +equ trap_R_AddPolyToScene -43 +equ trap_R_AddLightToScene -44 +equ trap_R_RenderScene -45 +equ trap_R_SetColor -46 +equ trap_R_DrawStretchPic -47 +equ trap_R_ModelBounds -48 +equ trap_R_LerpTag -49 +equ trap_GetGlconfig -50 +equ trap_GetGameState -51 +equ trap_GetCurrentSnapshotNumber -52 +equ trap_GetSnapshot -53 +equ trap_GetServerCommand -54 +equ trap_GetCurrentCmdNumber -55 +equ trap_GetUserCmd -56 +equ trap_SetUserCmdValue -57 +equ trap_R_RegisterShaderNoMip -58 +equ trap_MemoryRemaining -59 +equ trap_R_RegisterFont -60 +equ trap_Key_IsDown -61 +equ trap_Key_GetCatcher -62 +equ trap_Key_SetCatcher -63 +equ trap_Key_GetKey -64 +equ trap_PC_AddGlobalDefine -65 +equ trap_PC_LoadSource -66 +equ trap_PC_FreeSource -67 +equ trap_PC_ReadToken -68 +equ trap_PC_SourceFileAndLine -69 +equ trap_S_StopBackgroundTrack -70 +equ trap_RealTime -71 +equ trap_SnapVector -72 +equ trap_RemoveCommand -73 +equ trap_R_LightForPoint -74 +equ trap_CIN_PlayCinematic -75 +equ trap_CIN_StopCinematic -76 +equ trap_CIN_RunCinematic -77 +equ trap_CIN_DrawCinematic -78 +equ trap_CIN_SetExtents -79 +equ trap_R_RemapShader -80 +equ trap_S_AddRealLoopingSound -81 +equ trap_S_StopLoopingSound -82 +equ trap_CM_TempCapsuleModel -83 +equ trap_CM_CapsuleTrace -84 +equ trap_CM_TransformedCapsuleTrace -85 +equ trap_R_AddAdditiveLightToScene -86 +equ trap_GetEntityToken -87 +equ trap_R_AddPolysToScene -88 +equ trap_R_inPVS -89 +equ trap_FS_Seek -90 + +equ memset -101 +equ memcpy -102 +equ strncpy -103 +equ sin -104 +equ cos -105 +equ atan2 -106 +equ sqrt -107 +equ floor -108 +equ ceil -109 +equ testPrintInt -110 +equ testPrintFloat -111 +equ acos -112 + diff --git a/code/cgame/cg_syscalls.c b/code/cgame/cg_syscalls.c index a7bf659..c7ad938 100755 --- a/code/cgame/cg_syscalls.c +++ b/code/cgame/cg_syscalls.c @@ -1,445 +1,445 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// cg_syscalls.c -- this file is only included when building a dll -// cg_syscalls.asm is included instead when building a qvm -#ifdef Q3_VM -#error "Do not use in VM build" -#endif - -#include "cg_local.h" - -static int (QDECL *syscall)( int arg, ... ) = (int (QDECL *)( int, ...))-1; - - -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 *fmt ) { - syscall( CG_PRINT, fmt ); -} - -void trap_Error( const char *fmt ) { - syscall( CG_ERROR, fmt ); -} - -int trap_Milliseconds( void ) { - return syscall( CG_MILLISECONDS ); -} - -void trap_Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ) { - syscall( CG_CVAR_REGISTER, vmCvar, varName, defaultValue, flags ); -} - -void trap_Cvar_Update( vmCvar_t *vmCvar ) { - syscall( CG_CVAR_UPDATE, vmCvar ); -} - -void trap_Cvar_Set( const char *var_name, const char *value ) { - syscall( CG_CVAR_SET, var_name, value ); -} - -void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ) { - syscall( CG_CVAR_VARIABLESTRINGBUFFER, var_name, buffer, bufsize ); -} - -int trap_Argc( void ) { - return syscall( CG_ARGC ); -} - -void trap_Argv( int n, char *buffer, int bufferLength ) { - syscall( CG_ARGV, n, buffer, bufferLength ); -} - -void trap_Args( char *buffer, int bufferLength ) { - syscall( CG_ARGS, buffer, bufferLength ); -} - -int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ) { - return syscall( CG_FS_FOPENFILE, qpath, f, mode ); -} - -void trap_FS_Read( void *buffer, int len, fileHandle_t f ) { - syscall( CG_FS_READ, buffer, len, f ); -} - -void trap_FS_Write( const void *buffer, int len, fileHandle_t f ) { - syscall( CG_FS_WRITE, buffer, len, f ); -} - -void trap_FS_FCloseFile( fileHandle_t f ) { - syscall( CG_FS_FCLOSEFILE, f ); -} - -int trap_FS_Seek( fileHandle_t f, long offset, int origin ) { - return syscall( CG_FS_SEEK, f, offset, origin ); -} - -void trap_SendConsoleCommand( const char *text ) { - syscall( CG_SENDCONSOLECOMMAND, text ); -} - -void trap_AddCommand( const char *cmdName ) { - syscall( CG_ADDCOMMAND, cmdName ); -} - -void trap_RemoveCommand( const char *cmdName ) { - syscall( CG_REMOVECOMMAND, cmdName ); -} - -void trap_SendClientCommand( const char *s ) { - syscall( CG_SENDCLIENTCOMMAND, s ); -} - -void trap_UpdateScreen( void ) { - syscall( CG_UPDATESCREEN ); -} - -void trap_CM_LoadMap( const char *mapname ) { - syscall( CG_CM_LOADMAP, mapname ); -} - -int trap_CM_NumInlineModels( void ) { - return syscall( CG_CM_NUMINLINEMODELS ); -} - -clipHandle_t trap_CM_InlineModel( int index ) { - return syscall( CG_CM_INLINEMODEL, index ); -} - -clipHandle_t trap_CM_TempBoxModel( const vec3_t mins, const vec3_t maxs ) { - return syscall( CG_CM_TEMPBOXMODEL, mins, maxs ); -} - -clipHandle_t trap_CM_TempCapsuleModel( const vec3_t mins, const vec3_t maxs ) { - return syscall( CG_CM_TEMPCAPSULEMODEL, mins, maxs ); -} - -int trap_CM_PointContents( const vec3_t p, clipHandle_t model ) { - return syscall( CG_CM_POINTCONTENTS, p, model ); -} - -int trap_CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles ) { - return syscall( CG_CM_TRANSFORMEDPOINTCONTENTS, p, model, origin, angles ); -} - -void trap_CM_BoxTrace( trace_t *results, const vec3_t start, const vec3_t end, - const vec3_t mins, const vec3_t maxs, - clipHandle_t model, int brushmask ) { - syscall( CG_CM_BOXTRACE, results, start, end, mins, maxs, model, brushmask ); -} - -void trap_CM_CapsuleTrace( trace_t *results, const vec3_t start, const vec3_t end, - const vec3_t mins, const vec3_t maxs, - clipHandle_t model, int brushmask ) { - syscall( CG_CM_CAPSULETRACE, results, start, end, mins, maxs, model, brushmask ); -} - -void trap_CM_TransformedBoxTrace( trace_t *results, const vec3_t start, const vec3_t end, - const vec3_t mins, const vec3_t maxs, - clipHandle_t model, int brushmask, - const vec3_t origin, const vec3_t angles ) { - syscall( CG_CM_TRANSFORMEDBOXTRACE, results, start, end, mins, maxs, model, brushmask, origin, angles ); -} - -void trap_CM_TransformedCapsuleTrace( trace_t *results, const vec3_t start, const vec3_t end, - const vec3_t mins, const vec3_t maxs, - clipHandle_t model, int brushmask, - const vec3_t origin, const vec3_t angles ) { - syscall( CG_CM_TRANSFORMEDCAPSULETRACE, results, start, end, mins, maxs, model, brushmask, origin, angles ); -} - -int trap_CM_MarkFragments( int numPoints, const vec3_t *points, - const vec3_t projection, - int maxPoints, vec3_t pointBuffer, - int maxFragments, markFragment_t *fragmentBuffer ) { - return syscall( CG_CM_MARKFRAGMENTS, numPoints, points, projection, maxPoints, pointBuffer, maxFragments, fragmentBuffer ); -} - -void trap_S_StartSound( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx ) { - syscall( CG_S_STARTSOUND, origin, entityNum, entchannel, sfx ); -} - -void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum ) { - syscall( CG_S_STARTLOCALSOUND, sfx, channelNum ); -} - -void trap_S_ClearLoopingSounds( qboolean killall ) { - syscall( CG_S_CLEARLOOPINGSOUNDS, killall ); -} - -void trap_S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ) { - syscall( CG_S_ADDLOOPINGSOUND, entityNum, origin, velocity, sfx ); -} - -void trap_S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ) { - syscall( CG_S_ADDREALLOOPINGSOUND, entityNum, origin, velocity, sfx ); -} - -void trap_S_StopLoopingSound( int entityNum ) { - syscall( CG_S_STOPLOOPINGSOUND, entityNum ); -} - -void trap_S_UpdateEntityPosition( int entityNum, const vec3_t origin ) { - syscall( CG_S_UPDATEENTITYPOSITION, entityNum, origin ); -} - -void trap_S_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ) { - syscall( CG_S_RESPATIALIZE, entityNum, origin, axis, inwater ); -} - -sfxHandle_t trap_S_RegisterSound( const char *sample, qboolean compressed ) { - return syscall( CG_S_REGISTERSOUND, sample, compressed ); -} - -void trap_S_StartBackgroundTrack( const char *intro, const char *loop ) { - syscall( CG_S_STARTBACKGROUNDTRACK, intro, loop ); -} - -void trap_R_LoadWorldMap( const char *mapname ) { - syscall( CG_R_LOADWORLDMAP, mapname ); -} - -qhandle_t trap_R_RegisterModel( const char *name ) { - return syscall( CG_R_REGISTERMODEL, name ); -} - -qhandle_t trap_R_RegisterSkin( const char *name ) { - return syscall( CG_R_REGISTERSKIN, name ); -} - -qhandle_t trap_R_RegisterShader( const char *name ) { - return syscall( CG_R_REGISTERSHADER, name ); -} - -qhandle_t trap_R_RegisterShaderNoMip( const char *name ) { - return syscall( CG_R_REGISTERSHADERNOMIP, name ); -} - -void trap_R_RegisterFont(const char *fontName, int pointSize, fontInfo_t *font) { - syscall(CG_R_REGISTERFONT, fontName, pointSize, font ); -} - -void trap_R_ClearScene( void ) { - syscall( CG_R_CLEARSCENE ); -} - -void trap_R_AddRefEntityToScene( const refEntity_t *re ) { - syscall( CG_R_ADDREFENTITYTOSCENE, re ); -} - -void trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts ) { - syscall( CG_R_ADDPOLYTOSCENE, hShader, numVerts, verts ); -} - -void trap_R_AddPolysToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts, int num ) { - syscall( CG_R_ADDPOLYSTOSCENE, hShader, numVerts, verts, num ); -} - -int trap_R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir ) { - return syscall( CG_R_LIGHTFORPOINT, point, ambientLight, directedLight, lightDir ); -} - -void trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ) { - syscall( CG_R_ADDLIGHTTOSCENE, org, PASSFLOAT(intensity), PASSFLOAT(r), PASSFLOAT(g), PASSFLOAT(b) ); -} - -void trap_R_AddAdditiveLightToScene( const vec3_t org, float intensity, float r, float g, float b ) { - syscall( CG_R_ADDADDITIVELIGHTTOSCENE, org, PASSFLOAT(intensity), PASSFLOAT(r), PASSFLOAT(g), PASSFLOAT(b) ); -} - -void trap_R_RenderScene( const refdef_t *fd ) { - syscall( CG_R_RENDERSCENE, fd ); -} - -void trap_R_SetColor( const float *rgba ) { - syscall( CG_R_SETCOLOR, rgba ); -} - -void trap_R_DrawStretchPic( float x, float y, float w, float h, - float s1, float t1, float s2, float t2, qhandle_t hShader ) { - syscall( CG_R_DRAWSTRETCHPIC, PASSFLOAT(x), PASSFLOAT(y), PASSFLOAT(w), PASSFLOAT(h), PASSFLOAT(s1), PASSFLOAT(t1), PASSFLOAT(s2), PASSFLOAT(t2), hShader ); -} - -void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ) { - syscall( CG_R_MODELBOUNDS, model, mins, maxs ); -} - -int trap_R_LerpTag( orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame, - float frac, const char *tagName ) { - return syscall( CG_R_LERPTAG, tag, mod, startFrame, endFrame, PASSFLOAT(frac), tagName ); -} - -void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset ) { - syscall( CG_R_REMAP_SHADER, oldShader, newShader, timeOffset ); -} - -void trap_GetGlconfig( glconfig_t *glconfig ) { - syscall( CG_GETGLCONFIG, glconfig ); -} - -void trap_GetGameState( gameState_t *gamestate ) { - syscall( CG_GETGAMESTATE, gamestate ); -} - -void trap_GetCurrentSnapshotNumber( int *snapshotNumber, int *serverTime ) { - syscall( CG_GETCURRENTSNAPSHOTNUMBER, snapshotNumber, serverTime ); -} - -qboolean trap_GetSnapshot( int snapshotNumber, snapshot_t *snapshot ) { - return syscall( CG_GETSNAPSHOT, snapshotNumber, snapshot ); -} - -qboolean trap_GetServerCommand( int serverCommandNumber ) { - return syscall( CG_GETSERVERCOMMAND, serverCommandNumber ); -} - -int trap_GetCurrentCmdNumber( void ) { - return syscall( CG_GETCURRENTCMDNUMBER ); -} - -qboolean trap_GetUserCmd( int cmdNumber, usercmd_t *ucmd ) { - return syscall( CG_GETUSERCMD, cmdNumber, ucmd ); -} - -void trap_SetUserCmdValue( int stateValue, float sensitivityScale ) { - syscall( CG_SETUSERCMDVALUE, stateValue, PASSFLOAT(sensitivityScale) ); -} - -void testPrintInt( char *string, int i ) { - syscall( CG_TESTPRINTINT, string, i ); -} - -void testPrintFloat( char *string, float f ) { - syscall( CG_TESTPRINTFLOAT, string, PASSFLOAT(f) ); -} - -int trap_MemoryRemaining( void ) { - return syscall( CG_MEMORY_REMAINING ); -} - -qboolean trap_Key_IsDown( int keynum ) { - return syscall( CG_KEY_ISDOWN, keynum ); -} - -int trap_Key_GetCatcher( void ) { - return syscall( CG_KEY_GETCATCHER ); -} - -void trap_Key_SetCatcher( int catcher ) { - syscall( CG_KEY_SETCATCHER, catcher ); -} - -int trap_Key_GetKey( const char *binding ) { - return syscall( CG_KEY_GETKEY, binding ); -} - -int trap_PC_AddGlobalDefine( char *define ) { - return syscall( CG_PC_ADD_GLOBAL_DEFINE, define ); -} - -int trap_PC_LoadSource( const char *filename ) { - return syscall( CG_PC_LOAD_SOURCE, filename ); -} - -int trap_PC_FreeSource( int handle ) { - return syscall( CG_PC_FREE_SOURCE, handle ); -} - -int trap_PC_ReadToken( int handle, pc_token_t *pc_token ) { - return syscall( CG_PC_READ_TOKEN, handle, pc_token ); -} - -int trap_PC_SourceFileAndLine( int handle, char *filename, int *line ) { - return syscall( CG_PC_SOURCE_FILE_AND_LINE, handle, filename, line ); -} - -void trap_S_StopBackgroundTrack( void ) { - syscall( CG_S_STOPBACKGROUNDTRACK ); -} - -int trap_RealTime(qtime_t *qtime) { - return syscall( CG_REAL_TIME, qtime ); -} - -void trap_SnapVector( float *v ) { - syscall( CG_SNAPVECTOR, v ); -} - -// this returns a handle. arg0 is the name in the format "idlogo.roq", set arg1 to NULL, alteredstates to qfalse (do not alter gamestate) -int trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits) { - return syscall(CG_CIN_PLAYCINEMATIC, arg0, xpos, ypos, width, height, bits); -} - -// stops playing the cinematic and ends it. should always return FMV_EOF -// cinematics must be stopped in reverse order of when they are started -e_status trap_CIN_StopCinematic(int handle) { - return syscall(CG_CIN_STOPCINEMATIC, handle); -} - - -// will run a frame of the cinematic but will not draw it. Will return FMV_EOF if the end of the cinematic has been reached. -e_status trap_CIN_RunCinematic (int handle) { - return syscall(CG_CIN_RUNCINEMATIC, handle); -} - - -// draws the current frame -void trap_CIN_DrawCinematic (int handle) { - syscall(CG_CIN_DRAWCINEMATIC, handle); -} - - -// allows you to resize the animation dynamically -void trap_CIN_SetExtents (int handle, int x, int y, int w, int h) { - syscall(CG_CIN_SETEXTENTS, handle, x, y, w, h); -} - -/* -qboolean trap_loadCamera( const char *name ) { - return syscall( CG_LOADCAMERA, name ); -} - -void trap_startCamera(int time) { - syscall(CG_STARTCAMERA, time); -} - -qboolean trap_getCameraInfo( int time, vec3_t *origin, vec3_t *angles) { - return syscall( CG_GETCAMERAINFO, time, origin, angles ); -} -*/ - -qboolean trap_GetEntityToken( char *buffer, int bufferSize ) { - return syscall( CG_GET_ENTITY_TOKEN, buffer, bufferSize ); -} - -qboolean trap_R_inPVS( const vec3_t p1, const vec3_t p2 ) { - return syscall( CG_R_INPVS, p1, p2 ); -} +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// cg_syscalls.c -- this file is only included when building a dll +// cg_syscalls.asm is included instead when building a qvm +#ifdef Q3_VM +#error "Do not use in VM build" +#endif + +#include "cg_local.h" + +static int (QDECL *syscall)( int arg, ... ) = (int (QDECL *)( int, ...))-1; + + +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 *fmt ) { + syscall( CG_PRINT, fmt ); +} + +void trap_Error( const char *fmt ) { + syscall( CG_ERROR, fmt ); +} + +int trap_Milliseconds( void ) { + return syscall( CG_MILLISECONDS ); +} + +void trap_Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ) { + syscall( CG_CVAR_REGISTER, vmCvar, varName, defaultValue, flags ); +} + +void trap_Cvar_Update( vmCvar_t *vmCvar ) { + syscall( CG_CVAR_UPDATE, vmCvar ); +} + +void trap_Cvar_Set( const char *var_name, const char *value ) { + syscall( CG_CVAR_SET, var_name, value ); +} + +void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ) { + syscall( CG_CVAR_VARIABLESTRINGBUFFER, var_name, buffer, bufsize ); +} + +int trap_Argc( void ) { + return syscall( CG_ARGC ); +} + +void trap_Argv( int n, char *buffer, int bufferLength ) { + syscall( CG_ARGV, n, buffer, bufferLength ); +} + +void trap_Args( char *buffer, int bufferLength ) { + syscall( CG_ARGS, buffer, bufferLength ); +} + +int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ) { + return syscall( CG_FS_FOPENFILE, qpath, f, mode ); +} + +void trap_FS_Read( void *buffer, int len, fileHandle_t f ) { + syscall( CG_FS_READ, buffer, len, f ); +} + +void trap_FS_Write( const void *buffer, int len, fileHandle_t f ) { + syscall( CG_FS_WRITE, buffer, len, f ); +} + +void trap_FS_FCloseFile( fileHandle_t f ) { + syscall( CG_FS_FCLOSEFILE, f ); +} + +int trap_FS_Seek( fileHandle_t f, long offset, int origin ) { + return syscall( CG_FS_SEEK, f, offset, origin ); +} + +void trap_SendConsoleCommand( const char *text ) { + syscall( CG_SENDCONSOLECOMMAND, text ); +} + +void trap_AddCommand( const char *cmdName ) { + syscall( CG_ADDCOMMAND, cmdName ); +} + +void trap_RemoveCommand( const char *cmdName ) { + syscall( CG_REMOVECOMMAND, cmdName ); +} + +void trap_SendClientCommand( const char *s ) { + syscall( CG_SENDCLIENTCOMMAND, s ); +} + +void trap_UpdateScreen( void ) { + syscall( CG_UPDATESCREEN ); +} + +void trap_CM_LoadMap( const char *mapname ) { + syscall( CG_CM_LOADMAP, mapname ); +} + +int trap_CM_NumInlineModels( void ) { + return syscall( CG_CM_NUMINLINEMODELS ); +} + +clipHandle_t trap_CM_InlineModel( int index ) { + return syscall( CG_CM_INLINEMODEL, index ); +} + +clipHandle_t trap_CM_TempBoxModel( const vec3_t mins, const vec3_t maxs ) { + return syscall( CG_CM_TEMPBOXMODEL, mins, maxs ); +} + +clipHandle_t trap_CM_TempCapsuleModel( const vec3_t mins, const vec3_t maxs ) { + return syscall( CG_CM_TEMPCAPSULEMODEL, mins, maxs ); +} + +int trap_CM_PointContents( const vec3_t p, clipHandle_t model ) { + return syscall( CG_CM_POINTCONTENTS, p, model ); +} + +int trap_CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles ) { + return syscall( CG_CM_TRANSFORMEDPOINTCONTENTS, p, model, origin, angles ); +} + +void trap_CM_BoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask ) { + syscall( CG_CM_BOXTRACE, results, start, end, mins, maxs, model, brushmask ); +} + +void trap_CM_CapsuleTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask ) { + syscall( CG_CM_CAPSULETRACE, results, start, end, mins, maxs, model, brushmask ); +} + +void trap_CM_TransformedBoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask, + const vec3_t origin, const vec3_t angles ) { + syscall( CG_CM_TRANSFORMEDBOXTRACE, results, start, end, mins, maxs, model, brushmask, origin, angles ); +} + +void trap_CM_TransformedCapsuleTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask, + const vec3_t origin, const vec3_t angles ) { + syscall( CG_CM_TRANSFORMEDCAPSULETRACE, results, start, end, mins, maxs, model, brushmask, origin, angles ); +} + +int trap_CM_MarkFragments( int numPoints, const vec3_t *points, + const vec3_t projection, + int maxPoints, vec3_t pointBuffer, + int maxFragments, markFragment_t *fragmentBuffer ) { + return syscall( CG_CM_MARKFRAGMENTS, numPoints, points, projection, maxPoints, pointBuffer, maxFragments, fragmentBuffer ); +} + +void trap_S_StartSound( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx ) { + syscall( CG_S_STARTSOUND, origin, entityNum, entchannel, sfx ); +} + +void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum ) { + syscall( CG_S_STARTLOCALSOUND, sfx, channelNum ); +} + +void trap_S_ClearLoopingSounds( qboolean killall ) { + syscall( CG_S_CLEARLOOPINGSOUNDS, killall ); +} + +void trap_S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ) { + syscall( CG_S_ADDLOOPINGSOUND, entityNum, origin, velocity, sfx ); +} + +void trap_S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ) { + syscall( CG_S_ADDREALLOOPINGSOUND, entityNum, origin, velocity, sfx ); +} + +void trap_S_StopLoopingSound( int entityNum ) { + syscall( CG_S_STOPLOOPINGSOUND, entityNum ); +} + +void trap_S_UpdateEntityPosition( int entityNum, const vec3_t origin ) { + syscall( CG_S_UPDATEENTITYPOSITION, entityNum, origin ); +} + +void trap_S_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ) { + syscall( CG_S_RESPATIALIZE, entityNum, origin, axis, inwater ); +} + +sfxHandle_t trap_S_RegisterSound( const char *sample, qboolean compressed ) { + return syscall( CG_S_REGISTERSOUND, sample, compressed ); +} + +void trap_S_StartBackgroundTrack( const char *intro, const char *loop ) { + syscall( CG_S_STARTBACKGROUNDTRACK, intro, loop ); +} + +void trap_R_LoadWorldMap( const char *mapname ) { + syscall( CG_R_LOADWORLDMAP, mapname ); +} + +qhandle_t trap_R_RegisterModel( const char *name ) { + return syscall( CG_R_REGISTERMODEL, name ); +} + +qhandle_t trap_R_RegisterSkin( const char *name ) { + return syscall( CG_R_REGISTERSKIN, name ); +} + +qhandle_t trap_R_RegisterShader( const char *name ) { + return syscall( CG_R_REGISTERSHADER, name ); +} + +qhandle_t trap_R_RegisterShaderNoMip( const char *name ) { + return syscall( CG_R_REGISTERSHADERNOMIP, name ); +} + +void trap_R_RegisterFont(const char *fontName, int pointSize, fontInfo_t *font) { + syscall(CG_R_REGISTERFONT, fontName, pointSize, font ); +} + +void trap_R_ClearScene( void ) { + syscall( CG_R_CLEARSCENE ); +} + +void trap_R_AddRefEntityToScene( const refEntity_t *re ) { + syscall( CG_R_ADDREFENTITYTOSCENE, re ); +} + +void trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts ) { + syscall( CG_R_ADDPOLYTOSCENE, hShader, numVerts, verts ); +} + +void trap_R_AddPolysToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts, int num ) { + syscall( CG_R_ADDPOLYSTOSCENE, hShader, numVerts, verts, num ); +} + +int trap_R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir ) { + return syscall( CG_R_LIGHTFORPOINT, point, ambientLight, directedLight, lightDir ); +} + +void trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ) { + syscall( CG_R_ADDLIGHTTOSCENE, org, PASSFLOAT(intensity), PASSFLOAT(r), PASSFLOAT(g), PASSFLOAT(b) ); +} + +void trap_R_AddAdditiveLightToScene( const vec3_t org, float intensity, float r, float g, float b ) { + syscall( CG_R_ADDADDITIVELIGHTTOSCENE, org, PASSFLOAT(intensity), PASSFLOAT(r), PASSFLOAT(g), PASSFLOAT(b) ); +} + +void trap_R_RenderScene( const refdef_t *fd ) { + syscall( CG_R_RENDERSCENE, fd ); +} + +void trap_R_SetColor( const float *rgba ) { + syscall( CG_R_SETCOLOR, rgba ); +} + +void trap_R_DrawStretchPic( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader ) { + syscall( CG_R_DRAWSTRETCHPIC, PASSFLOAT(x), PASSFLOAT(y), PASSFLOAT(w), PASSFLOAT(h), PASSFLOAT(s1), PASSFLOAT(t1), PASSFLOAT(s2), PASSFLOAT(t2), hShader ); +} + +void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ) { + syscall( CG_R_MODELBOUNDS, model, mins, maxs ); +} + +int trap_R_LerpTag( orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame, + float frac, const char *tagName ) { + return syscall( CG_R_LERPTAG, tag, mod, startFrame, endFrame, PASSFLOAT(frac), tagName ); +} + +void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset ) { + syscall( CG_R_REMAP_SHADER, oldShader, newShader, timeOffset ); +} + +void trap_GetGlconfig( glconfig_t *glconfig ) { + syscall( CG_GETGLCONFIG, glconfig ); +} + +void trap_GetGameState( gameState_t *gamestate ) { + syscall( CG_GETGAMESTATE, gamestate ); +} + +void trap_GetCurrentSnapshotNumber( int *snapshotNumber, int *serverTime ) { + syscall( CG_GETCURRENTSNAPSHOTNUMBER, snapshotNumber, serverTime ); +} + +qboolean trap_GetSnapshot( int snapshotNumber, snapshot_t *snapshot ) { + return syscall( CG_GETSNAPSHOT, snapshotNumber, snapshot ); +} + +qboolean trap_GetServerCommand( int serverCommandNumber ) { + return syscall( CG_GETSERVERCOMMAND, serverCommandNumber ); +} + +int trap_GetCurrentCmdNumber( void ) { + return syscall( CG_GETCURRENTCMDNUMBER ); +} + +qboolean trap_GetUserCmd( int cmdNumber, usercmd_t *ucmd ) { + return syscall( CG_GETUSERCMD, cmdNumber, ucmd ); +} + +void trap_SetUserCmdValue( int stateValue, float sensitivityScale ) { + syscall( CG_SETUSERCMDVALUE, stateValue, PASSFLOAT(sensitivityScale) ); +} + +void testPrintInt( char *string, int i ) { + syscall( CG_TESTPRINTINT, string, i ); +} + +void testPrintFloat( char *string, float f ) { + syscall( CG_TESTPRINTFLOAT, string, PASSFLOAT(f) ); +} + +int trap_MemoryRemaining( void ) { + return syscall( CG_MEMORY_REMAINING ); +} + +qboolean trap_Key_IsDown( int keynum ) { + return syscall( CG_KEY_ISDOWN, keynum ); +} + +int trap_Key_GetCatcher( void ) { + return syscall( CG_KEY_GETCATCHER ); +} + +void trap_Key_SetCatcher( int catcher ) { + syscall( CG_KEY_SETCATCHER, catcher ); +} + +int trap_Key_GetKey( const char *binding ) { + return syscall( CG_KEY_GETKEY, binding ); +} + +int trap_PC_AddGlobalDefine( char *define ) { + return syscall( CG_PC_ADD_GLOBAL_DEFINE, define ); +} + +int trap_PC_LoadSource( const char *filename ) { + return syscall( CG_PC_LOAD_SOURCE, filename ); +} + +int trap_PC_FreeSource( int handle ) { + return syscall( CG_PC_FREE_SOURCE, handle ); +} + +int trap_PC_ReadToken( int handle, pc_token_t *pc_token ) { + return syscall( CG_PC_READ_TOKEN, handle, pc_token ); +} + +int trap_PC_SourceFileAndLine( int handle, char *filename, int *line ) { + return syscall( CG_PC_SOURCE_FILE_AND_LINE, handle, filename, line ); +} + +void trap_S_StopBackgroundTrack( void ) { + syscall( CG_S_STOPBACKGROUNDTRACK ); +} + +int trap_RealTime(qtime_t *qtime) { + return syscall( CG_REAL_TIME, qtime ); +} + +void trap_SnapVector( float *v ) { + syscall( CG_SNAPVECTOR, v ); +} + +// this returns a handle. arg0 is the name in the format "idlogo.roq", set arg1 to NULL, alteredstates to qfalse (do not alter gamestate) +int trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits) { + return syscall(CG_CIN_PLAYCINEMATIC, arg0, xpos, ypos, width, height, bits); +} + +// stops playing the cinematic and ends it. should always return FMV_EOF +// cinematics must be stopped in reverse order of when they are started +e_status trap_CIN_StopCinematic(int handle) { + return syscall(CG_CIN_STOPCINEMATIC, handle); +} + + +// will run a frame of the cinematic but will not draw it. Will return FMV_EOF if the end of the cinematic has been reached. +e_status trap_CIN_RunCinematic (int handle) { + return syscall(CG_CIN_RUNCINEMATIC, handle); +} + + +// draws the current frame +void trap_CIN_DrawCinematic (int handle) { + syscall(CG_CIN_DRAWCINEMATIC, handle); +} + + +// allows you to resize the animation dynamically +void trap_CIN_SetExtents (int handle, int x, int y, int w, int h) { + syscall(CG_CIN_SETEXTENTS, handle, x, y, w, h); +} + +/* +qboolean trap_loadCamera( const char *name ) { + return syscall( CG_LOADCAMERA, name ); +} + +void trap_startCamera(int time) { + syscall(CG_STARTCAMERA, time); +} + +qboolean trap_getCameraInfo( int time, vec3_t *origin, vec3_t *angles) { + return syscall( CG_GETCAMERAINFO, time, origin, angles ); +} +*/ + +qboolean trap_GetEntityToken( char *buffer, int bufferSize ) { + return syscall( CG_GET_ENTITY_TOKEN, buffer, bufferSize ); +} + +qboolean trap_R_inPVS( const vec3_t p1, const vec3_t p2 ) { + return syscall( CG_R_INPVS, p1, p2 ); +} diff --git a/code/cgame/cg_view.c b/code/cgame/cg_view.c index 83d5141..49c9ddd 100755 --- a/code/cgame/cg_view.c +++ b/code/cgame/cg_view.c @@ -1,876 +1,876 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// cg_view.c -- setup all the parameters (position, angle, etc) -// for a 3D rendering -#include "cg_local.h" - - -/* -============================================================================= - - MODEL TESTING - -The viewthing and gun positioning tools from Q2 have been integrated and -enhanced into a single model testing facility. - -Model viewing can begin with either "testmodel " or "testgun ". - -The names must be the full pathname after the basedir, like -"models/weapons/v_launch/tris.md3" or "players/male/tris.md3" - -Testmodel will create a fake entity 100 units in front of the current view -position, directly facing the viewer. It will remain immobile, so you can -move around it to view it from different angles. - -Testgun will cause the model to follow the player around and supress the real -view weapon model. The default frame 0 of most guns is completely off screen, -so you will probably have to cycle a couple frames to see it. - -"nextframe", "prevframe", "nextskin", and "prevskin" commands will change the -frame or skin of the testmodel. These are bound to F5, F6, F7, and F8 in -q3default.cfg. - -If a gun is being tested, the "gun_x", "gun_y", and "gun_z" variables will let -you adjust the positioning. - -Note that none of the model testing features update while the game is paused, so -it may be convenient to test with deathmatch set to 1 so that bringing down the -console doesn't pause the game. - -============================================================================= -*/ - -/* -================= -CG_TestModel_f - -Creates an entity in front of the current position, which -can then be moved around -================= -*/ -void CG_TestModel_f (void) { - vec3_t angles; - - memset( &cg.testModelEntity, 0, sizeof(cg.testModelEntity) ); - if ( trap_Argc() < 2 ) { - return; - } - - Q_strncpyz (cg.testModelName, CG_Argv( 1 ), MAX_QPATH ); - cg.testModelEntity.hModel = trap_R_RegisterModel( cg.testModelName ); - - if ( trap_Argc() == 3 ) { - cg.testModelEntity.backlerp = atof( CG_Argv( 2 ) ); - cg.testModelEntity.frame = 1; - cg.testModelEntity.oldframe = 0; - } - if (! cg.testModelEntity.hModel ) { - CG_Printf( "Can't register model\n" ); - return; - } - - VectorMA( cg.refdef.vieworg, 100, cg.refdef.viewaxis[0], cg.testModelEntity.origin ); - - angles[PITCH] = 0; - angles[YAW] = 180 + cg.refdefViewAngles[1]; - angles[ROLL] = 0; - - AnglesToAxis( angles, cg.testModelEntity.axis ); - cg.testGun = qfalse; -} - -/* -================= -CG_TestGun_f - -Replaces the current view weapon with the given model -================= -*/ -void CG_TestGun_f (void) { - CG_TestModel_f(); - cg.testGun = qtrue; - cg.testModelEntity.renderfx = RF_MINLIGHT | RF_DEPTHHACK | RF_FIRST_PERSON; -} - - -void CG_TestModelNextFrame_f (void) { - cg.testModelEntity.frame++; - CG_Printf( "frame %i\n", cg.testModelEntity.frame ); -} - -void CG_TestModelPrevFrame_f (void) { - cg.testModelEntity.frame--; - if ( cg.testModelEntity.frame < 0 ) { - cg.testModelEntity.frame = 0; - } - CG_Printf( "frame %i\n", cg.testModelEntity.frame ); -} - -void CG_TestModelNextSkin_f (void) { - cg.testModelEntity.skinNum++; - CG_Printf( "skin %i\n", cg.testModelEntity.skinNum ); -} - -void CG_TestModelPrevSkin_f (void) { - cg.testModelEntity.skinNum--; - if ( cg.testModelEntity.skinNum < 0 ) { - cg.testModelEntity.skinNum = 0; - } - CG_Printf( "skin %i\n", cg.testModelEntity.skinNum ); -} - -static void CG_AddTestModel (void) { - int i; - - // re-register the model, because the level may have changed - cg.testModelEntity.hModel = trap_R_RegisterModel( cg.testModelName ); - if (! cg.testModelEntity.hModel ) { - CG_Printf ("Can't register model\n"); - return; - } - - // if testing a gun, set the origin reletive to the view origin - if ( cg.testGun ) { - VectorCopy( cg.refdef.vieworg, cg.testModelEntity.origin ); - VectorCopy( cg.refdef.viewaxis[0], cg.testModelEntity.axis[0] ); - VectorCopy( cg.refdef.viewaxis[1], cg.testModelEntity.axis[1] ); - VectorCopy( cg.refdef.viewaxis[2], cg.testModelEntity.axis[2] ); - - // allow the position to be adjusted - for (i=0 ; i<3 ; i++) { - cg.testModelEntity.origin[i] += cg.refdef.viewaxis[0][i] * cg_gun_x.value; - cg.testModelEntity.origin[i] += cg.refdef.viewaxis[1][i] * cg_gun_y.value; - cg.testModelEntity.origin[i] += cg.refdef.viewaxis[2][i] * cg_gun_z.value; - } - } - - trap_R_AddRefEntityToScene( &cg.testModelEntity ); -} - - - -//============================================================================ - - -/* -================= -CG_CalcVrect - -Sets the coordinates of the rendered window -================= -*/ -static void CG_CalcVrect (void) { - int size; - - // the intermission should allways be full screen - if ( cg.snap->ps.pm_type == PM_INTERMISSION ) { - size = 100; - } else { - // bound normal viewsize - if (cg_viewsize.integer < 30) { - trap_Cvar_Set ("cg_viewsize","30"); - size = 30; - } else if (cg_viewsize.integer > 100) { - trap_Cvar_Set ("cg_viewsize","100"); - size = 100; - } else { - size = cg_viewsize.integer; - } - - } - cg.refdef.width = cgs.glconfig.vidWidth*size/100; - cg.refdef.width &= ~1; - - cg.refdef.height = cgs.glconfig.vidHeight*size/100; - cg.refdef.height &= ~1; - - cg.refdef.x = (cgs.glconfig.vidWidth - cg.refdef.width)/2; - cg.refdef.y = (cgs.glconfig.vidHeight - cg.refdef.height)/2; -} - -//============================================================================== - - -/* -=============== -CG_OffsetThirdPersonView - -=============== -*/ -#define FOCUS_DISTANCE 512 -static void CG_OffsetThirdPersonView( void ) { - vec3_t forward, right, up; - vec3_t view; - vec3_t focusAngles; - trace_t trace; - static vec3_t mins = { -4, -4, -4 }; - static vec3_t maxs = { 4, 4, 4 }; - vec3_t focusPoint; - float focusDist; - float forwardScale, sideScale; - - cg.refdef.vieworg[2] += cg.predictedPlayerState.viewheight; - - VectorCopy( cg.refdefViewAngles, focusAngles ); - - // if dead, look at killer - if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) { - focusAngles[YAW] = cg.predictedPlayerState.stats[STAT_DEAD_YAW]; - cg.refdefViewAngles[YAW] = cg.predictedPlayerState.stats[STAT_DEAD_YAW]; - } - - if ( focusAngles[PITCH] > 45 ) { - focusAngles[PITCH] = 45; // don't go too far overhead - } - AngleVectors( focusAngles, forward, NULL, NULL ); - - VectorMA( cg.refdef.vieworg, FOCUS_DISTANCE, forward, focusPoint ); - - VectorCopy( cg.refdef.vieworg, view ); - - view[2] += 8; - - cg.refdefViewAngles[PITCH] *= 0.5; - - AngleVectors( cg.refdefViewAngles, forward, right, up ); - - forwardScale = cos( cg_thirdPersonAngle.value / 180 * M_PI ); - sideScale = sin( cg_thirdPersonAngle.value / 180 * M_PI ); - VectorMA( view, -cg_thirdPersonRange.value * forwardScale, forward, view ); - VectorMA( view, -cg_thirdPersonRange.value * sideScale, right, view ); - - // trace a ray from the origin to the viewpoint to make sure the view isn't - // in a solid block. Use an 8 by 8 block to prevent the view from near clipping anything - - if (!cg_cameraMode.integer) { - CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, view, cg.predictedPlayerState.clientNum, MASK_SOLID ); - - if ( trace.fraction != 1.0 ) { - VectorCopy( trace.endpos, view ); - view[2] += (1.0 - trace.fraction) * 32; - // try another trace to this position, because a tunnel may have the ceiling - // close enogh that this is poking out - - CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, view, cg.predictedPlayerState.clientNum, MASK_SOLID ); - VectorCopy( trace.endpos, view ); - } - } - - - VectorCopy( view, cg.refdef.vieworg ); - - // select pitch to look at focus point from vieword - VectorSubtract( focusPoint, cg.refdef.vieworg, focusPoint ); - focusDist = sqrt( focusPoint[0] * focusPoint[0] + focusPoint[1] * focusPoint[1] ); - if ( focusDist < 1 ) { - focusDist = 1; // should never happen - } - cg.refdefViewAngles[PITCH] = -180 / M_PI * atan2( focusPoint[2], focusDist ); - cg.refdefViewAngles[YAW] -= cg_thirdPersonAngle.value; -} - - -// this causes a compiler bug on mac MrC compiler -static void CG_StepOffset( void ) { - int timeDelta; - - // smooth out stair climbing - timeDelta = cg.time - cg.stepTime; - if ( timeDelta < STEP_TIME ) { - cg.refdef.vieworg[2] -= cg.stepChange - * (STEP_TIME - timeDelta) / STEP_TIME; - } -} - -/* -=============== -CG_OffsetFirstPersonView - -=============== -*/ -static void CG_OffsetFirstPersonView( void ) { - float *origin; - float *angles; - float bob; - float ratio; - float delta; - float speed; - float f; - vec3_t predictedVelocity; - int timeDelta; - - if ( cg.snap->ps.pm_type == PM_INTERMISSION ) { - return; - } - - origin = cg.refdef.vieworg; - angles = cg.refdefViewAngles; - - // if dead, fix the angle and don't add any kick - if ( cg.snap->ps.stats[STAT_HEALTH] <= 0 ) { - angles[ROLL] = 40; - angles[PITCH] = -15; - angles[YAW] = cg.snap->ps.stats[STAT_DEAD_YAW]; - origin[2] += cg.predictedPlayerState.viewheight; - return; - } - - // add angles based on weapon kick - VectorAdd (angles, cg.kick_angles, angles); - - // add angles based on damage kick - if ( cg.damageTime ) { - ratio = cg.time - cg.damageTime; - if ( ratio < DAMAGE_DEFLECT_TIME ) { - ratio /= DAMAGE_DEFLECT_TIME; - angles[PITCH] += ratio * cg.v_dmg_pitch; - angles[ROLL] += ratio * cg.v_dmg_roll; - } else { - ratio = 1.0 - ( ratio - DAMAGE_DEFLECT_TIME ) / DAMAGE_RETURN_TIME; - if ( ratio > 0 ) { - angles[PITCH] += ratio * cg.v_dmg_pitch; - angles[ROLL] += ratio * cg.v_dmg_roll; - } - } - } - - // add pitch based on fall kick -#if 0 - ratio = ( cg.time - cg.landTime) / FALL_TIME; - if (ratio < 0) - ratio = 0; - angles[PITCH] += ratio * cg.fall_value; -#endif - - // add angles based on velocity - VectorCopy( cg.predictedPlayerState.velocity, predictedVelocity ); - - delta = DotProduct ( predictedVelocity, cg.refdef.viewaxis[0]); - angles[PITCH] += delta * cg_runpitch.value; - - delta = DotProduct ( predictedVelocity, cg.refdef.viewaxis[1]); - angles[ROLL] -= delta * cg_runroll.value; - - // add angles based on bob - - // make sure the bob is visible even at low speeds - speed = cg.xyspeed > 200 ? cg.xyspeed : 200; - - delta = cg.bobfracsin * cg_bobpitch.value * speed; - if (cg.predictedPlayerState.pm_flags & PMF_DUCKED) - delta *= 3; // crouching - angles[PITCH] += delta; - delta = cg.bobfracsin * cg_bobroll.value * speed; - if (cg.predictedPlayerState.pm_flags & PMF_DUCKED) - delta *= 3; // crouching accentuates roll - if (cg.bobcycle & 1) - delta = -delta; - angles[ROLL] += delta; - -//=================================== - - // add view height - origin[2] += cg.predictedPlayerState.viewheight; - - // smooth out duck height changes - timeDelta = cg.time - cg.duckTime; - if ( timeDelta < DUCK_TIME) { - cg.refdef.vieworg[2] -= cg.duckChange - * (DUCK_TIME - timeDelta) / DUCK_TIME; - } - - // add bob height - bob = cg.bobfracsin * cg.xyspeed * cg_bobup.value; - if (bob > 6) { - bob = 6; - } - - origin[2] += bob; - - - // add fall height - delta = cg.time - cg.landTime; - if ( delta < LAND_DEFLECT_TIME ) { - f = delta / LAND_DEFLECT_TIME; - cg.refdef.vieworg[2] += cg.landChange * f; - } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) { - delta -= LAND_DEFLECT_TIME; - f = 1.0 - ( delta / LAND_RETURN_TIME ); - cg.refdef.vieworg[2] += cg.landChange * f; - } - - // add step offset - CG_StepOffset(); - - // add kick offset - - VectorAdd (origin, cg.kick_origin, origin); - - // pivot the eye based on a neck length -#if 0 - { -#define NECK_LENGTH 8 - vec3_t forward, up; - - cg.refdef.vieworg[2] -= NECK_LENGTH; - AngleVectors( cg.refdefViewAngles, forward, NULL, up ); - VectorMA( cg.refdef.vieworg, 3, forward, cg.refdef.vieworg ); - VectorMA( cg.refdef.vieworg, NECK_LENGTH, up, cg.refdef.vieworg ); - } -#endif -} - -//====================================================================== - -void CG_ZoomDown_f( void ) { - if ( cg.zoomed ) { - return; - } - cg.zoomed = qtrue; - cg.zoomTime = cg.time; -} - -void CG_ZoomUp_f( void ) { - if ( !cg.zoomed ) { - return; - } - cg.zoomed = qfalse; - cg.zoomTime = cg.time; -} - - -/* -==================== -CG_CalcFov - -Fixed fov at intermissions, otherwise account for fov variable and zooms. -==================== -*/ -#define WAVE_AMPLITUDE 1 -#define WAVE_FREQUENCY 0.4 - -static int CG_CalcFov( void ) { - float x; - float phase; - float v; - int contents; - float fov_x, fov_y; - float zoomFov; - float f; - int inwater; - - if ( cg.predictedPlayerState.pm_type == PM_INTERMISSION ) { - // if in intermission, use a fixed value - fov_x = 90; - } else { - // user selectable - if ( cgs.dmflags & DF_FIXED_FOV ) { - // dmflag to prevent wide fov for all clients - fov_x = 90; - } else { - fov_x = cg_fov.value; - if ( fov_x < 1 ) { - fov_x = 1; - } else if ( fov_x > 160 ) { - fov_x = 160; - } - } - - // account for zooms - zoomFov = cg_zoomFov.value; - if ( zoomFov < 1 ) { - zoomFov = 1; - } else if ( zoomFov > 160 ) { - zoomFov = 160; - } - - if ( cg.zoomed ) { - f = ( cg.time - cg.zoomTime ) / (float)ZOOM_TIME; - if ( f > 1.0 ) { - fov_x = zoomFov; - } else { - fov_x = fov_x + f * ( zoomFov - fov_x ); - } - } else { - f = ( cg.time - cg.zoomTime ) / (float)ZOOM_TIME; - if ( f > 1.0 ) { - fov_x = fov_x; - } else { - fov_x = zoomFov + f * ( fov_x - zoomFov ); - } - } - } - - x = cg.refdef.width / tan( fov_x / 360 * M_PI ); - fov_y = atan2( cg.refdef.height, x ); - fov_y = fov_y * 360 / M_PI; - - // warp if underwater - contents = CG_PointContents( cg.refdef.vieworg, -1 ); - if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ){ - phase = cg.time / 1000.0 * WAVE_FREQUENCY * M_PI * 2; - v = WAVE_AMPLITUDE * sin( phase ); - fov_x += v; - fov_y -= v; - inwater = qtrue; - } - else { - inwater = qfalse; - } - - - // set it - cg.refdef.fov_x = fov_x; - cg.refdef.fov_y = fov_y; - - if ( !cg.zoomed ) { - cg.zoomSensitivity = 1; - } else { - cg.zoomSensitivity = cg.refdef.fov_y / 75.0; - } - - return inwater; -} - - - -/* -=============== -CG_DamageBlendBlob - -=============== -*/ -static void CG_DamageBlendBlob( void ) { - int t; - int maxTime; - refEntity_t ent; - - if ( !cg.damageValue ) { - return; - } - - //if (cg.cameraMode) { - // return; - //} - - // ragePro systems can't fade blends, so don't obscure the screen - if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) { - return; - } - - maxTime = DAMAGE_TIME; - t = cg.time - cg.damageTime; - if ( t <= 0 || t >= maxTime ) { - return; - } - - - memset( &ent, 0, sizeof( ent ) ); - ent.reType = RT_SPRITE; - ent.renderfx = RF_FIRST_PERSON; - - VectorMA( cg.refdef.vieworg, 8, cg.refdef.viewaxis[0], ent.origin ); - VectorMA( ent.origin, cg.damageX * -8, cg.refdef.viewaxis[1], ent.origin ); - VectorMA( ent.origin, cg.damageY * 8, cg.refdef.viewaxis[2], ent.origin ); - - ent.radius = cg.damageValue * 3; - ent.customShader = cgs.media.viewBloodShader; - ent.shaderRGBA[0] = 255; - ent.shaderRGBA[1] = 255; - ent.shaderRGBA[2] = 255; - ent.shaderRGBA[3] = 200 * ( 1.0 - ((float)t / maxTime) ); - trap_R_AddRefEntityToScene( &ent ); -} - - -/* -=============== -CG_CalcViewValues - -Sets cg.refdef view values -=============== -*/ -static int CG_CalcViewValues( void ) { - playerState_t *ps; - - memset( &cg.refdef, 0, sizeof( cg.refdef ) ); - - // strings for in game rendering - // Q_strncpyz( cg.refdef.text[0], "Park Ranger", sizeof(cg.refdef.text[0]) ); - // Q_strncpyz( cg.refdef.text[1], "19", sizeof(cg.refdef.text[1]) ); - - // calculate size of 3D view - CG_CalcVrect(); - - ps = &cg.predictedPlayerState; -/* - if (cg.cameraMode) { - vec3_t origin, angles; - if (trap_getCameraInfo(cg.time, &origin, &angles)) { - VectorCopy(origin, cg.refdef.vieworg); - angles[ROLL] = 0; - VectorCopy(angles, cg.refdefViewAngles); - AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis ); - return CG_CalcFov(); - } else { - cg.cameraMode = qfalse; - } - } -*/ - // intermission view - if ( ps->pm_type == PM_INTERMISSION ) { - VectorCopy( ps->origin, cg.refdef.vieworg ); - VectorCopy( ps->viewangles, cg.refdefViewAngles ); - AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis ); - return CG_CalcFov(); - } - - cg.bobcycle = ( ps->bobCycle & 128 ) >> 7; - cg.bobfracsin = fabs( sin( ( ps->bobCycle & 127 ) / 127.0 * M_PI ) ); - cg.xyspeed = sqrt( ps->velocity[0] * ps->velocity[0] + - ps->velocity[1] * ps->velocity[1] ); - - - VectorCopy( ps->origin, cg.refdef.vieworg ); - VectorCopy( ps->viewangles, cg.refdefViewAngles ); - - if (cg_cameraOrbit.integer) { - if (cg.time > cg.nextOrbitTime) { - cg.nextOrbitTime = cg.time + cg_cameraOrbitDelay.integer; - cg_thirdPersonAngle.value += cg_cameraOrbit.value; - } - } - // add error decay - if ( cg_errorDecay.value > 0 ) { - int t; - float f; - - t = cg.time - cg.predictedErrorTime; - f = ( cg_errorDecay.value - t ) / cg_errorDecay.value; - if ( f > 0 && f < 1 ) { - VectorMA( cg.refdef.vieworg, f, cg.predictedError, cg.refdef.vieworg ); - } else { - cg.predictedErrorTime = 0; - } - } - - if ( cg.renderingThirdPerson ) { - // back away from character - CG_OffsetThirdPersonView(); - } else { - // offset for local bobbing and kicks - CG_OffsetFirstPersonView(); - } - - // position eye reletive to origin - AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis ); - - if ( cg.hyperspace ) { - cg.refdef.rdflags |= RDF_NOWORLDMODEL | RDF_HYPERSPACE; - } - - // field of view - return CG_CalcFov(); -} - - -/* -===================== -CG_PowerupTimerSounds -===================== -*/ -static void CG_PowerupTimerSounds( void ) { - int i; - int t; - - // powerup timers going away - for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { - t = cg.snap->ps.powerups[i]; - if ( t <= cg.time ) { - continue; - } - if ( t - cg.time >= POWERUP_BLINKS * POWERUP_BLINK_TIME ) { - continue; - } - if ( ( t - cg.time ) / POWERUP_BLINK_TIME != ( t - cg.oldTime ) / POWERUP_BLINK_TIME ) { - trap_S_StartSound( NULL, cg.snap->ps.clientNum, CHAN_ITEM, cgs.media.wearOffSound ); - } - } -} - -/* -===================== -CG_AddBufferedSound -===================== -*/ -void CG_AddBufferedSound( sfxHandle_t sfx ) { - if ( !sfx ) - return; - cg.soundBuffer[cg.soundBufferIn] = sfx; - cg.soundBufferIn = (cg.soundBufferIn + 1) % MAX_SOUNDBUFFER; - if (cg.soundBufferIn == cg.soundBufferOut) { - cg.soundBufferOut++; - } -} - -/* -===================== -CG_PlayBufferedSounds -===================== -*/ -static void CG_PlayBufferedSounds( void ) { - if ( cg.soundTime < cg.time ) { - if (cg.soundBufferOut != cg.soundBufferIn && cg.soundBuffer[cg.soundBufferOut]) { - trap_S_StartLocalSound(cg.soundBuffer[cg.soundBufferOut], CHAN_ANNOUNCER); - cg.soundBuffer[cg.soundBufferOut] = 0; - cg.soundBufferOut = (cg.soundBufferOut + 1) % MAX_SOUNDBUFFER; - cg.soundTime = cg.time + 750; - } - } -} - -//========================================================================= - -/* -================= -CG_DrawActiveFrame - -Generates and draws a game scene and status information at the given time. -================= -*/ -void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ) { - int inwater; - - cg.time = serverTime; - cg.demoPlayback = demoPlayback; - - // update cvars - CG_UpdateCvars(); - - // if we are only updating the screen as a loading - // pacifier, don't even try to read snapshots - if ( cg.infoScreenText[0] != 0 ) { - CG_DrawInformation(); - return; - } - - // any looped sounds will be respecified as entities - // are added to the render list - trap_S_ClearLoopingSounds(qfalse); - - // clear all the render lists - trap_R_ClearScene(); - - // set up cg.snap and possibly cg.nextSnap - CG_ProcessSnapshots(); - - // if we haven't received any snapshots yet, all - // we can draw is the information screen - if ( !cg.snap || ( cg.snap->snapFlags & SNAPFLAG_NOT_ACTIVE ) ) { - CG_DrawInformation(); - return; - } - - // let the client system know what our weapon and zoom settings are - trap_SetUserCmdValue( cg.weaponSelect, cg.zoomSensitivity ); - - // this counter will be bumped for every valid scene we generate - cg.clientFrame++; - - // update cg.predictedPlayerState - CG_PredictPlayerState(); - - // decide on third person view - cg.renderingThirdPerson = cg_thirdPerson.integer || (cg.snap->ps.stats[STAT_HEALTH] <= 0); - - // build cg.refdef - inwater = CG_CalcViewValues(); - - // first person blend blobs, done after AnglesToAxis - if ( !cg.renderingThirdPerson ) { - CG_DamageBlendBlob(); - } - - // build the render lists - if ( !cg.hyperspace ) { - CG_AddPacketEntities(); // adter calcViewValues, so predicted player state is correct - CG_AddMarks(); - CG_AddParticles (); - CG_AddLocalEntities(); - } - CG_AddViewWeapon( &cg.predictedPlayerState ); - - // add buffered sounds - CG_PlayBufferedSounds(); - - // play buffered voice chats - CG_PlayBufferedVoiceChats(); - - // finish up the rest of the refdef - if ( cg.testModelEntity.hModel ) { - CG_AddTestModel(); - } - cg.refdef.time = cg.time; - memcpy( cg.refdef.areamask, cg.snap->areamask, sizeof( cg.refdef.areamask ) ); - - // warning sounds when powerup is wearing off - CG_PowerupTimerSounds(); - - // update audio positions - trap_S_Respatialize( cg.snap->ps.clientNum, cg.refdef.vieworg, cg.refdef.viewaxis, inwater ); - - // make sure the lagometerSample and frame timing isn't done twice when in stereo - if ( stereoView != STEREO_RIGHT ) { - cg.frametime = cg.time - cg.oldTime; - if ( cg.frametime < 0 ) { - cg.frametime = 0; - } - cg.oldTime = cg.time; - CG_AddLagometerFrameInfo(); - } - if (cg_timescale.value != cg_timescaleFadeEnd.value) { - if (cg_timescale.value < cg_timescaleFadeEnd.value) { - cg_timescale.value += cg_timescaleFadeSpeed.value * ((float)cg.frametime) / 1000; - if (cg_timescale.value > cg_timescaleFadeEnd.value) - cg_timescale.value = cg_timescaleFadeEnd.value; - } - else { - cg_timescale.value -= cg_timescaleFadeSpeed.value * ((float)cg.frametime) / 1000; - if (cg_timescale.value < cg_timescaleFadeEnd.value) - cg_timescale.value = cg_timescaleFadeEnd.value; - } - if (cg_timescaleFadeSpeed.value) { - trap_Cvar_Set("timescale", va("%f", cg_timescale.value)); - } - } - - // actually issue the rendering calls - CG_DrawActive( stereoView ); - - if ( cg_stats.integer ) { - CG_Printf( "cg.clientFrame:%i\n", cg.clientFrame ); - } - - -} - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// cg_view.c -- setup all the parameters (position, angle, etc) +// for a 3D rendering +#include "cg_local.h" + + +/* +============================================================================= + + MODEL TESTING + +The viewthing and gun positioning tools from Q2 have been integrated and +enhanced into a single model testing facility. + +Model viewing can begin with either "testmodel " or "testgun ". + +The names must be the full pathname after the basedir, like +"models/weapons/v_launch/tris.md3" or "players/male/tris.md3" + +Testmodel will create a fake entity 100 units in front of the current view +position, directly facing the viewer. It will remain immobile, so you can +move around it to view it from different angles. + +Testgun will cause the model to follow the player around and supress the real +view weapon model. The default frame 0 of most guns is completely off screen, +so you will probably have to cycle a couple frames to see it. + +"nextframe", "prevframe", "nextskin", and "prevskin" commands will change the +frame or skin of the testmodel. These are bound to F5, F6, F7, and F8 in +q3default.cfg. + +If a gun is being tested, the "gun_x", "gun_y", and "gun_z" variables will let +you adjust the positioning. + +Note that none of the model testing features update while the game is paused, so +it may be convenient to test with deathmatch set to 1 so that bringing down the +console doesn't pause the game. + +============================================================================= +*/ + +/* +================= +CG_TestModel_f + +Creates an entity in front of the current position, which +can then be moved around +================= +*/ +void CG_TestModel_f (void) { + vec3_t angles; + + memset( &cg.testModelEntity, 0, sizeof(cg.testModelEntity) ); + if ( trap_Argc() < 2 ) { + return; + } + + Q_strncpyz (cg.testModelName, CG_Argv( 1 ), MAX_QPATH ); + cg.testModelEntity.hModel = trap_R_RegisterModel( cg.testModelName ); + + if ( trap_Argc() == 3 ) { + cg.testModelEntity.backlerp = atof( CG_Argv( 2 ) ); + cg.testModelEntity.frame = 1; + cg.testModelEntity.oldframe = 0; + } + if (! cg.testModelEntity.hModel ) { + CG_Printf( "Can't register model\n" ); + return; + } + + VectorMA( cg.refdef.vieworg, 100, cg.refdef.viewaxis[0], cg.testModelEntity.origin ); + + angles[PITCH] = 0; + angles[YAW] = 180 + cg.refdefViewAngles[1]; + angles[ROLL] = 0; + + AnglesToAxis( angles, cg.testModelEntity.axis ); + cg.testGun = qfalse; +} + +/* +================= +CG_TestGun_f + +Replaces the current view weapon with the given model +================= +*/ +void CG_TestGun_f (void) { + CG_TestModel_f(); + cg.testGun = qtrue; + cg.testModelEntity.renderfx = RF_MINLIGHT | RF_DEPTHHACK | RF_FIRST_PERSON; +} + + +void CG_TestModelNextFrame_f (void) { + cg.testModelEntity.frame++; + CG_Printf( "frame %i\n", cg.testModelEntity.frame ); +} + +void CG_TestModelPrevFrame_f (void) { + cg.testModelEntity.frame--; + if ( cg.testModelEntity.frame < 0 ) { + cg.testModelEntity.frame = 0; + } + CG_Printf( "frame %i\n", cg.testModelEntity.frame ); +} + +void CG_TestModelNextSkin_f (void) { + cg.testModelEntity.skinNum++; + CG_Printf( "skin %i\n", cg.testModelEntity.skinNum ); +} + +void CG_TestModelPrevSkin_f (void) { + cg.testModelEntity.skinNum--; + if ( cg.testModelEntity.skinNum < 0 ) { + cg.testModelEntity.skinNum = 0; + } + CG_Printf( "skin %i\n", cg.testModelEntity.skinNum ); +} + +static void CG_AddTestModel (void) { + int i; + + // re-register the model, because the level may have changed + cg.testModelEntity.hModel = trap_R_RegisterModel( cg.testModelName ); + if (! cg.testModelEntity.hModel ) { + CG_Printf ("Can't register model\n"); + return; + } + + // if testing a gun, set the origin reletive to the view origin + if ( cg.testGun ) { + VectorCopy( cg.refdef.vieworg, cg.testModelEntity.origin ); + VectorCopy( cg.refdef.viewaxis[0], cg.testModelEntity.axis[0] ); + VectorCopy( cg.refdef.viewaxis[1], cg.testModelEntity.axis[1] ); + VectorCopy( cg.refdef.viewaxis[2], cg.testModelEntity.axis[2] ); + + // allow the position to be adjusted + for (i=0 ; i<3 ; i++) { + cg.testModelEntity.origin[i] += cg.refdef.viewaxis[0][i] * cg_gun_x.value; + cg.testModelEntity.origin[i] += cg.refdef.viewaxis[1][i] * cg_gun_y.value; + cg.testModelEntity.origin[i] += cg.refdef.viewaxis[2][i] * cg_gun_z.value; + } + } + + trap_R_AddRefEntityToScene( &cg.testModelEntity ); +} + + + +//============================================================================ + + +/* +================= +CG_CalcVrect + +Sets the coordinates of the rendered window +================= +*/ +static void CG_CalcVrect (void) { + int size; + + // the intermission should allways be full screen + if ( cg.snap->ps.pm_type == PM_INTERMISSION ) { + size = 100; + } else { + // bound normal viewsize + if (cg_viewsize.integer < 30) { + trap_Cvar_Set ("cg_viewsize","30"); + size = 30; + } else if (cg_viewsize.integer > 100) { + trap_Cvar_Set ("cg_viewsize","100"); + size = 100; + } else { + size = cg_viewsize.integer; + } + + } + cg.refdef.width = cgs.glconfig.vidWidth*size/100; + cg.refdef.width &= ~1; + + cg.refdef.height = cgs.glconfig.vidHeight*size/100; + cg.refdef.height &= ~1; + + cg.refdef.x = (cgs.glconfig.vidWidth - cg.refdef.width)/2; + cg.refdef.y = (cgs.glconfig.vidHeight - cg.refdef.height)/2; +} + +//============================================================================== + + +/* +=============== +CG_OffsetThirdPersonView + +=============== +*/ +#define FOCUS_DISTANCE 512 +static void CG_OffsetThirdPersonView( void ) { + vec3_t forward, right, up; + vec3_t view; + vec3_t focusAngles; + trace_t trace; + static vec3_t mins = { -4, -4, -4 }; + static vec3_t maxs = { 4, 4, 4 }; + vec3_t focusPoint; + float focusDist; + float forwardScale, sideScale; + + cg.refdef.vieworg[2] += cg.predictedPlayerState.viewheight; + + VectorCopy( cg.refdefViewAngles, focusAngles ); + + // if dead, look at killer + if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) { + focusAngles[YAW] = cg.predictedPlayerState.stats[STAT_DEAD_YAW]; + cg.refdefViewAngles[YAW] = cg.predictedPlayerState.stats[STAT_DEAD_YAW]; + } + + if ( focusAngles[PITCH] > 45 ) { + focusAngles[PITCH] = 45; // don't go too far overhead + } + AngleVectors( focusAngles, forward, NULL, NULL ); + + VectorMA( cg.refdef.vieworg, FOCUS_DISTANCE, forward, focusPoint ); + + VectorCopy( cg.refdef.vieworg, view ); + + view[2] += 8; + + cg.refdefViewAngles[PITCH] *= 0.5; + + AngleVectors( cg.refdefViewAngles, forward, right, up ); + + forwardScale = cos( cg_thirdPersonAngle.value / 180 * M_PI ); + sideScale = sin( cg_thirdPersonAngle.value / 180 * M_PI ); + VectorMA( view, -cg_thirdPersonRange.value * forwardScale, forward, view ); + VectorMA( view, -cg_thirdPersonRange.value * sideScale, right, view ); + + // trace a ray from the origin to the viewpoint to make sure the view isn't + // in a solid block. Use an 8 by 8 block to prevent the view from near clipping anything + + if (!cg_cameraMode.integer) { + CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, view, cg.predictedPlayerState.clientNum, MASK_SOLID ); + + if ( trace.fraction != 1.0 ) { + VectorCopy( trace.endpos, view ); + view[2] += (1.0 - trace.fraction) * 32; + // try another trace to this position, because a tunnel may have the ceiling + // close enogh that this is poking out + + CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, view, cg.predictedPlayerState.clientNum, MASK_SOLID ); + VectorCopy( trace.endpos, view ); + } + } + + + VectorCopy( view, cg.refdef.vieworg ); + + // select pitch to look at focus point from vieword + VectorSubtract( focusPoint, cg.refdef.vieworg, focusPoint ); + focusDist = sqrt( focusPoint[0] * focusPoint[0] + focusPoint[1] * focusPoint[1] ); + if ( focusDist < 1 ) { + focusDist = 1; // should never happen + } + cg.refdefViewAngles[PITCH] = -180 / M_PI * atan2( focusPoint[2], focusDist ); + cg.refdefViewAngles[YAW] -= cg_thirdPersonAngle.value; +} + + +// this causes a compiler bug on mac MrC compiler +static void CG_StepOffset( void ) { + int timeDelta; + + // smooth out stair climbing + timeDelta = cg.time - cg.stepTime; + if ( timeDelta < STEP_TIME ) { + cg.refdef.vieworg[2] -= cg.stepChange + * (STEP_TIME - timeDelta) / STEP_TIME; + } +} + +/* +=============== +CG_OffsetFirstPersonView + +=============== +*/ +static void CG_OffsetFirstPersonView( void ) { + float *origin; + float *angles; + float bob; + float ratio; + float delta; + float speed; + float f; + vec3_t predictedVelocity; + int timeDelta; + + if ( cg.snap->ps.pm_type == PM_INTERMISSION ) { + return; + } + + origin = cg.refdef.vieworg; + angles = cg.refdefViewAngles; + + // if dead, fix the angle and don't add any kick + if ( cg.snap->ps.stats[STAT_HEALTH] <= 0 ) { + angles[ROLL] = 40; + angles[PITCH] = -15; + angles[YAW] = cg.snap->ps.stats[STAT_DEAD_YAW]; + origin[2] += cg.predictedPlayerState.viewheight; + return; + } + + // add angles based on weapon kick + VectorAdd (angles, cg.kick_angles, angles); + + // add angles based on damage kick + if ( cg.damageTime ) { + ratio = cg.time - cg.damageTime; + if ( ratio < DAMAGE_DEFLECT_TIME ) { + ratio /= DAMAGE_DEFLECT_TIME; + angles[PITCH] += ratio * cg.v_dmg_pitch; + angles[ROLL] += ratio * cg.v_dmg_roll; + } else { + ratio = 1.0 - ( ratio - DAMAGE_DEFLECT_TIME ) / DAMAGE_RETURN_TIME; + if ( ratio > 0 ) { + angles[PITCH] += ratio * cg.v_dmg_pitch; + angles[ROLL] += ratio * cg.v_dmg_roll; + } + } + } + + // add pitch based on fall kick +#if 0 + ratio = ( cg.time - cg.landTime) / FALL_TIME; + if (ratio < 0) + ratio = 0; + angles[PITCH] += ratio * cg.fall_value; +#endif + + // add angles based on velocity + VectorCopy( cg.predictedPlayerState.velocity, predictedVelocity ); + + delta = DotProduct ( predictedVelocity, cg.refdef.viewaxis[0]); + angles[PITCH] += delta * cg_runpitch.value; + + delta = DotProduct ( predictedVelocity, cg.refdef.viewaxis[1]); + angles[ROLL] -= delta * cg_runroll.value; + + // add angles based on bob + + // make sure the bob is visible even at low speeds + speed = cg.xyspeed > 200 ? cg.xyspeed : 200; + + delta = cg.bobfracsin * cg_bobpitch.value * speed; + if (cg.predictedPlayerState.pm_flags & PMF_DUCKED) + delta *= 3; // crouching + angles[PITCH] += delta; + delta = cg.bobfracsin * cg_bobroll.value * speed; + if (cg.predictedPlayerState.pm_flags & PMF_DUCKED) + delta *= 3; // crouching accentuates roll + if (cg.bobcycle & 1) + delta = -delta; + angles[ROLL] += delta; + +//=================================== + + // add view height + origin[2] += cg.predictedPlayerState.viewheight; + + // smooth out duck height changes + timeDelta = cg.time - cg.duckTime; + if ( timeDelta < DUCK_TIME) { + cg.refdef.vieworg[2] -= cg.duckChange + * (DUCK_TIME - timeDelta) / DUCK_TIME; + } + + // add bob height + bob = cg.bobfracsin * cg.xyspeed * cg_bobup.value; + if (bob > 6) { + bob = 6; + } + + origin[2] += bob; + + + // add fall height + delta = cg.time - cg.landTime; + if ( delta < LAND_DEFLECT_TIME ) { + f = delta / LAND_DEFLECT_TIME; + cg.refdef.vieworg[2] += cg.landChange * f; + } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) { + delta -= LAND_DEFLECT_TIME; + f = 1.0 - ( delta / LAND_RETURN_TIME ); + cg.refdef.vieworg[2] += cg.landChange * f; + } + + // add step offset + CG_StepOffset(); + + // add kick offset + + VectorAdd (origin, cg.kick_origin, origin); + + // pivot the eye based on a neck length +#if 0 + { +#define NECK_LENGTH 8 + vec3_t forward, up; + + cg.refdef.vieworg[2] -= NECK_LENGTH; + AngleVectors( cg.refdefViewAngles, forward, NULL, up ); + VectorMA( cg.refdef.vieworg, 3, forward, cg.refdef.vieworg ); + VectorMA( cg.refdef.vieworg, NECK_LENGTH, up, cg.refdef.vieworg ); + } +#endif +} + +//====================================================================== + +void CG_ZoomDown_f( void ) { + if ( cg.zoomed ) { + return; + } + cg.zoomed = qtrue; + cg.zoomTime = cg.time; +} + +void CG_ZoomUp_f( void ) { + if ( !cg.zoomed ) { + return; + } + cg.zoomed = qfalse; + cg.zoomTime = cg.time; +} + + +/* +==================== +CG_CalcFov + +Fixed fov at intermissions, otherwise account for fov variable and zooms. +==================== +*/ +#define WAVE_AMPLITUDE 1 +#define WAVE_FREQUENCY 0.4 + +static int CG_CalcFov( void ) { + float x; + float phase; + float v; + int contents; + float fov_x, fov_y; + float zoomFov; + float f; + int inwater; + + if ( cg.predictedPlayerState.pm_type == PM_INTERMISSION ) { + // if in intermission, use a fixed value + fov_x = 90; + } else { + // user selectable + if ( cgs.dmflags & DF_FIXED_FOV ) { + // dmflag to prevent wide fov for all clients + fov_x = 90; + } else { + fov_x = cg_fov.value; + if ( fov_x < 1 ) { + fov_x = 1; + } else if ( fov_x > 160 ) { + fov_x = 160; + } + } + + // account for zooms + zoomFov = cg_zoomFov.value; + if ( zoomFov < 1 ) { + zoomFov = 1; + } else if ( zoomFov > 160 ) { + zoomFov = 160; + } + + if ( cg.zoomed ) { + f = ( cg.time - cg.zoomTime ) / (float)ZOOM_TIME; + if ( f > 1.0 ) { + fov_x = zoomFov; + } else { + fov_x = fov_x + f * ( zoomFov - fov_x ); + } + } else { + f = ( cg.time - cg.zoomTime ) / (float)ZOOM_TIME; + if ( f > 1.0 ) { + fov_x = fov_x; + } else { + fov_x = zoomFov + f * ( fov_x - zoomFov ); + } + } + } + + x = cg.refdef.width / tan( fov_x / 360 * M_PI ); + fov_y = atan2( cg.refdef.height, x ); + fov_y = fov_y * 360 / M_PI; + + // warp if underwater + contents = CG_PointContents( cg.refdef.vieworg, -1 ); + if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ){ + phase = cg.time / 1000.0 * WAVE_FREQUENCY * M_PI * 2; + v = WAVE_AMPLITUDE * sin( phase ); + fov_x += v; + fov_y -= v; + inwater = qtrue; + } + else { + inwater = qfalse; + } + + + // set it + cg.refdef.fov_x = fov_x; + cg.refdef.fov_y = fov_y; + + if ( !cg.zoomed ) { + cg.zoomSensitivity = 1; + } else { + cg.zoomSensitivity = cg.refdef.fov_y / 75.0; + } + + return inwater; +} + + + +/* +=============== +CG_DamageBlendBlob + +=============== +*/ +static void CG_DamageBlendBlob( void ) { + int t; + int maxTime; + refEntity_t ent; + + if ( !cg.damageValue ) { + return; + } + + //if (cg.cameraMode) { + // return; + //} + + // ragePro systems can't fade blends, so don't obscure the screen + if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) { + return; + } + + maxTime = DAMAGE_TIME; + t = cg.time - cg.damageTime; + if ( t <= 0 || t >= maxTime ) { + return; + } + + + memset( &ent, 0, sizeof( ent ) ); + ent.reType = RT_SPRITE; + ent.renderfx = RF_FIRST_PERSON; + + VectorMA( cg.refdef.vieworg, 8, cg.refdef.viewaxis[0], ent.origin ); + VectorMA( ent.origin, cg.damageX * -8, cg.refdef.viewaxis[1], ent.origin ); + VectorMA( ent.origin, cg.damageY * 8, cg.refdef.viewaxis[2], ent.origin ); + + ent.radius = cg.damageValue * 3; + ent.customShader = cgs.media.viewBloodShader; + ent.shaderRGBA[0] = 255; + ent.shaderRGBA[1] = 255; + ent.shaderRGBA[2] = 255; + ent.shaderRGBA[3] = 200 * ( 1.0 - ((float)t / maxTime) ); + trap_R_AddRefEntityToScene( &ent ); +} + + +/* +=============== +CG_CalcViewValues + +Sets cg.refdef view values +=============== +*/ +static int CG_CalcViewValues( void ) { + playerState_t *ps; + + memset( &cg.refdef, 0, sizeof( cg.refdef ) ); + + // strings for in game rendering + // Q_strncpyz( cg.refdef.text[0], "Park Ranger", sizeof(cg.refdef.text[0]) ); + // Q_strncpyz( cg.refdef.text[1], "19", sizeof(cg.refdef.text[1]) ); + + // calculate size of 3D view + CG_CalcVrect(); + + ps = &cg.predictedPlayerState; +/* + if (cg.cameraMode) { + vec3_t origin, angles; + if (trap_getCameraInfo(cg.time, &origin, &angles)) { + VectorCopy(origin, cg.refdef.vieworg); + angles[ROLL] = 0; + VectorCopy(angles, cg.refdefViewAngles); + AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis ); + return CG_CalcFov(); + } else { + cg.cameraMode = qfalse; + } + } +*/ + // intermission view + if ( ps->pm_type == PM_INTERMISSION ) { + VectorCopy( ps->origin, cg.refdef.vieworg ); + VectorCopy( ps->viewangles, cg.refdefViewAngles ); + AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis ); + return CG_CalcFov(); + } + + cg.bobcycle = ( ps->bobCycle & 128 ) >> 7; + cg.bobfracsin = fabs( sin( ( ps->bobCycle & 127 ) / 127.0 * M_PI ) ); + cg.xyspeed = sqrt( ps->velocity[0] * ps->velocity[0] + + ps->velocity[1] * ps->velocity[1] ); + + + VectorCopy( ps->origin, cg.refdef.vieworg ); + VectorCopy( ps->viewangles, cg.refdefViewAngles ); + + if (cg_cameraOrbit.integer) { + if (cg.time > cg.nextOrbitTime) { + cg.nextOrbitTime = cg.time + cg_cameraOrbitDelay.integer; + cg_thirdPersonAngle.value += cg_cameraOrbit.value; + } + } + // add error decay + if ( cg_errorDecay.value > 0 ) { + int t; + float f; + + t = cg.time - cg.predictedErrorTime; + f = ( cg_errorDecay.value - t ) / cg_errorDecay.value; + if ( f > 0 && f < 1 ) { + VectorMA( cg.refdef.vieworg, f, cg.predictedError, cg.refdef.vieworg ); + } else { + cg.predictedErrorTime = 0; + } + } + + if ( cg.renderingThirdPerson ) { + // back away from character + CG_OffsetThirdPersonView(); + } else { + // offset for local bobbing and kicks + CG_OffsetFirstPersonView(); + } + + // position eye reletive to origin + AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis ); + + if ( cg.hyperspace ) { + cg.refdef.rdflags |= RDF_NOWORLDMODEL | RDF_HYPERSPACE; + } + + // field of view + return CG_CalcFov(); +} + + +/* +===================== +CG_PowerupTimerSounds +===================== +*/ +static void CG_PowerupTimerSounds( void ) { + int i; + int t; + + // powerup timers going away + for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { + t = cg.snap->ps.powerups[i]; + if ( t <= cg.time ) { + continue; + } + if ( t - cg.time >= POWERUP_BLINKS * POWERUP_BLINK_TIME ) { + continue; + } + if ( ( t - cg.time ) / POWERUP_BLINK_TIME != ( t - cg.oldTime ) / POWERUP_BLINK_TIME ) { + trap_S_StartSound( NULL, cg.snap->ps.clientNum, CHAN_ITEM, cgs.media.wearOffSound ); + } + } +} + +/* +===================== +CG_AddBufferedSound +===================== +*/ +void CG_AddBufferedSound( sfxHandle_t sfx ) { + if ( !sfx ) + return; + cg.soundBuffer[cg.soundBufferIn] = sfx; + cg.soundBufferIn = (cg.soundBufferIn + 1) % MAX_SOUNDBUFFER; + if (cg.soundBufferIn == cg.soundBufferOut) { + cg.soundBufferOut++; + } +} + +/* +===================== +CG_PlayBufferedSounds +===================== +*/ +static void CG_PlayBufferedSounds( void ) { + if ( cg.soundTime < cg.time ) { + if (cg.soundBufferOut != cg.soundBufferIn && cg.soundBuffer[cg.soundBufferOut]) { + trap_S_StartLocalSound(cg.soundBuffer[cg.soundBufferOut], CHAN_ANNOUNCER); + cg.soundBuffer[cg.soundBufferOut] = 0; + cg.soundBufferOut = (cg.soundBufferOut + 1) % MAX_SOUNDBUFFER; + cg.soundTime = cg.time + 750; + } + } +} + +//========================================================================= + +/* +================= +CG_DrawActiveFrame + +Generates and draws a game scene and status information at the given time. +================= +*/ +void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ) { + int inwater; + + cg.time = serverTime; + cg.demoPlayback = demoPlayback; + + // update cvars + CG_UpdateCvars(); + + // if we are only updating the screen as a loading + // pacifier, don't even try to read snapshots + if ( cg.infoScreenText[0] != 0 ) { + CG_DrawInformation(); + return; + } + + // any looped sounds will be respecified as entities + // are added to the render list + trap_S_ClearLoopingSounds(qfalse); + + // clear all the render lists + trap_R_ClearScene(); + + // set up cg.snap and possibly cg.nextSnap + CG_ProcessSnapshots(); + + // if we haven't received any snapshots yet, all + // we can draw is the information screen + if ( !cg.snap || ( cg.snap->snapFlags & SNAPFLAG_NOT_ACTIVE ) ) { + CG_DrawInformation(); + return; + } + + // let the client system know what our weapon and zoom settings are + trap_SetUserCmdValue( cg.weaponSelect, cg.zoomSensitivity ); + + // this counter will be bumped for every valid scene we generate + cg.clientFrame++; + + // update cg.predictedPlayerState + CG_PredictPlayerState(); + + // decide on third person view + cg.renderingThirdPerson = cg_thirdPerson.integer || (cg.snap->ps.stats[STAT_HEALTH] <= 0); + + // build cg.refdef + inwater = CG_CalcViewValues(); + + // first person blend blobs, done after AnglesToAxis + if ( !cg.renderingThirdPerson ) { + CG_DamageBlendBlob(); + } + + // build the render lists + if ( !cg.hyperspace ) { + CG_AddPacketEntities(); // adter calcViewValues, so predicted player state is correct + CG_AddMarks(); + CG_AddParticles (); + CG_AddLocalEntities(); + } + CG_AddViewWeapon( &cg.predictedPlayerState ); + + // add buffered sounds + CG_PlayBufferedSounds(); + + // play buffered voice chats + CG_PlayBufferedVoiceChats(); + + // finish up the rest of the refdef + if ( cg.testModelEntity.hModel ) { + CG_AddTestModel(); + } + cg.refdef.time = cg.time; + memcpy( cg.refdef.areamask, cg.snap->areamask, sizeof( cg.refdef.areamask ) ); + + // warning sounds when powerup is wearing off + CG_PowerupTimerSounds(); + + // update audio positions + trap_S_Respatialize( cg.snap->ps.clientNum, cg.refdef.vieworg, cg.refdef.viewaxis, inwater ); + + // make sure the lagometerSample and frame timing isn't done twice when in stereo + if ( stereoView != STEREO_RIGHT ) { + cg.frametime = cg.time - cg.oldTime; + if ( cg.frametime < 0 ) { + cg.frametime = 0; + } + cg.oldTime = cg.time; + CG_AddLagometerFrameInfo(); + } + if (cg_timescale.value != cg_timescaleFadeEnd.value) { + if (cg_timescale.value < cg_timescaleFadeEnd.value) { + cg_timescale.value += cg_timescaleFadeSpeed.value * ((float)cg.frametime) / 1000; + if (cg_timescale.value > cg_timescaleFadeEnd.value) + cg_timescale.value = cg_timescaleFadeEnd.value; + } + else { + cg_timescale.value -= cg_timescaleFadeSpeed.value * ((float)cg.frametime) / 1000; + if (cg_timescale.value < cg_timescaleFadeEnd.value) + cg_timescale.value = cg_timescaleFadeEnd.value; + } + if (cg_timescaleFadeSpeed.value) { + trap_Cvar_Set("timescale", va("%f", cg_timescale.value)); + } + } + + // actually issue the rendering calls + CG_DrawActive( stereoView ); + + if ( cg_stats.integer ) { + CG_Printf( "cg.clientFrame:%i\n", cg.clientFrame ); + } + + +} + diff --git a/code/cgame/cg_weapons.c b/code/cgame/cg_weapons.c index 9ac1ca9..c4a1753 100755 --- a/code/cgame/cg_weapons.c +++ b/code/cgame/cg_weapons.c @@ -1,2277 +1,2277 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// cg_weapons.c -- events and effects dealing with weapons -#include "cg_local.h" - -/* -========================== -CG_MachineGunEjectBrass -========================== -*/ -static void CG_MachineGunEjectBrass( centity_t *cent ) { - localEntity_t *le; - refEntity_t *re; - vec3_t velocity, xvelocity; - vec3_t offset, xoffset; - float waterScale = 1.0f; - vec3_t v[3]; - - if ( cg_brassTime.integer <= 0 ) { - return; - } - - le = CG_AllocLocalEntity(); - re = &le->refEntity; - - velocity[0] = 0; - velocity[1] = -50 + 40 * crandom(); - velocity[2] = 100 + 50 * crandom(); - - le->leType = LE_FRAGMENT; - le->startTime = cg.time; - le->endTime = le->startTime + cg_brassTime.integer + ( cg_brassTime.integer / 4 ) * random(); - - le->pos.trType = TR_GRAVITY; - le->pos.trTime = cg.time - (rand()&15); - - AnglesToAxis( cent->lerpAngles, v ); - - offset[0] = 8; - offset[1] = -4; - offset[2] = 24; - - xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0]; - xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1]; - xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2]; - VectorAdd( cent->lerpOrigin, xoffset, re->origin ); - - VectorCopy( re->origin, le->pos.trBase ); - - if ( CG_PointContents( re->origin, -1 ) & CONTENTS_WATER ) { - waterScale = 0.10f; - } - - xvelocity[0] = velocity[0] * v[0][0] + velocity[1] * v[1][0] + velocity[2] * v[2][0]; - xvelocity[1] = velocity[0] * v[0][1] + velocity[1] * v[1][1] + velocity[2] * v[2][1]; - xvelocity[2] = velocity[0] * v[0][2] + velocity[1] * v[1][2] + velocity[2] * v[2][2]; - VectorScale( xvelocity, waterScale, le->pos.trDelta ); - - AxisCopy( axisDefault, re->axis ); - re->hModel = cgs.media.machinegunBrassModel; - - le->bounceFactor = 0.4 * waterScale; - - le->angles.trType = TR_LINEAR; - le->angles.trTime = cg.time; - le->angles.trBase[0] = rand()&31; - le->angles.trBase[1] = rand()&31; - le->angles.trBase[2] = rand()&31; - le->angles.trDelta[0] = 2; - le->angles.trDelta[1] = 1; - le->angles.trDelta[2] = 0; - - le->leFlags = LEF_TUMBLE; - le->leBounceSoundType = LEBS_BRASS; - le->leMarkType = LEMT_NONE; -} - -/* -========================== -CG_ShotgunEjectBrass -========================== -*/ -static void CG_ShotgunEjectBrass( centity_t *cent ) { - localEntity_t *le; - refEntity_t *re; - vec3_t velocity, xvelocity; - vec3_t offset, xoffset; - vec3_t v[3]; - int i; - - if ( cg_brassTime.integer <= 0 ) { - return; - } - - for ( i = 0; i < 2; i++ ) { - float waterScale = 1.0f; - - le = CG_AllocLocalEntity(); - re = &le->refEntity; - - velocity[0] = 60 + 60 * crandom(); - if ( i == 0 ) { - velocity[1] = 40 + 10 * crandom(); - } else { - velocity[1] = -40 + 10 * crandom(); - } - velocity[2] = 100 + 50 * crandom(); - - le->leType = LE_FRAGMENT; - le->startTime = cg.time; - le->endTime = le->startTime + cg_brassTime.integer*3 + cg_brassTime.integer * random(); - - le->pos.trType = TR_GRAVITY; - le->pos.trTime = cg.time; - - AnglesToAxis( cent->lerpAngles, v ); - - offset[0] = 8; - offset[1] = 0; - offset[2] = 24; - - xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0]; - xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1]; - xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2]; - VectorAdd( cent->lerpOrigin, xoffset, re->origin ); - VectorCopy( re->origin, le->pos.trBase ); - if ( CG_PointContents( re->origin, -1 ) & CONTENTS_WATER ) { - waterScale = 0.10f; - } - - xvelocity[0] = velocity[0] * v[0][0] + velocity[1] * v[1][0] + velocity[2] * v[2][0]; - xvelocity[1] = velocity[0] * v[0][1] + velocity[1] * v[1][1] + velocity[2] * v[2][1]; - xvelocity[2] = velocity[0] * v[0][2] + velocity[1] * v[1][2] + velocity[2] * v[2][2]; - VectorScale( xvelocity, waterScale, le->pos.trDelta ); - - AxisCopy( axisDefault, re->axis ); - re->hModel = cgs.media.shotgunBrassModel; - le->bounceFactor = 0.3f; - - le->angles.trType = TR_LINEAR; - le->angles.trTime = cg.time; - le->angles.trBase[0] = rand()&31; - le->angles.trBase[1] = rand()&31; - le->angles.trBase[2] = rand()&31; - le->angles.trDelta[0] = 1; - le->angles.trDelta[1] = 0.5; - le->angles.trDelta[2] = 0; - - le->leFlags = LEF_TUMBLE; - le->leBounceSoundType = LEBS_BRASS; - le->leMarkType = LEMT_NONE; - } -} - - -#ifdef MISSIONPACK -/* -========================== -CG_NailgunEjectBrass -========================== -*/ -static void CG_NailgunEjectBrass( centity_t *cent ) { - localEntity_t *smoke; - vec3_t origin; - vec3_t v[3]; - vec3_t offset; - vec3_t xoffset; - vec3_t up; - - AnglesToAxis( cent->lerpAngles, v ); - - offset[0] = 0; - offset[1] = -12; - offset[2] = 24; - - xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0]; - xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1]; - xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2]; - VectorAdd( cent->lerpOrigin, xoffset, origin ); - - VectorSet( up, 0, 0, 64 ); - - smoke = CG_SmokePuff( origin, up, 32, 1, 1, 1, 0.33f, 700, cg.time, 0, 0, cgs.media.smokePuffShader ); - // use the optimized local entity add - smoke->leType = LE_SCALE_FADE; -} -#endif - - -/* -========================== -CG_RailTrail -========================== -*/ -void CG_RailTrail (clientInfo_t *ci, vec3_t start, vec3_t end) { - vec3_t axis[36], move, move2, next_move, vec, temp; - float len; - int i, j, skip; - - localEntity_t *le; - refEntity_t *re; - -#define RADIUS 4 -#define ROTATION 1 -#define SPACING 5 - - start[2] -= 4; - VectorCopy (start, move); - VectorSubtract (end, start, vec); - len = VectorNormalize (vec); - PerpendicularVector(temp, vec); - for (i = 0 ; i < 36; i++) { - RotatePointAroundVector(axis[i], vec, temp, i * 10);//banshee 2.4 was 10 - } - - le = CG_AllocLocalEntity(); - re = &le->refEntity; - - le->leType = LE_FADE_RGB; - le->startTime = cg.time; - le->endTime = cg.time + cg_railTrailTime.value; - le->lifeRate = 1.0 / (le->endTime - le->startTime); - - re->shaderTime = cg.time / 1000.0f; - re->reType = RT_RAIL_CORE; - re->customShader = cgs.media.railCoreShader; - - VectorCopy(start, re->origin); - VectorCopy(end, re->oldorigin); - - re->shaderRGBA[0] = ci->color1[0] * 255; - re->shaderRGBA[1] = ci->color1[1] * 255; - re->shaderRGBA[2] = ci->color1[2] * 255; - re->shaderRGBA[3] = 255; - - le->color[0] = ci->color1[0] * 0.75; - le->color[1] = ci->color1[1] * 0.75; - le->color[2] = ci->color1[2] * 0.75; - le->color[3] = 1.0f; - - AxisClear( re->axis ); - - VectorMA(move, 20, vec, move); - VectorCopy(move, next_move); - VectorScale (vec, SPACING, vec); - - if (cg_oldRail.integer != 0) { - // nudge down a bit so it isn't exactly in center - re->origin[2] -= 8; - re->oldorigin[2] -= 8; - return; - } - skip = -1; - - j = 18; - for (i = 0; i < len; i += SPACING) { - if (i != skip) { - skip = i + SPACING; - le = CG_AllocLocalEntity(); - re = &le->refEntity; - le->leFlags = LEF_PUFF_DONT_SCALE; - le->leType = LE_MOVE_SCALE_FADE; - le->startTime = cg.time; - le->endTime = cg.time + (i>>1) + 600; - le->lifeRate = 1.0 / (le->endTime - le->startTime); - - re->shaderTime = cg.time / 1000.0f; - re->reType = RT_SPRITE; - re->radius = 1.1f; - re->customShader = cgs.media.railRingsShader; - - re->shaderRGBA[0] = ci->color2[0] * 255; - re->shaderRGBA[1] = ci->color2[1] * 255; - re->shaderRGBA[2] = ci->color2[2] * 255; - re->shaderRGBA[3] = 255; - - le->color[0] = ci->color2[0] * 0.75; - le->color[1] = ci->color2[1] * 0.75; - le->color[2] = ci->color2[2] * 0.75; - le->color[3] = 1.0f; - - le->pos.trType = TR_LINEAR; - le->pos.trTime = cg.time; - - VectorCopy( move, move2); - VectorMA(move2, RADIUS , axis[j], move2); - VectorCopy(move2, le->pos.trBase); - - le->pos.trDelta[0] = axis[j][0]*6; - le->pos.trDelta[1] = axis[j][1]*6; - le->pos.trDelta[2] = axis[j][2]*6; - } - - VectorAdd (move, vec, move); - - j = j + ROTATION < 36 ? j + ROTATION : (j + ROTATION) % 36; - } -} - -/* -========================== -CG_RocketTrail -========================== -*/ -static void CG_RocketTrail( centity_t *ent, const weaponInfo_t *wi ) { - int step; - vec3_t origin, lastPos; - int t; - int startTime, contents; - int lastContents; - entityState_t *es; - vec3_t up; - localEntity_t *smoke; - - if ( cg_noProjectileTrail.integer ) { - return; - } - - up[0] = 0; - up[1] = 0; - up[2] = 0; - - step = 50; - - es = &ent->currentState; - startTime = ent->trailTime; - t = step * ( (startTime + step) / step ); - - BG_EvaluateTrajectory( &es->pos, cg.time, origin ); - contents = CG_PointContents( origin, -1 ); - - // if object (e.g. grenade) is stationary, don't toss up smoke - if ( es->pos.trType == TR_STATIONARY ) { - ent->trailTime = cg.time; - return; - } - - BG_EvaluateTrajectory( &es->pos, ent->trailTime, lastPos ); - lastContents = CG_PointContents( lastPos, -1 ); - - ent->trailTime = cg.time; - - if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { - if ( contents & lastContents & CONTENTS_WATER ) { - CG_BubbleTrail( lastPos, origin, 8 ); - } - return; - } - - for ( ; t <= ent->trailTime ; t += step ) { - BG_EvaluateTrajectory( &es->pos, t, lastPos ); - - smoke = CG_SmokePuff( lastPos, up, - wi->trailRadius, - 1, 1, 1, 0.33f, - wi->wiTrailTime, - t, - 0, - 0, - cgs.media.smokePuffShader ); - // use the optimized local entity add - smoke->leType = LE_SCALE_FADE; - } - -} - -#ifdef MISSIONPACK -/* -========================== -CG_NailTrail -========================== -*/ -static void CG_NailTrail( centity_t *ent, const weaponInfo_t *wi ) { - int step; - vec3_t origin, lastPos; - int t; - int startTime, contents; - int lastContents; - entityState_t *es; - vec3_t up; - localEntity_t *smoke; - - if ( cg_noProjectileTrail.integer ) { - return; - } - - up[0] = 0; - up[1] = 0; - up[2] = 0; - - step = 50; - - es = &ent->currentState; - startTime = ent->trailTime; - t = step * ( (startTime + step) / step ); - - BG_EvaluateTrajectory( &es->pos, cg.time, origin ); - contents = CG_PointContents( origin, -1 ); - - // if object (e.g. grenade) is stationary, don't toss up smoke - if ( es->pos.trType == TR_STATIONARY ) { - ent->trailTime = cg.time; - return; - } - - BG_EvaluateTrajectory( &es->pos, ent->trailTime, lastPos ); - lastContents = CG_PointContents( lastPos, -1 ); - - ent->trailTime = cg.time; - - if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { - if ( contents & lastContents & CONTENTS_WATER ) { - CG_BubbleTrail( lastPos, origin, 8 ); - } - return; - } - - for ( ; t <= ent->trailTime ; t += step ) { - BG_EvaluateTrajectory( &es->pos, t, lastPos ); - - smoke = CG_SmokePuff( lastPos, up, - wi->trailRadius, - 1, 1, 1, 0.33f, - wi->wiTrailTime, - t, - 0, - 0, - cgs.media.nailPuffShader ); - // use the optimized local entity add - smoke->leType = LE_SCALE_FADE; - } - -} -#endif - -/* -========================== -CG_NailTrail -========================== -*/ -static void CG_PlasmaTrail( centity_t *cent, const weaponInfo_t *wi ) { - localEntity_t *le; - refEntity_t *re; - entityState_t *es; - vec3_t velocity, xvelocity, origin; - vec3_t offset, xoffset; - vec3_t v[3]; - int t, startTime, step; - - float waterScale = 1.0f; - - if ( cg_noProjectileTrail.integer || cg_oldPlasma.integer ) { - return; - } - - step = 50; - - es = ¢->currentState; - startTime = cent->trailTime; - t = step * ( (startTime + step) / step ); - - BG_EvaluateTrajectory( &es->pos, cg.time, origin ); - - le = CG_AllocLocalEntity(); - re = &le->refEntity; - - velocity[0] = 60 - 120 * crandom(); - velocity[1] = 40 - 80 * crandom(); - velocity[2] = 100 - 200 * crandom(); - - le->leType = LE_MOVE_SCALE_FADE; - le->leFlags = LEF_TUMBLE; - le->leBounceSoundType = LEBS_NONE; - le->leMarkType = LEMT_NONE; - - le->startTime = cg.time; - le->endTime = le->startTime + 600; - - le->pos.trType = TR_GRAVITY; - le->pos.trTime = cg.time; - - AnglesToAxis( cent->lerpAngles, v ); - - offset[0] = 2; - offset[1] = 2; - offset[2] = 2; - - xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0]; - xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1]; - xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2]; - - VectorAdd( origin, xoffset, re->origin ); - VectorCopy( re->origin, le->pos.trBase ); - - if ( CG_PointContents( re->origin, -1 ) & CONTENTS_WATER ) { - waterScale = 0.10f; - } - - xvelocity[0] = velocity[0] * v[0][0] + velocity[1] * v[1][0] + velocity[2] * v[2][0]; - xvelocity[1] = velocity[0] * v[0][1] + velocity[1] * v[1][1] + velocity[2] * v[2][1]; - xvelocity[2] = velocity[0] * v[0][2] + velocity[1] * v[1][2] + velocity[2] * v[2][2]; - VectorScale( xvelocity, waterScale, le->pos.trDelta ); - - AxisCopy( axisDefault, re->axis ); - re->shaderTime = cg.time / 1000.0f; - re->reType = RT_SPRITE; - re->radius = 0.25f; - re->customShader = cgs.media.railRingsShader; - le->bounceFactor = 0.3f; - - re->shaderRGBA[0] = wi->flashDlightColor[0] * 63; - re->shaderRGBA[1] = wi->flashDlightColor[1] * 63; - re->shaderRGBA[2] = wi->flashDlightColor[2] * 63; - re->shaderRGBA[3] = 63; - - le->color[0] = wi->flashDlightColor[0] * 0.2; - le->color[1] = wi->flashDlightColor[1] * 0.2; - le->color[2] = wi->flashDlightColor[2] * 0.2; - le->color[3] = 0.25f; - - le->angles.trType = TR_LINEAR; - le->angles.trTime = cg.time; - le->angles.trBase[0] = rand()&31; - le->angles.trBase[1] = rand()&31; - le->angles.trBase[2] = rand()&31; - le->angles.trDelta[0] = 1; - le->angles.trDelta[1] = 0.5; - le->angles.trDelta[2] = 0; - -} -/* -========================== -CG_GrappleTrail -========================== -*/ -void CG_GrappleTrail( centity_t *ent, const weaponInfo_t *wi ) { - vec3_t origin; - entityState_t *es; - vec3_t forward, up; - refEntity_t beam; - - es = &ent->currentState; - - BG_EvaluateTrajectory( &es->pos, cg.time, origin ); - ent->trailTime = cg.time; - - memset( &beam, 0, sizeof( beam ) ); - //FIXME adjust for muzzle position - VectorCopy ( cg_entities[ ent->currentState.otherEntityNum ].lerpOrigin, beam.origin ); - beam.origin[2] += 26; - AngleVectors( cg_entities[ ent->currentState.otherEntityNum ].lerpAngles, forward, NULL, up ); - VectorMA( beam.origin, -6, up, beam.origin ); - VectorCopy( origin, beam.oldorigin ); - - if (Distance( beam.origin, beam.oldorigin ) < 64 ) - return; // Don't draw if close - - beam.reType = RT_LIGHTNING; - beam.customShader = cgs.media.lightningShader; - - AxisClear( beam.axis ); - beam.shaderRGBA[0] = 0xff; - beam.shaderRGBA[1] = 0xff; - beam.shaderRGBA[2] = 0xff; - beam.shaderRGBA[3] = 0xff; - trap_R_AddRefEntityToScene( &beam ); -} - -/* -========================== -CG_GrenadeTrail -========================== -*/ -static void CG_GrenadeTrail( centity_t *ent, const weaponInfo_t *wi ) { - CG_RocketTrail( ent, wi ); -} - - -/* -================= -CG_RegisterWeapon - -The server says this item is used on this level -================= -*/ -void CG_RegisterWeapon( int weaponNum ) { - weaponInfo_t *weaponInfo; - gitem_t *item, *ammo; - char path[MAX_QPATH]; - vec3_t mins, maxs; - int i; - - weaponInfo = &cg_weapons[weaponNum]; - - if ( weaponNum == 0 ) { - return; - } - - if ( weaponInfo->registered ) { - return; - } - - memset( weaponInfo, 0, sizeof( *weaponInfo ) ); - weaponInfo->registered = qtrue; - - for ( item = bg_itemlist + 1 ; item->classname ; item++ ) { - if ( item->giType == IT_WEAPON && item->giTag == weaponNum ) { - weaponInfo->item = item; - break; - } - } - if ( !item->classname ) { - CG_Error( "Couldn't find weapon %i", weaponNum ); - } - CG_RegisterItemVisuals( item - bg_itemlist ); - - // load cmodel before model so filecache works - weaponInfo->weaponModel = trap_R_RegisterModel( item->world_model[0] ); - - // calc midpoint for rotation - trap_R_ModelBounds( weaponInfo->weaponModel, mins, maxs ); - for ( i = 0 ; i < 3 ; i++ ) { - weaponInfo->weaponMidpoint[i] = mins[i] + 0.5 * ( maxs[i] - mins[i] ); - } - - weaponInfo->weaponIcon = trap_R_RegisterShader( item->icon ); - weaponInfo->ammoIcon = trap_R_RegisterShader( item->icon ); - - for ( ammo = bg_itemlist + 1 ; ammo->classname ; ammo++ ) { - if ( ammo->giType == IT_AMMO && ammo->giTag == weaponNum ) { - break; - } - } - if ( ammo->classname && ammo->world_model[0] ) { - weaponInfo->ammoModel = trap_R_RegisterModel( ammo->world_model[0] ); - } - - strcpy( path, item->world_model[0] ); - COM_StripExtension( path, path ); - strcat( path, "_flash.md3" ); - weaponInfo->flashModel = trap_R_RegisterModel( path ); - - strcpy( path, item->world_model[0] ); - COM_StripExtension( path, path ); - strcat( path, "_barrel.md3" ); - weaponInfo->barrelModel = trap_R_RegisterModel( path ); - - strcpy( path, item->world_model[0] ); - COM_StripExtension( path, path ); - strcat( path, "_hand.md3" ); - weaponInfo->handsModel = trap_R_RegisterModel( path ); - - if ( !weaponInfo->handsModel ) { - weaponInfo->handsModel = trap_R_RegisterModel( "models/weapons2/shotgun/shotgun_hand.md3" ); - } - - weaponInfo->loopFireSound = qfalse; - - switch ( weaponNum ) { - case WP_GAUNTLET: - MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f ); - weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/melee/fstrun.wav", qfalse ); - weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/melee/fstatck.wav", qfalse ); - break; - - case WP_LIGHTNING: - MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f ); - weaponInfo->readySound = trap_S_RegisterSound( "sound/weapons/melee/fsthum.wav", qfalse ); - weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/lightning/lg_hum.wav", qfalse ); - - weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/lightning/lg_fire.wav", qfalse ); - cgs.media.lightningShader = trap_R_RegisterShader( "lightningBoltNew"); - cgs.media.lightningExplosionModel = trap_R_RegisterModel( "models/weaphits/crackle.md3" ); - cgs.media.sfx_lghit1 = trap_S_RegisterSound( "sound/weapons/lightning/lg_hit.wav", qfalse ); - cgs.media.sfx_lghit2 = trap_S_RegisterSound( "sound/weapons/lightning/lg_hit2.wav", qfalse ); - cgs.media.sfx_lghit3 = trap_S_RegisterSound( "sound/weapons/lightning/lg_hit3.wav", qfalse ); - - break; - - case WP_GRAPPLING_HOOK: - MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f ); - weaponInfo->missileModel = trap_R_RegisterModel( "models/ammo/rocket/rocket.md3" ); - weaponInfo->missileTrailFunc = CG_GrappleTrail; - weaponInfo->missileDlight = 200; - weaponInfo->wiTrailTime = 2000; - weaponInfo->trailRadius = 64; - MAKERGB( weaponInfo->missileDlightColor, 1, 0.75f, 0 ); - weaponInfo->readySound = trap_S_RegisterSound( "sound/weapons/melee/fsthum.wav", qfalse ); - weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/melee/fstrun.wav", qfalse ); - break; - -#ifdef MISSIONPACK - case WP_CHAINGUN: - weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/vulcan/wvulfire.wav", qfalse ); - weaponInfo->loopFireSound = qtrue; - MAKERGB( weaponInfo->flashDlightColor, 1, 1, 0 ); - weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/vulcan/vulcanf1b.wav", qfalse ); - weaponInfo->flashSound[1] = trap_S_RegisterSound( "sound/weapons/vulcan/vulcanf2b.wav", qfalse ); - weaponInfo->flashSound[2] = trap_S_RegisterSound( "sound/weapons/vulcan/vulcanf3b.wav", qfalse ); - weaponInfo->flashSound[3] = trap_S_RegisterSound( "sound/weapons/vulcan/vulcanf4b.wav", qfalse ); - weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass; - cgs.media.bulletExplosionShader = trap_R_RegisterShader( "bulletExplosion" ); - break; -#endif - - case WP_MACHINEGUN: - MAKERGB( weaponInfo->flashDlightColor, 1, 1, 0 ); - weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/machinegun/machgf1b.wav", qfalse ); - weaponInfo->flashSound[1] = trap_S_RegisterSound( "sound/weapons/machinegun/machgf2b.wav", qfalse ); - weaponInfo->flashSound[2] = trap_S_RegisterSound( "sound/weapons/machinegun/machgf3b.wav", qfalse ); - weaponInfo->flashSound[3] = trap_S_RegisterSound( "sound/weapons/machinegun/machgf4b.wav", qfalse ); - weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass; - cgs.media.bulletExplosionShader = trap_R_RegisterShader( "bulletExplosion" ); - break; - - case WP_SHOTGUN: - MAKERGB( weaponInfo->flashDlightColor, 1, 1, 0 ); - weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/shotgun/sshotf1b.wav", qfalse ); - weaponInfo->ejectBrassFunc = CG_ShotgunEjectBrass; - break; - - case WP_ROCKET_LAUNCHER: - weaponInfo->missileModel = trap_R_RegisterModel( "models/ammo/rocket/rocket.md3" ); - weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/rocket/rockfly.wav", qfalse ); - weaponInfo->missileTrailFunc = CG_RocketTrail; - weaponInfo->missileDlight = 200; - weaponInfo->wiTrailTime = 2000; - weaponInfo->trailRadius = 64; - - MAKERGB( weaponInfo->missileDlightColor, 1, 0.75f, 0 ); - MAKERGB( weaponInfo->flashDlightColor, 1, 0.75f, 0 ); - - weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/rocket/rocklf1a.wav", qfalse ); - cgs.media.rocketExplosionShader = trap_R_RegisterShader( "rocketExplosion" ); - break; - -#ifdef MISSIONPACK - case WP_PROX_LAUNCHER: - weaponInfo->missileModel = trap_R_RegisterModel( "models/weaphits/proxmine.md3" ); - weaponInfo->missileTrailFunc = CG_GrenadeTrail; - weaponInfo->wiTrailTime = 700; - weaponInfo->trailRadius = 32; - MAKERGB( weaponInfo->flashDlightColor, 1, 0.70f, 0 ); - weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/proxmine/wstbfire.wav", qfalse ); - cgs.media.grenadeExplosionShader = trap_R_RegisterShader( "grenadeExplosion" ); - break; -#endif - - case WP_GRENADE_LAUNCHER: - weaponInfo->missileModel = trap_R_RegisterModel( "models/ammo/grenade1.md3" ); - weaponInfo->missileTrailFunc = CG_GrenadeTrail; - weaponInfo->wiTrailTime = 700; - weaponInfo->trailRadius = 32; - MAKERGB( weaponInfo->flashDlightColor, 1, 0.70f, 0 ); - weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/grenade/grenlf1a.wav", qfalse ); - cgs.media.grenadeExplosionShader = trap_R_RegisterShader( "grenadeExplosion" ); - break; - -#ifdef MISSIONPACK - case WP_NAILGUN: - weaponInfo->ejectBrassFunc = CG_NailgunEjectBrass; - weaponInfo->missileTrailFunc = CG_NailTrail; -// weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/nailgun/wnalflit.wav", qfalse ); - weaponInfo->trailRadius = 16; - weaponInfo->wiTrailTime = 250; - weaponInfo->missileModel = trap_R_RegisterModel( "models/weaphits/nail.md3" ); - MAKERGB( weaponInfo->flashDlightColor, 1, 0.75f, 0 ); - weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/nailgun/wnalfire.wav", qfalse ); - break; -#endif - - case WP_PLASMAGUN: -// weaponInfo->missileModel = cgs.media.invulnerabilityPowerupModel; - weaponInfo->missileTrailFunc = CG_PlasmaTrail; - weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/plasma/lasfly.wav", qfalse ); - MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f ); - weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/plasma/hyprbf1a.wav", qfalse ); - cgs.media.plasmaExplosionShader = trap_R_RegisterShader( "plasmaExplosion" ); - cgs.media.railRingsShader = trap_R_RegisterShader( "railDisc" ); - break; - - case WP_RAILGUN: - weaponInfo->readySound = trap_S_RegisterSound( "sound/weapons/railgun/rg_hum.wav", qfalse ); - MAKERGB( weaponInfo->flashDlightColor, 1, 0.5f, 0 ); - weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/railgun/railgf1a.wav", qfalse ); - cgs.media.railExplosionShader = trap_R_RegisterShader( "railExplosion" ); - cgs.media.railRingsShader = trap_R_RegisterShader( "railDisc" ); - cgs.media.railCoreShader = trap_R_RegisterShader( "railCore" ); - break; - - case WP_BFG: - weaponInfo->readySound = trap_S_RegisterSound( "sound/weapons/bfg/bfg_hum.wav", qfalse ); - MAKERGB( weaponInfo->flashDlightColor, 1, 0.7f, 1 ); - weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/bfg/bfg_fire.wav", qfalse ); - cgs.media.bfgExplosionShader = trap_R_RegisterShader( "bfgExplosion" ); - weaponInfo->missileModel = trap_R_RegisterModel( "models/weaphits/bfg.md3" ); - weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/rocket/rockfly.wav", qfalse ); - break; - - default: - MAKERGB( weaponInfo->flashDlightColor, 1, 1, 1 ); - weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/rocket/rocklf1a.wav", qfalse ); - break; - } -} - -/* -================= -CG_RegisterItemVisuals - -The server says this item is used on this level -================= -*/ -void CG_RegisterItemVisuals( int itemNum ) { - itemInfo_t *itemInfo; - gitem_t *item; - - if ( itemNum < 0 || itemNum >= bg_numItems ) { - CG_Error( "CG_RegisterItemVisuals: itemNum %d out of range [0-%d]", itemNum, bg_numItems-1 ); - } - - itemInfo = &cg_items[ itemNum ]; - if ( itemInfo->registered ) { - return; - } - - item = &bg_itemlist[ itemNum ]; - - memset( itemInfo, 0, sizeof( &itemInfo ) ); - itemInfo->registered = qtrue; - - itemInfo->models[0] = trap_R_RegisterModel( item->world_model[0] ); - - itemInfo->icon = trap_R_RegisterShader( item->icon ); - - if ( item->giType == IT_WEAPON ) { - CG_RegisterWeapon( item->giTag ); - } - - // - // powerups have an accompanying ring or sphere - // - if ( item->giType == IT_POWERUP || item->giType == IT_HEALTH || - item->giType == IT_ARMOR || item->giType == IT_HOLDABLE ) { - if ( item->world_model[1] ) { - itemInfo->models[1] = trap_R_RegisterModel( item->world_model[1] ); - } - } -} - - -/* -======================================================================================== - -VIEW WEAPON - -======================================================================================== -*/ - -/* -================= -CG_MapTorsoToWeaponFrame - -================= -*/ -static int CG_MapTorsoToWeaponFrame( clientInfo_t *ci, int frame ) { - - // change weapon - if ( frame >= ci->animations[TORSO_DROP].firstFrame - && frame < ci->animations[TORSO_DROP].firstFrame + 9 ) { - return frame - ci->animations[TORSO_DROP].firstFrame + 6; - } - - // stand attack - if ( frame >= ci->animations[TORSO_ATTACK].firstFrame - && frame < ci->animations[TORSO_ATTACK].firstFrame + 6 ) { - return 1 + frame - ci->animations[TORSO_ATTACK].firstFrame; - } - - // stand attack 2 - if ( frame >= ci->animations[TORSO_ATTACK2].firstFrame - && frame < ci->animations[TORSO_ATTACK2].firstFrame + 6 ) { - return 1 + frame - ci->animations[TORSO_ATTACK2].firstFrame; - } - - return 0; -} - - -/* -============== -CG_CalculateWeaponPosition -============== -*/ -static void CG_CalculateWeaponPosition( vec3_t origin, vec3_t angles ) { - float scale; - int delta; - float fracsin; - - VectorCopy( cg.refdef.vieworg, origin ); - VectorCopy( cg.refdefViewAngles, angles ); - - // on odd legs, invert some angles - if ( cg.bobcycle & 1 ) { - scale = -cg.xyspeed; - } else { - scale = cg.xyspeed; - } - - // gun angles from bobbing - angles[ROLL] += scale * cg.bobfracsin * 0.005; - angles[YAW] += scale * cg.bobfracsin * 0.01; - angles[PITCH] += cg.xyspeed * cg.bobfracsin * 0.005; - - // drop the weapon when landing - delta = cg.time - cg.landTime; - if ( delta < LAND_DEFLECT_TIME ) { - origin[2] += cg.landChange*0.25 * delta / LAND_DEFLECT_TIME; - } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) { - origin[2] += cg.landChange*0.25 * - (LAND_DEFLECT_TIME + LAND_RETURN_TIME - delta) / LAND_RETURN_TIME; - } - -#if 0 - // drop the weapon when stair climbing - delta = cg.time - cg.stepTime; - if ( delta < STEP_TIME/2 ) { - origin[2] -= cg.stepChange*0.25 * delta / (STEP_TIME/2); - } else if ( delta < STEP_TIME ) { - origin[2] -= cg.stepChange*0.25 * (STEP_TIME - delta) / (STEP_TIME/2); - } -#endif - - // idle drift - scale = cg.xyspeed + 40; - fracsin = sin( cg.time * 0.001 ); - angles[ROLL] += scale * fracsin * 0.01; - angles[YAW] += scale * fracsin * 0.01; - angles[PITCH] += scale * fracsin * 0.01; -} - - -/* -=============== -CG_LightningBolt - -Origin will be the exact tag point, which is slightly -different than the muzzle point used for determining hits. -The cent should be the non-predicted cent if it is from the player, -so the endpoint will reflect the simulated strike (lagging the predicted -angle) -=============== -*/ -static void CG_LightningBolt( centity_t *cent, vec3_t origin ) { - trace_t trace; - refEntity_t beam; - vec3_t forward; - vec3_t muzzlePoint, endPoint; - - if (cent->currentState.weapon != WP_LIGHTNING) { - return; - } - - memset( &beam, 0, sizeof( beam ) ); - - // CPMA "true" lightning - if ((cent->currentState.number == cg.predictedPlayerState.clientNum) && (cg_trueLightning.value != 0)) { - vec3_t angle; - int i; - - for (i = 0; i < 3; i++) { - float a = cent->lerpAngles[i] - cg.refdefViewAngles[i]; - if (a > 180) { - a -= 360; - } - if (a < -180) { - a += 360; - } - - angle[i] = cg.refdefViewAngles[i] + a * (1.0 - cg_trueLightning.value); - if (angle[i] < 0) { - angle[i] += 360; - } - if (angle[i] > 360) { - angle[i] -= 360; - } - } - - AngleVectors(angle, forward, NULL, NULL ); - VectorCopy(cent->lerpOrigin, muzzlePoint ); -// VectorCopy(cg.refdef.vieworg, muzzlePoint ); - } else { - // !CPMA - AngleVectors( cent->lerpAngles, forward, NULL, NULL ); - VectorCopy(cent->lerpOrigin, muzzlePoint ); - } - - // FIXME: crouch - muzzlePoint[2] += DEFAULT_VIEWHEIGHT; - - VectorMA( muzzlePoint, 14, forward, muzzlePoint ); - - // project forward by the lightning range - VectorMA( muzzlePoint, LIGHTNING_RANGE, forward, endPoint ); - - // see if it hit a wall - CG_Trace( &trace, muzzlePoint, vec3_origin, vec3_origin, endPoint, - cent->currentState.number, MASK_SHOT ); - - // this is the endpoint - VectorCopy( trace.endpos, beam.oldorigin ); - - // use the provided origin, even though it may be slightly - // different than the muzzle origin - VectorCopy( origin, beam.origin ); - - beam.reType = RT_LIGHTNING; - beam.customShader = cgs.media.lightningShader; - trap_R_AddRefEntityToScene( &beam ); - - // add the impact flare if it hit something - if ( trace.fraction < 1.0 ) { - vec3_t angles; - vec3_t dir; - - VectorSubtract( beam.oldorigin, beam.origin, dir ); - VectorNormalize( dir ); - - memset( &beam, 0, sizeof( beam ) ); - beam.hModel = cgs.media.lightningExplosionModel; - - VectorMA( trace.endpos, -16, dir, beam.origin ); - - // make a random orientation - angles[0] = rand() % 360; - angles[1] = rand() % 360; - angles[2] = rand() % 360; - AnglesToAxis( angles, beam.axis ); - trap_R_AddRefEntityToScene( &beam ); - } -} -/* - -static void CG_LightningBolt( centity_t *cent, vec3_t origin ) { - trace_t trace; - refEntity_t beam; - vec3_t forward; - vec3_t muzzlePoint, endPoint; - - if ( cent->currentState.weapon != WP_LIGHTNING ) { - return; - } - - memset( &beam, 0, sizeof( beam ) ); - - // find muzzle point for this frame - VectorCopy( cent->lerpOrigin, muzzlePoint ); - AngleVectors( cent->lerpAngles, forward, NULL, NULL ); - - // FIXME: crouch - muzzlePoint[2] += DEFAULT_VIEWHEIGHT; - - VectorMA( muzzlePoint, 14, forward, muzzlePoint ); - - // project forward by the lightning range - VectorMA( muzzlePoint, LIGHTNING_RANGE, forward, endPoint ); - - // see if it hit a wall - CG_Trace( &trace, muzzlePoint, vec3_origin, vec3_origin, endPoint, - cent->currentState.number, MASK_SHOT ); - - // this is the endpoint - VectorCopy( trace.endpos, beam.oldorigin ); - - // use the provided origin, even though it may be slightly - // different than the muzzle origin - VectorCopy( origin, beam.origin ); - - beam.reType = RT_LIGHTNING; - beam.customShader = cgs.media.lightningShader; - trap_R_AddRefEntityToScene( &beam ); - - // add the impact flare if it hit something - if ( trace.fraction < 1.0 ) { - vec3_t angles; - vec3_t dir; - - VectorSubtract( beam.oldorigin, beam.origin, dir ); - VectorNormalize( dir ); - - memset( &beam, 0, sizeof( beam ) ); - beam.hModel = cgs.media.lightningExplosionModel; - - VectorMA( trace.endpos, -16, dir, beam.origin ); - - // make a random orientation - angles[0] = rand() % 360; - angles[1] = rand() % 360; - angles[2] = rand() % 360; - AnglesToAxis( angles, beam.axis ); - trap_R_AddRefEntityToScene( &beam ); - } -} -*/ - -/* -=============== -CG_SpawnRailTrail - -Origin will be the exact tag point, which is slightly -different than the muzzle point used for determining hits. -=============== -*/ -static void CG_SpawnRailTrail( centity_t *cent, vec3_t origin ) { - clientInfo_t *ci; - - if ( cent->currentState.weapon != WP_RAILGUN ) { - return; - } - if ( !cent->pe.railgunFlash ) { - return; - } - cent->pe.railgunFlash = qtrue; - ci = &cgs.clientinfo[ cent->currentState.clientNum ]; - CG_RailTrail( ci, origin, cent->pe.railgunImpact ); -} - - -/* -====================== -CG_MachinegunSpinAngle -====================== -*/ -#define SPIN_SPEED 0.9 -#define COAST_TIME 1000 -static float CG_MachinegunSpinAngle( centity_t *cent ) { - int delta; - float angle; - float speed; - - delta = cg.time - cent->pe.barrelTime; - if ( cent->pe.barrelSpinning ) { - angle = cent->pe.barrelAngle + delta * SPIN_SPEED; - } else { - if ( delta > COAST_TIME ) { - delta = COAST_TIME; - } - - speed = 0.5 * ( SPIN_SPEED + (float)( COAST_TIME - delta ) / COAST_TIME ); - angle = cent->pe.barrelAngle + delta * speed; - } - - if ( cent->pe.barrelSpinning == !(cent->currentState.eFlags & EF_FIRING) ) { - cent->pe.barrelTime = cg.time; - cent->pe.barrelAngle = AngleMod( angle ); - cent->pe.barrelSpinning = !!(cent->currentState.eFlags & EF_FIRING); -#ifdef MISSIONPACK - if ( cent->currentState.weapon == WP_CHAINGUN && !cent->pe.barrelSpinning ) { - trap_S_StartSound( NULL, cent->currentState.number, CHAN_WEAPON, trap_S_RegisterSound( "sound/weapons/vulcan/wvulwind.wav", qfalse ) ); - } -#endif - } - - return angle; -} - - -/* -======================== -CG_AddWeaponWithPowerups -======================== -*/ -static void CG_AddWeaponWithPowerups( refEntity_t *gun, int powerups ) { - // add powerup effects - if ( powerups & ( 1 << PW_INVIS ) ) { - gun->customShader = cgs.media.invisShader; - trap_R_AddRefEntityToScene( gun ); - } else { - trap_R_AddRefEntityToScene( gun ); - - if ( powerups & ( 1 << PW_BATTLESUIT ) ) { - gun->customShader = cgs.media.battleWeaponShader; - trap_R_AddRefEntityToScene( gun ); - } - if ( powerups & ( 1 << PW_QUAD ) ) { - gun->customShader = cgs.media.quadWeaponShader; - trap_R_AddRefEntityToScene( gun ); - } - } -} - - -/* -============= -CG_AddPlayerWeapon - -Used for both the view weapon (ps is valid) and the world modelother character models (ps is NULL) -The main player will have this called for BOTH cases, so effects like light and -sound should only be done on the world model case. -============= -*/ -void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent, int team ) { - refEntity_t gun; - refEntity_t barrel; - refEntity_t flash; - vec3_t angles; - weapon_t weaponNum; - weaponInfo_t *weapon; - centity_t *nonPredictedCent; -// int col; - - weaponNum = cent->currentState.weapon; - - CG_RegisterWeapon( weaponNum ); - weapon = &cg_weapons[weaponNum]; - - // add the weapon - memset( &gun, 0, sizeof( gun ) ); - VectorCopy( parent->lightingOrigin, gun.lightingOrigin ); - gun.shadowPlane = parent->shadowPlane; - gun.renderfx = parent->renderfx; - - // set custom shading for railgun refire rate - if ( ps ) { - if ( cg.predictedPlayerState.weapon == WP_RAILGUN - && cg.predictedPlayerState.weaponstate == WEAPON_FIRING ) { - float f; - - f = (float)cg.predictedPlayerState.weaponTime / 1500; - gun.shaderRGBA[1] = 0; - gun.shaderRGBA[0] = - gun.shaderRGBA[2] = 255 * ( 1.0 - f ); - } else { - gun.shaderRGBA[0] = 255; - gun.shaderRGBA[1] = 255; - gun.shaderRGBA[2] = 255; - gun.shaderRGBA[3] = 255; - } - } - - gun.hModel = weapon->weaponModel; - if (!gun.hModel) { - return; - } - - if ( !ps ) { - // add weapon ready sound - cent->pe.lightningFiring = qfalse; - if ( ( cent->currentState.eFlags & EF_FIRING ) && weapon->firingSound ) { - // lightning gun and guantlet make a different sound when fire is held down - trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->firingSound ); - cent->pe.lightningFiring = qtrue; - } else if ( weapon->readySound ) { - trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->readySound ); - } - } - - CG_PositionEntityOnTag( &gun, parent, parent->hModel, "tag_weapon"); - - CG_AddWeaponWithPowerups( &gun, cent->currentState.powerups ); - - // add the spinning barrel - if ( weapon->barrelModel ) { - memset( &barrel, 0, sizeof( barrel ) ); - VectorCopy( parent->lightingOrigin, barrel.lightingOrigin ); - barrel.shadowPlane = parent->shadowPlane; - barrel.renderfx = parent->renderfx; - - barrel.hModel = weapon->barrelModel; - angles[YAW] = 0; - angles[PITCH] = 0; - angles[ROLL] = CG_MachinegunSpinAngle( cent ); - AnglesToAxis( angles, barrel.axis ); - - CG_PositionRotatedEntityOnTag( &barrel, &gun, weapon->weaponModel, "tag_barrel" ); - - CG_AddWeaponWithPowerups( &barrel, cent->currentState.powerups ); - } - - // make sure we aren't looking at cg.predictedPlayerEntity for LG - nonPredictedCent = &cg_entities[cent->currentState.clientNum]; - - // if the index of the nonPredictedCent is not the same as the clientNum - // then this is a fake player (like on teh single player podiums), so - // go ahead and use the cent - if( ( nonPredictedCent - cg_entities ) != cent->currentState.clientNum ) { - nonPredictedCent = cent; - } - - // add the flash - if ( ( weaponNum == WP_LIGHTNING || weaponNum == WP_GAUNTLET || weaponNum == WP_GRAPPLING_HOOK ) - && ( nonPredictedCent->currentState.eFlags & EF_FIRING ) ) - { - // continuous flash - } else { - // impulse flash - if ( cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_TIME && !cent->pe.railgunFlash ) { - return; - } - } - - memset( &flash, 0, sizeof( flash ) ); - VectorCopy( parent->lightingOrigin, flash.lightingOrigin ); - flash.shadowPlane = parent->shadowPlane; - flash.renderfx = parent->renderfx; - - flash.hModel = weapon->flashModel; - if (!flash.hModel) { - return; - } - angles[YAW] = 0; - angles[PITCH] = 0; - angles[ROLL] = crandom() * 10; - AnglesToAxis( angles, flash.axis ); - - // colorize the railgun blast - if ( weaponNum == WP_RAILGUN ) { - clientInfo_t *ci; - - ci = &cgs.clientinfo[ cent->currentState.clientNum ]; - flash.shaderRGBA[0] = 255 * ci->color1[0]; - flash.shaderRGBA[1] = 255 * ci->color1[1]; - flash.shaderRGBA[2] = 255 * ci->color1[2]; - } - - CG_PositionRotatedEntityOnTag( &flash, &gun, weapon->weaponModel, "tag_flash"); - trap_R_AddRefEntityToScene( &flash ); - - if ( ps || cg.renderingThirdPerson || - cent->currentState.number != cg.predictedPlayerState.clientNum ) { - // add lightning bolt - CG_LightningBolt( nonPredictedCent, flash.origin ); - - // add rail trail - CG_SpawnRailTrail( cent, flash.origin ); - - if ( weapon->flashDlightColor[0] || weapon->flashDlightColor[1] || weapon->flashDlightColor[2] ) { - trap_R_AddLightToScene( flash.origin, 300 + (rand()&31), weapon->flashDlightColor[0], - weapon->flashDlightColor[1], weapon->flashDlightColor[2] ); - } - } -} - -/* -============== -CG_AddViewWeapon - -Add the weapon, and flash for the player's view -============== -*/ -void CG_AddViewWeapon( playerState_t *ps ) { - refEntity_t hand; - centity_t *cent; - clientInfo_t *ci; - float fovOffset; - vec3_t angles; - weaponInfo_t *weapon; - - if ( ps->persistant[PERS_TEAM] == TEAM_SPECTATOR ) { - return; - } - - if ( ps->pm_type == PM_INTERMISSION ) { - return; - } - - // no gun if in third person view or a camera is active - //if ( cg.renderingThirdPerson || cg.cameraMode) { - if ( cg.renderingThirdPerson ) { - return; - } - - - // allow the gun to be completely removed - if ( !cg_drawGun.integer ) { - vec3_t origin; - - if ( cg.predictedPlayerState.eFlags & EF_FIRING ) { - // special hack for lightning gun... - VectorCopy( cg.refdef.vieworg, origin ); - VectorMA( origin, -8, cg.refdef.viewaxis[2], origin ); - CG_LightningBolt( &cg_entities[ps->clientNum], origin ); - } - return; - } - - // don't draw if testing a gun model - if ( cg.testGun ) { - return; - } - - // drop gun lower at higher fov - if ( cg_fov.integer > 90 ) { - fovOffset = -0.2 * ( cg_fov.integer - 90 ); - } else { - fovOffset = 0; - } - - cent = &cg.predictedPlayerEntity; // &cg_entities[cg.snap->ps.clientNum]; - CG_RegisterWeapon( ps->weapon ); - weapon = &cg_weapons[ ps->weapon ]; - - memset (&hand, 0, sizeof(hand)); - - // set up gun position - CG_CalculateWeaponPosition( hand.origin, angles ); - - VectorMA( hand.origin, cg_gun_x.value, cg.refdef.viewaxis[0], hand.origin ); - VectorMA( hand.origin, cg_gun_y.value, cg.refdef.viewaxis[1], hand.origin ); - VectorMA( hand.origin, (cg_gun_z.value+fovOffset), cg.refdef.viewaxis[2], hand.origin ); - - AnglesToAxis( angles, hand.axis ); - - // map torso animations to weapon animations - if ( cg_gun_frame.integer ) { - // development tool - hand.frame = hand.oldframe = cg_gun_frame.integer; - hand.backlerp = 0; - } else { - // get clientinfo for animation map - ci = &cgs.clientinfo[ cent->currentState.clientNum ]; - hand.frame = CG_MapTorsoToWeaponFrame( ci, cent->pe.torso.frame ); - hand.oldframe = CG_MapTorsoToWeaponFrame( ci, cent->pe.torso.oldFrame ); - hand.backlerp = cent->pe.torso.backlerp; - } - - hand.hModel = weapon->handsModel; - hand.renderfx = RF_DEPTHHACK | RF_FIRST_PERSON | RF_MINLIGHT; - - // add everything onto the hand - CG_AddPlayerWeapon( &hand, ps, &cg.predictedPlayerEntity, ps->persistant[PERS_TEAM] ); -} - -/* -============================================================================== - -WEAPON SELECTION - -============================================================================== -*/ - -/* -=================== -CG_DrawWeaponSelect -=================== -*/ -void CG_DrawWeaponSelect( void ) { - int i; - int bits; - int count; - int x, y, w; - char *name; - float *color; - - // don't display if dead - if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) { - return; - } - - color = CG_FadeColor( cg.weaponSelectTime, WEAPON_SELECT_TIME ); - if ( !color ) { - return; - } - trap_R_SetColor( color ); - - // showing weapon select clears pickup item display, but not the blend blob - cg.itemPickupTime = 0; - - // count the number of weapons owned - bits = cg.snap->ps.stats[ STAT_WEAPONS ]; - count = 0; - for ( i = 1 ; i < 16 ; i++ ) { - if ( bits & ( 1 << i ) ) { - count++; - } - } - - x = 320 - count * 20; - y = 380; - - for ( i = 1 ; i < 16 ; i++ ) { - if ( !( bits & ( 1 << i ) ) ) { - continue; - } - - CG_RegisterWeapon( i ); - - // draw weapon icon - CG_DrawPic( x, y, 32, 32, cg_weapons[i].weaponIcon ); - - // draw selection marker - if ( i == cg.weaponSelect ) { - CG_DrawPic( x-4, y-4, 40, 40, cgs.media.selectShader ); - } - - // no ammo cross on top - if ( !cg.snap->ps.ammo[ i ] ) { - CG_DrawPic( x, y, 32, 32, cgs.media.noammoShader ); - } - - x += 40; - } - - // draw the selected name - if ( cg_weapons[ cg.weaponSelect ].item ) { - name = cg_weapons[ cg.weaponSelect ].item->pickup_name; - if ( name ) { - w = CG_DrawStrlen( name ) * BIGCHAR_WIDTH; - x = ( SCREEN_WIDTH - w ) / 2; - CG_DrawBigStringColor(x, y - 22, name, color); - } - } - - trap_R_SetColor( NULL ); -} - - -/* -=============== -CG_WeaponSelectable -=============== -*/ -static qboolean CG_WeaponSelectable( int i ) { - if ( !cg.snap->ps.ammo[i] ) { - return qfalse; - } - if ( ! (cg.snap->ps.stats[ STAT_WEAPONS ] & ( 1 << i ) ) ) { - return qfalse; - } - - return qtrue; -} - -/* -=============== -CG_NextWeapon_f -=============== -*/ -void CG_NextWeapon_f( void ) { - int i; - int original; - - if ( !cg.snap ) { - return; - } - if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { - return; - } - - cg.weaponSelectTime = cg.time; - original = cg.weaponSelect; - - for ( i = 0 ; i < 16 ; i++ ) { - cg.weaponSelect++; - if ( cg.weaponSelect == 16 ) { - cg.weaponSelect = 0; - } - if ( cg.weaponSelect == WP_GAUNTLET ) { - continue; // never cycle to gauntlet - } - if ( CG_WeaponSelectable( cg.weaponSelect ) ) { - break; - } - } - if ( i == 16 ) { - cg.weaponSelect = original; - } -} - -/* -=============== -CG_PrevWeapon_f -=============== -*/ -void CG_PrevWeapon_f( void ) { - int i; - int original; - - if ( !cg.snap ) { - return; - } - if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { - return; - } - - cg.weaponSelectTime = cg.time; - original = cg.weaponSelect; - - for ( i = 0 ; i < 16 ; i++ ) { - cg.weaponSelect--; - if ( cg.weaponSelect == -1 ) { - cg.weaponSelect = 15; - } - if ( cg.weaponSelect == WP_GAUNTLET ) { - continue; // never cycle to gauntlet - } - if ( CG_WeaponSelectable( cg.weaponSelect ) ) { - break; - } - } - if ( i == 16 ) { - cg.weaponSelect = original; - } -} - -/* -=============== -CG_Weapon_f -=============== -*/ -void CG_Weapon_f( void ) { - int num; - - if ( !cg.snap ) { - return; - } - if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { - return; - } - - num = atoi( CG_Argv( 1 ) ); - - if ( num < 1 || num > 15 ) { - return; - } - - cg.weaponSelectTime = cg.time; - - if ( ! ( cg.snap->ps.stats[STAT_WEAPONS] & ( 1 << num ) ) ) { - return; // don't have the weapon - } - - cg.weaponSelect = num; -} - -/* -=================== -CG_OutOfAmmoChange - -The current weapon has just run out of ammo -=================== -*/ -void CG_OutOfAmmoChange( void ) { - int i; - - cg.weaponSelectTime = cg.time; - - for ( i = 15 ; i > 0 ; i-- ) { - if ( CG_WeaponSelectable( i ) ) { - cg.weaponSelect = i; - break; - } - } -} - - - -/* -=================================================================================================== - -WEAPON EVENTS - -=================================================================================================== -*/ - -/* -================ -CG_FireWeapon - -Caused by an EV_FIRE_WEAPON event -================ -*/ -void CG_FireWeapon( centity_t *cent ) { - entityState_t *ent; - int c; - weaponInfo_t *weap; - - ent = ¢->currentState; - if ( ent->weapon == WP_NONE ) { - return; - } - if ( ent->weapon >= WP_NUM_WEAPONS ) { - CG_Error( "CG_FireWeapon: ent->weapon >= WP_NUM_WEAPONS" ); - return; - } - weap = &cg_weapons[ ent->weapon ]; - - // mark the entity as muzzle flashing, so when it is added it will - // append the flash to the weapon model - cent->muzzleFlashTime = cg.time; - - // lightning gun only does this this on initial press - if ( ent->weapon == WP_LIGHTNING ) { - if ( cent->pe.lightningFiring ) { - return; - } - } - - // play quad sound if needed - if ( cent->currentState.powerups & ( 1 << PW_QUAD ) ) { - trap_S_StartSound (NULL, cent->currentState.number, CHAN_ITEM, cgs.media.quadSound ); - } - - // play a sound - for ( c = 0 ; c < 4 ; c++ ) { - if ( !weap->flashSound[c] ) { - break; - } - } - if ( c > 0 ) { - c = rand() % c; - if ( weap->flashSound[c] ) - { - trap_S_StartSound( NULL, ent->number, CHAN_WEAPON, weap->flashSound[c] ); - } - } - - // do brass ejection - if ( weap->ejectBrassFunc && cg_brassTime.integer > 0 ) { - weap->ejectBrassFunc( cent ); - } -} - - -/* -================= -CG_MissileHitWall - -Caused by an EV_MISSILE_MISS event, or directly by local bullet tracing -================= -*/ -void CG_MissileHitWall( int weapon, int clientNum, vec3_t origin, vec3_t dir, impactSound_t soundType ) { - qhandle_t mod; - qhandle_t mark; - qhandle_t shader; - sfxHandle_t sfx; - float radius; - float light; - vec3_t lightColor; - localEntity_t *le; - int r; - qboolean alphaFade; - qboolean isSprite; - int duration; - vec3_t sprOrg; - vec3_t sprVel; - - mark = 0; - radius = 32; - sfx = 0; - mod = 0; - shader = 0; - light = 0; - lightColor[0] = 1; - lightColor[1] = 1; - lightColor[2] = 0; - - // set defaults - isSprite = qfalse; - duration = 600; - - switch ( weapon ) { - default: -#ifdef MISSIONPACK - case WP_NAILGUN: - if( soundType == IMPACTSOUND_FLESH ) { - sfx = cgs.media.sfx_nghitflesh; - } else if( soundType == IMPACTSOUND_METAL ) { - sfx = cgs.media.sfx_nghitmetal; - } else { - sfx = cgs.media.sfx_nghit; - } - mark = cgs.media.holeMarkShader; - radius = 12; - break; -#endif - case WP_LIGHTNING: - // no explosion at LG impact, it is added with the beam - r = rand() & 3; - if ( r < 2 ) { - sfx = cgs.media.sfx_lghit2; - } else if ( r == 2 ) { - sfx = cgs.media.sfx_lghit1; - } else { - sfx = cgs.media.sfx_lghit3; - } - mark = cgs.media.holeMarkShader; - radius = 12; - break; -#ifdef MISSIONPACK - case WP_PROX_LAUNCHER: - mod = cgs.media.dishFlashModel; - shader = cgs.media.grenadeExplosionShader; - sfx = cgs.media.sfx_proxexp; - mark = cgs.media.burnMarkShader; - radius = 64; - light = 300; - isSprite = qtrue; - break; -#endif - case WP_GRENADE_LAUNCHER: - mod = cgs.media.dishFlashModel; - shader = cgs.media.grenadeExplosionShader; - sfx = cgs.media.sfx_rockexp; - mark = cgs.media.burnMarkShader; - radius = 64; - light = 300; - isSprite = qtrue; - break; - case WP_ROCKET_LAUNCHER: - mod = cgs.media.dishFlashModel; - shader = cgs.media.rocketExplosionShader; - sfx = cgs.media.sfx_rockexp; - mark = cgs.media.burnMarkShader; - radius = 64; - light = 300; - isSprite = qtrue; - duration = 1000; - lightColor[0] = 1; - lightColor[1] = 0.75; - lightColor[2] = 0.0; - if (cg_oldRocket.integer == 0) { - // explosion sprite animation - VectorMA( origin, 24, dir, sprOrg ); - VectorScale( dir, 64, sprVel ); - - CG_ParticleExplosion( "explode1", sprOrg, sprVel, 1400, 20, 30 ); - } - break; - case WP_RAILGUN: - mod = cgs.media.ringFlashModel; - shader = cgs.media.railExplosionShader; - sfx = cgs.media.sfx_plasmaexp; - mark = cgs.media.energyMarkShader; - radius = 24; - break; - case WP_PLASMAGUN: - mod = cgs.media.ringFlashModel; - shader = cgs.media.plasmaExplosionShader; - sfx = cgs.media.sfx_plasmaexp; - mark = cgs.media.energyMarkShader; - radius = 16; - break; - case WP_BFG: - mod = cgs.media.dishFlashModel; - shader = cgs.media.bfgExplosionShader; - sfx = cgs.media.sfx_rockexp; - mark = cgs.media.burnMarkShader; - radius = 32; - isSprite = qtrue; - break; - case WP_SHOTGUN: - mod = cgs.media.bulletFlashModel; - shader = cgs.media.bulletExplosionShader; - mark = cgs.media.bulletMarkShader; - sfx = 0; - radius = 4; - break; - -#ifdef MISSIONPACK - case WP_CHAINGUN: - mod = cgs.media.bulletFlashModel; - if( soundType == IMPACTSOUND_FLESH ) { - sfx = cgs.media.sfx_chghitflesh; - } else if( soundType == IMPACTSOUND_METAL ) { - sfx = cgs.media.sfx_chghitmetal; - } else { - sfx = cgs.media.sfx_chghit; - } - mark = cgs.media.bulletMarkShader; - - r = rand() & 3; - if ( r < 2 ) { - sfx = cgs.media.sfx_ric1; - } else if ( r == 2 ) { - sfx = cgs.media.sfx_ric2; - } else { - sfx = cgs.media.sfx_ric3; - } - - radius = 8; - break; -#endif - - case WP_MACHINEGUN: - mod = cgs.media.bulletFlashModel; - shader = cgs.media.bulletExplosionShader; - mark = cgs.media.bulletMarkShader; - - r = rand() & 3; - if ( r == 0 ) { - sfx = cgs.media.sfx_ric1; - } else if ( r == 1 ) { - sfx = cgs.media.sfx_ric2; - } else { - sfx = cgs.media.sfx_ric3; - } - - radius = 8; - break; - } - - if ( sfx ) { - trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, sfx ); - } - - // - // create the explosion - // - if ( mod ) { - le = CG_MakeExplosion( origin, dir, - mod, shader, - duration, isSprite ); - le->light = light; - VectorCopy( lightColor, le->lightColor ); - if ( weapon == WP_RAILGUN ) { - // colorize with client color - VectorCopy( cgs.clientinfo[clientNum].color1, le->color ); - } - } - - // - // impact mark - // - alphaFade = (mark == cgs.media.energyMarkShader); // plasma fades alpha, all others fade color - if ( weapon == WP_RAILGUN ) { - float *color; - - // colorize with client color - color = cgs.clientinfo[clientNum].color2; - CG_ImpactMark( mark, origin, dir, random()*360, color[0],color[1], color[2],1, alphaFade, radius, qfalse ); - } else { - CG_ImpactMark( mark, origin, dir, random()*360, 1,1,1,1, alphaFade, radius, qfalse ); - } -} - - -/* -================= -CG_MissileHitPlayer -================= -*/ -void CG_MissileHitPlayer( int weapon, vec3_t origin, vec3_t dir, int entityNum ) { - CG_Bleed( origin, entityNum ); - - // some weapons will make an explosion with the blood, while - // others will just make the blood - switch ( weapon ) { - case WP_GRENADE_LAUNCHER: - case WP_ROCKET_LAUNCHER: -#ifdef MISSIONPACK - case WP_NAILGUN: - case WP_CHAINGUN: - case WP_PROX_LAUNCHER: -#endif - CG_MissileHitWall( weapon, 0, origin, dir, IMPACTSOUND_FLESH ); - break; - default: - break; - } -} - - - -/* -============================================================================ - -SHOTGUN TRACING - -============================================================================ -*/ - -/* -================ -CG_ShotgunPellet -================ -*/ -static void CG_ShotgunPellet( vec3_t start, vec3_t end, int skipNum ) { - trace_t tr; - int sourceContentType, destContentType; - - CG_Trace( &tr, start, NULL, NULL, end, skipNum, MASK_SHOT ); - - sourceContentType = trap_CM_PointContents( start, 0 ); - destContentType = trap_CM_PointContents( tr.endpos, 0 ); - - // FIXME: should probably move this cruft into CG_BubbleTrail - if ( sourceContentType == destContentType ) { - if ( sourceContentType & CONTENTS_WATER ) { - CG_BubbleTrail( start, tr.endpos, 32 ); - } - } else if ( sourceContentType & CONTENTS_WATER ) { - trace_t trace; - - trap_CM_BoxTrace( &trace, end, start, NULL, NULL, 0, CONTENTS_WATER ); - CG_BubbleTrail( start, trace.endpos, 32 ); - } else if ( destContentType & CONTENTS_WATER ) { - trace_t trace; - - trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, CONTENTS_WATER ); - CG_BubbleTrail( tr.endpos, trace.endpos, 32 ); - } - - if ( tr.surfaceFlags & SURF_NOIMPACT ) { - return; - } - - if ( cg_entities[tr.entityNum].currentState.eType == ET_PLAYER ) { - CG_MissileHitPlayer( WP_SHOTGUN, tr.endpos, tr.plane.normal, tr.entityNum ); - } else { - if ( tr.surfaceFlags & SURF_NOIMPACT ) { - // SURF_NOIMPACT will not make a flame puff or a mark - return; - } - if ( tr.surfaceFlags & SURF_METALSTEPS ) { - CG_MissileHitWall( WP_SHOTGUN, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_METAL ); - } else { - CG_MissileHitWall( WP_SHOTGUN, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_DEFAULT ); - } - } -} - -/* -================ -CG_ShotgunPattern - -Perform the same traces the server did to locate the -hit splashes -================ -*/ -static void CG_ShotgunPattern( vec3_t origin, vec3_t origin2, int seed, int otherEntNum ) { - int i; - float r, u; - vec3_t end; - vec3_t forward, right, up; - - // derive the right and up vectors from the forward vector, because - // the client won't have any other information - VectorNormalize2( origin2, forward ); - PerpendicularVector( right, forward ); - CrossProduct( forward, right, up ); - - // generate the "random" spread pattern - for ( i = 0 ; i < DEFAULT_SHOTGUN_COUNT ; i++ ) { - r = Q_crandom( &seed ) * DEFAULT_SHOTGUN_SPREAD * 16; - u = Q_crandom( &seed ) * DEFAULT_SHOTGUN_SPREAD * 16; - VectorMA( origin, 8192 * 16, forward, end); - VectorMA (end, r, right, end); - VectorMA (end, u, up, end); - - CG_ShotgunPellet( origin, end, otherEntNum ); - } -} - -/* -============== -CG_ShotgunFire -============== -*/ -void CG_ShotgunFire( entityState_t *es ) { - vec3_t v; - int contents; - - VectorSubtract( es->origin2, es->pos.trBase, v ); - VectorNormalize( v ); - VectorScale( v, 32, v ); - VectorAdd( es->pos.trBase, v, v ); - if ( cgs.glconfig.hardwareType != GLHW_RAGEPRO ) { - // ragepro can't alpha fade, so don't even bother with smoke - vec3_t up; - - contents = trap_CM_PointContents( es->pos.trBase, 0 ); - if ( !( contents & CONTENTS_WATER ) ) { - VectorSet( up, 0, 0, 8 ); - CG_SmokePuff( v, up, 32, 1, 1, 1, 0.33f, 900, cg.time, 0, LEF_PUFF_DONT_SCALE, cgs.media.shotgunSmokePuffShader ); - } - } - CG_ShotgunPattern( es->pos.trBase, es->origin2, es->eventParm, es->otherEntityNum ); -} - -/* -============================================================================ - -BULLETS - -============================================================================ -*/ - - -/* -=============== -CG_Tracer -=============== -*/ -void CG_Tracer( vec3_t source, vec3_t dest ) { - vec3_t forward, right; - polyVert_t verts[4]; - vec3_t line; - float len, begin, end; - vec3_t start, finish; - vec3_t midpoint; - - // tracer - VectorSubtract( dest, source, forward ); - len = VectorNormalize( forward ); - - // start at least a little ways from the muzzle - if ( len < 100 ) { - return; - } - begin = 50 + random() * (len - 60); - end = begin + cg_tracerLength.value; - if ( end > len ) { - end = len; - } - VectorMA( source, begin, forward, start ); - VectorMA( source, end, forward, finish ); - - line[0] = DotProduct( forward, cg.refdef.viewaxis[1] ); - line[1] = DotProduct( forward, cg.refdef.viewaxis[2] ); - - VectorScale( cg.refdef.viewaxis[1], line[1], right ); - VectorMA( right, -line[0], cg.refdef.viewaxis[2], right ); - VectorNormalize( right ); - - VectorMA( finish, cg_tracerWidth.value, right, verts[0].xyz ); - verts[0].st[0] = 0; - verts[0].st[1] = 1; - verts[0].modulate[0] = 255; - verts[0].modulate[1] = 255; - verts[0].modulate[2] = 255; - verts[0].modulate[3] = 255; - - VectorMA( finish, -cg_tracerWidth.value, right, verts[1].xyz ); - verts[1].st[0] = 1; - verts[1].st[1] = 0; - verts[1].modulate[0] = 255; - verts[1].modulate[1] = 255; - verts[1].modulate[2] = 255; - verts[1].modulate[3] = 255; - - VectorMA( start, -cg_tracerWidth.value, right, verts[2].xyz ); - verts[2].st[0] = 1; - verts[2].st[1] = 1; - verts[2].modulate[0] = 255; - verts[2].modulate[1] = 255; - verts[2].modulate[2] = 255; - verts[2].modulate[3] = 255; - - VectorMA( start, cg_tracerWidth.value, right, verts[3].xyz ); - verts[3].st[0] = 0; - verts[3].st[1] = 0; - verts[3].modulate[0] = 255; - verts[3].modulate[1] = 255; - verts[3].modulate[2] = 255; - verts[3].modulate[3] = 255; - - trap_R_AddPolyToScene( cgs.media.tracerShader, 4, verts ); - - midpoint[0] = ( start[0] + finish[0] ) * 0.5; - midpoint[1] = ( start[1] + finish[1] ) * 0.5; - midpoint[2] = ( start[2] + finish[2] ) * 0.5; - - // add the tracer sound - trap_S_StartSound( midpoint, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.tracerSound ); - -} - - -/* -====================== -CG_CalcMuzzlePoint -====================== -*/ -static qboolean CG_CalcMuzzlePoint( int entityNum, vec3_t muzzle ) { - vec3_t forward; - centity_t *cent; - int anim; - - if ( entityNum == cg.snap->ps.clientNum ) { - VectorCopy( cg.snap->ps.origin, muzzle ); - muzzle[2] += cg.snap->ps.viewheight; - AngleVectors( cg.snap->ps.viewangles, forward, NULL, NULL ); - VectorMA( muzzle, 14, forward, muzzle ); - return qtrue; - } - - cent = &cg_entities[entityNum]; - if ( !cent->currentValid ) { - return qfalse; - } - - VectorCopy( cent->currentState.pos.trBase, muzzle ); - - AngleVectors( cent->currentState.apos.trBase, forward, NULL, NULL ); - anim = cent->currentState.legsAnim & ~ANIM_TOGGLEBIT; - if ( anim == LEGS_WALKCR || anim == LEGS_IDLECR ) { - muzzle[2] += CROUCH_VIEWHEIGHT; - } else { - muzzle[2] += DEFAULT_VIEWHEIGHT; - } - - VectorMA( muzzle, 14, forward, muzzle ); - - return qtrue; - -} - -/* -====================== -CG_Bullet - -Renders bullet effects. -====================== -*/ -void CG_Bullet( vec3_t end, int sourceEntityNum, vec3_t normal, qboolean flesh, int fleshEntityNum ) { - trace_t trace; - int sourceContentType, destContentType; - vec3_t start; - - // if the shooter is currently valid, calc a source point and possibly - // do trail effects - if ( sourceEntityNum >= 0 && cg_tracerChance.value > 0 ) { - if ( CG_CalcMuzzlePoint( sourceEntityNum, start ) ) { - sourceContentType = trap_CM_PointContents( start, 0 ); - destContentType = trap_CM_PointContents( end, 0 ); - - // do a complete bubble trail if necessary - if ( ( sourceContentType == destContentType ) && ( sourceContentType & CONTENTS_WATER ) ) { - CG_BubbleTrail( start, end, 32 ); - } - // bubble trail from water into air - else if ( ( sourceContentType & CONTENTS_WATER ) ) { - trap_CM_BoxTrace( &trace, end, start, NULL, NULL, 0, CONTENTS_WATER ); - CG_BubbleTrail( start, trace.endpos, 32 ); - } - // bubble trail from air into water - else if ( ( destContentType & CONTENTS_WATER ) ) { - trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, CONTENTS_WATER ); - CG_BubbleTrail( trace.endpos, end, 32 ); - } - - // draw a tracer - if ( random() < cg_tracerChance.value ) { - CG_Tracer( start, end ); - } - } - } - - // impact splash and mark - if ( flesh ) { - CG_Bleed( end, fleshEntityNum ); - } else { - CG_MissileHitWall( WP_MACHINEGUN, 0, end, normal, IMPACTSOUND_DEFAULT ); - } - -} +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// cg_weapons.c -- events and effects dealing with weapons +#include "cg_local.h" + +/* +========================== +CG_MachineGunEjectBrass +========================== +*/ +static void CG_MachineGunEjectBrass( centity_t *cent ) { + localEntity_t *le; + refEntity_t *re; + vec3_t velocity, xvelocity; + vec3_t offset, xoffset; + float waterScale = 1.0f; + vec3_t v[3]; + + if ( cg_brassTime.integer <= 0 ) { + return; + } + + le = CG_AllocLocalEntity(); + re = &le->refEntity; + + velocity[0] = 0; + velocity[1] = -50 + 40 * crandom(); + velocity[2] = 100 + 50 * crandom(); + + le->leType = LE_FRAGMENT; + le->startTime = cg.time; + le->endTime = le->startTime + cg_brassTime.integer + ( cg_brassTime.integer / 4 ) * random(); + + le->pos.trType = TR_GRAVITY; + le->pos.trTime = cg.time - (rand()&15); + + AnglesToAxis( cent->lerpAngles, v ); + + offset[0] = 8; + offset[1] = -4; + offset[2] = 24; + + xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0]; + xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1]; + xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2]; + VectorAdd( cent->lerpOrigin, xoffset, re->origin ); + + VectorCopy( re->origin, le->pos.trBase ); + + if ( CG_PointContents( re->origin, -1 ) & CONTENTS_WATER ) { + waterScale = 0.10f; + } + + xvelocity[0] = velocity[0] * v[0][0] + velocity[1] * v[1][0] + velocity[2] * v[2][0]; + xvelocity[1] = velocity[0] * v[0][1] + velocity[1] * v[1][1] + velocity[2] * v[2][1]; + xvelocity[2] = velocity[0] * v[0][2] + velocity[1] * v[1][2] + velocity[2] * v[2][2]; + VectorScale( xvelocity, waterScale, le->pos.trDelta ); + + AxisCopy( axisDefault, re->axis ); + re->hModel = cgs.media.machinegunBrassModel; + + le->bounceFactor = 0.4 * waterScale; + + le->angles.trType = TR_LINEAR; + le->angles.trTime = cg.time; + le->angles.trBase[0] = rand()&31; + le->angles.trBase[1] = rand()&31; + le->angles.trBase[2] = rand()&31; + le->angles.trDelta[0] = 2; + le->angles.trDelta[1] = 1; + le->angles.trDelta[2] = 0; + + le->leFlags = LEF_TUMBLE; + le->leBounceSoundType = LEBS_BRASS; + le->leMarkType = LEMT_NONE; +} + +/* +========================== +CG_ShotgunEjectBrass +========================== +*/ +static void CG_ShotgunEjectBrass( centity_t *cent ) { + localEntity_t *le; + refEntity_t *re; + vec3_t velocity, xvelocity; + vec3_t offset, xoffset; + vec3_t v[3]; + int i; + + if ( cg_brassTime.integer <= 0 ) { + return; + } + + for ( i = 0; i < 2; i++ ) { + float waterScale = 1.0f; + + le = CG_AllocLocalEntity(); + re = &le->refEntity; + + velocity[0] = 60 + 60 * crandom(); + if ( i == 0 ) { + velocity[1] = 40 + 10 * crandom(); + } else { + velocity[1] = -40 + 10 * crandom(); + } + velocity[2] = 100 + 50 * crandom(); + + le->leType = LE_FRAGMENT; + le->startTime = cg.time; + le->endTime = le->startTime + cg_brassTime.integer*3 + cg_brassTime.integer * random(); + + le->pos.trType = TR_GRAVITY; + le->pos.trTime = cg.time; + + AnglesToAxis( cent->lerpAngles, v ); + + offset[0] = 8; + offset[1] = 0; + offset[2] = 24; + + xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0]; + xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1]; + xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2]; + VectorAdd( cent->lerpOrigin, xoffset, re->origin ); + VectorCopy( re->origin, le->pos.trBase ); + if ( CG_PointContents( re->origin, -1 ) & CONTENTS_WATER ) { + waterScale = 0.10f; + } + + xvelocity[0] = velocity[0] * v[0][0] + velocity[1] * v[1][0] + velocity[2] * v[2][0]; + xvelocity[1] = velocity[0] * v[0][1] + velocity[1] * v[1][1] + velocity[2] * v[2][1]; + xvelocity[2] = velocity[0] * v[0][2] + velocity[1] * v[1][2] + velocity[2] * v[2][2]; + VectorScale( xvelocity, waterScale, le->pos.trDelta ); + + AxisCopy( axisDefault, re->axis ); + re->hModel = cgs.media.shotgunBrassModel; + le->bounceFactor = 0.3f; + + le->angles.trType = TR_LINEAR; + le->angles.trTime = cg.time; + le->angles.trBase[0] = rand()&31; + le->angles.trBase[1] = rand()&31; + le->angles.trBase[2] = rand()&31; + le->angles.trDelta[0] = 1; + le->angles.trDelta[1] = 0.5; + le->angles.trDelta[2] = 0; + + le->leFlags = LEF_TUMBLE; + le->leBounceSoundType = LEBS_BRASS; + le->leMarkType = LEMT_NONE; + } +} + + +#ifdef MISSIONPACK +/* +========================== +CG_NailgunEjectBrass +========================== +*/ +static void CG_NailgunEjectBrass( centity_t *cent ) { + localEntity_t *smoke; + vec3_t origin; + vec3_t v[3]; + vec3_t offset; + vec3_t xoffset; + vec3_t up; + + AnglesToAxis( cent->lerpAngles, v ); + + offset[0] = 0; + offset[1] = -12; + offset[2] = 24; + + xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0]; + xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1]; + xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2]; + VectorAdd( cent->lerpOrigin, xoffset, origin ); + + VectorSet( up, 0, 0, 64 ); + + smoke = CG_SmokePuff( origin, up, 32, 1, 1, 1, 0.33f, 700, cg.time, 0, 0, cgs.media.smokePuffShader ); + // use the optimized local entity add + smoke->leType = LE_SCALE_FADE; +} +#endif + + +/* +========================== +CG_RailTrail +========================== +*/ +void CG_RailTrail (clientInfo_t *ci, vec3_t start, vec3_t end) { + vec3_t axis[36], move, move2, next_move, vec, temp; + float len; + int i, j, skip; + + localEntity_t *le; + refEntity_t *re; + +#define RADIUS 4 +#define ROTATION 1 +#define SPACING 5 + + start[2] -= 4; + VectorCopy (start, move); + VectorSubtract (end, start, vec); + len = VectorNormalize (vec); + PerpendicularVector(temp, vec); + for (i = 0 ; i < 36; i++) { + RotatePointAroundVector(axis[i], vec, temp, i * 10);//banshee 2.4 was 10 + } + + le = CG_AllocLocalEntity(); + re = &le->refEntity; + + le->leType = LE_FADE_RGB; + le->startTime = cg.time; + le->endTime = cg.time + cg_railTrailTime.value; + le->lifeRate = 1.0 / (le->endTime - le->startTime); + + re->shaderTime = cg.time / 1000.0f; + re->reType = RT_RAIL_CORE; + re->customShader = cgs.media.railCoreShader; + + VectorCopy(start, re->origin); + VectorCopy(end, re->oldorigin); + + re->shaderRGBA[0] = ci->color1[0] * 255; + re->shaderRGBA[1] = ci->color1[1] * 255; + re->shaderRGBA[2] = ci->color1[2] * 255; + re->shaderRGBA[3] = 255; + + le->color[0] = ci->color1[0] * 0.75; + le->color[1] = ci->color1[1] * 0.75; + le->color[2] = ci->color1[2] * 0.75; + le->color[3] = 1.0f; + + AxisClear( re->axis ); + + VectorMA(move, 20, vec, move); + VectorCopy(move, next_move); + VectorScale (vec, SPACING, vec); + + if (cg_oldRail.integer != 0) { + // nudge down a bit so it isn't exactly in center + re->origin[2] -= 8; + re->oldorigin[2] -= 8; + return; + } + skip = -1; + + j = 18; + for (i = 0; i < len; i += SPACING) { + if (i != skip) { + skip = i + SPACING; + le = CG_AllocLocalEntity(); + re = &le->refEntity; + le->leFlags = LEF_PUFF_DONT_SCALE; + le->leType = LE_MOVE_SCALE_FADE; + le->startTime = cg.time; + le->endTime = cg.time + (i>>1) + 600; + le->lifeRate = 1.0 / (le->endTime - le->startTime); + + re->shaderTime = cg.time / 1000.0f; + re->reType = RT_SPRITE; + re->radius = 1.1f; + re->customShader = cgs.media.railRingsShader; + + re->shaderRGBA[0] = ci->color2[0] * 255; + re->shaderRGBA[1] = ci->color2[1] * 255; + re->shaderRGBA[2] = ci->color2[2] * 255; + re->shaderRGBA[3] = 255; + + le->color[0] = ci->color2[0] * 0.75; + le->color[1] = ci->color2[1] * 0.75; + le->color[2] = ci->color2[2] * 0.75; + le->color[3] = 1.0f; + + le->pos.trType = TR_LINEAR; + le->pos.trTime = cg.time; + + VectorCopy( move, move2); + VectorMA(move2, RADIUS , axis[j], move2); + VectorCopy(move2, le->pos.trBase); + + le->pos.trDelta[0] = axis[j][0]*6; + le->pos.trDelta[1] = axis[j][1]*6; + le->pos.trDelta[2] = axis[j][2]*6; + } + + VectorAdd (move, vec, move); + + j = j + ROTATION < 36 ? j + ROTATION : (j + ROTATION) % 36; + } +} + +/* +========================== +CG_RocketTrail +========================== +*/ +static void CG_RocketTrail( centity_t *ent, const weaponInfo_t *wi ) { + int step; + vec3_t origin, lastPos; + int t; + int startTime, contents; + int lastContents; + entityState_t *es; + vec3_t up; + localEntity_t *smoke; + + if ( cg_noProjectileTrail.integer ) { + return; + } + + up[0] = 0; + up[1] = 0; + up[2] = 0; + + step = 50; + + es = &ent->currentState; + startTime = ent->trailTime; + t = step * ( (startTime + step) / step ); + + BG_EvaluateTrajectory( &es->pos, cg.time, origin ); + contents = CG_PointContents( origin, -1 ); + + // if object (e.g. grenade) is stationary, don't toss up smoke + if ( es->pos.trType == TR_STATIONARY ) { + ent->trailTime = cg.time; + return; + } + + BG_EvaluateTrajectory( &es->pos, ent->trailTime, lastPos ); + lastContents = CG_PointContents( lastPos, -1 ); + + ent->trailTime = cg.time; + + if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { + if ( contents & lastContents & CONTENTS_WATER ) { + CG_BubbleTrail( lastPos, origin, 8 ); + } + return; + } + + for ( ; t <= ent->trailTime ; t += step ) { + BG_EvaluateTrajectory( &es->pos, t, lastPos ); + + smoke = CG_SmokePuff( lastPos, up, + wi->trailRadius, + 1, 1, 1, 0.33f, + wi->wiTrailTime, + t, + 0, + 0, + cgs.media.smokePuffShader ); + // use the optimized local entity add + smoke->leType = LE_SCALE_FADE; + } + +} + +#ifdef MISSIONPACK +/* +========================== +CG_NailTrail +========================== +*/ +static void CG_NailTrail( centity_t *ent, const weaponInfo_t *wi ) { + int step; + vec3_t origin, lastPos; + int t; + int startTime, contents; + int lastContents; + entityState_t *es; + vec3_t up; + localEntity_t *smoke; + + if ( cg_noProjectileTrail.integer ) { + return; + } + + up[0] = 0; + up[1] = 0; + up[2] = 0; + + step = 50; + + es = &ent->currentState; + startTime = ent->trailTime; + t = step * ( (startTime + step) / step ); + + BG_EvaluateTrajectory( &es->pos, cg.time, origin ); + contents = CG_PointContents( origin, -1 ); + + // if object (e.g. grenade) is stationary, don't toss up smoke + if ( es->pos.trType == TR_STATIONARY ) { + ent->trailTime = cg.time; + return; + } + + BG_EvaluateTrajectory( &es->pos, ent->trailTime, lastPos ); + lastContents = CG_PointContents( lastPos, -1 ); + + ent->trailTime = cg.time; + + if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { + if ( contents & lastContents & CONTENTS_WATER ) { + CG_BubbleTrail( lastPos, origin, 8 ); + } + return; + } + + for ( ; t <= ent->trailTime ; t += step ) { + BG_EvaluateTrajectory( &es->pos, t, lastPos ); + + smoke = CG_SmokePuff( lastPos, up, + wi->trailRadius, + 1, 1, 1, 0.33f, + wi->wiTrailTime, + t, + 0, + 0, + cgs.media.nailPuffShader ); + // use the optimized local entity add + smoke->leType = LE_SCALE_FADE; + } + +} +#endif + +/* +========================== +CG_NailTrail +========================== +*/ +static void CG_PlasmaTrail( centity_t *cent, const weaponInfo_t *wi ) { + localEntity_t *le; + refEntity_t *re; + entityState_t *es; + vec3_t velocity, xvelocity, origin; + vec3_t offset, xoffset; + vec3_t v[3]; + int t, startTime, step; + + float waterScale = 1.0f; + + if ( cg_noProjectileTrail.integer || cg_oldPlasma.integer ) { + return; + } + + step = 50; + + es = ¢->currentState; + startTime = cent->trailTime; + t = step * ( (startTime + step) / step ); + + BG_EvaluateTrajectory( &es->pos, cg.time, origin ); + + le = CG_AllocLocalEntity(); + re = &le->refEntity; + + velocity[0] = 60 - 120 * crandom(); + velocity[1] = 40 - 80 * crandom(); + velocity[2] = 100 - 200 * crandom(); + + le->leType = LE_MOVE_SCALE_FADE; + le->leFlags = LEF_TUMBLE; + le->leBounceSoundType = LEBS_NONE; + le->leMarkType = LEMT_NONE; + + le->startTime = cg.time; + le->endTime = le->startTime + 600; + + le->pos.trType = TR_GRAVITY; + le->pos.trTime = cg.time; + + AnglesToAxis( cent->lerpAngles, v ); + + offset[0] = 2; + offset[1] = 2; + offset[2] = 2; + + xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0]; + xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1]; + xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2]; + + VectorAdd( origin, xoffset, re->origin ); + VectorCopy( re->origin, le->pos.trBase ); + + if ( CG_PointContents( re->origin, -1 ) & CONTENTS_WATER ) { + waterScale = 0.10f; + } + + xvelocity[0] = velocity[0] * v[0][0] + velocity[1] * v[1][0] + velocity[2] * v[2][0]; + xvelocity[1] = velocity[0] * v[0][1] + velocity[1] * v[1][1] + velocity[2] * v[2][1]; + xvelocity[2] = velocity[0] * v[0][2] + velocity[1] * v[1][2] + velocity[2] * v[2][2]; + VectorScale( xvelocity, waterScale, le->pos.trDelta ); + + AxisCopy( axisDefault, re->axis ); + re->shaderTime = cg.time / 1000.0f; + re->reType = RT_SPRITE; + re->radius = 0.25f; + re->customShader = cgs.media.railRingsShader; + le->bounceFactor = 0.3f; + + re->shaderRGBA[0] = wi->flashDlightColor[0] * 63; + re->shaderRGBA[1] = wi->flashDlightColor[1] * 63; + re->shaderRGBA[2] = wi->flashDlightColor[2] * 63; + re->shaderRGBA[3] = 63; + + le->color[0] = wi->flashDlightColor[0] * 0.2; + le->color[1] = wi->flashDlightColor[1] * 0.2; + le->color[2] = wi->flashDlightColor[2] * 0.2; + le->color[3] = 0.25f; + + le->angles.trType = TR_LINEAR; + le->angles.trTime = cg.time; + le->angles.trBase[0] = rand()&31; + le->angles.trBase[1] = rand()&31; + le->angles.trBase[2] = rand()&31; + le->angles.trDelta[0] = 1; + le->angles.trDelta[1] = 0.5; + le->angles.trDelta[2] = 0; + +} +/* +========================== +CG_GrappleTrail +========================== +*/ +void CG_GrappleTrail( centity_t *ent, const weaponInfo_t *wi ) { + vec3_t origin; + entityState_t *es; + vec3_t forward, up; + refEntity_t beam; + + es = &ent->currentState; + + BG_EvaluateTrajectory( &es->pos, cg.time, origin ); + ent->trailTime = cg.time; + + memset( &beam, 0, sizeof( beam ) ); + //FIXME adjust for muzzle position + VectorCopy ( cg_entities[ ent->currentState.otherEntityNum ].lerpOrigin, beam.origin ); + beam.origin[2] += 26; + AngleVectors( cg_entities[ ent->currentState.otherEntityNum ].lerpAngles, forward, NULL, up ); + VectorMA( beam.origin, -6, up, beam.origin ); + VectorCopy( origin, beam.oldorigin ); + + if (Distance( beam.origin, beam.oldorigin ) < 64 ) + return; // Don't draw if close + + beam.reType = RT_LIGHTNING; + beam.customShader = cgs.media.lightningShader; + + AxisClear( beam.axis ); + beam.shaderRGBA[0] = 0xff; + beam.shaderRGBA[1] = 0xff; + beam.shaderRGBA[2] = 0xff; + beam.shaderRGBA[3] = 0xff; + trap_R_AddRefEntityToScene( &beam ); +} + +/* +========================== +CG_GrenadeTrail +========================== +*/ +static void CG_GrenadeTrail( centity_t *ent, const weaponInfo_t *wi ) { + CG_RocketTrail( ent, wi ); +} + + +/* +================= +CG_RegisterWeapon + +The server says this item is used on this level +================= +*/ +void CG_RegisterWeapon( int weaponNum ) { + weaponInfo_t *weaponInfo; + gitem_t *item, *ammo; + char path[MAX_QPATH]; + vec3_t mins, maxs; + int i; + + weaponInfo = &cg_weapons[weaponNum]; + + if ( weaponNum == 0 ) { + return; + } + + if ( weaponInfo->registered ) { + return; + } + + memset( weaponInfo, 0, sizeof( *weaponInfo ) ); + weaponInfo->registered = qtrue; + + for ( item = bg_itemlist + 1 ; item->classname ; item++ ) { + if ( item->giType == IT_WEAPON && item->giTag == weaponNum ) { + weaponInfo->item = item; + break; + } + } + if ( !item->classname ) { + CG_Error( "Couldn't find weapon %i", weaponNum ); + } + CG_RegisterItemVisuals( item - bg_itemlist ); + + // load cmodel before model so filecache works + weaponInfo->weaponModel = trap_R_RegisterModel( item->world_model[0] ); + + // calc midpoint for rotation + trap_R_ModelBounds( weaponInfo->weaponModel, mins, maxs ); + for ( i = 0 ; i < 3 ; i++ ) { + weaponInfo->weaponMidpoint[i] = mins[i] + 0.5 * ( maxs[i] - mins[i] ); + } + + weaponInfo->weaponIcon = trap_R_RegisterShader( item->icon ); + weaponInfo->ammoIcon = trap_R_RegisterShader( item->icon ); + + for ( ammo = bg_itemlist + 1 ; ammo->classname ; ammo++ ) { + if ( ammo->giType == IT_AMMO && ammo->giTag == weaponNum ) { + break; + } + } + if ( ammo->classname && ammo->world_model[0] ) { + weaponInfo->ammoModel = trap_R_RegisterModel( ammo->world_model[0] ); + } + + strcpy( path, item->world_model[0] ); + COM_StripExtension( path, path ); + strcat( path, "_flash.md3" ); + weaponInfo->flashModel = trap_R_RegisterModel( path ); + + strcpy( path, item->world_model[0] ); + COM_StripExtension( path, path ); + strcat( path, "_barrel.md3" ); + weaponInfo->barrelModel = trap_R_RegisterModel( path ); + + strcpy( path, item->world_model[0] ); + COM_StripExtension( path, path ); + strcat( path, "_hand.md3" ); + weaponInfo->handsModel = trap_R_RegisterModel( path ); + + if ( !weaponInfo->handsModel ) { + weaponInfo->handsModel = trap_R_RegisterModel( "models/weapons2/shotgun/shotgun_hand.md3" ); + } + + weaponInfo->loopFireSound = qfalse; + + switch ( weaponNum ) { + case WP_GAUNTLET: + MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f ); + weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/melee/fstrun.wav", qfalse ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/melee/fstatck.wav", qfalse ); + break; + + case WP_LIGHTNING: + MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f ); + weaponInfo->readySound = trap_S_RegisterSound( "sound/weapons/melee/fsthum.wav", qfalse ); + weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/lightning/lg_hum.wav", qfalse ); + + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/lightning/lg_fire.wav", qfalse ); + cgs.media.lightningShader = trap_R_RegisterShader( "lightningBoltNew"); + cgs.media.lightningExplosionModel = trap_R_RegisterModel( "models/weaphits/crackle.md3" ); + cgs.media.sfx_lghit1 = trap_S_RegisterSound( "sound/weapons/lightning/lg_hit.wav", qfalse ); + cgs.media.sfx_lghit2 = trap_S_RegisterSound( "sound/weapons/lightning/lg_hit2.wav", qfalse ); + cgs.media.sfx_lghit3 = trap_S_RegisterSound( "sound/weapons/lightning/lg_hit3.wav", qfalse ); + + break; + + case WP_GRAPPLING_HOOK: + MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f ); + weaponInfo->missileModel = trap_R_RegisterModel( "models/ammo/rocket/rocket.md3" ); + weaponInfo->missileTrailFunc = CG_GrappleTrail; + weaponInfo->missileDlight = 200; + weaponInfo->wiTrailTime = 2000; + weaponInfo->trailRadius = 64; + MAKERGB( weaponInfo->missileDlightColor, 1, 0.75f, 0 ); + weaponInfo->readySound = trap_S_RegisterSound( "sound/weapons/melee/fsthum.wav", qfalse ); + weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/melee/fstrun.wav", qfalse ); + break; + +#ifdef MISSIONPACK + case WP_CHAINGUN: + weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/vulcan/wvulfire.wav", qfalse ); + weaponInfo->loopFireSound = qtrue; + MAKERGB( weaponInfo->flashDlightColor, 1, 1, 0 ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/vulcan/vulcanf1b.wav", qfalse ); + weaponInfo->flashSound[1] = trap_S_RegisterSound( "sound/weapons/vulcan/vulcanf2b.wav", qfalse ); + weaponInfo->flashSound[2] = trap_S_RegisterSound( "sound/weapons/vulcan/vulcanf3b.wav", qfalse ); + weaponInfo->flashSound[3] = trap_S_RegisterSound( "sound/weapons/vulcan/vulcanf4b.wav", qfalse ); + weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass; + cgs.media.bulletExplosionShader = trap_R_RegisterShader( "bulletExplosion" ); + break; +#endif + + case WP_MACHINEGUN: + MAKERGB( weaponInfo->flashDlightColor, 1, 1, 0 ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/machinegun/machgf1b.wav", qfalse ); + weaponInfo->flashSound[1] = trap_S_RegisterSound( "sound/weapons/machinegun/machgf2b.wav", qfalse ); + weaponInfo->flashSound[2] = trap_S_RegisterSound( "sound/weapons/machinegun/machgf3b.wav", qfalse ); + weaponInfo->flashSound[3] = trap_S_RegisterSound( "sound/weapons/machinegun/machgf4b.wav", qfalse ); + weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass; + cgs.media.bulletExplosionShader = trap_R_RegisterShader( "bulletExplosion" ); + break; + + case WP_SHOTGUN: + MAKERGB( weaponInfo->flashDlightColor, 1, 1, 0 ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/shotgun/sshotf1b.wav", qfalse ); + weaponInfo->ejectBrassFunc = CG_ShotgunEjectBrass; + break; + + case WP_ROCKET_LAUNCHER: + weaponInfo->missileModel = trap_R_RegisterModel( "models/ammo/rocket/rocket.md3" ); + weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/rocket/rockfly.wav", qfalse ); + weaponInfo->missileTrailFunc = CG_RocketTrail; + weaponInfo->missileDlight = 200; + weaponInfo->wiTrailTime = 2000; + weaponInfo->trailRadius = 64; + + MAKERGB( weaponInfo->missileDlightColor, 1, 0.75f, 0 ); + MAKERGB( weaponInfo->flashDlightColor, 1, 0.75f, 0 ); + + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/rocket/rocklf1a.wav", qfalse ); + cgs.media.rocketExplosionShader = trap_R_RegisterShader( "rocketExplosion" ); + break; + +#ifdef MISSIONPACK + case WP_PROX_LAUNCHER: + weaponInfo->missileModel = trap_R_RegisterModel( "models/weaphits/proxmine.md3" ); + weaponInfo->missileTrailFunc = CG_GrenadeTrail; + weaponInfo->wiTrailTime = 700; + weaponInfo->trailRadius = 32; + MAKERGB( weaponInfo->flashDlightColor, 1, 0.70f, 0 ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/proxmine/wstbfire.wav", qfalse ); + cgs.media.grenadeExplosionShader = trap_R_RegisterShader( "grenadeExplosion" ); + break; +#endif + + case WP_GRENADE_LAUNCHER: + weaponInfo->missileModel = trap_R_RegisterModel( "models/ammo/grenade1.md3" ); + weaponInfo->missileTrailFunc = CG_GrenadeTrail; + weaponInfo->wiTrailTime = 700; + weaponInfo->trailRadius = 32; + MAKERGB( weaponInfo->flashDlightColor, 1, 0.70f, 0 ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/grenade/grenlf1a.wav", qfalse ); + cgs.media.grenadeExplosionShader = trap_R_RegisterShader( "grenadeExplosion" ); + break; + +#ifdef MISSIONPACK + case WP_NAILGUN: + weaponInfo->ejectBrassFunc = CG_NailgunEjectBrass; + weaponInfo->missileTrailFunc = CG_NailTrail; +// weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/nailgun/wnalflit.wav", qfalse ); + weaponInfo->trailRadius = 16; + weaponInfo->wiTrailTime = 250; + weaponInfo->missileModel = trap_R_RegisterModel( "models/weaphits/nail.md3" ); + MAKERGB( weaponInfo->flashDlightColor, 1, 0.75f, 0 ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/nailgun/wnalfire.wav", qfalse ); + break; +#endif + + case WP_PLASMAGUN: +// weaponInfo->missileModel = cgs.media.invulnerabilityPowerupModel; + weaponInfo->missileTrailFunc = CG_PlasmaTrail; + weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/plasma/lasfly.wav", qfalse ); + MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/plasma/hyprbf1a.wav", qfalse ); + cgs.media.plasmaExplosionShader = trap_R_RegisterShader( "plasmaExplosion" ); + cgs.media.railRingsShader = trap_R_RegisterShader( "railDisc" ); + break; + + case WP_RAILGUN: + weaponInfo->readySound = trap_S_RegisterSound( "sound/weapons/railgun/rg_hum.wav", qfalse ); + MAKERGB( weaponInfo->flashDlightColor, 1, 0.5f, 0 ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/railgun/railgf1a.wav", qfalse ); + cgs.media.railExplosionShader = trap_R_RegisterShader( "railExplosion" ); + cgs.media.railRingsShader = trap_R_RegisterShader( "railDisc" ); + cgs.media.railCoreShader = trap_R_RegisterShader( "railCore" ); + break; + + case WP_BFG: + weaponInfo->readySound = trap_S_RegisterSound( "sound/weapons/bfg/bfg_hum.wav", qfalse ); + MAKERGB( weaponInfo->flashDlightColor, 1, 0.7f, 1 ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/bfg/bfg_fire.wav", qfalse ); + cgs.media.bfgExplosionShader = trap_R_RegisterShader( "bfgExplosion" ); + weaponInfo->missileModel = trap_R_RegisterModel( "models/weaphits/bfg.md3" ); + weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/rocket/rockfly.wav", qfalse ); + break; + + default: + MAKERGB( weaponInfo->flashDlightColor, 1, 1, 1 ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/rocket/rocklf1a.wav", qfalse ); + break; + } +} + +/* +================= +CG_RegisterItemVisuals + +The server says this item is used on this level +================= +*/ +void CG_RegisterItemVisuals( int itemNum ) { + itemInfo_t *itemInfo; + gitem_t *item; + + if ( itemNum < 0 || itemNum >= bg_numItems ) { + CG_Error( "CG_RegisterItemVisuals: itemNum %d out of range [0-%d]", itemNum, bg_numItems-1 ); + } + + itemInfo = &cg_items[ itemNum ]; + if ( itemInfo->registered ) { + return; + } + + item = &bg_itemlist[ itemNum ]; + + memset( itemInfo, 0, sizeof( &itemInfo ) ); + itemInfo->registered = qtrue; + + itemInfo->models[0] = trap_R_RegisterModel( item->world_model[0] ); + + itemInfo->icon = trap_R_RegisterShader( item->icon ); + + if ( item->giType == IT_WEAPON ) { + CG_RegisterWeapon( item->giTag ); + } + + // + // powerups have an accompanying ring or sphere + // + if ( item->giType == IT_POWERUP || item->giType == IT_HEALTH || + item->giType == IT_ARMOR || item->giType == IT_HOLDABLE ) { + if ( item->world_model[1] ) { + itemInfo->models[1] = trap_R_RegisterModel( item->world_model[1] ); + } + } +} + + +/* +======================================================================================== + +VIEW WEAPON + +======================================================================================== +*/ + +/* +================= +CG_MapTorsoToWeaponFrame + +================= +*/ +static int CG_MapTorsoToWeaponFrame( clientInfo_t *ci, int frame ) { + + // change weapon + if ( frame >= ci->animations[TORSO_DROP].firstFrame + && frame < ci->animations[TORSO_DROP].firstFrame + 9 ) { + return frame - ci->animations[TORSO_DROP].firstFrame + 6; + } + + // stand attack + if ( frame >= ci->animations[TORSO_ATTACK].firstFrame + && frame < ci->animations[TORSO_ATTACK].firstFrame + 6 ) { + return 1 + frame - ci->animations[TORSO_ATTACK].firstFrame; + } + + // stand attack 2 + if ( frame >= ci->animations[TORSO_ATTACK2].firstFrame + && frame < ci->animations[TORSO_ATTACK2].firstFrame + 6 ) { + return 1 + frame - ci->animations[TORSO_ATTACK2].firstFrame; + } + + return 0; +} + + +/* +============== +CG_CalculateWeaponPosition +============== +*/ +static void CG_CalculateWeaponPosition( vec3_t origin, vec3_t angles ) { + float scale; + int delta; + float fracsin; + + VectorCopy( cg.refdef.vieworg, origin ); + VectorCopy( cg.refdefViewAngles, angles ); + + // on odd legs, invert some angles + if ( cg.bobcycle & 1 ) { + scale = -cg.xyspeed; + } else { + scale = cg.xyspeed; + } + + // gun angles from bobbing + angles[ROLL] += scale * cg.bobfracsin * 0.005; + angles[YAW] += scale * cg.bobfracsin * 0.01; + angles[PITCH] += cg.xyspeed * cg.bobfracsin * 0.005; + + // drop the weapon when landing + delta = cg.time - cg.landTime; + if ( delta < LAND_DEFLECT_TIME ) { + origin[2] += cg.landChange*0.25 * delta / LAND_DEFLECT_TIME; + } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) { + origin[2] += cg.landChange*0.25 * + (LAND_DEFLECT_TIME + LAND_RETURN_TIME - delta) / LAND_RETURN_TIME; + } + +#if 0 + // drop the weapon when stair climbing + delta = cg.time - cg.stepTime; + if ( delta < STEP_TIME/2 ) { + origin[2] -= cg.stepChange*0.25 * delta / (STEP_TIME/2); + } else if ( delta < STEP_TIME ) { + origin[2] -= cg.stepChange*0.25 * (STEP_TIME - delta) / (STEP_TIME/2); + } +#endif + + // idle drift + scale = cg.xyspeed + 40; + fracsin = sin( cg.time * 0.001 ); + angles[ROLL] += scale * fracsin * 0.01; + angles[YAW] += scale * fracsin * 0.01; + angles[PITCH] += scale * fracsin * 0.01; +} + + +/* +=============== +CG_LightningBolt + +Origin will be the exact tag point, which is slightly +different than the muzzle point used for determining hits. +The cent should be the non-predicted cent if it is from the player, +so the endpoint will reflect the simulated strike (lagging the predicted +angle) +=============== +*/ +static void CG_LightningBolt( centity_t *cent, vec3_t origin ) { + trace_t trace; + refEntity_t beam; + vec3_t forward; + vec3_t muzzlePoint, endPoint; + + if (cent->currentState.weapon != WP_LIGHTNING) { + return; + } + + memset( &beam, 0, sizeof( beam ) ); + + // CPMA "true" lightning + if ((cent->currentState.number == cg.predictedPlayerState.clientNum) && (cg_trueLightning.value != 0)) { + vec3_t angle; + int i; + + for (i = 0; i < 3; i++) { + float a = cent->lerpAngles[i] - cg.refdefViewAngles[i]; + if (a > 180) { + a -= 360; + } + if (a < -180) { + a += 360; + } + + angle[i] = cg.refdefViewAngles[i] + a * (1.0 - cg_trueLightning.value); + if (angle[i] < 0) { + angle[i] += 360; + } + if (angle[i] > 360) { + angle[i] -= 360; + } + } + + AngleVectors(angle, forward, NULL, NULL ); + VectorCopy(cent->lerpOrigin, muzzlePoint ); +// VectorCopy(cg.refdef.vieworg, muzzlePoint ); + } else { + // !CPMA + AngleVectors( cent->lerpAngles, forward, NULL, NULL ); + VectorCopy(cent->lerpOrigin, muzzlePoint ); + } + + // FIXME: crouch + muzzlePoint[2] += DEFAULT_VIEWHEIGHT; + + VectorMA( muzzlePoint, 14, forward, muzzlePoint ); + + // project forward by the lightning range + VectorMA( muzzlePoint, LIGHTNING_RANGE, forward, endPoint ); + + // see if it hit a wall + CG_Trace( &trace, muzzlePoint, vec3_origin, vec3_origin, endPoint, + cent->currentState.number, MASK_SHOT ); + + // this is the endpoint + VectorCopy( trace.endpos, beam.oldorigin ); + + // use the provided origin, even though it may be slightly + // different than the muzzle origin + VectorCopy( origin, beam.origin ); + + beam.reType = RT_LIGHTNING; + beam.customShader = cgs.media.lightningShader; + trap_R_AddRefEntityToScene( &beam ); + + // add the impact flare if it hit something + if ( trace.fraction < 1.0 ) { + vec3_t angles; + vec3_t dir; + + VectorSubtract( beam.oldorigin, beam.origin, dir ); + VectorNormalize( dir ); + + memset( &beam, 0, sizeof( beam ) ); + beam.hModel = cgs.media.lightningExplosionModel; + + VectorMA( trace.endpos, -16, dir, beam.origin ); + + // make a random orientation + angles[0] = rand() % 360; + angles[1] = rand() % 360; + angles[2] = rand() % 360; + AnglesToAxis( angles, beam.axis ); + trap_R_AddRefEntityToScene( &beam ); + } +} +/* + +static void CG_LightningBolt( centity_t *cent, vec3_t origin ) { + trace_t trace; + refEntity_t beam; + vec3_t forward; + vec3_t muzzlePoint, endPoint; + + if ( cent->currentState.weapon != WP_LIGHTNING ) { + return; + } + + memset( &beam, 0, sizeof( beam ) ); + + // find muzzle point for this frame + VectorCopy( cent->lerpOrigin, muzzlePoint ); + AngleVectors( cent->lerpAngles, forward, NULL, NULL ); + + // FIXME: crouch + muzzlePoint[2] += DEFAULT_VIEWHEIGHT; + + VectorMA( muzzlePoint, 14, forward, muzzlePoint ); + + // project forward by the lightning range + VectorMA( muzzlePoint, LIGHTNING_RANGE, forward, endPoint ); + + // see if it hit a wall + CG_Trace( &trace, muzzlePoint, vec3_origin, vec3_origin, endPoint, + cent->currentState.number, MASK_SHOT ); + + // this is the endpoint + VectorCopy( trace.endpos, beam.oldorigin ); + + // use the provided origin, even though it may be slightly + // different than the muzzle origin + VectorCopy( origin, beam.origin ); + + beam.reType = RT_LIGHTNING; + beam.customShader = cgs.media.lightningShader; + trap_R_AddRefEntityToScene( &beam ); + + // add the impact flare if it hit something + if ( trace.fraction < 1.0 ) { + vec3_t angles; + vec3_t dir; + + VectorSubtract( beam.oldorigin, beam.origin, dir ); + VectorNormalize( dir ); + + memset( &beam, 0, sizeof( beam ) ); + beam.hModel = cgs.media.lightningExplosionModel; + + VectorMA( trace.endpos, -16, dir, beam.origin ); + + // make a random orientation + angles[0] = rand() % 360; + angles[1] = rand() % 360; + angles[2] = rand() % 360; + AnglesToAxis( angles, beam.axis ); + trap_R_AddRefEntityToScene( &beam ); + } +} +*/ + +/* +=============== +CG_SpawnRailTrail + +Origin will be the exact tag point, which is slightly +different than the muzzle point used for determining hits. +=============== +*/ +static void CG_SpawnRailTrail( centity_t *cent, vec3_t origin ) { + clientInfo_t *ci; + + if ( cent->currentState.weapon != WP_RAILGUN ) { + return; + } + if ( !cent->pe.railgunFlash ) { + return; + } + cent->pe.railgunFlash = qtrue; + ci = &cgs.clientinfo[ cent->currentState.clientNum ]; + CG_RailTrail( ci, origin, cent->pe.railgunImpact ); +} + + +/* +====================== +CG_MachinegunSpinAngle +====================== +*/ +#define SPIN_SPEED 0.9 +#define COAST_TIME 1000 +static float CG_MachinegunSpinAngle( centity_t *cent ) { + int delta; + float angle; + float speed; + + delta = cg.time - cent->pe.barrelTime; + if ( cent->pe.barrelSpinning ) { + angle = cent->pe.barrelAngle + delta * SPIN_SPEED; + } else { + if ( delta > COAST_TIME ) { + delta = COAST_TIME; + } + + speed = 0.5 * ( SPIN_SPEED + (float)( COAST_TIME - delta ) / COAST_TIME ); + angle = cent->pe.barrelAngle + delta * speed; + } + + if ( cent->pe.barrelSpinning == !(cent->currentState.eFlags & EF_FIRING) ) { + cent->pe.barrelTime = cg.time; + cent->pe.barrelAngle = AngleMod( angle ); + cent->pe.barrelSpinning = !!(cent->currentState.eFlags & EF_FIRING); +#ifdef MISSIONPACK + if ( cent->currentState.weapon == WP_CHAINGUN && !cent->pe.barrelSpinning ) { + trap_S_StartSound( NULL, cent->currentState.number, CHAN_WEAPON, trap_S_RegisterSound( "sound/weapons/vulcan/wvulwind.wav", qfalse ) ); + } +#endif + } + + return angle; +} + + +/* +======================== +CG_AddWeaponWithPowerups +======================== +*/ +static void CG_AddWeaponWithPowerups( refEntity_t *gun, int powerups ) { + // add powerup effects + if ( powerups & ( 1 << PW_INVIS ) ) { + gun->customShader = cgs.media.invisShader; + trap_R_AddRefEntityToScene( gun ); + } else { + trap_R_AddRefEntityToScene( gun ); + + if ( powerups & ( 1 << PW_BATTLESUIT ) ) { + gun->customShader = cgs.media.battleWeaponShader; + trap_R_AddRefEntityToScene( gun ); + } + if ( powerups & ( 1 << PW_QUAD ) ) { + gun->customShader = cgs.media.quadWeaponShader; + trap_R_AddRefEntityToScene( gun ); + } + } +} + + +/* +============= +CG_AddPlayerWeapon + +Used for both the view weapon (ps is valid) and the world modelother character models (ps is NULL) +The main player will have this called for BOTH cases, so effects like light and +sound should only be done on the world model case. +============= +*/ +void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent, int team ) { + refEntity_t gun; + refEntity_t barrel; + refEntity_t flash; + vec3_t angles; + weapon_t weaponNum; + weaponInfo_t *weapon; + centity_t *nonPredictedCent; +// int col; + + weaponNum = cent->currentState.weapon; + + CG_RegisterWeapon( weaponNum ); + weapon = &cg_weapons[weaponNum]; + + // add the weapon + memset( &gun, 0, sizeof( gun ) ); + VectorCopy( parent->lightingOrigin, gun.lightingOrigin ); + gun.shadowPlane = parent->shadowPlane; + gun.renderfx = parent->renderfx; + + // set custom shading for railgun refire rate + if ( ps ) { + if ( cg.predictedPlayerState.weapon == WP_RAILGUN + && cg.predictedPlayerState.weaponstate == WEAPON_FIRING ) { + float f; + + f = (float)cg.predictedPlayerState.weaponTime / 1500; + gun.shaderRGBA[1] = 0; + gun.shaderRGBA[0] = + gun.shaderRGBA[2] = 255 * ( 1.0 - f ); + } else { + gun.shaderRGBA[0] = 255; + gun.shaderRGBA[1] = 255; + gun.shaderRGBA[2] = 255; + gun.shaderRGBA[3] = 255; + } + } + + gun.hModel = weapon->weaponModel; + if (!gun.hModel) { + return; + } + + if ( !ps ) { + // add weapon ready sound + cent->pe.lightningFiring = qfalse; + if ( ( cent->currentState.eFlags & EF_FIRING ) && weapon->firingSound ) { + // lightning gun and guantlet make a different sound when fire is held down + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->firingSound ); + cent->pe.lightningFiring = qtrue; + } else if ( weapon->readySound ) { + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->readySound ); + } + } + + CG_PositionEntityOnTag( &gun, parent, parent->hModel, "tag_weapon"); + + CG_AddWeaponWithPowerups( &gun, cent->currentState.powerups ); + + // add the spinning barrel + if ( weapon->barrelModel ) { + memset( &barrel, 0, sizeof( barrel ) ); + VectorCopy( parent->lightingOrigin, barrel.lightingOrigin ); + barrel.shadowPlane = parent->shadowPlane; + barrel.renderfx = parent->renderfx; + + barrel.hModel = weapon->barrelModel; + angles[YAW] = 0; + angles[PITCH] = 0; + angles[ROLL] = CG_MachinegunSpinAngle( cent ); + AnglesToAxis( angles, barrel.axis ); + + CG_PositionRotatedEntityOnTag( &barrel, &gun, weapon->weaponModel, "tag_barrel" ); + + CG_AddWeaponWithPowerups( &barrel, cent->currentState.powerups ); + } + + // make sure we aren't looking at cg.predictedPlayerEntity for LG + nonPredictedCent = &cg_entities[cent->currentState.clientNum]; + + // if the index of the nonPredictedCent is not the same as the clientNum + // then this is a fake player (like on teh single player podiums), so + // go ahead and use the cent + if( ( nonPredictedCent - cg_entities ) != cent->currentState.clientNum ) { + nonPredictedCent = cent; + } + + // add the flash + if ( ( weaponNum == WP_LIGHTNING || weaponNum == WP_GAUNTLET || weaponNum == WP_GRAPPLING_HOOK ) + && ( nonPredictedCent->currentState.eFlags & EF_FIRING ) ) + { + // continuous flash + } else { + // impulse flash + if ( cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_TIME && !cent->pe.railgunFlash ) { + return; + } + } + + memset( &flash, 0, sizeof( flash ) ); + VectorCopy( parent->lightingOrigin, flash.lightingOrigin ); + flash.shadowPlane = parent->shadowPlane; + flash.renderfx = parent->renderfx; + + flash.hModel = weapon->flashModel; + if (!flash.hModel) { + return; + } + angles[YAW] = 0; + angles[PITCH] = 0; + angles[ROLL] = crandom() * 10; + AnglesToAxis( angles, flash.axis ); + + // colorize the railgun blast + if ( weaponNum == WP_RAILGUN ) { + clientInfo_t *ci; + + ci = &cgs.clientinfo[ cent->currentState.clientNum ]; + flash.shaderRGBA[0] = 255 * ci->color1[0]; + flash.shaderRGBA[1] = 255 * ci->color1[1]; + flash.shaderRGBA[2] = 255 * ci->color1[2]; + } + + CG_PositionRotatedEntityOnTag( &flash, &gun, weapon->weaponModel, "tag_flash"); + trap_R_AddRefEntityToScene( &flash ); + + if ( ps || cg.renderingThirdPerson || + cent->currentState.number != cg.predictedPlayerState.clientNum ) { + // add lightning bolt + CG_LightningBolt( nonPredictedCent, flash.origin ); + + // add rail trail + CG_SpawnRailTrail( cent, flash.origin ); + + if ( weapon->flashDlightColor[0] || weapon->flashDlightColor[1] || weapon->flashDlightColor[2] ) { + trap_R_AddLightToScene( flash.origin, 300 + (rand()&31), weapon->flashDlightColor[0], + weapon->flashDlightColor[1], weapon->flashDlightColor[2] ); + } + } +} + +/* +============== +CG_AddViewWeapon + +Add the weapon, and flash for the player's view +============== +*/ +void CG_AddViewWeapon( playerState_t *ps ) { + refEntity_t hand; + centity_t *cent; + clientInfo_t *ci; + float fovOffset; + vec3_t angles; + weaponInfo_t *weapon; + + if ( ps->persistant[PERS_TEAM] == TEAM_SPECTATOR ) { + return; + } + + if ( ps->pm_type == PM_INTERMISSION ) { + return; + } + + // no gun if in third person view or a camera is active + //if ( cg.renderingThirdPerson || cg.cameraMode) { + if ( cg.renderingThirdPerson ) { + return; + } + + + // allow the gun to be completely removed + if ( !cg_drawGun.integer ) { + vec3_t origin; + + if ( cg.predictedPlayerState.eFlags & EF_FIRING ) { + // special hack for lightning gun... + VectorCopy( cg.refdef.vieworg, origin ); + VectorMA( origin, -8, cg.refdef.viewaxis[2], origin ); + CG_LightningBolt( &cg_entities[ps->clientNum], origin ); + } + return; + } + + // don't draw if testing a gun model + if ( cg.testGun ) { + return; + } + + // drop gun lower at higher fov + if ( cg_fov.integer > 90 ) { + fovOffset = -0.2 * ( cg_fov.integer - 90 ); + } else { + fovOffset = 0; + } + + cent = &cg.predictedPlayerEntity; // &cg_entities[cg.snap->ps.clientNum]; + CG_RegisterWeapon( ps->weapon ); + weapon = &cg_weapons[ ps->weapon ]; + + memset (&hand, 0, sizeof(hand)); + + // set up gun position + CG_CalculateWeaponPosition( hand.origin, angles ); + + VectorMA( hand.origin, cg_gun_x.value, cg.refdef.viewaxis[0], hand.origin ); + VectorMA( hand.origin, cg_gun_y.value, cg.refdef.viewaxis[1], hand.origin ); + VectorMA( hand.origin, (cg_gun_z.value+fovOffset), cg.refdef.viewaxis[2], hand.origin ); + + AnglesToAxis( angles, hand.axis ); + + // map torso animations to weapon animations + if ( cg_gun_frame.integer ) { + // development tool + hand.frame = hand.oldframe = cg_gun_frame.integer; + hand.backlerp = 0; + } else { + // get clientinfo for animation map + ci = &cgs.clientinfo[ cent->currentState.clientNum ]; + hand.frame = CG_MapTorsoToWeaponFrame( ci, cent->pe.torso.frame ); + hand.oldframe = CG_MapTorsoToWeaponFrame( ci, cent->pe.torso.oldFrame ); + hand.backlerp = cent->pe.torso.backlerp; + } + + hand.hModel = weapon->handsModel; + hand.renderfx = RF_DEPTHHACK | RF_FIRST_PERSON | RF_MINLIGHT; + + // add everything onto the hand + CG_AddPlayerWeapon( &hand, ps, &cg.predictedPlayerEntity, ps->persistant[PERS_TEAM] ); +} + +/* +============================================================================== + +WEAPON SELECTION + +============================================================================== +*/ + +/* +=================== +CG_DrawWeaponSelect +=================== +*/ +void CG_DrawWeaponSelect( void ) { + int i; + int bits; + int count; + int x, y, w; + char *name; + float *color; + + // don't display if dead + if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) { + return; + } + + color = CG_FadeColor( cg.weaponSelectTime, WEAPON_SELECT_TIME ); + if ( !color ) { + return; + } + trap_R_SetColor( color ); + + // showing weapon select clears pickup item display, but not the blend blob + cg.itemPickupTime = 0; + + // count the number of weapons owned + bits = cg.snap->ps.stats[ STAT_WEAPONS ]; + count = 0; + for ( i = 1 ; i < 16 ; i++ ) { + if ( bits & ( 1 << i ) ) { + count++; + } + } + + x = 320 - count * 20; + y = 380; + + for ( i = 1 ; i < 16 ; i++ ) { + if ( !( bits & ( 1 << i ) ) ) { + continue; + } + + CG_RegisterWeapon( i ); + + // draw weapon icon + CG_DrawPic( x, y, 32, 32, cg_weapons[i].weaponIcon ); + + // draw selection marker + if ( i == cg.weaponSelect ) { + CG_DrawPic( x-4, y-4, 40, 40, cgs.media.selectShader ); + } + + // no ammo cross on top + if ( !cg.snap->ps.ammo[ i ] ) { + CG_DrawPic( x, y, 32, 32, cgs.media.noammoShader ); + } + + x += 40; + } + + // draw the selected name + if ( cg_weapons[ cg.weaponSelect ].item ) { + name = cg_weapons[ cg.weaponSelect ].item->pickup_name; + if ( name ) { + w = CG_DrawStrlen( name ) * BIGCHAR_WIDTH; + x = ( SCREEN_WIDTH - w ) / 2; + CG_DrawBigStringColor(x, y - 22, name, color); + } + } + + trap_R_SetColor( NULL ); +} + + +/* +=============== +CG_WeaponSelectable +=============== +*/ +static qboolean CG_WeaponSelectable( int i ) { + if ( !cg.snap->ps.ammo[i] ) { + return qfalse; + } + if ( ! (cg.snap->ps.stats[ STAT_WEAPONS ] & ( 1 << i ) ) ) { + return qfalse; + } + + return qtrue; +} + +/* +=============== +CG_NextWeapon_f +=============== +*/ +void CG_NextWeapon_f( void ) { + int i; + int original; + + if ( !cg.snap ) { + return; + } + if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { + return; + } + + cg.weaponSelectTime = cg.time; + original = cg.weaponSelect; + + for ( i = 0 ; i < 16 ; i++ ) { + cg.weaponSelect++; + if ( cg.weaponSelect == 16 ) { + cg.weaponSelect = 0; + } + if ( cg.weaponSelect == WP_GAUNTLET ) { + continue; // never cycle to gauntlet + } + if ( CG_WeaponSelectable( cg.weaponSelect ) ) { + break; + } + } + if ( i == 16 ) { + cg.weaponSelect = original; + } +} + +/* +=============== +CG_PrevWeapon_f +=============== +*/ +void CG_PrevWeapon_f( void ) { + int i; + int original; + + if ( !cg.snap ) { + return; + } + if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { + return; + } + + cg.weaponSelectTime = cg.time; + original = cg.weaponSelect; + + for ( i = 0 ; i < 16 ; i++ ) { + cg.weaponSelect--; + if ( cg.weaponSelect == -1 ) { + cg.weaponSelect = 15; + } + if ( cg.weaponSelect == WP_GAUNTLET ) { + continue; // never cycle to gauntlet + } + if ( CG_WeaponSelectable( cg.weaponSelect ) ) { + break; + } + } + if ( i == 16 ) { + cg.weaponSelect = original; + } +} + +/* +=============== +CG_Weapon_f +=============== +*/ +void CG_Weapon_f( void ) { + int num; + + if ( !cg.snap ) { + return; + } + if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { + return; + } + + num = atoi( CG_Argv( 1 ) ); + + if ( num < 1 || num > 15 ) { + return; + } + + cg.weaponSelectTime = cg.time; + + if ( ! ( cg.snap->ps.stats[STAT_WEAPONS] & ( 1 << num ) ) ) { + return; // don't have the weapon + } + + cg.weaponSelect = num; +} + +/* +=================== +CG_OutOfAmmoChange + +The current weapon has just run out of ammo +=================== +*/ +void CG_OutOfAmmoChange( void ) { + int i; + + cg.weaponSelectTime = cg.time; + + for ( i = 15 ; i > 0 ; i-- ) { + if ( CG_WeaponSelectable( i ) ) { + cg.weaponSelect = i; + break; + } + } +} + + + +/* +=================================================================================================== + +WEAPON EVENTS + +=================================================================================================== +*/ + +/* +================ +CG_FireWeapon + +Caused by an EV_FIRE_WEAPON event +================ +*/ +void CG_FireWeapon( centity_t *cent ) { + entityState_t *ent; + int c; + weaponInfo_t *weap; + + ent = ¢->currentState; + if ( ent->weapon == WP_NONE ) { + return; + } + if ( ent->weapon >= WP_NUM_WEAPONS ) { + CG_Error( "CG_FireWeapon: ent->weapon >= WP_NUM_WEAPONS" ); + return; + } + weap = &cg_weapons[ ent->weapon ]; + + // mark the entity as muzzle flashing, so when it is added it will + // append the flash to the weapon model + cent->muzzleFlashTime = cg.time; + + // lightning gun only does this this on initial press + if ( ent->weapon == WP_LIGHTNING ) { + if ( cent->pe.lightningFiring ) { + return; + } + } + + // play quad sound if needed + if ( cent->currentState.powerups & ( 1 << PW_QUAD ) ) { + trap_S_StartSound (NULL, cent->currentState.number, CHAN_ITEM, cgs.media.quadSound ); + } + + // play a sound + for ( c = 0 ; c < 4 ; c++ ) { + if ( !weap->flashSound[c] ) { + break; + } + } + if ( c > 0 ) { + c = rand() % c; + if ( weap->flashSound[c] ) + { + trap_S_StartSound( NULL, ent->number, CHAN_WEAPON, weap->flashSound[c] ); + } + } + + // do brass ejection + if ( weap->ejectBrassFunc && cg_brassTime.integer > 0 ) { + weap->ejectBrassFunc( cent ); + } +} + + +/* +================= +CG_MissileHitWall + +Caused by an EV_MISSILE_MISS event, or directly by local bullet tracing +================= +*/ +void CG_MissileHitWall( int weapon, int clientNum, vec3_t origin, vec3_t dir, impactSound_t soundType ) { + qhandle_t mod; + qhandle_t mark; + qhandle_t shader; + sfxHandle_t sfx; + float radius; + float light; + vec3_t lightColor; + localEntity_t *le; + int r; + qboolean alphaFade; + qboolean isSprite; + int duration; + vec3_t sprOrg; + vec3_t sprVel; + + mark = 0; + radius = 32; + sfx = 0; + mod = 0; + shader = 0; + light = 0; + lightColor[0] = 1; + lightColor[1] = 1; + lightColor[2] = 0; + + // set defaults + isSprite = qfalse; + duration = 600; + + switch ( weapon ) { + default: +#ifdef MISSIONPACK + case WP_NAILGUN: + if( soundType == IMPACTSOUND_FLESH ) { + sfx = cgs.media.sfx_nghitflesh; + } else if( soundType == IMPACTSOUND_METAL ) { + sfx = cgs.media.sfx_nghitmetal; + } else { + sfx = cgs.media.sfx_nghit; + } + mark = cgs.media.holeMarkShader; + radius = 12; + break; +#endif + case WP_LIGHTNING: + // no explosion at LG impact, it is added with the beam + r = rand() & 3; + if ( r < 2 ) { + sfx = cgs.media.sfx_lghit2; + } else if ( r == 2 ) { + sfx = cgs.media.sfx_lghit1; + } else { + sfx = cgs.media.sfx_lghit3; + } + mark = cgs.media.holeMarkShader; + radius = 12; + break; +#ifdef MISSIONPACK + case WP_PROX_LAUNCHER: + mod = cgs.media.dishFlashModel; + shader = cgs.media.grenadeExplosionShader; + sfx = cgs.media.sfx_proxexp; + mark = cgs.media.burnMarkShader; + radius = 64; + light = 300; + isSprite = qtrue; + break; +#endif + case WP_GRENADE_LAUNCHER: + mod = cgs.media.dishFlashModel; + shader = cgs.media.grenadeExplosionShader; + sfx = cgs.media.sfx_rockexp; + mark = cgs.media.burnMarkShader; + radius = 64; + light = 300; + isSprite = qtrue; + break; + case WP_ROCKET_LAUNCHER: + mod = cgs.media.dishFlashModel; + shader = cgs.media.rocketExplosionShader; + sfx = cgs.media.sfx_rockexp; + mark = cgs.media.burnMarkShader; + radius = 64; + light = 300; + isSprite = qtrue; + duration = 1000; + lightColor[0] = 1; + lightColor[1] = 0.75; + lightColor[2] = 0.0; + if (cg_oldRocket.integer == 0) { + // explosion sprite animation + VectorMA( origin, 24, dir, sprOrg ); + VectorScale( dir, 64, sprVel ); + + CG_ParticleExplosion( "explode1", sprOrg, sprVel, 1400, 20, 30 ); + } + break; + case WP_RAILGUN: + mod = cgs.media.ringFlashModel; + shader = cgs.media.railExplosionShader; + sfx = cgs.media.sfx_plasmaexp; + mark = cgs.media.energyMarkShader; + radius = 24; + break; + case WP_PLASMAGUN: + mod = cgs.media.ringFlashModel; + shader = cgs.media.plasmaExplosionShader; + sfx = cgs.media.sfx_plasmaexp; + mark = cgs.media.energyMarkShader; + radius = 16; + break; + case WP_BFG: + mod = cgs.media.dishFlashModel; + shader = cgs.media.bfgExplosionShader; + sfx = cgs.media.sfx_rockexp; + mark = cgs.media.burnMarkShader; + radius = 32; + isSprite = qtrue; + break; + case WP_SHOTGUN: + mod = cgs.media.bulletFlashModel; + shader = cgs.media.bulletExplosionShader; + mark = cgs.media.bulletMarkShader; + sfx = 0; + radius = 4; + break; + +#ifdef MISSIONPACK + case WP_CHAINGUN: + mod = cgs.media.bulletFlashModel; + if( soundType == IMPACTSOUND_FLESH ) { + sfx = cgs.media.sfx_chghitflesh; + } else if( soundType == IMPACTSOUND_METAL ) { + sfx = cgs.media.sfx_chghitmetal; + } else { + sfx = cgs.media.sfx_chghit; + } + mark = cgs.media.bulletMarkShader; + + r = rand() & 3; + if ( r < 2 ) { + sfx = cgs.media.sfx_ric1; + } else if ( r == 2 ) { + sfx = cgs.media.sfx_ric2; + } else { + sfx = cgs.media.sfx_ric3; + } + + radius = 8; + break; +#endif + + case WP_MACHINEGUN: + mod = cgs.media.bulletFlashModel; + shader = cgs.media.bulletExplosionShader; + mark = cgs.media.bulletMarkShader; + + r = rand() & 3; + if ( r == 0 ) { + sfx = cgs.media.sfx_ric1; + } else if ( r == 1 ) { + sfx = cgs.media.sfx_ric2; + } else { + sfx = cgs.media.sfx_ric3; + } + + radius = 8; + break; + } + + if ( sfx ) { + trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, sfx ); + } + + // + // create the explosion + // + if ( mod ) { + le = CG_MakeExplosion( origin, dir, + mod, shader, + duration, isSprite ); + le->light = light; + VectorCopy( lightColor, le->lightColor ); + if ( weapon == WP_RAILGUN ) { + // colorize with client color + VectorCopy( cgs.clientinfo[clientNum].color1, le->color ); + } + } + + // + // impact mark + // + alphaFade = (mark == cgs.media.energyMarkShader); // plasma fades alpha, all others fade color + if ( weapon == WP_RAILGUN ) { + float *color; + + // colorize with client color + color = cgs.clientinfo[clientNum].color2; + CG_ImpactMark( mark, origin, dir, random()*360, color[0],color[1], color[2],1, alphaFade, radius, qfalse ); + } else { + CG_ImpactMark( mark, origin, dir, random()*360, 1,1,1,1, alphaFade, radius, qfalse ); + } +} + + +/* +================= +CG_MissileHitPlayer +================= +*/ +void CG_MissileHitPlayer( int weapon, vec3_t origin, vec3_t dir, int entityNum ) { + CG_Bleed( origin, entityNum ); + + // some weapons will make an explosion with the blood, while + // others will just make the blood + switch ( weapon ) { + case WP_GRENADE_LAUNCHER: + case WP_ROCKET_LAUNCHER: +#ifdef MISSIONPACK + case WP_NAILGUN: + case WP_CHAINGUN: + case WP_PROX_LAUNCHER: +#endif + CG_MissileHitWall( weapon, 0, origin, dir, IMPACTSOUND_FLESH ); + break; + default: + break; + } +} + + + +/* +============================================================================ + +SHOTGUN TRACING + +============================================================================ +*/ + +/* +================ +CG_ShotgunPellet +================ +*/ +static void CG_ShotgunPellet( vec3_t start, vec3_t end, int skipNum ) { + trace_t tr; + int sourceContentType, destContentType; + + CG_Trace( &tr, start, NULL, NULL, end, skipNum, MASK_SHOT ); + + sourceContentType = trap_CM_PointContents( start, 0 ); + destContentType = trap_CM_PointContents( tr.endpos, 0 ); + + // FIXME: should probably move this cruft into CG_BubbleTrail + if ( sourceContentType == destContentType ) { + if ( sourceContentType & CONTENTS_WATER ) { + CG_BubbleTrail( start, tr.endpos, 32 ); + } + } else if ( sourceContentType & CONTENTS_WATER ) { + trace_t trace; + + trap_CM_BoxTrace( &trace, end, start, NULL, NULL, 0, CONTENTS_WATER ); + CG_BubbleTrail( start, trace.endpos, 32 ); + } else if ( destContentType & CONTENTS_WATER ) { + trace_t trace; + + trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, CONTENTS_WATER ); + CG_BubbleTrail( tr.endpos, trace.endpos, 32 ); + } + + if ( tr.surfaceFlags & SURF_NOIMPACT ) { + return; + } + + if ( cg_entities[tr.entityNum].currentState.eType == ET_PLAYER ) { + CG_MissileHitPlayer( WP_SHOTGUN, tr.endpos, tr.plane.normal, tr.entityNum ); + } else { + if ( tr.surfaceFlags & SURF_NOIMPACT ) { + // SURF_NOIMPACT will not make a flame puff or a mark + return; + } + if ( tr.surfaceFlags & SURF_METALSTEPS ) { + CG_MissileHitWall( WP_SHOTGUN, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_METAL ); + } else { + CG_MissileHitWall( WP_SHOTGUN, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_DEFAULT ); + } + } +} + +/* +================ +CG_ShotgunPattern + +Perform the same traces the server did to locate the +hit splashes +================ +*/ +static void CG_ShotgunPattern( vec3_t origin, vec3_t origin2, int seed, int otherEntNum ) { + int i; + float r, u; + vec3_t end; + vec3_t forward, right, up; + + // derive the right and up vectors from the forward vector, because + // the client won't have any other information + VectorNormalize2( origin2, forward ); + PerpendicularVector( right, forward ); + CrossProduct( forward, right, up ); + + // generate the "random" spread pattern + for ( i = 0 ; i < DEFAULT_SHOTGUN_COUNT ; i++ ) { + r = Q_crandom( &seed ) * DEFAULT_SHOTGUN_SPREAD * 16; + u = Q_crandom( &seed ) * DEFAULT_SHOTGUN_SPREAD * 16; + VectorMA( origin, 8192 * 16, forward, end); + VectorMA (end, r, right, end); + VectorMA (end, u, up, end); + + CG_ShotgunPellet( origin, end, otherEntNum ); + } +} + +/* +============== +CG_ShotgunFire +============== +*/ +void CG_ShotgunFire( entityState_t *es ) { + vec3_t v; + int contents; + + VectorSubtract( es->origin2, es->pos.trBase, v ); + VectorNormalize( v ); + VectorScale( v, 32, v ); + VectorAdd( es->pos.trBase, v, v ); + if ( cgs.glconfig.hardwareType != GLHW_RAGEPRO ) { + // ragepro can't alpha fade, so don't even bother with smoke + vec3_t up; + + contents = trap_CM_PointContents( es->pos.trBase, 0 ); + if ( !( contents & CONTENTS_WATER ) ) { + VectorSet( up, 0, 0, 8 ); + CG_SmokePuff( v, up, 32, 1, 1, 1, 0.33f, 900, cg.time, 0, LEF_PUFF_DONT_SCALE, cgs.media.shotgunSmokePuffShader ); + } + } + CG_ShotgunPattern( es->pos.trBase, es->origin2, es->eventParm, es->otherEntityNum ); +} + +/* +============================================================================ + +BULLETS + +============================================================================ +*/ + + +/* +=============== +CG_Tracer +=============== +*/ +void CG_Tracer( vec3_t source, vec3_t dest ) { + vec3_t forward, right; + polyVert_t verts[4]; + vec3_t line; + float len, begin, end; + vec3_t start, finish; + vec3_t midpoint; + + // tracer + VectorSubtract( dest, source, forward ); + len = VectorNormalize( forward ); + + // start at least a little ways from the muzzle + if ( len < 100 ) { + return; + } + begin = 50 + random() * (len - 60); + end = begin + cg_tracerLength.value; + if ( end > len ) { + end = len; + } + VectorMA( source, begin, forward, start ); + VectorMA( source, end, forward, finish ); + + line[0] = DotProduct( forward, cg.refdef.viewaxis[1] ); + line[1] = DotProduct( forward, cg.refdef.viewaxis[2] ); + + VectorScale( cg.refdef.viewaxis[1], line[1], right ); + VectorMA( right, -line[0], cg.refdef.viewaxis[2], right ); + VectorNormalize( right ); + + VectorMA( finish, cg_tracerWidth.value, right, verts[0].xyz ); + verts[0].st[0] = 0; + verts[0].st[1] = 1; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255; + + VectorMA( finish, -cg_tracerWidth.value, right, verts[1].xyz ); + verts[1].st[0] = 1; + verts[1].st[1] = 0; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255; + + VectorMA( start, -cg_tracerWidth.value, right, verts[2].xyz ); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255; + + VectorMA( start, cg_tracerWidth.value, right, verts[3].xyz ); + verts[3].st[0] = 0; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255; + + trap_R_AddPolyToScene( cgs.media.tracerShader, 4, verts ); + + midpoint[0] = ( start[0] + finish[0] ) * 0.5; + midpoint[1] = ( start[1] + finish[1] ) * 0.5; + midpoint[2] = ( start[2] + finish[2] ) * 0.5; + + // add the tracer sound + trap_S_StartSound( midpoint, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.tracerSound ); + +} + + +/* +====================== +CG_CalcMuzzlePoint +====================== +*/ +static qboolean CG_CalcMuzzlePoint( int entityNum, vec3_t muzzle ) { + vec3_t forward; + centity_t *cent; + int anim; + + if ( entityNum == cg.snap->ps.clientNum ) { + VectorCopy( cg.snap->ps.origin, muzzle ); + muzzle[2] += cg.snap->ps.viewheight; + AngleVectors( cg.snap->ps.viewangles, forward, NULL, NULL ); + VectorMA( muzzle, 14, forward, muzzle ); + return qtrue; + } + + cent = &cg_entities[entityNum]; + if ( !cent->currentValid ) { + return qfalse; + } + + VectorCopy( cent->currentState.pos.trBase, muzzle ); + + AngleVectors( cent->currentState.apos.trBase, forward, NULL, NULL ); + anim = cent->currentState.legsAnim & ~ANIM_TOGGLEBIT; + if ( anim == LEGS_WALKCR || anim == LEGS_IDLECR ) { + muzzle[2] += CROUCH_VIEWHEIGHT; + } else { + muzzle[2] += DEFAULT_VIEWHEIGHT; + } + + VectorMA( muzzle, 14, forward, muzzle ); + + return qtrue; + +} + +/* +====================== +CG_Bullet + +Renders bullet effects. +====================== +*/ +void CG_Bullet( vec3_t end, int sourceEntityNum, vec3_t normal, qboolean flesh, int fleshEntityNum ) { + trace_t trace; + int sourceContentType, destContentType; + vec3_t start; + + // if the shooter is currently valid, calc a source point and possibly + // do trail effects + if ( sourceEntityNum >= 0 && cg_tracerChance.value > 0 ) { + if ( CG_CalcMuzzlePoint( sourceEntityNum, start ) ) { + sourceContentType = trap_CM_PointContents( start, 0 ); + destContentType = trap_CM_PointContents( end, 0 ); + + // do a complete bubble trail if necessary + if ( ( sourceContentType == destContentType ) && ( sourceContentType & CONTENTS_WATER ) ) { + CG_BubbleTrail( start, end, 32 ); + } + // bubble trail from water into air + else if ( ( sourceContentType & CONTENTS_WATER ) ) { + trap_CM_BoxTrace( &trace, end, start, NULL, NULL, 0, CONTENTS_WATER ); + CG_BubbleTrail( start, trace.endpos, 32 ); + } + // bubble trail from air into water + else if ( ( destContentType & CONTENTS_WATER ) ) { + trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, CONTENTS_WATER ); + CG_BubbleTrail( trace.endpos, end, 32 ); + } + + // draw a tracer + if ( random() < cg_tracerChance.value ) { + CG_Tracer( start, end ); + } + } + } + + // impact splash and mark + if ( flesh ) { + CG_Bleed( end, fleshEntityNum ); + } else { + CG_MissileHitWall( WP_MACHINEGUN, 0, end, normal, IMPACTSOUND_DEFAULT ); + } + +} diff --git a/code/cgame/cgame.bat b/code/cgame/cgame.bat index 6e13523..a59381e 100755 --- a/code/cgame/cgame.bat +++ b/code/cgame/cgame.bat @@ -1,63 +1,63 @@ -rem make sure we have a safe environement -set LIBRARY= -set INCLUDE= - -mkdir vm -cd vm -set cc=lcc -DQ3_VM -DCGAME -S -Wf-target=bytecode -Wf-g -I..\..\cgame -I..\..\game -I..\..\ui %1 - -%cc% ../../game/bg_misc.c -@if errorlevel 1 goto quit -%cc% ../../game/bg_pmove.c -@if errorlevel 1 goto quit -%cc% ../../game/bg_slidemove.c -@if errorlevel 1 goto quit -%cc% ../../game/bg_lib.c -@if errorlevel 1 goto quit -%cc% ../../game/q_math.c -@if errorlevel 1 goto quit -%cc% ../../game/q_shared.c -@if errorlevel 1 goto quit -%cc% ../cg_consolecmds.c -@if errorlevel 1 goto quit -%cc% ../cg_draw.c -@if errorlevel 1 goto quit -%cc% ../cg_drawtools.c -@if errorlevel 1 goto quit -%cc% ../cg_effects.c -@if errorlevel 1 goto quit -%cc% ../cg_ents.c -@if errorlevel 1 goto quit -%cc% ../cg_event.c -@if errorlevel 1 goto quit -%cc% ../cg_info.c -@if errorlevel 1 goto quit -%cc% ../cg_localents.c -@if errorlevel 1 goto quit -%cc% ../cg_main.c -@if errorlevel 1 goto quit -%cc% ../cg_marks.c -@if errorlevel 1 goto quit -%cc% ../cg_players.c -@if errorlevel 1 goto quit -%cc% ../cg_playerstate.c -@if errorlevel 1 goto quit -%cc% ../cg_predict.c -@if errorlevel 1 goto quit -%cc% ../cg_scoreboard.c -@if errorlevel 1 goto quit -%cc% ../cg_servercmds.c -@if errorlevel 1 goto quit -%cc% ../cg_snapshot.c -@if errorlevel 1 goto quit -%cc% ../cg_view.c -@if errorlevel 1 goto quit -%cc% ../cg_weapons.c -@if errorlevel 1 goto quit - - - - -q3asm -f ../cgame -:quit -cd .. +rem make sure we have a safe environement +set LIBRARY= +set INCLUDE= + +mkdir vm +cd vm +set cc=lcc -DQ3_VM -DCGAME -S -Wf-target=bytecode -Wf-g -I..\..\cgame -I..\..\game -I..\..\ui %1 + +%cc% ../../game/bg_misc.c +@if errorlevel 1 goto quit +%cc% ../../game/bg_pmove.c +@if errorlevel 1 goto quit +%cc% ../../game/bg_slidemove.c +@if errorlevel 1 goto quit +%cc% ../../game/bg_lib.c +@if errorlevel 1 goto quit +%cc% ../../game/q_math.c +@if errorlevel 1 goto quit +%cc% ../../game/q_shared.c +@if errorlevel 1 goto quit +%cc% ../cg_consolecmds.c +@if errorlevel 1 goto quit +%cc% ../cg_draw.c +@if errorlevel 1 goto quit +%cc% ../cg_drawtools.c +@if errorlevel 1 goto quit +%cc% ../cg_effects.c +@if errorlevel 1 goto quit +%cc% ../cg_ents.c +@if errorlevel 1 goto quit +%cc% ../cg_event.c +@if errorlevel 1 goto quit +%cc% ../cg_info.c +@if errorlevel 1 goto quit +%cc% ../cg_localents.c +@if errorlevel 1 goto quit +%cc% ../cg_main.c +@if errorlevel 1 goto quit +%cc% ../cg_marks.c +@if errorlevel 1 goto quit +%cc% ../cg_players.c +@if errorlevel 1 goto quit +%cc% ../cg_playerstate.c +@if errorlevel 1 goto quit +%cc% ../cg_predict.c +@if errorlevel 1 goto quit +%cc% ../cg_scoreboard.c +@if errorlevel 1 goto quit +%cc% ../cg_servercmds.c +@if errorlevel 1 goto quit +%cc% ../cg_snapshot.c +@if errorlevel 1 goto quit +%cc% ../cg_view.c +@if errorlevel 1 goto quit +%cc% ../cg_weapons.c +@if errorlevel 1 goto quit + + + + +q3asm -f ../cgame +:quit +cd .. diff --git a/code/cgame/cgame.def b/code/cgame/cgame.def index 01861ba..2ee748e 100755 --- a/code/cgame/cgame.def +++ b/code/cgame/cgame.def @@ -1,3 +1,3 @@ -EXPORTS - vmMain - dllEntry +EXPORTS + vmMain + dllEntry diff --git a/code/cgame/cgame.plg b/code/cgame/cgame.plg index b7c80ee..781b1eb 100755 --- a/code/cgame/cgame.plg +++ b/code/cgame/cgame.plg @@ -1,124 +1,124 @@ - - -
-

Build Log

-

---------------------Configuration: cgame - Win32 Release-------------------- -

-

Command Lines

-Creating temporary file "C:\WINNT\Profiles\ADMINI~1\LOCALS~1\Temp\RSP4BE.tmp" with contents -[ -/nologo /G6 /ML /W4 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /Fp"Release/cgame.pch" /YX /Fo"Release/" /Fd"Release/" /FD /c -"D:\quake3\MissionPack\code\game\bg_misc.c" -"D:\quake3\MissionPack\code\game\bg_pmove.c" -"D:\quake3\MissionPack\code\game\bg_slidemove.c" -"D:\quake3\MissionPack\code\cgame\cg_consolecmds.c" -"D:\quake3\MissionPack\code\cgame\cg_draw.c" -"D:\quake3\MissionPack\code\cgame\cg_drawtools.c" -"D:\quake3\MissionPack\code\cgame\cg_effects.c" -"D:\quake3\MissionPack\code\cgame\cg_ents.c" -"D:\quake3\MissionPack\code\cgame\cg_event.c" -"D:\quake3\MissionPack\code\cgame\cg_info.c" -"D:\quake3\MissionPack\code\cgame\cg_localents.c" -"D:\quake3\MissionPack\code\cgame\cg_main.c" -"D:\quake3\MissionPack\code\cgame\cg_marks.c" -"D:\quake3\MissionPack\code\cgame\cg_players.c" -"D:\quake3\MissionPack\code\cgame\cg_playerstate.c" -"D:\quake3\MissionPack\code\cgame\cg_predict.c" -"D:\quake3\MissionPack\code\cgame\cg_rankings.c" -"D:\quake3\MissionPack\code\cgame\cg_scoreboard.c" -"D:\quake3\MissionPack\code\cgame\cg_servercmds.c" -"D:\quake3\MissionPack\code\cgame\cg_snapshot.c" -"D:\quake3\MissionPack\code\cgame\cg_syscalls.c" -"D:\quake3\MissionPack\code\cgame\cg_view.c" -"D:\quake3\MissionPack\code\cgame\cg_weapons.c" -"D:\quake3\MissionPack\code\game\q_math.c" -"D:\quake3\MissionPack\code\game\q_shared.c" -"D:\quake3\MissionPack\code\ui\ui_shared.c" -] -Creating command line "cl.exe @C:\WINNT\Profiles\ADMINI~1\LOCALS~1\Temp\RSP4BE.tmp" -Creating temporary file "C:\WINNT\Profiles\ADMINI~1\LOCALS~1\Temp\RSP4BF.tmp" with contents -[ -/nologo /base:"0x30000000" /subsystem:windows /dll /incremental:no /pdb:"Release/cgamex86.pdb" /map:"Release/cgamex86.map" /machine:I386 /def:".\cgame.def" /out:"../Release/cgamex86.dll" /implib:"Release/cgamex86.lib" -.\Release\bg_misc.obj -.\Release\bg_pmove.obj -.\Release\bg_slidemove.obj -.\Release\cg_consolecmds.obj -.\Release\cg_draw.obj -.\Release\cg_drawtools.obj -.\Release\cg_effects.obj -.\Release\cg_ents.obj -.\Release\cg_event.obj -.\Release\cg_info.obj -.\Release\cg_localents.obj -.\Release\cg_main.obj -.\Release\cg_marks.obj -.\Release\cg_players.obj -.\Release\cg_playerstate.obj -.\Release\cg_predict.obj -.\Release\cg_rankings.obj -.\Release\cg_scoreboard.obj -.\Release\cg_servercmds.obj -.\Release\cg_snapshot.obj -.\Release\cg_syscalls.obj -.\Release\cg_view.obj -.\Release\cg_weapons.obj -.\Release\q_math.obj -.\Release\q_shared.obj -.\Release\ui_shared.obj -] -Creating command line "link.exe @C:\WINNT\Profiles\ADMINI~1\LOCALS~1\Temp\RSP4BF.tmp" -

Output Window

-Compiling... -bg_misc.c -bg_pmove.c -D:\quake3\MissionPack\code\game\bg_pmove.c(987) : warning C4189: 'shit' : local variable is initialized but not referenced -D:\quake3\MissionPack\code\game\bg_pmove.c(2001) : warning C4505: 'PM_InvulnerabilityMove' : unreferenced local function has been removed - D:\quake3\MissionPack\code\game\bg_pmove.c(519) : see declaration of 'PM_InvulnerabilityMove' -bg_slidemove.c -cg_consolecmds.c -cg_draw.c -cg_drawtools.c -cg_effects.c -cg_ents.c -cg_event.c -cg_info.c -cg_localents.c -cg_main.c -D:\quake3\MissionPack\code\cgame\cg_main.c(1819) : warning C4505: 'CG_Cvar_Get' : unreferenced local function has been removed - D:\quake3\MissionPack\code\cgame\cg_main.c(1513) : see declaration of 'CG_Cvar_Get' -cg_marks.c -cg_players.c -D:\quake3\MissionPack\code\cgame\cg_players.c(2209) : warning C4505: 'CG_PlayerTokens' : unreferenced local function has been removed - D:\quake3\MissionPack\code\cgame\cg_players.c(1371) : see declaration of 'CG_PlayerTokens' -cg_playerstate.c -cg_predict.c -cg_rankings.c -cg_scoreboard.c -cg_servercmds.c -cg_snapshot.c -cg_syscalls.c -cg_view.c -cg_weapons.c -q_math.c -q_shared.c -ui_shared.c -D:\quake3\MissionPack\code\ui\ui_shared.c(2223) : warning C4189: 'parent' : local variable is initialized but not referenced -D:\quake3\MissionPack\code\ui\ui_shared.c(3501) : warning C4189: 'collision' : local variable is initialized but not referenced -D:\quake3\MissionPack\code\ui\ui_shared.c(4622) : warning C4505: 'Controls_SetDefaults' : unreferenced local function has been removed - D:\quake3\MissionPack\code\ui\ui_shared.c(2595) : see declaration of 'Controls_SetDefaults' -D:\quake3\MissionPack\code\ui\ui_shared.c(1540) : warning C4701: local variable 'value' may be used without having been initialized -D:\quake3\MissionPack\code\ui\ui_shared.c(1566) : warning C4701: local variable 'value' may be used without having been initialized -D:\quake3\MissionPack\code\ui\ui_shared.c(1912) : warning C4702: unreachable code -D:\quake3\MissionPack\code\ui\ui_shared.c(4013) : warning C4702: unreachable code -D:\quake3\MissionPack\code\ui\ui_shared.c(4056) : warning C4702: unreachable code -Linking... - Creating library Release/cgamex86.lib and object Release/cgamex86.exp - - - -

Results

-cgamex86.dll - 0 error(s), 12 warning(s) -
- - + + +
+

Build Log

+

+--------------------Configuration: cgame - Win32 Release-------------------- +

+

Command Lines

+Creating temporary file "C:\WINNT\Profiles\ADMINI~1\LOCALS~1\Temp\RSP4BE.tmp" with contents +[ +/nologo /G6 /ML /W4 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /Fp"Release/cgame.pch" /YX /Fo"Release/" /Fd"Release/" /FD /c +"D:\quake3\MissionPack\code\game\bg_misc.c" +"D:\quake3\MissionPack\code\game\bg_pmove.c" +"D:\quake3\MissionPack\code\game\bg_slidemove.c" +"D:\quake3\MissionPack\code\cgame\cg_consolecmds.c" +"D:\quake3\MissionPack\code\cgame\cg_draw.c" +"D:\quake3\MissionPack\code\cgame\cg_drawtools.c" +"D:\quake3\MissionPack\code\cgame\cg_effects.c" +"D:\quake3\MissionPack\code\cgame\cg_ents.c" +"D:\quake3\MissionPack\code\cgame\cg_event.c" +"D:\quake3\MissionPack\code\cgame\cg_info.c" +"D:\quake3\MissionPack\code\cgame\cg_localents.c" +"D:\quake3\MissionPack\code\cgame\cg_main.c" +"D:\quake3\MissionPack\code\cgame\cg_marks.c" +"D:\quake3\MissionPack\code\cgame\cg_players.c" +"D:\quake3\MissionPack\code\cgame\cg_playerstate.c" +"D:\quake3\MissionPack\code\cgame\cg_predict.c" +"D:\quake3\MissionPack\code\cgame\cg_rankings.c" +"D:\quake3\MissionPack\code\cgame\cg_scoreboard.c" +"D:\quake3\MissionPack\code\cgame\cg_servercmds.c" +"D:\quake3\MissionPack\code\cgame\cg_snapshot.c" +"D:\quake3\MissionPack\code\cgame\cg_syscalls.c" +"D:\quake3\MissionPack\code\cgame\cg_view.c" +"D:\quake3\MissionPack\code\cgame\cg_weapons.c" +"D:\quake3\MissionPack\code\game\q_math.c" +"D:\quake3\MissionPack\code\game\q_shared.c" +"D:\quake3\MissionPack\code\ui\ui_shared.c" +] +Creating command line "cl.exe @C:\WINNT\Profiles\ADMINI~1\LOCALS~1\Temp\RSP4BE.tmp" +Creating temporary file "C:\WINNT\Profiles\ADMINI~1\LOCALS~1\Temp\RSP4BF.tmp" with contents +[ +/nologo /base:"0x30000000" /subsystem:windows /dll /incremental:no /pdb:"Release/cgamex86.pdb" /map:"Release/cgamex86.map" /machine:I386 /def:".\cgame.def" /out:"../Release/cgamex86.dll" /implib:"Release/cgamex86.lib" +.\Release\bg_misc.obj +.\Release\bg_pmove.obj +.\Release\bg_slidemove.obj +.\Release\cg_consolecmds.obj +.\Release\cg_draw.obj +.\Release\cg_drawtools.obj +.\Release\cg_effects.obj +.\Release\cg_ents.obj +.\Release\cg_event.obj +.\Release\cg_info.obj +.\Release\cg_localents.obj +.\Release\cg_main.obj +.\Release\cg_marks.obj +.\Release\cg_players.obj +.\Release\cg_playerstate.obj +.\Release\cg_predict.obj +.\Release\cg_rankings.obj +.\Release\cg_scoreboard.obj +.\Release\cg_servercmds.obj +.\Release\cg_snapshot.obj +.\Release\cg_syscalls.obj +.\Release\cg_view.obj +.\Release\cg_weapons.obj +.\Release\q_math.obj +.\Release\q_shared.obj +.\Release\ui_shared.obj +] +Creating command line "link.exe @C:\WINNT\Profiles\ADMINI~1\LOCALS~1\Temp\RSP4BF.tmp" +

Output Window

+Compiling... +bg_misc.c +bg_pmove.c +D:\quake3\MissionPack\code\game\bg_pmove.c(987) : warning C4189: 'shit' : local variable is initialized but not referenced +D:\quake3\MissionPack\code\game\bg_pmove.c(2001) : warning C4505: 'PM_InvulnerabilityMove' : unreferenced local function has been removed + D:\quake3\MissionPack\code\game\bg_pmove.c(519) : see declaration of 'PM_InvulnerabilityMove' +bg_slidemove.c +cg_consolecmds.c +cg_draw.c +cg_drawtools.c +cg_effects.c +cg_ents.c +cg_event.c +cg_info.c +cg_localents.c +cg_main.c +D:\quake3\MissionPack\code\cgame\cg_main.c(1819) : warning C4505: 'CG_Cvar_Get' : unreferenced local function has been removed + D:\quake3\MissionPack\code\cgame\cg_main.c(1513) : see declaration of 'CG_Cvar_Get' +cg_marks.c +cg_players.c +D:\quake3\MissionPack\code\cgame\cg_players.c(2209) : warning C4505: 'CG_PlayerTokens' : unreferenced local function has been removed + D:\quake3\MissionPack\code\cgame\cg_players.c(1371) : see declaration of 'CG_PlayerTokens' +cg_playerstate.c +cg_predict.c +cg_rankings.c +cg_scoreboard.c +cg_servercmds.c +cg_snapshot.c +cg_syscalls.c +cg_view.c +cg_weapons.c +q_math.c +q_shared.c +ui_shared.c +D:\quake3\MissionPack\code\ui\ui_shared.c(2223) : warning C4189: 'parent' : local variable is initialized but not referenced +D:\quake3\MissionPack\code\ui\ui_shared.c(3501) : warning C4189: 'collision' : local variable is initialized but not referenced +D:\quake3\MissionPack\code\ui\ui_shared.c(4622) : warning C4505: 'Controls_SetDefaults' : unreferenced local function has been removed + D:\quake3\MissionPack\code\ui\ui_shared.c(2595) : see declaration of 'Controls_SetDefaults' +D:\quake3\MissionPack\code\ui\ui_shared.c(1540) : warning C4701: local variable 'value' may be used without having been initialized +D:\quake3\MissionPack\code\ui\ui_shared.c(1566) : warning C4701: local variable 'value' may be used without having been initialized +D:\quake3\MissionPack\code\ui\ui_shared.c(1912) : warning C4702: unreachable code +D:\quake3\MissionPack\code\ui\ui_shared.c(4013) : warning C4702: unreachable code +D:\quake3\MissionPack\code\ui\ui_shared.c(4056) : warning C4702: unreachable code +Linking... + Creating library Release/cgamex86.lib and object Release/cgamex86.exp + + + +

Results

+cgamex86.dll - 0 error(s), 12 warning(s) +
+ + diff --git a/code/cgame/cgame.q3asm b/code/cgame/cgame.q3asm index 7923f73..8bd1d4a 100755 --- a/code/cgame/cgame.q3asm +++ b/code/cgame/cgame.q3asm @@ -1,26 +1,26 @@ --o "\quake3\baseq3\vm\cgame" -cg_main -..\cg_syscalls -cg_consolecmds -cg_draw -cg_drawtools -cg_effects -cg_ents -cg_event -cg_info -cg_localents -cg_marks -cg_players -cg_playerstate -cg_predict -cg_scoreboard -cg_servercmds -cg_snapshot -cg_view -cg_weapons -bg_slidemove -bg_pmove -bg_lib -bg_misc -q_math -q_shared +-o "\quake3\baseq3\vm\cgame" +cg_main +..\cg_syscalls +cg_consolecmds +cg_draw +cg_drawtools +cg_effects +cg_ents +cg_event +cg_info +cg_localents +cg_marks +cg_players +cg_playerstate +cg_predict +cg_scoreboard +cg_servercmds +cg_snapshot +cg_view +cg_weapons +bg_slidemove +bg_pmove +bg_lib +bg_misc +q_math +q_shared diff --git a/code/cgame/cgame.sh b/code/cgame/cgame.sh index 62f2570..cdba268 100755 --- a/code/cgame/cgame.sh +++ b/code/cgame/cgame.sh @@ -1,36 +1,36 @@ -#!/bin/sh - -mkdir -p vm -cd vm - -CC="q3lcc -DQ3_VM -DCGAME -S -Wf-target=bytecode -Wf-g -I../../cgame -I../../game -I../../q3_ui" - -$CC ../cg_syscalls.c -$CC ../../game/bg_misc.c -$CC ../../game/bg_pmove.c -$CC ../../game/bg_slidemove.c -$CC ../../game/bg_lib.c -$CC ../../game/q_math.c -$CC ../../game/q_shared.c -$CC ../cg_consolecmds.c -$CC ../cg_draw.c -$CC ../cg_drawtools.c -$CC ../cg_effects.c -$CC ../cg_ents.c -$CC ../cg_event.c -$CC ../cg_info.c -$CC ../cg_localents.c -$CC ../cg_main.c -$CC ../cg_marks.c -$CC ../cg_players.c -$CC ../cg_playerstate.c -$CC ../cg_predict.c -$CC ../cg_scoreboard.c -$CC ../cg_servercmds.c -$CC ../cg_snapshot.c -$CC ../cg_view.c -$CC ../cg_weapons.c - -q3asm -f ../cgame - -cd .. +#!/bin/sh + +mkdir -p vm +cd vm + +CC="q3lcc -DQ3_VM -DCGAME -S -Wf-target=bytecode -Wf-g -I../../cgame -I../../game -I../../q3_ui" + +$CC ../cg_syscalls.c +$CC ../../game/bg_misc.c +$CC ../../game/bg_pmove.c +$CC ../../game/bg_slidemove.c +$CC ../../game/bg_lib.c +$CC ../../game/q_math.c +$CC ../../game/q_shared.c +$CC ../cg_consolecmds.c +$CC ../cg_draw.c +$CC ../cg_drawtools.c +$CC ../cg_effects.c +$CC ../cg_ents.c +$CC ../cg_event.c +$CC ../cg_info.c +$CC ../cg_localents.c +$CC ../cg_main.c +$CC ../cg_marks.c +$CC ../cg_players.c +$CC ../cg_playerstate.c +$CC ../cg_predict.c +$CC ../cg_scoreboard.c +$CC ../cg_servercmds.c +$CC ../cg_snapshot.c +$CC ../cg_view.c +$CC ../cg_weapons.c + +q3asm -f ../cgame + +cd .. diff --git a/code/cgame/cgame.vcproj b/code/cgame/cgame.vcproj index 4dbdfd0..fe2a212 100755 --- a/code/cgame/cgame.vcproj +++ b/code/cgame/cgame.vcproj @@ -1,1221 +1,1221 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/cgame/cgame_ta.bat b/code/cgame/cgame_ta.bat index 266e017..098be0e 100755 --- a/code/cgame/cgame_ta.bat +++ b/code/cgame/cgame_ta.bat @@ -1,65 +1,65 @@ -rem make sure we have a safe environement -set LIBRARY= -set INCLUDE= - -mkdir vm -cd vm -set cc=lcc -DQ3_VM -DMISSIONPACK -DCGAME -S -Wf-target=bytecode -Wf-g -I..\..\cgame -I..\..\game -I..\..\ui %1 - -%cc% ../../game/bg_misc.c -@if errorlevel 1 goto quit -%cc% ../../game/bg_pmove.c -@if errorlevel 1 goto quit -%cc% ../../game/bg_slidemove.c -@if errorlevel 1 goto quit -%cc% ../../game/bg_lib.c -@if errorlevel 1 goto quit -%cc% ../../game/q_math.c -@if errorlevel 1 goto quit -%cc% ../../game/q_shared.c -@if errorlevel 1 goto quit -%cc% ../cg_consolecmds.c -@if errorlevel 1 goto quit -%cc% ../cg_draw.c -@if errorlevel 1 goto quit -%cc% ../cg_drawtools.c -@if errorlevel 1 goto quit -%cc% ../cg_effects.c -@if errorlevel 1 goto quit -%cc% ../cg_ents.c -@if errorlevel 1 goto quit -%cc% ../cg_event.c -@if errorlevel 1 goto quit -%cc% ../cg_info.c -@if errorlevel 1 goto quit -%cc% ../cg_localents.c -@if errorlevel 1 goto quit -%cc% ../cg_main.c -@if errorlevel 1 goto quit -%cc% ../cg_marks.c -@if errorlevel 1 goto quit -%cc% ../cg_players.c -@if errorlevel 1 goto quit -%cc% ../cg_playerstate.c -@if errorlevel 1 goto quit -%cc% ../cg_predict.c -@if errorlevel 1 goto quit -%cc% ../cg_scoreboard.c -@if errorlevel 1 goto quit -%cc% ../cg_servercmds.c -@if errorlevel 1 goto quit -%cc% ../cg_snapshot.c -@if errorlevel 1 goto quit -%cc% ../cg_view.c -@if errorlevel 1 goto quit -%cc% ../cg_weapons.c -@if errorlevel 1 goto quit -%cc% ../../ui/ui_shared.c -@if errorlevel 1 goto quit -%cc% ../cg_newdraw.c -@if errorlevel 1 goto quit - - -q3asm -f ../cgame_ta -:quit -cd .. +rem make sure we have a safe environement +set LIBRARY= +set INCLUDE= + +mkdir vm +cd vm +set cc=lcc -DQ3_VM -DMISSIONPACK -DCGAME -S -Wf-target=bytecode -Wf-g -I..\..\cgame -I..\..\game -I..\..\ui %1 + +%cc% ../../game/bg_misc.c +@if errorlevel 1 goto quit +%cc% ../../game/bg_pmove.c +@if errorlevel 1 goto quit +%cc% ../../game/bg_slidemove.c +@if errorlevel 1 goto quit +%cc% ../../game/bg_lib.c +@if errorlevel 1 goto quit +%cc% ../../game/q_math.c +@if errorlevel 1 goto quit +%cc% ../../game/q_shared.c +@if errorlevel 1 goto quit +%cc% ../cg_consolecmds.c +@if errorlevel 1 goto quit +%cc% ../cg_draw.c +@if errorlevel 1 goto quit +%cc% ../cg_drawtools.c +@if errorlevel 1 goto quit +%cc% ../cg_effects.c +@if errorlevel 1 goto quit +%cc% ../cg_ents.c +@if errorlevel 1 goto quit +%cc% ../cg_event.c +@if errorlevel 1 goto quit +%cc% ../cg_info.c +@if errorlevel 1 goto quit +%cc% ../cg_localents.c +@if errorlevel 1 goto quit +%cc% ../cg_main.c +@if errorlevel 1 goto quit +%cc% ../cg_marks.c +@if errorlevel 1 goto quit +%cc% ../cg_players.c +@if errorlevel 1 goto quit +%cc% ../cg_playerstate.c +@if errorlevel 1 goto quit +%cc% ../cg_predict.c +@if errorlevel 1 goto quit +%cc% ../cg_scoreboard.c +@if errorlevel 1 goto quit +%cc% ../cg_servercmds.c +@if errorlevel 1 goto quit +%cc% ../cg_snapshot.c +@if errorlevel 1 goto quit +%cc% ../cg_view.c +@if errorlevel 1 goto quit +%cc% ../cg_weapons.c +@if errorlevel 1 goto quit +%cc% ../../ui/ui_shared.c +@if errorlevel 1 goto quit +%cc% ../cg_newdraw.c +@if errorlevel 1 goto quit + + +q3asm -f ../cgame_ta +:quit +cd .. diff --git a/code/cgame/cgame_ta.q3asm b/code/cgame/cgame_ta.q3asm index 8b14ed9..629a778 100755 --- a/code/cgame/cgame_ta.q3asm +++ b/code/cgame/cgame_ta.q3asm @@ -1,28 +1,28 @@ --o "\quake3\missionpack\vm\cgame" -cg_main -..\cg_syscalls -cg_consolecmds -cg_draw -cg_drawtools -cg_effects -cg_ents -cg_event -cg_info -cg_localents -cg_marks -cg_players -cg_playerstate -cg_predict -cg_scoreboard -cg_servercmds -cg_snapshot -cg_view -cg_weapons -bg_slidemove -bg_pmove -bg_lib -bg_misc -q_math -q_shared -ui_shared -cg_newdraw +-o "\quake3\missionpack\vm\cgame" +cg_main +..\cg_syscalls +cg_consolecmds +cg_draw +cg_drawtools +cg_effects +cg_ents +cg_event +cg_info +cg_localents +cg_marks +cg_players +cg_playerstate +cg_predict +cg_scoreboard +cg_servercmds +cg_snapshot +cg_view +cg_weapons +bg_slidemove +bg_pmove +bg_lib +bg_misc +q_math +q_shared +ui_shared +cg_newdraw diff --git a/code/cgame/cgame_ta.sh b/code/cgame/cgame_ta.sh index 5f87eb5..ce22b30 100755 --- a/code/cgame/cgame_ta.sh +++ b/code/cgame/cgame_ta.sh @@ -1,38 +1,38 @@ -#!/bin/sh - -mkdir -p vm -cd vm - -CC="q3lcc -DQ3_VM -DCGAME -DMISSIONPACK -S -Wf-target=bytecode -Wf-g -I../../cgame -I../../game -I../../ui" - -$CC ../cg_syscalls.c -$CC ../../game/bg_misc.c -$CC ../../game/bg_pmove.c -$CC ../../game/bg_slidemove.c -$CC ../../game/bg_lib.c -$CC ../../game/q_math.c -$CC ../../game/q_shared.c -$CC ../cg_consolecmds.c -$CC ../cg_draw.c -$CC ../cg_drawtools.c -$CC ../cg_effects.c -$CC ../cg_ents.c -$CC ../cg_event.c -$CC ../cg_info.c -$CC ../cg_localents.c -$CC ../cg_main.c -$CC ../cg_marks.c -$CC ../cg_players.c -$CC ../cg_playerstate.c -$CC ../cg_predict.c -$CC ../cg_scoreboard.c -$CC ../cg_servercmds.c -$CC ../cg_snapshot.c -$CC ../cg_view.c -$CC ../cg_weapons.c -$CC ../../ui/ui_shared.c -$CC ../cg_newdraw.c - -q3asm -f ../cgame_ta - -cd .. +#!/bin/sh + +mkdir -p vm +cd vm + +CC="q3lcc -DQ3_VM -DCGAME -DMISSIONPACK -S -Wf-target=bytecode -Wf-g -I../../cgame -I../../game -I../../ui" + +$CC ../cg_syscalls.c +$CC ../../game/bg_misc.c +$CC ../../game/bg_pmove.c +$CC ../../game/bg_slidemove.c +$CC ../../game/bg_lib.c +$CC ../../game/q_math.c +$CC ../../game/q_shared.c +$CC ../cg_consolecmds.c +$CC ../cg_draw.c +$CC ../cg_drawtools.c +$CC ../cg_effects.c +$CC ../cg_ents.c +$CC ../cg_event.c +$CC ../cg_info.c +$CC ../cg_localents.c +$CC ../cg_main.c +$CC ../cg_marks.c +$CC ../cg_players.c +$CC ../cg_playerstate.c +$CC ../cg_predict.c +$CC ../cg_scoreboard.c +$CC ../cg_servercmds.c +$CC ../cg_snapshot.c +$CC ../cg_view.c +$CC ../cg_weapons.c +$CC ../../ui/ui_shared.c +$CC ../cg_newdraw.c + +q3asm -f ../cgame_ta + +cd .. diff --git a/code/cgame/tr_types.h b/code/cgame/tr_types.h index ec2e054..d68a174 100755 --- a/code/cgame/tr_types.h +++ b/code/cgame/tr_types.h @@ -1,229 +1,229 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -#ifndef __TR_TYPES_H -#define __TR_TYPES_H - - -#define MAX_DLIGHTS 32 // can't be increased, because bit flags are used on surfaces -#define MAX_ENTITIES 1023 // can't be increased without changing drawsurf bit packing - -// renderfx flags -#define RF_MINLIGHT 1 // allways have some light (viewmodel, some items) -#define RF_THIRD_PERSON 2 // don't draw through eyes, only mirrors (player bodies, chat sprites) -#define RF_FIRST_PERSON 4 // only draw through eyes (view weapon, damage blood blob) -#define RF_DEPTHHACK 8 // for view weapon Z crunching -#define RF_NOSHADOW 64 // don't add stencil shadows - -#define RF_LIGHTING_ORIGIN 128 // 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 256 // use refEntity->shadowPlane -#define RF_WRAP_FRAMES 512 // mod the model frames by the maxframes to allow continuous - // animation without needing to know the frame count - -// refdef flags -#define RDF_NOWORLDMODEL 1 // used for player configuration screen -#define RDF_HYPERSPACE 4 // teleportation effect - -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_BEAM, - RT_RAIL_CORE, - RT_RAIL_RINGS, - RT_LIGHTNING, - RT_PORTALSURFACE, // doesn't draw anything, just info for portals - - 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 rgbgen entity shaders - float shaderTexCoord[2]; // texture coordinates used by tcMod entity modifiers - float shaderTime; // subtracted from refdef time to control effect start times - - // extra sprite information - float radius; - float rotation; -} 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 - - // 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 -} textureCompression_t; - -typedef enum { - GLDRV_ICD, // driver is integrated with window system - // WARNING: there are tests that check for - // > GLDRV_ICD for minidriverness, so this - // should always be the lowest value in this - // enum set - GLDRV_STANDALONE, // driver is a non-3Dfx standalone driver - GLDRV_VOODOO // driver is a 3Dfx standalone driver -} glDriverType_t; - -typedef enum { - GLHW_GENERIC, // where everthing works the way it should - GLHW_3DFX_2D3D, // Voodoo Banshee or Voodoo3, relevant since if this is - // the hardware type then there can NOT exist a secondary - // display adapter - GLHW_RIVA128, // where you can't interpolate alpha - GLHW_RAGEPRO, // where you can't modulate alpha on alpha textures - GLHW_PERMEDIA2 // where you don't have src*dst -} glHardwareType_t; - -typedef struct { - char renderer_string[MAX_STRING_CHARS]; - char vendor_string[MAX_STRING_CHARS]; - char version_string[MAX_STRING_CHARS]; - char extensions_string[BIG_INFO_STRING]; - - int maxTextureSize; // queried from GL - int maxActiveTextures; // multitexture ability - - int colorBits, depthBits, stencilBits; - - glDriverType_t driverType; - glHardwareType_t hardwareType; - - qboolean deviceSupportsGamma; - textureCompression_t textureCompression; - qboolean textureEnvAddAvailable; - - int vidWidth, vidHeight; - // aspect is the screen's physical width / height, which may be different - // than scrWidth / scrHeight if the pixels are non-square - // normal screens should be 4/3, but wide aspect monitors may be 16/9 - float windowAspect; - - 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; - qboolean smpActive; // dual processor -} glconfig_t; - -// FIXME: VM should be OS agnostic .. in theory - -/* -#ifdef Q3_VM - -#define _3DFX_DRIVER_NAME "Voodoo" -#define OPENGL_DRIVER_NAME "Default" - -#elif defined(_WIN32) -*/ - -#if defined(Q3_VM) || defined(_WIN32) - -#define _3DFX_DRIVER_NAME "3dfxvgl" -#define OPENGL_DRIVER_NAME "opengl32" - -#else - -#define _3DFX_DRIVER_NAME "libMesaVoodooGL.so" -// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=524 -#define OPENGL_DRIVER_NAME "libGL.so.1" - -#endif // !defined _WIN32 - -#endif // __TR_TYPES_H +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +#ifndef __TR_TYPES_H +#define __TR_TYPES_H + + +#define MAX_DLIGHTS 32 // can't be increased, because bit flags are used on surfaces +#define MAX_ENTITIES 1023 // can't be increased without changing drawsurf bit packing + +// renderfx flags +#define RF_MINLIGHT 1 // allways have some light (viewmodel, some items) +#define RF_THIRD_PERSON 2 // don't draw through eyes, only mirrors (player bodies, chat sprites) +#define RF_FIRST_PERSON 4 // only draw through eyes (view weapon, damage blood blob) +#define RF_DEPTHHACK 8 // for view weapon Z crunching +#define RF_NOSHADOW 64 // don't add stencil shadows + +#define RF_LIGHTING_ORIGIN 128 // 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 256 // use refEntity->shadowPlane +#define RF_WRAP_FRAMES 512 // mod the model frames by the maxframes to allow continuous + // animation without needing to know the frame count + +// refdef flags +#define RDF_NOWORLDMODEL 1 // used for player configuration screen +#define RDF_HYPERSPACE 4 // teleportation effect + +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_BEAM, + RT_RAIL_CORE, + RT_RAIL_RINGS, + RT_LIGHTNING, + RT_PORTALSURFACE, // doesn't draw anything, just info for portals + + 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 rgbgen entity shaders + float shaderTexCoord[2]; // texture coordinates used by tcMod entity modifiers + float shaderTime; // subtracted from refdef time to control effect start times + + // extra sprite information + float radius; + float rotation; +} 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 + + // 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 +} textureCompression_t; + +typedef enum { + GLDRV_ICD, // driver is integrated with window system + // WARNING: there are tests that check for + // > GLDRV_ICD for minidriverness, so this + // should always be the lowest value in this + // enum set + GLDRV_STANDALONE, // driver is a non-3Dfx standalone driver + GLDRV_VOODOO // driver is a 3Dfx standalone driver +} glDriverType_t; + +typedef enum { + GLHW_GENERIC, // where everthing works the way it should + GLHW_3DFX_2D3D, // Voodoo Banshee or Voodoo3, relevant since if this is + // the hardware type then there can NOT exist a secondary + // display adapter + GLHW_RIVA128, // where you can't interpolate alpha + GLHW_RAGEPRO, // where you can't modulate alpha on alpha textures + GLHW_PERMEDIA2 // where you don't have src*dst +} glHardwareType_t; + +typedef struct { + char renderer_string[MAX_STRING_CHARS]; + char vendor_string[MAX_STRING_CHARS]; + char version_string[MAX_STRING_CHARS]; + char extensions_string[BIG_INFO_STRING]; + + int maxTextureSize; // queried from GL + int maxActiveTextures; // multitexture ability + + int colorBits, depthBits, stencilBits; + + glDriverType_t driverType; + glHardwareType_t hardwareType; + + qboolean deviceSupportsGamma; + textureCompression_t textureCompression; + qboolean textureEnvAddAvailable; + + int vidWidth, vidHeight; + // aspect is the screen's physical width / height, which may be different + // than scrWidth / scrHeight if the pixels are non-square + // normal screens should be 4/3, but wide aspect monitors may be 16/9 + float windowAspect; + + 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; + qboolean smpActive; // dual processor +} glconfig_t; + +// FIXME: VM should be OS agnostic .. in theory + +/* +#ifdef Q3_VM + +#define _3DFX_DRIVER_NAME "Voodoo" +#define OPENGL_DRIVER_NAME "Default" + +#elif defined(_WIN32) +*/ + +#if defined(Q3_VM) || defined(_WIN32) + +#define _3DFX_DRIVER_NAME "3dfxvgl" +#define OPENGL_DRIVER_NAME "opengl32" + +#else + +#define _3DFX_DRIVER_NAME "libMesaVoodooGL.so" +// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=524 +#define OPENGL_DRIVER_NAME "libGL.so.1" + +#endif // !defined _WIN32 + +#endif // __TR_TYPES_H diff --git a/code/clean.bat b/code/clean.bat index 3daad51..c756cf7 100755 --- a/code/clean.bat +++ b/code/clean.bat @@ -1,61 +1,61 @@ -rmdir debug /s /q -rmdir release /s /q -del quake3.ncb -del quake3.opt -del quake3.plg -del quake3.stt -rmdir cgame\debug /s /q -rmdir cgame\release /s /q -del cgame\cgame.ncb -del cgame\cgame.opt -del cgame\cgame.plg -del cgame\cgame.stt -rmdir game\debug /s /q -rmdir game\release /s /q -del game\game.ncb -del game\game.opt -del game\game.plg -del game\game.stt -rmdir ui\debug /s /q -rmdir ui\release /s /q -del ui\ui.ncb -del ui\ui.opt -del ui\ui.plg -del ui\ui.stt -rmdir renderer\debug /s /q -rmdir renderer\release /s /q -del renderer\renderer.ncb -del renderer\renderer.opt -del renderer\renderer.plg -del renderer\renderer.stt -rmdir botlib\debug /s /q -rmdir botlib\release /s /q -del botlib\botlib.ncb -del botlib\botlib.opt -del botlib\botlib.plg -del botlib\botlib.stt -rmdir botlai\debug /s /q -rmdir botlai\release /s /q -del botai\botai.dsp -del botai\botai.plg -rmdir bspc\debug /s /q -rmdir bspc\release /s /q -del bspc\bspc.exe -del bspc\bspc.log -del bspc\bspc.ncb -del bspc\bspc.opt -del bspc\bspc.pdb -del bspc\bspc.plg -rmdir unix\debugi386-glibc /s /q -rmdir unix\releasei386-glibc /s /q -rmdir "mac\MacQuake3 Data" /s /q -rmdir macosx\Client\Q3Test.app /s /q -rmdir macosx\Client\Q3Test.build /s /q -del *.o /s -del *.obj /s -del *.lib /s -del *.dll /s -del *.ncb /s -del *.plg /s -del *.map /s -del *.opt /s +rmdir debug /s /q +rmdir release /s /q +del quake3.ncb +del quake3.opt +del quake3.plg +del quake3.stt +rmdir cgame\debug /s /q +rmdir cgame\release /s /q +del cgame\cgame.ncb +del cgame\cgame.opt +del cgame\cgame.plg +del cgame\cgame.stt +rmdir game\debug /s /q +rmdir game\release /s /q +del game\game.ncb +del game\game.opt +del game\game.plg +del game\game.stt +rmdir ui\debug /s /q +rmdir ui\release /s /q +del ui\ui.ncb +del ui\ui.opt +del ui\ui.plg +del ui\ui.stt +rmdir renderer\debug /s /q +rmdir renderer\release /s /q +del renderer\renderer.ncb +del renderer\renderer.opt +del renderer\renderer.plg +del renderer\renderer.stt +rmdir botlib\debug /s /q +rmdir botlib\release /s /q +del botlib\botlib.ncb +del botlib\botlib.opt +del botlib\botlib.plg +del botlib\botlib.stt +rmdir botlai\debug /s /q +rmdir botlai\release /s /q +del botai\botai.dsp +del botai\botai.plg +rmdir bspc\debug /s /q +rmdir bspc\release /s /q +del bspc\bspc.exe +del bspc\bspc.log +del bspc\bspc.ncb +del bspc\bspc.opt +del bspc\bspc.pdb +del bspc\bspc.plg +rmdir unix\debugi386-glibc /s /q +rmdir unix\releasei386-glibc /s /q +rmdir "mac\MacQuake3 Data" /s /q +rmdir macosx\Client\Q3Test.app /s /q +rmdir macosx\Client\Q3Test.build /s /q +del *.o /s +del *.obj /s +del *.lib /s +del *.dll /s +del *.ncb /s +del *.plg /s +del *.map /s +del *.opt /s diff --git a/code/client/cl_cgame.c b/code/client/cl_cgame.c index 0b259d3..56dc01c 100755 --- a/code/client/cl_cgame.c +++ b/code/client/cl_cgame.c @@ -1,1029 +1,1029 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// cl_cgame.c -- client system interaction with client game - -#include "client.h" - -#include "../game/botlib.h" - -extern botlib_export_t *botlib_export; - -extern qboolean loadCamera(const char *name); -extern void startCamera(int time); -extern qboolean getCameraInfo(int time, vec3_t *origin, vec3_t *angles); - -/* -==================== -CL_GetGameState -==================== -*/ -void CL_GetGameState( gameState_t *gs ) { - *gs = cl.gameState; -} - -/* -==================== -CL_GetGlconfig -==================== -*/ -void CL_GetGlconfig( glconfig_t *glconfig ) { - *glconfig = cls.glconfig; -} - - -/* -==================== -CL_GetUserCmd -==================== -*/ -qboolean CL_GetUserCmd( int cmdNumber, usercmd_t *ucmd ) { - // cmds[cmdNumber] is the last properly generated command - - // can't return anything that we haven't created yet - if ( cmdNumber > cl.cmdNumber ) { - Com_Error( ERR_DROP, "CL_GetUserCmd: %i >= %i", cmdNumber, cl.cmdNumber ); - } - - // the usercmd has been overwritten in the wrapping - // buffer because it is too far out of date - if ( cmdNumber <= cl.cmdNumber - CMD_BACKUP ) { - return qfalse; - } - - *ucmd = cl.cmds[ cmdNumber & CMD_MASK ]; - - return qtrue; -} - -int CL_GetCurrentCmdNumber( void ) { - return cl.cmdNumber; -} - - -/* -==================== -CL_GetParseEntityState -==================== -*/ -qboolean CL_GetParseEntityState( int parseEntityNumber, entityState_t *state ) { - // can't return anything that hasn't been parsed yet - if ( parseEntityNumber >= cl.parseEntitiesNum ) { - Com_Error( ERR_DROP, "CL_GetParseEntityState: %i >= %i", - parseEntityNumber, cl.parseEntitiesNum ); - } - - // can't return anything that has been overwritten in the circular buffer - if ( parseEntityNumber <= cl.parseEntitiesNum - MAX_PARSE_ENTITIES ) { - return qfalse; - } - - *state = cl.parseEntities[ parseEntityNumber & ( MAX_PARSE_ENTITIES - 1 ) ]; - return qtrue; -} - -/* -==================== -CL_GetCurrentSnapshotNumber -==================== -*/ -void CL_GetCurrentSnapshotNumber( int *snapshotNumber, int *serverTime ) { - *snapshotNumber = cl.snap.messageNum; - *serverTime = cl.snap.serverTime; -} - -/* -==================== -CL_GetSnapshot -==================== -*/ -qboolean CL_GetSnapshot( int snapshotNumber, snapshot_t *snapshot ) { - clSnapshot_t *clSnap; - int i, count; - - if ( snapshotNumber > cl.snap.messageNum ) { - Com_Error( ERR_DROP, "CL_GetSnapshot: snapshotNumber > cl.snapshot.messageNum" ); - } - - // if the frame has fallen out of the circular buffer, we can't return it - if ( cl.snap.messageNum - snapshotNumber >= PACKET_BACKUP ) { - return qfalse; - } - - // if the frame is not valid, we can't return it - clSnap = &cl.snapshots[snapshotNumber & PACKET_MASK]; - if ( !clSnap->valid ) { - return qfalse; - } - - // if the entities in the frame have fallen out of their - // circular buffer, we can't return it - if ( cl.parseEntitiesNum - clSnap->parseEntitiesNum >= MAX_PARSE_ENTITIES ) { - return qfalse; - } - - // write the snapshot - snapshot->snapFlags = clSnap->snapFlags; - snapshot->serverCommandSequence = clSnap->serverCommandNum; - snapshot->ping = clSnap->ping; - snapshot->serverTime = clSnap->serverTime; - Com_Memcpy( snapshot->areamask, clSnap->areamask, sizeof( snapshot->areamask ) ); - snapshot->ps = clSnap->ps; - count = clSnap->numEntities; - if ( count > MAX_ENTITIES_IN_SNAPSHOT ) { - Com_DPrintf( "CL_GetSnapshot: truncated %i entities to %i\n", count, MAX_ENTITIES_IN_SNAPSHOT ); - count = MAX_ENTITIES_IN_SNAPSHOT; - } - snapshot->numEntities = count; - for ( i = 0 ; i < count ; i++ ) { - snapshot->entities[i] = - cl.parseEntities[ ( clSnap->parseEntitiesNum + i ) & (MAX_PARSE_ENTITIES-1) ]; - } - - // FIXME: configstring changes and server commands!!! - - return qtrue; -} - -/* -===================== -CL_SetUserCmdValue -===================== -*/ -void CL_SetUserCmdValue( int userCmdValue, float sensitivityScale ) { - cl.cgameUserCmdValue = userCmdValue; - cl.cgameSensitivity = sensitivityScale; -} - -/* -===================== -CL_AddCgameCommand -===================== -*/ -void CL_AddCgameCommand( const char *cmdName ) { - Cmd_AddCommand( cmdName, NULL ); -} - -/* -===================== -CL_CgameError -===================== -*/ -void CL_CgameError( const char *string ) { - Com_Error( ERR_DROP, "%s", string ); -} - - -/* -===================== -CL_ConfigstringModified -===================== -*/ -void CL_ConfigstringModified( void ) { - char *old, *s; - int i, index; - char *dup; - gameState_t oldGs; - int len; - - index = atoi( Cmd_Argv(1) ); - if ( index < 0 || index >= MAX_CONFIGSTRINGS ) { - Com_Error( ERR_DROP, "configstring > MAX_CONFIGSTRINGS" ); - } - // get everything after "cs " - s = Cmd_ArgsFrom(2); - - old = cl.gameState.stringData + cl.gameState.stringOffsets[ index ]; - if ( !strcmp( old, s ) ) { - return; // unchanged - } - - // build the new gameState_t - oldGs = cl.gameState; - - Com_Memset( &cl.gameState, 0, sizeof( cl.gameState ) ); - - // leave the first 0 for uninitialized strings - cl.gameState.dataCount = 1; - - for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { - if ( i == index ) { - dup = s; - } else { - dup = oldGs.stringData + oldGs.stringOffsets[ i ]; - } - if ( !dup[0] ) { - continue; // leave with the default empty string - } - - len = strlen( dup ); - - if ( len + 1 + cl.gameState.dataCount > MAX_GAMESTATE_CHARS ) { - Com_Error( ERR_DROP, "MAX_GAMESTATE_CHARS exceeded" ); - } - - // append it to the gameState string buffer - cl.gameState.stringOffsets[ i ] = cl.gameState.dataCount; - Com_Memcpy( cl.gameState.stringData + cl.gameState.dataCount, dup, len + 1 ); - cl.gameState.dataCount += len + 1; - } - - if ( index == CS_SYSTEMINFO ) { - // parse serverId and other cvars - CL_SystemInfoChanged(); - } - -} - - -/* -=================== -CL_GetServerCommand - -Set up argc/argv for the given command -=================== -*/ -qboolean CL_GetServerCommand( int serverCommandNumber ) { - char *s; - char *cmd; - static char bigConfigString[BIG_INFO_STRING]; - int argc; - - // if we have irretrievably lost a reliable command, drop the connection - if ( serverCommandNumber <= clc.serverCommandSequence - MAX_RELIABLE_COMMANDS ) { - // when a demo record was started after the client got a whole bunch of - // reliable commands then the client never got those first reliable commands - if ( clc.demoplaying ) - return qfalse; - Com_Error( ERR_DROP, "CL_GetServerCommand: a reliable command was cycled out" ); - return qfalse; - } - - if ( serverCommandNumber > clc.serverCommandSequence ) { - Com_Error( ERR_DROP, "CL_GetServerCommand: requested a command not received" ); - return qfalse; - } - - s = clc.serverCommands[ serverCommandNumber & ( MAX_RELIABLE_COMMANDS - 1 ) ]; - clc.lastExecutedServerCommand = serverCommandNumber; - - Com_DPrintf( "serverCommand: %i : %s\n", serverCommandNumber, s ); - -rescan: - Cmd_TokenizeString( s ); - cmd = Cmd_Argv(0); - argc = Cmd_Argc(); - - if ( !strcmp( cmd, "disconnect" ) ) { - // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=552 - // allow server to indicate why they were disconnected - if ( argc >= 2 ) - Com_Error (ERR_SERVERDISCONNECT, va( "Server Disconnected - %s", Cmd_Argv( 1 ) ) ); - else - Com_Error (ERR_SERVERDISCONNECT,"Server disconnected\n"); - } - - if ( !strcmp( cmd, "bcs0" ) ) { - Com_sprintf( bigConfigString, BIG_INFO_STRING, "cs %s \"%s", Cmd_Argv(1), Cmd_Argv(2) ); - return qfalse; - } - - if ( !strcmp( cmd, "bcs1" ) ) { - s = Cmd_Argv(2); - if( strlen(bigConfigString) + strlen(s) >= BIG_INFO_STRING ) { - Com_Error( ERR_DROP, "bcs exceeded BIG_INFO_STRING" ); - } - strcat( bigConfigString, s ); - return qfalse; - } - - if ( !strcmp( cmd, "bcs2" ) ) { - s = Cmd_Argv(2); - if( strlen(bigConfigString) + strlen(s) + 1 >= BIG_INFO_STRING ) { - Com_Error( ERR_DROP, "bcs exceeded BIG_INFO_STRING" ); - } - strcat( bigConfigString, s ); - strcat( bigConfigString, "\"" ); - s = bigConfigString; - goto rescan; - } - - if ( !strcmp( cmd, "cs" ) ) { - CL_ConfigstringModified(); - // reparse the string, because CL_ConfigstringModified may have done another Cmd_TokenizeString() - Cmd_TokenizeString( s ); - return qtrue; - } - - if ( !strcmp( cmd, "map_restart" ) ) { - // clear notify lines and outgoing commands before passing - // the restart to the cgame - Con_ClearNotify(); - Com_Memset( cl.cmds, 0, sizeof( cl.cmds ) ); - return qtrue; - } - - // the clientLevelShot command is used during development - // to generate 128*128 screenshots from the intermission - // point of levels for the menu system to use - // we pass it along to the cgame to make apropriate adjustments, - // but we also clear the console and notify lines here - if ( !strcmp( cmd, "clientLevelShot" ) ) { - // don't do it if we aren't running the server locally, - // otherwise malicious remote servers could overwrite - // the existing thumbnails - if ( !com_sv_running->integer ) { - return qfalse; - } - // close the console - Con_Close(); - // take a special screenshot next frame - Cbuf_AddText( "wait ; wait ; wait ; wait ; screenshot levelshot\n" ); - return qtrue; - } - - // we may want to put a "connect to other server" command here - - // cgame can now act on the command - return qtrue; -} - - -/* -==================== -CL_CM_LoadMap - -Just adds default parameters that cgame doesn't need to know about -==================== -*/ -void CL_CM_LoadMap( const char *mapname ) { - int checksum; - - CM_LoadMap( mapname, qtrue, &checksum ); -} - -/* -==================== -CL_ShutdonwCGame - -==================== -*/ -void CL_ShutdownCGame( void ) { - cls.keyCatchers &= ~KEYCATCH_CGAME; - cls.cgameStarted = qfalse; - if ( !cgvm ) { - return; - } - VM_Call( cgvm, CG_SHUTDOWN ); - VM_Free( cgvm ); - cgvm = NULL; -} - -static int FloatAsInt( float f ) { - int temp; - - *(float *)&temp = f; - - return temp; -} - -/* -==================== -CL_CgameSystemCalls - -The cgame module is making a system call -==================== -*/ -#define VMA(x) VM_ArgPtr(args[x]) -#define VMF(x) ((float *)args)[x] -int CL_CgameSystemCalls( int *args ) { - switch( args[0] ) { - case CG_PRINT: - Com_Printf( "%s", VMA(1) ); - return 0; - case CG_ERROR: - Com_Error( ERR_DROP, "%s", VMA(1) ); - return 0; - case CG_MILLISECONDS: - return Sys_Milliseconds(); - case CG_CVAR_REGISTER: - Cvar_Register( VMA(1), VMA(2), VMA(3), args[4] ); - return 0; - case CG_CVAR_UPDATE: - Cvar_Update( VMA(1) ); - return 0; - case CG_CVAR_SET: - Cvar_Set( VMA(1), VMA(2) ); - return 0; - case CG_CVAR_VARIABLESTRINGBUFFER: - Cvar_VariableStringBuffer( VMA(1), VMA(2), args[3] ); - return 0; - case CG_ARGC: - return Cmd_Argc(); - case CG_ARGV: - Cmd_ArgvBuffer( args[1], VMA(2), args[3] ); - return 0; - case CG_ARGS: - Cmd_ArgsBuffer( VMA(1), args[2] ); - return 0; - case CG_FS_FOPENFILE: - return FS_FOpenFileByMode( VMA(1), VMA(2), args[3] ); - case CG_FS_READ: - FS_Read2( VMA(1), args[2], args[3] ); - return 0; - case CG_FS_WRITE: - FS_Write( VMA(1), args[2], args[3] ); - return 0; - case CG_FS_FCLOSEFILE: - FS_FCloseFile( args[1] ); - return 0; - case CG_FS_SEEK: - return FS_Seek( args[1], args[2], args[3] ); - case CG_SENDCONSOLECOMMAND: - Cbuf_AddText( VMA(1) ); - return 0; - case CG_ADDCOMMAND: - CL_AddCgameCommand( VMA(1) ); - return 0; - case CG_REMOVECOMMAND: - Cmd_RemoveCommand( VMA(1) ); - return 0; - case CG_SENDCLIENTCOMMAND: - CL_AddReliableCommand( VMA(1) ); - return 0; - case CG_UPDATESCREEN: - // this is used during lengthy level loading, so pump message loop -// Com_EventLoop(); // FIXME: if a server restarts here, BAD THINGS HAPPEN! -// We can't call Com_EventLoop here, a restart will crash and this _does_ happen -// if there is a map change while we are downloading at pk3. -// ZOID - SCR_UpdateScreen(); - return 0; - case CG_CM_LOADMAP: - CL_CM_LoadMap( VMA(1) ); - return 0; - case CG_CM_NUMINLINEMODELS: - return CM_NumInlineModels(); - case CG_CM_INLINEMODEL: - return CM_InlineModel( args[1] ); - case CG_CM_TEMPBOXMODEL: - return CM_TempBoxModel( VMA(1), VMA(2), /*int capsule*/ qfalse ); - case CG_CM_TEMPCAPSULEMODEL: - return CM_TempBoxModel( VMA(1), VMA(2), /*int capsule*/ qtrue ); - case CG_CM_POINTCONTENTS: - return CM_PointContents( VMA(1), args[2] ); - case CG_CM_TRANSFORMEDPOINTCONTENTS: - return CM_TransformedPointContents( VMA(1), args[2], VMA(3), VMA(4) ); - case CG_CM_BOXTRACE: - CM_BoxTrace( VMA(1), VMA(2), VMA(3), VMA(4), VMA(5), args[6], args[7], /*int capsule*/ qfalse ); - return 0; - case CG_CM_CAPSULETRACE: - CM_BoxTrace( VMA(1), VMA(2), VMA(3), VMA(4), VMA(5), args[6], args[7], /*int capsule*/ qtrue ); - return 0; - case CG_CM_TRANSFORMEDBOXTRACE: - CM_TransformedBoxTrace( VMA(1), VMA(2), VMA(3), VMA(4), VMA(5), args[6], args[7], VMA(8), VMA(9), /*int capsule*/ qfalse ); - return 0; - case CG_CM_TRANSFORMEDCAPSULETRACE: - CM_TransformedBoxTrace( VMA(1), VMA(2), VMA(3), VMA(4), VMA(5), args[6], args[7], VMA(8), VMA(9), /*int capsule*/ qtrue ); - return 0; - case CG_CM_MARKFRAGMENTS: - return re.MarkFragments( args[1], VMA(2), VMA(3), args[4], VMA(5), args[6], VMA(7) ); - case CG_S_STARTSOUND: - S_StartSound( VMA(1), args[2], args[3], args[4] ); - return 0; - case CG_S_STARTLOCALSOUND: - S_StartLocalSound( args[1], args[2] ); - return 0; - case CG_S_CLEARLOOPINGSOUNDS: - S_ClearLoopingSounds(args[1]); - return 0; - case CG_S_ADDLOOPINGSOUND: - S_AddLoopingSound( args[1], VMA(2), VMA(3), args[4] ); - return 0; - case CG_S_ADDREALLOOPINGSOUND: - S_AddRealLoopingSound( args[1], VMA(2), VMA(3), args[4] ); - return 0; - case CG_S_STOPLOOPINGSOUND: - S_StopLoopingSound( args[1] ); - return 0; - case CG_S_UPDATEENTITYPOSITION: - S_UpdateEntityPosition( args[1], VMA(2) ); - return 0; - case CG_S_RESPATIALIZE: - S_Respatialize( args[1], VMA(2), VMA(3), args[4] ); - return 0; - case CG_S_REGISTERSOUND: - return S_RegisterSound( VMA(1), args[2] ); - case CG_S_STARTBACKGROUNDTRACK: - S_StartBackgroundTrack( VMA(1), VMA(2) ); - return 0; - case CG_R_LOADWORLDMAP: - re.LoadWorld( VMA(1) ); - return 0; - case CG_R_REGISTERMODEL: - return re.RegisterModel( VMA(1) ); - case CG_R_REGISTERSKIN: - return re.RegisterSkin( VMA(1) ); - case CG_R_REGISTERSHADER: - return re.RegisterShader( VMA(1) ); - case CG_R_REGISTERSHADERNOMIP: - return re.RegisterShaderNoMip( VMA(1) ); - case CG_R_REGISTERFONT: - re.RegisterFont( VMA(1), args[2], VMA(3)); - case CG_R_CLEARSCENE: - re.ClearScene(); - return 0; - case CG_R_ADDREFENTITYTOSCENE: - re.AddRefEntityToScene( VMA(1) ); - return 0; - case CG_R_ADDPOLYTOSCENE: - re.AddPolyToScene( args[1], args[2], VMA(3), 1 ); - return 0; - case CG_R_ADDPOLYSTOSCENE: - re.AddPolyToScene( args[1], args[2], VMA(3), args[4] ); - return 0; - case CG_R_LIGHTFORPOINT: - return re.LightForPoint( VMA(1), VMA(2), VMA(3), VMA(4) ); - case CG_R_ADDLIGHTTOSCENE: - re.AddLightToScene( VMA(1), VMF(2), VMF(3), VMF(4), VMF(5) ); - return 0; - case CG_R_ADDADDITIVELIGHTTOSCENE: - re.AddAdditiveLightToScene( VMA(1), VMF(2), VMF(3), VMF(4), VMF(5) ); - return 0; - case CG_R_RENDERSCENE: - re.RenderScene( VMA(1) ); - return 0; - case CG_R_SETCOLOR: - re.SetColor( VMA(1) ); - return 0; - case CG_R_DRAWSTRETCHPIC: - re.DrawStretchPic( VMF(1), VMF(2), VMF(3), VMF(4), VMF(5), VMF(6), VMF(7), VMF(8), args[9] ); - return 0; - case CG_R_MODELBOUNDS: - re.ModelBounds( args[1], VMA(2), VMA(3) ); - return 0; - case CG_R_LERPTAG: - return re.LerpTag( VMA(1), args[2], args[3], args[4], VMF(5), VMA(6) ); - case CG_GETGLCONFIG: - CL_GetGlconfig( VMA(1) ); - return 0; - case CG_GETGAMESTATE: - CL_GetGameState( VMA(1) ); - return 0; - case CG_GETCURRENTSNAPSHOTNUMBER: - CL_GetCurrentSnapshotNumber( VMA(1), VMA(2) ); - return 0; - case CG_GETSNAPSHOT: - return CL_GetSnapshot( args[1], VMA(2) ); - case CG_GETSERVERCOMMAND: - return CL_GetServerCommand( args[1] ); - case CG_GETCURRENTCMDNUMBER: - return CL_GetCurrentCmdNumber(); - case CG_GETUSERCMD: - return CL_GetUserCmd( args[1], VMA(2) ); - case CG_SETUSERCMDVALUE: - CL_SetUserCmdValue( args[1], VMF(2) ); - return 0; - case CG_MEMORY_REMAINING: - return Hunk_MemoryRemaining(); - case CG_KEY_ISDOWN: - return Key_IsDown( args[1] ); - case CG_KEY_GETCATCHER: - return Key_GetCatcher(); - case CG_KEY_SETCATCHER: - Key_SetCatcher( args[1] ); - return 0; - case CG_KEY_GETKEY: - return Key_GetKey( VMA(1) ); - - - - case CG_MEMSET: - Com_Memset( VMA(1), args[2], args[3] ); - return 0; - case CG_MEMCPY: - Com_Memcpy( VMA(1), VMA(2), args[3] ); - return 0; - case CG_STRNCPY: - return (int)strncpy( VMA(1), VMA(2), args[3] ); - case CG_SIN: - return FloatAsInt( sin( VMF(1) ) ); - case CG_COS: - return FloatAsInt( cos( VMF(1) ) ); - case CG_ATAN2: - return FloatAsInt( atan2( VMF(1), VMF(2) ) ); - case CG_SQRT: - return FloatAsInt( sqrt( VMF(1) ) ); - case CG_FLOOR: - return FloatAsInt( floor( VMF(1) ) ); - case CG_CEIL: - return FloatAsInt( ceil( VMF(1) ) ); - case CG_ACOS: - return FloatAsInt( Q_acos( VMF(1) ) ); - - case CG_PC_ADD_GLOBAL_DEFINE: - return botlib_export->PC_AddGlobalDefine( VMA(1) ); - case CG_PC_LOAD_SOURCE: - return botlib_export->PC_LoadSourceHandle( VMA(1) ); - case CG_PC_FREE_SOURCE: - return botlib_export->PC_FreeSourceHandle( args[1] ); - case CG_PC_READ_TOKEN: - return botlib_export->PC_ReadTokenHandle( args[1], VMA(2) ); - case CG_PC_SOURCE_FILE_AND_LINE: - return botlib_export->PC_SourceFileAndLine( args[1], VMA(2), VMA(3) ); - - case CG_S_STOPBACKGROUNDTRACK: - S_StopBackgroundTrack(); - return 0; - - case CG_REAL_TIME: - return Com_RealTime( VMA(1) ); - case CG_SNAPVECTOR: - Sys_SnapVector( VMA(1) ); - return 0; - - case CG_CIN_PLAYCINEMATIC: - return CIN_PlayCinematic(VMA(1), args[2], args[3], args[4], args[5], args[6]); - - case CG_CIN_STOPCINEMATIC: - return CIN_StopCinematic(args[1]); - - case CG_CIN_RUNCINEMATIC: - return CIN_RunCinematic(args[1]); - - case CG_CIN_DRAWCINEMATIC: - CIN_DrawCinematic(args[1]); - return 0; - - case CG_CIN_SETEXTENTS: - CIN_SetExtents(args[1], args[2], args[3], args[4], args[5]); - return 0; - - case CG_R_REMAP_SHADER: - re.RemapShader( VMA(1), VMA(2), VMA(3) ); - return 0; - -/* - case CG_LOADCAMERA: - return loadCamera(VMA(1)); - - case CG_STARTCAMERA: - startCamera(args[1]); - return 0; - - case CG_GETCAMERAINFO: - return getCameraInfo(args[1], VMA(2), VMA(3)); -*/ - case CG_GET_ENTITY_TOKEN: - return re.GetEntityToken( VMA(1), args[2] ); - case CG_R_INPVS: - return re.inPVS( VMA(1), VMA(2) ); - - default: - assert(0); // bk010102 - Com_Error( ERR_DROP, "Bad cgame system trap: %i", args[0] ); - } - return 0; -} - - -/* -==================== -CL_InitCGame - -Should only be called by CL_StartHunkUsers -==================== -*/ -void CL_InitCGame( void ) { - const char *info; - const char *mapname; - int t1, t2; - vmInterpret_t interpret; - - t1 = Sys_Milliseconds(); - - // put away the console - Con_Close(); - - // find the current mapname - info = cl.gameState.stringData + cl.gameState.stringOffsets[ CS_SERVERINFO ]; - mapname = Info_ValueForKey( info, "mapname" ); - Com_sprintf( cl.mapname, sizeof( cl.mapname ), "maps/%s.bsp", mapname ); - - // load the dll or bytecode - if ( cl_connectedToPureServer != 0 ) { - // if sv_pure is set we only allow qvms to be loaded - interpret = VMI_COMPILED; - } - else { - interpret = Cvar_VariableValue( "vm_cgame" ); - } - cgvm = VM_Create( "cgame", CL_CgameSystemCalls, interpret ); - if ( !cgvm ) { - Com_Error( ERR_DROP, "VM_Create on cgame failed" ); - } - cls.state = CA_LOADING; - - // init for this gamestate - // use the lastExecutedServerCommand instead of the serverCommandSequence - // otherwise server commands sent just before a gamestate are dropped - VM_Call( cgvm, CG_INIT, clc.serverMessageSequence, clc.lastExecutedServerCommand, clc.clientNum ); - - // we will send a usercmd this frame, which - // will cause the server to send us the first snapshot - cls.state = CA_PRIMED; - - t2 = Sys_Milliseconds(); - - Com_Printf( "CL_InitCGame: %5.2f seconds\n", (t2-t1)/1000.0 ); - - // have the renderer touch all its images, so they are present - // on the card even if the driver does deferred loading - re.EndRegistration(); - - // make sure everything is paged in - if (!Sys_LowPhysicalMemory()) { - Com_TouchMemory(); - } - - // clear anything that got printed - Con_ClearNotify (); -} - - -/* -==================== -CL_GameCommand - -See if the current console command is claimed by the cgame -==================== -*/ -qboolean CL_GameCommand( void ) { - if ( !cgvm ) { - return qfalse; - } - - return VM_Call( cgvm, CG_CONSOLE_COMMAND ); -} - - - -/* -===================== -CL_CGameRendering -===================== -*/ -void CL_CGameRendering( stereoFrame_t stereo ) { - VM_Call( cgvm, CG_DRAW_ACTIVE_FRAME, cl.serverTime, stereo, clc.demoplaying ); - VM_Debug( 0 ); -} - - -/* -================= -CL_AdjustTimeDelta - -Adjust the clients view of server time. - -We attempt to have cl.serverTime exactly equal the server's view -of time plus the timeNudge, but with variable latencies over -the internet it will often need to drift a bit to match conditions. - -Our ideal time would be to have the adjusted time approach, but not pass, -the very latest snapshot. - -Adjustments are only made when a new snapshot arrives with a rational -latency, which keeps the adjustment process framerate independent and -prevents massive overadjustment during times of significant packet loss -or bursted delayed packets. -================= -*/ - -#define RESET_TIME 500 - -void CL_AdjustTimeDelta( void ) { - int resetTime; - int newDelta; - int deltaDelta; - - cl.newSnapshots = qfalse; - - // the delta never drifts when replaying a demo - if ( clc.demoplaying ) { - return; - } - - // if the current time is WAY off, just correct to the current value - if ( com_sv_running->integer ) { - resetTime = 100; - } else { - resetTime = RESET_TIME; - } - - newDelta = cl.snap.serverTime - cls.realtime; - deltaDelta = abs( newDelta - cl.serverTimeDelta ); - - if ( deltaDelta > RESET_TIME ) { - cl.serverTimeDelta = newDelta; - cl.oldServerTime = cl.snap.serverTime; // FIXME: is this a problem for cgame? - cl.serverTime = cl.snap.serverTime; - if ( cl_showTimeDelta->integer ) { - Com_Printf( " " ); - } - } else if ( deltaDelta > 100 ) { - // fast adjust, cut the difference in half - if ( cl_showTimeDelta->integer ) { - Com_Printf( " " ); - } - cl.serverTimeDelta = ( cl.serverTimeDelta + newDelta ) >> 1; - } else { - // slow drift adjust, only move 1 or 2 msec - - // if any of the frames between this and the previous snapshot - // had to be extrapolated, nudge our sense of time back a little - // the granularity of +1 / -2 is too high for timescale modified frametimes - if ( com_timescale->value == 0 || com_timescale->value == 1 ) { - if ( cl.extrapolatedSnapshot ) { - cl.extrapolatedSnapshot = qfalse; - cl.serverTimeDelta -= 2; - } else { - // otherwise, move our sense of time forward to minimize total latency - cl.serverTimeDelta++; - } - } - } - - if ( cl_showTimeDelta->integer ) { - Com_Printf( "%i ", cl.serverTimeDelta ); - } -} - - -/* -================== -CL_FirstSnapshot -================== -*/ -void CL_FirstSnapshot( void ) { - // ignore snapshots that don't have entities - if ( cl.snap.snapFlags & SNAPFLAG_NOT_ACTIVE ) { - return; - } - cls.state = CA_ACTIVE; - - // set the timedelta so we are exactly on this first frame - cl.serverTimeDelta = cl.snap.serverTime - cls.realtime; - cl.oldServerTime = cl.snap.serverTime; - - clc.timeDemoBaseTime = cl.snap.serverTime; - - // if this is the first frame of active play, - // execute the contents of activeAction now - // this is to allow scripting a timedemo to start right - // after loading - if ( cl_activeAction->string[0] ) { - Cbuf_AddText( cl_activeAction->string ); - Cvar_Set( "activeAction", "" ); - } - - Sys_BeginProfiling(); -} - -/* -================== -CL_SetCGameTime -================== -*/ -void CL_SetCGameTime( void ) { - // getting a valid frame message ends the connection process - if ( cls.state != CA_ACTIVE ) { - if ( cls.state != CA_PRIMED ) { - return; - } - if ( clc.demoplaying ) { - // we shouldn't get the first snapshot on the same frame - // as the gamestate, because it causes a bad time skip - if ( !clc.firstDemoFrameSkipped ) { - clc.firstDemoFrameSkipped = qtrue; - return; - } - CL_ReadDemoMessage(); - } - if ( cl.newSnapshots ) { - cl.newSnapshots = qfalse; - CL_FirstSnapshot(); - } - if ( cls.state != CA_ACTIVE ) { - return; - } - } - - // if we have gotten to this point, cl.snap is guaranteed to be valid - if ( !cl.snap.valid ) { - Com_Error( ERR_DROP, "CL_SetCGameTime: !cl.snap.valid" ); - } - - // allow pause in single player - if ( sv_paused->integer && cl_paused->integer && com_sv_running->integer ) { - // paused - return; - } - - if ( cl.snap.serverTime < cl.oldFrameServerTime ) { - Com_Error( ERR_DROP, "cl.snap.serverTime < cl.oldFrameServerTime" ); - } - cl.oldFrameServerTime = cl.snap.serverTime; - - - // get our current view of time - - if ( clc.demoplaying && cl_freezeDemo->integer ) { - // cl_freezeDemo is used to lock a demo in place for single frame advances - - } else { - // cl_timeNudge is a user adjustable cvar that allows more - // or less latency to be added in the interest of better - // smoothness or better responsiveness. - int tn; - - tn = cl_timeNudge->integer; - if (tn<-30) { - tn = -30; - } else if (tn>30) { - tn = 30; - } - - cl.serverTime = cls.realtime + cl.serverTimeDelta - tn; - - // guarantee that time will never flow backwards, even if - // serverTimeDelta made an adjustment or cl_timeNudge was changed - if ( cl.serverTime < cl.oldServerTime ) { - cl.serverTime = cl.oldServerTime; - } - cl.oldServerTime = cl.serverTime; - - // note if we are almost past the latest frame (without timeNudge), - // so we will try and adjust back a bit when the next snapshot arrives - if ( cls.realtime + cl.serverTimeDelta >= cl.snap.serverTime - 5 ) { - cl.extrapolatedSnapshot = qtrue; - } - } - - // if we have gotten new snapshots, drift serverTimeDelta - // don't do this every frame, or a period of packet loss would - // make a huge adjustment - if ( cl.newSnapshots ) { - CL_AdjustTimeDelta(); - } - - if ( !clc.demoplaying ) { - return; - } - - // if we are playing a demo back, we can just keep reading - // messages from the demo file until the cgame definately - // has valid snapshots to interpolate between - - // a timedemo will always use a deterministic set of time samples - // no matter what speed machine it is run on, - // while a normal demo may have different time samples - // each time it is played back - if ( cl_timedemo->integer ) { - if (!clc.timeDemoStart) { - clc.timeDemoStart = Sys_Milliseconds(); - } - clc.timeDemoFrames++; - cl.serverTime = clc.timeDemoBaseTime + clc.timeDemoFrames * 50; - } - - while ( cl.serverTime >= cl.snap.serverTime ) { - // feed another messag, which should change - // the contents of cl.snap - CL_ReadDemoMessage(); - if ( cls.state != CA_ACTIVE ) { - return; // end of demo - } - } - -} - - - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// cl_cgame.c -- client system interaction with client game + +#include "client.h" + +#include "../game/botlib.h" + +extern botlib_export_t *botlib_export; + +extern qboolean loadCamera(const char *name); +extern void startCamera(int time); +extern qboolean getCameraInfo(int time, vec3_t *origin, vec3_t *angles); + +/* +==================== +CL_GetGameState +==================== +*/ +void CL_GetGameState( gameState_t *gs ) { + *gs = cl.gameState; +} + +/* +==================== +CL_GetGlconfig +==================== +*/ +void CL_GetGlconfig( glconfig_t *glconfig ) { + *glconfig = cls.glconfig; +} + + +/* +==================== +CL_GetUserCmd +==================== +*/ +qboolean CL_GetUserCmd( int cmdNumber, usercmd_t *ucmd ) { + // cmds[cmdNumber] is the last properly generated command + + // can't return anything that we haven't created yet + if ( cmdNumber > cl.cmdNumber ) { + Com_Error( ERR_DROP, "CL_GetUserCmd: %i >= %i", cmdNumber, cl.cmdNumber ); + } + + // the usercmd has been overwritten in the wrapping + // buffer because it is too far out of date + if ( cmdNumber <= cl.cmdNumber - CMD_BACKUP ) { + return qfalse; + } + + *ucmd = cl.cmds[ cmdNumber & CMD_MASK ]; + + return qtrue; +} + +int CL_GetCurrentCmdNumber( void ) { + return cl.cmdNumber; +} + + +/* +==================== +CL_GetParseEntityState +==================== +*/ +qboolean CL_GetParseEntityState( int parseEntityNumber, entityState_t *state ) { + // can't return anything that hasn't been parsed yet + if ( parseEntityNumber >= cl.parseEntitiesNum ) { + Com_Error( ERR_DROP, "CL_GetParseEntityState: %i >= %i", + parseEntityNumber, cl.parseEntitiesNum ); + } + + // can't return anything that has been overwritten in the circular buffer + if ( parseEntityNumber <= cl.parseEntitiesNum - MAX_PARSE_ENTITIES ) { + return qfalse; + } + + *state = cl.parseEntities[ parseEntityNumber & ( MAX_PARSE_ENTITIES - 1 ) ]; + return qtrue; +} + +/* +==================== +CL_GetCurrentSnapshotNumber +==================== +*/ +void CL_GetCurrentSnapshotNumber( int *snapshotNumber, int *serverTime ) { + *snapshotNumber = cl.snap.messageNum; + *serverTime = cl.snap.serverTime; +} + +/* +==================== +CL_GetSnapshot +==================== +*/ +qboolean CL_GetSnapshot( int snapshotNumber, snapshot_t *snapshot ) { + clSnapshot_t *clSnap; + int i, count; + + if ( snapshotNumber > cl.snap.messageNum ) { + Com_Error( ERR_DROP, "CL_GetSnapshot: snapshotNumber > cl.snapshot.messageNum" ); + } + + // if the frame has fallen out of the circular buffer, we can't return it + if ( cl.snap.messageNum - snapshotNumber >= PACKET_BACKUP ) { + return qfalse; + } + + // if the frame is not valid, we can't return it + clSnap = &cl.snapshots[snapshotNumber & PACKET_MASK]; + if ( !clSnap->valid ) { + return qfalse; + } + + // if the entities in the frame have fallen out of their + // circular buffer, we can't return it + if ( cl.parseEntitiesNum - clSnap->parseEntitiesNum >= MAX_PARSE_ENTITIES ) { + return qfalse; + } + + // write the snapshot + snapshot->snapFlags = clSnap->snapFlags; + snapshot->serverCommandSequence = clSnap->serverCommandNum; + snapshot->ping = clSnap->ping; + snapshot->serverTime = clSnap->serverTime; + Com_Memcpy( snapshot->areamask, clSnap->areamask, sizeof( snapshot->areamask ) ); + snapshot->ps = clSnap->ps; + count = clSnap->numEntities; + if ( count > MAX_ENTITIES_IN_SNAPSHOT ) { + Com_DPrintf( "CL_GetSnapshot: truncated %i entities to %i\n", count, MAX_ENTITIES_IN_SNAPSHOT ); + count = MAX_ENTITIES_IN_SNAPSHOT; + } + snapshot->numEntities = count; + for ( i = 0 ; i < count ; i++ ) { + snapshot->entities[i] = + cl.parseEntities[ ( clSnap->parseEntitiesNum + i ) & (MAX_PARSE_ENTITIES-1) ]; + } + + // FIXME: configstring changes and server commands!!! + + return qtrue; +} + +/* +===================== +CL_SetUserCmdValue +===================== +*/ +void CL_SetUserCmdValue( int userCmdValue, float sensitivityScale ) { + cl.cgameUserCmdValue = userCmdValue; + cl.cgameSensitivity = sensitivityScale; +} + +/* +===================== +CL_AddCgameCommand +===================== +*/ +void CL_AddCgameCommand( const char *cmdName ) { + Cmd_AddCommand( cmdName, NULL ); +} + +/* +===================== +CL_CgameError +===================== +*/ +void CL_CgameError( const char *string ) { + Com_Error( ERR_DROP, "%s", string ); +} + + +/* +===================== +CL_ConfigstringModified +===================== +*/ +void CL_ConfigstringModified( void ) { + char *old, *s; + int i, index; + char *dup; + gameState_t oldGs; + int len; + + index = atoi( Cmd_Argv(1) ); + if ( index < 0 || index >= MAX_CONFIGSTRINGS ) { + Com_Error( ERR_DROP, "configstring > MAX_CONFIGSTRINGS" ); + } + // get everything after "cs " + s = Cmd_ArgsFrom(2); + + old = cl.gameState.stringData + cl.gameState.stringOffsets[ index ]; + if ( !strcmp( old, s ) ) { + return; // unchanged + } + + // build the new gameState_t + oldGs = cl.gameState; + + Com_Memset( &cl.gameState, 0, sizeof( cl.gameState ) ); + + // leave the first 0 for uninitialized strings + cl.gameState.dataCount = 1; + + for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { + if ( i == index ) { + dup = s; + } else { + dup = oldGs.stringData + oldGs.stringOffsets[ i ]; + } + if ( !dup[0] ) { + continue; // leave with the default empty string + } + + len = strlen( dup ); + + if ( len + 1 + cl.gameState.dataCount > MAX_GAMESTATE_CHARS ) { + Com_Error( ERR_DROP, "MAX_GAMESTATE_CHARS exceeded" ); + } + + // append it to the gameState string buffer + cl.gameState.stringOffsets[ i ] = cl.gameState.dataCount; + Com_Memcpy( cl.gameState.stringData + cl.gameState.dataCount, dup, len + 1 ); + cl.gameState.dataCount += len + 1; + } + + if ( index == CS_SYSTEMINFO ) { + // parse serverId and other cvars + CL_SystemInfoChanged(); + } + +} + + +/* +=================== +CL_GetServerCommand + +Set up argc/argv for the given command +=================== +*/ +qboolean CL_GetServerCommand( int serverCommandNumber ) { + char *s; + char *cmd; + static char bigConfigString[BIG_INFO_STRING]; + int argc; + + // if we have irretrievably lost a reliable command, drop the connection + if ( serverCommandNumber <= clc.serverCommandSequence - MAX_RELIABLE_COMMANDS ) { + // when a demo record was started after the client got a whole bunch of + // reliable commands then the client never got those first reliable commands + if ( clc.demoplaying ) + return qfalse; + Com_Error( ERR_DROP, "CL_GetServerCommand: a reliable command was cycled out" ); + return qfalse; + } + + if ( serverCommandNumber > clc.serverCommandSequence ) { + Com_Error( ERR_DROP, "CL_GetServerCommand: requested a command not received" ); + return qfalse; + } + + s = clc.serverCommands[ serverCommandNumber & ( MAX_RELIABLE_COMMANDS - 1 ) ]; + clc.lastExecutedServerCommand = serverCommandNumber; + + Com_DPrintf( "serverCommand: %i : %s\n", serverCommandNumber, s ); + +rescan: + Cmd_TokenizeString( s ); + cmd = Cmd_Argv(0); + argc = Cmd_Argc(); + + if ( !strcmp( cmd, "disconnect" ) ) { + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=552 + // allow server to indicate why they were disconnected + if ( argc >= 2 ) + Com_Error (ERR_SERVERDISCONNECT, va( "Server Disconnected - %s", Cmd_Argv( 1 ) ) ); + else + Com_Error (ERR_SERVERDISCONNECT,"Server disconnected\n"); + } + + if ( !strcmp( cmd, "bcs0" ) ) { + Com_sprintf( bigConfigString, BIG_INFO_STRING, "cs %s \"%s", Cmd_Argv(1), Cmd_Argv(2) ); + return qfalse; + } + + if ( !strcmp( cmd, "bcs1" ) ) { + s = Cmd_Argv(2); + if( strlen(bigConfigString) + strlen(s) >= BIG_INFO_STRING ) { + Com_Error( ERR_DROP, "bcs exceeded BIG_INFO_STRING" ); + } + strcat( bigConfigString, s ); + return qfalse; + } + + if ( !strcmp( cmd, "bcs2" ) ) { + s = Cmd_Argv(2); + if( strlen(bigConfigString) + strlen(s) + 1 >= BIG_INFO_STRING ) { + Com_Error( ERR_DROP, "bcs exceeded BIG_INFO_STRING" ); + } + strcat( bigConfigString, s ); + strcat( bigConfigString, "\"" ); + s = bigConfigString; + goto rescan; + } + + if ( !strcmp( cmd, "cs" ) ) { + CL_ConfigstringModified(); + // reparse the string, because CL_ConfigstringModified may have done another Cmd_TokenizeString() + Cmd_TokenizeString( s ); + return qtrue; + } + + if ( !strcmp( cmd, "map_restart" ) ) { + // clear notify lines and outgoing commands before passing + // the restart to the cgame + Con_ClearNotify(); + Com_Memset( cl.cmds, 0, sizeof( cl.cmds ) ); + return qtrue; + } + + // the clientLevelShot command is used during development + // to generate 128*128 screenshots from the intermission + // point of levels for the menu system to use + // we pass it along to the cgame to make apropriate adjustments, + // but we also clear the console and notify lines here + if ( !strcmp( cmd, "clientLevelShot" ) ) { + // don't do it if we aren't running the server locally, + // otherwise malicious remote servers could overwrite + // the existing thumbnails + if ( !com_sv_running->integer ) { + return qfalse; + } + // close the console + Con_Close(); + // take a special screenshot next frame + Cbuf_AddText( "wait ; wait ; wait ; wait ; screenshot levelshot\n" ); + return qtrue; + } + + // we may want to put a "connect to other server" command here + + // cgame can now act on the command + return qtrue; +} + + +/* +==================== +CL_CM_LoadMap + +Just adds default parameters that cgame doesn't need to know about +==================== +*/ +void CL_CM_LoadMap( const char *mapname ) { + int checksum; + + CM_LoadMap( mapname, qtrue, &checksum ); +} + +/* +==================== +CL_ShutdonwCGame + +==================== +*/ +void CL_ShutdownCGame( void ) { + cls.keyCatchers &= ~KEYCATCH_CGAME; + cls.cgameStarted = qfalse; + if ( !cgvm ) { + return; + } + VM_Call( cgvm, CG_SHUTDOWN ); + VM_Free( cgvm ); + cgvm = NULL; +} + +static int FloatAsInt( float f ) { + int temp; + + *(float *)&temp = f; + + return temp; +} + +/* +==================== +CL_CgameSystemCalls + +The cgame module is making a system call +==================== +*/ +#define VMA(x) VM_ArgPtr(args[x]) +#define VMF(x) ((float *)args)[x] +int CL_CgameSystemCalls( int *args ) { + switch( args[0] ) { + case CG_PRINT: + Com_Printf( "%s", VMA(1) ); + return 0; + case CG_ERROR: + Com_Error( ERR_DROP, "%s", VMA(1) ); + return 0; + case CG_MILLISECONDS: + return Sys_Milliseconds(); + case CG_CVAR_REGISTER: + Cvar_Register( VMA(1), VMA(2), VMA(3), args[4] ); + return 0; + case CG_CVAR_UPDATE: + Cvar_Update( VMA(1) ); + return 0; + case CG_CVAR_SET: + Cvar_Set( VMA(1), VMA(2) ); + return 0; + case CG_CVAR_VARIABLESTRINGBUFFER: + Cvar_VariableStringBuffer( VMA(1), VMA(2), args[3] ); + return 0; + case CG_ARGC: + return Cmd_Argc(); + case CG_ARGV: + Cmd_ArgvBuffer( args[1], VMA(2), args[3] ); + return 0; + case CG_ARGS: + Cmd_ArgsBuffer( VMA(1), args[2] ); + return 0; + case CG_FS_FOPENFILE: + return FS_FOpenFileByMode( VMA(1), VMA(2), args[3] ); + case CG_FS_READ: + FS_Read2( VMA(1), args[2], args[3] ); + return 0; + case CG_FS_WRITE: + FS_Write( VMA(1), args[2], args[3] ); + return 0; + case CG_FS_FCLOSEFILE: + FS_FCloseFile( args[1] ); + return 0; + case CG_FS_SEEK: + return FS_Seek( args[1], args[2], args[3] ); + case CG_SENDCONSOLECOMMAND: + Cbuf_AddText( VMA(1) ); + return 0; + case CG_ADDCOMMAND: + CL_AddCgameCommand( VMA(1) ); + return 0; + case CG_REMOVECOMMAND: + Cmd_RemoveCommand( VMA(1) ); + return 0; + case CG_SENDCLIENTCOMMAND: + CL_AddReliableCommand( VMA(1) ); + return 0; + case CG_UPDATESCREEN: + // this is used during lengthy level loading, so pump message loop +// Com_EventLoop(); // FIXME: if a server restarts here, BAD THINGS HAPPEN! +// We can't call Com_EventLoop here, a restart will crash and this _does_ happen +// if there is a map change while we are downloading at pk3. +// ZOID + SCR_UpdateScreen(); + return 0; + case CG_CM_LOADMAP: + CL_CM_LoadMap( VMA(1) ); + return 0; + case CG_CM_NUMINLINEMODELS: + return CM_NumInlineModels(); + case CG_CM_INLINEMODEL: + return CM_InlineModel( args[1] ); + case CG_CM_TEMPBOXMODEL: + return CM_TempBoxModel( VMA(1), VMA(2), /*int capsule*/ qfalse ); + case CG_CM_TEMPCAPSULEMODEL: + return CM_TempBoxModel( VMA(1), VMA(2), /*int capsule*/ qtrue ); + case CG_CM_POINTCONTENTS: + return CM_PointContents( VMA(1), args[2] ); + case CG_CM_TRANSFORMEDPOINTCONTENTS: + return CM_TransformedPointContents( VMA(1), args[2], VMA(3), VMA(4) ); + case CG_CM_BOXTRACE: + CM_BoxTrace( VMA(1), VMA(2), VMA(3), VMA(4), VMA(5), args[6], args[7], /*int capsule*/ qfalse ); + return 0; + case CG_CM_CAPSULETRACE: + CM_BoxTrace( VMA(1), VMA(2), VMA(3), VMA(4), VMA(5), args[6], args[7], /*int capsule*/ qtrue ); + return 0; + case CG_CM_TRANSFORMEDBOXTRACE: + CM_TransformedBoxTrace( VMA(1), VMA(2), VMA(3), VMA(4), VMA(5), args[6], args[7], VMA(8), VMA(9), /*int capsule*/ qfalse ); + return 0; + case CG_CM_TRANSFORMEDCAPSULETRACE: + CM_TransformedBoxTrace( VMA(1), VMA(2), VMA(3), VMA(4), VMA(5), args[6], args[7], VMA(8), VMA(9), /*int capsule*/ qtrue ); + return 0; + case CG_CM_MARKFRAGMENTS: + return re.MarkFragments( args[1], VMA(2), VMA(3), args[4], VMA(5), args[6], VMA(7) ); + case CG_S_STARTSOUND: + S_StartSound( VMA(1), args[2], args[3], args[4] ); + return 0; + case CG_S_STARTLOCALSOUND: + S_StartLocalSound( args[1], args[2] ); + return 0; + case CG_S_CLEARLOOPINGSOUNDS: + S_ClearLoopingSounds(args[1]); + return 0; + case CG_S_ADDLOOPINGSOUND: + S_AddLoopingSound( args[1], VMA(2), VMA(3), args[4] ); + return 0; + case CG_S_ADDREALLOOPINGSOUND: + S_AddRealLoopingSound( args[1], VMA(2), VMA(3), args[4] ); + return 0; + case CG_S_STOPLOOPINGSOUND: + S_StopLoopingSound( args[1] ); + return 0; + case CG_S_UPDATEENTITYPOSITION: + S_UpdateEntityPosition( args[1], VMA(2) ); + return 0; + case CG_S_RESPATIALIZE: + S_Respatialize( args[1], VMA(2), VMA(3), args[4] ); + return 0; + case CG_S_REGISTERSOUND: + return S_RegisterSound( VMA(1), args[2] ); + case CG_S_STARTBACKGROUNDTRACK: + S_StartBackgroundTrack( VMA(1), VMA(2) ); + return 0; + case CG_R_LOADWORLDMAP: + re.LoadWorld( VMA(1) ); + return 0; + case CG_R_REGISTERMODEL: + return re.RegisterModel( VMA(1) ); + case CG_R_REGISTERSKIN: + return re.RegisterSkin( VMA(1) ); + case CG_R_REGISTERSHADER: + return re.RegisterShader( VMA(1) ); + case CG_R_REGISTERSHADERNOMIP: + return re.RegisterShaderNoMip( VMA(1) ); + case CG_R_REGISTERFONT: + re.RegisterFont( VMA(1), args[2], VMA(3)); + case CG_R_CLEARSCENE: + re.ClearScene(); + return 0; + case CG_R_ADDREFENTITYTOSCENE: + re.AddRefEntityToScene( VMA(1) ); + return 0; + case CG_R_ADDPOLYTOSCENE: + re.AddPolyToScene( args[1], args[2], VMA(3), 1 ); + return 0; + case CG_R_ADDPOLYSTOSCENE: + re.AddPolyToScene( args[1], args[2], VMA(3), args[4] ); + return 0; + case CG_R_LIGHTFORPOINT: + return re.LightForPoint( VMA(1), VMA(2), VMA(3), VMA(4) ); + case CG_R_ADDLIGHTTOSCENE: + re.AddLightToScene( VMA(1), VMF(2), VMF(3), VMF(4), VMF(5) ); + return 0; + case CG_R_ADDADDITIVELIGHTTOSCENE: + re.AddAdditiveLightToScene( VMA(1), VMF(2), VMF(3), VMF(4), VMF(5) ); + return 0; + case CG_R_RENDERSCENE: + re.RenderScene( VMA(1) ); + return 0; + case CG_R_SETCOLOR: + re.SetColor( VMA(1) ); + return 0; + case CG_R_DRAWSTRETCHPIC: + re.DrawStretchPic( VMF(1), VMF(2), VMF(3), VMF(4), VMF(5), VMF(6), VMF(7), VMF(8), args[9] ); + return 0; + case CG_R_MODELBOUNDS: + re.ModelBounds( args[1], VMA(2), VMA(3) ); + return 0; + case CG_R_LERPTAG: + return re.LerpTag( VMA(1), args[2], args[3], args[4], VMF(5), VMA(6) ); + case CG_GETGLCONFIG: + CL_GetGlconfig( VMA(1) ); + return 0; + case CG_GETGAMESTATE: + CL_GetGameState( VMA(1) ); + return 0; + case CG_GETCURRENTSNAPSHOTNUMBER: + CL_GetCurrentSnapshotNumber( VMA(1), VMA(2) ); + return 0; + case CG_GETSNAPSHOT: + return CL_GetSnapshot( args[1], VMA(2) ); + case CG_GETSERVERCOMMAND: + return CL_GetServerCommand( args[1] ); + case CG_GETCURRENTCMDNUMBER: + return CL_GetCurrentCmdNumber(); + case CG_GETUSERCMD: + return CL_GetUserCmd( args[1], VMA(2) ); + case CG_SETUSERCMDVALUE: + CL_SetUserCmdValue( args[1], VMF(2) ); + return 0; + case CG_MEMORY_REMAINING: + return Hunk_MemoryRemaining(); + case CG_KEY_ISDOWN: + return Key_IsDown( args[1] ); + case CG_KEY_GETCATCHER: + return Key_GetCatcher(); + case CG_KEY_SETCATCHER: + Key_SetCatcher( args[1] ); + return 0; + case CG_KEY_GETKEY: + return Key_GetKey( VMA(1) ); + + + + case CG_MEMSET: + Com_Memset( VMA(1), args[2], args[3] ); + return 0; + case CG_MEMCPY: + Com_Memcpy( VMA(1), VMA(2), args[3] ); + return 0; + case CG_STRNCPY: + return (int)strncpy( VMA(1), VMA(2), args[3] ); + case CG_SIN: + return FloatAsInt( sin( VMF(1) ) ); + case CG_COS: + return FloatAsInt( cos( VMF(1) ) ); + case CG_ATAN2: + return FloatAsInt( atan2( VMF(1), VMF(2) ) ); + case CG_SQRT: + return FloatAsInt( sqrt( VMF(1) ) ); + case CG_FLOOR: + return FloatAsInt( floor( VMF(1) ) ); + case CG_CEIL: + return FloatAsInt( ceil( VMF(1) ) ); + case CG_ACOS: + return FloatAsInt( Q_acos( VMF(1) ) ); + + case CG_PC_ADD_GLOBAL_DEFINE: + return botlib_export->PC_AddGlobalDefine( VMA(1) ); + case CG_PC_LOAD_SOURCE: + return botlib_export->PC_LoadSourceHandle( VMA(1) ); + case CG_PC_FREE_SOURCE: + return botlib_export->PC_FreeSourceHandle( args[1] ); + case CG_PC_READ_TOKEN: + return botlib_export->PC_ReadTokenHandle( args[1], VMA(2) ); + case CG_PC_SOURCE_FILE_AND_LINE: + return botlib_export->PC_SourceFileAndLine( args[1], VMA(2), VMA(3) ); + + case CG_S_STOPBACKGROUNDTRACK: + S_StopBackgroundTrack(); + return 0; + + case CG_REAL_TIME: + return Com_RealTime( VMA(1) ); + case CG_SNAPVECTOR: + Sys_SnapVector( VMA(1) ); + return 0; + + case CG_CIN_PLAYCINEMATIC: + return CIN_PlayCinematic(VMA(1), args[2], args[3], args[4], args[5], args[6]); + + case CG_CIN_STOPCINEMATIC: + return CIN_StopCinematic(args[1]); + + case CG_CIN_RUNCINEMATIC: + return CIN_RunCinematic(args[1]); + + case CG_CIN_DRAWCINEMATIC: + CIN_DrawCinematic(args[1]); + return 0; + + case CG_CIN_SETEXTENTS: + CIN_SetExtents(args[1], args[2], args[3], args[4], args[5]); + return 0; + + case CG_R_REMAP_SHADER: + re.RemapShader( VMA(1), VMA(2), VMA(3) ); + return 0; + +/* + case CG_LOADCAMERA: + return loadCamera(VMA(1)); + + case CG_STARTCAMERA: + startCamera(args[1]); + return 0; + + case CG_GETCAMERAINFO: + return getCameraInfo(args[1], VMA(2), VMA(3)); +*/ + case CG_GET_ENTITY_TOKEN: + return re.GetEntityToken( VMA(1), args[2] ); + case CG_R_INPVS: + return re.inPVS( VMA(1), VMA(2) ); + + default: + assert(0); // bk010102 + Com_Error( ERR_DROP, "Bad cgame system trap: %i", args[0] ); + } + return 0; +} + + +/* +==================== +CL_InitCGame + +Should only be called by CL_StartHunkUsers +==================== +*/ +void CL_InitCGame( void ) { + const char *info; + const char *mapname; + int t1, t2; + vmInterpret_t interpret; + + t1 = Sys_Milliseconds(); + + // put away the console + Con_Close(); + + // find the current mapname + info = cl.gameState.stringData + cl.gameState.stringOffsets[ CS_SERVERINFO ]; + mapname = Info_ValueForKey( info, "mapname" ); + Com_sprintf( cl.mapname, sizeof( cl.mapname ), "maps/%s.bsp", mapname ); + + // load the dll or bytecode + if ( cl_connectedToPureServer != 0 ) { + // if sv_pure is set we only allow qvms to be loaded + interpret = VMI_COMPILED; + } + else { + interpret = Cvar_VariableValue( "vm_cgame" ); + } + cgvm = VM_Create( "cgame", CL_CgameSystemCalls, interpret ); + if ( !cgvm ) { + Com_Error( ERR_DROP, "VM_Create on cgame failed" ); + } + cls.state = CA_LOADING; + + // init for this gamestate + // use the lastExecutedServerCommand instead of the serverCommandSequence + // otherwise server commands sent just before a gamestate are dropped + VM_Call( cgvm, CG_INIT, clc.serverMessageSequence, clc.lastExecutedServerCommand, clc.clientNum ); + + // we will send a usercmd this frame, which + // will cause the server to send us the first snapshot + cls.state = CA_PRIMED; + + t2 = Sys_Milliseconds(); + + Com_Printf( "CL_InitCGame: %5.2f seconds\n", (t2-t1)/1000.0 ); + + // have the renderer touch all its images, so they are present + // on the card even if the driver does deferred loading + re.EndRegistration(); + + // make sure everything is paged in + if (!Sys_LowPhysicalMemory()) { + Com_TouchMemory(); + } + + // clear anything that got printed + Con_ClearNotify (); +} + + +/* +==================== +CL_GameCommand + +See if the current console command is claimed by the cgame +==================== +*/ +qboolean CL_GameCommand( void ) { + if ( !cgvm ) { + return qfalse; + } + + return VM_Call( cgvm, CG_CONSOLE_COMMAND ); +} + + + +/* +===================== +CL_CGameRendering +===================== +*/ +void CL_CGameRendering( stereoFrame_t stereo ) { + VM_Call( cgvm, CG_DRAW_ACTIVE_FRAME, cl.serverTime, stereo, clc.demoplaying ); + VM_Debug( 0 ); +} + + +/* +================= +CL_AdjustTimeDelta + +Adjust the clients view of server time. + +We attempt to have cl.serverTime exactly equal the server's view +of time plus the timeNudge, but with variable latencies over +the internet it will often need to drift a bit to match conditions. + +Our ideal time would be to have the adjusted time approach, but not pass, +the very latest snapshot. + +Adjustments are only made when a new snapshot arrives with a rational +latency, which keeps the adjustment process framerate independent and +prevents massive overadjustment during times of significant packet loss +or bursted delayed packets. +================= +*/ + +#define RESET_TIME 500 + +void CL_AdjustTimeDelta( void ) { + int resetTime; + int newDelta; + int deltaDelta; + + cl.newSnapshots = qfalse; + + // the delta never drifts when replaying a demo + if ( clc.demoplaying ) { + return; + } + + // if the current time is WAY off, just correct to the current value + if ( com_sv_running->integer ) { + resetTime = 100; + } else { + resetTime = RESET_TIME; + } + + newDelta = cl.snap.serverTime - cls.realtime; + deltaDelta = abs( newDelta - cl.serverTimeDelta ); + + if ( deltaDelta > RESET_TIME ) { + cl.serverTimeDelta = newDelta; + cl.oldServerTime = cl.snap.serverTime; // FIXME: is this a problem for cgame? + cl.serverTime = cl.snap.serverTime; + if ( cl_showTimeDelta->integer ) { + Com_Printf( " " ); + } + } else if ( deltaDelta > 100 ) { + // fast adjust, cut the difference in half + if ( cl_showTimeDelta->integer ) { + Com_Printf( " " ); + } + cl.serverTimeDelta = ( cl.serverTimeDelta + newDelta ) >> 1; + } else { + // slow drift adjust, only move 1 or 2 msec + + // if any of the frames between this and the previous snapshot + // had to be extrapolated, nudge our sense of time back a little + // the granularity of +1 / -2 is too high for timescale modified frametimes + if ( com_timescale->value == 0 || com_timescale->value == 1 ) { + if ( cl.extrapolatedSnapshot ) { + cl.extrapolatedSnapshot = qfalse; + cl.serverTimeDelta -= 2; + } else { + // otherwise, move our sense of time forward to minimize total latency + cl.serverTimeDelta++; + } + } + } + + if ( cl_showTimeDelta->integer ) { + Com_Printf( "%i ", cl.serverTimeDelta ); + } +} + + +/* +================== +CL_FirstSnapshot +================== +*/ +void CL_FirstSnapshot( void ) { + // ignore snapshots that don't have entities + if ( cl.snap.snapFlags & SNAPFLAG_NOT_ACTIVE ) { + return; + } + cls.state = CA_ACTIVE; + + // set the timedelta so we are exactly on this first frame + cl.serverTimeDelta = cl.snap.serverTime - cls.realtime; + cl.oldServerTime = cl.snap.serverTime; + + clc.timeDemoBaseTime = cl.snap.serverTime; + + // if this is the first frame of active play, + // execute the contents of activeAction now + // this is to allow scripting a timedemo to start right + // after loading + if ( cl_activeAction->string[0] ) { + Cbuf_AddText( cl_activeAction->string ); + Cvar_Set( "activeAction", "" ); + } + + Sys_BeginProfiling(); +} + +/* +================== +CL_SetCGameTime +================== +*/ +void CL_SetCGameTime( void ) { + // getting a valid frame message ends the connection process + if ( cls.state != CA_ACTIVE ) { + if ( cls.state != CA_PRIMED ) { + return; + } + if ( clc.demoplaying ) { + // we shouldn't get the first snapshot on the same frame + // as the gamestate, because it causes a bad time skip + if ( !clc.firstDemoFrameSkipped ) { + clc.firstDemoFrameSkipped = qtrue; + return; + } + CL_ReadDemoMessage(); + } + if ( cl.newSnapshots ) { + cl.newSnapshots = qfalse; + CL_FirstSnapshot(); + } + if ( cls.state != CA_ACTIVE ) { + return; + } + } + + // if we have gotten to this point, cl.snap is guaranteed to be valid + if ( !cl.snap.valid ) { + Com_Error( ERR_DROP, "CL_SetCGameTime: !cl.snap.valid" ); + } + + // allow pause in single player + if ( sv_paused->integer && cl_paused->integer && com_sv_running->integer ) { + // paused + return; + } + + if ( cl.snap.serverTime < cl.oldFrameServerTime ) { + Com_Error( ERR_DROP, "cl.snap.serverTime < cl.oldFrameServerTime" ); + } + cl.oldFrameServerTime = cl.snap.serverTime; + + + // get our current view of time + + if ( clc.demoplaying && cl_freezeDemo->integer ) { + // cl_freezeDemo is used to lock a demo in place for single frame advances + + } else { + // cl_timeNudge is a user adjustable cvar that allows more + // or less latency to be added in the interest of better + // smoothness or better responsiveness. + int tn; + + tn = cl_timeNudge->integer; + if (tn<-30) { + tn = -30; + } else if (tn>30) { + tn = 30; + } + + cl.serverTime = cls.realtime + cl.serverTimeDelta - tn; + + // guarantee that time will never flow backwards, even if + // serverTimeDelta made an adjustment or cl_timeNudge was changed + if ( cl.serverTime < cl.oldServerTime ) { + cl.serverTime = cl.oldServerTime; + } + cl.oldServerTime = cl.serverTime; + + // note if we are almost past the latest frame (without timeNudge), + // so we will try and adjust back a bit when the next snapshot arrives + if ( cls.realtime + cl.serverTimeDelta >= cl.snap.serverTime - 5 ) { + cl.extrapolatedSnapshot = qtrue; + } + } + + // if we have gotten new snapshots, drift serverTimeDelta + // don't do this every frame, or a period of packet loss would + // make a huge adjustment + if ( cl.newSnapshots ) { + CL_AdjustTimeDelta(); + } + + if ( !clc.demoplaying ) { + return; + } + + // if we are playing a demo back, we can just keep reading + // messages from the demo file until the cgame definately + // has valid snapshots to interpolate between + + // a timedemo will always use a deterministic set of time samples + // no matter what speed machine it is run on, + // while a normal demo may have different time samples + // each time it is played back + if ( cl_timedemo->integer ) { + if (!clc.timeDemoStart) { + clc.timeDemoStart = Sys_Milliseconds(); + } + clc.timeDemoFrames++; + cl.serverTime = clc.timeDemoBaseTime + clc.timeDemoFrames * 50; + } + + while ( cl.serverTime >= cl.snap.serverTime ) { + // feed another messag, which should change + // the contents of cl.snap + CL_ReadDemoMessage(); + if ( cls.state != CA_ACTIVE ) { + return; // end of demo + } + } + +} + + + diff --git a/code/client/cl_cin.c b/code/client/cl_cin.c index 7929624..f042d23 100755 --- a/code/client/cl_cin.c +++ b/code/client/cl_cin.c @@ -1,1740 +1,1740 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: cl_cin.c - * - * desc: video and cinematic playback - * - * $Archive: /MissionPack/code/client/cl_cin.c $ - * - * cl_glconfig.hwtype trtypes 3dfx/ragepro need 256x256 - * - *****************************************************************************/ - -#include "client.h" -#include "snd_local.h" - -#define MAXSIZE 8 -#define MINSIZE 4 - -#define DEFAULT_CIN_WIDTH 512 -#define DEFAULT_CIN_HEIGHT 512 - -#define ROQ_QUAD 0x1000 -#define ROQ_QUAD_INFO 0x1001 -#define ROQ_CODEBOOK 0x1002 -#define ROQ_QUAD_VQ 0x1011 -#define ROQ_QUAD_JPEG 0x1012 -#define ROQ_QUAD_HANG 0x1013 -#define ROQ_PACKET 0x1030 -#define ZA_SOUND_MONO 0x1020 -#define ZA_SOUND_STEREO 0x1021 - -#define MAX_VIDEO_HANDLES 16 - -extern glconfig_t glConfig; -extern int s_paintedtime; -extern int s_rawend; - - -static void RoQ_init( void ); - -/****************************************************************************** -* -* Class: trFMV -* -* Description: RoQ/RnR manipulation routines -* not entirely complete for first run -* -******************************************************************************/ - -static long ROQ_YY_tab[256]; -static long ROQ_UB_tab[256]; -static long ROQ_UG_tab[256]; -static long ROQ_VG_tab[256]; -static long ROQ_VR_tab[256]; -static unsigned short vq2[256*16*4]; -static unsigned short vq4[256*64*4]; -static unsigned short vq8[256*256*4]; - - -typedef struct { - byte linbuf[DEFAULT_CIN_WIDTH*DEFAULT_CIN_HEIGHT*4*2]; - byte file[65536]; - short sqrTable[256]; - - unsigned int mcomp[256]; - byte *qStatus[2][32768]; - - long oldXOff, oldYOff, oldysize, oldxsize; - - int currentHandle; -} cinematics_t; - -typedef struct { - char fileName[MAX_OSPATH]; - int CIN_WIDTH, CIN_HEIGHT; - int xpos, ypos, width, height; - qboolean looping, holdAtEnd, dirty, alterGameState, silent, shader; - fileHandle_t iFile; - e_status status; - unsigned int startTime; - unsigned int lastTime; - long tfps; - long RoQPlayed; - long ROQSize; - unsigned int RoQFrameSize; - long onQuad; - long numQuads; - long samplesPerLine; - unsigned int roq_id; - long screenDelta; - - void ( *VQ0)(byte *status, void *qdata ); - void ( *VQ1)(byte *status, void *qdata ); - void ( *VQNormal)(byte *status, void *qdata ); - void ( *VQBuffer)(byte *status, void *qdata ); - - long samplesPerPixel; // defaults to 2 - byte* gray; - unsigned int xsize, ysize, maxsize, minsize; - - qboolean half, smootheddouble, inMemory; - long normalBuffer0; - long roq_flags; - long roqF0; - long roqF1; - long t[2]; - long roqFPS; - int playonwalls; - byte* buf; - long drawX, drawY; -} cin_cache; - -static cinematics_t cin; -static cin_cache cinTable[MAX_VIDEO_HANDLES]; -static int currentHandle = -1; -static int CL_handle = -1; - -extern int s_soundtime; // sample PAIRS -extern int s_paintedtime; // sample PAIRS - - -void CIN_CloseAllVideos(void) { - int i; - - for ( i = 0 ; i < MAX_VIDEO_HANDLES ; i++ ) { - if (cinTable[i].fileName[0] != 0 ) { - CIN_StopCinematic(i); - } - } -} - - -static int CIN_HandleForVideo(void) { - int i; - - for ( i = 0 ; i < MAX_VIDEO_HANDLES ; i++ ) { - if ( cinTable[i].fileName[0] == 0 ) { - return i; - } - } - Com_Error( ERR_DROP, "CIN_HandleForVideo: none free" ); - return -1; -} - - -extern int CL_ScaledMilliseconds(void); - -//----------------------------------------------------------------------------- -// RllSetupTable -// -// Allocates and initializes the square table. -// -// Parameters: None -// -// Returns: Nothing -//----------------------------------------------------------------------------- -static void RllSetupTable() -{ - int z; - - for (z=0;z<128;z++) { - cin.sqrTable[z] = (short)(z*z); - cin.sqrTable[z+128] = (short)(-cin.sqrTable[z]); - } -} - - - -//----------------------------------------------------------------------------- -// RllDecodeMonoToMono -// -// Decode mono source data into a mono buffer. -// -// Parameters: from -> buffer holding encoded data -// to -> buffer to hold decoded data -// size = number of bytes of input (= # of shorts of output) -// signedOutput = 0 for unsigned output, non-zero for signed output -// flag = flags from asset header -// -// Returns: Number of samples placed in output buffer -//----------------------------------------------------------------------------- -long RllDecodeMonoToMono(unsigned char *from,short *to,unsigned int size,char signedOutput ,unsigned short flag) -{ - unsigned int z; - int prev; - - if (signedOutput) - prev = flag - 0x8000; - else - prev = flag; - - for (z=0;z buffer holding encoded data -// to -> buffer to hold decoded data -// size = number of bytes of input (= 1/4 # of bytes of output) -// signedOutput = 0 for unsigned output, non-zero for signed output -// flag = flags from asset header -// -// Returns: Number of samples placed in output buffer -//----------------------------------------------------------------------------- -long RllDecodeMonoToStereo(unsigned char *from,short *to,unsigned int size,char signedOutput,unsigned short flag) -{ - unsigned int z; - int prev; - - if (signedOutput) - prev = flag - 0x8000; - else - prev = flag; - - for (z = 0; z < size; z++) { - prev = (short)(prev + cin.sqrTable[from[z]]); - to[z*2+0] = to[z*2+1] = (short)(prev); - } - - return size; // * 2 * sizeof(short)); -} - - -//----------------------------------------------------------------------------- -// RllDecodeStereoToStereo -// -// Decode stereo source data into a stereo buffer. -// -// Parameters: from -> buffer holding encoded data -// to -> buffer to hold decoded data -// size = number of bytes of input (= 1/2 # of bytes of output) -// signedOutput = 0 for unsigned output, non-zero for signed output -// flag = flags from asset header -// -// Returns: Number of samples placed in output buffer -//----------------------------------------------------------------------------- -long RllDecodeStereoToStereo(unsigned char *from,short *to,unsigned int size,char signedOutput, unsigned short flag) -{ - unsigned int z; - unsigned char *zz = from; - int prevL, prevR; - - if (signedOutput) { - prevL = (flag & 0xff00) - 0x8000; - prevR = ((flag & 0x00ff) << 8) - 0x8000; - } else { - prevL = flag & 0xff00; - prevR = (flag & 0x00ff) << 8; - } - - for (z=0;z>1); //*sizeof(short)); -} - - -//----------------------------------------------------------------------------- -// RllDecodeStereoToMono -// -// Decode stereo source data into a mono buffer. -// -// Parameters: from -> buffer holding encoded data -// to -> buffer to hold decoded data -// size = number of bytes of input (= # of bytes of output) -// signedOutput = 0 for unsigned output, non-zero for signed output -// flag = flags from asset header -// -// Returns: Number of samples placed in output buffer -//----------------------------------------------------------------------------- -long RllDecodeStereoToMono(unsigned char *from,short *to,unsigned int size,char signedOutput, unsigned short flag) -{ - unsigned int z; - int prevL,prevR; - - if (signedOutput) { - prevL = (flag & 0xff00) - 0x8000; - prevR = ((flag & 0x00ff) << 8) -0x8000; - } else { - prevL = flag & 0xff00; - prevR = (flag & 0x00ff) << 8; - } - - for (z=0;z>3; - - ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; - dsrc += dspl; ddst += dspl; - ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; - dsrc += dspl; ddst += dspl; - ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; - dsrc += dspl; ddst += dspl; - ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; - dsrc += dspl; ddst += dspl; - ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; - dsrc += dspl; ddst += dspl; - ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; - dsrc += dspl; ddst += dspl; - ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; - dsrc += dspl; ddst += dspl; - ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; -} - -/****************************************************************************** -* -* Function: -* -* Description: -* -******************************************************************************/ - -static void move4_32( byte *src, byte *dst, int spl ) -{ - double *dsrc, *ddst; - int dspl; - - dsrc = (double *)src; - ddst = (double *)dst; - dspl = spl>>3; - - ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; - dsrc += dspl; ddst += dspl; - ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; - dsrc += dspl; ddst += dspl; - ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; - dsrc += dspl; ddst += dspl; - ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; -} - -/****************************************************************************** -* -* Function: -* -* Description: -* -******************************************************************************/ - -static void blit8_32( byte *src, byte *dst, int spl ) -{ - double *dsrc, *ddst; - int dspl; - - dsrc = (double *)src; - ddst = (double *)dst; - dspl = spl>>3; - - ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; - dsrc += 4; ddst += dspl; - ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; - dsrc += 4; ddst += dspl; - ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; - dsrc += 4; ddst += dspl; - ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; - dsrc += 4; ddst += dspl; - ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; - dsrc += 4; ddst += dspl; - ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; - dsrc += 4; ddst += dspl; - ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; - dsrc += 4; ddst += dspl; - ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; -} - -/****************************************************************************** -* -* Function: -* -* Description: -* -******************************************************************************/ -#define movs double -static void blit4_32( byte *src, byte *dst, int spl ) -{ - movs *dsrc, *ddst; - int dspl; - - dsrc = (movs *)src; - ddst = (movs *)dst; - dspl = spl>>3; - - ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; - dsrc += 2; ddst += dspl; - ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; - dsrc += 2; ddst += dspl; - ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; - dsrc += 2; ddst += dspl; - ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; -} - -/****************************************************************************** -* -* Function: -* -* Description: -* -******************************************************************************/ - -static void blit2_32( byte *src, byte *dst, int spl ) -{ - double *dsrc, *ddst; - int dspl; - - dsrc = (double *)src; - ddst = (double *)dst; - dspl = spl>>3; - - ddst[0] = dsrc[0]; - ddst[dspl] = dsrc[1]; -} - -/****************************************************************************** -* -* Function: -* -* Description: -* -******************************************************************************/ - -static void blitVQQuad32fs( byte **status, unsigned char *data ) -{ -unsigned short newd, celdata, code; -unsigned int index, i; -int spl; - - newd = 0; - celdata = 0; - index = 0; - - spl = cinTable[currentHandle].samplesPerLine; - - do { - if (!newd) { - newd = 7; - celdata = data[0] + data[1]*256; - data += 2; - } else { - newd--; - } - - code = (unsigned short)(celdata&0xc000); - celdata <<= 2; - - switch (code) { - case 0x8000: // vq code - blit8_32( (byte *)&vq8[(*data)*128], status[index], spl ); - data++; - index += 5; - break; - case 0xc000: // drop - index++; // skip 8x8 - for(i=0;i<4;i++) { - if (!newd) { - newd = 7; - celdata = data[0] + data[1]*256; - data += 2; - } else { - newd--; - } - - code = (unsigned short)(celdata&0xc000); celdata <<= 2; - - switch (code) { // code in top two bits of code - case 0x8000: // 4x4 vq code - blit4_32( (byte *)&vq4[(*data)*32], status[index], spl ); - data++; - break; - case 0xc000: // 2x2 vq code - blit2_32( (byte *)&vq2[(*data)*8], status[index], spl ); - data++; - blit2_32( (byte *)&vq2[(*data)*8], status[index]+8, spl ); - data++; - blit2_32( (byte *)&vq2[(*data)*8], status[index]+spl*2, spl ); - data++; - blit2_32( (byte *)&vq2[(*data)*8], status[index]+spl*2+8, spl ); - data++; - break; - case 0x4000: // motion compensation - move4_32( status[index] + cin.mcomp[(*data)], status[index], spl ); - data++; - break; - } - index++; - } - break; - case 0x4000: // motion compensation - move8_32( status[index] + cin.mcomp[(*data)], status[index], spl ); - data++; - index += 5; - break; - case 0x0000: - index += 5; - break; - } - } while ( status[index] != NULL ); -} - -/****************************************************************************** -* -* Function: -* -* Description: -* -******************************************************************************/ - -static void ROQ_GenYUVTables( void ) -{ - float t_ub,t_vr,t_ug,t_vg; - long i; - - t_ub = (1.77200f/2.0f) * (float)(1<<6) + 0.5f; - t_vr = (1.40200f/2.0f) * (float)(1<<6) + 0.5f; - t_ug = (0.34414f/2.0f) * (float)(1<<6) + 0.5f; - t_vg = (0.71414f/2.0f) * (float)(1<<6) + 0.5f; - for(i=0;i<256;i++) { - float x = (float)(2 * i - 255); - - ROQ_UB_tab[i] = (long)( ( t_ub * x) + (1<<5)); - ROQ_VR_tab[i] = (long)( ( t_vr * x) + (1<<5)); - ROQ_UG_tab[i] = (long)( (-t_ug * x) ); - ROQ_VG_tab[i] = (long)( (-t_vg * x) + (1<<5)); - ROQ_YY_tab[i] = (long)( (i << 6) | (i >> 2) ); - } -} - -#define VQ2TO4(a,b,c,d) { \ - *c++ = a[0]; \ - *d++ = a[0]; \ - *d++ = a[0]; \ - *c++ = a[1]; \ - *d++ = a[1]; \ - *d++ = a[1]; \ - *c++ = b[0]; \ - *d++ = b[0]; \ - *d++ = b[0]; \ - *c++ = b[1]; \ - *d++ = b[1]; \ - *d++ = b[1]; \ - *d++ = a[0]; \ - *d++ = a[0]; \ - *d++ = a[1]; \ - *d++ = a[1]; \ - *d++ = b[0]; \ - *d++ = b[0]; \ - *d++ = b[1]; \ - *d++ = b[1]; \ - a += 2; b += 2; } - -#define VQ2TO2(a,b,c,d) { \ - *c++ = *a; \ - *d++ = *a; \ - *d++ = *a; \ - *c++ = *b; \ - *d++ = *b; \ - *d++ = *b; \ - *d++ = *a; \ - *d++ = *a; \ - *d++ = *b; \ - *d++ = *b; \ - a++; b++; } - -/****************************************************************************** -* -* Function: -* -* Description: -* -******************************************************************************/ - -static unsigned short yuv_to_rgb( long y, long u, long v ) -{ - long r,g,b,YY = (long)(ROQ_YY_tab[(y)]); - - r = (YY + ROQ_VR_tab[v]) >> 9; - g = (YY + ROQ_UG_tab[u] + ROQ_VG_tab[v]) >> 8; - b = (YY + ROQ_UB_tab[u]) >> 9; - - if (r<0) r = 0; if (g<0) g = 0; if (b<0) b = 0; - if (r > 31) r = 31; if (g > 63) g = 63; if (b > 31) b = 31; - - return (unsigned short)((r<<11)+(g<<5)+(b)); -} - -/****************************************************************************** -* -* Function: -* -* Description: -* -******************************************************************************/ -#if defined(MACOS_X) - -static inline unsigned int yuv_to_rgb24( long y, long u, long v ) -{ - long r,g,b,YY; - - YY = (long)(ROQ_YY_tab[(y)]); - - r = (YY + ROQ_VR_tab[v]) >> 6; - g = (YY + ROQ_UG_tab[u] + ROQ_VG_tab[v]) >> 6; - b = (YY + ROQ_UB_tab[u]) >> 6; - - if (r<0) r = 0; if (g<0) g = 0; if (b<0) b = 0; - if (r > 255) r = 255; if (g > 255) g = 255; if (b > 255) b = 255; - - return ((r<<24)|(g<<16)|(b<<8))|(255); //+(255<<24)); -} - -#else -static unsigned int yuv_to_rgb24( long y, long u, long v ) -{ - long r,g,b,YY = (long)(ROQ_YY_tab[(y)]); - - r = (YY + ROQ_VR_tab[v]) >> 6; - g = (YY + ROQ_UG_tab[u] + ROQ_VG_tab[v]) >> 6; - b = (YY + ROQ_UB_tab[u]) >> 6; - - if (r<0) r = 0; if (g<0) g = 0; if (b<0) b = 0; - if (r > 255) r = 255; if (g > 255) g = 255; if (b > 255) b = 255; - - return LittleLong ((r)|(g<<8)|(b<<16)|(255<<24)); -} -#endif - -/****************************************************************************** -* -* Function: -* -* Description: -* -******************************************************************************/ - -static void decodeCodeBook( byte *input, unsigned short roq_flags ) -{ - long i, j, two, four; - unsigned short *aptr, *bptr, *cptr, *dptr; - long y0,y1,y2,y3,cr,cb; - byte *bbptr, *baptr, *bcptr, *bdptr; - unsigned int *iaptr, *ibptr, *icptr, *idptr; - - if (!roq_flags) { - two = four = 256; - } else { - two = roq_flags>>8; - if (!two) two = 256; - four = roq_flags&0xff; - } - - four *= 2; - - bptr = (unsigned short *)vq2; - - if (!cinTable[currentHandle].half) { - if (!cinTable[currentHandle].smootheddouble) { -// -// normal height -// - if (cinTable[currentHandle].samplesPerPixel==2) { - for(i=0;i cinTable[currentHandle].CIN_WIDTH) bigx = cinTable[currentHandle].CIN_WIDTH; - if (bigy > cinTable[currentHandle].CIN_HEIGHT) bigy = cinTable[currentHandle].CIN_HEIGHT; - - if ( (startX >= lowx) && (startX+quadSize) <= (bigx) && (startY+quadSize) <= (bigy) && (startY >= lowy) && quadSize <= MAXSIZE) { - useY = startY; - scroff = cin.linbuf + (useY+((cinTable[currentHandle].CIN_HEIGHT-bigy)>>1)+yOff)*(cinTable[currentHandle].samplesPerLine) + (((startX+xOff))*cinTable[currentHandle].samplesPerPixel); - - cin.qStatus[0][cinTable[currentHandle].onQuad ] = scroff; - cin.qStatus[1][cinTable[currentHandle].onQuad++] = scroff+offset; - } - - if ( quadSize != MINSIZE ) { - quadSize >>= 1; - recurseQuad( startX, startY , quadSize, xOff, yOff ); - recurseQuad( startX+quadSize, startY , quadSize, xOff, yOff ); - recurseQuad( startX, startY+quadSize , quadSize, xOff, yOff ); - recurseQuad( startX+quadSize, startY+quadSize , quadSize, xOff, yOff ); - } -} - - -/****************************************************************************** -* -* Function: -* -* Description: -* -******************************************************************************/ - -static void setupQuad( long xOff, long yOff ) -{ - long numQuadCels, i,x,y; - byte *temp; - - if (xOff == cin.oldXOff && yOff == cin.oldYOff && cinTable[currentHandle].ysize == cin.oldysize && cinTable[currentHandle].xsize == cin.oldxsize) { - return; - } - - cin.oldXOff = xOff; - cin.oldYOff = yOff; - cin.oldysize = cinTable[currentHandle].ysize; - cin.oldxsize = cinTable[currentHandle].xsize; - - numQuadCels = (cinTable[currentHandle].CIN_WIDTH*cinTable[currentHandle].CIN_HEIGHT) / (16); - numQuadCels += numQuadCels/4 + numQuadCels/16; - numQuadCels += 64; // for overflow - - numQuadCels = (cinTable[currentHandle].xsize*cinTable[currentHandle].ysize) / (16); - numQuadCels += numQuadCels/4; - numQuadCels += 64; // for overflow - - cinTable[currentHandle].onQuad = 0; - - for(y=0;y<(long)cinTable[currentHandle].ysize;y+=16) - for(x=0;x<(long)cinTable[currentHandle].xsize;x+=16) - recurseQuad( x, y, 16, xOff, yOff ); - - temp = NULL; - - for(i=(numQuadCels-64);i256) { - cinTable[currentHandle].drawX = 256; - } - if (cinTable[currentHandle].drawY>256) { - cinTable[currentHandle].drawY = 256; - } - if (cinTable[currentHandle].CIN_WIDTH != 256 || cinTable[currentHandle].CIN_HEIGHT != 256) { - Com_Printf("HACK: approxmimating cinematic for Rage Pro or Voodoo\n"); - } - } -#if defined(MACOS_X) - cinTable[currentHandle].drawX = 256; - cinTable[currentHandle].drawX = 256; -#endif -} - -/****************************************************************************** -* -* Function: -* -* Description: -* -******************************************************************************/ - -static void RoQPrepMcomp( long xoff, long yoff ) -{ - long i, j, x, y, temp, temp2; - - i=cinTable[currentHandle].samplesPerLine; j=cinTable[currentHandle].samplesPerPixel; - if ( cinTable[currentHandle].xsize == (cinTable[currentHandle].ysize*4) && !cinTable[currentHandle].half ) { j = j+j; i = i+i; } - - for(y=0;y<16;y++) { - temp2 = (y+yoff-8)*i; - for(x=0;x<16;x++) { - temp = (x+xoff-8)*j; - cin.mcomp[(x*16)+y] = cinTable[currentHandle].normalBuffer0-(temp2+temp); - } - } -} - -/****************************************************************************** -* -* Function: -* -* Description: -* -******************************************************************************/ - -static void initRoQ() -{ - if (currentHandle < 0) return; - - cinTable[currentHandle].VQNormal = (void (*)(byte *, void *))blitVQQuad32fs; - cinTable[currentHandle].VQBuffer = (void (*)(byte *, void *))blitVQQuad32fs; - cinTable[currentHandle].samplesPerPixel = 4; - ROQ_GenYUVTables(); - RllSetupTable(); -} - -/****************************************************************************** -* -* Function: -* -* Description: -* -******************************************************************************/ -/* -static byte* RoQFetchInterlaced( byte *source ) { - int x, *src, *dst; - - if (currentHandle < 0) return NULL; - - src = (int *)source; - dst = (int *)cinTable[currentHandle].buf2; - - for(x=0;x<256*256;x++) { - *dst = *src; - dst++; src += 2; - } - return cinTable[currentHandle].buf2; -} -*/ -static void RoQReset() { - - if (currentHandle < 0) return; - - Sys_EndStreamedFile(cinTable[currentHandle].iFile); - FS_FCloseFile( cinTable[currentHandle].iFile ); - FS_FOpenFileRead (cinTable[currentHandle].fileName, &cinTable[currentHandle].iFile, qtrue); - // let the background thread start reading ahead - Sys_BeginStreamedFile( cinTable[currentHandle].iFile, 0x10000 ); - Sys_StreamedRead (cin.file, 16, 1, cinTable[currentHandle].iFile); - RoQ_init(); - cinTable[currentHandle].status = FMV_LOOPED; -} - -/****************************************************************************** -* -* Function: -* -* Description: -* -******************************************************************************/ - -static void RoQInterrupt(void) -{ - byte *framedata; - short sbuf[32768]; - int ssize; - - if (currentHandle < 0) return; - - Sys_StreamedRead( cin.file, cinTable[currentHandle].RoQFrameSize+8, 1, cinTable[currentHandle].iFile ); - if ( cinTable[currentHandle].RoQPlayed >= cinTable[currentHandle].ROQSize ) { - if (cinTable[currentHandle].holdAtEnd==qfalse) { - if (cinTable[currentHandle].looping) { - RoQReset(); - } else { - cinTable[currentHandle].status = FMV_EOF; - } - } else { - cinTable[currentHandle].status = FMV_IDLE; - } - return; - } - - framedata = cin.file; -// -// new frame is ready -// -redump: - switch(cinTable[currentHandle].roq_id) - { - case ROQ_QUAD_VQ: - if ((cinTable[currentHandle].numQuads&1)) { - cinTable[currentHandle].normalBuffer0 = cinTable[currentHandle].t[1]; - RoQPrepMcomp( cinTable[currentHandle].roqF0, cinTable[currentHandle].roqF1 ); - cinTable[currentHandle].VQ1( (byte *)cin.qStatus[1], framedata); - cinTable[currentHandle].buf = cin.linbuf + cinTable[currentHandle].screenDelta; - } else { - cinTable[currentHandle].normalBuffer0 = cinTable[currentHandle].t[0]; - RoQPrepMcomp( cinTable[currentHandle].roqF0, cinTable[currentHandle].roqF1 ); - cinTable[currentHandle].VQ0( (byte *)cin.qStatus[0], framedata ); - cinTable[currentHandle].buf = cin.linbuf; - } - if (cinTable[currentHandle].numQuads == 0) { // first frame - Com_Memcpy(cin.linbuf+cinTable[currentHandle].screenDelta, cin.linbuf, cinTable[currentHandle].samplesPerLine*cinTable[currentHandle].ysize); - } - cinTable[currentHandle].numQuads++; - cinTable[currentHandle].dirty = qtrue; - break; - case ROQ_CODEBOOK: - decodeCodeBook( framedata, (unsigned short)cinTable[currentHandle].roq_flags ); - break; - case ZA_SOUND_MONO: - if (!cinTable[currentHandle].silent) { - ssize = RllDecodeMonoToStereo( framedata, sbuf, cinTable[currentHandle].RoQFrameSize, 0, (unsigned short)cinTable[currentHandle].roq_flags); - S_RawSamples( ssize, 22050, 2, 1, (byte *)sbuf, 1.0f ); - } - break; - case ZA_SOUND_STEREO: - if (!cinTable[currentHandle].silent) { - if (cinTable[currentHandle].numQuads == -1) { - S_Update(); - s_rawend = s_soundtime; - } - ssize = RllDecodeStereoToStereo( framedata, sbuf, cinTable[currentHandle].RoQFrameSize, 0, (unsigned short)cinTable[currentHandle].roq_flags); - S_RawSamples( ssize, 22050, 2, 2, (byte *)sbuf, 1.0f ); - } - break; - case ROQ_QUAD_INFO: - if (cinTable[currentHandle].numQuads == -1) { - readQuadInfo( framedata ); - setupQuad( 0, 0 ); - // we need to use CL_ScaledMilliseconds because of the smp mode calls from the renderer - cinTable[currentHandle].startTime = cinTable[currentHandle].lastTime = CL_ScaledMilliseconds()*com_timescale->value; - } - if (cinTable[currentHandle].numQuads != 1) cinTable[currentHandle].numQuads = 0; - break; - case ROQ_PACKET: - cinTable[currentHandle].inMemory = cinTable[currentHandle].roq_flags; - cinTable[currentHandle].RoQFrameSize = 0; // for header - break; - case ROQ_QUAD_HANG: - cinTable[currentHandle].RoQFrameSize = 0; - break; - case ROQ_QUAD_JPEG: - break; - default: - cinTable[currentHandle].status = FMV_EOF; - break; - } -// -// read in next frame data -// - if ( cinTable[currentHandle].RoQPlayed >= cinTable[currentHandle].ROQSize ) { - if (cinTable[currentHandle].holdAtEnd==qfalse) { - if (cinTable[currentHandle].looping) { - RoQReset(); - } else { - cinTable[currentHandle].status = FMV_EOF; - } - } else { - cinTable[currentHandle].status = FMV_IDLE; - } - return; - } - - framedata += cinTable[currentHandle].RoQFrameSize; - cinTable[currentHandle].roq_id = framedata[0] + framedata[1]*256; - cinTable[currentHandle].RoQFrameSize = framedata[2] + framedata[3]*256 + framedata[4]*65536; - cinTable[currentHandle].roq_flags = framedata[6] + framedata[7]*256; - cinTable[currentHandle].roqF0 = (char)framedata[7]; - cinTable[currentHandle].roqF1 = (char)framedata[6]; - - if (cinTable[currentHandle].RoQFrameSize>65536||cinTable[currentHandle].roq_id==0x1084) { - Com_DPrintf("roq_size>65536||roq_id==0x1084\n"); - cinTable[currentHandle].status = FMV_EOF; - if (cinTable[currentHandle].looping) { - RoQReset(); - } - return; - } - if (cinTable[currentHandle].inMemory && (cinTable[currentHandle].status != FMV_EOF)) { cinTable[currentHandle].inMemory--; framedata += 8; goto redump; } -// -// one more frame hits the dust -// -// assert(cinTable[currentHandle].RoQFrameSize <= 65536); -// r = Sys_StreamedRead( cin.file, cinTable[currentHandle].RoQFrameSize+8, 1, cinTable[currentHandle].iFile ); - cinTable[currentHandle].RoQPlayed += cinTable[currentHandle].RoQFrameSize+8; -} - -/****************************************************************************** -* -* Function: -* -* Description: -* -******************************************************************************/ - -static void RoQ_init( void ) -{ - // we need to use CL_ScaledMilliseconds because of the smp mode calls from the renderer - cinTable[currentHandle].startTime = cinTable[currentHandle].lastTime = CL_ScaledMilliseconds()*com_timescale->value; - - cinTable[currentHandle].RoQPlayed = 24; - -/* get frame rate */ - cinTable[currentHandle].roqFPS = cin.file[ 6] + cin.file[ 7]*256; - - if (!cinTable[currentHandle].roqFPS) cinTable[currentHandle].roqFPS = 30; - - cinTable[currentHandle].numQuads = -1; - - cinTable[currentHandle].roq_id = cin.file[ 8] + cin.file[ 9]*256; - cinTable[currentHandle].RoQFrameSize = cin.file[10] + cin.file[11]*256 + cin.file[12]*65536; - cinTable[currentHandle].roq_flags = cin.file[14] + cin.file[15]*256; - - if (cinTable[currentHandle].RoQFrameSize > 65536 || !cinTable[currentHandle].RoQFrameSize) { - return; - } - -} - -/****************************************************************************** -* -* Function: -* -* Description: -* -******************************************************************************/ - -static void RoQShutdown( void ) { - const char *s; - - if (!cinTable[currentHandle].buf) { - return; - } - - if ( cinTable[currentHandle].status == FMV_IDLE ) { - return; - } - Com_DPrintf("finished cinematic\n"); - cinTable[currentHandle].status = FMV_IDLE; - - if (cinTable[currentHandle].iFile) { - Sys_EndStreamedFile( cinTable[currentHandle].iFile ); - FS_FCloseFile( cinTable[currentHandle].iFile ); - cinTable[currentHandle].iFile = 0; - } - - if (cinTable[currentHandle].alterGameState) { - cls.state = CA_DISCONNECTED; - // we can't just do a vstr nextmap, because - // if we are aborting the intro cinematic with - // a devmap command, nextmap would be valid by - // the time it was referenced - s = Cvar_VariableString( "nextmap" ); - if ( s[0] ) { - Cbuf_ExecuteText( EXEC_APPEND, va("%s\n", s) ); - Cvar_Set( "nextmap", "" ); - } - CL_handle = -1; - } - cinTable[currentHandle].fileName[0] = 0; - currentHandle = -1; -} - -/* -================== -SCR_StopCinematic -================== -*/ -e_status CIN_StopCinematic(int handle) { - - if (handle < 0 || handle>= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return FMV_EOF; - currentHandle = handle; - - Com_DPrintf("trFMV::stop(), closing %s\n", cinTable[currentHandle].fileName); - - if (!cinTable[currentHandle].buf) { - return FMV_EOF; - } - - if (cinTable[currentHandle].alterGameState) { - if ( cls.state != CA_CINEMATIC ) { - return cinTable[currentHandle].status; - } - } - cinTable[currentHandle].status = FMV_EOF; - RoQShutdown(); - - return FMV_EOF; -} - -/* -================== -SCR_RunCinematic - -Fetch and decompress the pending frame -================== -*/ - - -e_status CIN_RunCinematic (int handle) -{ - // bk001204 - init - int start = 0; - int thisTime = 0; - - if (handle < 0 || handle>= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return FMV_EOF; - - if (cin.currentHandle != handle) { - currentHandle = handle; - cin.currentHandle = currentHandle; - cinTable[currentHandle].status = FMV_EOF; - RoQReset(); - } - - if (cinTable[handle].playonwalls < -1) - { - return cinTable[handle].status; - } - - currentHandle = handle; - - if (cinTable[currentHandle].alterGameState) { - if ( cls.state != CA_CINEMATIC ) { - return cinTable[currentHandle].status; - } - } - - if (cinTable[currentHandle].status == FMV_IDLE) { - return cinTable[currentHandle].status; - } - - // we need to use CL_ScaledMilliseconds because of the smp mode calls from the renderer - thisTime = CL_ScaledMilliseconds()*com_timescale->value; - if (cinTable[currentHandle].shader && (abs(thisTime - cinTable[currentHandle].lastTime))>100) { - cinTable[currentHandle].startTime += thisTime - cinTable[currentHandle].lastTime; - } - // we need to use CL_ScaledMilliseconds because of the smp mode calls from the renderer - cinTable[currentHandle].tfps = ((((CL_ScaledMilliseconds()*com_timescale->value) - cinTable[currentHandle].startTime)*3)/100); - - start = cinTable[currentHandle].startTime; - while( (cinTable[currentHandle].tfps != cinTable[currentHandle].numQuads) - && (cinTable[currentHandle].status == FMV_PLAY) ) - { - RoQInterrupt(); - if (start != cinTable[currentHandle].startTime) { - // we need to use CL_ScaledMilliseconds because of the smp mode calls from the renderer - cinTable[currentHandle].tfps = ((((CL_ScaledMilliseconds()*com_timescale->value) - - cinTable[currentHandle].startTime)*3)/100); - start = cinTable[currentHandle].startTime; - } - } - - cinTable[currentHandle].lastTime = thisTime; - - if (cinTable[currentHandle].status == FMV_LOOPED) { - cinTable[currentHandle].status = FMV_PLAY; - } - - if (cinTable[currentHandle].status == FMV_EOF) { - if (cinTable[currentHandle].looping) { - RoQReset(); - } else { - RoQShutdown(); - } - } - - return cinTable[currentHandle].status; -} - -/* -================== -CL_PlayCinematic - -================== -*/ -int CIN_PlayCinematic( const char *arg, int x, int y, int w, int h, int systemBits ) { - unsigned short RoQID; - char name[MAX_OSPATH]; - int i; - - if (strstr(arg, "/") == NULL && strstr(arg, "\\") == NULL) { - Com_sprintf (name, sizeof(name), "video/%s", arg); - } else { - Com_sprintf (name, sizeof(name), "%s", arg); - } - - if (!(systemBits & CIN_system)) { - for ( i = 0 ; i < MAX_VIDEO_HANDLES ; i++ ) { - if (!strcmp(cinTable[i].fileName, name) ) { - return i; - } - } - } - - Com_DPrintf("SCR_PlayCinematic( %s )\n", arg); - - Com_Memset(&cin, 0, sizeof(cinematics_t) ); - currentHandle = CIN_HandleForVideo(); - - cin.currentHandle = currentHandle; - - strcpy(cinTable[currentHandle].fileName, name); - - cinTable[currentHandle].ROQSize = 0; - cinTable[currentHandle].ROQSize = FS_FOpenFileRead (cinTable[currentHandle].fileName, &cinTable[currentHandle].iFile, qtrue); - - if (cinTable[currentHandle].ROQSize<=0) { - Com_DPrintf("play(%s), ROQSize<=0\n", arg); - cinTable[currentHandle].fileName[0] = 0; - return -1; - } - - CIN_SetExtents(currentHandle, x, y, w, h); - CIN_SetLooping(currentHandle, (systemBits & CIN_loop)!=0); - - cinTable[currentHandle].CIN_HEIGHT = DEFAULT_CIN_HEIGHT; - cinTable[currentHandle].CIN_WIDTH = DEFAULT_CIN_WIDTH; - cinTable[currentHandle].holdAtEnd = (systemBits & CIN_hold) != 0; - cinTable[currentHandle].alterGameState = (systemBits & CIN_system) != 0; - cinTable[currentHandle].playonwalls = 1; - cinTable[currentHandle].silent = (systemBits & CIN_silent) != 0; - cinTable[currentHandle].shader = (systemBits & CIN_shader) != 0; - - if (cinTable[currentHandle].alterGameState) { - // close the menu - if ( uivm ) { - VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_NONE ); - } - } else { - cinTable[currentHandle].playonwalls = cl_inGameVideo->integer; - } - - initRoQ(); - - FS_Read (cin.file, 16, cinTable[currentHandle].iFile); - - RoQID = (unsigned short)(cin.file[0]) + (unsigned short)(cin.file[1])*256; - if (RoQID == 0x1084) - { - RoQ_init(); -// FS_Read (cin.file, cinTable[currentHandle].RoQFrameSize+8, cinTable[currentHandle].iFile); - // let the background thread start reading ahead - Sys_BeginStreamedFile( cinTable[currentHandle].iFile, 0x10000 ); - - cinTable[currentHandle].status = FMV_PLAY; - Com_DPrintf("trFMV::play(), playing %s\n", arg); - - if (cinTable[currentHandle].alterGameState) { - cls.state = CA_CINEMATIC; - } - - Con_Close(); - - s_rawend = s_soundtime; - - return currentHandle; - } - Com_DPrintf("trFMV::play(), invalid RoQ ID\n"); - - RoQShutdown(); - return -1; -} - -void CIN_SetExtents (int handle, int x, int y, int w, int h) { - if (handle < 0 || handle>= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return; - cinTable[handle].xpos = x; - cinTable[handle].ypos = y; - cinTable[handle].width = w; - cinTable[handle].height = h; - cinTable[handle].dirty = qtrue; -} - -void CIN_SetLooping(int handle, qboolean loop) { - if (handle < 0 || handle>= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return; - cinTable[handle].looping = loop; -} - -/* -================== -SCR_DrawCinematic - -================== -*/ -void CIN_DrawCinematic (int handle) { - float x, y, w, h; - byte *buf; - - if (handle < 0 || handle>= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return; - - if (!cinTable[handle].buf) { - return; - } - - x = cinTable[handle].xpos; - y = cinTable[handle].ypos; - w = cinTable[handle].width; - h = cinTable[handle].height; - buf = cinTable[handle].buf; - SCR_AdjustFrom640( &x, &y, &w, &h ); - - if (cinTable[handle].dirty && (cinTable[handle].CIN_WIDTH != cinTable[handle].drawX || cinTable[handle].CIN_HEIGHT != cinTable[handle].drawY)) { - int ix, iy, *buf2, *buf3, xm, ym, ll; - - xm = cinTable[handle].CIN_WIDTH/256; - ym = cinTable[handle].CIN_HEIGHT/256; - ll = 8; - if (cinTable[handle].CIN_WIDTH==512) { - ll = 9; - } - - buf3 = (int*)buf; - buf2 = Hunk_AllocateTempMemory( 256*256*4 ); - if (xm==2 && ym==2) { - byte *bc2, *bc3; - int ic, iiy; - - bc2 = (byte *)buf2; - bc3 = (byte *)buf3; - for (iy = 0; iy<256; iy++) { - iiy = iy<<12; - for (ix = 0; ix<2048; ix+=8) { - for(ic = ix;ic<(ix+4);ic++) { - *bc2=(bc3[iiy+ic]+bc3[iiy+4+ic]+bc3[iiy+2048+ic]+bc3[iiy+2048+4+ic])>>2; - bc2++; - } - } - } - } else if (xm==2 && ym==1) { - byte *bc2, *bc3; - int ic, iiy; - - bc2 = (byte *)buf2; - bc3 = (byte *)buf3; - for (iy = 0; iy<256; iy++) { - iiy = iy<<11; - for (ix = 0; ix<2048; ix+=8) { - for(ic = ix;ic<(ix+4);ic++) { - *bc2=(bc3[iiy+ic]+bc3[iiy+4+ic])>>1; - bc2++; - } - } - } - } else { - for (iy = 0; iy<256; iy++) { - for (ix = 0; ix<256; ix++) { - buf2[(iy<<8)+ix] = buf3[((iy*ym)<= 0) { - do { - SCR_RunCinematic(); - } while (cinTable[currentHandle].buf == NULL && cinTable[currentHandle].status == FMV_PLAY); // wait for first frame (load codebook and sound) - } -} - - -void SCR_DrawCinematic (void) { - if (CL_handle >= 0 && CL_handle < MAX_VIDEO_HANDLES) { - CIN_DrawCinematic(CL_handle); - } -} - -void SCR_RunCinematic (void) -{ - if (CL_handle >= 0 && CL_handle < MAX_VIDEO_HANDLES) { - CIN_RunCinematic(CL_handle); - } -} - -void SCR_StopCinematic(void) { - if (CL_handle >= 0 && CL_handle < MAX_VIDEO_HANDLES) { - CIN_StopCinematic(CL_handle); - S_StopAllSounds (); - CL_handle = -1; - } -} - -void CIN_UploadCinematic(int handle) { - if (handle >= 0 && handle < MAX_VIDEO_HANDLES) { - if (!cinTable[handle].buf) { - return; - } - if (cinTable[handle].playonwalls <= 0 && cinTable[handle].dirty) { - if (cinTable[handle].playonwalls == 0) { - cinTable[handle].playonwalls = -1; - } else { - if (cinTable[handle].playonwalls == -1) { - cinTable[handle].playonwalls = -2; - } else { - cinTable[handle].dirty = qfalse; - } - } - } - re.UploadCinematic( 256, 256, 256, 256, cinTable[handle].buf, handle, cinTable[handle].dirty); - if (cl_inGameVideo->integer == 0 && cinTable[handle].playonwalls == 1) { - cinTable[handle].playonwalls--; - } - } -} - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: cl_cin.c + * + * desc: video and cinematic playback + * + * $Archive: /MissionPack/code/client/cl_cin.c $ + * + * cl_glconfig.hwtype trtypes 3dfx/ragepro need 256x256 + * + *****************************************************************************/ + +#include "client.h" +#include "snd_local.h" + +#define MAXSIZE 8 +#define MINSIZE 4 + +#define DEFAULT_CIN_WIDTH 512 +#define DEFAULT_CIN_HEIGHT 512 + +#define ROQ_QUAD 0x1000 +#define ROQ_QUAD_INFO 0x1001 +#define ROQ_CODEBOOK 0x1002 +#define ROQ_QUAD_VQ 0x1011 +#define ROQ_QUAD_JPEG 0x1012 +#define ROQ_QUAD_HANG 0x1013 +#define ROQ_PACKET 0x1030 +#define ZA_SOUND_MONO 0x1020 +#define ZA_SOUND_STEREO 0x1021 + +#define MAX_VIDEO_HANDLES 16 + +extern glconfig_t glConfig; +extern int s_paintedtime; +extern int s_rawend; + + +static void RoQ_init( void ); + +/****************************************************************************** +* +* Class: trFMV +* +* Description: RoQ/RnR manipulation routines +* not entirely complete for first run +* +******************************************************************************/ + +static long ROQ_YY_tab[256]; +static long ROQ_UB_tab[256]; +static long ROQ_UG_tab[256]; +static long ROQ_VG_tab[256]; +static long ROQ_VR_tab[256]; +static unsigned short vq2[256*16*4]; +static unsigned short vq4[256*64*4]; +static unsigned short vq8[256*256*4]; + + +typedef struct { + byte linbuf[DEFAULT_CIN_WIDTH*DEFAULT_CIN_HEIGHT*4*2]; + byte file[65536]; + short sqrTable[256]; + + unsigned int mcomp[256]; + byte *qStatus[2][32768]; + + long oldXOff, oldYOff, oldysize, oldxsize; + + int currentHandle; +} cinematics_t; + +typedef struct { + char fileName[MAX_OSPATH]; + int CIN_WIDTH, CIN_HEIGHT; + int xpos, ypos, width, height; + qboolean looping, holdAtEnd, dirty, alterGameState, silent, shader; + fileHandle_t iFile; + e_status status; + unsigned int startTime; + unsigned int lastTime; + long tfps; + long RoQPlayed; + long ROQSize; + unsigned int RoQFrameSize; + long onQuad; + long numQuads; + long samplesPerLine; + unsigned int roq_id; + long screenDelta; + + void ( *VQ0)(byte *status, void *qdata ); + void ( *VQ1)(byte *status, void *qdata ); + void ( *VQNormal)(byte *status, void *qdata ); + void ( *VQBuffer)(byte *status, void *qdata ); + + long samplesPerPixel; // defaults to 2 + byte* gray; + unsigned int xsize, ysize, maxsize, minsize; + + qboolean half, smootheddouble, inMemory; + long normalBuffer0; + long roq_flags; + long roqF0; + long roqF1; + long t[2]; + long roqFPS; + int playonwalls; + byte* buf; + long drawX, drawY; +} cin_cache; + +static cinematics_t cin; +static cin_cache cinTable[MAX_VIDEO_HANDLES]; +static int currentHandle = -1; +static int CL_handle = -1; + +extern int s_soundtime; // sample PAIRS +extern int s_paintedtime; // sample PAIRS + + +void CIN_CloseAllVideos(void) { + int i; + + for ( i = 0 ; i < MAX_VIDEO_HANDLES ; i++ ) { + if (cinTable[i].fileName[0] != 0 ) { + CIN_StopCinematic(i); + } + } +} + + +static int CIN_HandleForVideo(void) { + int i; + + for ( i = 0 ; i < MAX_VIDEO_HANDLES ; i++ ) { + if ( cinTable[i].fileName[0] == 0 ) { + return i; + } + } + Com_Error( ERR_DROP, "CIN_HandleForVideo: none free" ); + return -1; +} + + +extern int CL_ScaledMilliseconds(void); + +//----------------------------------------------------------------------------- +// RllSetupTable +// +// Allocates and initializes the square table. +// +// Parameters: None +// +// Returns: Nothing +//----------------------------------------------------------------------------- +static void RllSetupTable() +{ + int z; + + for (z=0;z<128;z++) { + cin.sqrTable[z] = (short)(z*z); + cin.sqrTable[z+128] = (short)(-cin.sqrTable[z]); + } +} + + + +//----------------------------------------------------------------------------- +// RllDecodeMonoToMono +// +// Decode mono source data into a mono buffer. +// +// Parameters: from -> buffer holding encoded data +// to -> buffer to hold decoded data +// size = number of bytes of input (= # of shorts of output) +// signedOutput = 0 for unsigned output, non-zero for signed output +// flag = flags from asset header +// +// Returns: Number of samples placed in output buffer +//----------------------------------------------------------------------------- +long RllDecodeMonoToMono(unsigned char *from,short *to,unsigned int size,char signedOutput ,unsigned short flag) +{ + unsigned int z; + int prev; + + if (signedOutput) + prev = flag - 0x8000; + else + prev = flag; + + for (z=0;z buffer holding encoded data +// to -> buffer to hold decoded data +// size = number of bytes of input (= 1/4 # of bytes of output) +// signedOutput = 0 for unsigned output, non-zero for signed output +// flag = flags from asset header +// +// Returns: Number of samples placed in output buffer +//----------------------------------------------------------------------------- +long RllDecodeMonoToStereo(unsigned char *from,short *to,unsigned int size,char signedOutput,unsigned short flag) +{ + unsigned int z; + int prev; + + if (signedOutput) + prev = flag - 0x8000; + else + prev = flag; + + for (z = 0; z < size; z++) { + prev = (short)(prev + cin.sqrTable[from[z]]); + to[z*2+0] = to[z*2+1] = (short)(prev); + } + + return size; // * 2 * sizeof(short)); +} + + +//----------------------------------------------------------------------------- +// RllDecodeStereoToStereo +// +// Decode stereo source data into a stereo buffer. +// +// Parameters: from -> buffer holding encoded data +// to -> buffer to hold decoded data +// size = number of bytes of input (= 1/2 # of bytes of output) +// signedOutput = 0 for unsigned output, non-zero for signed output +// flag = flags from asset header +// +// Returns: Number of samples placed in output buffer +//----------------------------------------------------------------------------- +long RllDecodeStereoToStereo(unsigned char *from,short *to,unsigned int size,char signedOutput, unsigned short flag) +{ + unsigned int z; + unsigned char *zz = from; + int prevL, prevR; + + if (signedOutput) { + prevL = (flag & 0xff00) - 0x8000; + prevR = ((flag & 0x00ff) << 8) - 0x8000; + } else { + prevL = flag & 0xff00; + prevR = (flag & 0x00ff) << 8; + } + + for (z=0;z>1); //*sizeof(short)); +} + + +//----------------------------------------------------------------------------- +// RllDecodeStereoToMono +// +// Decode stereo source data into a mono buffer. +// +// Parameters: from -> buffer holding encoded data +// to -> buffer to hold decoded data +// size = number of bytes of input (= # of bytes of output) +// signedOutput = 0 for unsigned output, non-zero for signed output +// flag = flags from asset header +// +// Returns: Number of samples placed in output buffer +//----------------------------------------------------------------------------- +long RllDecodeStereoToMono(unsigned char *from,short *to,unsigned int size,char signedOutput, unsigned short flag) +{ + unsigned int z; + int prevL,prevR; + + if (signedOutput) { + prevL = (flag & 0xff00) - 0x8000; + prevR = ((flag & 0x00ff) << 8) -0x8000; + } else { + prevL = flag & 0xff00; + prevR = (flag & 0x00ff) << 8; + } + + for (z=0;z>3; + + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += dspl; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += dspl; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += dspl; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += dspl; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += dspl; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += dspl; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += dspl; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void move4_32( byte *src, byte *dst, int spl ) +{ + double *dsrc, *ddst; + int dspl; + + dsrc = (double *)src; + ddst = (double *)dst; + dspl = spl>>3; + + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; + dsrc += dspl; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; + dsrc += dspl; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; + dsrc += dspl; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void blit8_32( byte *src, byte *dst, int spl ) +{ + double *dsrc, *ddst; + int dspl; + + dsrc = (double *)src; + ddst = (double *)dst; + dspl = spl>>3; + + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += 4; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += 4; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += 4; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += 4; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += 4; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += 4; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += 4; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ +#define movs double +static void blit4_32( byte *src, byte *dst, int spl ) +{ + movs *dsrc, *ddst; + int dspl; + + dsrc = (movs *)src; + ddst = (movs *)dst; + dspl = spl>>3; + + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; + dsrc += 2; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; + dsrc += 2; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; + dsrc += 2; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void blit2_32( byte *src, byte *dst, int spl ) +{ + double *dsrc, *ddst; + int dspl; + + dsrc = (double *)src; + ddst = (double *)dst; + dspl = spl>>3; + + ddst[0] = dsrc[0]; + ddst[dspl] = dsrc[1]; +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void blitVQQuad32fs( byte **status, unsigned char *data ) +{ +unsigned short newd, celdata, code; +unsigned int index, i; +int spl; + + newd = 0; + celdata = 0; + index = 0; + + spl = cinTable[currentHandle].samplesPerLine; + + do { + if (!newd) { + newd = 7; + celdata = data[0] + data[1]*256; + data += 2; + } else { + newd--; + } + + code = (unsigned short)(celdata&0xc000); + celdata <<= 2; + + switch (code) { + case 0x8000: // vq code + blit8_32( (byte *)&vq8[(*data)*128], status[index], spl ); + data++; + index += 5; + break; + case 0xc000: // drop + index++; // skip 8x8 + for(i=0;i<4;i++) { + if (!newd) { + newd = 7; + celdata = data[0] + data[1]*256; + data += 2; + } else { + newd--; + } + + code = (unsigned short)(celdata&0xc000); celdata <<= 2; + + switch (code) { // code in top two bits of code + case 0x8000: // 4x4 vq code + blit4_32( (byte *)&vq4[(*data)*32], status[index], spl ); + data++; + break; + case 0xc000: // 2x2 vq code + blit2_32( (byte *)&vq2[(*data)*8], status[index], spl ); + data++; + blit2_32( (byte *)&vq2[(*data)*8], status[index]+8, spl ); + data++; + blit2_32( (byte *)&vq2[(*data)*8], status[index]+spl*2, spl ); + data++; + blit2_32( (byte *)&vq2[(*data)*8], status[index]+spl*2+8, spl ); + data++; + break; + case 0x4000: // motion compensation + move4_32( status[index] + cin.mcomp[(*data)], status[index], spl ); + data++; + break; + } + index++; + } + break; + case 0x4000: // motion compensation + move8_32( status[index] + cin.mcomp[(*data)], status[index], spl ); + data++; + index += 5; + break; + case 0x0000: + index += 5; + break; + } + } while ( status[index] != NULL ); +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void ROQ_GenYUVTables( void ) +{ + float t_ub,t_vr,t_ug,t_vg; + long i; + + t_ub = (1.77200f/2.0f) * (float)(1<<6) + 0.5f; + t_vr = (1.40200f/2.0f) * (float)(1<<6) + 0.5f; + t_ug = (0.34414f/2.0f) * (float)(1<<6) + 0.5f; + t_vg = (0.71414f/2.0f) * (float)(1<<6) + 0.5f; + for(i=0;i<256;i++) { + float x = (float)(2 * i - 255); + + ROQ_UB_tab[i] = (long)( ( t_ub * x) + (1<<5)); + ROQ_VR_tab[i] = (long)( ( t_vr * x) + (1<<5)); + ROQ_UG_tab[i] = (long)( (-t_ug * x) ); + ROQ_VG_tab[i] = (long)( (-t_vg * x) + (1<<5)); + ROQ_YY_tab[i] = (long)( (i << 6) | (i >> 2) ); + } +} + +#define VQ2TO4(a,b,c,d) { \ + *c++ = a[0]; \ + *d++ = a[0]; \ + *d++ = a[0]; \ + *c++ = a[1]; \ + *d++ = a[1]; \ + *d++ = a[1]; \ + *c++ = b[0]; \ + *d++ = b[0]; \ + *d++ = b[0]; \ + *c++ = b[1]; \ + *d++ = b[1]; \ + *d++ = b[1]; \ + *d++ = a[0]; \ + *d++ = a[0]; \ + *d++ = a[1]; \ + *d++ = a[1]; \ + *d++ = b[0]; \ + *d++ = b[0]; \ + *d++ = b[1]; \ + *d++ = b[1]; \ + a += 2; b += 2; } + +#define VQ2TO2(a,b,c,d) { \ + *c++ = *a; \ + *d++ = *a; \ + *d++ = *a; \ + *c++ = *b; \ + *d++ = *b; \ + *d++ = *b; \ + *d++ = *a; \ + *d++ = *a; \ + *d++ = *b; \ + *d++ = *b; \ + a++; b++; } + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static unsigned short yuv_to_rgb( long y, long u, long v ) +{ + long r,g,b,YY = (long)(ROQ_YY_tab[(y)]); + + r = (YY + ROQ_VR_tab[v]) >> 9; + g = (YY + ROQ_UG_tab[u] + ROQ_VG_tab[v]) >> 8; + b = (YY + ROQ_UB_tab[u]) >> 9; + + if (r<0) r = 0; if (g<0) g = 0; if (b<0) b = 0; + if (r > 31) r = 31; if (g > 63) g = 63; if (b > 31) b = 31; + + return (unsigned short)((r<<11)+(g<<5)+(b)); +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ +#if defined(MACOS_X) + +static inline unsigned int yuv_to_rgb24( long y, long u, long v ) +{ + long r,g,b,YY; + + YY = (long)(ROQ_YY_tab[(y)]); + + r = (YY + ROQ_VR_tab[v]) >> 6; + g = (YY + ROQ_UG_tab[u] + ROQ_VG_tab[v]) >> 6; + b = (YY + ROQ_UB_tab[u]) >> 6; + + if (r<0) r = 0; if (g<0) g = 0; if (b<0) b = 0; + if (r > 255) r = 255; if (g > 255) g = 255; if (b > 255) b = 255; + + return ((r<<24)|(g<<16)|(b<<8))|(255); //+(255<<24)); +} + +#else +static unsigned int yuv_to_rgb24( long y, long u, long v ) +{ + long r,g,b,YY = (long)(ROQ_YY_tab[(y)]); + + r = (YY + ROQ_VR_tab[v]) >> 6; + g = (YY + ROQ_UG_tab[u] + ROQ_VG_tab[v]) >> 6; + b = (YY + ROQ_UB_tab[u]) >> 6; + + if (r<0) r = 0; if (g<0) g = 0; if (b<0) b = 0; + if (r > 255) r = 255; if (g > 255) g = 255; if (b > 255) b = 255; + + return LittleLong ((r)|(g<<8)|(b<<16)|(255<<24)); +} +#endif + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void decodeCodeBook( byte *input, unsigned short roq_flags ) +{ + long i, j, two, four; + unsigned short *aptr, *bptr, *cptr, *dptr; + long y0,y1,y2,y3,cr,cb; + byte *bbptr, *baptr, *bcptr, *bdptr; + unsigned int *iaptr, *ibptr, *icptr, *idptr; + + if (!roq_flags) { + two = four = 256; + } else { + two = roq_flags>>8; + if (!two) two = 256; + four = roq_flags&0xff; + } + + four *= 2; + + bptr = (unsigned short *)vq2; + + if (!cinTable[currentHandle].half) { + if (!cinTable[currentHandle].smootheddouble) { +// +// normal height +// + if (cinTable[currentHandle].samplesPerPixel==2) { + for(i=0;i cinTable[currentHandle].CIN_WIDTH) bigx = cinTable[currentHandle].CIN_WIDTH; + if (bigy > cinTable[currentHandle].CIN_HEIGHT) bigy = cinTable[currentHandle].CIN_HEIGHT; + + if ( (startX >= lowx) && (startX+quadSize) <= (bigx) && (startY+quadSize) <= (bigy) && (startY >= lowy) && quadSize <= MAXSIZE) { + useY = startY; + scroff = cin.linbuf + (useY+((cinTable[currentHandle].CIN_HEIGHT-bigy)>>1)+yOff)*(cinTable[currentHandle].samplesPerLine) + (((startX+xOff))*cinTable[currentHandle].samplesPerPixel); + + cin.qStatus[0][cinTable[currentHandle].onQuad ] = scroff; + cin.qStatus[1][cinTable[currentHandle].onQuad++] = scroff+offset; + } + + if ( quadSize != MINSIZE ) { + quadSize >>= 1; + recurseQuad( startX, startY , quadSize, xOff, yOff ); + recurseQuad( startX+quadSize, startY , quadSize, xOff, yOff ); + recurseQuad( startX, startY+quadSize , quadSize, xOff, yOff ); + recurseQuad( startX+quadSize, startY+quadSize , quadSize, xOff, yOff ); + } +} + + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void setupQuad( long xOff, long yOff ) +{ + long numQuadCels, i,x,y; + byte *temp; + + if (xOff == cin.oldXOff && yOff == cin.oldYOff && cinTable[currentHandle].ysize == cin.oldysize && cinTable[currentHandle].xsize == cin.oldxsize) { + return; + } + + cin.oldXOff = xOff; + cin.oldYOff = yOff; + cin.oldysize = cinTable[currentHandle].ysize; + cin.oldxsize = cinTable[currentHandle].xsize; + + numQuadCels = (cinTable[currentHandle].CIN_WIDTH*cinTable[currentHandle].CIN_HEIGHT) / (16); + numQuadCels += numQuadCels/4 + numQuadCels/16; + numQuadCels += 64; // for overflow + + numQuadCels = (cinTable[currentHandle].xsize*cinTable[currentHandle].ysize) / (16); + numQuadCels += numQuadCels/4; + numQuadCels += 64; // for overflow + + cinTable[currentHandle].onQuad = 0; + + for(y=0;y<(long)cinTable[currentHandle].ysize;y+=16) + for(x=0;x<(long)cinTable[currentHandle].xsize;x+=16) + recurseQuad( x, y, 16, xOff, yOff ); + + temp = NULL; + + for(i=(numQuadCels-64);i256) { + cinTable[currentHandle].drawX = 256; + } + if (cinTable[currentHandle].drawY>256) { + cinTable[currentHandle].drawY = 256; + } + if (cinTable[currentHandle].CIN_WIDTH != 256 || cinTable[currentHandle].CIN_HEIGHT != 256) { + Com_Printf("HACK: approxmimating cinematic for Rage Pro or Voodoo\n"); + } + } +#if defined(MACOS_X) + cinTable[currentHandle].drawX = 256; + cinTable[currentHandle].drawX = 256; +#endif +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void RoQPrepMcomp( long xoff, long yoff ) +{ + long i, j, x, y, temp, temp2; + + i=cinTable[currentHandle].samplesPerLine; j=cinTable[currentHandle].samplesPerPixel; + if ( cinTable[currentHandle].xsize == (cinTable[currentHandle].ysize*4) && !cinTable[currentHandle].half ) { j = j+j; i = i+i; } + + for(y=0;y<16;y++) { + temp2 = (y+yoff-8)*i; + for(x=0;x<16;x++) { + temp = (x+xoff-8)*j; + cin.mcomp[(x*16)+y] = cinTable[currentHandle].normalBuffer0-(temp2+temp); + } + } +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void initRoQ() +{ + if (currentHandle < 0) return; + + cinTable[currentHandle].VQNormal = (void (*)(byte *, void *))blitVQQuad32fs; + cinTable[currentHandle].VQBuffer = (void (*)(byte *, void *))blitVQQuad32fs; + cinTable[currentHandle].samplesPerPixel = 4; + ROQ_GenYUVTables(); + RllSetupTable(); +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ +/* +static byte* RoQFetchInterlaced( byte *source ) { + int x, *src, *dst; + + if (currentHandle < 0) return NULL; + + src = (int *)source; + dst = (int *)cinTable[currentHandle].buf2; + + for(x=0;x<256*256;x++) { + *dst = *src; + dst++; src += 2; + } + return cinTable[currentHandle].buf2; +} +*/ +static void RoQReset() { + + if (currentHandle < 0) return; + + Sys_EndStreamedFile(cinTable[currentHandle].iFile); + FS_FCloseFile( cinTable[currentHandle].iFile ); + FS_FOpenFileRead (cinTable[currentHandle].fileName, &cinTable[currentHandle].iFile, qtrue); + // let the background thread start reading ahead + Sys_BeginStreamedFile( cinTable[currentHandle].iFile, 0x10000 ); + Sys_StreamedRead (cin.file, 16, 1, cinTable[currentHandle].iFile); + RoQ_init(); + cinTable[currentHandle].status = FMV_LOOPED; +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void RoQInterrupt(void) +{ + byte *framedata; + short sbuf[32768]; + int ssize; + + if (currentHandle < 0) return; + + Sys_StreamedRead( cin.file, cinTable[currentHandle].RoQFrameSize+8, 1, cinTable[currentHandle].iFile ); + if ( cinTable[currentHandle].RoQPlayed >= cinTable[currentHandle].ROQSize ) { + if (cinTable[currentHandle].holdAtEnd==qfalse) { + if (cinTable[currentHandle].looping) { + RoQReset(); + } else { + cinTable[currentHandle].status = FMV_EOF; + } + } else { + cinTable[currentHandle].status = FMV_IDLE; + } + return; + } + + framedata = cin.file; +// +// new frame is ready +// +redump: + switch(cinTable[currentHandle].roq_id) + { + case ROQ_QUAD_VQ: + if ((cinTable[currentHandle].numQuads&1)) { + cinTable[currentHandle].normalBuffer0 = cinTable[currentHandle].t[1]; + RoQPrepMcomp( cinTable[currentHandle].roqF0, cinTable[currentHandle].roqF1 ); + cinTable[currentHandle].VQ1( (byte *)cin.qStatus[1], framedata); + cinTable[currentHandle].buf = cin.linbuf + cinTable[currentHandle].screenDelta; + } else { + cinTable[currentHandle].normalBuffer0 = cinTable[currentHandle].t[0]; + RoQPrepMcomp( cinTable[currentHandle].roqF0, cinTable[currentHandle].roqF1 ); + cinTable[currentHandle].VQ0( (byte *)cin.qStatus[0], framedata ); + cinTable[currentHandle].buf = cin.linbuf; + } + if (cinTable[currentHandle].numQuads == 0) { // first frame + Com_Memcpy(cin.linbuf+cinTable[currentHandle].screenDelta, cin.linbuf, cinTable[currentHandle].samplesPerLine*cinTable[currentHandle].ysize); + } + cinTable[currentHandle].numQuads++; + cinTable[currentHandle].dirty = qtrue; + break; + case ROQ_CODEBOOK: + decodeCodeBook( framedata, (unsigned short)cinTable[currentHandle].roq_flags ); + break; + case ZA_SOUND_MONO: + if (!cinTable[currentHandle].silent) { + ssize = RllDecodeMonoToStereo( framedata, sbuf, cinTable[currentHandle].RoQFrameSize, 0, (unsigned short)cinTable[currentHandle].roq_flags); + S_RawSamples( ssize, 22050, 2, 1, (byte *)sbuf, 1.0f ); + } + break; + case ZA_SOUND_STEREO: + if (!cinTable[currentHandle].silent) { + if (cinTable[currentHandle].numQuads == -1) { + S_Update(); + s_rawend = s_soundtime; + } + ssize = RllDecodeStereoToStereo( framedata, sbuf, cinTable[currentHandle].RoQFrameSize, 0, (unsigned short)cinTable[currentHandle].roq_flags); + S_RawSamples( ssize, 22050, 2, 2, (byte *)sbuf, 1.0f ); + } + break; + case ROQ_QUAD_INFO: + if (cinTable[currentHandle].numQuads == -1) { + readQuadInfo( framedata ); + setupQuad( 0, 0 ); + // we need to use CL_ScaledMilliseconds because of the smp mode calls from the renderer + cinTable[currentHandle].startTime = cinTable[currentHandle].lastTime = CL_ScaledMilliseconds()*com_timescale->value; + } + if (cinTable[currentHandle].numQuads != 1) cinTable[currentHandle].numQuads = 0; + break; + case ROQ_PACKET: + cinTable[currentHandle].inMemory = cinTable[currentHandle].roq_flags; + cinTable[currentHandle].RoQFrameSize = 0; // for header + break; + case ROQ_QUAD_HANG: + cinTable[currentHandle].RoQFrameSize = 0; + break; + case ROQ_QUAD_JPEG: + break; + default: + cinTable[currentHandle].status = FMV_EOF; + break; + } +// +// read in next frame data +// + if ( cinTable[currentHandle].RoQPlayed >= cinTable[currentHandle].ROQSize ) { + if (cinTable[currentHandle].holdAtEnd==qfalse) { + if (cinTable[currentHandle].looping) { + RoQReset(); + } else { + cinTable[currentHandle].status = FMV_EOF; + } + } else { + cinTable[currentHandle].status = FMV_IDLE; + } + return; + } + + framedata += cinTable[currentHandle].RoQFrameSize; + cinTable[currentHandle].roq_id = framedata[0] + framedata[1]*256; + cinTable[currentHandle].RoQFrameSize = framedata[2] + framedata[3]*256 + framedata[4]*65536; + cinTable[currentHandle].roq_flags = framedata[6] + framedata[7]*256; + cinTable[currentHandle].roqF0 = (char)framedata[7]; + cinTable[currentHandle].roqF1 = (char)framedata[6]; + + if (cinTable[currentHandle].RoQFrameSize>65536||cinTable[currentHandle].roq_id==0x1084) { + Com_DPrintf("roq_size>65536||roq_id==0x1084\n"); + cinTable[currentHandle].status = FMV_EOF; + if (cinTable[currentHandle].looping) { + RoQReset(); + } + return; + } + if (cinTable[currentHandle].inMemory && (cinTable[currentHandle].status != FMV_EOF)) { cinTable[currentHandle].inMemory--; framedata += 8; goto redump; } +// +// one more frame hits the dust +// +// assert(cinTable[currentHandle].RoQFrameSize <= 65536); +// r = Sys_StreamedRead( cin.file, cinTable[currentHandle].RoQFrameSize+8, 1, cinTable[currentHandle].iFile ); + cinTable[currentHandle].RoQPlayed += cinTable[currentHandle].RoQFrameSize+8; +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void RoQ_init( void ) +{ + // we need to use CL_ScaledMilliseconds because of the smp mode calls from the renderer + cinTable[currentHandle].startTime = cinTable[currentHandle].lastTime = CL_ScaledMilliseconds()*com_timescale->value; + + cinTable[currentHandle].RoQPlayed = 24; + +/* get frame rate */ + cinTable[currentHandle].roqFPS = cin.file[ 6] + cin.file[ 7]*256; + + if (!cinTable[currentHandle].roqFPS) cinTable[currentHandle].roqFPS = 30; + + cinTable[currentHandle].numQuads = -1; + + cinTable[currentHandle].roq_id = cin.file[ 8] + cin.file[ 9]*256; + cinTable[currentHandle].RoQFrameSize = cin.file[10] + cin.file[11]*256 + cin.file[12]*65536; + cinTable[currentHandle].roq_flags = cin.file[14] + cin.file[15]*256; + + if (cinTable[currentHandle].RoQFrameSize > 65536 || !cinTable[currentHandle].RoQFrameSize) { + return; + } + +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void RoQShutdown( void ) { + const char *s; + + if (!cinTable[currentHandle].buf) { + return; + } + + if ( cinTable[currentHandle].status == FMV_IDLE ) { + return; + } + Com_DPrintf("finished cinematic\n"); + cinTable[currentHandle].status = FMV_IDLE; + + if (cinTable[currentHandle].iFile) { + Sys_EndStreamedFile( cinTable[currentHandle].iFile ); + FS_FCloseFile( cinTable[currentHandle].iFile ); + cinTable[currentHandle].iFile = 0; + } + + if (cinTable[currentHandle].alterGameState) { + cls.state = CA_DISCONNECTED; + // we can't just do a vstr nextmap, because + // if we are aborting the intro cinematic with + // a devmap command, nextmap would be valid by + // the time it was referenced + s = Cvar_VariableString( "nextmap" ); + if ( s[0] ) { + Cbuf_ExecuteText( EXEC_APPEND, va("%s\n", s) ); + Cvar_Set( "nextmap", "" ); + } + CL_handle = -1; + } + cinTable[currentHandle].fileName[0] = 0; + currentHandle = -1; +} + +/* +================== +SCR_StopCinematic +================== +*/ +e_status CIN_StopCinematic(int handle) { + + if (handle < 0 || handle>= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return FMV_EOF; + currentHandle = handle; + + Com_DPrintf("trFMV::stop(), closing %s\n", cinTable[currentHandle].fileName); + + if (!cinTable[currentHandle].buf) { + return FMV_EOF; + } + + if (cinTable[currentHandle].alterGameState) { + if ( cls.state != CA_CINEMATIC ) { + return cinTable[currentHandle].status; + } + } + cinTable[currentHandle].status = FMV_EOF; + RoQShutdown(); + + return FMV_EOF; +} + +/* +================== +SCR_RunCinematic + +Fetch and decompress the pending frame +================== +*/ + + +e_status CIN_RunCinematic (int handle) +{ + // bk001204 - init + int start = 0; + int thisTime = 0; + + if (handle < 0 || handle>= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return FMV_EOF; + + if (cin.currentHandle != handle) { + currentHandle = handle; + cin.currentHandle = currentHandle; + cinTable[currentHandle].status = FMV_EOF; + RoQReset(); + } + + if (cinTable[handle].playonwalls < -1) + { + return cinTable[handle].status; + } + + currentHandle = handle; + + if (cinTable[currentHandle].alterGameState) { + if ( cls.state != CA_CINEMATIC ) { + return cinTable[currentHandle].status; + } + } + + if (cinTable[currentHandle].status == FMV_IDLE) { + return cinTable[currentHandle].status; + } + + // we need to use CL_ScaledMilliseconds because of the smp mode calls from the renderer + thisTime = CL_ScaledMilliseconds()*com_timescale->value; + if (cinTable[currentHandle].shader && (abs(thisTime - cinTable[currentHandle].lastTime))>100) { + cinTable[currentHandle].startTime += thisTime - cinTable[currentHandle].lastTime; + } + // we need to use CL_ScaledMilliseconds because of the smp mode calls from the renderer + cinTable[currentHandle].tfps = ((((CL_ScaledMilliseconds()*com_timescale->value) - cinTable[currentHandle].startTime)*3)/100); + + start = cinTable[currentHandle].startTime; + while( (cinTable[currentHandle].tfps != cinTable[currentHandle].numQuads) + && (cinTable[currentHandle].status == FMV_PLAY) ) + { + RoQInterrupt(); + if (start != cinTable[currentHandle].startTime) { + // we need to use CL_ScaledMilliseconds because of the smp mode calls from the renderer + cinTable[currentHandle].tfps = ((((CL_ScaledMilliseconds()*com_timescale->value) + - cinTable[currentHandle].startTime)*3)/100); + start = cinTable[currentHandle].startTime; + } + } + + cinTable[currentHandle].lastTime = thisTime; + + if (cinTable[currentHandle].status == FMV_LOOPED) { + cinTable[currentHandle].status = FMV_PLAY; + } + + if (cinTable[currentHandle].status == FMV_EOF) { + if (cinTable[currentHandle].looping) { + RoQReset(); + } else { + RoQShutdown(); + } + } + + return cinTable[currentHandle].status; +} + +/* +================== +CL_PlayCinematic + +================== +*/ +int CIN_PlayCinematic( const char *arg, int x, int y, int w, int h, int systemBits ) { + unsigned short RoQID; + char name[MAX_OSPATH]; + int i; + + if (strstr(arg, "/") == NULL && strstr(arg, "\\") == NULL) { + Com_sprintf (name, sizeof(name), "video/%s", arg); + } else { + Com_sprintf (name, sizeof(name), "%s", arg); + } + + if (!(systemBits & CIN_system)) { + for ( i = 0 ; i < MAX_VIDEO_HANDLES ; i++ ) { + if (!strcmp(cinTable[i].fileName, name) ) { + return i; + } + } + } + + Com_DPrintf("SCR_PlayCinematic( %s )\n", arg); + + Com_Memset(&cin, 0, sizeof(cinematics_t) ); + currentHandle = CIN_HandleForVideo(); + + cin.currentHandle = currentHandle; + + strcpy(cinTable[currentHandle].fileName, name); + + cinTable[currentHandle].ROQSize = 0; + cinTable[currentHandle].ROQSize = FS_FOpenFileRead (cinTable[currentHandle].fileName, &cinTable[currentHandle].iFile, qtrue); + + if (cinTable[currentHandle].ROQSize<=0) { + Com_DPrintf("play(%s), ROQSize<=0\n", arg); + cinTable[currentHandle].fileName[0] = 0; + return -1; + } + + CIN_SetExtents(currentHandle, x, y, w, h); + CIN_SetLooping(currentHandle, (systemBits & CIN_loop)!=0); + + cinTable[currentHandle].CIN_HEIGHT = DEFAULT_CIN_HEIGHT; + cinTable[currentHandle].CIN_WIDTH = DEFAULT_CIN_WIDTH; + cinTable[currentHandle].holdAtEnd = (systemBits & CIN_hold) != 0; + cinTable[currentHandle].alterGameState = (systemBits & CIN_system) != 0; + cinTable[currentHandle].playonwalls = 1; + cinTable[currentHandle].silent = (systemBits & CIN_silent) != 0; + cinTable[currentHandle].shader = (systemBits & CIN_shader) != 0; + + if (cinTable[currentHandle].alterGameState) { + // close the menu + if ( uivm ) { + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_NONE ); + } + } else { + cinTable[currentHandle].playonwalls = cl_inGameVideo->integer; + } + + initRoQ(); + + FS_Read (cin.file, 16, cinTable[currentHandle].iFile); + + RoQID = (unsigned short)(cin.file[0]) + (unsigned short)(cin.file[1])*256; + if (RoQID == 0x1084) + { + RoQ_init(); +// FS_Read (cin.file, cinTable[currentHandle].RoQFrameSize+8, cinTable[currentHandle].iFile); + // let the background thread start reading ahead + Sys_BeginStreamedFile( cinTable[currentHandle].iFile, 0x10000 ); + + cinTable[currentHandle].status = FMV_PLAY; + Com_DPrintf("trFMV::play(), playing %s\n", arg); + + if (cinTable[currentHandle].alterGameState) { + cls.state = CA_CINEMATIC; + } + + Con_Close(); + + s_rawend = s_soundtime; + + return currentHandle; + } + Com_DPrintf("trFMV::play(), invalid RoQ ID\n"); + + RoQShutdown(); + return -1; +} + +void CIN_SetExtents (int handle, int x, int y, int w, int h) { + if (handle < 0 || handle>= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return; + cinTable[handle].xpos = x; + cinTable[handle].ypos = y; + cinTable[handle].width = w; + cinTable[handle].height = h; + cinTable[handle].dirty = qtrue; +} + +void CIN_SetLooping(int handle, qboolean loop) { + if (handle < 0 || handle>= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return; + cinTable[handle].looping = loop; +} + +/* +================== +SCR_DrawCinematic + +================== +*/ +void CIN_DrawCinematic (int handle) { + float x, y, w, h; + byte *buf; + + if (handle < 0 || handle>= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return; + + if (!cinTable[handle].buf) { + return; + } + + x = cinTable[handle].xpos; + y = cinTable[handle].ypos; + w = cinTable[handle].width; + h = cinTable[handle].height; + buf = cinTable[handle].buf; + SCR_AdjustFrom640( &x, &y, &w, &h ); + + if (cinTable[handle].dirty && (cinTable[handle].CIN_WIDTH != cinTable[handle].drawX || cinTable[handle].CIN_HEIGHT != cinTable[handle].drawY)) { + int ix, iy, *buf2, *buf3, xm, ym, ll; + + xm = cinTable[handle].CIN_WIDTH/256; + ym = cinTable[handle].CIN_HEIGHT/256; + ll = 8; + if (cinTable[handle].CIN_WIDTH==512) { + ll = 9; + } + + buf3 = (int*)buf; + buf2 = Hunk_AllocateTempMemory( 256*256*4 ); + if (xm==2 && ym==2) { + byte *bc2, *bc3; + int ic, iiy; + + bc2 = (byte *)buf2; + bc3 = (byte *)buf3; + for (iy = 0; iy<256; iy++) { + iiy = iy<<12; + for (ix = 0; ix<2048; ix+=8) { + for(ic = ix;ic<(ix+4);ic++) { + *bc2=(bc3[iiy+ic]+bc3[iiy+4+ic]+bc3[iiy+2048+ic]+bc3[iiy+2048+4+ic])>>2; + bc2++; + } + } + } + } else if (xm==2 && ym==1) { + byte *bc2, *bc3; + int ic, iiy; + + bc2 = (byte *)buf2; + bc3 = (byte *)buf3; + for (iy = 0; iy<256; iy++) { + iiy = iy<<11; + for (ix = 0; ix<2048; ix+=8) { + for(ic = ix;ic<(ix+4);ic++) { + *bc2=(bc3[iiy+ic]+bc3[iiy+4+ic])>>1; + bc2++; + } + } + } + } else { + for (iy = 0; iy<256; iy++) { + for (ix = 0; ix<256; ix++) { + buf2[(iy<<8)+ix] = buf3[((iy*ym)<= 0) { + do { + SCR_RunCinematic(); + } while (cinTable[currentHandle].buf == NULL && cinTable[currentHandle].status == FMV_PLAY); // wait for first frame (load codebook and sound) + } +} + + +void SCR_DrawCinematic (void) { + if (CL_handle >= 0 && CL_handle < MAX_VIDEO_HANDLES) { + CIN_DrawCinematic(CL_handle); + } +} + +void SCR_RunCinematic (void) +{ + if (CL_handle >= 0 && CL_handle < MAX_VIDEO_HANDLES) { + CIN_RunCinematic(CL_handle); + } +} + +void SCR_StopCinematic(void) { + if (CL_handle >= 0 && CL_handle < MAX_VIDEO_HANDLES) { + CIN_StopCinematic(CL_handle); + S_StopAllSounds (); + CL_handle = -1; + } +} + +void CIN_UploadCinematic(int handle) { + if (handle >= 0 && handle < MAX_VIDEO_HANDLES) { + if (!cinTable[handle].buf) { + return; + } + if (cinTable[handle].playonwalls <= 0 && cinTable[handle].dirty) { + if (cinTable[handle].playonwalls == 0) { + cinTable[handle].playonwalls = -1; + } else { + if (cinTable[handle].playonwalls == -1) { + cinTable[handle].playonwalls = -2; + } else { + cinTable[handle].dirty = qfalse; + } + } + } + re.UploadCinematic( 256, 256, 256, 256, cinTable[handle].buf, handle, cinTable[handle].dirty); + if (cl_inGameVideo->integer == 0 && cinTable[handle].playonwalls == 1) { + cinTable[handle].playonwalls--; + } + } +} + diff --git a/code/client/cl_console.c b/code/client/cl_console.c index 50df0e2..a428e3f 100755 --- a/code/client/cl_console.c +++ b/code/client/cl_console.c @@ -1,786 +1,786 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// console.c - -#include "client.h" - - -int g_console_field_width = 78; - - -#define NUM_CON_TIMES 4 - -#define CON_TEXTSIZE 32768 -typedef struct { - qboolean initialized; - - short text[CON_TEXTSIZE]; - int current; // line where next message will be printed - int x; // offset in current line for next print - int display; // bottom of console displays this line - - int linewidth; // characters across screen - int totallines; // total lines in console scrollback - - float xadjust; // for wide aspect screens - - float displayFrac; // aproaches finalFrac at scr_conspeed - float finalFrac; // 0.0 to 1.0 lines of console to display - - int vislines; // in scanlines - - int times[NUM_CON_TIMES]; // cls.realtime time the line was generated - // for transparent notify lines - vec4_t color; -} console_t; - -extern console_t con; - -console_t con; - -cvar_t *con_conspeed; -cvar_t *con_notifytime; - -#define DEFAULT_CONSOLE_WIDTH 78 - -vec4_t console_color = {1.0, 1.0, 1.0, 1.0}; - - -/* -================ -Con_ToggleConsole_f -================ -*/ -void Con_ToggleConsole_f (void) { - // closing a full screen console restarts the demo loop - if ( cls.state == CA_DISCONNECTED && cls.keyCatchers == KEYCATCH_CONSOLE ) { - CL_StartDemoLoop(); - return; - } - - Field_Clear( &g_consoleField ); - g_consoleField.widthInChars = g_console_field_width; - - Con_ClearNotify (); - cls.keyCatchers ^= KEYCATCH_CONSOLE; -} - -/* -================ -Con_MessageMode_f -================ -*/ -void Con_MessageMode_f (void) { - chat_playerNum = -1; - chat_team = qfalse; - Field_Clear( &chatField ); - chatField.widthInChars = 30; - - cls.keyCatchers ^= KEYCATCH_MESSAGE; -} - -/* -================ -Con_MessageMode2_f -================ -*/ -void Con_MessageMode2_f (void) { - chat_playerNum = -1; - chat_team = qtrue; - Field_Clear( &chatField ); - chatField.widthInChars = 25; - cls.keyCatchers ^= KEYCATCH_MESSAGE; -} - -/* -================ -Con_MessageMode3_f -================ -*/ -void Con_MessageMode3_f (void) { - chat_playerNum = VM_Call( cgvm, CG_CROSSHAIR_PLAYER ); - if ( chat_playerNum < 0 || chat_playerNum >= MAX_CLIENTS ) { - chat_playerNum = -1; - return; - } - chat_team = qfalse; - Field_Clear( &chatField ); - chatField.widthInChars = 30; - cls.keyCatchers ^= KEYCATCH_MESSAGE; -} - -/* -================ -Con_MessageMode4_f -================ -*/ -void Con_MessageMode4_f (void) { - chat_playerNum = VM_Call( cgvm, CG_LAST_ATTACKER ); - if ( chat_playerNum < 0 || chat_playerNum >= MAX_CLIENTS ) { - chat_playerNum = -1; - return; - } - chat_team = qfalse; - Field_Clear( &chatField ); - chatField.widthInChars = 30; - cls.keyCatchers ^= KEYCATCH_MESSAGE; -} - -/* -================ -Con_Clear_f -================ -*/ -void Con_Clear_f (void) { - int i; - - for ( i = 0 ; i < CON_TEXTSIZE ; i++ ) { - con.text[i] = (ColorIndex(COLOR_WHITE)<<8) | ' '; - } - - Con_Bottom(); // go to end -} - - -/* -================ -Con_Dump_f - -Save the console contents out to a file -================ -*/ -void Con_Dump_f (void) -{ - int l, x, i; - short *line; - fileHandle_t f; - char buffer[1024]; - - if (Cmd_Argc() != 2) - { - Com_Printf ("usage: condump \n"); - return; - } - - Com_Printf ("Dumped console text to %s.\n", Cmd_Argv(1) ); - - f = FS_FOpenFileWrite( Cmd_Argv( 1 ) ); - if (!f) - { - Com_Printf ("ERROR: couldn't open.\n"); - return; - } - - // skip empty lines - for (l = con.current - con.totallines + 1 ; l <= con.current ; l++) - { - line = con.text + (l%con.totallines)*con.linewidth; - for (x=0 ; x=0 ; x--) - { - if (buffer[x] == ' ') - buffer[x] = 0; - else - break; - } - strcat( buffer, "\n" ); - FS_Write(buffer, strlen(buffer), f); - } - - FS_FCloseFile( f ); -} - - -/* -================ -Con_ClearNotify -================ -*/ -void Con_ClearNotify( void ) { - int i; - - for ( i = 0 ; i < NUM_CON_TIMES ; i++ ) { - con.times[i] = 0; - } -} - - - -/* -================ -Con_CheckResize - -If the line width has changed, reformat the buffer. -================ -*/ -void Con_CheckResize (void) -{ - int i, j, width, oldwidth, oldtotallines, numlines, numchars; - MAC_STATIC short tbuf[CON_TEXTSIZE]; - - width = (SCREEN_WIDTH / SMALLCHAR_WIDTH) - 2; - - if (width == con.linewidth) - return; - - if (width < 1) // video hasn't been initialized yet - { - width = DEFAULT_CONSOLE_WIDTH; - con.linewidth = width; - con.totallines = CON_TEXTSIZE / con.linewidth; - for(i=0; i= 0) - { - if (skipnotify) - con.times[con.current % NUM_CON_TIMES] = 0; - else - con.times[con.current % NUM_CON_TIMES] = cls.realtime; - } - - con.x = 0; - if (con.display == con.current) - con.display++; - con.current++; - for(i=0; iinteger ) { - return; - } - - if (!con.initialized) { - con.color[0] = - con.color[1] = - con.color[2] = - con.color[3] = 1.0f; - con.linewidth = -1; - Con_CheckResize (); - con.initialized = qtrue; - } - - color = ColorIndex(COLOR_WHITE); - - while ( (c = *txt) != 0 ) { - if ( Q_IsColorString( txt ) ) { - color = ColorIndex( *(txt+1) ); - txt += 2; - continue; - } - - // count word length - for (l=0 ; l< con.linewidth ; l++) { - if ( txt[l] <= ' ') { - break; - } - - } - - // word wrap - if (l != con.linewidth && (con.x + l >= con.linewidth) ) { - Con_Linefeed(skipnotify); - - } - - txt++; - - switch (c) - { - case '\n': - Con_Linefeed (skipnotify); - break; - case '\r': - con.x = 0; - break; - default: // display character and advance - y = con.current % con.totallines; - con.text[y*con.linewidth+con.x] = (color << 8) | c; - con.x++; - if (con.x >= con.linewidth) { - Con_Linefeed(skipnotify); - con.x = 0; - } - break; - } - } - - - // mark time for transparent overlay - if (con.current >= 0) { - // NERVE - SMF - if ( skipnotify ) { - prev = con.current % NUM_CON_TIMES - 1; - if ( prev < 0 ) - prev = NUM_CON_TIMES - 1; - con.times[prev] = 0; - } - else - // -NERVE - SMF - con.times[con.current % NUM_CON_TIMES] = cls.realtime; - } -} - - -/* -============================================================================== - -DRAWING - -============================================================================== -*/ - - -/* -================ -Con_DrawInput - -Draw the editline after a ] prompt -================ -*/ -void Con_DrawInput (void) { - int y; - - if ( cls.state != CA_DISCONNECTED && !(cls.keyCatchers & KEYCATCH_CONSOLE ) ) { - return; - } - - y = con.vislines - ( SMALLCHAR_HEIGHT * 2 ); - - re.SetColor( con.color ); - - SCR_DrawSmallChar( con.xadjust + 1 * SMALLCHAR_WIDTH, y, ']' ); - - Field_Draw( &g_consoleField, con.xadjust + 2 * SMALLCHAR_WIDTH, y, - SCREEN_WIDTH - 3 * SMALLCHAR_WIDTH, qtrue ); -} - - -/* -================ -Con_DrawNotify - -Draws the last few lines of output transparently over the game top -================ -*/ -void Con_DrawNotify (void) -{ - int x, v; - short *text; - int i; - int time; - int skip; - int currentColor; - - currentColor = 7; - re.SetColor( g_color_table[currentColor] ); - - v = 0; - for (i= con.current-NUM_CON_TIMES+1 ; i<=con.current ; i++) - { - if (i < 0) - continue; - time = con.times[i % NUM_CON_TIMES]; - if (time == 0) - continue; - time = cls.realtime - time; - if (time > con_notifytime->value*1000) - continue; - text = con.text + (i % con.totallines)*con.linewidth; - - if (cl.snap.ps.pm_type != PM_INTERMISSION && cls.keyCatchers & (KEYCATCH_UI | KEYCATCH_CGAME) ) { - continue; - } - - for (x = 0 ; x < con.linewidth ; x++) { - if ( ( text[x] & 0xff ) == ' ' ) { - continue; - } - if ( ( (text[x]>>8)&7 ) != currentColor ) { - currentColor = (text[x]>>8)&7; - re.SetColor( g_color_table[currentColor] ); - } - SCR_DrawSmallChar( cl_conXOffset->integer + con.xadjust + (x+1)*SMALLCHAR_WIDTH, v, text[x] & 0xff ); - } - - v += SMALLCHAR_HEIGHT; - } - - re.SetColor( NULL ); - - if (cls.keyCatchers & (KEYCATCH_UI | KEYCATCH_CGAME) ) { - return; - } - - // draw the chat line - if ( cls.keyCatchers & KEYCATCH_MESSAGE ) - { - if (chat_team) - { - SCR_DrawBigString (8, v, "say_team:", 1.0f ); - skip = 11; - } - else - { - SCR_DrawBigString (8, v, "say:", 1.0f ); - skip = 5; - } - - Field_BigDraw( &chatField, skip * BIGCHAR_WIDTH, v, - SCREEN_WIDTH - ( skip + 1 ) * BIGCHAR_WIDTH, qtrue ); - - v += BIGCHAR_HEIGHT; - } - -} - -/* -================ -Con_DrawSolidConsole - -Draws the console with the solid background -================ -*/ -void Con_DrawSolidConsole( float frac ) { - int i, x, y; - int rows; - short *text; - int row; - int lines; -// qhandle_t conShader; - int currentColor; - vec4_t color; - - lines = cls.glconfig.vidHeight * frac; - if (lines <= 0) - return; - - if (lines > cls.glconfig.vidHeight ) - lines = cls.glconfig.vidHeight; - - // on wide screens, we will center the text - con.xadjust = 0; - SCR_AdjustFrom640( &con.xadjust, NULL, NULL, NULL ); - - // draw the background - y = frac * SCREEN_HEIGHT - 2; - if ( y < 1 ) { - y = 0; - } - else { - SCR_DrawPic( 0, 0, SCREEN_WIDTH, y, cls.consoleShader ); - } - - color[0] = 1; - color[1] = 0; - color[2] = 0; - color[3] = 1; - SCR_FillRect( 0, y, SCREEN_WIDTH, 2, color ); - - - // draw the version number - - re.SetColor( g_color_table[ColorIndex(COLOR_RED)] ); - - i = strlen( Q3_VERSION ); - - for (x=0 ; x= con.totallines) { - // past scrollback wrap point - continue; - } - - text = con.text + (row % con.totallines)*con.linewidth; - - for (x=0 ; x>8)&7 ) != currentColor ) { - currentColor = (text[x]>>8)&7; - re.SetColor( g_color_table[currentColor] ); - } - SCR_DrawSmallChar( con.xadjust + (x+1)*SMALLCHAR_WIDTH, y, text[x] & 0xff ); - } - } - - // draw the input prompt, user text, and cursor if desired - Con_DrawInput (); - - re.SetColor( NULL ); -} - - - -/* -================== -Con_DrawConsole -================== -*/ -void Con_DrawConsole( void ) { - // check for console width changes from a vid mode change - Con_CheckResize (); - - // if disconnected, render console full screen - if ( cls.state == CA_DISCONNECTED ) { - if ( !( cls.keyCatchers & (KEYCATCH_UI | KEYCATCH_CGAME)) ) { - Con_DrawSolidConsole( 1.0 ); - return; - } - } - - if ( con.displayFrac ) { - Con_DrawSolidConsole( con.displayFrac ); - } else { - // draw notify lines - if ( cls.state == CA_ACTIVE ) { - Con_DrawNotify (); - } - } -} - -//================================================================ - -/* -================== -Con_RunConsole - -Scroll it up or down -================== -*/ -void Con_RunConsole (void) { - // decide on the destination height of the console - if ( cls.keyCatchers & KEYCATCH_CONSOLE ) - con.finalFrac = 0.5; // half screen - else - con.finalFrac = 0; // none visible - - // scroll towards the destination height - if (con.finalFrac < con.displayFrac) - { - con.displayFrac -= con_conspeed->value*cls.realFrametime*0.001; - if (con.finalFrac > con.displayFrac) - con.displayFrac = con.finalFrac; - - } - else if (con.finalFrac > con.displayFrac) - { - con.displayFrac += con_conspeed->value*cls.realFrametime*0.001; - if (con.finalFrac < con.displayFrac) - con.displayFrac = con.finalFrac; - } - -} - - -void Con_PageUp( void ) { - con.display -= 2; - if ( con.current - con.display >= con.totallines ) { - con.display = con.current - con.totallines + 1; - } -} - -void Con_PageDown( void ) { - con.display += 2; - if (con.display > con.current) { - con.display = con.current; - } -} - -void Con_Top( void ) { - con.display = con.totallines; - if ( con.current - con.display >= con.totallines ) { - con.display = con.current - con.totallines + 1; - } -} - -void Con_Bottom( void ) { - con.display = con.current; -} - - -void Con_Close( void ) { - if ( !com_cl_running->integer ) { - return; - } - Field_Clear( &g_consoleField ); - Con_ClearNotify (); - cls.keyCatchers &= ~KEYCATCH_CONSOLE; - con.finalFrac = 0; // none visible - con.displayFrac = 0; -} +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// console.c + +#include "client.h" + + +int g_console_field_width = 78; + + +#define NUM_CON_TIMES 4 + +#define CON_TEXTSIZE 32768 +typedef struct { + qboolean initialized; + + short text[CON_TEXTSIZE]; + int current; // line where next message will be printed + int x; // offset in current line for next print + int display; // bottom of console displays this line + + int linewidth; // characters across screen + int totallines; // total lines in console scrollback + + float xadjust; // for wide aspect screens + + float displayFrac; // aproaches finalFrac at scr_conspeed + float finalFrac; // 0.0 to 1.0 lines of console to display + + int vislines; // in scanlines + + int times[NUM_CON_TIMES]; // cls.realtime time the line was generated + // for transparent notify lines + vec4_t color; +} console_t; + +extern console_t con; + +console_t con; + +cvar_t *con_conspeed; +cvar_t *con_notifytime; + +#define DEFAULT_CONSOLE_WIDTH 78 + +vec4_t console_color = {1.0, 1.0, 1.0, 1.0}; + + +/* +================ +Con_ToggleConsole_f +================ +*/ +void Con_ToggleConsole_f (void) { + // closing a full screen console restarts the demo loop + if ( cls.state == CA_DISCONNECTED && cls.keyCatchers == KEYCATCH_CONSOLE ) { + CL_StartDemoLoop(); + return; + } + + Field_Clear( &g_consoleField ); + g_consoleField.widthInChars = g_console_field_width; + + Con_ClearNotify (); + cls.keyCatchers ^= KEYCATCH_CONSOLE; +} + +/* +================ +Con_MessageMode_f +================ +*/ +void Con_MessageMode_f (void) { + chat_playerNum = -1; + chat_team = qfalse; + Field_Clear( &chatField ); + chatField.widthInChars = 30; + + cls.keyCatchers ^= KEYCATCH_MESSAGE; +} + +/* +================ +Con_MessageMode2_f +================ +*/ +void Con_MessageMode2_f (void) { + chat_playerNum = -1; + chat_team = qtrue; + Field_Clear( &chatField ); + chatField.widthInChars = 25; + cls.keyCatchers ^= KEYCATCH_MESSAGE; +} + +/* +================ +Con_MessageMode3_f +================ +*/ +void Con_MessageMode3_f (void) { + chat_playerNum = VM_Call( cgvm, CG_CROSSHAIR_PLAYER ); + if ( chat_playerNum < 0 || chat_playerNum >= MAX_CLIENTS ) { + chat_playerNum = -1; + return; + } + chat_team = qfalse; + Field_Clear( &chatField ); + chatField.widthInChars = 30; + cls.keyCatchers ^= KEYCATCH_MESSAGE; +} + +/* +================ +Con_MessageMode4_f +================ +*/ +void Con_MessageMode4_f (void) { + chat_playerNum = VM_Call( cgvm, CG_LAST_ATTACKER ); + if ( chat_playerNum < 0 || chat_playerNum >= MAX_CLIENTS ) { + chat_playerNum = -1; + return; + } + chat_team = qfalse; + Field_Clear( &chatField ); + chatField.widthInChars = 30; + cls.keyCatchers ^= KEYCATCH_MESSAGE; +} + +/* +================ +Con_Clear_f +================ +*/ +void Con_Clear_f (void) { + int i; + + for ( i = 0 ; i < CON_TEXTSIZE ; i++ ) { + con.text[i] = (ColorIndex(COLOR_WHITE)<<8) | ' '; + } + + Con_Bottom(); // go to end +} + + +/* +================ +Con_Dump_f + +Save the console contents out to a file +================ +*/ +void Con_Dump_f (void) +{ + int l, x, i; + short *line; + fileHandle_t f; + char buffer[1024]; + + if (Cmd_Argc() != 2) + { + Com_Printf ("usage: condump \n"); + return; + } + + Com_Printf ("Dumped console text to %s.\n", Cmd_Argv(1) ); + + f = FS_FOpenFileWrite( Cmd_Argv( 1 ) ); + if (!f) + { + Com_Printf ("ERROR: couldn't open.\n"); + return; + } + + // skip empty lines + for (l = con.current - con.totallines + 1 ; l <= con.current ; l++) + { + line = con.text + (l%con.totallines)*con.linewidth; + for (x=0 ; x=0 ; x--) + { + if (buffer[x] == ' ') + buffer[x] = 0; + else + break; + } + strcat( buffer, "\n" ); + FS_Write(buffer, strlen(buffer), f); + } + + FS_FCloseFile( f ); +} + + +/* +================ +Con_ClearNotify +================ +*/ +void Con_ClearNotify( void ) { + int i; + + for ( i = 0 ; i < NUM_CON_TIMES ; i++ ) { + con.times[i] = 0; + } +} + + + +/* +================ +Con_CheckResize + +If the line width has changed, reformat the buffer. +================ +*/ +void Con_CheckResize (void) +{ + int i, j, width, oldwidth, oldtotallines, numlines, numchars; + MAC_STATIC short tbuf[CON_TEXTSIZE]; + + width = (SCREEN_WIDTH / SMALLCHAR_WIDTH) - 2; + + if (width == con.linewidth) + return; + + if (width < 1) // video hasn't been initialized yet + { + width = DEFAULT_CONSOLE_WIDTH; + con.linewidth = width; + con.totallines = CON_TEXTSIZE / con.linewidth; + for(i=0; i= 0) + { + if (skipnotify) + con.times[con.current % NUM_CON_TIMES] = 0; + else + con.times[con.current % NUM_CON_TIMES] = cls.realtime; + } + + con.x = 0; + if (con.display == con.current) + con.display++; + con.current++; + for(i=0; iinteger ) { + return; + } + + if (!con.initialized) { + con.color[0] = + con.color[1] = + con.color[2] = + con.color[3] = 1.0f; + con.linewidth = -1; + Con_CheckResize (); + con.initialized = qtrue; + } + + color = ColorIndex(COLOR_WHITE); + + while ( (c = *txt) != 0 ) { + if ( Q_IsColorString( txt ) ) { + color = ColorIndex( *(txt+1) ); + txt += 2; + continue; + } + + // count word length + for (l=0 ; l< con.linewidth ; l++) { + if ( txt[l] <= ' ') { + break; + } + + } + + // word wrap + if (l != con.linewidth && (con.x + l >= con.linewidth) ) { + Con_Linefeed(skipnotify); + + } + + txt++; + + switch (c) + { + case '\n': + Con_Linefeed (skipnotify); + break; + case '\r': + con.x = 0; + break; + default: // display character and advance + y = con.current % con.totallines; + con.text[y*con.linewidth+con.x] = (color << 8) | c; + con.x++; + if (con.x >= con.linewidth) { + Con_Linefeed(skipnotify); + con.x = 0; + } + break; + } + } + + + // mark time for transparent overlay + if (con.current >= 0) { + // NERVE - SMF + if ( skipnotify ) { + prev = con.current % NUM_CON_TIMES - 1; + if ( prev < 0 ) + prev = NUM_CON_TIMES - 1; + con.times[prev] = 0; + } + else + // -NERVE - SMF + con.times[con.current % NUM_CON_TIMES] = cls.realtime; + } +} + + +/* +============================================================================== + +DRAWING + +============================================================================== +*/ + + +/* +================ +Con_DrawInput + +Draw the editline after a ] prompt +================ +*/ +void Con_DrawInput (void) { + int y; + + if ( cls.state != CA_DISCONNECTED && !(cls.keyCatchers & KEYCATCH_CONSOLE ) ) { + return; + } + + y = con.vislines - ( SMALLCHAR_HEIGHT * 2 ); + + re.SetColor( con.color ); + + SCR_DrawSmallChar( con.xadjust + 1 * SMALLCHAR_WIDTH, y, ']' ); + + Field_Draw( &g_consoleField, con.xadjust + 2 * SMALLCHAR_WIDTH, y, + SCREEN_WIDTH - 3 * SMALLCHAR_WIDTH, qtrue ); +} + + +/* +================ +Con_DrawNotify + +Draws the last few lines of output transparently over the game top +================ +*/ +void Con_DrawNotify (void) +{ + int x, v; + short *text; + int i; + int time; + int skip; + int currentColor; + + currentColor = 7; + re.SetColor( g_color_table[currentColor] ); + + v = 0; + for (i= con.current-NUM_CON_TIMES+1 ; i<=con.current ; i++) + { + if (i < 0) + continue; + time = con.times[i % NUM_CON_TIMES]; + if (time == 0) + continue; + time = cls.realtime - time; + if (time > con_notifytime->value*1000) + continue; + text = con.text + (i % con.totallines)*con.linewidth; + + if (cl.snap.ps.pm_type != PM_INTERMISSION && cls.keyCatchers & (KEYCATCH_UI | KEYCATCH_CGAME) ) { + continue; + } + + for (x = 0 ; x < con.linewidth ; x++) { + if ( ( text[x] & 0xff ) == ' ' ) { + continue; + } + if ( ( (text[x]>>8)&7 ) != currentColor ) { + currentColor = (text[x]>>8)&7; + re.SetColor( g_color_table[currentColor] ); + } + SCR_DrawSmallChar( cl_conXOffset->integer + con.xadjust + (x+1)*SMALLCHAR_WIDTH, v, text[x] & 0xff ); + } + + v += SMALLCHAR_HEIGHT; + } + + re.SetColor( NULL ); + + if (cls.keyCatchers & (KEYCATCH_UI | KEYCATCH_CGAME) ) { + return; + } + + // draw the chat line + if ( cls.keyCatchers & KEYCATCH_MESSAGE ) + { + if (chat_team) + { + SCR_DrawBigString (8, v, "say_team:", 1.0f ); + skip = 11; + } + else + { + SCR_DrawBigString (8, v, "say:", 1.0f ); + skip = 5; + } + + Field_BigDraw( &chatField, skip * BIGCHAR_WIDTH, v, + SCREEN_WIDTH - ( skip + 1 ) * BIGCHAR_WIDTH, qtrue ); + + v += BIGCHAR_HEIGHT; + } + +} + +/* +================ +Con_DrawSolidConsole + +Draws the console with the solid background +================ +*/ +void Con_DrawSolidConsole( float frac ) { + int i, x, y; + int rows; + short *text; + int row; + int lines; +// qhandle_t conShader; + int currentColor; + vec4_t color; + + lines = cls.glconfig.vidHeight * frac; + if (lines <= 0) + return; + + if (lines > cls.glconfig.vidHeight ) + lines = cls.glconfig.vidHeight; + + // on wide screens, we will center the text + con.xadjust = 0; + SCR_AdjustFrom640( &con.xadjust, NULL, NULL, NULL ); + + // draw the background + y = frac * SCREEN_HEIGHT - 2; + if ( y < 1 ) { + y = 0; + } + else { + SCR_DrawPic( 0, 0, SCREEN_WIDTH, y, cls.consoleShader ); + } + + color[0] = 1; + color[1] = 0; + color[2] = 0; + color[3] = 1; + SCR_FillRect( 0, y, SCREEN_WIDTH, 2, color ); + + + // draw the version number + + re.SetColor( g_color_table[ColorIndex(COLOR_RED)] ); + + i = strlen( Q3_VERSION ); + + for (x=0 ; x= con.totallines) { + // past scrollback wrap point + continue; + } + + text = con.text + (row % con.totallines)*con.linewidth; + + for (x=0 ; x>8)&7 ) != currentColor ) { + currentColor = (text[x]>>8)&7; + re.SetColor( g_color_table[currentColor] ); + } + SCR_DrawSmallChar( con.xadjust + (x+1)*SMALLCHAR_WIDTH, y, text[x] & 0xff ); + } + } + + // draw the input prompt, user text, and cursor if desired + Con_DrawInput (); + + re.SetColor( NULL ); +} + + + +/* +================== +Con_DrawConsole +================== +*/ +void Con_DrawConsole( void ) { + // check for console width changes from a vid mode change + Con_CheckResize (); + + // if disconnected, render console full screen + if ( cls.state == CA_DISCONNECTED ) { + if ( !( cls.keyCatchers & (KEYCATCH_UI | KEYCATCH_CGAME)) ) { + Con_DrawSolidConsole( 1.0 ); + return; + } + } + + if ( con.displayFrac ) { + Con_DrawSolidConsole( con.displayFrac ); + } else { + // draw notify lines + if ( cls.state == CA_ACTIVE ) { + Con_DrawNotify (); + } + } +} + +//================================================================ + +/* +================== +Con_RunConsole + +Scroll it up or down +================== +*/ +void Con_RunConsole (void) { + // decide on the destination height of the console + if ( cls.keyCatchers & KEYCATCH_CONSOLE ) + con.finalFrac = 0.5; // half screen + else + con.finalFrac = 0; // none visible + + // scroll towards the destination height + if (con.finalFrac < con.displayFrac) + { + con.displayFrac -= con_conspeed->value*cls.realFrametime*0.001; + if (con.finalFrac > con.displayFrac) + con.displayFrac = con.finalFrac; + + } + else if (con.finalFrac > con.displayFrac) + { + con.displayFrac += con_conspeed->value*cls.realFrametime*0.001; + if (con.finalFrac < con.displayFrac) + con.displayFrac = con.finalFrac; + } + +} + + +void Con_PageUp( void ) { + con.display -= 2; + if ( con.current - con.display >= con.totallines ) { + con.display = con.current - con.totallines + 1; + } +} + +void Con_PageDown( void ) { + con.display += 2; + if (con.display > con.current) { + con.display = con.current; + } +} + +void Con_Top( void ) { + con.display = con.totallines; + if ( con.current - con.display >= con.totallines ) { + con.display = con.current - con.totallines + 1; + } +} + +void Con_Bottom( void ) { + con.display = con.current; +} + + +void Con_Close( void ) { + if ( !com_cl_running->integer ) { + return; + } + Field_Clear( &g_consoleField ); + Con_ClearNotify (); + cls.keyCatchers &= ~KEYCATCH_CONSOLE; + con.finalFrac = 0; // none visible + con.displayFrac = 0; +} diff --git a/code/client/cl_input.c b/code/client/cl_input.c index f450ee7..06266eb 100755 --- a/code/client/cl_input.c +++ b/code/client/cl_input.c @@ -1,901 +1,901 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// cl.input.c -- builds an intended movement command to send to the server - -#include "client.h" - -unsigned frame_msec; -int old_com_frameTime; - -/* -=============================================================================== - -KEY BUTTONS - -Continuous button event tracking is complicated by the fact that two different -input sources (say, mouse button 1 and the control key) can both press the -same button, but the button should only be released when both of the -pressing key have been released. - -When a key event issues a button command (+forward, +attack, etc), it appends -its key number as argv(1) so it can be matched up with the release. - -argv(2) will be set to the time the event happened, which allows exact -control even at low framerates when the down and up events may both get qued -at the same time. - -=============================================================================== -*/ - - -kbutton_t in_left, in_right, in_forward, in_back; -kbutton_t in_lookup, in_lookdown, in_moveleft, in_moveright; -kbutton_t in_strafe, in_speed; -kbutton_t in_up, in_down; - -kbutton_t in_buttons[16]; - - -qboolean in_mlooking; - - -void IN_MLookDown( void ) { - in_mlooking = qtrue; -} - -void IN_MLookUp( void ) { - in_mlooking = qfalse; - if ( !cl_freelook->integer ) { - IN_CenterView (); - } -} - -void IN_KeyDown( kbutton_t *b ) { - int k; - char *c; - - c = Cmd_Argv(1); - if ( c[0] ) { - k = atoi(c); - } else { - k = -1; // typed manually at the console for continuous down - } - - if ( k == b->down[0] || k == b->down[1] ) { - return; // repeating key - } - - if ( !b->down[0] ) { - b->down[0] = k; - } else if ( !b->down[1] ) { - b->down[1] = k; - } else { - Com_Printf ("Three keys down for a button!\n"); - return; - } - - if ( b->active ) { - return; // still down - } - - // save timestamp for partial frame summing - c = Cmd_Argv(2); - b->downtime = atoi(c); - - b->active = qtrue; - b->wasPressed = qtrue; -} - -void IN_KeyUp( kbutton_t *b ) { - int k; - char *c; - unsigned uptime; - - c = Cmd_Argv(1); - if ( c[0] ) { - k = atoi(c); - } else { - // typed manually at the console, assume for unsticking, so clear all - b->down[0] = b->down[1] = 0; - b->active = qfalse; - return; - } - - if ( b->down[0] == k ) { - b->down[0] = 0; - } else if ( b->down[1] == k ) { - b->down[1] = 0; - } else { - return; // key up without coresponding down (menu pass through) - } - if ( b->down[0] || b->down[1] ) { - return; // some other key is still holding it down - } - - b->active = qfalse; - - // save timestamp for partial frame summing - c = Cmd_Argv(2); - uptime = atoi(c); - if ( uptime ) { - b->msec += uptime - b->downtime; - } else { - b->msec += frame_msec / 2; - } - - b->active = qfalse; -} - - - -/* -=============== -CL_KeyState - -Returns the fraction of the frame that the key was down -=============== -*/ -float CL_KeyState( kbutton_t *key ) { - float val; - int msec; - - msec = key->msec; - key->msec = 0; - - if ( key->active ) { - // still down - if ( !key->downtime ) { - msec = com_frameTime; - } else { - msec += com_frameTime - key->downtime; - } - key->downtime = com_frameTime; - } - -#if 0 - if (msec) { - Com_Printf ("%i ", msec); - } -#endif - - val = (float)msec / frame_msec; - if ( val < 0 ) { - val = 0; - } - if ( val > 1 ) { - val = 1; - } - - return val; -} - - - -void IN_UpDown(void) {IN_KeyDown(&in_up);} -void IN_UpUp(void) {IN_KeyUp(&in_up);} -void IN_DownDown(void) {IN_KeyDown(&in_down);} -void IN_DownUp(void) {IN_KeyUp(&in_down);} -void IN_LeftDown(void) {IN_KeyDown(&in_left);} -void IN_LeftUp(void) {IN_KeyUp(&in_left);} -void IN_RightDown(void) {IN_KeyDown(&in_right);} -void IN_RightUp(void) {IN_KeyUp(&in_right);} -void IN_ForwardDown(void) {IN_KeyDown(&in_forward);} -void IN_ForwardUp(void) {IN_KeyUp(&in_forward);} -void IN_BackDown(void) {IN_KeyDown(&in_back);} -void IN_BackUp(void) {IN_KeyUp(&in_back);} -void IN_LookupDown(void) {IN_KeyDown(&in_lookup);} -void IN_LookupUp(void) {IN_KeyUp(&in_lookup);} -void IN_LookdownDown(void) {IN_KeyDown(&in_lookdown);} -void IN_LookdownUp(void) {IN_KeyUp(&in_lookdown);} -void IN_MoveleftDown(void) {IN_KeyDown(&in_moveleft);} -void IN_MoveleftUp(void) {IN_KeyUp(&in_moveleft);} -void IN_MoverightDown(void) {IN_KeyDown(&in_moveright);} -void IN_MoverightUp(void) {IN_KeyUp(&in_moveright);} - -void IN_SpeedDown(void) {IN_KeyDown(&in_speed);} -void IN_SpeedUp(void) {IN_KeyUp(&in_speed);} -void IN_StrafeDown(void) {IN_KeyDown(&in_strafe);} -void IN_StrafeUp(void) {IN_KeyUp(&in_strafe);} - -void IN_Button0Down(void) {IN_KeyDown(&in_buttons[0]);} -void IN_Button0Up(void) {IN_KeyUp(&in_buttons[0]);} -void IN_Button1Down(void) {IN_KeyDown(&in_buttons[1]);} -void IN_Button1Up(void) {IN_KeyUp(&in_buttons[1]);} -void IN_Button2Down(void) {IN_KeyDown(&in_buttons[2]);} -void IN_Button2Up(void) {IN_KeyUp(&in_buttons[2]);} -void IN_Button3Down(void) {IN_KeyDown(&in_buttons[3]);} -void IN_Button3Up(void) {IN_KeyUp(&in_buttons[3]);} -void IN_Button4Down(void) {IN_KeyDown(&in_buttons[4]);} -void IN_Button4Up(void) {IN_KeyUp(&in_buttons[4]);} -void IN_Button5Down(void) {IN_KeyDown(&in_buttons[5]);} -void IN_Button5Up(void) {IN_KeyUp(&in_buttons[5]);} -void IN_Button6Down(void) {IN_KeyDown(&in_buttons[6]);} -void IN_Button6Up(void) {IN_KeyUp(&in_buttons[6]);} -void IN_Button7Down(void) {IN_KeyDown(&in_buttons[7]);} -void IN_Button7Up(void) {IN_KeyUp(&in_buttons[7]);} -void IN_Button8Down(void) {IN_KeyDown(&in_buttons[8]);} -void IN_Button8Up(void) {IN_KeyUp(&in_buttons[8]);} -void IN_Button9Down(void) {IN_KeyDown(&in_buttons[9]);} -void IN_Button9Up(void) {IN_KeyUp(&in_buttons[9]);} -void IN_Button10Down(void) {IN_KeyDown(&in_buttons[10]);} -void IN_Button10Up(void) {IN_KeyUp(&in_buttons[10]);} -void IN_Button11Down(void) {IN_KeyDown(&in_buttons[11]);} -void IN_Button11Up(void) {IN_KeyUp(&in_buttons[11]);} -void IN_Button12Down(void) {IN_KeyDown(&in_buttons[12]);} -void IN_Button12Up(void) {IN_KeyUp(&in_buttons[12]);} -void IN_Button13Down(void) {IN_KeyDown(&in_buttons[13]);} -void IN_Button13Up(void) {IN_KeyUp(&in_buttons[13]);} -void IN_Button14Down(void) {IN_KeyDown(&in_buttons[14]);} -void IN_Button14Up(void) {IN_KeyUp(&in_buttons[14]);} -void IN_Button15Down(void) {IN_KeyDown(&in_buttons[15]);} -void IN_Button15Up(void) {IN_KeyUp(&in_buttons[15]);} - -void IN_ButtonDown (void) { - IN_KeyDown(&in_buttons[1]);} -void IN_ButtonUp (void) { - IN_KeyUp(&in_buttons[1]);} - -void IN_CenterView (void) { - cl.viewangles[PITCH] = -SHORT2ANGLE(cl.snap.ps.delta_angles[PITCH]); -} - - -//========================================================================== - -cvar_t *cl_upspeed; -cvar_t *cl_forwardspeed; -cvar_t *cl_sidespeed; - -cvar_t *cl_yawspeed; -cvar_t *cl_pitchspeed; - -cvar_t *cl_run; - -cvar_t *cl_anglespeedkey; - - -/* -================ -CL_AdjustAngles - -Moves the local angle positions -================ -*/ -void CL_AdjustAngles( void ) { - float speed; - - if ( in_speed.active ) { - speed = 0.001 * cls.frametime * cl_anglespeedkey->value; - } else { - speed = 0.001 * cls.frametime; - } - - if ( !in_strafe.active ) { - cl.viewangles[YAW] -= speed*cl_yawspeed->value*CL_KeyState (&in_right); - cl.viewangles[YAW] += speed*cl_yawspeed->value*CL_KeyState (&in_left); - } - - cl.viewangles[PITCH] -= speed*cl_pitchspeed->value * CL_KeyState (&in_lookup); - cl.viewangles[PITCH] += speed*cl_pitchspeed->value * CL_KeyState (&in_lookdown); -} - -/* -================ -CL_KeyMove - -Sets the usercmd_t based on key states -================ -*/ -void CL_KeyMove( usercmd_t *cmd ) { - int movespeed; - int forward, side, up; - - // - // adjust for speed key / running - // the walking flag is to keep animations consistant - // even during acceleration and develeration - // - if ( in_speed.active ^ cl_run->integer ) { - movespeed = 127; - cmd->buttons &= ~BUTTON_WALKING; - } else { - cmd->buttons |= BUTTON_WALKING; - movespeed = 64; - } - - forward = 0; - side = 0; - up = 0; - if ( in_strafe.active ) { - side += movespeed * CL_KeyState (&in_right); - side -= movespeed * CL_KeyState (&in_left); - } - - side += movespeed * CL_KeyState (&in_moveright); - side -= movespeed * CL_KeyState (&in_moveleft); - - - up += movespeed * CL_KeyState (&in_up); - up -= movespeed * CL_KeyState (&in_down); - - forward += movespeed * CL_KeyState (&in_forward); - forward -= movespeed * CL_KeyState (&in_back); - - cmd->forwardmove = ClampChar( forward ); - cmd->rightmove = ClampChar( side ); - cmd->upmove = ClampChar( up ); -} - -/* -================= -CL_MouseEvent -================= -*/ -void CL_MouseEvent( int dx, int dy, int time ) { - if ( cls.keyCatchers & KEYCATCH_UI ) { - VM_Call( uivm, UI_MOUSE_EVENT, dx, dy ); - } else if (cls.keyCatchers & KEYCATCH_CGAME) { - VM_Call (cgvm, CG_MOUSE_EVENT, dx, dy); - } else { - cl.mouseDx[cl.mouseIndex] += dx; - cl.mouseDy[cl.mouseIndex] += dy; - } -} - -/* -================= -CL_JoystickEvent - -Joystick values stay set until changed -================= -*/ -void CL_JoystickEvent( int axis, int value, int time ) { - if ( axis < 0 || axis >= MAX_JOYSTICK_AXIS ) { - Com_Error( ERR_DROP, "CL_JoystickEvent: bad axis %i", axis ); - } - cl.joystickAxis[axis] = value; -} - -/* -================= -CL_JoystickMove -================= -*/ -void CL_JoystickMove( usercmd_t *cmd ) { - int movespeed; - float anglespeed; - - if ( in_speed.active ^ cl_run->integer ) { - movespeed = 2; - } else { - movespeed = 1; - cmd->buttons |= BUTTON_WALKING; - } - - if ( in_speed.active ) { - anglespeed = 0.001 * cls.frametime * cl_anglespeedkey->value; - } else { - anglespeed = 0.001 * cls.frametime; - } - - if ( !in_strafe.active ) { - cl.viewangles[YAW] += anglespeed * cl_yawspeed->value * cl.joystickAxis[AXIS_SIDE]; - } else { - cmd->rightmove = ClampChar( cmd->rightmove + cl.joystickAxis[AXIS_SIDE] ); - } - - if ( in_mlooking ) { - cl.viewangles[PITCH] += anglespeed * cl_pitchspeed->value * cl.joystickAxis[AXIS_FORWARD]; - } else { - cmd->forwardmove = ClampChar( cmd->forwardmove + cl.joystickAxis[AXIS_FORWARD] ); - } - - cmd->upmove = ClampChar( cmd->upmove + cl.joystickAxis[AXIS_UP] ); -} - -/* -================= -CL_MouseMove -================= -*/ -void CL_MouseMove( usercmd_t *cmd ) { - float mx, my; - float accelSensitivity; - float rate; - - // allow mouse smoothing - if ( m_filter->integer ) { - mx = ( cl.mouseDx[0] + cl.mouseDx[1] ) * 0.5; - my = ( cl.mouseDy[0] + cl.mouseDy[1] ) * 0.5; - } else { - mx = cl.mouseDx[cl.mouseIndex]; - my = cl.mouseDy[cl.mouseIndex]; - } - cl.mouseIndex ^= 1; - cl.mouseDx[cl.mouseIndex] = 0; - cl.mouseDy[cl.mouseIndex] = 0; - - rate = sqrt( mx * mx + my * my ) / (float)frame_msec; - accelSensitivity = cl_sensitivity->value + rate * cl_mouseAccel->value; - - // scale by FOV - accelSensitivity *= cl.cgameSensitivity; - - if ( rate && cl_showMouseRate->integer ) { - Com_Printf( "%f : %f\n", rate, accelSensitivity ); - } - - mx *= accelSensitivity; - my *= accelSensitivity; - - if (!mx && !my) { - return; - } - - // add mouse X/Y movement to cmd - if ( in_strafe.active ) { - cmd->rightmove = ClampChar( cmd->rightmove + m_side->value * mx ); - } else { - cl.viewangles[YAW] -= m_yaw->value * mx; - } - - if ( (in_mlooking || cl_freelook->integer) && !in_strafe.active ) { - cl.viewangles[PITCH] += m_pitch->value * my; - } else { - cmd->forwardmove = ClampChar( cmd->forwardmove - m_forward->value * my ); - } -} - - -/* -============== -CL_CmdButtons -============== -*/ -void CL_CmdButtons( usercmd_t *cmd ) { - int i; - - // - // figure button bits - // send a button bit even if the key was pressed and released in - // less than a frame - // - for (i = 0 ; i < 15 ; i++) { - if ( in_buttons[i].active || in_buttons[i].wasPressed ) { - cmd->buttons |= 1 << i; - } - in_buttons[i].wasPressed = qfalse; - } - - if ( cls.keyCatchers ) { - cmd->buttons |= BUTTON_TALK; - } - - // allow the game to know if any key at all is - // currently pressed, even if it isn't bound to anything - if ( anykeydown && !cls.keyCatchers ) { - cmd->buttons |= BUTTON_ANY; - } -} - - -/* -============== -CL_FinishMove -============== -*/ -void CL_FinishMove( usercmd_t *cmd ) { - int i; - - // copy the state that the cgame is currently sending - cmd->weapon = cl.cgameUserCmdValue; - - // send the current server time so the amount of movement - // can be determined without allowing cheating - cmd->serverTime = cl.serverTime; - - for (i=0 ; i<3 ; i++) { - cmd->angles[i] = ANGLE2SHORT(cl.viewangles[i]); - } -} - - -/* -================= -CL_CreateCmd -================= -*/ -usercmd_t CL_CreateCmd( void ) { - usercmd_t cmd; - vec3_t oldAngles; - - VectorCopy( cl.viewangles, oldAngles ); - - // keyboard angle adjustment - CL_AdjustAngles (); - - Com_Memset( &cmd, 0, sizeof( cmd ) ); - - CL_CmdButtons( &cmd ); - - // get basic movement from keyboard - CL_KeyMove( &cmd ); - - // get basic movement from mouse - CL_MouseMove( &cmd ); - - // get basic movement from joystick - CL_JoystickMove( &cmd ); - - // check to make sure the angles haven't wrapped - if ( cl.viewangles[PITCH] - oldAngles[PITCH] > 90 ) { - cl.viewangles[PITCH] = oldAngles[PITCH] + 90; - } else if ( oldAngles[PITCH] - cl.viewangles[PITCH] > 90 ) { - cl.viewangles[PITCH] = oldAngles[PITCH] - 90; - } - - // store out the final values - CL_FinishMove( &cmd ); - - // draw debug graphs of turning for mouse testing - if ( cl_debugMove->integer ) { - if ( cl_debugMove->integer == 1 ) { - SCR_DebugGraph( abs(cl.viewangles[YAW] - oldAngles[YAW]), 0 ); - } - if ( cl_debugMove->integer == 2 ) { - SCR_DebugGraph( abs(cl.viewangles[PITCH] - oldAngles[PITCH]), 0 ); - } - } - - return cmd; -} - - -/* -================= -CL_CreateNewCommands - -Create a new usercmd_t structure for this frame -================= -*/ -void CL_CreateNewCommands( void ) { - usercmd_t *cmd; - int cmdNum; - - // no need to create usercmds until we have a gamestate - if ( cls.state < CA_PRIMED ) { - return; - } - - frame_msec = com_frameTime - old_com_frameTime; - - // if running less than 5fps, truncate the extra time to prevent - // unexpected moves after a hitch - if ( frame_msec > 200 ) { - frame_msec = 200; - } - old_com_frameTime = com_frameTime; - - - // generate a command for this frame - cl.cmdNumber++; - cmdNum = cl.cmdNumber & CMD_MASK; - cl.cmds[cmdNum] = CL_CreateCmd (); - cmd = &cl.cmds[cmdNum]; -} - -/* -================= -CL_ReadyToSendPacket - -Returns qfalse if we are over the maxpackets limit -and should choke back the bandwidth a bit by not sending -a packet this frame. All the commands will still get -delivered in the next packet, but saving a header and -getting more delta compression will reduce total bandwidth. -================= -*/ -qboolean CL_ReadyToSendPacket( void ) { - int oldPacketNum; - int delta; - - // don't send anything if playing back a demo - if ( clc.demoplaying || cls.state == CA_CINEMATIC ) { - return qfalse; - } - - // If we are downloading, we send no less than 50ms between packets - if ( *clc.downloadTempName && - cls.realtime - clc.lastPacketSentTime < 50 ) { - return qfalse; - } - - // if we don't have a valid gamestate yet, only send - // one packet a second - if ( cls.state != CA_ACTIVE && - cls.state != CA_PRIMED && - !*clc.downloadTempName && - cls.realtime - clc.lastPacketSentTime < 1000 ) { - return qfalse; - } - - // send every frame for loopbacks - if ( clc.netchan.remoteAddress.type == NA_LOOPBACK ) { - return qtrue; - } - - // send every frame for LAN - if ( Sys_IsLANAddress( clc.netchan.remoteAddress ) ) { - return qtrue; - } - - // check for exceeding cl_maxpackets - if ( cl_maxpackets->integer < 15 ) { - Cvar_Set( "cl_maxpackets", "15" ); - } else if ( cl_maxpackets->integer > 125 ) { - Cvar_Set( "cl_maxpackets", "125" ); - } - oldPacketNum = (clc.netchan.outgoingSequence - 1) & PACKET_MASK; - delta = cls.realtime - cl.outPackets[ oldPacketNum ].p_realtime; - if ( delta < 1000 / cl_maxpackets->integer ) { - // the accumulated commands will go out in the next packet - return qfalse; - } - - return qtrue; -} - -/* -=================== -CL_WritePacket - -Create and send the command packet to the server -Including both the reliable commands and the usercmds - -During normal gameplay, a client packet will contain something like: - -4 sequence number -2 qport -4 serverid -4 acknowledged sequence number -4 clc.serverCommandSequence - -1 clc_move or clc_moveNoDelta -1 command count - - -=================== -*/ -void CL_WritePacket( void ) { - msg_t buf; - byte data[MAX_MSGLEN]; - int i, j; - usercmd_t *cmd, *oldcmd; - usercmd_t nullcmd; - int packetNum; - int oldPacketNum; - int count, key; - - // don't send anything if playing back a demo - if ( clc.demoplaying || cls.state == CA_CINEMATIC ) { - return; - } - - Com_Memset( &nullcmd, 0, sizeof(nullcmd) ); - oldcmd = &nullcmd; - - MSG_Init( &buf, data, sizeof(data) ); - - MSG_Bitstream( &buf ); - // write the current serverId so the server - // can tell if this is from the current gameState - MSG_WriteLong( &buf, cl.serverId ); - - // write the last message we received, which can - // be used for delta compression, and is also used - // to tell if we dropped a gamestate - MSG_WriteLong( &buf, clc.serverMessageSequence ); - - // write the last reliable message we received - MSG_WriteLong( &buf, clc.serverCommandSequence ); - - // write any unacknowledged clientCommands - for ( i = clc.reliableAcknowledge + 1 ; i <= clc.reliableSequence ; i++ ) { - MSG_WriteByte( &buf, clc_clientCommand ); - MSG_WriteLong( &buf, i ); - MSG_WriteString( &buf, clc.reliableCommands[ i & (MAX_RELIABLE_COMMANDS-1) ] ); - } - - // we want to send all the usercmds that were generated in the last - // few packet, so even if a couple packets are dropped in a row, - // all the cmds will make it to the server - if ( cl_packetdup->integer < 0 ) { - Cvar_Set( "cl_packetdup", "0" ); - } else if ( cl_packetdup->integer > 5 ) { - Cvar_Set( "cl_packetdup", "5" ); - } - oldPacketNum = (clc.netchan.outgoingSequence - 1 - cl_packetdup->integer) & PACKET_MASK; - count = cl.cmdNumber - cl.outPackets[ oldPacketNum ].p_cmdNumber; - if ( count > MAX_PACKET_USERCMDS ) { - count = MAX_PACKET_USERCMDS; - Com_Printf("MAX_PACKET_USERCMDS\n"); - } - if ( count >= 1 ) { - if ( cl_showSend->integer ) { - Com_Printf( "(%i)", count ); - } - - // begin a client move command - if ( cl_nodelta->integer || !cl.snap.valid || clc.demowaiting - || clc.serverMessageSequence != cl.snap.messageNum ) { - MSG_WriteByte (&buf, clc_moveNoDelta); - } else { - MSG_WriteByte (&buf, clc_move); - } - - // write the command count - MSG_WriteByte( &buf, count ); - - // use the checksum feed in the key - key = clc.checksumFeed; - // also use the message acknowledge - key ^= clc.serverMessageSequence; - // also use the last acknowledged server command in the key - key ^= Com_HashKey(clc.serverCommands[ clc.serverCommandSequence & (MAX_RELIABLE_COMMANDS-1) ], 32); - - // write all the commands, including the predicted command - for ( i = 0 ; i < count ; i++ ) { - j = (cl.cmdNumber - count + i + 1) & CMD_MASK; - cmd = &cl.cmds[j]; - MSG_WriteDeltaUsercmdKey (&buf, key, oldcmd, cmd); - oldcmd = cmd; - } - } - - // - // deliver the message - // - packetNum = clc.netchan.outgoingSequence & PACKET_MASK; - cl.outPackets[ packetNum ].p_realtime = cls.realtime; - cl.outPackets[ packetNum ].p_serverTime = oldcmd->serverTime; - cl.outPackets[ packetNum ].p_cmdNumber = cl.cmdNumber; - clc.lastPacketSentTime = cls.realtime; - - if ( cl_showSend->integer ) { - Com_Printf( "%i ", buf.cursize ); - } - - CL_Netchan_Transmit (&clc.netchan, &buf); - - // clients never really should have messages large enough - // to fragment, but in case they do, fire them all off - // at once - // TTimo: this causes a packet burst, which is bad karma for winsock - // added a WARNING message, we'll see if there are legit situations where this happens - while ( clc.netchan.unsentFragments ) { - Com_DPrintf( "WARNING: #462 unsent fragments (not supposed to happen!)\n" ); - CL_Netchan_TransmitNextFragment( &clc.netchan ); - } -} - -/* -================= -CL_SendCmd - -Called every frame to builds and sends a command packet to the server. -================= -*/ -void CL_SendCmd( void ) { - // don't send any message if not connected - if ( cls.state < CA_CONNECTED ) { - return; - } - - // don't send commands if paused - if ( com_sv_running->integer && sv_paused->integer && cl_paused->integer ) { - return; - } - - // we create commands even if a demo is playing, - CL_CreateNewCommands(); - - // don't send a packet if the last packet was sent too recently - if ( !CL_ReadyToSendPacket() ) { - if ( cl_showSend->integer ) { - Com_Printf( ". " ); - } - return; - } - - CL_WritePacket(); -} - -/* -============ -CL_InitInput -============ -*/ -void CL_InitInput( void ) { - Cmd_AddCommand ("centerview",IN_CenterView); - - Cmd_AddCommand ("+moveup",IN_UpDown); - Cmd_AddCommand ("-moveup",IN_UpUp); - Cmd_AddCommand ("+movedown",IN_DownDown); - Cmd_AddCommand ("-movedown",IN_DownUp); - Cmd_AddCommand ("+left",IN_LeftDown); - Cmd_AddCommand ("-left",IN_LeftUp); - Cmd_AddCommand ("+right",IN_RightDown); - Cmd_AddCommand ("-right",IN_RightUp); - Cmd_AddCommand ("+forward",IN_ForwardDown); - Cmd_AddCommand ("-forward",IN_ForwardUp); - Cmd_AddCommand ("+back",IN_BackDown); - Cmd_AddCommand ("-back",IN_BackUp); - Cmd_AddCommand ("+lookup", IN_LookupDown); - Cmd_AddCommand ("-lookup", IN_LookupUp); - Cmd_AddCommand ("+lookdown", IN_LookdownDown); - Cmd_AddCommand ("-lookdown", IN_LookdownUp); - Cmd_AddCommand ("+strafe", IN_StrafeDown); - Cmd_AddCommand ("-strafe", IN_StrafeUp); - Cmd_AddCommand ("+moveleft", IN_MoveleftDown); - Cmd_AddCommand ("-moveleft", IN_MoveleftUp); - Cmd_AddCommand ("+moveright", IN_MoverightDown); - Cmd_AddCommand ("-moveright", IN_MoverightUp); - Cmd_AddCommand ("+speed", IN_SpeedDown); - Cmd_AddCommand ("-speed", IN_SpeedUp); - Cmd_AddCommand ("+attack", IN_Button0Down); - Cmd_AddCommand ("-attack", IN_Button0Up); - Cmd_AddCommand ("+button0", IN_Button0Down); - Cmd_AddCommand ("-button0", IN_Button0Up); - Cmd_AddCommand ("+button1", IN_Button1Down); - Cmd_AddCommand ("-button1", IN_Button1Up); - Cmd_AddCommand ("+button2", IN_Button2Down); - Cmd_AddCommand ("-button2", IN_Button2Up); - Cmd_AddCommand ("+button3", IN_Button3Down); - Cmd_AddCommand ("-button3", IN_Button3Up); - Cmd_AddCommand ("+button4", IN_Button4Down); - Cmd_AddCommand ("-button4", IN_Button4Up); - Cmd_AddCommand ("+button5", IN_Button5Down); - Cmd_AddCommand ("-button5", IN_Button5Up); - Cmd_AddCommand ("+button6", IN_Button6Down); - Cmd_AddCommand ("-button6", IN_Button6Up); - Cmd_AddCommand ("+button7", IN_Button7Down); - Cmd_AddCommand ("-button7", IN_Button7Up); - Cmd_AddCommand ("+button8", IN_Button8Down); - Cmd_AddCommand ("-button8", IN_Button8Up); - Cmd_AddCommand ("+button9", IN_Button9Down); - Cmd_AddCommand ("-button9", IN_Button9Up); - Cmd_AddCommand ("+button10", IN_Button10Down); - Cmd_AddCommand ("-button10", IN_Button10Up); - Cmd_AddCommand ("+button11", IN_Button11Down); - Cmd_AddCommand ("-button11", IN_Button11Up); - Cmd_AddCommand ("+button12", IN_Button12Down); - Cmd_AddCommand ("-button12", IN_Button12Up); - Cmd_AddCommand ("+button13", IN_Button13Down); - Cmd_AddCommand ("-button13", IN_Button13Up); - Cmd_AddCommand ("+button14", IN_Button14Down); - Cmd_AddCommand ("-button14", IN_Button14Up); - Cmd_AddCommand ("+mlook", IN_MLookDown); - Cmd_AddCommand ("-mlook", IN_MLookUp); - - cl_nodelta = Cvar_Get ("cl_nodelta", "0", 0); - cl_debugMove = Cvar_Get ("cl_debugMove", "0", 0); -} +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// cl.input.c -- builds an intended movement command to send to the server + +#include "client.h" + +unsigned frame_msec; +int old_com_frameTime; + +/* +=============================================================================== + +KEY BUTTONS + +Continuous button event tracking is complicated by the fact that two different +input sources (say, mouse button 1 and the control key) can both press the +same button, but the button should only be released when both of the +pressing key have been released. + +When a key event issues a button command (+forward, +attack, etc), it appends +its key number as argv(1) so it can be matched up with the release. + +argv(2) will be set to the time the event happened, which allows exact +control even at low framerates when the down and up events may both get qued +at the same time. + +=============================================================================== +*/ + + +kbutton_t in_left, in_right, in_forward, in_back; +kbutton_t in_lookup, in_lookdown, in_moveleft, in_moveright; +kbutton_t in_strafe, in_speed; +kbutton_t in_up, in_down; + +kbutton_t in_buttons[16]; + + +qboolean in_mlooking; + + +void IN_MLookDown( void ) { + in_mlooking = qtrue; +} + +void IN_MLookUp( void ) { + in_mlooking = qfalse; + if ( !cl_freelook->integer ) { + IN_CenterView (); + } +} + +void IN_KeyDown( kbutton_t *b ) { + int k; + char *c; + + c = Cmd_Argv(1); + if ( c[0] ) { + k = atoi(c); + } else { + k = -1; // typed manually at the console for continuous down + } + + if ( k == b->down[0] || k == b->down[1] ) { + return; // repeating key + } + + if ( !b->down[0] ) { + b->down[0] = k; + } else if ( !b->down[1] ) { + b->down[1] = k; + } else { + Com_Printf ("Three keys down for a button!\n"); + return; + } + + if ( b->active ) { + return; // still down + } + + // save timestamp for partial frame summing + c = Cmd_Argv(2); + b->downtime = atoi(c); + + b->active = qtrue; + b->wasPressed = qtrue; +} + +void IN_KeyUp( kbutton_t *b ) { + int k; + char *c; + unsigned uptime; + + c = Cmd_Argv(1); + if ( c[0] ) { + k = atoi(c); + } else { + // typed manually at the console, assume for unsticking, so clear all + b->down[0] = b->down[1] = 0; + b->active = qfalse; + return; + } + + if ( b->down[0] == k ) { + b->down[0] = 0; + } else if ( b->down[1] == k ) { + b->down[1] = 0; + } else { + return; // key up without coresponding down (menu pass through) + } + if ( b->down[0] || b->down[1] ) { + return; // some other key is still holding it down + } + + b->active = qfalse; + + // save timestamp for partial frame summing + c = Cmd_Argv(2); + uptime = atoi(c); + if ( uptime ) { + b->msec += uptime - b->downtime; + } else { + b->msec += frame_msec / 2; + } + + b->active = qfalse; +} + + + +/* +=============== +CL_KeyState + +Returns the fraction of the frame that the key was down +=============== +*/ +float CL_KeyState( kbutton_t *key ) { + float val; + int msec; + + msec = key->msec; + key->msec = 0; + + if ( key->active ) { + // still down + if ( !key->downtime ) { + msec = com_frameTime; + } else { + msec += com_frameTime - key->downtime; + } + key->downtime = com_frameTime; + } + +#if 0 + if (msec) { + Com_Printf ("%i ", msec); + } +#endif + + val = (float)msec / frame_msec; + if ( val < 0 ) { + val = 0; + } + if ( val > 1 ) { + val = 1; + } + + return val; +} + + + +void IN_UpDown(void) {IN_KeyDown(&in_up);} +void IN_UpUp(void) {IN_KeyUp(&in_up);} +void IN_DownDown(void) {IN_KeyDown(&in_down);} +void IN_DownUp(void) {IN_KeyUp(&in_down);} +void IN_LeftDown(void) {IN_KeyDown(&in_left);} +void IN_LeftUp(void) {IN_KeyUp(&in_left);} +void IN_RightDown(void) {IN_KeyDown(&in_right);} +void IN_RightUp(void) {IN_KeyUp(&in_right);} +void IN_ForwardDown(void) {IN_KeyDown(&in_forward);} +void IN_ForwardUp(void) {IN_KeyUp(&in_forward);} +void IN_BackDown(void) {IN_KeyDown(&in_back);} +void IN_BackUp(void) {IN_KeyUp(&in_back);} +void IN_LookupDown(void) {IN_KeyDown(&in_lookup);} +void IN_LookupUp(void) {IN_KeyUp(&in_lookup);} +void IN_LookdownDown(void) {IN_KeyDown(&in_lookdown);} +void IN_LookdownUp(void) {IN_KeyUp(&in_lookdown);} +void IN_MoveleftDown(void) {IN_KeyDown(&in_moveleft);} +void IN_MoveleftUp(void) {IN_KeyUp(&in_moveleft);} +void IN_MoverightDown(void) {IN_KeyDown(&in_moveright);} +void IN_MoverightUp(void) {IN_KeyUp(&in_moveright);} + +void IN_SpeedDown(void) {IN_KeyDown(&in_speed);} +void IN_SpeedUp(void) {IN_KeyUp(&in_speed);} +void IN_StrafeDown(void) {IN_KeyDown(&in_strafe);} +void IN_StrafeUp(void) {IN_KeyUp(&in_strafe);} + +void IN_Button0Down(void) {IN_KeyDown(&in_buttons[0]);} +void IN_Button0Up(void) {IN_KeyUp(&in_buttons[0]);} +void IN_Button1Down(void) {IN_KeyDown(&in_buttons[1]);} +void IN_Button1Up(void) {IN_KeyUp(&in_buttons[1]);} +void IN_Button2Down(void) {IN_KeyDown(&in_buttons[2]);} +void IN_Button2Up(void) {IN_KeyUp(&in_buttons[2]);} +void IN_Button3Down(void) {IN_KeyDown(&in_buttons[3]);} +void IN_Button3Up(void) {IN_KeyUp(&in_buttons[3]);} +void IN_Button4Down(void) {IN_KeyDown(&in_buttons[4]);} +void IN_Button4Up(void) {IN_KeyUp(&in_buttons[4]);} +void IN_Button5Down(void) {IN_KeyDown(&in_buttons[5]);} +void IN_Button5Up(void) {IN_KeyUp(&in_buttons[5]);} +void IN_Button6Down(void) {IN_KeyDown(&in_buttons[6]);} +void IN_Button6Up(void) {IN_KeyUp(&in_buttons[6]);} +void IN_Button7Down(void) {IN_KeyDown(&in_buttons[7]);} +void IN_Button7Up(void) {IN_KeyUp(&in_buttons[7]);} +void IN_Button8Down(void) {IN_KeyDown(&in_buttons[8]);} +void IN_Button8Up(void) {IN_KeyUp(&in_buttons[8]);} +void IN_Button9Down(void) {IN_KeyDown(&in_buttons[9]);} +void IN_Button9Up(void) {IN_KeyUp(&in_buttons[9]);} +void IN_Button10Down(void) {IN_KeyDown(&in_buttons[10]);} +void IN_Button10Up(void) {IN_KeyUp(&in_buttons[10]);} +void IN_Button11Down(void) {IN_KeyDown(&in_buttons[11]);} +void IN_Button11Up(void) {IN_KeyUp(&in_buttons[11]);} +void IN_Button12Down(void) {IN_KeyDown(&in_buttons[12]);} +void IN_Button12Up(void) {IN_KeyUp(&in_buttons[12]);} +void IN_Button13Down(void) {IN_KeyDown(&in_buttons[13]);} +void IN_Button13Up(void) {IN_KeyUp(&in_buttons[13]);} +void IN_Button14Down(void) {IN_KeyDown(&in_buttons[14]);} +void IN_Button14Up(void) {IN_KeyUp(&in_buttons[14]);} +void IN_Button15Down(void) {IN_KeyDown(&in_buttons[15]);} +void IN_Button15Up(void) {IN_KeyUp(&in_buttons[15]);} + +void IN_ButtonDown (void) { + IN_KeyDown(&in_buttons[1]);} +void IN_ButtonUp (void) { + IN_KeyUp(&in_buttons[1]);} + +void IN_CenterView (void) { + cl.viewangles[PITCH] = -SHORT2ANGLE(cl.snap.ps.delta_angles[PITCH]); +} + + +//========================================================================== + +cvar_t *cl_upspeed; +cvar_t *cl_forwardspeed; +cvar_t *cl_sidespeed; + +cvar_t *cl_yawspeed; +cvar_t *cl_pitchspeed; + +cvar_t *cl_run; + +cvar_t *cl_anglespeedkey; + + +/* +================ +CL_AdjustAngles + +Moves the local angle positions +================ +*/ +void CL_AdjustAngles( void ) { + float speed; + + if ( in_speed.active ) { + speed = 0.001 * cls.frametime * cl_anglespeedkey->value; + } else { + speed = 0.001 * cls.frametime; + } + + if ( !in_strafe.active ) { + cl.viewangles[YAW] -= speed*cl_yawspeed->value*CL_KeyState (&in_right); + cl.viewangles[YAW] += speed*cl_yawspeed->value*CL_KeyState (&in_left); + } + + cl.viewangles[PITCH] -= speed*cl_pitchspeed->value * CL_KeyState (&in_lookup); + cl.viewangles[PITCH] += speed*cl_pitchspeed->value * CL_KeyState (&in_lookdown); +} + +/* +================ +CL_KeyMove + +Sets the usercmd_t based on key states +================ +*/ +void CL_KeyMove( usercmd_t *cmd ) { + int movespeed; + int forward, side, up; + + // + // adjust for speed key / running + // the walking flag is to keep animations consistant + // even during acceleration and develeration + // + if ( in_speed.active ^ cl_run->integer ) { + movespeed = 127; + cmd->buttons &= ~BUTTON_WALKING; + } else { + cmd->buttons |= BUTTON_WALKING; + movespeed = 64; + } + + forward = 0; + side = 0; + up = 0; + if ( in_strafe.active ) { + side += movespeed * CL_KeyState (&in_right); + side -= movespeed * CL_KeyState (&in_left); + } + + side += movespeed * CL_KeyState (&in_moveright); + side -= movespeed * CL_KeyState (&in_moveleft); + + + up += movespeed * CL_KeyState (&in_up); + up -= movespeed * CL_KeyState (&in_down); + + forward += movespeed * CL_KeyState (&in_forward); + forward -= movespeed * CL_KeyState (&in_back); + + cmd->forwardmove = ClampChar( forward ); + cmd->rightmove = ClampChar( side ); + cmd->upmove = ClampChar( up ); +} + +/* +================= +CL_MouseEvent +================= +*/ +void CL_MouseEvent( int dx, int dy, int time ) { + if ( cls.keyCatchers & KEYCATCH_UI ) { + VM_Call( uivm, UI_MOUSE_EVENT, dx, dy ); + } else if (cls.keyCatchers & KEYCATCH_CGAME) { + VM_Call (cgvm, CG_MOUSE_EVENT, dx, dy); + } else { + cl.mouseDx[cl.mouseIndex] += dx; + cl.mouseDy[cl.mouseIndex] += dy; + } +} + +/* +================= +CL_JoystickEvent + +Joystick values stay set until changed +================= +*/ +void CL_JoystickEvent( int axis, int value, int time ) { + if ( axis < 0 || axis >= MAX_JOYSTICK_AXIS ) { + Com_Error( ERR_DROP, "CL_JoystickEvent: bad axis %i", axis ); + } + cl.joystickAxis[axis] = value; +} + +/* +================= +CL_JoystickMove +================= +*/ +void CL_JoystickMove( usercmd_t *cmd ) { + int movespeed; + float anglespeed; + + if ( in_speed.active ^ cl_run->integer ) { + movespeed = 2; + } else { + movespeed = 1; + cmd->buttons |= BUTTON_WALKING; + } + + if ( in_speed.active ) { + anglespeed = 0.001 * cls.frametime * cl_anglespeedkey->value; + } else { + anglespeed = 0.001 * cls.frametime; + } + + if ( !in_strafe.active ) { + cl.viewangles[YAW] += anglespeed * cl_yawspeed->value * cl.joystickAxis[AXIS_SIDE]; + } else { + cmd->rightmove = ClampChar( cmd->rightmove + cl.joystickAxis[AXIS_SIDE] ); + } + + if ( in_mlooking ) { + cl.viewangles[PITCH] += anglespeed * cl_pitchspeed->value * cl.joystickAxis[AXIS_FORWARD]; + } else { + cmd->forwardmove = ClampChar( cmd->forwardmove + cl.joystickAxis[AXIS_FORWARD] ); + } + + cmd->upmove = ClampChar( cmd->upmove + cl.joystickAxis[AXIS_UP] ); +} + +/* +================= +CL_MouseMove +================= +*/ +void CL_MouseMove( usercmd_t *cmd ) { + float mx, my; + float accelSensitivity; + float rate; + + // allow mouse smoothing + if ( m_filter->integer ) { + mx = ( cl.mouseDx[0] + cl.mouseDx[1] ) * 0.5; + my = ( cl.mouseDy[0] + cl.mouseDy[1] ) * 0.5; + } else { + mx = cl.mouseDx[cl.mouseIndex]; + my = cl.mouseDy[cl.mouseIndex]; + } + cl.mouseIndex ^= 1; + cl.mouseDx[cl.mouseIndex] = 0; + cl.mouseDy[cl.mouseIndex] = 0; + + rate = sqrt( mx * mx + my * my ) / (float)frame_msec; + accelSensitivity = cl_sensitivity->value + rate * cl_mouseAccel->value; + + // scale by FOV + accelSensitivity *= cl.cgameSensitivity; + + if ( rate && cl_showMouseRate->integer ) { + Com_Printf( "%f : %f\n", rate, accelSensitivity ); + } + + mx *= accelSensitivity; + my *= accelSensitivity; + + if (!mx && !my) { + return; + } + + // add mouse X/Y movement to cmd + if ( in_strafe.active ) { + cmd->rightmove = ClampChar( cmd->rightmove + m_side->value * mx ); + } else { + cl.viewangles[YAW] -= m_yaw->value * mx; + } + + if ( (in_mlooking || cl_freelook->integer) && !in_strafe.active ) { + cl.viewangles[PITCH] += m_pitch->value * my; + } else { + cmd->forwardmove = ClampChar( cmd->forwardmove - m_forward->value * my ); + } +} + + +/* +============== +CL_CmdButtons +============== +*/ +void CL_CmdButtons( usercmd_t *cmd ) { + int i; + + // + // figure button bits + // send a button bit even if the key was pressed and released in + // less than a frame + // + for (i = 0 ; i < 15 ; i++) { + if ( in_buttons[i].active || in_buttons[i].wasPressed ) { + cmd->buttons |= 1 << i; + } + in_buttons[i].wasPressed = qfalse; + } + + if ( cls.keyCatchers ) { + cmd->buttons |= BUTTON_TALK; + } + + // allow the game to know if any key at all is + // currently pressed, even if it isn't bound to anything + if ( anykeydown && !cls.keyCatchers ) { + cmd->buttons |= BUTTON_ANY; + } +} + + +/* +============== +CL_FinishMove +============== +*/ +void CL_FinishMove( usercmd_t *cmd ) { + int i; + + // copy the state that the cgame is currently sending + cmd->weapon = cl.cgameUserCmdValue; + + // send the current server time so the amount of movement + // can be determined without allowing cheating + cmd->serverTime = cl.serverTime; + + for (i=0 ; i<3 ; i++) { + cmd->angles[i] = ANGLE2SHORT(cl.viewangles[i]); + } +} + + +/* +================= +CL_CreateCmd +================= +*/ +usercmd_t CL_CreateCmd( void ) { + usercmd_t cmd; + vec3_t oldAngles; + + VectorCopy( cl.viewangles, oldAngles ); + + // keyboard angle adjustment + CL_AdjustAngles (); + + Com_Memset( &cmd, 0, sizeof( cmd ) ); + + CL_CmdButtons( &cmd ); + + // get basic movement from keyboard + CL_KeyMove( &cmd ); + + // get basic movement from mouse + CL_MouseMove( &cmd ); + + // get basic movement from joystick + CL_JoystickMove( &cmd ); + + // check to make sure the angles haven't wrapped + if ( cl.viewangles[PITCH] - oldAngles[PITCH] > 90 ) { + cl.viewangles[PITCH] = oldAngles[PITCH] + 90; + } else if ( oldAngles[PITCH] - cl.viewangles[PITCH] > 90 ) { + cl.viewangles[PITCH] = oldAngles[PITCH] - 90; + } + + // store out the final values + CL_FinishMove( &cmd ); + + // draw debug graphs of turning for mouse testing + if ( cl_debugMove->integer ) { + if ( cl_debugMove->integer == 1 ) { + SCR_DebugGraph( abs(cl.viewangles[YAW] - oldAngles[YAW]), 0 ); + } + if ( cl_debugMove->integer == 2 ) { + SCR_DebugGraph( abs(cl.viewangles[PITCH] - oldAngles[PITCH]), 0 ); + } + } + + return cmd; +} + + +/* +================= +CL_CreateNewCommands + +Create a new usercmd_t structure for this frame +================= +*/ +void CL_CreateNewCommands( void ) { + usercmd_t *cmd; + int cmdNum; + + // no need to create usercmds until we have a gamestate + if ( cls.state < CA_PRIMED ) { + return; + } + + frame_msec = com_frameTime - old_com_frameTime; + + // if running less than 5fps, truncate the extra time to prevent + // unexpected moves after a hitch + if ( frame_msec > 200 ) { + frame_msec = 200; + } + old_com_frameTime = com_frameTime; + + + // generate a command for this frame + cl.cmdNumber++; + cmdNum = cl.cmdNumber & CMD_MASK; + cl.cmds[cmdNum] = CL_CreateCmd (); + cmd = &cl.cmds[cmdNum]; +} + +/* +================= +CL_ReadyToSendPacket + +Returns qfalse if we are over the maxpackets limit +and should choke back the bandwidth a bit by not sending +a packet this frame. All the commands will still get +delivered in the next packet, but saving a header and +getting more delta compression will reduce total bandwidth. +================= +*/ +qboolean CL_ReadyToSendPacket( void ) { + int oldPacketNum; + int delta; + + // don't send anything if playing back a demo + if ( clc.demoplaying || cls.state == CA_CINEMATIC ) { + return qfalse; + } + + // If we are downloading, we send no less than 50ms between packets + if ( *clc.downloadTempName && + cls.realtime - clc.lastPacketSentTime < 50 ) { + return qfalse; + } + + // if we don't have a valid gamestate yet, only send + // one packet a second + if ( cls.state != CA_ACTIVE && + cls.state != CA_PRIMED && + !*clc.downloadTempName && + cls.realtime - clc.lastPacketSentTime < 1000 ) { + return qfalse; + } + + // send every frame for loopbacks + if ( clc.netchan.remoteAddress.type == NA_LOOPBACK ) { + return qtrue; + } + + // send every frame for LAN + if ( Sys_IsLANAddress( clc.netchan.remoteAddress ) ) { + return qtrue; + } + + // check for exceeding cl_maxpackets + if ( cl_maxpackets->integer < 15 ) { + Cvar_Set( "cl_maxpackets", "15" ); + } else if ( cl_maxpackets->integer > 125 ) { + Cvar_Set( "cl_maxpackets", "125" ); + } + oldPacketNum = (clc.netchan.outgoingSequence - 1) & PACKET_MASK; + delta = cls.realtime - cl.outPackets[ oldPacketNum ].p_realtime; + if ( delta < 1000 / cl_maxpackets->integer ) { + // the accumulated commands will go out in the next packet + return qfalse; + } + + return qtrue; +} + +/* +=================== +CL_WritePacket + +Create and send the command packet to the server +Including both the reliable commands and the usercmds + +During normal gameplay, a client packet will contain something like: + +4 sequence number +2 qport +4 serverid +4 acknowledged sequence number +4 clc.serverCommandSequence + +1 clc_move or clc_moveNoDelta +1 command count + + +=================== +*/ +void CL_WritePacket( void ) { + msg_t buf; + byte data[MAX_MSGLEN]; + int i, j; + usercmd_t *cmd, *oldcmd; + usercmd_t nullcmd; + int packetNum; + int oldPacketNum; + int count, key; + + // don't send anything if playing back a demo + if ( clc.demoplaying || cls.state == CA_CINEMATIC ) { + return; + } + + Com_Memset( &nullcmd, 0, sizeof(nullcmd) ); + oldcmd = &nullcmd; + + MSG_Init( &buf, data, sizeof(data) ); + + MSG_Bitstream( &buf ); + // write the current serverId so the server + // can tell if this is from the current gameState + MSG_WriteLong( &buf, cl.serverId ); + + // write the last message we received, which can + // be used for delta compression, and is also used + // to tell if we dropped a gamestate + MSG_WriteLong( &buf, clc.serverMessageSequence ); + + // write the last reliable message we received + MSG_WriteLong( &buf, clc.serverCommandSequence ); + + // write any unacknowledged clientCommands + for ( i = clc.reliableAcknowledge + 1 ; i <= clc.reliableSequence ; i++ ) { + MSG_WriteByte( &buf, clc_clientCommand ); + MSG_WriteLong( &buf, i ); + MSG_WriteString( &buf, clc.reliableCommands[ i & (MAX_RELIABLE_COMMANDS-1) ] ); + } + + // we want to send all the usercmds that were generated in the last + // few packet, so even if a couple packets are dropped in a row, + // all the cmds will make it to the server + if ( cl_packetdup->integer < 0 ) { + Cvar_Set( "cl_packetdup", "0" ); + } else if ( cl_packetdup->integer > 5 ) { + Cvar_Set( "cl_packetdup", "5" ); + } + oldPacketNum = (clc.netchan.outgoingSequence - 1 - cl_packetdup->integer) & PACKET_MASK; + count = cl.cmdNumber - cl.outPackets[ oldPacketNum ].p_cmdNumber; + if ( count > MAX_PACKET_USERCMDS ) { + count = MAX_PACKET_USERCMDS; + Com_Printf("MAX_PACKET_USERCMDS\n"); + } + if ( count >= 1 ) { + if ( cl_showSend->integer ) { + Com_Printf( "(%i)", count ); + } + + // begin a client move command + if ( cl_nodelta->integer || !cl.snap.valid || clc.demowaiting + || clc.serverMessageSequence != cl.snap.messageNum ) { + MSG_WriteByte (&buf, clc_moveNoDelta); + } else { + MSG_WriteByte (&buf, clc_move); + } + + // write the command count + MSG_WriteByte( &buf, count ); + + // use the checksum feed in the key + key = clc.checksumFeed; + // also use the message acknowledge + key ^= clc.serverMessageSequence; + // also use the last acknowledged server command in the key + key ^= Com_HashKey(clc.serverCommands[ clc.serverCommandSequence & (MAX_RELIABLE_COMMANDS-1) ], 32); + + // write all the commands, including the predicted command + for ( i = 0 ; i < count ; i++ ) { + j = (cl.cmdNumber - count + i + 1) & CMD_MASK; + cmd = &cl.cmds[j]; + MSG_WriteDeltaUsercmdKey (&buf, key, oldcmd, cmd); + oldcmd = cmd; + } + } + + // + // deliver the message + // + packetNum = clc.netchan.outgoingSequence & PACKET_MASK; + cl.outPackets[ packetNum ].p_realtime = cls.realtime; + cl.outPackets[ packetNum ].p_serverTime = oldcmd->serverTime; + cl.outPackets[ packetNum ].p_cmdNumber = cl.cmdNumber; + clc.lastPacketSentTime = cls.realtime; + + if ( cl_showSend->integer ) { + Com_Printf( "%i ", buf.cursize ); + } + + CL_Netchan_Transmit (&clc.netchan, &buf); + + // clients never really should have messages large enough + // to fragment, but in case they do, fire them all off + // at once + // TTimo: this causes a packet burst, which is bad karma for winsock + // added a WARNING message, we'll see if there are legit situations where this happens + while ( clc.netchan.unsentFragments ) { + Com_DPrintf( "WARNING: #462 unsent fragments (not supposed to happen!)\n" ); + CL_Netchan_TransmitNextFragment( &clc.netchan ); + } +} + +/* +================= +CL_SendCmd + +Called every frame to builds and sends a command packet to the server. +================= +*/ +void CL_SendCmd( void ) { + // don't send any message if not connected + if ( cls.state < CA_CONNECTED ) { + return; + } + + // don't send commands if paused + if ( com_sv_running->integer && sv_paused->integer && cl_paused->integer ) { + return; + } + + // we create commands even if a demo is playing, + CL_CreateNewCommands(); + + // don't send a packet if the last packet was sent too recently + if ( !CL_ReadyToSendPacket() ) { + if ( cl_showSend->integer ) { + Com_Printf( ". " ); + } + return; + } + + CL_WritePacket(); +} + +/* +============ +CL_InitInput +============ +*/ +void CL_InitInput( void ) { + Cmd_AddCommand ("centerview",IN_CenterView); + + Cmd_AddCommand ("+moveup",IN_UpDown); + Cmd_AddCommand ("-moveup",IN_UpUp); + Cmd_AddCommand ("+movedown",IN_DownDown); + Cmd_AddCommand ("-movedown",IN_DownUp); + Cmd_AddCommand ("+left",IN_LeftDown); + Cmd_AddCommand ("-left",IN_LeftUp); + Cmd_AddCommand ("+right",IN_RightDown); + Cmd_AddCommand ("-right",IN_RightUp); + Cmd_AddCommand ("+forward",IN_ForwardDown); + Cmd_AddCommand ("-forward",IN_ForwardUp); + Cmd_AddCommand ("+back",IN_BackDown); + Cmd_AddCommand ("-back",IN_BackUp); + Cmd_AddCommand ("+lookup", IN_LookupDown); + Cmd_AddCommand ("-lookup", IN_LookupUp); + Cmd_AddCommand ("+lookdown", IN_LookdownDown); + Cmd_AddCommand ("-lookdown", IN_LookdownUp); + Cmd_AddCommand ("+strafe", IN_StrafeDown); + Cmd_AddCommand ("-strafe", IN_StrafeUp); + Cmd_AddCommand ("+moveleft", IN_MoveleftDown); + Cmd_AddCommand ("-moveleft", IN_MoveleftUp); + Cmd_AddCommand ("+moveright", IN_MoverightDown); + Cmd_AddCommand ("-moveright", IN_MoverightUp); + Cmd_AddCommand ("+speed", IN_SpeedDown); + Cmd_AddCommand ("-speed", IN_SpeedUp); + Cmd_AddCommand ("+attack", IN_Button0Down); + Cmd_AddCommand ("-attack", IN_Button0Up); + Cmd_AddCommand ("+button0", IN_Button0Down); + Cmd_AddCommand ("-button0", IN_Button0Up); + Cmd_AddCommand ("+button1", IN_Button1Down); + Cmd_AddCommand ("-button1", IN_Button1Up); + Cmd_AddCommand ("+button2", IN_Button2Down); + Cmd_AddCommand ("-button2", IN_Button2Up); + Cmd_AddCommand ("+button3", IN_Button3Down); + Cmd_AddCommand ("-button3", IN_Button3Up); + Cmd_AddCommand ("+button4", IN_Button4Down); + Cmd_AddCommand ("-button4", IN_Button4Up); + Cmd_AddCommand ("+button5", IN_Button5Down); + Cmd_AddCommand ("-button5", IN_Button5Up); + Cmd_AddCommand ("+button6", IN_Button6Down); + Cmd_AddCommand ("-button6", IN_Button6Up); + Cmd_AddCommand ("+button7", IN_Button7Down); + Cmd_AddCommand ("-button7", IN_Button7Up); + Cmd_AddCommand ("+button8", IN_Button8Down); + Cmd_AddCommand ("-button8", IN_Button8Up); + Cmd_AddCommand ("+button9", IN_Button9Down); + Cmd_AddCommand ("-button9", IN_Button9Up); + Cmd_AddCommand ("+button10", IN_Button10Down); + Cmd_AddCommand ("-button10", IN_Button10Up); + Cmd_AddCommand ("+button11", IN_Button11Down); + Cmd_AddCommand ("-button11", IN_Button11Up); + Cmd_AddCommand ("+button12", IN_Button12Down); + Cmd_AddCommand ("-button12", IN_Button12Up); + Cmd_AddCommand ("+button13", IN_Button13Down); + Cmd_AddCommand ("-button13", IN_Button13Up); + Cmd_AddCommand ("+button14", IN_Button14Down); + Cmd_AddCommand ("-button14", IN_Button14Up); + Cmd_AddCommand ("+mlook", IN_MLookDown); + Cmd_AddCommand ("-mlook", IN_MLookUp); + + cl_nodelta = Cvar_Get ("cl_nodelta", "0", 0); + cl_debugMove = Cvar_Get ("cl_debugMove", "0", 0); +} diff --git a/code/client/cl_keys.c b/code/client/cl_keys.c index 7dcd7d8..1ccfc52 100755 --- a/code/client/cl_keys.c +++ b/code/client/cl_keys.c @@ -1,1252 +1,1252 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -#include "client.h" - -/* - -key up events are sent even if in console mode - -*/ - -field_t historyEditLines[COMMAND_HISTORY]; - -int nextHistoryLine; // the last line in the history buffer, not masked -int historyLine; // the line being displayed from history buffer - // will be <= nextHistoryLine - -field_t g_consoleField; -field_t chatField; -qboolean chat_team; - -int chat_playerNum; - - -qboolean key_overstrikeMode; - -qboolean anykeydown; -qkey_t keys[MAX_KEYS]; - - -typedef struct { - char *name; - int keynum; -} keyname_t; - - -// names not in this list can either be lowercase ascii, or '0xnn' hex sequences -keyname_t keynames[] = -{ - {"TAB", K_TAB}, - {"ENTER", K_ENTER}, - {"ESCAPE", K_ESCAPE}, - {"SPACE", K_SPACE}, - {"BACKSPACE", K_BACKSPACE}, - {"UPARROW", K_UPARROW}, - {"DOWNARROW", K_DOWNARROW}, - {"LEFTARROW", K_LEFTARROW}, - {"RIGHTARROW", K_RIGHTARROW}, - - {"ALT", K_ALT}, - {"CTRL", K_CTRL}, - {"SHIFT", K_SHIFT}, - - {"COMMAND", K_COMMAND}, - - {"CAPSLOCK", K_CAPSLOCK}, - - - {"F1", K_F1}, - {"F2", K_F2}, - {"F3", K_F3}, - {"F4", K_F4}, - {"F5", K_F5}, - {"F6", K_F6}, - {"F7", K_F7}, - {"F8", K_F8}, - {"F9", K_F9}, - {"F10", K_F10}, - {"F11", K_F11}, - {"F12", K_F12}, - - {"INS", K_INS}, - {"DEL", K_DEL}, - {"PGDN", K_PGDN}, - {"PGUP", K_PGUP}, - {"HOME", K_HOME}, - {"END", K_END}, - - {"MOUSE1", K_MOUSE1}, - {"MOUSE2", K_MOUSE2}, - {"MOUSE3", K_MOUSE3}, - {"MOUSE4", K_MOUSE4}, - {"MOUSE5", K_MOUSE5}, - - {"MWHEELUP", K_MWHEELUP }, - {"MWHEELDOWN", K_MWHEELDOWN }, - - {"JOY1", K_JOY1}, - {"JOY2", K_JOY2}, - {"JOY3", K_JOY3}, - {"JOY4", K_JOY4}, - {"JOY5", K_JOY5}, - {"JOY6", K_JOY6}, - {"JOY7", K_JOY7}, - {"JOY8", K_JOY8}, - {"JOY9", K_JOY9}, - {"JOY10", K_JOY10}, - {"JOY11", K_JOY11}, - {"JOY12", K_JOY12}, - {"JOY13", K_JOY13}, - {"JOY14", K_JOY14}, - {"JOY15", K_JOY15}, - {"JOY16", K_JOY16}, - {"JOY17", K_JOY17}, - {"JOY18", K_JOY18}, - {"JOY19", K_JOY19}, - {"JOY20", K_JOY20}, - {"JOY21", K_JOY21}, - {"JOY22", K_JOY22}, - {"JOY23", K_JOY23}, - {"JOY24", K_JOY24}, - {"JOY25", K_JOY25}, - {"JOY26", K_JOY26}, - {"JOY27", K_JOY27}, - {"JOY28", K_JOY28}, - {"JOY29", K_JOY29}, - {"JOY30", K_JOY30}, - {"JOY31", K_JOY31}, - {"JOY32", K_JOY32}, - - {"AUX1", K_AUX1}, - {"AUX2", K_AUX2}, - {"AUX3", K_AUX3}, - {"AUX4", K_AUX4}, - {"AUX5", K_AUX5}, - {"AUX6", K_AUX6}, - {"AUX7", K_AUX7}, - {"AUX8", K_AUX8}, - {"AUX9", K_AUX9}, - {"AUX10", K_AUX10}, - {"AUX11", K_AUX11}, - {"AUX12", K_AUX12}, - {"AUX13", K_AUX13}, - {"AUX14", K_AUX14}, - {"AUX15", K_AUX15}, - {"AUX16", K_AUX16}, - - {"KP_HOME", K_KP_HOME }, - {"KP_UPARROW", K_KP_UPARROW }, - {"KP_PGUP", K_KP_PGUP }, - {"KP_LEFTARROW", K_KP_LEFTARROW }, - {"KP_5", K_KP_5 }, - {"KP_RIGHTARROW", K_KP_RIGHTARROW }, - {"KP_END", K_KP_END }, - {"KP_DOWNARROW", K_KP_DOWNARROW }, - {"KP_PGDN", K_KP_PGDN }, - {"KP_ENTER", K_KP_ENTER }, - {"KP_INS", K_KP_INS }, - {"KP_DEL", K_KP_DEL }, - {"KP_SLASH", K_KP_SLASH }, - {"KP_MINUS", K_KP_MINUS }, - {"KP_PLUS", K_KP_PLUS }, - {"KP_NUMLOCK", K_KP_NUMLOCK }, - {"KP_STAR", K_KP_STAR }, - {"KP_EQUALS", K_KP_EQUALS }, - - {"PAUSE", K_PAUSE}, - - {"SEMICOLON", ';'}, // because a raw semicolon seperates commands - - {NULL,0} -}; - -/* -============================================================================= - -EDIT FIELDS - -============================================================================= -*/ - - -/* -=================== -Field_Draw - -Handles horizontal scrolling and cursor blinking -x, y, amd width are in pixels -=================== -*/ -void Field_VariableSizeDraw( field_t *edit, int x, int y, int width, int size, qboolean showCursor ) { - int len; - int drawLen; - int prestep; - int cursorChar; - char str[MAX_STRING_CHARS]; - int i; - - drawLen = edit->widthInChars; - len = strlen( edit->buffer ) + 1; - - // guarantee that cursor will be visible - if ( len <= drawLen ) { - prestep = 0; - } else { - if ( edit->scroll + drawLen > len ) { - edit->scroll = len - drawLen; - if ( edit->scroll < 0 ) { - edit->scroll = 0; - } - } - prestep = edit->scroll; - -/* - if ( edit->cursor < len - drawLen ) { - prestep = edit->cursor; // cursor at start - } else { - prestep = len - drawLen; - } -*/ - } - - if ( prestep + drawLen > len ) { - drawLen = len - prestep; - } - - // extract characters from the field at - if ( drawLen >= MAX_STRING_CHARS ) { - Com_Error( ERR_DROP, "drawLen >= MAX_STRING_CHARS" ); - } - - Com_Memcpy( str, edit->buffer + prestep, drawLen ); - str[ drawLen ] = 0; - - // draw it - if ( size == SMALLCHAR_WIDTH ) { - float color[4]; - - color[0] = color[1] = color[2] = color[3] = 1.0; - SCR_DrawSmallStringExt( x, y, str, color, qfalse ); - } else { - // draw big string with drop shadow - SCR_DrawBigString( x, y, str, 1.0 ); - } - - // draw the cursor - if ( !showCursor ) { - return; - } - - if ( (int)( cls.realtime >> 8 ) & 1 ) { - return; // off blink - } - - if ( key_overstrikeMode ) { - cursorChar = 11; - } else { - cursorChar = 10; - } - - i = drawLen - ( Q_PrintStrlen( str ) + 1 ); - - if ( size == SMALLCHAR_WIDTH ) { - SCR_DrawSmallChar( x + ( edit->cursor - prestep - i ) * size, y, cursorChar ); - } else { - str[0] = cursorChar; - str[1] = 0; - SCR_DrawBigString( x + ( edit->cursor - prestep - i ) * size, y, str, 1.0 ); - - } -} - -void Field_Draw( field_t *edit, int x, int y, int width, qboolean showCursor ) -{ - Field_VariableSizeDraw( edit, x, y, width, SMALLCHAR_WIDTH, showCursor ); -} - -void Field_BigDraw( field_t *edit, int x, int y, int width, qboolean showCursor ) -{ - Field_VariableSizeDraw( edit, x, y, width, BIGCHAR_WIDTH, showCursor ); -} - -/* -================ -Field_Paste -================ -*/ -void Field_Paste( field_t *edit ) { - char *cbd; - int pasteLen, i; - - cbd = Sys_GetClipboardData(); - - if ( !cbd ) { - return; - } - - // send as if typed, so insert / overstrike works properly - pasteLen = strlen( cbd ); - for ( i = 0 ; i < pasteLen ; i++ ) { - Field_CharEvent( edit, cbd[i] ); - } - - Z_Free( cbd ); -} - -/* -================= -Field_KeyDownEvent - -Performs the basic line editing functions for the console, -in-game talk, and menu fields - -Key events are used for non-printable characters, others are gotten from char events. -================= -*/ -void Field_KeyDownEvent( field_t *edit, int key ) { - int len; - - // shift-insert is paste - if ( ( ( key == K_INS ) || ( key == K_KP_INS ) ) && keys[K_SHIFT].down ) { - Field_Paste( edit ); - return; - } - - len = strlen( edit->buffer ); - - if ( key == K_DEL ) { - if ( edit->cursor < len ) { - memmove( edit->buffer + edit->cursor, - edit->buffer + edit->cursor + 1, len - edit->cursor ); - } - return; - } - - if ( key == K_RIGHTARROW ) - { - if ( edit->cursor < len ) { - edit->cursor++; - } - - if ( edit->cursor >= edit->scroll + edit->widthInChars && edit->cursor <= len ) - { - edit->scroll++; - } - return; - } - - if ( key == K_LEFTARROW ) - { - if ( edit->cursor > 0 ) { - edit->cursor--; - } - if ( edit->cursor < edit->scroll ) - { - edit->scroll--; - } - return; - } - - if ( key == K_HOME || ( tolower(key) == 'a' && keys[K_CTRL].down ) ) { - edit->cursor = 0; - return; - } - - if ( key == K_END || ( tolower(key) == 'e' && keys[K_CTRL].down ) ) { - edit->cursor = len; - return; - } - - if ( key == K_INS ) { - key_overstrikeMode = !key_overstrikeMode; - return; - } -} - -/* -================== -Field_CharEvent -================== -*/ -void Field_CharEvent( field_t *edit, int ch ) { - int len; - - if ( ch == 'v' - 'a' + 1 ) { // ctrl-v is paste - Field_Paste( edit ); - return; - } - - if ( ch == 'c' - 'a' + 1 ) { // ctrl-c clears the field - Field_Clear( edit ); - return; - } - - len = strlen( edit->buffer ); - - if ( ch == 'h' - 'a' + 1 ) { // ctrl-h is backspace - if ( edit->cursor > 0 ) { - memmove( edit->buffer + edit->cursor - 1, - edit->buffer + edit->cursor, len + 1 - edit->cursor ); - edit->cursor--; - if ( edit->cursor < edit->scroll ) - { - edit->scroll--; - } - } - return; - } - - if ( ch == 'a' - 'a' + 1 ) { // ctrl-a is home - edit->cursor = 0; - edit->scroll = 0; - return; - } - - if ( ch == 'e' - 'a' + 1 ) { // ctrl-e is end - edit->cursor = len; - edit->scroll = edit->cursor - edit->widthInChars; - return; - } - - // - // ignore any other non printable chars - // - if ( ch < 32 ) { - return; - } - - if ( key_overstrikeMode ) { - if ( edit->cursor == MAX_EDIT_LINE - 1 ) - return; - edit->buffer[edit->cursor] = ch; - edit->cursor++; - } else { // insert mode - if ( len == MAX_EDIT_LINE - 1 ) { - return; // all full - } - memmove( edit->buffer + edit->cursor + 1, - edit->buffer + edit->cursor, len + 1 - edit->cursor ); - edit->buffer[edit->cursor] = ch; - edit->cursor++; - } - - - if ( edit->cursor >= edit->widthInChars ) { - edit->scroll++; - } - - if ( edit->cursor == len + 1) { - edit->buffer[edit->cursor] = 0; - } -} - -/* -============================================================================= - -CONSOLE LINE EDITING - -============================================================================== -*/ - -/* -==================== -Console_Key - -Handles history and console scrollback -==================== -*/ -void Console_Key (int key) { - // ctrl-L clears screen - if ( key == 'l' && keys[K_CTRL].down ) { - Cbuf_AddText ("clear\n"); - return; - } - - // enter finishes the line - if ( key == K_ENTER || key == K_KP_ENTER ) { - // if not in the game explicitly prepent a slash if needed - if ( cls.state != CA_ACTIVE && g_consoleField.buffer[0] != '\\' - && g_consoleField.buffer[0] != '/' ) { - char temp[MAX_STRING_CHARS]; - - Q_strncpyz( temp, g_consoleField.buffer, sizeof( temp ) ); - Com_sprintf( g_consoleField.buffer, sizeof( g_consoleField.buffer ), "\\%s", temp ); - g_consoleField.cursor++; - } - - Com_Printf ( "]%s\n", g_consoleField.buffer ); - - // leading slash is an explicit command - if ( g_consoleField.buffer[0] == '\\' || g_consoleField.buffer[0] == '/' ) { - Cbuf_AddText( g_consoleField.buffer+1 ); // valid command - Cbuf_AddText ("\n"); - } else { - // other text will be chat messages - if ( !g_consoleField.buffer[0] ) { - return; // empty lines just scroll the console without adding to history - } else { - Cbuf_AddText ("cmd say "); - Cbuf_AddText( g_consoleField.buffer ); - Cbuf_AddText ("\n"); - } - } - - // copy line to history buffer - historyEditLines[nextHistoryLine % COMMAND_HISTORY] = g_consoleField; - nextHistoryLine++; - historyLine = nextHistoryLine; - - Field_Clear( &g_consoleField ); - - g_consoleField.widthInChars = g_console_field_width; - - if ( cls.state == CA_DISCONNECTED ) { - SCR_UpdateScreen (); // force an update, because the command - } // may take some time - return; - } - - // command completion - - if (key == K_TAB) { - Field_CompleteCommand(&g_consoleField); - return; - } - - // command history (ctrl-p ctrl-n for unix style) - - if ( (key == K_MWHEELUP && keys[K_SHIFT].down) || ( key == K_UPARROW ) || ( key == K_KP_UPARROW ) || - ( ( tolower(key) == 'p' ) && keys[K_CTRL].down ) ) { - if ( nextHistoryLine - historyLine < COMMAND_HISTORY - && historyLine > 0 ) { - historyLine--; - } - g_consoleField = historyEditLines[ historyLine % COMMAND_HISTORY ]; - return; - } - - if ( (key == K_MWHEELDOWN && keys[K_SHIFT].down) || ( key == K_DOWNARROW ) || ( key == K_KP_DOWNARROW ) || - ( ( tolower(key) == 'n' ) && keys[K_CTRL].down ) ) { - if (historyLine == nextHistoryLine) - return; - historyLine++; - g_consoleField = historyEditLines[ historyLine % COMMAND_HISTORY ]; - return; - } - - // console scrolling - if ( key == K_PGUP ) { - Con_PageUp(); - return; - } - - if ( key == K_PGDN) { - Con_PageDown(); - return; - } - - if ( key == K_MWHEELUP) { //----(SA) added some mousewheel functionality to the console - Con_PageUp(); - if(keys[K_CTRL].down) { // hold to accelerate scrolling - Con_PageUp(); - Con_PageUp(); - } - return; - } - - if ( key == K_MWHEELDOWN) { //----(SA) added some mousewheel functionality to the console - Con_PageDown(); - if(keys[K_CTRL].down) { // hold to accelerate scrolling - Con_PageDown(); - Con_PageDown(); - } - return; - } - - // ctrl-home = top of console - if ( key == K_HOME && keys[K_CTRL].down ) { - Con_Top(); - return; - } - - // ctrl-end = bottom of console - if ( key == K_END && keys[K_CTRL].down ) { - Con_Bottom(); - return; - } - - // pass to the normal editline routine - Field_KeyDownEvent( &g_consoleField, key ); -} - -//============================================================================ - - -/* -================ -Message_Key - -In game talk message -================ -*/ -void Message_Key( int key ) { - - char buffer[MAX_STRING_CHARS]; - - - if (key == K_ESCAPE) { - cls.keyCatchers &= ~KEYCATCH_MESSAGE; - Field_Clear( &chatField ); - return; - } - - if ( key == K_ENTER || key == K_KP_ENTER ) - { - if ( chatField.buffer[0] && cls.state == CA_ACTIVE ) { - if (chat_playerNum != -1 ) - - Com_sprintf( buffer, sizeof( buffer ), "tell %i \"%s\"\n", chat_playerNum, chatField.buffer ); - - else if (chat_team) - - Com_sprintf( buffer, sizeof( buffer ), "say_team \"%s\"\n", chatField.buffer ); - else - Com_sprintf( buffer, sizeof( buffer ), "say \"%s\"\n", chatField.buffer ); - - - - CL_AddReliableCommand( buffer ); - } - cls.keyCatchers &= ~KEYCATCH_MESSAGE; - Field_Clear( &chatField ); - return; - } - - Field_KeyDownEvent( &chatField, key ); -} - -//============================================================================ - - -qboolean Key_GetOverstrikeMode( void ) { - return key_overstrikeMode; -} - - -void Key_SetOverstrikeMode( qboolean state ) { - key_overstrikeMode = state; -} - - -/* -=================== -Key_IsDown -=================== -*/ -qboolean Key_IsDown( int keynum ) { - if ( keynum == -1 ) { - return qfalse; - } - - return keys[keynum].down; -} - - -/* -=================== -Key_StringToKeynum - -Returns a key number to be used to index keys[] by looking at -the given string. Single ascii characters return themselves, while -the K_* names are matched up. - -0x11 will be interpreted as raw hex, which will allow new controlers - -to be configured even if they don't have defined names. -=================== -*/ -int Key_StringToKeynum( char *str ) { - keyname_t *kn; - - if ( !str || !str[0] ) { - return -1; - } - if ( !str[1] ) { - return str[0]; - } - - // check for hex code - if ( str[0] == '0' && str[1] == 'x' && strlen( str ) == 4) { - int n1, n2; - - n1 = str[2]; - if ( n1 >= '0' && n1 <= '9' ) { - n1 -= '0'; - } else if ( n1 >= 'a' && n1 <= 'f' ) { - n1 = n1 - 'a' + 10; - } else { - n1 = 0; - } - - n2 = str[3]; - if ( n2 >= '0' && n2 <= '9' ) { - n2 -= '0'; - } else if ( n2 >= 'a' && n2 <= 'f' ) { - n2 = n2 - 'a' + 10; - } else { - n2 = 0; - } - - return n1 * 16 + n2; - } - - // scan for a text match - for ( kn=keynames ; kn->name ; kn++ ) { - if ( !Q_stricmp( str,kn->name ) ) - return kn->keynum; - } - - return -1; -} - -/* -=================== -Key_KeynumToString - -Returns a string (either a single ascii char, a K_* name, or a 0x11 hex string) for the -given keynum. -=================== -*/ -char *Key_KeynumToString( int keynum ) { - keyname_t *kn; - static char tinystr[5]; - int i, j; - - if ( keynum == -1 ) { - return ""; - } - - if ( keynum < 0 || keynum > 255 ) { - return ""; - } - - // check for printable ascii (don't use quote) - if ( keynum > 32 && keynum < 127 && keynum != '"' && keynum != ';' ) { - tinystr[0] = keynum; - tinystr[1] = 0; - return tinystr; - } - - // check for a key string - for ( kn=keynames ; kn->name ; kn++ ) { - if (keynum == kn->keynum) { - return kn->name; - } - } - - // make a hex string - i = keynum >> 4; - j = keynum & 15; - - tinystr[0] = '0'; - tinystr[1] = 'x'; - tinystr[2] = i > 9 ? i - 10 + 'a' : i + '0'; - tinystr[3] = j > 9 ? j - 10 + 'a' : j + '0'; - tinystr[4] = 0; - - return tinystr; -} - - -/* -=================== -Key_SetBinding -=================== -*/ -void Key_SetBinding( int keynum, const char *binding ) { - if ( keynum == -1 ) { - return; - } - - // free old bindings - if ( keys[ keynum ].binding ) { - Z_Free( keys[ keynum ].binding ); - } - - // allocate memory for new binding - keys[keynum].binding = CopyString( binding ); - - // consider this like modifying an archived cvar, so the - // file write will be triggered at the next oportunity - cvar_modifiedFlags |= CVAR_ARCHIVE; -} - - -/* -=================== -Key_GetBinding -=================== -*/ -char *Key_GetBinding( int keynum ) { - if ( keynum == -1 ) { - return ""; - } - - return keys[ keynum ].binding; -} - -/* -=================== -Key_GetKey -=================== -*/ - -int Key_GetKey(const char *binding) { - int i; - - if (binding) { - for (i=0 ; i<256 ; i++) { - if (keys[i].binding && Q_stricmp(binding, keys[i].binding) == 0) { - return i; - } - } - } - return -1; -} - -/* -=================== -Key_Unbind_f -=================== -*/ -void Key_Unbind_f (void) -{ - int b; - - if (Cmd_Argc() != 2) - { - Com_Printf ("unbind : remove commands from a key\n"); - return; - } - - b = Key_StringToKeynum (Cmd_Argv(1)); - if (b==-1) - { - Com_Printf ("\"%s\" isn't a valid key\n", Cmd_Argv(1)); - return; - } - - Key_SetBinding (b, ""); -} - -/* -=================== -Key_Unbindall_f -=================== -*/ -void Key_Unbindall_f (void) -{ - int i; - - for (i=0 ; i<256 ; i++) - if (keys[i].binding) - Key_SetBinding (i, ""); -} - - -/* -=================== -Key_Bind_f -=================== -*/ -void Key_Bind_f (void) -{ - int i, c, b; - char cmd[1024]; - - c = Cmd_Argc(); - - if (c < 2) - { - Com_Printf ("bind [command] : attach a command to a key\n"); - return; - } - b = Key_StringToKeynum (Cmd_Argv(1)); - if (b==-1) - { - Com_Printf ("\"%s\" isn't a valid key\n", Cmd_Argv(1)); - return; - } - - if (c == 2) - { - if (keys[b].binding) - Com_Printf ("\"%s\" = \"%s\"\n", Cmd_Argv(1), keys[b].binding ); - else - Com_Printf ("\"%s\" is not bound\n", Cmd_Argv(1) ); - return; - } - -// copy the rest of the command line - cmd[0] = 0; // start out with a null string - for (i=2 ; i< c ; i++) - { - strcat (cmd, Cmd_Argv(i)); - if (i != (c-1)) - strcat (cmd, " "); - } - - Key_SetBinding (b, cmd); -} - -/* -============ -Key_WriteBindings - -Writes lines containing "bind key value" -============ -*/ -void Key_WriteBindings( fileHandle_t f ) { - int i; - - FS_Printf (f, "unbindall\n" ); - - for (i=0 ; i<256 ; i++) { - if (keys[i].binding && keys[i].binding[0] ) { - FS_Printf (f, "bind %s \"%s\"\n", Key_KeynumToString(i), keys[i].binding); - - } - - } -} - - -/* -============ -Key_Bindlist_f - -============ -*/ -void Key_Bindlist_f( void ) { - int i; - - for ( i = 0 ; i < 256 ; i++ ) { - if ( keys[i].binding && keys[i].binding[0] ) { - Com_Printf( "%s \"%s\"\n", Key_KeynumToString(i), keys[i].binding ); - } - } -} - -/* -=================== -CL_InitKeyCommands -=================== -*/ -void CL_InitKeyCommands( void ) { - // register our functions - Cmd_AddCommand ("bind",Key_Bind_f); - Cmd_AddCommand ("unbind",Key_Unbind_f); - Cmd_AddCommand ("unbindall",Key_Unbindall_f); - Cmd_AddCommand ("bindlist",Key_Bindlist_f); -} - -/* -=================== -CL_AddKeyUpCommands -=================== -*/ -void CL_AddKeyUpCommands( int key, char *kb ) { - int i; - char button[1024], *buttonPtr; - char cmd[1024]; - qboolean keyevent; - - if ( !kb ) { - return; - } - keyevent = qfalse; - buttonPtr = button; - for ( i = 0; ; i++ ) { - if ( kb[i] == ';' || !kb[i] ) { - *buttonPtr = '\0'; - if ( button[0] == '+') { - // button commands add keynum and time as parms so that multiple - // sources can be discriminated and subframe corrected - Com_sprintf (cmd, sizeof(cmd), "-%s %i %i\n", button+1, key, time); - Cbuf_AddText (cmd); - keyevent = qtrue; - } else { - if (keyevent) { - // down-only command - Cbuf_AddText (button); - Cbuf_AddText ("\n"); - } - } - buttonPtr = button; - while ( (kb[i] <= ' ' || kb[i] == ';') && kb[i] != 0 ) { - i++; - } - } - *buttonPtr++ = kb[i]; - if ( !kb[i] ) { - break; - } - } -} - -/* -=================== -CL_KeyEvent - -Called by the system for both key up and key down events -=================== -*/ -void CL_KeyEvent (int key, qboolean down, unsigned time) { - char *kb; - char cmd[1024]; - - // update auto-repeat status and BUTTON_ANY status - keys[key].down = down; - - if (down) { - keys[key].repeats++; - if ( keys[key].repeats == 1) { - anykeydown++; - } - } else { - keys[key].repeats = 0; - anykeydown--; - if (anykeydown < 0) { - anykeydown = 0; - } - } - -#ifdef __linux__ - if (key == K_ENTER) - { - if (down) - { - if (keys[K_ALT].down) - { - Key_ClearStates(); - if (Cvar_VariableValue("r_fullscreen") == 0) - { - Com_Printf("Switching to fullscreen rendering\n"); - Cvar_Set("r_fullscreen", "1"); - } - else - { - Com_Printf("Switching to windowed rendering\n"); - Cvar_Set("r_fullscreen", "0"); - } - Cbuf_ExecuteText( EXEC_APPEND, "vid_restart\n"); - return; - } - } - } -#endif - - // console key is hardcoded, so the user can never unbind it - if (key == '`' || key == '~') { - if (!down) { - return; - } - Con_ToggleConsole_f (); - return; - } - - - // keys can still be used for bound actions - if ( down && ( key < 128 || key == K_MOUSE1 ) && ( clc.demoplaying || cls.state == CA_CINEMATIC ) && !cls.keyCatchers) { - - if (Cvar_VariableValue ("com_cameraMode") == 0) { - Cvar_Set ("nextdemo",""); - key = K_ESCAPE; - } - } - - - // escape is always handled special - if ( key == K_ESCAPE && down ) { - if ( cls.keyCatchers & KEYCATCH_MESSAGE ) { - // clear message mode - Message_Key( key ); - return; - } - - // escape always gets out of CGAME stuff - if (cls.keyCatchers & KEYCATCH_CGAME) { - cls.keyCatchers &= ~KEYCATCH_CGAME; - VM_Call (cgvm, CG_EVENT_HANDLING, CGAME_EVENT_NONE); - return; - } - - if ( !( cls.keyCatchers & KEYCATCH_UI ) ) { - if ( cls.state == CA_ACTIVE && !clc.demoplaying ) { - VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_INGAME ); - } - else { - CL_Disconnect_f(); - S_StopAllSounds(); - VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_MAIN ); - } - return; - } - - VM_Call( uivm, UI_KEY_EVENT, key, down ); - return; - } - - // - // key up events only perform actions if the game key binding is - // a button command (leading + sign). These will be processed even in - // console mode and menu mode, to keep the character from continuing - // an action started before a mode switch. - // - if (!down) { - kb = keys[key].binding; - - CL_AddKeyUpCommands( key, kb ); - - if ( cls.keyCatchers & KEYCATCH_UI && uivm ) { - VM_Call( uivm, UI_KEY_EVENT, key, down ); - } else if ( cls.keyCatchers & KEYCATCH_CGAME && cgvm ) { - VM_Call( cgvm, CG_KEY_EVENT, key, down ); - } - - return; - } - - - // distribute the key down event to the apropriate handler - if ( cls.keyCatchers & KEYCATCH_CONSOLE ) { - Console_Key( key ); - } else if ( cls.keyCatchers & KEYCATCH_UI ) { - if ( uivm ) { - VM_Call( uivm, UI_KEY_EVENT, key, down ); - } - } else if ( cls.keyCatchers & KEYCATCH_CGAME ) { - if ( cgvm ) { - VM_Call( cgvm, CG_KEY_EVENT, key, down ); - } - } else if ( cls.keyCatchers & KEYCATCH_MESSAGE ) { - Message_Key( key ); - } else if ( cls.state == CA_DISCONNECTED ) { - Console_Key( key ); - } else { - // send the bound action - kb = keys[key].binding; - if ( !kb ) { - if (key >= 200) { - Com_Printf ("%s is unbound, use controls menu to set.\n" - , Key_KeynumToString( key ) ); - } - } else if (kb[0] == '+') { - int i; - char button[1024], *buttonPtr; - buttonPtr = button; - for ( i = 0; ; i++ ) { - if ( kb[i] == ';' || !kb[i] ) { - *buttonPtr = '\0'; - if ( button[0] == '+') { - // button commands add keynum and time as parms so that multiple - // sources can be discriminated and subframe corrected - Com_sprintf (cmd, sizeof(cmd), "%s %i %i\n", button, key, time); - Cbuf_AddText (cmd); - } else { - // down-only command - Cbuf_AddText (button); - Cbuf_AddText ("\n"); - } - buttonPtr = button; - while ( (kb[i] <= ' ' || kb[i] == ';') && kb[i] != 0 ) { - i++; - } - } - *buttonPtr++ = kb[i]; - if ( !kb[i] ) { - break; - } - } - } else { - // down-only command - Cbuf_AddText (kb); - Cbuf_AddText ("\n"); - } - } -} - - -/* -=================== -CL_CharEvent - -Normal keyboard characters, already shifted / capslocked / etc -=================== -*/ -void CL_CharEvent( int key ) { - // the console key should never be used as a char - if ( key == '`' || key == '~' ) { - return; - } - - // distribute the key down event to the apropriate handler - if ( cls.keyCatchers & KEYCATCH_CONSOLE ) - { - Field_CharEvent( &g_consoleField, key ); - } - else if ( cls.keyCatchers & KEYCATCH_UI ) - { - VM_Call( uivm, UI_KEY_EVENT, key | K_CHAR_FLAG, qtrue ); - } - else if ( cls.keyCatchers & KEYCATCH_MESSAGE ) - { - Field_CharEvent( &chatField, key ); - } - else if ( cls.state == CA_DISCONNECTED ) - { - Field_CharEvent( &g_consoleField, key ); - } -} - - -/* -=================== -Key_ClearStates -=================== -*/ -void Key_ClearStates (void) -{ - int i; - - anykeydown = qfalse; - - for ( i=0 ; i < MAX_KEYS ; i++ ) { - if ( keys[i].down ) { - CL_KeyEvent( i, qfalse, 0 ); - - } - keys[i].down = 0; - keys[i].repeats = 0; - } -} - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +#include "client.h" + +/* + +key up events are sent even if in console mode + +*/ + +field_t historyEditLines[COMMAND_HISTORY]; + +int nextHistoryLine; // the last line in the history buffer, not masked +int historyLine; // the line being displayed from history buffer + // will be <= nextHistoryLine + +field_t g_consoleField; +field_t chatField; +qboolean chat_team; + +int chat_playerNum; + + +qboolean key_overstrikeMode; + +qboolean anykeydown; +qkey_t keys[MAX_KEYS]; + + +typedef struct { + char *name; + int keynum; +} keyname_t; + + +// names not in this list can either be lowercase ascii, or '0xnn' hex sequences +keyname_t keynames[] = +{ + {"TAB", K_TAB}, + {"ENTER", K_ENTER}, + {"ESCAPE", K_ESCAPE}, + {"SPACE", K_SPACE}, + {"BACKSPACE", K_BACKSPACE}, + {"UPARROW", K_UPARROW}, + {"DOWNARROW", K_DOWNARROW}, + {"LEFTARROW", K_LEFTARROW}, + {"RIGHTARROW", K_RIGHTARROW}, + + {"ALT", K_ALT}, + {"CTRL", K_CTRL}, + {"SHIFT", K_SHIFT}, + + {"COMMAND", K_COMMAND}, + + {"CAPSLOCK", K_CAPSLOCK}, + + + {"F1", K_F1}, + {"F2", K_F2}, + {"F3", K_F3}, + {"F4", K_F4}, + {"F5", K_F5}, + {"F6", K_F6}, + {"F7", K_F7}, + {"F8", K_F8}, + {"F9", K_F9}, + {"F10", K_F10}, + {"F11", K_F11}, + {"F12", K_F12}, + + {"INS", K_INS}, + {"DEL", K_DEL}, + {"PGDN", K_PGDN}, + {"PGUP", K_PGUP}, + {"HOME", K_HOME}, + {"END", K_END}, + + {"MOUSE1", K_MOUSE1}, + {"MOUSE2", K_MOUSE2}, + {"MOUSE3", K_MOUSE3}, + {"MOUSE4", K_MOUSE4}, + {"MOUSE5", K_MOUSE5}, + + {"MWHEELUP", K_MWHEELUP }, + {"MWHEELDOWN", K_MWHEELDOWN }, + + {"JOY1", K_JOY1}, + {"JOY2", K_JOY2}, + {"JOY3", K_JOY3}, + {"JOY4", K_JOY4}, + {"JOY5", K_JOY5}, + {"JOY6", K_JOY6}, + {"JOY7", K_JOY7}, + {"JOY8", K_JOY8}, + {"JOY9", K_JOY9}, + {"JOY10", K_JOY10}, + {"JOY11", K_JOY11}, + {"JOY12", K_JOY12}, + {"JOY13", K_JOY13}, + {"JOY14", K_JOY14}, + {"JOY15", K_JOY15}, + {"JOY16", K_JOY16}, + {"JOY17", K_JOY17}, + {"JOY18", K_JOY18}, + {"JOY19", K_JOY19}, + {"JOY20", K_JOY20}, + {"JOY21", K_JOY21}, + {"JOY22", K_JOY22}, + {"JOY23", K_JOY23}, + {"JOY24", K_JOY24}, + {"JOY25", K_JOY25}, + {"JOY26", K_JOY26}, + {"JOY27", K_JOY27}, + {"JOY28", K_JOY28}, + {"JOY29", K_JOY29}, + {"JOY30", K_JOY30}, + {"JOY31", K_JOY31}, + {"JOY32", K_JOY32}, + + {"AUX1", K_AUX1}, + {"AUX2", K_AUX2}, + {"AUX3", K_AUX3}, + {"AUX4", K_AUX4}, + {"AUX5", K_AUX5}, + {"AUX6", K_AUX6}, + {"AUX7", K_AUX7}, + {"AUX8", K_AUX8}, + {"AUX9", K_AUX9}, + {"AUX10", K_AUX10}, + {"AUX11", K_AUX11}, + {"AUX12", K_AUX12}, + {"AUX13", K_AUX13}, + {"AUX14", K_AUX14}, + {"AUX15", K_AUX15}, + {"AUX16", K_AUX16}, + + {"KP_HOME", K_KP_HOME }, + {"KP_UPARROW", K_KP_UPARROW }, + {"KP_PGUP", K_KP_PGUP }, + {"KP_LEFTARROW", K_KP_LEFTARROW }, + {"KP_5", K_KP_5 }, + {"KP_RIGHTARROW", K_KP_RIGHTARROW }, + {"KP_END", K_KP_END }, + {"KP_DOWNARROW", K_KP_DOWNARROW }, + {"KP_PGDN", K_KP_PGDN }, + {"KP_ENTER", K_KP_ENTER }, + {"KP_INS", K_KP_INS }, + {"KP_DEL", K_KP_DEL }, + {"KP_SLASH", K_KP_SLASH }, + {"KP_MINUS", K_KP_MINUS }, + {"KP_PLUS", K_KP_PLUS }, + {"KP_NUMLOCK", K_KP_NUMLOCK }, + {"KP_STAR", K_KP_STAR }, + {"KP_EQUALS", K_KP_EQUALS }, + + {"PAUSE", K_PAUSE}, + + {"SEMICOLON", ';'}, // because a raw semicolon seperates commands + + {NULL,0} +}; + +/* +============================================================================= + +EDIT FIELDS + +============================================================================= +*/ + + +/* +=================== +Field_Draw + +Handles horizontal scrolling and cursor blinking +x, y, amd width are in pixels +=================== +*/ +void Field_VariableSizeDraw( field_t *edit, int x, int y, int width, int size, qboolean showCursor ) { + int len; + int drawLen; + int prestep; + int cursorChar; + char str[MAX_STRING_CHARS]; + int i; + + drawLen = edit->widthInChars; + len = strlen( edit->buffer ) + 1; + + // guarantee that cursor will be visible + if ( len <= drawLen ) { + prestep = 0; + } else { + if ( edit->scroll + drawLen > len ) { + edit->scroll = len - drawLen; + if ( edit->scroll < 0 ) { + edit->scroll = 0; + } + } + prestep = edit->scroll; + +/* + if ( edit->cursor < len - drawLen ) { + prestep = edit->cursor; // cursor at start + } else { + prestep = len - drawLen; + } +*/ + } + + if ( prestep + drawLen > len ) { + drawLen = len - prestep; + } + + // extract characters from the field at + if ( drawLen >= MAX_STRING_CHARS ) { + Com_Error( ERR_DROP, "drawLen >= MAX_STRING_CHARS" ); + } + + Com_Memcpy( str, edit->buffer + prestep, drawLen ); + str[ drawLen ] = 0; + + // draw it + if ( size == SMALLCHAR_WIDTH ) { + float color[4]; + + color[0] = color[1] = color[2] = color[3] = 1.0; + SCR_DrawSmallStringExt( x, y, str, color, qfalse ); + } else { + // draw big string with drop shadow + SCR_DrawBigString( x, y, str, 1.0 ); + } + + // draw the cursor + if ( !showCursor ) { + return; + } + + if ( (int)( cls.realtime >> 8 ) & 1 ) { + return; // off blink + } + + if ( key_overstrikeMode ) { + cursorChar = 11; + } else { + cursorChar = 10; + } + + i = drawLen - ( Q_PrintStrlen( str ) + 1 ); + + if ( size == SMALLCHAR_WIDTH ) { + SCR_DrawSmallChar( x + ( edit->cursor - prestep - i ) * size, y, cursorChar ); + } else { + str[0] = cursorChar; + str[1] = 0; + SCR_DrawBigString( x + ( edit->cursor - prestep - i ) * size, y, str, 1.0 ); + + } +} + +void Field_Draw( field_t *edit, int x, int y, int width, qboolean showCursor ) +{ + Field_VariableSizeDraw( edit, x, y, width, SMALLCHAR_WIDTH, showCursor ); +} + +void Field_BigDraw( field_t *edit, int x, int y, int width, qboolean showCursor ) +{ + Field_VariableSizeDraw( edit, x, y, width, BIGCHAR_WIDTH, showCursor ); +} + +/* +================ +Field_Paste +================ +*/ +void Field_Paste( field_t *edit ) { + char *cbd; + int pasteLen, i; + + cbd = Sys_GetClipboardData(); + + if ( !cbd ) { + return; + } + + // send as if typed, so insert / overstrike works properly + pasteLen = strlen( cbd ); + for ( i = 0 ; i < pasteLen ; i++ ) { + Field_CharEvent( edit, cbd[i] ); + } + + Z_Free( cbd ); +} + +/* +================= +Field_KeyDownEvent + +Performs the basic line editing functions for the console, +in-game talk, and menu fields + +Key events are used for non-printable characters, others are gotten from char events. +================= +*/ +void Field_KeyDownEvent( field_t *edit, int key ) { + int len; + + // shift-insert is paste + if ( ( ( key == K_INS ) || ( key == K_KP_INS ) ) && keys[K_SHIFT].down ) { + Field_Paste( edit ); + return; + } + + len = strlen( edit->buffer ); + + if ( key == K_DEL ) { + if ( edit->cursor < len ) { + memmove( edit->buffer + edit->cursor, + edit->buffer + edit->cursor + 1, len - edit->cursor ); + } + return; + } + + if ( key == K_RIGHTARROW ) + { + if ( edit->cursor < len ) { + edit->cursor++; + } + + if ( edit->cursor >= edit->scroll + edit->widthInChars && edit->cursor <= len ) + { + edit->scroll++; + } + return; + } + + if ( key == K_LEFTARROW ) + { + if ( edit->cursor > 0 ) { + edit->cursor--; + } + if ( edit->cursor < edit->scroll ) + { + edit->scroll--; + } + return; + } + + if ( key == K_HOME || ( tolower(key) == 'a' && keys[K_CTRL].down ) ) { + edit->cursor = 0; + return; + } + + if ( key == K_END || ( tolower(key) == 'e' && keys[K_CTRL].down ) ) { + edit->cursor = len; + return; + } + + if ( key == K_INS ) { + key_overstrikeMode = !key_overstrikeMode; + return; + } +} + +/* +================== +Field_CharEvent +================== +*/ +void Field_CharEvent( field_t *edit, int ch ) { + int len; + + if ( ch == 'v' - 'a' + 1 ) { // ctrl-v is paste + Field_Paste( edit ); + return; + } + + if ( ch == 'c' - 'a' + 1 ) { // ctrl-c clears the field + Field_Clear( edit ); + return; + } + + len = strlen( edit->buffer ); + + if ( ch == 'h' - 'a' + 1 ) { // ctrl-h is backspace + if ( edit->cursor > 0 ) { + memmove( edit->buffer + edit->cursor - 1, + edit->buffer + edit->cursor, len + 1 - edit->cursor ); + edit->cursor--; + if ( edit->cursor < edit->scroll ) + { + edit->scroll--; + } + } + return; + } + + if ( ch == 'a' - 'a' + 1 ) { // ctrl-a is home + edit->cursor = 0; + edit->scroll = 0; + return; + } + + if ( ch == 'e' - 'a' + 1 ) { // ctrl-e is end + edit->cursor = len; + edit->scroll = edit->cursor - edit->widthInChars; + return; + } + + // + // ignore any other non printable chars + // + if ( ch < 32 ) { + return; + } + + if ( key_overstrikeMode ) { + if ( edit->cursor == MAX_EDIT_LINE - 1 ) + return; + edit->buffer[edit->cursor] = ch; + edit->cursor++; + } else { // insert mode + if ( len == MAX_EDIT_LINE - 1 ) { + return; // all full + } + memmove( edit->buffer + edit->cursor + 1, + edit->buffer + edit->cursor, len + 1 - edit->cursor ); + edit->buffer[edit->cursor] = ch; + edit->cursor++; + } + + + if ( edit->cursor >= edit->widthInChars ) { + edit->scroll++; + } + + if ( edit->cursor == len + 1) { + edit->buffer[edit->cursor] = 0; + } +} + +/* +============================================================================= + +CONSOLE LINE EDITING + +============================================================================== +*/ + +/* +==================== +Console_Key + +Handles history and console scrollback +==================== +*/ +void Console_Key (int key) { + // ctrl-L clears screen + if ( key == 'l' && keys[K_CTRL].down ) { + Cbuf_AddText ("clear\n"); + return; + } + + // enter finishes the line + if ( key == K_ENTER || key == K_KP_ENTER ) { + // if not in the game explicitly prepent a slash if needed + if ( cls.state != CA_ACTIVE && g_consoleField.buffer[0] != '\\' + && g_consoleField.buffer[0] != '/' ) { + char temp[MAX_STRING_CHARS]; + + Q_strncpyz( temp, g_consoleField.buffer, sizeof( temp ) ); + Com_sprintf( g_consoleField.buffer, sizeof( g_consoleField.buffer ), "\\%s", temp ); + g_consoleField.cursor++; + } + + Com_Printf ( "]%s\n", g_consoleField.buffer ); + + // leading slash is an explicit command + if ( g_consoleField.buffer[0] == '\\' || g_consoleField.buffer[0] == '/' ) { + Cbuf_AddText( g_consoleField.buffer+1 ); // valid command + Cbuf_AddText ("\n"); + } else { + // other text will be chat messages + if ( !g_consoleField.buffer[0] ) { + return; // empty lines just scroll the console without adding to history + } else { + Cbuf_AddText ("cmd say "); + Cbuf_AddText( g_consoleField.buffer ); + Cbuf_AddText ("\n"); + } + } + + // copy line to history buffer + historyEditLines[nextHistoryLine % COMMAND_HISTORY] = g_consoleField; + nextHistoryLine++; + historyLine = nextHistoryLine; + + Field_Clear( &g_consoleField ); + + g_consoleField.widthInChars = g_console_field_width; + + if ( cls.state == CA_DISCONNECTED ) { + SCR_UpdateScreen (); // force an update, because the command + } // may take some time + return; + } + + // command completion + + if (key == K_TAB) { + Field_CompleteCommand(&g_consoleField); + return; + } + + // command history (ctrl-p ctrl-n for unix style) + + if ( (key == K_MWHEELUP && keys[K_SHIFT].down) || ( key == K_UPARROW ) || ( key == K_KP_UPARROW ) || + ( ( tolower(key) == 'p' ) && keys[K_CTRL].down ) ) { + if ( nextHistoryLine - historyLine < COMMAND_HISTORY + && historyLine > 0 ) { + historyLine--; + } + g_consoleField = historyEditLines[ historyLine % COMMAND_HISTORY ]; + return; + } + + if ( (key == K_MWHEELDOWN && keys[K_SHIFT].down) || ( key == K_DOWNARROW ) || ( key == K_KP_DOWNARROW ) || + ( ( tolower(key) == 'n' ) && keys[K_CTRL].down ) ) { + if (historyLine == nextHistoryLine) + return; + historyLine++; + g_consoleField = historyEditLines[ historyLine % COMMAND_HISTORY ]; + return; + } + + // console scrolling + if ( key == K_PGUP ) { + Con_PageUp(); + return; + } + + if ( key == K_PGDN) { + Con_PageDown(); + return; + } + + if ( key == K_MWHEELUP) { //----(SA) added some mousewheel functionality to the console + Con_PageUp(); + if(keys[K_CTRL].down) { // hold to accelerate scrolling + Con_PageUp(); + Con_PageUp(); + } + return; + } + + if ( key == K_MWHEELDOWN) { //----(SA) added some mousewheel functionality to the console + Con_PageDown(); + if(keys[K_CTRL].down) { // hold to accelerate scrolling + Con_PageDown(); + Con_PageDown(); + } + return; + } + + // ctrl-home = top of console + if ( key == K_HOME && keys[K_CTRL].down ) { + Con_Top(); + return; + } + + // ctrl-end = bottom of console + if ( key == K_END && keys[K_CTRL].down ) { + Con_Bottom(); + return; + } + + // pass to the normal editline routine + Field_KeyDownEvent( &g_consoleField, key ); +} + +//============================================================================ + + +/* +================ +Message_Key + +In game talk message +================ +*/ +void Message_Key( int key ) { + + char buffer[MAX_STRING_CHARS]; + + + if (key == K_ESCAPE) { + cls.keyCatchers &= ~KEYCATCH_MESSAGE; + Field_Clear( &chatField ); + return; + } + + if ( key == K_ENTER || key == K_KP_ENTER ) + { + if ( chatField.buffer[0] && cls.state == CA_ACTIVE ) { + if (chat_playerNum != -1 ) + + Com_sprintf( buffer, sizeof( buffer ), "tell %i \"%s\"\n", chat_playerNum, chatField.buffer ); + + else if (chat_team) + + Com_sprintf( buffer, sizeof( buffer ), "say_team \"%s\"\n", chatField.buffer ); + else + Com_sprintf( buffer, sizeof( buffer ), "say \"%s\"\n", chatField.buffer ); + + + + CL_AddReliableCommand( buffer ); + } + cls.keyCatchers &= ~KEYCATCH_MESSAGE; + Field_Clear( &chatField ); + return; + } + + Field_KeyDownEvent( &chatField, key ); +} + +//============================================================================ + + +qboolean Key_GetOverstrikeMode( void ) { + return key_overstrikeMode; +} + + +void Key_SetOverstrikeMode( qboolean state ) { + key_overstrikeMode = state; +} + + +/* +=================== +Key_IsDown +=================== +*/ +qboolean Key_IsDown( int keynum ) { + if ( keynum == -1 ) { + return qfalse; + } + + return keys[keynum].down; +} + + +/* +=================== +Key_StringToKeynum + +Returns a key number to be used to index keys[] by looking at +the given string. Single ascii characters return themselves, while +the K_* names are matched up. + +0x11 will be interpreted as raw hex, which will allow new controlers + +to be configured even if they don't have defined names. +=================== +*/ +int Key_StringToKeynum( char *str ) { + keyname_t *kn; + + if ( !str || !str[0] ) { + return -1; + } + if ( !str[1] ) { + return str[0]; + } + + // check for hex code + if ( str[0] == '0' && str[1] == 'x' && strlen( str ) == 4) { + int n1, n2; + + n1 = str[2]; + if ( n1 >= '0' && n1 <= '9' ) { + n1 -= '0'; + } else if ( n1 >= 'a' && n1 <= 'f' ) { + n1 = n1 - 'a' + 10; + } else { + n1 = 0; + } + + n2 = str[3]; + if ( n2 >= '0' && n2 <= '9' ) { + n2 -= '0'; + } else if ( n2 >= 'a' && n2 <= 'f' ) { + n2 = n2 - 'a' + 10; + } else { + n2 = 0; + } + + return n1 * 16 + n2; + } + + // scan for a text match + for ( kn=keynames ; kn->name ; kn++ ) { + if ( !Q_stricmp( str,kn->name ) ) + return kn->keynum; + } + + return -1; +} + +/* +=================== +Key_KeynumToString + +Returns a string (either a single ascii char, a K_* name, or a 0x11 hex string) for the +given keynum. +=================== +*/ +char *Key_KeynumToString( int keynum ) { + keyname_t *kn; + static char tinystr[5]; + int i, j; + + if ( keynum == -1 ) { + return ""; + } + + if ( keynum < 0 || keynum > 255 ) { + return ""; + } + + // check for printable ascii (don't use quote) + if ( keynum > 32 && keynum < 127 && keynum != '"' && keynum != ';' ) { + tinystr[0] = keynum; + tinystr[1] = 0; + return tinystr; + } + + // check for a key string + for ( kn=keynames ; kn->name ; kn++ ) { + if (keynum == kn->keynum) { + return kn->name; + } + } + + // make a hex string + i = keynum >> 4; + j = keynum & 15; + + tinystr[0] = '0'; + tinystr[1] = 'x'; + tinystr[2] = i > 9 ? i - 10 + 'a' : i + '0'; + tinystr[3] = j > 9 ? j - 10 + 'a' : j + '0'; + tinystr[4] = 0; + + return tinystr; +} + + +/* +=================== +Key_SetBinding +=================== +*/ +void Key_SetBinding( int keynum, const char *binding ) { + if ( keynum == -1 ) { + return; + } + + // free old bindings + if ( keys[ keynum ].binding ) { + Z_Free( keys[ keynum ].binding ); + } + + // allocate memory for new binding + keys[keynum].binding = CopyString( binding ); + + // consider this like modifying an archived cvar, so the + // file write will be triggered at the next oportunity + cvar_modifiedFlags |= CVAR_ARCHIVE; +} + + +/* +=================== +Key_GetBinding +=================== +*/ +char *Key_GetBinding( int keynum ) { + if ( keynum == -1 ) { + return ""; + } + + return keys[ keynum ].binding; +} + +/* +=================== +Key_GetKey +=================== +*/ + +int Key_GetKey(const char *binding) { + int i; + + if (binding) { + for (i=0 ; i<256 ; i++) { + if (keys[i].binding && Q_stricmp(binding, keys[i].binding) == 0) { + return i; + } + } + } + return -1; +} + +/* +=================== +Key_Unbind_f +=================== +*/ +void Key_Unbind_f (void) +{ + int b; + + if (Cmd_Argc() != 2) + { + Com_Printf ("unbind : remove commands from a key\n"); + return; + } + + b = Key_StringToKeynum (Cmd_Argv(1)); + if (b==-1) + { + Com_Printf ("\"%s\" isn't a valid key\n", Cmd_Argv(1)); + return; + } + + Key_SetBinding (b, ""); +} + +/* +=================== +Key_Unbindall_f +=================== +*/ +void Key_Unbindall_f (void) +{ + int i; + + for (i=0 ; i<256 ; i++) + if (keys[i].binding) + Key_SetBinding (i, ""); +} + + +/* +=================== +Key_Bind_f +=================== +*/ +void Key_Bind_f (void) +{ + int i, c, b; + char cmd[1024]; + + c = Cmd_Argc(); + + if (c < 2) + { + Com_Printf ("bind [command] : attach a command to a key\n"); + return; + } + b = Key_StringToKeynum (Cmd_Argv(1)); + if (b==-1) + { + Com_Printf ("\"%s\" isn't a valid key\n", Cmd_Argv(1)); + return; + } + + if (c == 2) + { + if (keys[b].binding) + Com_Printf ("\"%s\" = \"%s\"\n", Cmd_Argv(1), keys[b].binding ); + else + Com_Printf ("\"%s\" is not bound\n", Cmd_Argv(1) ); + return; + } + +// copy the rest of the command line + cmd[0] = 0; // start out with a null string + for (i=2 ; i< c ; i++) + { + strcat (cmd, Cmd_Argv(i)); + if (i != (c-1)) + strcat (cmd, " "); + } + + Key_SetBinding (b, cmd); +} + +/* +============ +Key_WriteBindings + +Writes lines containing "bind key value" +============ +*/ +void Key_WriteBindings( fileHandle_t f ) { + int i; + + FS_Printf (f, "unbindall\n" ); + + for (i=0 ; i<256 ; i++) { + if (keys[i].binding && keys[i].binding[0] ) { + FS_Printf (f, "bind %s \"%s\"\n", Key_KeynumToString(i), keys[i].binding); + + } + + } +} + + +/* +============ +Key_Bindlist_f + +============ +*/ +void Key_Bindlist_f( void ) { + int i; + + for ( i = 0 ; i < 256 ; i++ ) { + if ( keys[i].binding && keys[i].binding[0] ) { + Com_Printf( "%s \"%s\"\n", Key_KeynumToString(i), keys[i].binding ); + } + } +} + +/* +=================== +CL_InitKeyCommands +=================== +*/ +void CL_InitKeyCommands( void ) { + // register our functions + Cmd_AddCommand ("bind",Key_Bind_f); + Cmd_AddCommand ("unbind",Key_Unbind_f); + Cmd_AddCommand ("unbindall",Key_Unbindall_f); + Cmd_AddCommand ("bindlist",Key_Bindlist_f); +} + +/* +=================== +CL_AddKeyUpCommands +=================== +*/ +void CL_AddKeyUpCommands( int key, char *kb ) { + int i; + char button[1024], *buttonPtr; + char cmd[1024]; + qboolean keyevent; + + if ( !kb ) { + return; + } + keyevent = qfalse; + buttonPtr = button; + for ( i = 0; ; i++ ) { + if ( kb[i] == ';' || !kb[i] ) { + *buttonPtr = '\0'; + if ( button[0] == '+') { + // button commands add keynum and time as parms so that multiple + // sources can be discriminated and subframe corrected + Com_sprintf (cmd, sizeof(cmd), "-%s %i %i\n", button+1, key, time); + Cbuf_AddText (cmd); + keyevent = qtrue; + } else { + if (keyevent) { + // down-only command + Cbuf_AddText (button); + Cbuf_AddText ("\n"); + } + } + buttonPtr = button; + while ( (kb[i] <= ' ' || kb[i] == ';') && kb[i] != 0 ) { + i++; + } + } + *buttonPtr++ = kb[i]; + if ( !kb[i] ) { + break; + } + } +} + +/* +=================== +CL_KeyEvent + +Called by the system for both key up and key down events +=================== +*/ +void CL_KeyEvent (int key, qboolean down, unsigned time) { + char *kb; + char cmd[1024]; + + // update auto-repeat status and BUTTON_ANY status + keys[key].down = down; + + if (down) { + keys[key].repeats++; + if ( keys[key].repeats == 1) { + anykeydown++; + } + } else { + keys[key].repeats = 0; + anykeydown--; + if (anykeydown < 0) { + anykeydown = 0; + } + } + +#ifdef __linux__ + if (key == K_ENTER) + { + if (down) + { + if (keys[K_ALT].down) + { + Key_ClearStates(); + if (Cvar_VariableValue("r_fullscreen") == 0) + { + Com_Printf("Switching to fullscreen rendering\n"); + Cvar_Set("r_fullscreen", "1"); + } + else + { + Com_Printf("Switching to windowed rendering\n"); + Cvar_Set("r_fullscreen", "0"); + } + Cbuf_ExecuteText( EXEC_APPEND, "vid_restart\n"); + return; + } + } + } +#endif + + // console key is hardcoded, so the user can never unbind it + if (key == '`' || key == '~') { + if (!down) { + return; + } + Con_ToggleConsole_f (); + return; + } + + + // keys can still be used for bound actions + if ( down && ( key < 128 || key == K_MOUSE1 ) && ( clc.demoplaying || cls.state == CA_CINEMATIC ) && !cls.keyCatchers) { + + if (Cvar_VariableValue ("com_cameraMode") == 0) { + Cvar_Set ("nextdemo",""); + key = K_ESCAPE; + } + } + + + // escape is always handled special + if ( key == K_ESCAPE && down ) { + if ( cls.keyCatchers & KEYCATCH_MESSAGE ) { + // clear message mode + Message_Key( key ); + return; + } + + // escape always gets out of CGAME stuff + if (cls.keyCatchers & KEYCATCH_CGAME) { + cls.keyCatchers &= ~KEYCATCH_CGAME; + VM_Call (cgvm, CG_EVENT_HANDLING, CGAME_EVENT_NONE); + return; + } + + if ( !( cls.keyCatchers & KEYCATCH_UI ) ) { + if ( cls.state == CA_ACTIVE && !clc.demoplaying ) { + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_INGAME ); + } + else { + CL_Disconnect_f(); + S_StopAllSounds(); + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_MAIN ); + } + return; + } + + VM_Call( uivm, UI_KEY_EVENT, key, down ); + return; + } + + // + // key up events only perform actions if the game key binding is + // a button command (leading + sign). These will be processed even in + // console mode and menu mode, to keep the character from continuing + // an action started before a mode switch. + // + if (!down) { + kb = keys[key].binding; + + CL_AddKeyUpCommands( key, kb ); + + if ( cls.keyCatchers & KEYCATCH_UI && uivm ) { + VM_Call( uivm, UI_KEY_EVENT, key, down ); + } else if ( cls.keyCatchers & KEYCATCH_CGAME && cgvm ) { + VM_Call( cgvm, CG_KEY_EVENT, key, down ); + } + + return; + } + + + // distribute the key down event to the apropriate handler + if ( cls.keyCatchers & KEYCATCH_CONSOLE ) { + Console_Key( key ); + } else if ( cls.keyCatchers & KEYCATCH_UI ) { + if ( uivm ) { + VM_Call( uivm, UI_KEY_EVENT, key, down ); + } + } else if ( cls.keyCatchers & KEYCATCH_CGAME ) { + if ( cgvm ) { + VM_Call( cgvm, CG_KEY_EVENT, key, down ); + } + } else if ( cls.keyCatchers & KEYCATCH_MESSAGE ) { + Message_Key( key ); + } else if ( cls.state == CA_DISCONNECTED ) { + Console_Key( key ); + } else { + // send the bound action + kb = keys[key].binding; + if ( !kb ) { + if (key >= 200) { + Com_Printf ("%s is unbound, use controls menu to set.\n" + , Key_KeynumToString( key ) ); + } + } else if (kb[0] == '+') { + int i; + char button[1024], *buttonPtr; + buttonPtr = button; + for ( i = 0; ; i++ ) { + if ( kb[i] == ';' || !kb[i] ) { + *buttonPtr = '\0'; + if ( button[0] == '+') { + // button commands add keynum and time as parms so that multiple + // sources can be discriminated and subframe corrected + Com_sprintf (cmd, sizeof(cmd), "%s %i %i\n", button, key, time); + Cbuf_AddText (cmd); + } else { + // down-only command + Cbuf_AddText (button); + Cbuf_AddText ("\n"); + } + buttonPtr = button; + while ( (kb[i] <= ' ' || kb[i] == ';') && kb[i] != 0 ) { + i++; + } + } + *buttonPtr++ = kb[i]; + if ( !kb[i] ) { + break; + } + } + } else { + // down-only command + Cbuf_AddText (kb); + Cbuf_AddText ("\n"); + } + } +} + + +/* +=================== +CL_CharEvent + +Normal keyboard characters, already shifted / capslocked / etc +=================== +*/ +void CL_CharEvent( int key ) { + // the console key should never be used as a char + if ( key == '`' || key == '~' ) { + return; + } + + // distribute the key down event to the apropriate handler + if ( cls.keyCatchers & KEYCATCH_CONSOLE ) + { + Field_CharEvent( &g_consoleField, key ); + } + else if ( cls.keyCatchers & KEYCATCH_UI ) + { + VM_Call( uivm, UI_KEY_EVENT, key | K_CHAR_FLAG, qtrue ); + } + else if ( cls.keyCatchers & KEYCATCH_MESSAGE ) + { + Field_CharEvent( &chatField, key ); + } + else if ( cls.state == CA_DISCONNECTED ) + { + Field_CharEvent( &g_consoleField, key ); + } +} + + +/* +=================== +Key_ClearStates +=================== +*/ +void Key_ClearStates (void) +{ + int i; + + anykeydown = qfalse; + + for ( i=0 ; i < MAX_KEYS ; i++ ) { + if ( keys[i].down ) { + CL_KeyEvent( i, qfalse, 0 ); + + } + keys[i].down = 0; + keys[i].repeats = 0; + } +} + diff --git a/code/client/cl_main.c b/code/client/cl_main.c index 89d3b48..61dbcc7 100755 --- a/code/client/cl_main.c +++ b/code/client/cl_main.c @@ -1,3324 +1,3324 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// cl_main.c -- client main loop - -#include "client.h" -#include - -cvar_t *cl_nodelta; -cvar_t *cl_debugMove; - -cvar_t *cl_noprint; -cvar_t *cl_motd; - -cvar_t *rcon_client_password; -cvar_t *rconAddress; - -cvar_t *cl_timeout; -cvar_t *cl_maxpackets; -cvar_t *cl_packetdup; -cvar_t *cl_timeNudge; -cvar_t *cl_showTimeDelta; -cvar_t *cl_freezeDemo; - -cvar_t *cl_shownet; -cvar_t *cl_showSend; -cvar_t *cl_timedemo; -cvar_t *cl_avidemo; -cvar_t *cl_forceavidemo; - -cvar_t *cl_freelook; -cvar_t *cl_sensitivity; - -cvar_t *cl_mouseAccel; -cvar_t *cl_showMouseRate; - -cvar_t *m_pitch; -cvar_t *m_yaw; -cvar_t *m_forward; -cvar_t *m_side; -cvar_t *m_filter; - -cvar_t *cl_activeAction; - -cvar_t *cl_motdString; - -cvar_t *cl_allowDownload; -cvar_t *cl_conXOffset; -cvar_t *cl_inGameVideo; - -cvar_t *cl_serverStatusResendTime; -cvar_t *cl_trn; - -clientActive_t cl; -clientConnection_t clc; -clientStatic_t cls; -vm_t *cgvm; - -// Structure containing functions exported from refresh DLL -refexport_t re; - -ping_t cl_pinglist[MAX_PINGREQUESTS]; - -typedef struct serverStatus_s -{ - char string[BIG_INFO_STRING]; - netadr_t address; - int time, startTime; - qboolean pending; - qboolean print; - qboolean retrieved; -} serverStatus_t; - -serverStatus_t cl_serverStatusList[MAX_SERVERSTATUSREQUESTS]; -int serverStatusCount; - -#if defined __USEA3D && defined __A3D_GEOM - void hA3Dg_ExportRenderGeom (refexport_t *incoming_re); -#endif - -extern void SV_BotFrame( int time ); -void CL_CheckForResend( void ); -void CL_ShowIP_f(void); -void CL_ServerStatus_f(void); -void CL_ServerStatusResponse( netadr_t from, msg_t *msg ); - -/* -=============== -CL_CDDialog - -Called by Com_Error when a cd is needed -=============== -*/ -void CL_CDDialog( void ) { - cls.cddialog = qtrue; // start it next frame -} - - -/* -======================================================================= - -CLIENT RELIABLE COMMAND COMMUNICATION - -======================================================================= -*/ - -/* -====================== -CL_AddReliableCommand - -The given command will be transmitted to the server, and is gauranteed to -not have future usercmd_t executed before it is executed -====================== -*/ -void CL_AddReliableCommand( const char *cmd ) { - int index; - - // if we would be losing an old command that hasn't been acknowledged, - // we must drop the connection - if ( clc.reliableSequence - clc.reliableAcknowledge > MAX_RELIABLE_COMMANDS ) { - Com_Error( ERR_DROP, "Client command overflow" ); - } - clc.reliableSequence++; - index = clc.reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 ); - Q_strncpyz( clc.reliableCommands[ index ], cmd, sizeof( clc.reliableCommands[ index ] ) ); -} - -/* -====================== -CL_ChangeReliableCommand -====================== -*/ -void CL_ChangeReliableCommand( void ) { - int r, index, l; - - r = clc.reliableSequence - (random() * 5); - index = clc.reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 ); - l = strlen(clc.reliableCommands[ index ]); - if ( l >= MAX_STRING_CHARS - 1 ) { - l = MAX_STRING_CHARS - 2; - } - clc.reliableCommands[ index ][ l ] = '\n'; - clc.reliableCommands[ index ][ l+1 ] = '\0'; -} - -/* -======================================================================= - -CLIENT SIDE DEMO RECORDING - -======================================================================= -*/ - -/* -==================== -CL_WriteDemoMessage - -Dumps the current net message, prefixed by the length -==================== -*/ -void CL_WriteDemoMessage ( msg_t *msg, int headerBytes ) { - int len, swlen; - - // write the packet sequence - len = clc.serverMessageSequence; - swlen = LittleLong( len ); - FS_Write (&swlen, 4, clc.demofile); - - // skip the packet sequencing information - len = msg->cursize - headerBytes; - swlen = LittleLong(len); - FS_Write (&swlen, 4, clc.demofile); - FS_Write ( msg->data + headerBytes, len, clc.demofile ); -} - - -/* -==================== -CL_StopRecording_f - -stop recording a demo -==================== -*/ -void CL_StopRecord_f( void ) { - int len; - - if ( !clc.demorecording ) { - Com_Printf ("Not recording a demo.\n"); - return; - } - - // finish up - len = -1; - FS_Write (&len, 4, clc.demofile); - FS_Write (&len, 4, clc.demofile); - FS_FCloseFile (clc.demofile); - clc.demofile = 0; - clc.demorecording = qfalse; - clc.spDemoRecording = qfalse; - Com_Printf ("Stopped demo.\n"); -} - -/* -================== -CL_DemoFilename -================== -*/ -void CL_DemoFilename( int number, char *fileName ) { - int a,b,c,d; - - if ( number < 0 || number > 9999 ) { - Com_sprintf( fileName, MAX_OSPATH, "demo9999.tga" ); - return; - } - - a = number / 1000; - number -= a*1000; - b = number / 100; - number -= b*100; - c = number / 10; - number -= c*10; - d = number; - - Com_sprintf( fileName, MAX_OSPATH, "demo%i%i%i%i" - , a, b, c, d ); -} - -/* -==================== -CL_Record_f - -record - -Begins recording a demo from the current position -==================== -*/ -static char demoName[MAX_QPATH]; // compiler bug workaround -void CL_Record_f( void ) { - char name[MAX_OSPATH]; - byte bufData[MAX_MSGLEN]; - msg_t buf; - int i; - int len; - entityState_t *ent; - entityState_t nullstate; - char *s; - - if ( Cmd_Argc() > 2 ) { - Com_Printf ("record \n"); - return; - } - - if ( clc.demorecording ) { - if (!clc.spDemoRecording) { - Com_Printf ("Already recording.\n"); - } - return; - } - - if ( cls.state != CA_ACTIVE ) { - Com_Printf ("You must be in a level to record.\n"); - return; - } - - // sync 0 doesn't prevent recording, so not forcing it off .. everyone does g_sync 1 ; record ; g_sync 0 .. - if ( !Cvar_VariableValue( "g_synchronousClients" ) ) { - Com_Printf (S_COLOR_YELLOW "WARNING: You should set 'g_synchronousClients 1' for smoother demo recording\n"); - } - - if ( Cmd_Argc() == 2 ) { - s = Cmd_Argv(1); - Q_strncpyz( demoName, s, sizeof( demoName ) ); - Com_sprintf (name, sizeof(name), "demos/%s.dm_%d", demoName, PROTOCOL_VERSION ); - } else { - int number; - - // scan for a free demo name - for ( number = 0 ; number <= 9999 ; number++ ) { - CL_DemoFilename( number, demoName ); - Com_sprintf (name, sizeof(name), "demos/%s.dm_%d", demoName, PROTOCOL_VERSION ); - - len = FS_ReadFile( name, NULL ); - if ( len <= 0 ) { - break; // file doesn't exist - } - } - } - - // open the demo file - - Com_Printf ("recording to %s.\n", name); - clc.demofile = FS_FOpenFileWrite( name ); - if ( !clc.demofile ) { - Com_Printf ("ERROR: couldn't open.\n"); - return; - } - clc.demorecording = qtrue; - if (Cvar_VariableValue("ui_recordSPDemo")) { - clc.spDemoRecording = qtrue; - } else { - clc.spDemoRecording = qfalse; - } - - - Q_strncpyz( clc.demoName, demoName, sizeof( clc.demoName ) ); - - // don't start saving messages until a non-delta compressed message is received - clc.demowaiting = qtrue; - - // write out the gamestate message - MSG_Init (&buf, bufData, sizeof(bufData)); - MSG_Bitstream(&buf); - - // NOTE, MRE: all server->client messages now acknowledge - MSG_WriteLong( &buf, clc.reliableSequence ); - - MSG_WriteByte (&buf, svc_gamestate); - MSG_WriteLong (&buf, clc.serverCommandSequence ); - - // configstrings - for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { - if ( !cl.gameState.stringOffsets[i] ) { - continue; - } - s = cl.gameState.stringData + cl.gameState.stringOffsets[i]; - MSG_WriteByte (&buf, svc_configstring); - MSG_WriteShort (&buf, i); - MSG_WriteBigString (&buf, s); - } - - // baselines - Com_Memset (&nullstate, 0, sizeof(nullstate)); - for ( i = 0; i < MAX_GENTITIES ; i++ ) { - ent = &cl.entityBaselines[i]; - if ( !ent->number ) { - continue; - } - MSG_WriteByte (&buf, svc_baseline); - MSG_WriteDeltaEntity (&buf, &nullstate, ent, qtrue ); - } - - MSG_WriteByte( &buf, svc_EOF ); - - // finished writing the gamestate stuff - - // write the client num - MSG_WriteLong(&buf, clc.clientNum); - // write the checksum feed - MSG_WriteLong(&buf, clc.checksumFeed); - - // finished writing the client packet - MSG_WriteByte( &buf, svc_EOF ); - - // write it to the demo file - len = LittleLong( clc.serverMessageSequence - 1 ); - FS_Write (&len, 4, clc.demofile); - - len = LittleLong (buf.cursize); - FS_Write (&len, 4, clc.demofile); - FS_Write (buf.data, buf.cursize, clc.demofile); - - // the rest of the demo file will be copied from net messages -} - -/* -======================================================================= - -CLIENT SIDE DEMO PLAYBACK - -======================================================================= -*/ - -/* -================= -CL_DemoCompleted -================= -*/ -void CL_DemoCompleted( void ) { - if (cl_timedemo && cl_timedemo->integer) { - int time; - - time = Sys_Milliseconds() - clc.timeDemoStart; - if ( time > 0 ) { - Com_Printf ("%i frames, %3.1f seconds: %3.1f fps\n", clc.timeDemoFrames, - time/1000.0, clc.timeDemoFrames*1000.0 / time); - } - } - - CL_Disconnect( qtrue ); - CL_NextDemo(); -} - -/* -================= -CL_ReadDemoMessage -================= -*/ -void CL_ReadDemoMessage( void ) { - int r; - msg_t buf; - byte bufData[ MAX_MSGLEN ]; - int s; - - if ( !clc.demofile ) { - CL_DemoCompleted (); - return; - } - - // get the sequence number - r = FS_Read( &s, 4, clc.demofile); - if ( r != 4 ) { - CL_DemoCompleted (); - return; - } - clc.serverMessageSequence = LittleLong( s ); - - // init the message - MSG_Init( &buf, bufData, sizeof( bufData ) ); - - // get the length - r = FS_Read (&buf.cursize, 4, clc.demofile); - if ( r != 4 ) { - CL_DemoCompleted (); - return; - } - buf.cursize = LittleLong( buf.cursize ); - if ( buf.cursize == -1 ) { - CL_DemoCompleted (); - return; - } - if ( buf.cursize > buf.maxsize ) { - Com_Error (ERR_DROP, "CL_ReadDemoMessage: demoMsglen > MAX_MSGLEN"); - } - r = FS_Read( buf.data, buf.cursize, clc.demofile ); - if ( r != buf.cursize ) { - Com_Printf( "Demo file was truncated.\n"); - CL_DemoCompleted (); - return; - } - - clc.lastPacketTime = cls.realtime; - buf.readcount = 0; - CL_ParseServerMessage( &buf ); -} - -/* -==================== -CL_WalkDemoExt -==================== -*/ -static void CL_WalkDemoExt(char *arg, char *name, int *demofile) -{ - int i = 0; - *demofile = 0; - while(demo_protocols[i]) - { - Com_sprintf (name, MAX_OSPATH, "demos/%s.dm_%d", arg, demo_protocols[i]); - FS_FOpenFileRead( name, demofile, qtrue ); - if (*demofile) - { - Com_Printf("Demo file: %s\n", name); - break; - } - else - Com_Printf("Not found: %s\n", name); - i++; - } -} - -/* -==================== -CL_PlayDemo_f - -demo - -==================== -*/ -void CL_PlayDemo_f( void ) { - char name[MAX_OSPATH]; - char *arg, *ext_test; - int protocol, i; - char retry[MAX_OSPATH]; - - if (Cmd_Argc() != 2) { - Com_Printf ("playdemo \n"); - return; - } - - // make sure a local server is killed - Cvar_Set( "sv_killserver", "1" ); - - CL_Disconnect( qtrue ); - - // open the demo file - arg = Cmd_Argv(1); - - // check for an extension .dm_?? (?? is protocol) - ext_test = arg + strlen(arg) - 6; - if ((strlen(arg) > 6) && (ext_test[0] == '.') && ((ext_test[1] == 'd') || (ext_test[1] == 'D')) && ((ext_test[2] == 'm') || (ext_test[2] == 'M')) && (ext_test[3] == '_')) - { - protocol = atoi(ext_test+4); - i=0; - while(demo_protocols[i]) - { - if (demo_protocols[i] == protocol) - break; - i++; - } - if (demo_protocols[i]) - { - Com_sprintf (name, sizeof(name), "demos/%s", arg); - FS_FOpenFileRead( name, &clc.demofile, qtrue ); - } else { - Com_Printf("Protocol %d not supported for demos\n", protocol); - Q_strncpyz(retry, arg, sizeof(retry)); - retry[strlen(retry)-6] = 0; - CL_WalkDemoExt( retry, name, &clc.demofile ); - } - } else { - CL_WalkDemoExt( arg, name, &clc.demofile ); - } - - if (!clc.demofile) { - Com_Error( ERR_DROP, "couldn't open %s", name); - return; - } - Q_strncpyz( clc.demoName, Cmd_Argv(1), sizeof( clc.demoName ) ); - - Con_Close(); - - cls.state = CA_CONNECTED; - clc.demoplaying = qtrue; - Q_strncpyz( cls.servername, Cmd_Argv(1), sizeof( cls.servername ) ); - - // read demo messages until connected - while ( cls.state >= CA_CONNECTED && cls.state < CA_PRIMED ) { - CL_ReadDemoMessage(); - } - // don't get the first snapshot this frame, to prevent the long - // time from the gamestate load from messing causing a time skip - clc.firstDemoFrameSkipped = qfalse; -} - - -/* -==================== -CL_StartDemoLoop - -Closing the main menu will restart the demo loop -==================== -*/ -void CL_StartDemoLoop( void ) { - // start the demo loop again - Cbuf_AddText ("d1\n"); - cls.keyCatchers = 0; -} - -/* -================== -CL_NextDemo - -Called when a demo or cinematic finishes -If the "nextdemo" cvar is set, that command will be issued -================== -*/ -void CL_NextDemo( void ) { - char v[MAX_STRING_CHARS]; - - Q_strncpyz( v, Cvar_VariableString ("nextdemo"), sizeof(v) ); - v[MAX_STRING_CHARS-1] = 0; - Com_DPrintf("CL_NextDemo: %s\n", v ); - if (!v[0]) { - return; - } - - Cvar_Set ("nextdemo",""); - Cbuf_AddText (v); - Cbuf_AddText ("\n"); - Cbuf_Execute(); -} - - -//====================================================================== - -/* -===================== -CL_ShutdownAll -===================== -*/ -void CL_ShutdownAll(void) { - - // clear sounds - S_DisableSounds(); - // shutdown CGame - CL_ShutdownCGame(); - // shutdown UI - CL_ShutdownUI(); - - // shutdown the renderer - if ( re.Shutdown ) { - re.Shutdown( qfalse ); // don't destroy window or context - } - - cls.uiStarted = qfalse; - cls.cgameStarted = qfalse; - cls.rendererStarted = qfalse; - cls.soundRegistered = qfalse; -} - -/* -================= -CL_FlushMemory - -Called by CL_MapLoading, CL_Connect_f, CL_PlayDemo_f, and CL_ParseGamestate the only -ways a client gets into a game -Also called by Com_Error -================= -*/ -void CL_FlushMemory( void ) { - - // shutdown all the client stuff - CL_ShutdownAll(); - - // if not running a server clear the whole hunk - if ( !com_sv_running->integer ) { - // clear the whole hunk - Hunk_Clear(); - // clear collision map data - CM_ClearMap(); - } - else { - // clear all the client data on the hunk - Hunk_ClearToMark(); - } - - CL_StartHunkUsers(); -} - -/* -===================== -CL_MapLoading - -A local server is starting to load a map, so update the -screen to let the user know about it, then dump all client -memory on the hunk from cgame, ui, and renderer -===================== -*/ -void CL_MapLoading( void ) { - if ( !com_cl_running->integer ) { - return; - } - - Con_Close(); - cls.keyCatchers = 0; - - // if we are already connected to the local host, stay connected - if ( cls.state >= CA_CONNECTED && !Q_stricmp( cls.servername, "localhost" ) ) { - cls.state = CA_CONNECTED; // so the connect screen is drawn - Com_Memset( cls.updateInfoString, 0, sizeof( cls.updateInfoString ) ); - Com_Memset( clc.serverMessage, 0, sizeof( clc.serverMessage ) ); - Com_Memset( &cl.gameState, 0, sizeof( cl.gameState ) ); - clc.lastPacketSentTime = -9999; - SCR_UpdateScreen(); - } else { - // clear nextmap so the cinematic shutdown doesn't execute it - Cvar_Set( "nextmap", "" ); - CL_Disconnect( qtrue ); - Q_strncpyz( cls.servername, "localhost", sizeof(cls.servername) ); - cls.state = CA_CHALLENGING; // so the connect screen is drawn - cls.keyCatchers = 0; - SCR_UpdateScreen(); - clc.connectTime = -RETRANSMIT_TIMEOUT; - NET_StringToAdr( cls.servername, &clc.serverAddress); - // we don't need a challenge on the localhost - - CL_CheckForResend(); - } -} - -/* -===================== -CL_ClearState - -Called before parsing a gamestate -===================== -*/ -void CL_ClearState (void) { - -// S_StopAllSounds(); - - Com_Memset( &cl, 0, sizeof( cl ) ); -} - - -/* -===================== -CL_Disconnect - -Called when a connection, demo, or cinematic is being terminated. -Goes from a connected state to either a menu state or a console state -Sends a disconnect message to the server -This is also called on Com_Error and Com_Quit, so it shouldn't cause any errors -===================== -*/ -void CL_Disconnect( qboolean showMainMenu ) { - if ( !com_cl_running || !com_cl_running->integer ) { - return; - } - - // shutting down the client so enter full screen ui mode - Cvar_Set("r_uiFullScreen", "1"); - - if ( clc.demorecording ) { - CL_StopRecord_f (); - } - - if (clc.download) { - FS_FCloseFile( clc.download ); - clc.download = 0; - } - *clc.downloadTempName = *clc.downloadName = 0; - Cvar_Set( "cl_downloadName", "" ); - - if ( clc.demofile ) { - FS_FCloseFile( clc.demofile ); - clc.demofile = 0; - } - - if ( uivm && showMainMenu ) { - VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_NONE ); - } - - SCR_StopCinematic (); - S_ClearSoundBuffer(); - - // send a disconnect message to the server - // send it a few times in case one is dropped - if ( cls.state >= CA_CONNECTED ) { - CL_AddReliableCommand( "disconnect" ); - CL_WritePacket(); - CL_WritePacket(); - CL_WritePacket(); - } - - CL_ClearState (); - - // wipe the client connection - Com_Memset( &clc, 0, sizeof( clc ) ); - - cls.state = CA_DISCONNECTED; - - // allow cheats locally - Cvar_Set( "sv_cheats", "1" ); - - // not connected to a pure server anymore - cl_connectedToPureServer = qfalse; -} - - -/* -=================== -CL_ForwardCommandToServer - -adds the current command line as a clientCommand -things like godmode, noclip, etc, are commands directed to the server, -so when they are typed in at the console, they will need to be forwarded. -=================== -*/ -void CL_ForwardCommandToServer( const char *string ) { - char *cmd; - - cmd = Cmd_Argv(0); - - // ignore key up commands - if ( cmd[0] == '-' ) { - return; - } - - if ( clc.demoplaying || cls.state < CA_CONNECTED || cmd[0] == '+' ) { - Com_Printf ("Unknown command \"%s\"\n", cmd); - return; - } - - if ( Cmd_Argc() > 1 ) { - CL_AddReliableCommand( string ); - } else { - CL_AddReliableCommand( cmd ); - } -} - -/* -=================== -CL_RequestMotd - -=================== -*/ -void CL_RequestMotd( void ) { - char info[MAX_INFO_STRING]; - - if ( !cl_motd->integer ) { - return; - } - Com_Printf( "Resolving %s\n", UPDATE_SERVER_NAME ); - if ( !NET_StringToAdr( UPDATE_SERVER_NAME, &cls.updateServer ) ) { - Com_Printf( "Couldn't resolve address\n" ); - return; - } - cls.updateServer.port = BigShort( PORT_UPDATE ); - Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", UPDATE_SERVER_NAME, - cls.updateServer.ip[0], cls.updateServer.ip[1], - cls.updateServer.ip[2], cls.updateServer.ip[3], - BigShort( cls.updateServer.port ) ); - - info[0] = 0; - // NOTE TTimo xoring against Com_Milliseconds, otherwise we may not have a true randomization - // only srand I could catch before here is tr_noise.c l:26 srand(1001) - // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=382 - // NOTE: the Com_Milliseconds xoring only affects the lower 16-bit word, - // but I decided it was enough randomization - Com_sprintf( cls.updateChallenge, sizeof( cls.updateChallenge ), "%i", ((rand() << 16) ^ rand()) ^ Com_Milliseconds()); - - Info_SetValueForKey( info, "challenge", cls.updateChallenge ); - Info_SetValueForKey( info, "renderer", cls.glconfig.renderer_string ); - Info_SetValueForKey( info, "version", com_version->string ); - - NET_OutOfBandPrint( NS_CLIENT, cls.updateServer, "getmotd \"%s\"\n", info ); -} - -/* -=================== -CL_RequestAuthorization - -Authorization server protocol ------------------------------ - -All commands are text in Q3 out of band packets (leading 0xff 0xff 0xff 0xff). - -Whenever the client tries to get a challenge from the server it wants to -connect to, it also blindly fires off a packet to the authorize server: - -getKeyAuthorize - -cdkey may be "demo" - - -#OLD The authorize server returns a: -#OLD -#OLD keyAthorize -#OLD -#OLD A client will be accepted if the cdkey is valid and it has not been used by any other IP -#OLD address in the last 15 minutes. - - -The server sends a: - -getIpAuthorize - -The authorize server returns a: - -ipAuthorize - -A client will be accepted if a valid cdkey was sent by that ip (only) in the last 15 minutes. -If no response is received from the authorize server after two tries, the client will be let -in anyway. -=================== -*/ -void CL_RequestAuthorization( void ) { - char nums[64]; - int i, j, l; - cvar_t *fs; - - if ( !cls.authorizeServer.port ) { - Com_Printf( "Resolving %s\n", AUTHORIZE_SERVER_NAME ); - if ( !NET_StringToAdr( AUTHORIZE_SERVER_NAME, &cls.authorizeServer ) ) { - Com_Printf( "Couldn't resolve address\n" ); - return; - } - - cls.authorizeServer.port = BigShort( PORT_AUTHORIZE ); - Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", AUTHORIZE_SERVER_NAME, - cls.authorizeServer.ip[0], cls.authorizeServer.ip[1], - cls.authorizeServer.ip[2], cls.authorizeServer.ip[3], - BigShort( cls.authorizeServer.port ) ); - } - if ( cls.authorizeServer.type == NA_BAD ) { - return; - } - - if ( Cvar_VariableValue( "fs_restrict" ) ) { - Q_strncpyz( nums, "demota", sizeof( nums ) ); - } else { - // only grab the alphanumeric values from the cdkey, to avoid any dashes or spaces - j = 0; - l = strlen( cl_cdkey ); - if ( l > 32 ) { - l = 32; - } - for ( i = 0 ; i < l ; i++ ) { - if ( ( cl_cdkey[i] >= '0' && cl_cdkey[i] <= '9' ) - || ( cl_cdkey[i] >= 'a' && cl_cdkey[i] <= 'z' ) - || ( cl_cdkey[i] >= 'A' && cl_cdkey[i] <= 'Z' ) - ) { - nums[j] = cl_cdkey[i]; - j++; - } - } - nums[j] = 0; - } - - fs = Cvar_Get ("cl_anonymous", "0", CVAR_INIT|CVAR_SYSTEMINFO ); - - NET_OutOfBandPrint(NS_CLIENT, cls.authorizeServer, va("getKeyAuthorize %i %s", fs->integer, nums) ); -} - -/* -====================================================================== - -CONSOLE COMMANDS - -====================================================================== -*/ - -/* -================== -CL_ForwardToServer_f -================== -*/ -void CL_ForwardToServer_f( void ) { - if ( cls.state != CA_ACTIVE || clc.demoplaying ) { - Com_Printf ("Not connected to a server.\n"); - return; - } - - // don't forward the first argument - if ( Cmd_Argc() > 1 ) { - CL_AddReliableCommand( Cmd_Args() ); - } -} - -/* -================== -CL_Setenv_f - -Mostly for controlling voodoo environment variables -================== -*/ -void CL_Setenv_f( void ) { - int argc = Cmd_Argc(); - - if ( argc > 2 ) { - char buffer[1024]; - int i; - - strcpy( buffer, Cmd_Argv(1) ); - strcat( buffer, "=" ); - - for ( i = 2; i < argc; i++ ) { - strcat( buffer, Cmd_Argv( i ) ); - strcat( buffer, " " ); - } - - putenv( buffer ); - } else if ( argc == 2 ) { - char *env = getenv( Cmd_Argv(1) ); - - if ( env ) { - Com_Printf( "%s=%s\n", Cmd_Argv(1), env ); - } else { - Com_Printf( "%s undefined\n", Cmd_Argv(1), env ); - } - } -} - - -/* -================== -CL_Disconnect_f -================== -*/ -void CL_Disconnect_f( void ) { - SCR_StopCinematic(); - Cvar_Set("ui_singlePlayerActive", "0"); - if ( cls.state != CA_DISCONNECTED && cls.state != CA_CINEMATIC ) { - Com_Error (ERR_DISCONNECT, "Disconnected from server"); - } -} - - -/* -================ -CL_Reconnect_f - -================ -*/ -void CL_Reconnect_f( void ) { - if ( !strlen( cls.servername ) || !strcmp( cls.servername, "localhost" ) ) { - Com_Printf( "Can't reconnect to localhost.\n" ); - return; - } - Cvar_Set("ui_singlePlayerActive", "0"); - Cbuf_AddText( va("connect %s\n", cls.servername ) ); -} - -/* -================ -CL_Connect_f - -================ -*/ -void CL_Connect_f( void ) { - char *server; - - if ( Cmd_Argc() != 2 ) { - Com_Printf( "usage: connect [server]\n"); - return; - } - - Cvar_Set("ui_singlePlayerActive", "0"); - - // fire a message off to the motd server - CL_RequestMotd(); - - // clear any previous "server full" type messages - clc.serverMessage[0] = 0; - - server = Cmd_Argv (1); - - if ( com_sv_running->integer && !strcmp( server, "localhost" ) ) { - // if running a local server, kill it - SV_Shutdown( "Server quit\n" ); - } - - // make sure a local server is killed - Cvar_Set( "sv_killserver", "1" ); - SV_Frame( 0 ); - - CL_Disconnect( qtrue ); - Con_Close(); - - /* MrE: 2000-09-13: now called in CL_DownloadsComplete - CL_FlushMemory( ); - */ - - Q_strncpyz( cls.servername, server, sizeof(cls.servername) ); - - if (!NET_StringToAdr( cls.servername, &clc.serverAddress) ) { - Com_Printf ("Bad server address\n"); - cls.state = CA_DISCONNECTED; - return; - } - if (clc.serverAddress.port == 0) { - clc.serverAddress.port = BigShort( PORT_SERVER ); - } - Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", cls.servername, - clc.serverAddress.ip[0], clc.serverAddress.ip[1], - clc.serverAddress.ip[2], clc.serverAddress.ip[3], - BigShort( clc.serverAddress.port ) ); - - // if we aren't playing on a lan, we need to authenticate - // with the cd key - if ( NET_IsLocalAddress( clc.serverAddress ) ) { - cls.state = CA_CHALLENGING; - } else { - cls.state = CA_CONNECTING; - } - - cls.keyCatchers = 0; - clc.connectTime = -99999; // CL_CheckForResend() will fire immediately - clc.connectPacketCount = 0; - - // server connection string - Cvar_Set( "cl_currentServerAddress", server ); -} - - -/* -===================== -CL_Rcon_f - - Send the rest of the command line over as - an unconnected command. -===================== -*/ -void CL_Rcon_f( void ) { - char message[1024]; - netadr_t to; - - if ( !rcon_client_password->string ) { - Com_Printf ("You must set 'rconpassword' before\n" - "issuing an rcon command.\n"); - return; - } - - message[0] = -1; - message[1] = -1; - message[2] = -1; - message[3] = -1; - message[4] = 0; - - strcat (message, "rcon "); - - strcat (message, rcon_client_password->string); - strcat (message, " "); - - // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=543 - strcat (message, Cmd_Cmd()+5); - - if ( cls.state >= CA_CONNECTED ) { - to = clc.netchan.remoteAddress; - } else { - if (!strlen(rconAddress->string)) { - Com_Printf ("You must either be connected,\n" - "or set the 'rconAddress' cvar\n" - "to issue rcon commands\n"); - - return; - } - NET_StringToAdr (rconAddress->string, &to); - if (to.port == 0) { - to.port = BigShort (PORT_SERVER); - } - } - - NET_SendPacket (NS_CLIENT, strlen(message)+1, message, to); -} - -/* -================= -CL_SendPureChecksums -================= -*/ -void CL_SendPureChecksums( void ) { - const char *pChecksums; - char cMsg[MAX_INFO_VALUE]; - int i; - - // if we are pure we need to send back a command with our referenced pk3 checksums - pChecksums = FS_ReferencedPakPureChecksums(); - - // "cp" - // "Yf" - Com_sprintf(cMsg, sizeof(cMsg), "Yf "); - Q_strcat(cMsg, sizeof(cMsg), va("%d ", cl.serverId) ); - Q_strcat(cMsg, sizeof(cMsg), pChecksums); - for (i = 0; i < 2; i++) { - cMsg[i] += 10; - } - CL_AddReliableCommand( cMsg ); -} - -/* -================= -CL_ResetPureClientAtServer -================= -*/ -void CL_ResetPureClientAtServer( void ) { - CL_AddReliableCommand( va("vdr") ); -} - -/* -================= -CL_Vid_Restart_f - -Restart the video subsystem - -we also have to reload the UI and CGame because the renderer -doesn't know what graphics to reload -================= -*/ -void CL_Vid_Restart_f( void ) { - - // don't let them loop during the restart - S_StopAllSounds(); - // shutdown the UI - CL_ShutdownUI(); - // shutdown the CGame - CL_ShutdownCGame(); - // shutdown the renderer and clear the renderer interface - CL_ShutdownRef(); - // client is no longer pure untill new checksums are sent - CL_ResetPureClientAtServer(); - // clear pak references - FS_ClearPakReferences( FS_UI_REF | FS_CGAME_REF ); - // reinitialize the filesystem if the game directory or checksum has changed - FS_ConditionalRestart( clc.checksumFeed ); - - cls.rendererStarted = qfalse; - cls.uiStarted = qfalse; - cls.cgameStarted = qfalse; - cls.soundRegistered = qfalse; - - // unpause so the cgame definately gets a snapshot and renders a frame - Cvar_Set( "cl_paused", "0" ); - - // if not running a server clear the whole hunk - if ( !com_sv_running->integer ) { - // clear the whole hunk - Hunk_Clear(); - } - else { - // clear all the client data on the hunk - Hunk_ClearToMark(); - } - - // initialize the renderer interface - CL_InitRef(); - - // startup all the client stuff - CL_StartHunkUsers(); - - // start the cgame if connected - if ( cls.state > CA_CONNECTED && cls.state != CA_CINEMATIC ) { - cls.cgameStarted = qtrue; - CL_InitCGame(); - // send pure checksums - CL_SendPureChecksums(); - } -} - -/* -================= -CL_Snd_Restart_f - -Restart the sound subsystem -The cgame and game must also be forced to restart because -handles will be invalid -================= -*/ -void CL_Snd_Restart_f( void ) { - S_Shutdown(); - S_Init(); - - CL_Vid_Restart_f(); -} - - -/* -================== -CL_PK3List_f -================== -*/ -void CL_OpenedPK3List_f( void ) { - Com_Printf("Opened PK3 Names: %s\n", FS_LoadedPakNames()); -} - -/* -================== -CL_PureList_f -================== -*/ -void CL_ReferencedPK3List_f( void ) { - Com_Printf("Referenced PK3 Names: %s\n", FS_ReferencedPakNames()); -} - -/* -================== -CL_Configstrings_f -================== -*/ -void CL_Configstrings_f( void ) { - int i; - int ofs; - - if ( cls.state != CA_ACTIVE ) { - Com_Printf( "Not connected to a server.\n"); - return; - } - - for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { - ofs = cl.gameState.stringOffsets[ i ]; - if ( !ofs ) { - continue; - } - Com_Printf( "%4i: %s\n", i, cl.gameState.stringData + ofs ); - } -} - -/* -============== -CL_Clientinfo_f -============== -*/ -void CL_Clientinfo_f( void ) { - Com_Printf( "--------- Client Information ---------\n" ); - Com_Printf( "state: %i\n", cls.state ); - Com_Printf( "Server: %s\n", cls.servername ); - Com_Printf ("User info settings:\n"); - Info_Print( Cvar_InfoString( CVAR_USERINFO ) ); - Com_Printf( "--------------------------------------\n" ); -} - - -//==================================================================== - -/* -================= -CL_DownloadsComplete - -Called when all downloading has been completed -================= -*/ -void CL_DownloadsComplete( void ) { - - // if we downloaded files we need to restart the file system - if (clc.downloadRestart) { - clc.downloadRestart = qfalse; - - FS_Restart(clc.checksumFeed); // We possibly downloaded a pak, restart the file system to load it - - // inform the server so we get new gamestate info - CL_AddReliableCommand( "donedl" ); - - // by sending the donedl command we request a new gamestate - // so we don't want to load stuff yet - return; - } - - // let the client game init and load data - cls.state = CA_LOADING; - - // Pump the loop, this may change gamestate! - Com_EventLoop(); - - // if the gamestate was changed by calling Com_EventLoop - // then we loaded everything already and we don't want to do it again. - if ( cls.state != CA_LOADING ) { - return; - } - - // starting to load a map so we get out of full screen ui mode - Cvar_Set("r_uiFullScreen", "0"); - - // flush client memory and start loading stuff - // this will also (re)load the UI - // if this is a local client then only the client part of the hunk - // will be cleared, note that this is done after the hunk mark has been set - CL_FlushMemory(); - - // initialize the CGame - cls.cgameStarted = qtrue; - CL_InitCGame(); - - // set pure checksums - CL_SendPureChecksums(); - - CL_WritePacket(); - CL_WritePacket(); - CL_WritePacket(); -} - -/* -================= -CL_BeginDownload - -Requests a file to download from the server. Stores it in the current -game directory. -================= -*/ -void CL_BeginDownload( const char *localName, const char *remoteName ) { - - Com_DPrintf("***** CL_BeginDownload *****\n" - "Localname: %s\n" - "Remotename: %s\n" - "****************************\n", localName, remoteName); - - Q_strncpyz ( clc.downloadName, localName, sizeof(clc.downloadName) ); - Com_sprintf( clc.downloadTempName, sizeof(clc.downloadTempName), "%s.tmp", localName ); - - // Set so UI gets access to it - Cvar_Set( "cl_downloadName", remoteName ); - Cvar_Set( "cl_downloadSize", "0" ); - Cvar_Set( "cl_downloadCount", "0" ); - Cvar_SetValue( "cl_downloadTime", cls.realtime ); - - clc.downloadBlock = 0; // Starting new file - clc.downloadCount = 0; - - CL_AddReliableCommand( va("download %s", remoteName) ); -} - -/* -================= -CL_NextDownload - -A download completed or failed -================= -*/ -void CL_NextDownload(void) { - char *s; - char *remoteName, *localName; - - // We are looking to start a download here - if (*clc.downloadList) { - s = clc.downloadList; - - // format is: - // @remotename@localname@remotename@localname, etc. - - if (*s == '@') - s++; - remoteName = s; - - if ( (s = strchr(s, '@')) == NULL ) { - CL_DownloadsComplete(); - return; - } - - *s++ = 0; - localName = s; - if ( (s = strchr(s, '@')) != NULL ) - *s++ = 0; - else - s = localName + strlen(localName); // point at the nul byte - - CL_BeginDownload( localName, remoteName ); - - clc.downloadRestart = qtrue; - - // move over the rest - memmove( clc.downloadList, s, strlen(s) + 1); - - return; - } - - CL_DownloadsComplete(); -} - -/* -================= -CL_InitDownloads - -After receiving a valid game state, we valid the cgame and local zip files here -and determine if we need to download them -================= -*/ -void CL_InitDownloads(void) { - char missingfiles[1024]; - - if ( !cl_allowDownload->integer ) - { - // autodownload is disabled on the client - // but it's possible that some referenced files on the server are missing - if (FS_ComparePaks( missingfiles, sizeof( missingfiles ), qfalse ) ) - { - // NOTE TTimo I would rather have that printed as a modal message box - // but at this point while joining the game we don't know wether we will successfully join or not - Com_Printf( "\nWARNING: You are missing some files referenced by the server:\n%s" - "You might not be able to join the game\n" - "Go to the setting menu to turn on autodownload, or get the file elsewhere\n\n", missingfiles ); - } - } - else if ( FS_ComparePaks( clc.downloadList, sizeof( clc.downloadList ) , qtrue ) ) { - - Com_Printf("Need paks: %s\n", clc.downloadList ); - - if ( *clc.downloadList ) { - // if autodownloading is not enabled on the server - cls.state = CA_CONNECTED; - CL_NextDownload(); - return; - } - - } - - CL_DownloadsComplete(); -} - -/* -================= -CL_CheckForResend - -Resend a connect message if the last one has timed out -================= -*/ -void CL_CheckForResend( void ) { - int port, i; - char info[MAX_INFO_STRING]; - char data[MAX_INFO_STRING]; - - // don't send anything if playing back a demo - if ( clc.demoplaying ) { - return; - } - - // resend if we haven't gotten a reply yet - if ( cls.state != CA_CONNECTING && cls.state != CA_CHALLENGING ) { - return; - } - - if ( cls.realtime - clc.connectTime < RETRANSMIT_TIMEOUT ) { - return; - } - - clc.connectTime = cls.realtime; // for retransmit requests - clc.connectPacketCount++; - - - switch ( cls.state ) { - case CA_CONNECTING: - // requesting a challenge - if ( !Sys_IsLANAddress( clc.serverAddress ) ) { - CL_RequestAuthorization(); - } - NET_OutOfBandPrint(NS_CLIENT, clc.serverAddress, "getchallenge"); - break; - - case CA_CHALLENGING: - // sending back the challenge - port = Cvar_VariableValue ("net_qport"); - - Q_strncpyz( info, Cvar_InfoString( CVAR_USERINFO ), sizeof( info ) ); - Info_SetValueForKey( info, "protocol", va("%i", PROTOCOL_VERSION ) ); - Info_SetValueForKey( info, "qport", va("%i", port ) ); - Info_SetValueForKey( info, "challenge", va("%i", clc.challenge ) ); - - strcpy(data, "connect "); - // TTimo adding " " around the userinfo string to avoid truncated userinfo on the server - // (Com_TokenizeString tokenizes around spaces) - data[8] = '"'; - - for(i=0;iadr.type = NA_IP; - server->adr.ip[0] = address->ip[0]; - server->adr.ip[1] = address->ip[1]; - server->adr.ip[2] = address->ip[2]; - server->adr.ip[3] = address->ip[3]; - server->adr.port = address->port; - server->clients = 0; - server->hostName[0] = '\0'; - server->mapName[0] = '\0'; - server->maxClients = 0; - server->maxPing = 0; - server->minPing = 0; - server->ping = -1; - server->game[0] = '\0'; - server->gameType = 0; - server->netType = 0; -} - -#define MAX_SERVERSPERPACKET 256 - -/* -=================== -CL_ServersResponsePacket -=================== -*/ -void CL_ServersResponsePacket( netadr_t from, msg_t *msg ) { - int i, count, max, total; - serverAddress_t addresses[MAX_SERVERSPERPACKET]; - int numservers; - byte* buffptr; - byte* buffend; - - Com_Printf("CL_ServersResponsePacket\n"); - - if (cls.numglobalservers == -1) { - // state to detect lack of servers or lack of response - cls.numglobalservers = 0; - cls.numGlobalServerAddresses = 0; - } - - if (cls.nummplayerservers == -1) { - cls.nummplayerservers = 0; - } - - // parse through server response string - numservers = 0; - buffptr = msg->data; - buffend = buffptr + msg->cursize; - while (buffptr+1 < buffend) { - // advance to initial token - do { - if (*buffptr++ == '\\') - break; - } - while (buffptr < buffend); - - if ( buffptr >= buffend - 6 ) { - break; - } - - // parse out ip - addresses[numservers].ip[0] = *buffptr++; - addresses[numservers].ip[1] = *buffptr++; - addresses[numservers].ip[2] = *buffptr++; - addresses[numservers].ip[3] = *buffptr++; - - // parse out port - addresses[numservers].port = (*buffptr++)<<8; - addresses[numservers].port += *buffptr++; - addresses[numservers].port = BigShort( addresses[numservers].port ); - - // syntax check - if (*buffptr != '\\') { - break; - } - - Com_DPrintf( "server: %d ip: %d.%d.%d.%d:%d\n",numservers, - addresses[numservers].ip[0], - addresses[numservers].ip[1], - addresses[numservers].ip[2], - addresses[numservers].ip[3], - addresses[numservers].port ); - - numservers++; - if (numservers >= MAX_SERVERSPERPACKET) { - break; - } - - // parse out EOT - if (buffptr[1] == 'E' && buffptr[2] == 'O' && buffptr[3] == 'T') { - break; - } - } - - if (cls.masterNum == 0) { - count = cls.numglobalservers; - max = MAX_GLOBAL_SERVERS; - } else { - count = cls.nummplayerservers; - max = MAX_OTHER_SERVERS; - } - - for (i = 0; i < numservers && count < max; i++) { - // build net address - serverInfo_t *server = (cls.masterNum == 0) ? &cls.globalServers[count] : &cls.mplayerServers[count]; - - CL_InitServerInfo( server, &addresses[i] ); - // advance to next slot - count++; - } - - // if getting the global list - if (cls.masterNum == 0) { - if ( cls.numGlobalServerAddresses < MAX_GLOBAL_SERVERS ) { - // if we couldn't store the servers in the main list anymore - for (; i < numservers && count >= max; i++) { - serverAddress_t *addr; - // just store the addresses in an additional list - addr = &cls.globalServerAddresses[cls.numGlobalServerAddresses++]; - addr->ip[0] = addresses[i].ip[0]; - addr->ip[1] = addresses[i].ip[1]; - addr->ip[2] = addresses[i].ip[2]; - addr->ip[3] = addresses[i].ip[3]; - addr->port = addresses[i].port; - } - } - } - - if (cls.masterNum == 0) { - cls.numglobalservers = count; - total = count + cls.numGlobalServerAddresses; - } else { - cls.nummplayerservers = count; - total = count; - } - - Com_Printf("%d servers parsed (total %d)\n", numservers, total); -} - -/* -================= -CL_ConnectionlessPacket - -Responses to broadcasts, etc -================= -*/ -void CL_ConnectionlessPacket( netadr_t from, msg_t *msg ) { - char *s; - char *c; - - MSG_BeginReadingOOB( msg ); - MSG_ReadLong( msg ); // skip the -1 - - s = MSG_ReadStringLine( msg ); - - Cmd_TokenizeString( s ); - - c = Cmd_Argv(0); - - Com_DPrintf ("CL packet %s: %s\n", NET_AdrToString(from), c); - - // challenge from the server we are connecting to - if ( !Q_stricmp(c, "challengeResponse") ) { - if ( cls.state != CA_CONNECTING ) { - Com_Printf( "Unwanted challenge response received. Ignored.\n" ); - } else { - // start sending challenge repsonse instead of challenge request packets - clc.challenge = atoi(Cmd_Argv(1)); - cls.state = CA_CHALLENGING; - clc.connectPacketCount = 0; - clc.connectTime = -99999; - - // take this address as the new server address. This allows - // a server proxy to hand off connections to multiple servers - clc.serverAddress = from; - Com_DPrintf ("challengeResponse: %d\n", clc.challenge); - } - return; - } - - // server connection - if ( !Q_stricmp(c, "connectResponse") ) { - if ( cls.state >= CA_CONNECTED ) { - Com_Printf ("Dup connect received. Ignored.\n"); - return; - } - if ( cls.state != CA_CHALLENGING ) { - Com_Printf ("connectResponse packet while not connecting. Ignored.\n"); - return; - } - if ( !NET_CompareBaseAdr( from, clc.serverAddress ) ) { - Com_Printf( "connectResponse from a different address. Ignored.\n" ); - Com_Printf( "%s should have been %s\n", NET_AdrToString( from ), - NET_AdrToString( clc.serverAddress ) ); - return; - } - Netchan_Setup (NS_CLIENT, &clc.netchan, from, Cvar_VariableValue( "net_qport" ) ); - cls.state = CA_CONNECTED; - clc.lastPacketSentTime = -9999; // send first packet immediately - return; - } - - // server responding to an info broadcast - if ( !Q_stricmp(c, "infoResponse") ) { - CL_ServerInfoPacket( from, msg ); - return; - } - - // server responding to a get playerlist - if ( !Q_stricmp(c, "statusResponse") ) { - CL_ServerStatusResponse( from, msg ); - return; - } - - // a disconnect message from the server, which will happen if the server - // dropped the connection but it is still getting packets from us - if (!Q_stricmp(c, "disconnect")) { - CL_DisconnectPacket( from ); - return; - } - - // echo request from server - if ( !Q_stricmp(c, "echo") ) { - NET_OutOfBandPrint( NS_CLIENT, from, "%s", Cmd_Argv(1) ); - return; - } - - // cd check - if ( !Q_stricmp(c, "keyAuthorize") ) { - // we don't use these now, so dump them on the floor - return; - } - - // global MOTD from id - if ( !Q_stricmp(c, "motd") ) { - CL_MotdPacket( from ); - return; - } - - // echo request from server - if ( !Q_stricmp(c, "print") ) { - s = MSG_ReadString( msg ); - Q_strncpyz( clc.serverMessage, s, sizeof( clc.serverMessage ) ); - Com_Printf( "%s", s ); - return; - } - - // echo request from server - if ( !Q_strncmp(c, "getserversResponse", 18) ) { - CL_ServersResponsePacket( from, msg ); - return; - } - - Com_DPrintf ("Unknown connectionless packet command.\n"); -} - - -/* -================= -CL_PacketEvent - -A packet has arrived from the main event loop -================= -*/ -void CL_PacketEvent( netadr_t from, msg_t *msg ) { - int headerBytes; - - clc.lastPacketTime = cls.realtime; - - if ( msg->cursize >= 4 && *(int *)msg->data == -1 ) { - CL_ConnectionlessPacket( from, msg ); - return; - } - - if ( cls.state < CA_CONNECTED ) { - return; // can't be a valid sequenced packet - } - - if ( msg->cursize < 4 ) { - Com_Printf ("%s: Runt packet\n",NET_AdrToString( from )); - return; - } - - // - // packet from server - // - if ( !NET_CompareAdr( from, clc.netchan.remoteAddress ) ) { - Com_DPrintf ("%s:sequenced packet without connection\n" - ,NET_AdrToString( from ) ); - // FIXME: send a client disconnect? - return; - } - - if (!CL_Netchan_Process( &clc.netchan, msg) ) { - return; // out of order, duplicated, etc - } - - // the header is different lengths for reliable and unreliable messages - headerBytes = msg->readcount; - - // track the last message received so it can be returned in - // client messages, allowing the server to detect a dropped - // gamestate - clc.serverMessageSequence = LittleLong( *(int *)msg->data ); - - clc.lastPacketTime = cls.realtime; - CL_ParseServerMessage( msg ); - - // - // we don't know if it is ok to save a demo message until - // after we have parsed the frame - // - if ( clc.demorecording && !clc.demowaiting ) { - CL_WriteDemoMessage( msg, headerBytes ); - } -} - -/* -================== -CL_CheckTimeout - -================== -*/ -void CL_CheckTimeout( void ) { - // - // check timeout - // - if ( ( !cl_paused->integer || !sv_paused->integer ) - && cls.state >= CA_CONNECTED && cls.state != CA_CINEMATIC - && cls.realtime - clc.lastPacketTime > cl_timeout->value*1000) { - if (++cl.timeoutcount > 5) { // timeoutcount saves debugger - Com_Printf ("\nServer connection timed out.\n"); - CL_Disconnect( qtrue ); - return; - } - } else { - cl.timeoutcount = 0; - } -} - - -//============================================================================ - -/* -================== -CL_CheckUserinfo - -================== -*/ -void CL_CheckUserinfo( void ) { - // don't add reliable commands when not yet connected - if ( cls.state < CA_CHALLENGING ) { - return; - } - // don't overflow the reliable command buffer when paused - if ( cl_paused->integer ) { - return; - } - // send a reliable userinfo update if needed - if ( cvar_modifiedFlags & CVAR_USERINFO ) { - cvar_modifiedFlags &= ~CVAR_USERINFO; - CL_AddReliableCommand( va("userinfo \"%s\"", Cvar_InfoString( CVAR_USERINFO ) ) ); - } - -} - -/* -================== -CL_Frame - -================== -*/ -void CL_Frame ( int msec ) { - - if ( !com_cl_running->integer ) { - return; - } - - if ( cls.cddialog ) { - // bring up the cd error dialog if needed - cls.cddialog = qfalse; - VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_NEED_CD ); - } else if ( cls.state == CA_DISCONNECTED && !( cls.keyCatchers & KEYCATCH_UI ) - && !com_sv_running->integer ) { - // if disconnected, bring up the menu - S_StopAllSounds(); - VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_MAIN ); - } - - // if recording an avi, lock to a fixed fps - if ( cl_avidemo->integer && msec) { - // save the current screen - if ( cls.state == CA_ACTIVE || cl_forceavidemo->integer) { - Cbuf_ExecuteText( EXEC_NOW, "screenshot silent\n" ); - } - // fixed time for next frame' - msec = (1000 / cl_avidemo->integer) * com_timescale->value; - if (msec == 0) { - msec = 1; - } - } - - // save the msec before checking pause - cls.realFrametime = msec; - - // decide the simulation time - cls.frametime = msec; - - cls.realtime += cls.frametime; - - if ( cl_timegraph->integer ) { - SCR_DebugGraph ( cls.realFrametime * 0.25, 0 ); - } - - // see if we need to update any userinfo - CL_CheckUserinfo(); - - // if we haven't gotten a packet in a long time, - // drop the connection - CL_CheckTimeout(); - - // send intentions now - CL_SendCmd(); - - // resend a connection request if necessary - CL_CheckForResend(); - - // decide on the serverTime to render - CL_SetCGameTime(); - - // update the screen - SCR_UpdateScreen(); - - // update audio - S_Update(); - - // advance local effects for next frame - SCR_RunCinematic(); - - Con_RunConsole(); - - cls.framecount++; -} - - -//============================================================================ - -/* -================ -CL_RefPrintf - -DLL glue -================ -*/ -void QDECL CL_RefPrintf( int print_level, const char *fmt, ...) { - va_list argptr; - char msg[MAXPRINTMSG]; - - va_start (argptr,fmt); - Q_vsnprintf (msg, sizeof(msg), fmt, argptr); - va_end (argptr); - - if ( print_level == PRINT_ALL ) { - Com_Printf ("%s", msg); - } else if ( print_level == PRINT_WARNING ) { - Com_Printf (S_COLOR_YELLOW "%s", msg); // yellow - } else if ( print_level == PRINT_DEVELOPER ) { - Com_DPrintf (S_COLOR_RED "%s", msg); // red - } -} - - - -/* -============ -CL_ShutdownRef -============ -*/ -void CL_ShutdownRef( void ) { - if ( !re.Shutdown ) { - return; - } - re.Shutdown( qtrue ); - Com_Memset( &re, 0, sizeof( re ) ); -} - -/* -============ -CL_InitRenderer -============ -*/ -void CL_InitRenderer( void ) { - // this sets up the renderer and calls R_Init - re.BeginRegistration( &cls.glconfig ); - - // load character sets - cls.charSetShader = re.RegisterShader( "gfx/2d/bigchars" ); - cls.whiteShader = re.RegisterShader( "white" ); - cls.consoleShader = re.RegisterShader( "console" ); - g_console_field_width = cls.glconfig.vidWidth / SMALLCHAR_WIDTH - 2; - g_consoleField.widthInChars = g_console_field_width; -} - -/* -============================ -CL_StartHunkUsers - -After the server has cleared the hunk, these will need to be restarted -This is the only place that any of these functions are called from -============================ -*/ -void CL_StartHunkUsers( void ) { - if (!com_cl_running) { - return; - } - - if ( !com_cl_running->integer ) { - return; - } - - if ( !cls.rendererStarted ) { - cls.rendererStarted = qtrue; - CL_InitRenderer(); - } - - if ( !cls.soundStarted ) { - cls.soundStarted = qtrue; - S_Init(); - } - - if ( !cls.soundRegistered ) { - cls.soundRegistered = qtrue; - S_BeginRegistration(); - } - - if ( !cls.uiStarted ) { - cls.uiStarted = qtrue; - CL_InitUI(); - } -} - -/* -============ -CL_RefMalloc -============ -*/ -void *CL_RefMalloc( int size ) { - return Z_TagMalloc( size, TAG_RENDERER ); -} - -int CL_ScaledMilliseconds(void) { - return Sys_Milliseconds()*com_timescale->value; -} - -/* -============ -CL_InitRef -============ -*/ -void CL_InitRef( void ) { - refimport_t ri; - refexport_t *ret; - - Com_Printf( "----- Initializing Renderer ----\n" ); - - ri.Cmd_AddCommand = Cmd_AddCommand; - ri.Cmd_RemoveCommand = Cmd_RemoveCommand; - ri.Cmd_Argc = Cmd_Argc; - ri.Cmd_Argv = Cmd_Argv; - ri.Cmd_ExecuteText = Cbuf_ExecuteText; - ri.Printf = CL_RefPrintf; - ri.Error = Com_Error; - ri.Milliseconds = CL_ScaledMilliseconds; - ri.Malloc = CL_RefMalloc; - ri.Free = Z_Free; -#ifdef HUNK_DEBUG - ri.Hunk_AllocDebug = Hunk_AllocDebug; -#else - ri.Hunk_Alloc = Hunk_Alloc; -#endif - ri.Hunk_AllocateTempMemory = Hunk_AllocateTempMemory; - ri.Hunk_FreeTempMemory = Hunk_FreeTempMemory; - ri.CM_DrawDebugSurface = CM_DrawDebugSurface; - ri.FS_ReadFile = FS_ReadFile; - ri.FS_FreeFile = FS_FreeFile; - ri.FS_WriteFile = FS_WriteFile; - ri.FS_FreeFileList = FS_FreeFileList; - ri.FS_ListFiles = FS_ListFiles; - ri.FS_FileIsInPAK = FS_FileIsInPAK; - ri.FS_FileExists = FS_FileExists; - ri.Cvar_Get = Cvar_Get; - ri.Cvar_Set = Cvar_Set; - - // cinematic stuff - - ri.CIN_UploadCinematic = CIN_UploadCinematic; - ri.CIN_PlayCinematic = CIN_PlayCinematic; - ri.CIN_RunCinematic = CIN_RunCinematic; - - ret = GetRefAPI( REF_API_VERSION, &ri ); - -#if defined __USEA3D && defined __A3D_GEOM - hA3Dg_ExportRenderGeom (ret); -#endif - - Com_Printf( "-------------------------------\n"); - - if ( !ret ) { - Com_Error (ERR_FATAL, "Couldn't initialize refresh" ); - } - - re = *ret; - - // unpause so the cgame definately gets a snapshot and renders a frame - Cvar_Set( "cl_paused", "0" ); -} - - -//=========================================================================================== - - -void CL_SetModel_f( void ) { - char *arg; - char name[256]; - - arg = Cmd_Argv( 1 ); - if (arg[0]) { - Cvar_Set( "model", arg ); - Cvar_Set( "headmodel", arg ); - } else { - Cvar_VariableStringBuffer( "model", name, sizeof(name) ); - Com_Printf("model is set to %s\n", name); - } -} - -/* -==================== -CL_Init -==================== -*/ -void CL_Init( void ) { - Com_Printf( "----- Client Initialization -----\n" ); - - Con_Init (); - - CL_ClearState (); - - cls.state = CA_DISCONNECTED; // no longer CA_UNINITIALIZED - - cls.realtime = 0; - - CL_InitInput (); - - // - // register our variables - // - cl_noprint = Cvar_Get( "cl_noprint", "0", 0 ); - cl_motd = Cvar_Get ("cl_motd", "1", 0); - - cl_timeout = Cvar_Get ("cl_timeout", "200", 0); - - cl_timeNudge = Cvar_Get ("cl_timeNudge", "0", CVAR_TEMP ); - cl_shownet = Cvar_Get ("cl_shownet", "0", CVAR_TEMP ); - cl_showSend = Cvar_Get ("cl_showSend", "0", CVAR_TEMP ); - cl_showTimeDelta = Cvar_Get ("cl_showTimeDelta", "0", CVAR_TEMP ); - cl_freezeDemo = Cvar_Get ("cl_freezeDemo", "0", CVAR_TEMP ); - rcon_client_password = Cvar_Get ("rconPassword", "", CVAR_TEMP ); - cl_activeAction = Cvar_Get( "activeAction", "", CVAR_TEMP ); - - cl_timedemo = Cvar_Get ("timedemo", "0", 0); - cl_avidemo = Cvar_Get ("cl_avidemo", "0", 0); - cl_forceavidemo = Cvar_Get ("cl_forceavidemo", "0", 0); - - rconAddress = Cvar_Get ("rconAddress", "", 0); - - cl_yawspeed = Cvar_Get ("cl_yawspeed", "140", CVAR_ARCHIVE); - cl_pitchspeed = Cvar_Get ("cl_pitchspeed", "140", CVAR_ARCHIVE); - cl_anglespeedkey = Cvar_Get ("cl_anglespeedkey", "1.5", 0); - - cl_maxpackets = Cvar_Get ("cl_maxpackets", "30", CVAR_ARCHIVE ); - cl_packetdup = Cvar_Get ("cl_packetdup", "1", CVAR_ARCHIVE ); - - cl_run = Cvar_Get ("cl_run", "1", CVAR_ARCHIVE); - cl_sensitivity = Cvar_Get ("sensitivity", "5", CVAR_ARCHIVE); - cl_mouseAccel = Cvar_Get ("cl_mouseAccel", "0", CVAR_ARCHIVE); - cl_freelook = Cvar_Get( "cl_freelook", "1", CVAR_ARCHIVE ); - - cl_showMouseRate = Cvar_Get ("cl_showmouserate", "0", 0); - - cl_allowDownload = Cvar_Get ("cl_allowDownload", "0", CVAR_ARCHIVE); - - cl_conXOffset = Cvar_Get ("cl_conXOffset", "0", 0); -#ifdef MACOS_X - // In game video is REALLY slow in Mac OS X right now due to driver slowness - cl_inGameVideo = Cvar_Get ("r_inGameVideo", "0", CVAR_ARCHIVE); -#else - cl_inGameVideo = Cvar_Get ("r_inGameVideo", "1", CVAR_ARCHIVE); -#endif - - cl_serverStatusResendTime = Cvar_Get ("cl_serverStatusResendTime", "750", 0); - - // init autoswitch so the ui will have it correctly even - // if the cgame hasn't been started - Cvar_Get ("cg_autoswitch", "1", CVAR_ARCHIVE); - - m_pitch = Cvar_Get ("m_pitch", "0.022", CVAR_ARCHIVE); - m_yaw = Cvar_Get ("m_yaw", "0.022", CVAR_ARCHIVE); - m_forward = Cvar_Get ("m_forward", "0.25", CVAR_ARCHIVE); - m_side = Cvar_Get ("m_side", "0.25", CVAR_ARCHIVE); -#ifdef MACOS_X - // Input is jittery on OS X w/o this - m_filter = Cvar_Get ("m_filter", "1", CVAR_ARCHIVE); -#else - m_filter = Cvar_Get ("m_filter", "0", CVAR_ARCHIVE); -#endif - - cl_motdString = Cvar_Get( "cl_motdString", "", CVAR_ROM ); - - Cvar_Get( "cl_maxPing", "800", CVAR_ARCHIVE ); - - - // userinfo - Cvar_Get ("name", "UnnamedPlayer", CVAR_USERINFO | CVAR_ARCHIVE ); - Cvar_Get ("rate", "3000", CVAR_USERINFO | CVAR_ARCHIVE ); - Cvar_Get ("snaps", "20", CVAR_USERINFO | CVAR_ARCHIVE ); - Cvar_Get ("model", "sarge", CVAR_USERINFO | CVAR_ARCHIVE ); - Cvar_Get ("headmodel", "sarge", CVAR_USERINFO | CVAR_ARCHIVE ); - Cvar_Get ("team_model", "james", CVAR_USERINFO | CVAR_ARCHIVE ); - Cvar_Get ("team_headmodel", "*james", CVAR_USERINFO | CVAR_ARCHIVE ); - Cvar_Get ("g_redTeam", "Stroggs", CVAR_SERVERINFO | CVAR_ARCHIVE); - Cvar_Get ("g_blueTeam", "Pagans", CVAR_SERVERINFO | CVAR_ARCHIVE); - Cvar_Get ("color1", "4", CVAR_USERINFO | CVAR_ARCHIVE ); - Cvar_Get ("color2", "5", CVAR_USERINFO | CVAR_ARCHIVE ); - Cvar_Get ("handicap", "100", CVAR_USERINFO | CVAR_ARCHIVE ); - Cvar_Get ("teamtask", "0", CVAR_USERINFO ); - Cvar_Get ("sex", "male", CVAR_USERINFO | CVAR_ARCHIVE ); - Cvar_Get ("cl_anonymous", "0", CVAR_USERINFO | CVAR_ARCHIVE ); - - Cvar_Get ("password", "", CVAR_USERINFO); - Cvar_Get ("cg_predictItems", "1", CVAR_USERINFO | CVAR_ARCHIVE ); - - - // cgame might not be initialized before menu is used - Cvar_Get ("cg_viewsize", "100", CVAR_ARCHIVE ); - - // - // register our commands - // - Cmd_AddCommand ("cmd", CL_ForwardToServer_f); - Cmd_AddCommand ("configstrings", CL_Configstrings_f); - Cmd_AddCommand ("clientinfo", CL_Clientinfo_f); - Cmd_AddCommand ("snd_restart", CL_Snd_Restart_f); - Cmd_AddCommand ("vid_restart", CL_Vid_Restart_f); - Cmd_AddCommand ("disconnect", CL_Disconnect_f); - Cmd_AddCommand ("record", CL_Record_f); - Cmd_AddCommand ("demo", CL_PlayDemo_f); - Cmd_AddCommand ("cinematic", CL_PlayCinematic_f); - Cmd_AddCommand ("stoprecord", CL_StopRecord_f); - Cmd_AddCommand ("connect", CL_Connect_f); - Cmd_AddCommand ("reconnect", CL_Reconnect_f); - Cmd_AddCommand ("localservers", CL_LocalServers_f); - Cmd_AddCommand ("globalservers", CL_GlobalServers_f); - Cmd_AddCommand ("rcon", CL_Rcon_f); - Cmd_AddCommand ("setenv", CL_Setenv_f ); - Cmd_AddCommand ("ping", CL_Ping_f ); - Cmd_AddCommand ("serverstatus", CL_ServerStatus_f ); - Cmd_AddCommand ("showip", CL_ShowIP_f ); - Cmd_AddCommand ("fs_openedList", CL_OpenedPK3List_f ); - Cmd_AddCommand ("fs_referencedList", CL_ReferencedPK3List_f ); - Cmd_AddCommand ("model", CL_SetModel_f ); - CL_InitRef(); - - SCR_Init (); - - Cbuf_Execute (); - - Cvar_Set( "cl_running", "1" ); - - Com_Printf( "----- Client Initialization Complete -----\n" ); -} - - -/* -=============== -CL_Shutdown - -=============== -*/ -void CL_Shutdown( void ) { - static qboolean recursive = qfalse; - - Com_Printf( "----- CL_Shutdown -----\n" ); - - if ( recursive ) { - printf ("recursive shutdown\n"); - return; - } - recursive = qtrue; - - CL_Disconnect( qtrue ); - - S_Shutdown(); - CL_ShutdownRef(); - - CL_ShutdownUI(); - - Cmd_RemoveCommand ("cmd"); - Cmd_RemoveCommand ("configstrings"); - Cmd_RemoveCommand ("userinfo"); - Cmd_RemoveCommand ("snd_restart"); - Cmd_RemoveCommand ("vid_restart"); - Cmd_RemoveCommand ("disconnect"); - Cmd_RemoveCommand ("record"); - Cmd_RemoveCommand ("demo"); - Cmd_RemoveCommand ("cinematic"); - Cmd_RemoveCommand ("stoprecord"); - Cmd_RemoveCommand ("connect"); - Cmd_RemoveCommand ("localservers"); - Cmd_RemoveCommand ("globalservers"); - Cmd_RemoveCommand ("rcon"); - Cmd_RemoveCommand ("setenv"); - Cmd_RemoveCommand ("ping"); - Cmd_RemoveCommand ("serverstatus"); - Cmd_RemoveCommand ("showip"); - Cmd_RemoveCommand ("model"); - - Cvar_Set( "cl_running", "0" ); - - recursive = qfalse; - - Com_Memset( &cls, 0, sizeof( cls ) ); - - Com_Printf( "-----------------------\n" ); - -} - -static void CL_SetServerInfo(serverInfo_t *server, const char *info, int ping) { - if (server) { - if (info) { - server->clients = atoi(Info_ValueForKey(info, "clients")); - Q_strncpyz(server->hostName,Info_ValueForKey(info, "hostname"), MAX_NAME_LENGTH); - Q_strncpyz(server->mapName, Info_ValueForKey(info, "mapname"), MAX_NAME_LENGTH); - server->maxClients = atoi(Info_ValueForKey(info, "sv_maxclients")); - Q_strncpyz(server->game,Info_ValueForKey(info, "game"), MAX_NAME_LENGTH); - server->gameType = atoi(Info_ValueForKey(info, "gametype")); - server->netType = atoi(Info_ValueForKey(info, "nettype")); - server->minPing = atoi(Info_ValueForKey(info, "minping")); - server->maxPing = atoi(Info_ValueForKey(info, "maxping")); - server->punkbuster = atoi(Info_ValueForKey(info, "punkbuster")); - } - server->ping = ping; - } -} - -static void CL_SetServerInfoByAddress(netadr_t from, const char *info, int ping) { - int i; - - for (i = 0; i < MAX_OTHER_SERVERS; i++) { - if (NET_CompareAdr(from, cls.localServers[i].adr)) { - CL_SetServerInfo(&cls.localServers[i], info, ping); - } - } - - for (i = 0; i < MAX_OTHER_SERVERS; i++) { - if (NET_CompareAdr(from, cls.mplayerServers[i].adr)) { - CL_SetServerInfo(&cls.mplayerServers[i], info, ping); - } - } - - for (i = 0; i < MAX_GLOBAL_SERVERS; i++) { - if (NET_CompareAdr(from, cls.globalServers[i].adr)) { - CL_SetServerInfo(&cls.globalServers[i], info, ping); - } - } - - for (i = 0; i < MAX_OTHER_SERVERS; i++) { - if (NET_CompareAdr(from, cls.favoriteServers[i].adr)) { - CL_SetServerInfo(&cls.favoriteServers[i], info, ping); - } - } - -} - -/* -=================== -CL_ServerInfoPacket -=================== -*/ -void CL_ServerInfoPacket( netadr_t from, msg_t *msg ) { - int i, type; - char info[MAX_INFO_STRING]; - char* str; - char *infoString; - int prot; - - infoString = MSG_ReadString( msg ); - - // if this isn't the correct protocol version, ignore it - prot = atoi( Info_ValueForKey( infoString, "protocol" ) ); - if ( prot != PROTOCOL_VERSION ) { - Com_DPrintf( "Different protocol info packet: %s\n", infoString ); - return; - } - - // iterate servers waiting for ping response - for (i=0; iretrieved = qtrue; - return qfalse; - } - - // if this server status request has the same address - if ( NET_CompareAdr( to, serverStatus->address) ) { - // if we recieved an response for this server status request - if (!serverStatus->pending) { - Q_strncpyz(serverStatusString, serverStatus->string, maxLen); - serverStatus->retrieved = qtrue; - serverStatus->startTime = 0; - return qtrue; - } - // resend the request regularly - else if ( serverStatus->startTime < Com_Milliseconds() - cl_serverStatusResendTime->integer ) { - serverStatus->print = qfalse; - serverStatus->pending = qtrue; - serverStatus->retrieved = qfalse; - serverStatus->time = 0; - serverStatus->startTime = Com_Milliseconds(); - NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" ); - return qfalse; - } - } - // if retrieved - else if ( serverStatus->retrieved ) { - serverStatus->address = to; - serverStatus->print = qfalse; - serverStatus->pending = qtrue; - serverStatus->retrieved = qfalse; - serverStatus->startTime = Com_Milliseconds(); - serverStatus->time = 0; - NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" ); - return qfalse; - } - return qfalse; -} - -/* -=================== -CL_ServerStatusResponse -=================== -*/ -void CL_ServerStatusResponse( netadr_t from, msg_t *msg ) { - char *s; - char info[MAX_INFO_STRING]; - int i, l, score, ping; - int len; - serverStatus_t *serverStatus; - - serverStatus = NULL; - for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) { - if ( NET_CompareAdr( from, cl_serverStatusList[i].address ) ) { - serverStatus = &cl_serverStatusList[i]; - break; - } - } - // if we didn't request this server status - if (!serverStatus) { - return; - } - - s = MSG_ReadStringLine( msg ); - - len = 0; - Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "%s", s); - - if (serverStatus->print) { - Com_Printf("Server settings:\n"); - // print cvars - while (*s) { - for (i = 0; i < 2 && *s; i++) { - if (*s == '\\') - s++; - l = 0; - while (*s) { - info[l++] = *s; - if (l >= MAX_INFO_STRING-1) - break; - s++; - if (*s == '\\') { - break; - } - } - info[l] = '\0'; - if (i) { - Com_Printf("%s\n", info); - } - else { - Com_Printf("%-24s", info); - } - } - } - } - - len = strlen(serverStatus->string); - Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "\\"); - - if (serverStatus->print) { - Com_Printf("\nPlayers:\n"); - Com_Printf("num: score: ping: name:\n"); - } - for (i = 0, s = MSG_ReadStringLine( msg ); *s; s = MSG_ReadStringLine( msg ), i++) { - - len = strlen(serverStatus->string); - Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "\\%s", s); - - if (serverStatus->print) { - score = ping = 0; - sscanf(s, "%d %d", &score, &ping); - s = strchr(s, ' '); - if (s) - s = strchr(s+1, ' '); - if (s) - s++; - else - s = "unknown"; - Com_Printf("%-2d %-3d %-3d %s\n", i, score, ping, s ); - } - } - len = strlen(serverStatus->string); - Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "\\"); - - serverStatus->time = Com_Milliseconds(); - serverStatus->address = from; - serverStatus->pending = qfalse; - if (serverStatus->print) { - serverStatus->retrieved = qtrue; - } -} - -/* -================== -CL_LocalServers_f -================== -*/ -void CL_LocalServers_f( void ) { - char *message; - int i, j; - netadr_t to; - - Com_Printf( "Scanning for servers on the local network...\n"); - - // reset the list, waiting for response - cls.numlocalservers = 0; - cls.pingUpdateSource = AS_LOCAL; - - for (i = 0; i < MAX_OTHER_SERVERS; i++) { - qboolean b = cls.localServers[i].visible; - Com_Memset(&cls.localServers[i], 0, sizeof(cls.localServers[i])); - cls.localServers[i].visible = b; - } - Com_Memset( &to, 0, sizeof( to ) ); - - // The 'xxx' in the message is a challenge that will be echoed back - // by the server. We don't care about that here, but master servers - // can use that to prevent spoofed server responses from invalid ip - message = "\377\377\377\377getinfo xxx"; - - // send each message twice in case one is dropped - for ( i = 0 ; i < 2 ; i++ ) { - // send a broadcast packet on each server port - // we support multiple server ports so a single machine - // can nicely run multiple servers - for ( j = 0 ; j < NUM_SERVER_PORTS ; j++ ) { - to.port = BigShort( (short)(PORT_SERVER + j) ); - - to.type = NA_BROADCAST; - NET_SendPacket( NS_CLIENT, strlen( message ), message, to ); - - to.type = NA_BROADCAST_IPX; - NET_SendPacket( NS_CLIENT, strlen( message ), message, to ); - } - } -} - -/* -================== -CL_GlobalServers_f -================== -*/ -void CL_GlobalServers_f( void ) { - netadr_t to; - int i; - int count; - char *buffptr; - char command[1024]; - - if ( Cmd_Argc() < 3) { - Com_Printf( "usage: globalservers [keywords]\n"); - return; - } - - cls.masterNum = atoi( Cmd_Argv(1) ); - - Com_Printf( "Requesting servers from the master...\n"); - - // reset the list, waiting for response - // -1 is used to distinguish a "no response" - - if( cls.masterNum == 1 ) { - NET_StringToAdr( MASTER_SERVER_NAME, &to ); - cls.nummplayerservers = -1; - cls.pingUpdateSource = AS_MPLAYER; - } - else { - NET_StringToAdr( MASTER_SERVER_NAME, &to ); - cls.numglobalservers = -1; - cls.pingUpdateSource = AS_GLOBAL; - } - to.type = NA_IP; - to.port = BigShort(PORT_MASTER); - - sprintf( command, "getservers %s", Cmd_Argv(2) ); - - // tack on keywords - buffptr = command + strlen( command ); - count = Cmd_Argc(); - for (i=3; i= MAX_PINGREQUESTS) - return; - - cl_pinglist[n].adr.port = 0; -} - -/* -================== -CL_GetPingQueueCount -================== -*/ -int CL_GetPingQueueCount( void ) -{ - int i; - int count; - ping_t* pingptr; - - count = 0; - pingptr = cl_pinglist; - - for (i=0; iadr.port) { - count++; - } - } - - return (count); -} - -/* -================== -CL_GetFreePing -================== -*/ -ping_t* CL_GetFreePing( void ) -{ - ping_t* pingptr; - ping_t* best; - int oldest; - int i; - int time; - - pingptr = cl_pinglist; - for (i=0; iadr.port) - { - if (!pingptr->time) - { - if (cls.realtime - pingptr->start < 500) - { - // still waiting for response - continue; - } - } - else if (pingptr->time < 500) - { - // results have not been queried - continue; - } - } - - // clear it - pingptr->adr.port = 0; - return (pingptr); - } - - // use oldest entry - pingptr = cl_pinglist; - best = cl_pinglist; - oldest = INT_MIN; - for (i=0; istart; - if (time > oldest) - { - oldest = time; - best = pingptr; - } - } - - return (best); -} - -/* -================== -CL_Ping_f -================== -*/ -void CL_Ping_f( void ) { - netadr_t to; - ping_t* pingptr; - char* server; - - if ( Cmd_Argc() != 2 ) { - Com_Printf( "usage: ping [server]\n"); - return; - } - - Com_Memset( &to, 0, sizeof(netadr_t) ); - - server = Cmd_Argv(1); - - if ( !NET_StringToAdr( server, &to ) ) { - return; - } - - pingptr = CL_GetFreePing(); - - memcpy( &pingptr->adr, &to, sizeof (netadr_t) ); - pingptr->start = cls.realtime; - pingptr->time = 0; - - CL_SetServerInfoByAddress(pingptr->adr, NULL, 0); - - NET_OutOfBandPrint( NS_CLIENT, to, "getinfo xxx" ); -} - -/* -================== -CL_UpdateVisiblePings_f -================== -*/ -qboolean CL_UpdateVisiblePings_f(int source) { - int slots, i; - char buff[MAX_STRING_CHARS]; - int pingTime; - int max; - qboolean status = qfalse; - - if (source < 0 || source > AS_FAVORITES) { - return qfalse; - } - - cls.pingUpdateSource = source; - - slots = CL_GetPingQueueCount(); - if (slots < MAX_PINGREQUESTS) { - serverInfo_t *server = NULL; - - max = (source == AS_GLOBAL) ? MAX_GLOBAL_SERVERS : MAX_OTHER_SERVERS; - switch (source) { - case AS_LOCAL : - server = &cls.localServers[0]; - max = cls.numlocalservers; - break; - case AS_MPLAYER : - server = &cls.mplayerServers[0]; - max = cls.nummplayerservers; - break; - case AS_GLOBAL : - server = &cls.globalServers[0]; - max = cls.numglobalservers; - break; - case AS_FAVORITES : - server = &cls.favoriteServers[0]; - max = cls.numfavoriteservers; - break; - } - for (i = 0; i < max; i++) { - if (server[i].visible) { - if (server[i].ping == -1) { - int j; - - if (slots >= MAX_PINGREQUESTS) { - break; - } - for (j = 0; j < MAX_PINGREQUESTS; j++) { - if (!cl_pinglist[j].adr.port) { - continue; - } - if (NET_CompareAdr( cl_pinglist[j].adr, server[i].adr)) { - // already on the list - break; - } - } - if (j >= MAX_PINGREQUESTS) { - status = qtrue; - for (j = 0; j < MAX_PINGREQUESTS; j++) { - if (!cl_pinglist[j].adr.port) { - break; - } - } - memcpy(&cl_pinglist[j].adr, &server[i].adr, sizeof(netadr_t)); - cl_pinglist[j].start = cls.realtime; - cl_pinglist[j].time = 0; - NET_OutOfBandPrint( NS_CLIENT, cl_pinglist[j].adr, "getinfo xxx" ); - slots++; - } - } - // if the server has a ping higher than cl_maxPing or - // the ping packet got lost - else if (server[i].ping == 0) { - // if we are updating global servers - if (source == AS_GLOBAL) { - // - if ( cls.numGlobalServerAddresses > 0 ) { - // overwrite this server with one from the additional global servers - cls.numGlobalServerAddresses--; - CL_InitServerInfo(&server[i], &cls.globalServerAddresses[cls.numGlobalServerAddresses]); - // NOTE: the server[i].visible flag stays untouched - } - } - } - } - } - } - - if (slots) { - status = qtrue; - } - for (i = 0; i < MAX_PINGREQUESTS; i++) { - if (!cl_pinglist[i].adr.port) { - continue; - } - CL_GetPing( i, buff, MAX_STRING_CHARS, &pingTime ); - if (pingTime != 0) { - CL_ClearPing(i); - status = qtrue; - } - } - - return status; -} - -/* -================== -CL_ServerStatus_f -================== -*/ -void CL_ServerStatus_f(void) { - netadr_t to; - char *server; - serverStatus_t *serverStatus; - - Com_Memset( &to, 0, sizeof(netadr_t) ); - - if ( Cmd_Argc() != 2 ) { - if ( cls.state != CA_ACTIVE || clc.demoplaying ) { - Com_Printf ("Not connected to a server.\n"); - Com_Printf( "Usage: serverstatus [server]\n"); - return; - } - server = cls.servername; - } - else { - server = Cmd_Argv(1); - } - - if ( !NET_StringToAdr( server, &to ) ) { - return; - } - - NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" ); - - serverStatus = CL_GetServerStatus( to ); - serverStatus->address = to; - serverStatus->print = qtrue; - serverStatus->pending = qtrue; -} - -/* -================== -CL_ShowIP_f -================== -*/ -void CL_ShowIP_f(void) { - Sys_ShowIP(); -} - -/* -================= -bool CL_CDKeyValidate -================= -*/ -qboolean CL_CDKeyValidate( const char *key, const char *checksum ) { - char ch; - byte sum; - char chs[3]; - int i, len; - - len = strlen(key); - if( len != CDKEY_LEN ) { - return qfalse; - } - - if( checksum && strlen( checksum ) != CDCHKSUM_LEN ) { - return qfalse; - } - - sum = 0; - // for loop gets rid of conditional assignment warning - for (i = 0; i < len; i++) { - ch = *key++; - if (ch>='a' && ch<='z') { - ch -= 32; - } - switch( ch ) { - case '2': - case '3': - case '7': - case 'A': - case 'B': - case 'C': - case 'D': - case 'G': - case 'H': - case 'J': - case 'L': - case 'P': - case 'R': - case 'S': - case 'T': - case 'W': - sum += ch; - continue; - default: - return qfalse; - } - } - - sprintf(chs, "%02x", sum); - - if (checksum && !Q_stricmp(chs, checksum)) { - return qtrue; - } - - if (!checksum) { - return qtrue; - } - - return qfalse; -} - - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// cl_main.c -- client main loop + +#include "client.h" +#include + +cvar_t *cl_nodelta; +cvar_t *cl_debugMove; + +cvar_t *cl_noprint; +cvar_t *cl_motd; + +cvar_t *rcon_client_password; +cvar_t *rconAddress; + +cvar_t *cl_timeout; +cvar_t *cl_maxpackets; +cvar_t *cl_packetdup; +cvar_t *cl_timeNudge; +cvar_t *cl_showTimeDelta; +cvar_t *cl_freezeDemo; + +cvar_t *cl_shownet; +cvar_t *cl_showSend; +cvar_t *cl_timedemo; +cvar_t *cl_avidemo; +cvar_t *cl_forceavidemo; + +cvar_t *cl_freelook; +cvar_t *cl_sensitivity; + +cvar_t *cl_mouseAccel; +cvar_t *cl_showMouseRate; + +cvar_t *m_pitch; +cvar_t *m_yaw; +cvar_t *m_forward; +cvar_t *m_side; +cvar_t *m_filter; + +cvar_t *cl_activeAction; + +cvar_t *cl_motdString; + +cvar_t *cl_allowDownload; +cvar_t *cl_conXOffset; +cvar_t *cl_inGameVideo; + +cvar_t *cl_serverStatusResendTime; +cvar_t *cl_trn; + +clientActive_t cl; +clientConnection_t clc; +clientStatic_t cls; +vm_t *cgvm; + +// Structure containing functions exported from refresh DLL +refexport_t re; + +ping_t cl_pinglist[MAX_PINGREQUESTS]; + +typedef struct serverStatus_s +{ + char string[BIG_INFO_STRING]; + netadr_t address; + int time, startTime; + qboolean pending; + qboolean print; + qboolean retrieved; +} serverStatus_t; + +serverStatus_t cl_serverStatusList[MAX_SERVERSTATUSREQUESTS]; +int serverStatusCount; + +#if defined __USEA3D && defined __A3D_GEOM + void hA3Dg_ExportRenderGeom (refexport_t *incoming_re); +#endif + +extern void SV_BotFrame( int time ); +void CL_CheckForResend( void ); +void CL_ShowIP_f(void); +void CL_ServerStatus_f(void); +void CL_ServerStatusResponse( netadr_t from, msg_t *msg ); + +/* +=============== +CL_CDDialog + +Called by Com_Error when a cd is needed +=============== +*/ +void CL_CDDialog( void ) { + cls.cddialog = qtrue; // start it next frame +} + + +/* +======================================================================= + +CLIENT RELIABLE COMMAND COMMUNICATION + +======================================================================= +*/ + +/* +====================== +CL_AddReliableCommand + +The given command will be transmitted to the server, and is gauranteed to +not have future usercmd_t executed before it is executed +====================== +*/ +void CL_AddReliableCommand( const char *cmd ) { + int index; + + // if we would be losing an old command that hasn't been acknowledged, + // we must drop the connection + if ( clc.reliableSequence - clc.reliableAcknowledge > MAX_RELIABLE_COMMANDS ) { + Com_Error( ERR_DROP, "Client command overflow" ); + } + clc.reliableSequence++; + index = clc.reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 ); + Q_strncpyz( clc.reliableCommands[ index ], cmd, sizeof( clc.reliableCommands[ index ] ) ); +} + +/* +====================== +CL_ChangeReliableCommand +====================== +*/ +void CL_ChangeReliableCommand( void ) { + int r, index, l; + + r = clc.reliableSequence - (random() * 5); + index = clc.reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 ); + l = strlen(clc.reliableCommands[ index ]); + if ( l >= MAX_STRING_CHARS - 1 ) { + l = MAX_STRING_CHARS - 2; + } + clc.reliableCommands[ index ][ l ] = '\n'; + clc.reliableCommands[ index ][ l+1 ] = '\0'; +} + +/* +======================================================================= + +CLIENT SIDE DEMO RECORDING + +======================================================================= +*/ + +/* +==================== +CL_WriteDemoMessage + +Dumps the current net message, prefixed by the length +==================== +*/ +void CL_WriteDemoMessage ( msg_t *msg, int headerBytes ) { + int len, swlen; + + // write the packet sequence + len = clc.serverMessageSequence; + swlen = LittleLong( len ); + FS_Write (&swlen, 4, clc.demofile); + + // skip the packet sequencing information + len = msg->cursize - headerBytes; + swlen = LittleLong(len); + FS_Write (&swlen, 4, clc.demofile); + FS_Write ( msg->data + headerBytes, len, clc.demofile ); +} + + +/* +==================== +CL_StopRecording_f + +stop recording a demo +==================== +*/ +void CL_StopRecord_f( void ) { + int len; + + if ( !clc.demorecording ) { + Com_Printf ("Not recording a demo.\n"); + return; + } + + // finish up + len = -1; + FS_Write (&len, 4, clc.demofile); + FS_Write (&len, 4, clc.demofile); + FS_FCloseFile (clc.demofile); + clc.demofile = 0; + clc.demorecording = qfalse; + clc.spDemoRecording = qfalse; + Com_Printf ("Stopped demo.\n"); +} + +/* +================== +CL_DemoFilename +================== +*/ +void CL_DemoFilename( int number, char *fileName ) { + int a,b,c,d; + + if ( number < 0 || number > 9999 ) { + Com_sprintf( fileName, MAX_OSPATH, "demo9999.tga" ); + return; + } + + a = number / 1000; + number -= a*1000; + b = number / 100; + number -= b*100; + c = number / 10; + number -= c*10; + d = number; + + Com_sprintf( fileName, MAX_OSPATH, "demo%i%i%i%i" + , a, b, c, d ); +} + +/* +==================== +CL_Record_f + +record + +Begins recording a demo from the current position +==================== +*/ +static char demoName[MAX_QPATH]; // compiler bug workaround +void CL_Record_f( void ) { + char name[MAX_OSPATH]; + byte bufData[MAX_MSGLEN]; + msg_t buf; + int i; + int len; + entityState_t *ent; + entityState_t nullstate; + char *s; + + if ( Cmd_Argc() > 2 ) { + Com_Printf ("record \n"); + return; + } + + if ( clc.demorecording ) { + if (!clc.spDemoRecording) { + Com_Printf ("Already recording.\n"); + } + return; + } + + if ( cls.state != CA_ACTIVE ) { + Com_Printf ("You must be in a level to record.\n"); + return; + } + + // sync 0 doesn't prevent recording, so not forcing it off .. everyone does g_sync 1 ; record ; g_sync 0 .. + if ( !Cvar_VariableValue( "g_synchronousClients" ) ) { + Com_Printf (S_COLOR_YELLOW "WARNING: You should set 'g_synchronousClients 1' for smoother demo recording\n"); + } + + if ( Cmd_Argc() == 2 ) { + s = Cmd_Argv(1); + Q_strncpyz( demoName, s, sizeof( demoName ) ); + Com_sprintf (name, sizeof(name), "demos/%s.dm_%d", demoName, PROTOCOL_VERSION ); + } else { + int number; + + // scan for a free demo name + for ( number = 0 ; number <= 9999 ; number++ ) { + CL_DemoFilename( number, demoName ); + Com_sprintf (name, sizeof(name), "demos/%s.dm_%d", demoName, PROTOCOL_VERSION ); + + len = FS_ReadFile( name, NULL ); + if ( len <= 0 ) { + break; // file doesn't exist + } + } + } + + // open the demo file + + Com_Printf ("recording to %s.\n", name); + clc.demofile = FS_FOpenFileWrite( name ); + if ( !clc.demofile ) { + Com_Printf ("ERROR: couldn't open.\n"); + return; + } + clc.demorecording = qtrue; + if (Cvar_VariableValue("ui_recordSPDemo")) { + clc.spDemoRecording = qtrue; + } else { + clc.spDemoRecording = qfalse; + } + + + Q_strncpyz( clc.demoName, demoName, sizeof( clc.demoName ) ); + + // don't start saving messages until a non-delta compressed message is received + clc.demowaiting = qtrue; + + // write out the gamestate message + MSG_Init (&buf, bufData, sizeof(bufData)); + MSG_Bitstream(&buf); + + // NOTE, MRE: all server->client messages now acknowledge + MSG_WriteLong( &buf, clc.reliableSequence ); + + MSG_WriteByte (&buf, svc_gamestate); + MSG_WriteLong (&buf, clc.serverCommandSequence ); + + // configstrings + for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { + if ( !cl.gameState.stringOffsets[i] ) { + continue; + } + s = cl.gameState.stringData + cl.gameState.stringOffsets[i]; + MSG_WriteByte (&buf, svc_configstring); + MSG_WriteShort (&buf, i); + MSG_WriteBigString (&buf, s); + } + + // baselines + Com_Memset (&nullstate, 0, sizeof(nullstate)); + for ( i = 0; i < MAX_GENTITIES ; i++ ) { + ent = &cl.entityBaselines[i]; + if ( !ent->number ) { + continue; + } + MSG_WriteByte (&buf, svc_baseline); + MSG_WriteDeltaEntity (&buf, &nullstate, ent, qtrue ); + } + + MSG_WriteByte( &buf, svc_EOF ); + + // finished writing the gamestate stuff + + // write the client num + MSG_WriteLong(&buf, clc.clientNum); + // write the checksum feed + MSG_WriteLong(&buf, clc.checksumFeed); + + // finished writing the client packet + MSG_WriteByte( &buf, svc_EOF ); + + // write it to the demo file + len = LittleLong( clc.serverMessageSequence - 1 ); + FS_Write (&len, 4, clc.demofile); + + len = LittleLong (buf.cursize); + FS_Write (&len, 4, clc.demofile); + FS_Write (buf.data, buf.cursize, clc.demofile); + + // the rest of the demo file will be copied from net messages +} + +/* +======================================================================= + +CLIENT SIDE DEMO PLAYBACK + +======================================================================= +*/ + +/* +================= +CL_DemoCompleted +================= +*/ +void CL_DemoCompleted( void ) { + if (cl_timedemo && cl_timedemo->integer) { + int time; + + time = Sys_Milliseconds() - clc.timeDemoStart; + if ( time > 0 ) { + Com_Printf ("%i frames, %3.1f seconds: %3.1f fps\n", clc.timeDemoFrames, + time/1000.0, clc.timeDemoFrames*1000.0 / time); + } + } + + CL_Disconnect( qtrue ); + CL_NextDemo(); +} + +/* +================= +CL_ReadDemoMessage +================= +*/ +void CL_ReadDemoMessage( void ) { + int r; + msg_t buf; + byte bufData[ MAX_MSGLEN ]; + int s; + + if ( !clc.demofile ) { + CL_DemoCompleted (); + return; + } + + // get the sequence number + r = FS_Read( &s, 4, clc.demofile); + if ( r != 4 ) { + CL_DemoCompleted (); + return; + } + clc.serverMessageSequence = LittleLong( s ); + + // init the message + MSG_Init( &buf, bufData, sizeof( bufData ) ); + + // get the length + r = FS_Read (&buf.cursize, 4, clc.demofile); + if ( r != 4 ) { + CL_DemoCompleted (); + return; + } + buf.cursize = LittleLong( buf.cursize ); + if ( buf.cursize == -1 ) { + CL_DemoCompleted (); + return; + } + if ( buf.cursize > buf.maxsize ) { + Com_Error (ERR_DROP, "CL_ReadDemoMessage: demoMsglen > MAX_MSGLEN"); + } + r = FS_Read( buf.data, buf.cursize, clc.demofile ); + if ( r != buf.cursize ) { + Com_Printf( "Demo file was truncated.\n"); + CL_DemoCompleted (); + return; + } + + clc.lastPacketTime = cls.realtime; + buf.readcount = 0; + CL_ParseServerMessage( &buf ); +} + +/* +==================== +CL_WalkDemoExt +==================== +*/ +static void CL_WalkDemoExt(char *arg, char *name, int *demofile) +{ + int i = 0; + *demofile = 0; + while(demo_protocols[i]) + { + Com_sprintf (name, MAX_OSPATH, "demos/%s.dm_%d", arg, demo_protocols[i]); + FS_FOpenFileRead( name, demofile, qtrue ); + if (*demofile) + { + Com_Printf("Demo file: %s\n", name); + break; + } + else + Com_Printf("Not found: %s\n", name); + i++; + } +} + +/* +==================== +CL_PlayDemo_f + +demo + +==================== +*/ +void CL_PlayDemo_f( void ) { + char name[MAX_OSPATH]; + char *arg, *ext_test; + int protocol, i; + char retry[MAX_OSPATH]; + + if (Cmd_Argc() != 2) { + Com_Printf ("playdemo \n"); + return; + } + + // make sure a local server is killed + Cvar_Set( "sv_killserver", "1" ); + + CL_Disconnect( qtrue ); + + // open the demo file + arg = Cmd_Argv(1); + + // check for an extension .dm_?? (?? is protocol) + ext_test = arg + strlen(arg) - 6; + if ((strlen(arg) > 6) && (ext_test[0] == '.') && ((ext_test[1] == 'd') || (ext_test[1] == 'D')) && ((ext_test[2] == 'm') || (ext_test[2] == 'M')) && (ext_test[3] == '_')) + { + protocol = atoi(ext_test+4); + i=0; + while(demo_protocols[i]) + { + if (demo_protocols[i] == protocol) + break; + i++; + } + if (demo_protocols[i]) + { + Com_sprintf (name, sizeof(name), "demos/%s", arg); + FS_FOpenFileRead( name, &clc.demofile, qtrue ); + } else { + Com_Printf("Protocol %d not supported for demos\n", protocol); + Q_strncpyz(retry, arg, sizeof(retry)); + retry[strlen(retry)-6] = 0; + CL_WalkDemoExt( retry, name, &clc.demofile ); + } + } else { + CL_WalkDemoExt( arg, name, &clc.demofile ); + } + + if (!clc.demofile) { + Com_Error( ERR_DROP, "couldn't open %s", name); + return; + } + Q_strncpyz( clc.demoName, Cmd_Argv(1), sizeof( clc.demoName ) ); + + Con_Close(); + + cls.state = CA_CONNECTED; + clc.demoplaying = qtrue; + Q_strncpyz( cls.servername, Cmd_Argv(1), sizeof( cls.servername ) ); + + // read demo messages until connected + while ( cls.state >= CA_CONNECTED && cls.state < CA_PRIMED ) { + CL_ReadDemoMessage(); + } + // don't get the first snapshot this frame, to prevent the long + // time from the gamestate load from messing causing a time skip + clc.firstDemoFrameSkipped = qfalse; +} + + +/* +==================== +CL_StartDemoLoop + +Closing the main menu will restart the demo loop +==================== +*/ +void CL_StartDemoLoop( void ) { + // start the demo loop again + Cbuf_AddText ("d1\n"); + cls.keyCatchers = 0; +} + +/* +================== +CL_NextDemo + +Called when a demo or cinematic finishes +If the "nextdemo" cvar is set, that command will be issued +================== +*/ +void CL_NextDemo( void ) { + char v[MAX_STRING_CHARS]; + + Q_strncpyz( v, Cvar_VariableString ("nextdemo"), sizeof(v) ); + v[MAX_STRING_CHARS-1] = 0; + Com_DPrintf("CL_NextDemo: %s\n", v ); + if (!v[0]) { + return; + } + + Cvar_Set ("nextdemo",""); + Cbuf_AddText (v); + Cbuf_AddText ("\n"); + Cbuf_Execute(); +} + + +//====================================================================== + +/* +===================== +CL_ShutdownAll +===================== +*/ +void CL_ShutdownAll(void) { + + // clear sounds + S_DisableSounds(); + // shutdown CGame + CL_ShutdownCGame(); + // shutdown UI + CL_ShutdownUI(); + + // shutdown the renderer + if ( re.Shutdown ) { + re.Shutdown( qfalse ); // don't destroy window or context + } + + cls.uiStarted = qfalse; + cls.cgameStarted = qfalse; + cls.rendererStarted = qfalse; + cls.soundRegistered = qfalse; +} + +/* +================= +CL_FlushMemory + +Called by CL_MapLoading, CL_Connect_f, CL_PlayDemo_f, and CL_ParseGamestate the only +ways a client gets into a game +Also called by Com_Error +================= +*/ +void CL_FlushMemory( void ) { + + // shutdown all the client stuff + CL_ShutdownAll(); + + // if not running a server clear the whole hunk + if ( !com_sv_running->integer ) { + // clear the whole hunk + Hunk_Clear(); + // clear collision map data + CM_ClearMap(); + } + else { + // clear all the client data on the hunk + Hunk_ClearToMark(); + } + + CL_StartHunkUsers(); +} + +/* +===================== +CL_MapLoading + +A local server is starting to load a map, so update the +screen to let the user know about it, then dump all client +memory on the hunk from cgame, ui, and renderer +===================== +*/ +void CL_MapLoading( void ) { + if ( !com_cl_running->integer ) { + return; + } + + Con_Close(); + cls.keyCatchers = 0; + + // if we are already connected to the local host, stay connected + if ( cls.state >= CA_CONNECTED && !Q_stricmp( cls.servername, "localhost" ) ) { + cls.state = CA_CONNECTED; // so the connect screen is drawn + Com_Memset( cls.updateInfoString, 0, sizeof( cls.updateInfoString ) ); + Com_Memset( clc.serverMessage, 0, sizeof( clc.serverMessage ) ); + Com_Memset( &cl.gameState, 0, sizeof( cl.gameState ) ); + clc.lastPacketSentTime = -9999; + SCR_UpdateScreen(); + } else { + // clear nextmap so the cinematic shutdown doesn't execute it + Cvar_Set( "nextmap", "" ); + CL_Disconnect( qtrue ); + Q_strncpyz( cls.servername, "localhost", sizeof(cls.servername) ); + cls.state = CA_CHALLENGING; // so the connect screen is drawn + cls.keyCatchers = 0; + SCR_UpdateScreen(); + clc.connectTime = -RETRANSMIT_TIMEOUT; + NET_StringToAdr( cls.servername, &clc.serverAddress); + // we don't need a challenge on the localhost + + CL_CheckForResend(); + } +} + +/* +===================== +CL_ClearState + +Called before parsing a gamestate +===================== +*/ +void CL_ClearState (void) { + +// S_StopAllSounds(); + + Com_Memset( &cl, 0, sizeof( cl ) ); +} + + +/* +===================== +CL_Disconnect + +Called when a connection, demo, or cinematic is being terminated. +Goes from a connected state to either a menu state or a console state +Sends a disconnect message to the server +This is also called on Com_Error and Com_Quit, so it shouldn't cause any errors +===================== +*/ +void CL_Disconnect( qboolean showMainMenu ) { + if ( !com_cl_running || !com_cl_running->integer ) { + return; + } + + // shutting down the client so enter full screen ui mode + Cvar_Set("r_uiFullScreen", "1"); + + if ( clc.demorecording ) { + CL_StopRecord_f (); + } + + if (clc.download) { + FS_FCloseFile( clc.download ); + clc.download = 0; + } + *clc.downloadTempName = *clc.downloadName = 0; + Cvar_Set( "cl_downloadName", "" ); + + if ( clc.demofile ) { + FS_FCloseFile( clc.demofile ); + clc.demofile = 0; + } + + if ( uivm && showMainMenu ) { + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_NONE ); + } + + SCR_StopCinematic (); + S_ClearSoundBuffer(); + + // send a disconnect message to the server + // send it a few times in case one is dropped + if ( cls.state >= CA_CONNECTED ) { + CL_AddReliableCommand( "disconnect" ); + CL_WritePacket(); + CL_WritePacket(); + CL_WritePacket(); + } + + CL_ClearState (); + + // wipe the client connection + Com_Memset( &clc, 0, sizeof( clc ) ); + + cls.state = CA_DISCONNECTED; + + // allow cheats locally + Cvar_Set( "sv_cheats", "1" ); + + // not connected to a pure server anymore + cl_connectedToPureServer = qfalse; +} + + +/* +=================== +CL_ForwardCommandToServer + +adds the current command line as a clientCommand +things like godmode, noclip, etc, are commands directed to the server, +so when they are typed in at the console, they will need to be forwarded. +=================== +*/ +void CL_ForwardCommandToServer( const char *string ) { + char *cmd; + + cmd = Cmd_Argv(0); + + // ignore key up commands + if ( cmd[0] == '-' ) { + return; + } + + if ( clc.demoplaying || cls.state < CA_CONNECTED || cmd[0] == '+' ) { + Com_Printf ("Unknown command \"%s\"\n", cmd); + return; + } + + if ( Cmd_Argc() > 1 ) { + CL_AddReliableCommand( string ); + } else { + CL_AddReliableCommand( cmd ); + } +} + +/* +=================== +CL_RequestMotd + +=================== +*/ +void CL_RequestMotd( void ) { + char info[MAX_INFO_STRING]; + + if ( !cl_motd->integer ) { + return; + } + Com_Printf( "Resolving %s\n", UPDATE_SERVER_NAME ); + if ( !NET_StringToAdr( UPDATE_SERVER_NAME, &cls.updateServer ) ) { + Com_Printf( "Couldn't resolve address\n" ); + return; + } + cls.updateServer.port = BigShort( PORT_UPDATE ); + Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", UPDATE_SERVER_NAME, + cls.updateServer.ip[0], cls.updateServer.ip[1], + cls.updateServer.ip[2], cls.updateServer.ip[3], + BigShort( cls.updateServer.port ) ); + + info[0] = 0; + // NOTE TTimo xoring against Com_Milliseconds, otherwise we may not have a true randomization + // only srand I could catch before here is tr_noise.c l:26 srand(1001) + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=382 + // NOTE: the Com_Milliseconds xoring only affects the lower 16-bit word, + // but I decided it was enough randomization + Com_sprintf( cls.updateChallenge, sizeof( cls.updateChallenge ), "%i", ((rand() << 16) ^ rand()) ^ Com_Milliseconds()); + + Info_SetValueForKey( info, "challenge", cls.updateChallenge ); + Info_SetValueForKey( info, "renderer", cls.glconfig.renderer_string ); + Info_SetValueForKey( info, "version", com_version->string ); + + NET_OutOfBandPrint( NS_CLIENT, cls.updateServer, "getmotd \"%s\"\n", info ); +} + +/* +=================== +CL_RequestAuthorization + +Authorization server protocol +----------------------------- + +All commands are text in Q3 out of band packets (leading 0xff 0xff 0xff 0xff). + +Whenever the client tries to get a challenge from the server it wants to +connect to, it also blindly fires off a packet to the authorize server: + +getKeyAuthorize + +cdkey may be "demo" + + +#OLD The authorize server returns a: +#OLD +#OLD keyAthorize +#OLD +#OLD A client will be accepted if the cdkey is valid and it has not been used by any other IP +#OLD address in the last 15 minutes. + + +The server sends a: + +getIpAuthorize + +The authorize server returns a: + +ipAuthorize + +A client will be accepted if a valid cdkey was sent by that ip (only) in the last 15 minutes. +If no response is received from the authorize server after two tries, the client will be let +in anyway. +=================== +*/ +void CL_RequestAuthorization( void ) { + char nums[64]; + int i, j, l; + cvar_t *fs; + + if ( !cls.authorizeServer.port ) { + Com_Printf( "Resolving %s\n", AUTHORIZE_SERVER_NAME ); + if ( !NET_StringToAdr( AUTHORIZE_SERVER_NAME, &cls.authorizeServer ) ) { + Com_Printf( "Couldn't resolve address\n" ); + return; + } + + cls.authorizeServer.port = BigShort( PORT_AUTHORIZE ); + Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", AUTHORIZE_SERVER_NAME, + cls.authorizeServer.ip[0], cls.authorizeServer.ip[1], + cls.authorizeServer.ip[2], cls.authorizeServer.ip[3], + BigShort( cls.authorizeServer.port ) ); + } + if ( cls.authorizeServer.type == NA_BAD ) { + return; + } + + if ( Cvar_VariableValue( "fs_restrict" ) ) { + Q_strncpyz( nums, "demota", sizeof( nums ) ); + } else { + // only grab the alphanumeric values from the cdkey, to avoid any dashes or spaces + j = 0; + l = strlen( cl_cdkey ); + if ( l > 32 ) { + l = 32; + } + for ( i = 0 ; i < l ; i++ ) { + if ( ( cl_cdkey[i] >= '0' && cl_cdkey[i] <= '9' ) + || ( cl_cdkey[i] >= 'a' && cl_cdkey[i] <= 'z' ) + || ( cl_cdkey[i] >= 'A' && cl_cdkey[i] <= 'Z' ) + ) { + nums[j] = cl_cdkey[i]; + j++; + } + } + nums[j] = 0; + } + + fs = Cvar_Get ("cl_anonymous", "0", CVAR_INIT|CVAR_SYSTEMINFO ); + + NET_OutOfBandPrint(NS_CLIENT, cls.authorizeServer, va("getKeyAuthorize %i %s", fs->integer, nums) ); +} + +/* +====================================================================== + +CONSOLE COMMANDS + +====================================================================== +*/ + +/* +================== +CL_ForwardToServer_f +================== +*/ +void CL_ForwardToServer_f( void ) { + if ( cls.state != CA_ACTIVE || clc.demoplaying ) { + Com_Printf ("Not connected to a server.\n"); + return; + } + + // don't forward the first argument + if ( Cmd_Argc() > 1 ) { + CL_AddReliableCommand( Cmd_Args() ); + } +} + +/* +================== +CL_Setenv_f + +Mostly for controlling voodoo environment variables +================== +*/ +void CL_Setenv_f( void ) { + int argc = Cmd_Argc(); + + if ( argc > 2 ) { + char buffer[1024]; + int i; + + strcpy( buffer, Cmd_Argv(1) ); + strcat( buffer, "=" ); + + for ( i = 2; i < argc; i++ ) { + strcat( buffer, Cmd_Argv( i ) ); + strcat( buffer, " " ); + } + + putenv( buffer ); + } else if ( argc == 2 ) { + char *env = getenv( Cmd_Argv(1) ); + + if ( env ) { + Com_Printf( "%s=%s\n", Cmd_Argv(1), env ); + } else { + Com_Printf( "%s undefined\n", Cmd_Argv(1), env ); + } + } +} + + +/* +================== +CL_Disconnect_f +================== +*/ +void CL_Disconnect_f( void ) { + SCR_StopCinematic(); + Cvar_Set("ui_singlePlayerActive", "0"); + if ( cls.state != CA_DISCONNECTED && cls.state != CA_CINEMATIC ) { + Com_Error (ERR_DISCONNECT, "Disconnected from server"); + } +} + + +/* +================ +CL_Reconnect_f + +================ +*/ +void CL_Reconnect_f( void ) { + if ( !strlen( cls.servername ) || !strcmp( cls.servername, "localhost" ) ) { + Com_Printf( "Can't reconnect to localhost.\n" ); + return; + } + Cvar_Set("ui_singlePlayerActive", "0"); + Cbuf_AddText( va("connect %s\n", cls.servername ) ); +} + +/* +================ +CL_Connect_f + +================ +*/ +void CL_Connect_f( void ) { + char *server; + + if ( Cmd_Argc() != 2 ) { + Com_Printf( "usage: connect [server]\n"); + return; + } + + Cvar_Set("ui_singlePlayerActive", "0"); + + // fire a message off to the motd server + CL_RequestMotd(); + + // clear any previous "server full" type messages + clc.serverMessage[0] = 0; + + server = Cmd_Argv (1); + + if ( com_sv_running->integer && !strcmp( server, "localhost" ) ) { + // if running a local server, kill it + SV_Shutdown( "Server quit\n" ); + } + + // make sure a local server is killed + Cvar_Set( "sv_killserver", "1" ); + SV_Frame( 0 ); + + CL_Disconnect( qtrue ); + Con_Close(); + + /* MrE: 2000-09-13: now called in CL_DownloadsComplete + CL_FlushMemory( ); + */ + + Q_strncpyz( cls.servername, server, sizeof(cls.servername) ); + + if (!NET_StringToAdr( cls.servername, &clc.serverAddress) ) { + Com_Printf ("Bad server address\n"); + cls.state = CA_DISCONNECTED; + return; + } + if (clc.serverAddress.port == 0) { + clc.serverAddress.port = BigShort( PORT_SERVER ); + } + Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", cls.servername, + clc.serverAddress.ip[0], clc.serverAddress.ip[1], + clc.serverAddress.ip[2], clc.serverAddress.ip[3], + BigShort( clc.serverAddress.port ) ); + + // if we aren't playing on a lan, we need to authenticate + // with the cd key + if ( NET_IsLocalAddress( clc.serverAddress ) ) { + cls.state = CA_CHALLENGING; + } else { + cls.state = CA_CONNECTING; + } + + cls.keyCatchers = 0; + clc.connectTime = -99999; // CL_CheckForResend() will fire immediately + clc.connectPacketCount = 0; + + // server connection string + Cvar_Set( "cl_currentServerAddress", server ); +} + + +/* +===================== +CL_Rcon_f + + Send the rest of the command line over as + an unconnected command. +===================== +*/ +void CL_Rcon_f( void ) { + char message[1024]; + netadr_t to; + + if ( !rcon_client_password->string ) { + Com_Printf ("You must set 'rconpassword' before\n" + "issuing an rcon command.\n"); + return; + } + + message[0] = -1; + message[1] = -1; + message[2] = -1; + message[3] = -1; + message[4] = 0; + + strcat (message, "rcon "); + + strcat (message, rcon_client_password->string); + strcat (message, " "); + + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=543 + strcat (message, Cmd_Cmd()+5); + + if ( cls.state >= CA_CONNECTED ) { + to = clc.netchan.remoteAddress; + } else { + if (!strlen(rconAddress->string)) { + Com_Printf ("You must either be connected,\n" + "or set the 'rconAddress' cvar\n" + "to issue rcon commands\n"); + + return; + } + NET_StringToAdr (rconAddress->string, &to); + if (to.port == 0) { + to.port = BigShort (PORT_SERVER); + } + } + + NET_SendPacket (NS_CLIENT, strlen(message)+1, message, to); +} + +/* +================= +CL_SendPureChecksums +================= +*/ +void CL_SendPureChecksums( void ) { + const char *pChecksums; + char cMsg[MAX_INFO_VALUE]; + int i; + + // if we are pure we need to send back a command with our referenced pk3 checksums + pChecksums = FS_ReferencedPakPureChecksums(); + + // "cp" + // "Yf" + Com_sprintf(cMsg, sizeof(cMsg), "Yf "); + Q_strcat(cMsg, sizeof(cMsg), va("%d ", cl.serverId) ); + Q_strcat(cMsg, sizeof(cMsg), pChecksums); + for (i = 0; i < 2; i++) { + cMsg[i] += 10; + } + CL_AddReliableCommand( cMsg ); +} + +/* +================= +CL_ResetPureClientAtServer +================= +*/ +void CL_ResetPureClientAtServer( void ) { + CL_AddReliableCommand( va("vdr") ); +} + +/* +================= +CL_Vid_Restart_f + +Restart the video subsystem + +we also have to reload the UI and CGame because the renderer +doesn't know what graphics to reload +================= +*/ +void CL_Vid_Restart_f( void ) { + + // don't let them loop during the restart + S_StopAllSounds(); + // shutdown the UI + CL_ShutdownUI(); + // shutdown the CGame + CL_ShutdownCGame(); + // shutdown the renderer and clear the renderer interface + CL_ShutdownRef(); + // client is no longer pure untill new checksums are sent + CL_ResetPureClientAtServer(); + // clear pak references + FS_ClearPakReferences( FS_UI_REF | FS_CGAME_REF ); + // reinitialize the filesystem if the game directory or checksum has changed + FS_ConditionalRestart( clc.checksumFeed ); + + cls.rendererStarted = qfalse; + cls.uiStarted = qfalse; + cls.cgameStarted = qfalse; + cls.soundRegistered = qfalse; + + // unpause so the cgame definately gets a snapshot and renders a frame + Cvar_Set( "cl_paused", "0" ); + + // if not running a server clear the whole hunk + if ( !com_sv_running->integer ) { + // clear the whole hunk + Hunk_Clear(); + } + else { + // clear all the client data on the hunk + Hunk_ClearToMark(); + } + + // initialize the renderer interface + CL_InitRef(); + + // startup all the client stuff + CL_StartHunkUsers(); + + // start the cgame if connected + if ( cls.state > CA_CONNECTED && cls.state != CA_CINEMATIC ) { + cls.cgameStarted = qtrue; + CL_InitCGame(); + // send pure checksums + CL_SendPureChecksums(); + } +} + +/* +================= +CL_Snd_Restart_f + +Restart the sound subsystem +The cgame and game must also be forced to restart because +handles will be invalid +================= +*/ +void CL_Snd_Restart_f( void ) { + S_Shutdown(); + S_Init(); + + CL_Vid_Restart_f(); +} + + +/* +================== +CL_PK3List_f +================== +*/ +void CL_OpenedPK3List_f( void ) { + Com_Printf("Opened PK3 Names: %s\n", FS_LoadedPakNames()); +} + +/* +================== +CL_PureList_f +================== +*/ +void CL_ReferencedPK3List_f( void ) { + Com_Printf("Referenced PK3 Names: %s\n", FS_ReferencedPakNames()); +} + +/* +================== +CL_Configstrings_f +================== +*/ +void CL_Configstrings_f( void ) { + int i; + int ofs; + + if ( cls.state != CA_ACTIVE ) { + Com_Printf( "Not connected to a server.\n"); + return; + } + + for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { + ofs = cl.gameState.stringOffsets[ i ]; + if ( !ofs ) { + continue; + } + Com_Printf( "%4i: %s\n", i, cl.gameState.stringData + ofs ); + } +} + +/* +============== +CL_Clientinfo_f +============== +*/ +void CL_Clientinfo_f( void ) { + Com_Printf( "--------- Client Information ---------\n" ); + Com_Printf( "state: %i\n", cls.state ); + Com_Printf( "Server: %s\n", cls.servername ); + Com_Printf ("User info settings:\n"); + Info_Print( Cvar_InfoString( CVAR_USERINFO ) ); + Com_Printf( "--------------------------------------\n" ); +} + + +//==================================================================== + +/* +================= +CL_DownloadsComplete + +Called when all downloading has been completed +================= +*/ +void CL_DownloadsComplete( void ) { + + // if we downloaded files we need to restart the file system + if (clc.downloadRestart) { + clc.downloadRestart = qfalse; + + FS_Restart(clc.checksumFeed); // We possibly downloaded a pak, restart the file system to load it + + // inform the server so we get new gamestate info + CL_AddReliableCommand( "donedl" ); + + // by sending the donedl command we request a new gamestate + // so we don't want to load stuff yet + return; + } + + // let the client game init and load data + cls.state = CA_LOADING; + + // Pump the loop, this may change gamestate! + Com_EventLoop(); + + // if the gamestate was changed by calling Com_EventLoop + // then we loaded everything already and we don't want to do it again. + if ( cls.state != CA_LOADING ) { + return; + } + + // starting to load a map so we get out of full screen ui mode + Cvar_Set("r_uiFullScreen", "0"); + + // flush client memory and start loading stuff + // this will also (re)load the UI + // if this is a local client then only the client part of the hunk + // will be cleared, note that this is done after the hunk mark has been set + CL_FlushMemory(); + + // initialize the CGame + cls.cgameStarted = qtrue; + CL_InitCGame(); + + // set pure checksums + CL_SendPureChecksums(); + + CL_WritePacket(); + CL_WritePacket(); + CL_WritePacket(); +} + +/* +================= +CL_BeginDownload + +Requests a file to download from the server. Stores it in the current +game directory. +================= +*/ +void CL_BeginDownload( const char *localName, const char *remoteName ) { + + Com_DPrintf("***** CL_BeginDownload *****\n" + "Localname: %s\n" + "Remotename: %s\n" + "****************************\n", localName, remoteName); + + Q_strncpyz ( clc.downloadName, localName, sizeof(clc.downloadName) ); + Com_sprintf( clc.downloadTempName, sizeof(clc.downloadTempName), "%s.tmp", localName ); + + // Set so UI gets access to it + Cvar_Set( "cl_downloadName", remoteName ); + Cvar_Set( "cl_downloadSize", "0" ); + Cvar_Set( "cl_downloadCount", "0" ); + Cvar_SetValue( "cl_downloadTime", cls.realtime ); + + clc.downloadBlock = 0; // Starting new file + clc.downloadCount = 0; + + CL_AddReliableCommand( va("download %s", remoteName) ); +} + +/* +================= +CL_NextDownload + +A download completed or failed +================= +*/ +void CL_NextDownload(void) { + char *s; + char *remoteName, *localName; + + // We are looking to start a download here + if (*clc.downloadList) { + s = clc.downloadList; + + // format is: + // @remotename@localname@remotename@localname, etc. + + if (*s == '@') + s++; + remoteName = s; + + if ( (s = strchr(s, '@')) == NULL ) { + CL_DownloadsComplete(); + return; + } + + *s++ = 0; + localName = s; + if ( (s = strchr(s, '@')) != NULL ) + *s++ = 0; + else + s = localName + strlen(localName); // point at the nul byte + + CL_BeginDownload( localName, remoteName ); + + clc.downloadRestart = qtrue; + + // move over the rest + memmove( clc.downloadList, s, strlen(s) + 1); + + return; + } + + CL_DownloadsComplete(); +} + +/* +================= +CL_InitDownloads + +After receiving a valid game state, we valid the cgame and local zip files here +and determine if we need to download them +================= +*/ +void CL_InitDownloads(void) { + char missingfiles[1024]; + + if ( !cl_allowDownload->integer ) + { + // autodownload is disabled on the client + // but it's possible that some referenced files on the server are missing + if (FS_ComparePaks( missingfiles, sizeof( missingfiles ), qfalse ) ) + { + // NOTE TTimo I would rather have that printed as a modal message box + // but at this point while joining the game we don't know wether we will successfully join or not + Com_Printf( "\nWARNING: You are missing some files referenced by the server:\n%s" + "You might not be able to join the game\n" + "Go to the setting menu to turn on autodownload, or get the file elsewhere\n\n", missingfiles ); + } + } + else if ( FS_ComparePaks( clc.downloadList, sizeof( clc.downloadList ) , qtrue ) ) { + + Com_Printf("Need paks: %s\n", clc.downloadList ); + + if ( *clc.downloadList ) { + // if autodownloading is not enabled on the server + cls.state = CA_CONNECTED; + CL_NextDownload(); + return; + } + + } + + CL_DownloadsComplete(); +} + +/* +================= +CL_CheckForResend + +Resend a connect message if the last one has timed out +================= +*/ +void CL_CheckForResend( void ) { + int port, i; + char info[MAX_INFO_STRING]; + char data[MAX_INFO_STRING]; + + // don't send anything if playing back a demo + if ( clc.demoplaying ) { + return; + } + + // resend if we haven't gotten a reply yet + if ( cls.state != CA_CONNECTING && cls.state != CA_CHALLENGING ) { + return; + } + + if ( cls.realtime - clc.connectTime < RETRANSMIT_TIMEOUT ) { + return; + } + + clc.connectTime = cls.realtime; // for retransmit requests + clc.connectPacketCount++; + + + switch ( cls.state ) { + case CA_CONNECTING: + // requesting a challenge + if ( !Sys_IsLANAddress( clc.serverAddress ) ) { + CL_RequestAuthorization(); + } + NET_OutOfBandPrint(NS_CLIENT, clc.serverAddress, "getchallenge"); + break; + + case CA_CHALLENGING: + // sending back the challenge + port = Cvar_VariableValue ("net_qport"); + + Q_strncpyz( info, Cvar_InfoString( CVAR_USERINFO ), sizeof( info ) ); + Info_SetValueForKey( info, "protocol", va("%i", PROTOCOL_VERSION ) ); + Info_SetValueForKey( info, "qport", va("%i", port ) ); + Info_SetValueForKey( info, "challenge", va("%i", clc.challenge ) ); + + strcpy(data, "connect "); + // TTimo adding " " around the userinfo string to avoid truncated userinfo on the server + // (Com_TokenizeString tokenizes around spaces) + data[8] = '"'; + + for(i=0;iadr.type = NA_IP; + server->adr.ip[0] = address->ip[0]; + server->adr.ip[1] = address->ip[1]; + server->adr.ip[2] = address->ip[2]; + server->adr.ip[3] = address->ip[3]; + server->adr.port = address->port; + server->clients = 0; + server->hostName[0] = '\0'; + server->mapName[0] = '\0'; + server->maxClients = 0; + server->maxPing = 0; + server->minPing = 0; + server->ping = -1; + server->game[0] = '\0'; + server->gameType = 0; + server->netType = 0; +} + +#define MAX_SERVERSPERPACKET 256 + +/* +=================== +CL_ServersResponsePacket +=================== +*/ +void CL_ServersResponsePacket( netadr_t from, msg_t *msg ) { + int i, count, max, total; + serverAddress_t addresses[MAX_SERVERSPERPACKET]; + int numservers; + byte* buffptr; + byte* buffend; + + Com_Printf("CL_ServersResponsePacket\n"); + + if (cls.numglobalservers == -1) { + // state to detect lack of servers or lack of response + cls.numglobalservers = 0; + cls.numGlobalServerAddresses = 0; + } + + if (cls.nummplayerservers == -1) { + cls.nummplayerservers = 0; + } + + // parse through server response string + numservers = 0; + buffptr = msg->data; + buffend = buffptr + msg->cursize; + while (buffptr+1 < buffend) { + // advance to initial token + do { + if (*buffptr++ == '\\') + break; + } + while (buffptr < buffend); + + if ( buffptr >= buffend - 6 ) { + break; + } + + // parse out ip + addresses[numservers].ip[0] = *buffptr++; + addresses[numservers].ip[1] = *buffptr++; + addresses[numservers].ip[2] = *buffptr++; + addresses[numservers].ip[3] = *buffptr++; + + // parse out port + addresses[numservers].port = (*buffptr++)<<8; + addresses[numservers].port += *buffptr++; + addresses[numservers].port = BigShort( addresses[numservers].port ); + + // syntax check + if (*buffptr != '\\') { + break; + } + + Com_DPrintf( "server: %d ip: %d.%d.%d.%d:%d\n",numservers, + addresses[numservers].ip[0], + addresses[numservers].ip[1], + addresses[numservers].ip[2], + addresses[numservers].ip[3], + addresses[numservers].port ); + + numservers++; + if (numservers >= MAX_SERVERSPERPACKET) { + break; + } + + // parse out EOT + if (buffptr[1] == 'E' && buffptr[2] == 'O' && buffptr[3] == 'T') { + break; + } + } + + if (cls.masterNum == 0) { + count = cls.numglobalservers; + max = MAX_GLOBAL_SERVERS; + } else { + count = cls.nummplayerservers; + max = MAX_OTHER_SERVERS; + } + + for (i = 0; i < numservers && count < max; i++) { + // build net address + serverInfo_t *server = (cls.masterNum == 0) ? &cls.globalServers[count] : &cls.mplayerServers[count]; + + CL_InitServerInfo( server, &addresses[i] ); + // advance to next slot + count++; + } + + // if getting the global list + if (cls.masterNum == 0) { + if ( cls.numGlobalServerAddresses < MAX_GLOBAL_SERVERS ) { + // if we couldn't store the servers in the main list anymore + for (; i < numservers && count >= max; i++) { + serverAddress_t *addr; + // just store the addresses in an additional list + addr = &cls.globalServerAddresses[cls.numGlobalServerAddresses++]; + addr->ip[0] = addresses[i].ip[0]; + addr->ip[1] = addresses[i].ip[1]; + addr->ip[2] = addresses[i].ip[2]; + addr->ip[3] = addresses[i].ip[3]; + addr->port = addresses[i].port; + } + } + } + + if (cls.masterNum == 0) { + cls.numglobalservers = count; + total = count + cls.numGlobalServerAddresses; + } else { + cls.nummplayerservers = count; + total = count; + } + + Com_Printf("%d servers parsed (total %d)\n", numservers, total); +} + +/* +================= +CL_ConnectionlessPacket + +Responses to broadcasts, etc +================= +*/ +void CL_ConnectionlessPacket( netadr_t from, msg_t *msg ) { + char *s; + char *c; + + MSG_BeginReadingOOB( msg ); + MSG_ReadLong( msg ); // skip the -1 + + s = MSG_ReadStringLine( msg ); + + Cmd_TokenizeString( s ); + + c = Cmd_Argv(0); + + Com_DPrintf ("CL packet %s: %s\n", NET_AdrToString(from), c); + + // challenge from the server we are connecting to + if ( !Q_stricmp(c, "challengeResponse") ) { + if ( cls.state != CA_CONNECTING ) { + Com_Printf( "Unwanted challenge response received. Ignored.\n" ); + } else { + // start sending challenge repsonse instead of challenge request packets + clc.challenge = atoi(Cmd_Argv(1)); + cls.state = CA_CHALLENGING; + clc.connectPacketCount = 0; + clc.connectTime = -99999; + + // take this address as the new server address. This allows + // a server proxy to hand off connections to multiple servers + clc.serverAddress = from; + Com_DPrintf ("challengeResponse: %d\n", clc.challenge); + } + return; + } + + // server connection + if ( !Q_stricmp(c, "connectResponse") ) { + if ( cls.state >= CA_CONNECTED ) { + Com_Printf ("Dup connect received. Ignored.\n"); + return; + } + if ( cls.state != CA_CHALLENGING ) { + Com_Printf ("connectResponse packet while not connecting. Ignored.\n"); + return; + } + if ( !NET_CompareBaseAdr( from, clc.serverAddress ) ) { + Com_Printf( "connectResponse from a different address. Ignored.\n" ); + Com_Printf( "%s should have been %s\n", NET_AdrToString( from ), + NET_AdrToString( clc.serverAddress ) ); + return; + } + Netchan_Setup (NS_CLIENT, &clc.netchan, from, Cvar_VariableValue( "net_qport" ) ); + cls.state = CA_CONNECTED; + clc.lastPacketSentTime = -9999; // send first packet immediately + return; + } + + // server responding to an info broadcast + if ( !Q_stricmp(c, "infoResponse") ) { + CL_ServerInfoPacket( from, msg ); + return; + } + + // server responding to a get playerlist + if ( !Q_stricmp(c, "statusResponse") ) { + CL_ServerStatusResponse( from, msg ); + return; + } + + // a disconnect message from the server, which will happen if the server + // dropped the connection but it is still getting packets from us + if (!Q_stricmp(c, "disconnect")) { + CL_DisconnectPacket( from ); + return; + } + + // echo request from server + if ( !Q_stricmp(c, "echo") ) { + NET_OutOfBandPrint( NS_CLIENT, from, "%s", Cmd_Argv(1) ); + return; + } + + // cd check + if ( !Q_stricmp(c, "keyAuthorize") ) { + // we don't use these now, so dump them on the floor + return; + } + + // global MOTD from id + if ( !Q_stricmp(c, "motd") ) { + CL_MotdPacket( from ); + return; + } + + // echo request from server + if ( !Q_stricmp(c, "print") ) { + s = MSG_ReadString( msg ); + Q_strncpyz( clc.serverMessage, s, sizeof( clc.serverMessage ) ); + Com_Printf( "%s", s ); + return; + } + + // echo request from server + if ( !Q_strncmp(c, "getserversResponse", 18) ) { + CL_ServersResponsePacket( from, msg ); + return; + } + + Com_DPrintf ("Unknown connectionless packet command.\n"); +} + + +/* +================= +CL_PacketEvent + +A packet has arrived from the main event loop +================= +*/ +void CL_PacketEvent( netadr_t from, msg_t *msg ) { + int headerBytes; + + clc.lastPacketTime = cls.realtime; + + if ( msg->cursize >= 4 && *(int *)msg->data == -1 ) { + CL_ConnectionlessPacket( from, msg ); + return; + } + + if ( cls.state < CA_CONNECTED ) { + return; // can't be a valid sequenced packet + } + + if ( msg->cursize < 4 ) { + Com_Printf ("%s: Runt packet\n",NET_AdrToString( from )); + return; + } + + // + // packet from server + // + if ( !NET_CompareAdr( from, clc.netchan.remoteAddress ) ) { + Com_DPrintf ("%s:sequenced packet without connection\n" + ,NET_AdrToString( from ) ); + // FIXME: send a client disconnect? + return; + } + + if (!CL_Netchan_Process( &clc.netchan, msg) ) { + return; // out of order, duplicated, etc + } + + // the header is different lengths for reliable and unreliable messages + headerBytes = msg->readcount; + + // track the last message received so it can be returned in + // client messages, allowing the server to detect a dropped + // gamestate + clc.serverMessageSequence = LittleLong( *(int *)msg->data ); + + clc.lastPacketTime = cls.realtime; + CL_ParseServerMessage( msg ); + + // + // we don't know if it is ok to save a demo message until + // after we have parsed the frame + // + if ( clc.demorecording && !clc.demowaiting ) { + CL_WriteDemoMessage( msg, headerBytes ); + } +} + +/* +================== +CL_CheckTimeout + +================== +*/ +void CL_CheckTimeout( void ) { + // + // check timeout + // + if ( ( !cl_paused->integer || !sv_paused->integer ) + && cls.state >= CA_CONNECTED && cls.state != CA_CINEMATIC + && cls.realtime - clc.lastPacketTime > cl_timeout->value*1000) { + if (++cl.timeoutcount > 5) { // timeoutcount saves debugger + Com_Printf ("\nServer connection timed out.\n"); + CL_Disconnect( qtrue ); + return; + } + } else { + cl.timeoutcount = 0; + } +} + + +//============================================================================ + +/* +================== +CL_CheckUserinfo + +================== +*/ +void CL_CheckUserinfo( void ) { + // don't add reliable commands when not yet connected + if ( cls.state < CA_CHALLENGING ) { + return; + } + // don't overflow the reliable command buffer when paused + if ( cl_paused->integer ) { + return; + } + // send a reliable userinfo update if needed + if ( cvar_modifiedFlags & CVAR_USERINFO ) { + cvar_modifiedFlags &= ~CVAR_USERINFO; + CL_AddReliableCommand( va("userinfo \"%s\"", Cvar_InfoString( CVAR_USERINFO ) ) ); + } + +} + +/* +================== +CL_Frame + +================== +*/ +void CL_Frame ( int msec ) { + + if ( !com_cl_running->integer ) { + return; + } + + if ( cls.cddialog ) { + // bring up the cd error dialog if needed + cls.cddialog = qfalse; + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_NEED_CD ); + } else if ( cls.state == CA_DISCONNECTED && !( cls.keyCatchers & KEYCATCH_UI ) + && !com_sv_running->integer ) { + // if disconnected, bring up the menu + S_StopAllSounds(); + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_MAIN ); + } + + // if recording an avi, lock to a fixed fps + if ( cl_avidemo->integer && msec) { + // save the current screen + if ( cls.state == CA_ACTIVE || cl_forceavidemo->integer) { + Cbuf_ExecuteText( EXEC_NOW, "screenshot silent\n" ); + } + // fixed time for next frame' + msec = (1000 / cl_avidemo->integer) * com_timescale->value; + if (msec == 0) { + msec = 1; + } + } + + // save the msec before checking pause + cls.realFrametime = msec; + + // decide the simulation time + cls.frametime = msec; + + cls.realtime += cls.frametime; + + if ( cl_timegraph->integer ) { + SCR_DebugGraph ( cls.realFrametime * 0.25, 0 ); + } + + // see if we need to update any userinfo + CL_CheckUserinfo(); + + // if we haven't gotten a packet in a long time, + // drop the connection + CL_CheckTimeout(); + + // send intentions now + CL_SendCmd(); + + // resend a connection request if necessary + CL_CheckForResend(); + + // decide on the serverTime to render + CL_SetCGameTime(); + + // update the screen + SCR_UpdateScreen(); + + // update audio + S_Update(); + + // advance local effects for next frame + SCR_RunCinematic(); + + Con_RunConsole(); + + cls.framecount++; +} + + +//============================================================================ + +/* +================ +CL_RefPrintf + +DLL glue +================ +*/ +void QDECL CL_RefPrintf( int print_level, const char *fmt, ...) { + va_list argptr; + char msg[MAXPRINTMSG]; + + va_start (argptr,fmt); + Q_vsnprintf (msg, sizeof(msg), fmt, argptr); + va_end (argptr); + + if ( print_level == PRINT_ALL ) { + Com_Printf ("%s", msg); + } else if ( print_level == PRINT_WARNING ) { + Com_Printf (S_COLOR_YELLOW "%s", msg); // yellow + } else if ( print_level == PRINT_DEVELOPER ) { + Com_DPrintf (S_COLOR_RED "%s", msg); // red + } +} + + + +/* +============ +CL_ShutdownRef +============ +*/ +void CL_ShutdownRef( void ) { + if ( !re.Shutdown ) { + return; + } + re.Shutdown( qtrue ); + Com_Memset( &re, 0, sizeof( re ) ); +} + +/* +============ +CL_InitRenderer +============ +*/ +void CL_InitRenderer( void ) { + // this sets up the renderer and calls R_Init + re.BeginRegistration( &cls.glconfig ); + + // load character sets + cls.charSetShader = re.RegisterShader( "gfx/2d/bigchars" ); + cls.whiteShader = re.RegisterShader( "white" ); + cls.consoleShader = re.RegisterShader( "console" ); + g_console_field_width = cls.glconfig.vidWidth / SMALLCHAR_WIDTH - 2; + g_consoleField.widthInChars = g_console_field_width; +} + +/* +============================ +CL_StartHunkUsers + +After the server has cleared the hunk, these will need to be restarted +This is the only place that any of these functions are called from +============================ +*/ +void CL_StartHunkUsers( void ) { + if (!com_cl_running) { + return; + } + + if ( !com_cl_running->integer ) { + return; + } + + if ( !cls.rendererStarted ) { + cls.rendererStarted = qtrue; + CL_InitRenderer(); + } + + if ( !cls.soundStarted ) { + cls.soundStarted = qtrue; + S_Init(); + } + + if ( !cls.soundRegistered ) { + cls.soundRegistered = qtrue; + S_BeginRegistration(); + } + + if ( !cls.uiStarted ) { + cls.uiStarted = qtrue; + CL_InitUI(); + } +} + +/* +============ +CL_RefMalloc +============ +*/ +void *CL_RefMalloc( int size ) { + return Z_TagMalloc( size, TAG_RENDERER ); +} + +int CL_ScaledMilliseconds(void) { + return Sys_Milliseconds()*com_timescale->value; +} + +/* +============ +CL_InitRef +============ +*/ +void CL_InitRef( void ) { + refimport_t ri; + refexport_t *ret; + + Com_Printf( "----- Initializing Renderer ----\n" ); + + ri.Cmd_AddCommand = Cmd_AddCommand; + ri.Cmd_RemoveCommand = Cmd_RemoveCommand; + ri.Cmd_Argc = Cmd_Argc; + ri.Cmd_Argv = Cmd_Argv; + ri.Cmd_ExecuteText = Cbuf_ExecuteText; + ri.Printf = CL_RefPrintf; + ri.Error = Com_Error; + ri.Milliseconds = CL_ScaledMilliseconds; + ri.Malloc = CL_RefMalloc; + ri.Free = Z_Free; +#ifdef HUNK_DEBUG + ri.Hunk_AllocDebug = Hunk_AllocDebug; +#else + ri.Hunk_Alloc = Hunk_Alloc; +#endif + ri.Hunk_AllocateTempMemory = Hunk_AllocateTempMemory; + ri.Hunk_FreeTempMemory = Hunk_FreeTempMemory; + ri.CM_DrawDebugSurface = CM_DrawDebugSurface; + ri.FS_ReadFile = FS_ReadFile; + ri.FS_FreeFile = FS_FreeFile; + ri.FS_WriteFile = FS_WriteFile; + ri.FS_FreeFileList = FS_FreeFileList; + ri.FS_ListFiles = FS_ListFiles; + ri.FS_FileIsInPAK = FS_FileIsInPAK; + ri.FS_FileExists = FS_FileExists; + ri.Cvar_Get = Cvar_Get; + ri.Cvar_Set = Cvar_Set; + + // cinematic stuff + + ri.CIN_UploadCinematic = CIN_UploadCinematic; + ri.CIN_PlayCinematic = CIN_PlayCinematic; + ri.CIN_RunCinematic = CIN_RunCinematic; + + ret = GetRefAPI( REF_API_VERSION, &ri ); + +#if defined __USEA3D && defined __A3D_GEOM + hA3Dg_ExportRenderGeom (ret); +#endif + + Com_Printf( "-------------------------------\n"); + + if ( !ret ) { + Com_Error (ERR_FATAL, "Couldn't initialize refresh" ); + } + + re = *ret; + + // unpause so the cgame definately gets a snapshot and renders a frame + Cvar_Set( "cl_paused", "0" ); +} + + +//=========================================================================================== + + +void CL_SetModel_f( void ) { + char *arg; + char name[256]; + + arg = Cmd_Argv( 1 ); + if (arg[0]) { + Cvar_Set( "model", arg ); + Cvar_Set( "headmodel", arg ); + } else { + Cvar_VariableStringBuffer( "model", name, sizeof(name) ); + Com_Printf("model is set to %s\n", name); + } +} + +/* +==================== +CL_Init +==================== +*/ +void CL_Init( void ) { + Com_Printf( "----- Client Initialization -----\n" ); + + Con_Init (); + + CL_ClearState (); + + cls.state = CA_DISCONNECTED; // no longer CA_UNINITIALIZED + + cls.realtime = 0; + + CL_InitInput (); + + // + // register our variables + // + cl_noprint = Cvar_Get( "cl_noprint", "0", 0 ); + cl_motd = Cvar_Get ("cl_motd", "1", 0); + + cl_timeout = Cvar_Get ("cl_timeout", "200", 0); + + cl_timeNudge = Cvar_Get ("cl_timeNudge", "0", CVAR_TEMP ); + cl_shownet = Cvar_Get ("cl_shownet", "0", CVAR_TEMP ); + cl_showSend = Cvar_Get ("cl_showSend", "0", CVAR_TEMP ); + cl_showTimeDelta = Cvar_Get ("cl_showTimeDelta", "0", CVAR_TEMP ); + cl_freezeDemo = Cvar_Get ("cl_freezeDemo", "0", CVAR_TEMP ); + rcon_client_password = Cvar_Get ("rconPassword", "", CVAR_TEMP ); + cl_activeAction = Cvar_Get( "activeAction", "", CVAR_TEMP ); + + cl_timedemo = Cvar_Get ("timedemo", "0", 0); + cl_avidemo = Cvar_Get ("cl_avidemo", "0", 0); + cl_forceavidemo = Cvar_Get ("cl_forceavidemo", "0", 0); + + rconAddress = Cvar_Get ("rconAddress", "", 0); + + cl_yawspeed = Cvar_Get ("cl_yawspeed", "140", CVAR_ARCHIVE); + cl_pitchspeed = Cvar_Get ("cl_pitchspeed", "140", CVAR_ARCHIVE); + cl_anglespeedkey = Cvar_Get ("cl_anglespeedkey", "1.5", 0); + + cl_maxpackets = Cvar_Get ("cl_maxpackets", "30", CVAR_ARCHIVE ); + cl_packetdup = Cvar_Get ("cl_packetdup", "1", CVAR_ARCHIVE ); + + cl_run = Cvar_Get ("cl_run", "1", CVAR_ARCHIVE); + cl_sensitivity = Cvar_Get ("sensitivity", "5", CVAR_ARCHIVE); + cl_mouseAccel = Cvar_Get ("cl_mouseAccel", "0", CVAR_ARCHIVE); + cl_freelook = Cvar_Get( "cl_freelook", "1", CVAR_ARCHIVE ); + + cl_showMouseRate = Cvar_Get ("cl_showmouserate", "0", 0); + + cl_allowDownload = Cvar_Get ("cl_allowDownload", "0", CVAR_ARCHIVE); + + cl_conXOffset = Cvar_Get ("cl_conXOffset", "0", 0); +#ifdef MACOS_X + // In game video is REALLY slow in Mac OS X right now due to driver slowness + cl_inGameVideo = Cvar_Get ("r_inGameVideo", "0", CVAR_ARCHIVE); +#else + cl_inGameVideo = Cvar_Get ("r_inGameVideo", "1", CVAR_ARCHIVE); +#endif + + cl_serverStatusResendTime = Cvar_Get ("cl_serverStatusResendTime", "750", 0); + + // init autoswitch so the ui will have it correctly even + // if the cgame hasn't been started + Cvar_Get ("cg_autoswitch", "1", CVAR_ARCHIVE); + + m_pitch = Cvar_Get ("m_pitch", "0.022", CVAR_ARCHIVE); + m_yaw = Cvar_Get ("m_yaw", "0.022", CVAR_ARCHIVE); + m_forward = Cvar_Get ("m_forward", "0.25", CVAR_ARCHIVE); + m_side = Cvar_Get ("m_side", "0.25", CVAR_ARCHIVE); +#ifdef MACOS_X + // Input is jittery on OS X w/o this + m_filter = Cvar_Get ("m_filter", "1", CVAR_ARCHIVE); +#else + m_filter = Cvar_Get ("m_filter", "0", CVAR_ARCHIVE); +#endif + + cl_motdString = Cvar_Get( "cl_motdString", "", CVAR_ROM ); + + Cvar_Get( "cl_maxPing", "800", CVAR_ARCHIVE ); + + + // userinfo + Cvar_Get ("name", "UnnamedPlayer", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("rate", "3000", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("snaps", "20", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("model", "sarge", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("headmodel", "sarge", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("team_model", "james", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("team_headmodel", "*james", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("g_redTeam", "Stroggs", CVAR_SERVERINFO | CVAR_ARCHIVE); + Cvar_Get ("g_blueTeam", "Pagans", CVAR_SERVERINFO | CVAR_ARCHIVE); + Cvar_Get ("color1", "4", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("color2", "5", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("handicap", "100", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("teamtask", "0", CVAR_USERINFO ); + Cvar_Get ("sex", "male", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("cl_anonymous", "0", CVAR_USERINFO | CVAR_ARCHIVE ); + + Cvar_Get ("password", "", CVAR_USERINFO); + Cvar_Get ("cg_predictItems", "1", CVAR_USERINFO | CVAR_ARCHIVE ); + + + // cgame might not be initialized before menu is used + Cvar_Get ("cg_viewsize", "100", CVAR_ARCHIVE ); + + // + // register our commands + // + Cmd_AddCommand ("cmd", CL_ForwardToServer_f); + Cmd_AddCommand ("configstrings", CL_Configstrings_f); + Cmd_AddCommand ("clientinfo", CL_Clientinfo_f); + Cmd_AddCommand ("snd_restart", CL_Snd_Restart_f); + Cmd_AddCommand ("vid_restart", CL_Vid_Restart_f); + Cmd_AddCommand ("disconnect", CL_Disconnect_f); + Cmd_AddCommand ("record", CL_Record_f); + Cmd_AddCommand ("demo", CL_PlayDemo_f); + Cmd_AddCommand ("cinematic", CL_PlayCinematic_f); + Cmd_AddCommand ("stoprecord", CL_StopRecord_f); + Cmd_AddCommand ("connect", CL_Connect_f); + Cmd_AddCommand ("reconnect", CL_Reconnect_f); + Cmd_AddCommand ("localservers", CL_LocalServers_f); + Cmd_AddCommand ("globalservers", CL_GlobalServers_f); + Cmd_AddCommand ("rcon", CL_Rcon_f); + Cmd_AddCommand ("setenv", CL_Setenv_f ); + Cmd_AddCommand ("ping", CL_Ping_f ); + Cmd_AddCommand ("serverstatus", CL_ServerStatus_f ); + Cmd_AddCommand ("showip", CL_ShowIP_f ); + Cmd_AddCommand ("fs_openedList", CL_OpenedPK3List_f ); + Cmd_AddCommand ("fs_referencedList", CL_ReferencedPK3List_f ); + Cmd_AddCommand ("model", CL_SetModel_f ); + CL_InitRef(); + + SCR_Init (); + + Cbuf_Execute (); + + Cvar_Set( "cl_running", "1" ); + + Com_Printf( "----- Client Initialization Complete -----\n" ); +} + + +/* +=============== +CL_Shutdown + +=============== +*/ +void CL_Shutdown( void ) { + static qboolean recursive = qfalse; + + Com_Printf( "----- CL_Shutdown -----\n" ); + + if ( recursive ) { + printf ("recursive shutdown\n"); + return; + } + recursive = qtrue; + + CL_Disconnect( qtrue ); + + S_Shutdown(); + CL_ShutdownRef(); + + CL_ShutdownUI(); + + Cmd_RemoveCommand ("cmd"); + Cmd_RemoveCommand ("configstrings"); + Cmd_RemoveCommand ("userinfo"); + Cmd_RemoveCommand ("snd_restart"); + Cmd_RemoveCommand ("vid_restart"); + Cmd_RemoveCommand ("disconnect"); + Cmd_RemoveCommand ("record"); + Cmd_RemoveCommand ("demo"); + Cmd_RemoveCommand ("cinematic"); + Cmd_RemoveCommand ("stoprecord"); + Cmd_RemoveCommand ("connect"); + Cmd_RemoveCommand ("localservers"); + Cmd_RemoveCommand ("globalservers"); + Cmd_RemoveCommand ("rcon"); + Cmd_RemoveCommand ("setenv"); + Cmd_RemoveCommand ("ping"); + Cmd_RemoveCommand ("serverstatus"); + Cmd_RemoveCommand ("showip"); + Cmd_RemoveCommand ("model"); + + Cvar_Set( "cl_running", "0" ); + + recursive = qfalse; + + Com_Memset( &cls, 0, sizeof( cls ) ); + + Com_Printf( "-----------------------\n" ); + +} + +static void CL_SetServerInfo(serverInfo_t *server, const char *info, int ping) { + if (server) { + if (info) { + server->clients = atoi(Info_ValueForKey(info, "clients")); + Q_strncpyz(server->hostName,Info_ValueForKey(info, "hostname"), MAX_NAME_LENGTH); + Q_strncpyz(server->mapName, Info_ValueForKey(info, "mapname"), MAX_NAME_LENGTH); + server->maxClients = atoi(Info_ValueForKey(info, "sv_maxclients")); + Q_strncpyz(server->game,Info_ValueForKey(info, "game"), MAX_NAME_LENGTH); + server->gameType = atoi(Info_ValueForKey(info, "gametype")); + server->netType = atoi(Info_ValueForKey(info, "nettype")); + server->minPing = atoi(Info_ValueForKey(info, "minping")); + server->maxPing = atoi(Info_ValueForKey(info, "maxping")); + server->punkbuster = atoi(Info_ValueForKey(info, "punkbuster")); + } + server->ping = ping; + } +} + +static void CL_SetServerInfoByAddress(netadr_t from, const char *info, int ping) { + int i; + + for (i = 0; i < MAX_OTHER_SERVERS; i++) { + if (NET_CompareAdr(from, cls.localServers[i].adr)) { + CL_SetServerInfo(&cls.localServers[i], info, ping); + } + } + + for (i = 0; i < MAX_OTHER_SERVERS; i++) { + if (NET_CompareAdr(from, cls.mplayerServers[i].adr)) { + CL_SetServerInfo(&cls.mplayerServers[i], info, ping); + } + } + + for (i = 0; i < MAX_GLOBAL_SERVERS; i++) { + if (NET_CompareAdr(from, cls.globalServers[i].adr)) { + CL_SetServerInfo(&cls.globalServers[i], info, ping); + } + } + + for (i = 0; i < MAX_OTHER_SERVERS; i++) { + if (NET_CompareAdr(from, cls.favoriteServers[i].adr)) { + CL_SetServerInfo(&cls.favoriteServers[i], info, ping); + } + } + +} + +/* +=================== +CL_ServerInfoPacket +=================== +*/ +void CL_ServerInfoPacket( netadr_t from, msg_t *msg ) { + int i, type; + char info[MAX_INFO_STRING]; + char* str; + char *infoString; + int prot; + + infoString = MSG_ReadString( msg ); + + // if this isn't the correct protocol version, ignore it + prot = atoi( Info_ValueForKey( infoString, "protocol" ) ); + if ( prot != PROTOCOL_VERSION ) { + Com_DPrintf( "Different protocol info packet: %s\n", infoString ); + return; + } + + // iterate servers waiting for ping response + for (i=0; iretrieved = qtrue; + return qfalse; + } + + // if this server status request has the same address + if ( NET_CompareAdr( to, serverStatus->address) ) { + // if we recieved an response for this server status request + if (!serverStatus->pending) { + Q_strncpyz(serverStatusString, serverStatus->string, maxLen); + serverStatus->retrieved = qtrue; + serverStatus->startTime = 0; + return qtrue; + } + // resend the request regularly + else if ( serverStatus->startTime < Com_Milliseconds() - cl_serverStatusResendTime->integer ) { + serverStatus->print = qfalse; + serverStatus->pending = qtrue; + serverStatus->retrieved = qfalse; + serverStatus->time = 0; + serverStatus->startTime = Com_Milliseconds(); + NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" ); + return qfalse; + } + } + // if retrieved + else if ( serverStatus->retrieved ) { + serverStatus->address = to; + serverStatus->print = qfalse; + serverStatus->pending = qtrue; + serverStatus->retrieved = qfalse; + serverStatus->startTime = Com_Milliseconds(); + serverStatus->time = 0; + NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" ); + return qfalse; + } + return qfalse; +} + +/* +=================== +CL_ServerStatusResponse +=================== +*/ +void CL_ServerStatusResponse( netadr_t from, msg_t *msg ) { + char *s; + char info[MAX_INFO_STRING]; + int i, l, score, ping; + int len; + serverStatus_t *serverStatus; + + serverStatus = NULL; + for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) { + if ( NET_CompareAdr( from, cl_serverStatusList[i].address ) ) { + serverStatus = &cl_serverStatusList[i]; + break; + } + } + // if we didn't request this server status + if (!serverStatus) { + return; + } + + s = MSG_ReadStringLine( msg ); + + len = 0; + Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "%s", s); + + if (serverStatus->print) { + Com_Printf("Server settings:\n"); + // print cvars + while (*s) { + for (i = 0; i < 2 && *s; i++) { + if (*s == '\\') + s++; + l = 0; + while (*s) { + info[l++] = *s; + if (l >= MAX_INFO_STRING-1) + break; + s++; + if (*s == '\\') { + break; + } + } + info[l] = '\0'; + if (i) { + Com_Printf("%s\n", info); + } + else { + Com_Printf("%-24s", info); + } + } + } + } + + len = strlen(serverStatus->string); + Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "\\"); + + if (serverStatus->print) { + Com_Printf("\nPlayers:\n"); + Com_Printf("num: score: ping: name:\n"); + } + for (i = 0, s = MSG_ReadStringLine( msg ); *s; s = MSG_ReadStringLine( msg ), i++) { + + len = strlen(serverStatus->string); + Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "\\%s", s); + + if (serverStatus->print) { + score = ping = 0; + sscanf(s, "%d %d", &score, &ping); + s = strchr(s, ' '); + if (s) + s = strchr(s+1, ' '); + if (s) + s++; + else + s = "unknown"; + Com_Printf("%-2d %-3d %-3d %s\n", i, score, ping, s ); + } + } + len = strlen(serverStatus->string); + Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "\\"); + + serverStatus->time = Com_Milliseconds(); + serverStatus->address = from; + serverStatus->pending = qfalse; + if (serverStatus->print) { + serverStatus->retrieved = qtrue; + } +} + +/* +================== +CL_LocalServers_f +================== +*/ +void CL_LocalServers_f( void ) { + char *message; + int i, j; + netadr_t to; + + Com_Printf( "Scanning for servers on the local network...\n"); + + // reset the list, waiting for response + cls.numlocalservers = 0; + cls.pingUpdateSource = AS_LOCAL; + + for (i = 0; i < MAX_OTHER_SERVERS; i++) { + qboolean b = cls.localServers[i].visible; + Com_Memset(&cls.localServers[i], 0, sizeof(cls.localServers[i])); + cls.localServers[i].visible = b; + } + Com_Memset( &to, 0, sizeof( to ) ); + + // The 'xxx' in the message is a challenge that will be echoed back + // by the server. We don't care about that here, but master servers + // can use that to prevent spoofed server responses from invalid ip + message = "\377\377\377\377getinfo xxx"; + + // send each message twice in case one is dropped + for ( i = 0 ; i < 2 ; i++ ) { + // send a broadcast packet on each server port + // we support multiple server ports so a single machine + // can nicely run multiple servers + for ( j = 0 ; j < NUM_SERVER_PORTS ; j++ ) { + to.port = BigShort( (short)(PORT_SERVER + j) ); + + to.type = NA_BROADCAST; + NET_SendPacket( NS_CLIENT, strlen( message ), message, to ); + + to.type = NA_BROADCAST_IPX; + NET_SendPacket( NS_CLIENT, strlen( message ), message, to ); + } + } +} + +/* +================== +CL_GlobalServers_f +================== +*/ +void CL_GlobalServers_f( void ) { + netadr_t to; + int i; + int count; + char *buffptr; + char command[1024]; + + if ( Cmd_Argc() < 3) { + Com_Printf( "usage: globalservers [keywords]\n"); + return; + } + + cls.masterNum = atoi( Cmd_Argv(1) ); + + Com_Printf( "Requesting servers from the master...\n"); + + // reset the list, waiting for response + // -1 is used to distinguish a "no response" + + if( cls.masterNum == 1 ) { + NET_StringToAdr( MASTER_SERVER_NAME, &to ); + cls.nummplayerservers = -1; + cls.pingUpdateSource = AS_MPLAYER; + } + else { + NET_StringToAdr( MASTER_SERVER_NAME, &to ); + cls.numglobalservers = -1; + cls.pingUpdateSource = AS_GLOBAL; + } + to.type = NA_IP; + to.port = BigShort(PORT_MASTER); + + sprintf( command, "getservers %s", Cmd_Argv(2) ); + + // tack on keywords + buffptr = command + strlen( command ); + count = Cmd_Argc(); + for (i=3; i= MAX_PINGREQUESTS) + return; + + cl_pinglist[n].adr.port = 0; +} + +/* +================== +CL_GetPingQueueCount +================== +*/ +int CL_GetPingQueueCount( void ) +{ + int i; + int count; + ping_t* pingptr; + + count = 0; + pingptr = cl_pinglist; + + for (i=0; iadr.port) { + count++; + } + } + + return (count); +} + +/* +================== +CL_GetFreePing +================== +*/ +ping_t* CL_GetFreePing( void ) +{ + ping_t* pingptr; + ping_t* best; + int oldest; + int i; + int time; + + pingptr = cl_pinglist; + for (i=0; iadr.port) + { + if (!pingptr->time) + { + if (cls.realtime - pingptr->start < 500) + { + // still waiting for response + continue; + } + } + else if (pingptr->time < 500) + { + // results have not been queried + continue; + } + } + + // clear it + pingptr->adr.port = 0; + return (pingptr); + } + + // use oldest entry + pingptr = cl_pinglist; + best = cl_pinglist; + oldest = INT_MIN; + for (i=0; istart; + if (time > oldest) + { + oldest = time; + best = pingptr; + } + } + + return (best); +} + +/* +================== +CL_Ping_f +================== +*/ +void CL_Ping_f( void ) { + netadr_t to; + ping_t* pingptr; + char* server; + + if ( Cmd_Argc() != 2 ) { + Com_Printf( "usage: ping [server]\n"); + return; + } + + Com_Memset( &to, 0, sizeof(netadr_t) ); + + server = Cmd_Argv(1); + + if ( !NET_StringToAdr( server, &to ) ) { + return; + } + + pingptr = CL_GetFreePing(); + + memcpy( &pingptr->adr, &to, sizeof (netadr_t) ); + pingptr->start = cls.realtime; + pingptr->time = 0; + + CL_SetServerInfoByAddress(pingptr->adr, NULL, 0); + + NET_OutOfBandPrint( NS_CLIENT, to, "getinfo xxx" ); +} + +/* +================== +CL_UpdateVisiblePings_f +================== +*/ +qboolean CL_UpdateVisiblePings_f(int source) { + int slots, i; + char buff[MAX_STRING_CHARS]; + int pingTime; + int max; + qboolean status = qfalse; + + if (source < 0 || source > AS_FAVORITES) { + return qfalse; + } + + cls.pingUpdateSource = source; + + slots = CL_GetPingQueueCount(); + if (slots < MAX_PINGREQUESTS) { + serverInfo_t *server = NULL; + + max = (source == AS_GLOBAL) ? MAX_GLOBAL_SERVERS : MAX_OTHER_SERVERS; + switch (source) { + case AS_LOCAL : + server = &cls.localServers[0]; + max = cls.numlocalservers; + break; + case AS_MPLAYER : + server = &cls.mplayerServers[0]; + max = cls.nummplayerservers; + break; + case AS_GLOBAL : + server = &cls.globalServers[0]; + max = cls.numglobalservers; + break; + case AS_FAVORITES : + server = &cls.favoriteServers[0]; + max = cls.numfavoriteservers; + break; + } + for (i = 0; i < max; i++) { + if (server[i].visible) { + if (server[i].ping == -1) { + int j; + + if (slots >= MAX_PINGREQUESTS) { + break; + } + for (j = 0; j < MAX_PINGREQUESTS; j++) { + if (!cl_pinglist[j].adr.port) { + continue; + } + if (NET_CompareAdr( cl_pinglist[j].adr, server[i].adr)) { + // already on the list + break; + } + } + if (j >= MAX_PINGREQUESTS) { + status = qtrue; + for (j = 0; j < MAX_PINGREQUESTS; j++) { + if (!cl_pinglist[j].adr.port) { + break; + } + } + memcpy(&cl_pinglist[j].adr, &server[i].adr, sizeof(netadr_t)); + cl_pinglist[j].start = cls.realtime; + cl_pinglist[j].time = 0; + NET_OutOfBandPrint( NS_CLIENT, cl_pinglist[j].adr, "getinfo xxx" ); + slots++; + } + } + // if the server has a ping higher than cl_maxPing or + // the ping packet got lost + else if (server[i].ping == 0) { + // if we are updating global servers + if (source == AS_GLOBAL) { + // + if ( cls.numGlobalServerAddresses > 0 ) { + // overwrite this server with one from the additional global servers + cls.numGlobalServerAddresses--; + CL_InitServerInfo(&server[i], &cls.globalServerAddresses[cls.numGlobalServerAddresses]); + // NOTE: the server[i].visible flag stays untouched + } + } + } + } + } + } + + if (slots) { + status = qtrue; + } + for (i = 0; i < MAX_PINGREQUESTS; i++) { + if (!cl_pinglist[i].adr.port) { + continue; + } + CL_GetPing( i, buff, MAX_STRING_CHARS, &pingTime ); + if (pingTime != 0) { + CL_ClearPing(i); + status = qtrue; + } + } + + return status; +} + +/* +================== +CL_ServerStatus_f +================== +*/ +void CL_ServerStatus_f(void) { + netadr_t to; + char *server; + serverStatus_t *serverStatus; + + Com_Memset( &to, 0, sizeof(netadr_t) ); + + if ( Cmd_Argc() != 2 ) { + if ( cls.state != CA_ACTIVE || clc.demoplaying ) { + Com_Printf ("Not connected to a server.\n"); + Com_Printf( "Usage: serverstatus [server]\n"); + return; + } + server = cls.servername; + } + else { + server = Cmd_Argv(1); + } + + if ( !NET_StringToAdr( server, &to ) ) { + return; + } + + NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" ); + + serverStatus = CL_GetServerStatus( to ); + serverStatus->address = to; + serverStatus->print = qtrue; + serverStatus->pending = qtrue; +} + +/* +================== +CL_ShowIP_f +================== +*/ +void CL_ShowIP_f(void) { + Sys_ShowIP(); +} + +/* +================= +bool CL_CDKeyValidate +================= +*/ +qboolean CL_CDKeyValidate( const char *key, const char *checksum ) { + char ch; + byte sum; + char chs[3]; + int i, len; + + len = strlen(key); + if( len != CDKEY_LEN ) { + return qfalse; + } + + if( checksum && strlen( checksum ) != CDCHKSUM_LEN ) { + return qfalse; + } + + sum = 0; + // for loop gets rid of conditional assignment warning + for (i = 0; i < len; i++) { + ch = *key++; + if (ch>='a' && ch<='z') { + ch -= 32; + } + switch( ch ) { + case '2': + case '3': + case '7': + case 'A': + case 'B': + case 'C': + case 'D': + case 'G': + case 'H': + case 'J': + case 'L': + case 'P': + case 'R': + case 'S': + case 'T': + case 'W': + sum += ch; + continue; + default: + return qfalse; + } + } + + sprintf(chs, "%02x", sum); + + if (checksum && !Q_stricmp(chs, checksum)) { + return qtrue; + } + + if (!checksum) { + return qtrue; + } + + return qfalse; +} + + diff --git a/code/client/cl_net_chan.c b/code/client/cl_net_chan.c index d1e0b08..904636e 100755 --- a/code/client/cl_net_chan.c +++ b/code/client/cl_net_chan.c @@ -1,167 +1,167 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "../game/q_shared.h" -#include "../qcommon/qcommon.h" -#include "client.h" - -/* -============== -CL_Netchan_Encode - - // first 12 bytes of the data are always: - long serverId; - long messageAcknowledge; - long reliableAcknowledge; - -============== -*/ -static void CL_Netchan_Encode( msg_t *msg ) { - int serverId, messageAcknowledge, reliableAcknowledge; - int i, index, srdc, sbit, soob; - byte key, *string; - - if ( msg->cursize <= CL_ENCODE_START ) { - return; - } - - srdc = msg->readcount; - sbit = msg->bit; - soob = msg->oob; - - msg->bit = 0; - msg->readcount = 0; - msg->oob = 0; - - serverId = MSG_ReadLong(msg); - messageAcknowledge = MSG_ReadLong(msg); - reliableAcknowledge = MSG_ReadLong(msg); - - msg->oob = soob; - msg->bit = sbit; - msg->readcount = srdc; - - string = (byte *)clc.serverCommands[ reliableAcknowledge & (MAX_RELIABLE_COMMANDS-1) ]; - index = 0; - // - key = clc.challenge ^ serverId ^ messageAcknowledge; - for (i = CL_ENCODE_START; i < msg->cursize; i++) { - // modify the key with the last received now acknowledged server command - if (!string[index]) - index = 0; - if (string[index] > 127 || string[index] == '%') { - key ^= '.' << (i & 1); - } - else { - key ^= string[index] << (i & 1); - } - index++; - // encode the data with this key - *(msg->data + i) = (*(msg->data + i)) ^ key; - } -} - -/* -============== -CL_Netchan_Decode - - // first four bytes of the data are always: - long reliableAcknowledge; - -============== -*/ -static void CL_Netchan_Decode( msg_t *msg ) { - long reliableAcknowledge, i, index; - byte key, *string; - int srdc, sbit, soob; - - srdc = msg->readcount; - sbit = msg->bit; - soob = msg->oob; - - msg->oob = 0; - - reliableAcknowledge = MSG_ReadLong(msg); - - msg->oob = soob; - msg->bit = sbit; - msg->readcount = srdc; - - string = clc.reliableCommands[ reliableAcknowledge & (MAX_RELIABLE_COMMANDS-1) ]; - index = 0; - // xor the client challenge with the netchan sequence number (need something that changes every message) - key = clc.challenge ^ LittleLong( *(unsigned *)msg->data ); - for (i = msg->readcount + CL_DECODE_START; i < msg->cursize; i++) { - // modify the key with the last sent and with this message acknowledged client command - if (!string[index]) - index = 0; - if (string[index] > 127 || 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; - } -} - -/* -================= -CL_Netchan_TransmitNextFragment -================= -*/ -void CL_Netchan_TransmitNextFragment( netchan_t *chan ) { - Netchan_TransmitNextFragment( chan ); -} - -/* -=============== -CL_Netchan_Transmit -================ -*/ -void CL_Netchan_Transmit( netchan_t *chan, msg_t* msg ) { - MSG_WriteByte( msg, clc_EOF ); - - CL_Netchan_Encode( msg ); - Netchan_Transmit( chan, msg->cursize, msg->data ); -} - -extern int oldsize; -int newsize = 0; - -/* -================= -CL_Netchan_Process -================= -*/ -qboolean CL_Netchan_Process( netchan_t *chan, msg_t *msg ) { - int ret; - - ret = Netchan_Process( chan, msg ); - if (!ret) - return qfalse; - CL_Netchan_Decode( msg ); - newsize += msg->cursize; - return qtrue; -} +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "../game/q_shared.h" +#include "../qcommon/qcommon.h" +#include "client.h" + +/* +============== +CL_Netchan_Encode + + // first 12 bytes of the data are always: + long serverId; + long messageAcknowledge; + long reliableAcknowledge; + +============== +*/ +static void CL_Netchan_Encode( msg_t *msg ) { + int serverId, messageAcknowledge, reliableAcknowledge; + int i, index, srdc, sbit, soob; + byte key, *string; + + if ( msg->cursize <= CL_ENCODE_START ) { + return; + } + + srdc = msg->readcount; + sbit = msg->bit; + soob = msg->oob; + + msg->bit = 0; + msg->readcount = 0; + msg->oob = 0; + + serverId = MSG_ReadLong(msg); + messageAcknowledge = MSG_ReadLong(msg); + reliableAcknowledge = MSG_ReadLong(msg); + + msg->oob = soob; + msg->bit = sbit; + msg->readcount = srdc; + + string = (byte *)clc.serverCommands[ reliableAcknowledge & (MAX_RELIABLE_COMMANDS-1) ]; + index = 0; + // + key = clc.challenge ^ serverId ^ messageAcknowledge; + for (i = CL_ENCODE_START; i < msg->cursize; i++) { + // modify the key with the last received now acknowledged server command + if (!string[index]) + index = 0; + if (string[index] > 127 || string[index] == '%') { + key ^= '.' << (i & 1); + } + else { + key ^= string[index] << (i & 1); + } + index++; + // encode the data with this key + *(msg->data + i) = (*(msg->data + i)) ^ key; + } +} + +/* +============== +CL_Netchan_Decode + + // first four bytes of the data are always: + long reliableAcknowledge; + +============== +*/ +static void CL_Netchan_Decode( msg_t *msg ) { + long reliableAcknowledge, i, index; + byte key, *string; + int srdc, sbit, soob; + + srdc = msg->readcount; + sbit = msg->bit; + soob = msg->oob; + + msg->oob = 0; + + reliableAcknowledge = MSG_ReadLong(msg); + + msg->oob = soob; + msg->bit = sbit; + msg->readcount = srdc; + + string = clc.reliableCommands[ reliableAcknowledge & (MAX_RELIABLE_COMMANDS-1) ]; + index = 0; + // xor the client challenge with the netchan sequence number (need something that changes every message) + key = clc.challenge ^ LittleLong( *(unsigned *)msg->data ); + for (i = msg->readcount + CL_DECODE_START; i < msg->cursize; i++) { + // modify the key with the last sent and with this message acknowledged client command + if (!string[index]) + index = 0; + if (string[index] > 127 || 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; + } +} + +/* +================= +CL_Netchan_TransmitNextFragment +================= +*/ +void CL_Netchan_TransmitNextFragment( netchan_t *chan ) { + Netchan_TransmitNextFragment( chan ); +} + +/* +=============== +CL_Netchan_Transmit +================ +*/ +void CL_Netchan_Transmit( netchan_t *chan, msg_t* msg ) { + MSG_WriteByte( msg, clc_EOF ); + + CL_Netchan_Encode( msg ); + Netchan_Transmit( chan, msg->cursize, msg->data ); +} + +extern int oldsize; +int newsize = 0; + +/* +================= +CL_Netchan_Process +================= +*/ +qboolean CL_Netchan_Process( netchan_t *chan, msg_t *msg ) { + int ret; + + ret = Netchan_Process( chan, msg ); + if (!ret) + return qfalse; + CL_Netchan_Decode( msg ); + newsize += msg->cursize; + return qtrue; +} diff --git a/code/client/cl_parse.c b/code/client/cl_parse.c index 9de3588..e3dedb3 100755 --- a/code/client/cl_parse.c +++ b/code/client/cl_parse.c @@ -1,655 +1,655 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// cl_parse.c -- parse a message received from the server - -#include "client.h" - -char *svc_strings[256] = { - "svc_bad", - - "svc_nop", - "svc_gamestate", - "svc_configstring", - "svc_baseline", - "svc_serverCommand", - "svc_download", - "svc_snapshot" -}; - -void SHOWNET( msg_t *msg, char *s) { - if ( cl_shownet->integer >= 2) { - Com_Printf ("%3i:%s\n", msg->readcount-1, s); - } -} - - -/* -========================================================================= - -MESSAGE PARSING - -========================================================================= -*/ - -/* -================== -CL_DeltaEntity - -Parses deltas from the given base and adds the resulting entity -to the current frame -================== -*/ -void CL_DeltaEntity (msg_t *msg, clSnapshot_t *frame, int newnum, entityState_t *old, - qboolean unchanged) { - entityState_t *state; - - // save the parsed entity state into the big circular buffer so - // it can be used as the source for a later delta - state = &cl.parseEntities[cl.parseEntitiesNum & (MAX_PARSE_ENTITIES-1)]; - - if ( unchanged ) { - *state = *old; - } else { - MSG_ReadDeltaEntity( msg, old, state, newnum ); - } - - if ( state->number == (MAX_GENTITIES-1) ) { - return; // entity was delta removed - } - cl.parseEntitiesNum++; - frame->numEntities++; -} - -/* -================== -CL_ParsePacketEntities - -================== -*/ -void CL_ParsePacketEntities( msg_t *msg, clSnapshot_t *oldframe, clSnapshot_t *newframe) { - int newnum; - entityState_t *oldstate; - int oldindex, oldnum; - - newframe->parseEntitiesNum = cl.parseEntitiesNum; - newframe->numEntities = 0; - - // delta from the entities present in oldframe - oldindex = 0; - oldstate = NULL; - if (!oldframe) { - oldnum = 99999; - } else { - if ( oldindex >= oldframe->numEntities ) { - oldnum = 99999; - } else { - oldstate = &cl.parseEntities[ - (oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)]; - oldnum = oldstate->number; - } - } - - while ( 1 ) { - // read the entity index number - newnum = MSG_ReadBits( msg, GENTITYNUM_BITS ); - - if ( newnum == (MAX_GENTITIES-1) ) { - break; - } - - if ( msg->readcount > msg->cursize ) { - Com_Error (ERR_DROP,"CL_ParsePacketEntities: end of message"); - } - - while ( oldnum < newnum ) { - // one or more entities from the old packet are unchanged - if ( cl_shownet->integer == 3 ) { - Com_Printf ("%3i: unchanged: %i\n", msg->readcount, oldnum); - } - CL_DeltaEntity( msg, newframe, oldnum, oldstate, qtrue ); - - oldindex++; - - if ( oldindex >= oldframe->numEntities ) { - oldnum = 99999; - } else { - oldstate = &cl.parseEntities[ - (oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)]; - oldnum = oldstate->number; - } - } - if (oldnum == newnum) { - // delta from previous state - if ( cl_shownet->integer == 3 ) { - Com_Printf ("%3i: delta: %i\n", msg->readcount, newnum); - } - CL_DeltaEntity( msg, newframe, newnum, oldstate, qfalse ); - - oldindex++; - - if ( oldindex >= oldframe->numEntities ) { - oldnum = 99999; - } else { - oldstate = &cl.parseEntities[ - (oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)]; - oldnum = oldstate->number; - } - continue; - } - - if ( oldnum > newnum ) { - // delta from baseline - if ( cl_shownet->integer == 3 ) { - Com_Printf ("%3i: baseline: %i\n", msg->readcount, newnum); - } - CL_DeltaEntity( msg, newframe, newnum, &cl.entityBaselines[newnum], qfalse ); - continue; - } - - } - - // any remaining entities in the old frame are copied over - while ( oldnum != 99999 ) { - // one or more entities from the old packet are unchanged - if ( cl_shownet->integer == 3 ) { - Com_Printf ("%3i: unchanged: %i\n", msg->readcount, oldnum); - } - CL_DeltaEntity( msg, newframe, oldnum, oldstate, qtrue ); - - oldindex++; - - if ( oldindex >= oldframe->numEntities ) { - oldnum = 99999; - } else { - oldstate = &cl.parseEntities[ - (oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)]; - oldnum = oldstate->number; - } - } -} - - -/* -================ -CL_ParseSnapshot - -If the snapshot is parsed properly, it will be copied to -cl.snap and saved in cl.snapshots[]. If the snapshot is invalid -for any reason, no changes to the state will be made at all. -================ -*/ -void CL_ParseSnapshot( msg_t *msg ) { - int len; - clSnapshot_t *old; - clSnapshot_t newSnap; - int deltaNum; - int oldMessageNum; - int i, packetNum; - - // get the reliable sequence acknowledge number - // NOTE: now sent with all server to client messages - //clc.reliableAcknowledge = MSG_ReadLong( msg ); - - // read in the new snapshot to a temporary buffer - // we will only copy to cl.snap if it is valid - Com_Memset (&newSnap, 0, sizeof(newSnap)); - - // we will have read any new server commands in this - // message before we got to svc_snapshot - newSnap.serverCommandNum = clc.serverCommandSequence; - - newSnap.serverTime = MSG_ReadLong( msg ); - - newSnap.messageNum = clc.serverMessageSequence; - - deltaNum = MSG_ReadByte( msg ); - if ( !deltaNum ) { - newSnap.deltaNum = -1; - } else { - newSnap.deltaNum = newSnap.messageNum - deltaNum; - } - newSnap.snapFlags = MSG_ReadByte( msg ); - - // If the frame is delta compressed from data that we - // no longer have available, we must suck up the rest of - // the frame, but not use it, then ask for a non-compressed - // message - if ( newSnap.deltaNum <= 0 ) { - newSnap.valid = qtrue; // uncompressed frame - old = NULL; - clc.demowaiting = qfalse; // we can start recording now - } else { - old = &cl.snapshots[newSnap.deltaNum & PACKET_MASK]; - if ( !old->valid ) { - // should never happen - Com_Printf ("Delta from invalid frame (not supposed to happen!).\n"); - } else if ( old->messageNum != newSnap.deltaNum ) { - // The frame that the server did the delta from - // is too old, so we can't reconstruct it properly. - Com_Printf ("Delta frame too old.\n"); - } else if ( cl.parseEntitiesNum - old->parseEntitiesNum > MAX_PARSE_ENTITIES-128 ) { - Com_Printf ("Delta parseEntitiesNum too old.\n"); - } else { - newSnap.valid = qtrue; // valid delta parse - } - } - - // read areamask - len = MSG_ReadByte( msg ); - MSG_ReadData( msg, &newSnap.areamask, len); - - // read playerinfo - SHOWNET( msg, "playerstate" ); - if ( old ) { - MSG_ReadDeltaPlayerstate( msg, &old->ps, &newSnap.ps ); - } else { - MSG_ReadDeltaPlayerstate( msg, NULL, &newSnap.ps ); - } - - // read packet entities - SHOWNET( msg, "packet entities" ); - CL_ParsePacketEntities( msg, old, &newSnap ); - - // if not valid, dump the entire thing now that it has - // been properly read - if ( !newSnap.valid ) { - return; - } - - // clear the valid flags of any snapshots between the last - // received and this one, so if there was a dropped packet - // it won't look like something valid to delta from next - // time we wrap around in the buffer - oldMessageNum = cl.snap.messageNum + 1; - - if ( newSnap.messageNum - oldMessageNum >= PACKET_BACKUP ) { - oldMessageNum = newSnap.messageNum - ( PACKET_BACKUP - 1 ); - } - for ( ; oldMessageNum < newSnap.messageNum ; oldMessageNum++ ) { - cl.snapshots[oldMessageNum & PACKET_MASK].valid = qfalse; - } - - // copy to the current good spot - cl.snap = newSnap; - cl.snap.ping = 999; - // calculate ping time - for ( i = 0 ; i < PACKET_BACKUP ; i++ ) { - packetNum = ( clc.netchan.outgoingSequence - 1 - i ) & PACKET_MASK; - if ( cl.snap.ps.commandTime >= cl.outPackets[ packetNum ].p_serverTime ) { - cl.snap.ping = cls.realtime - cl.outPackets[ packetNum ].p_realtime; - break; - } - } - // save the frame off in the backup array for later delta comparisons - cl.snapshots[cl.snap.messageNum & PACKET_MASK] = cl.snap; - - if (cl_shownet->integer == 3) { - Com_Printf( " snapshot:%i delta:%i ping:%i\n", cl.snap.messageNum, - cl.snap.deltaNum, cl.snap.ping ); - } - - cl.newSnapshots = qtrue; -} - - -//===================================================================== - -int cl_connectedToPureServer; - -/* -================== -CL_SystemInfoChanged - -The systeminfo configstring has been changed, so parse -new information out of it. This will happen at every -gamestate, and possibly during gameplay. -================== -*/ -void CL_SystemInfoChanged( void ) { - char *systemInfo; - const char *s, *t; - char key[BIG_INFO_KEY]; - char value[BIG_INFO_VALUE]; - qboolean gameSet; - - systemInfo = cl.gameState.stringData + cl.gameState.stringOffsets[ CS_SYSTEMINFO ]; - // NOTE TTimo: - // when the serverId changes, any further messages we send to the server will use this new serverId - // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=475 - // in some cases, outdated cp commands might get sent with this news serverId - cl.serverId = atoi( Info_ValueForKey( systemInfo, "sv_serverid" ) ); - - // don't set any vars when playing a demo - if ( clc.demoplaying ) { - return; - } - - s = Info_ValueForKey( systemInfo, "sv_cheats" ); - if ( atoi(s) == 0 ) { - Cvar_SetCheatState(); - } - - // check pure server string - s = Info_ValueForKey( systemInfo, "sv_paks" ); - t = Info_ValueForKey( systemInfo, "sv_pakNames" ); - FS_PureServerSetLoadedPaks( s, t ); - - s = Info_ValueForKey( systemInfo, "sv_referencedPaks" ); - t = Info_ValueForKey( systemInfo, "sv_referencedPakNames" ); - FS_PureServerSetReferencedPaks( s, t ); - - gameSet = qfalse; - // scan through all the variables in the systeminfo and locally set cvars to match - s = systemInfo; - while ( s ) { - Info_NextPair( &s, key, value ); - if ( !key[0] ) { - break; - } - // ehw! - if ( !Q_stricmp( key, "fs_game" ) ) { - gameSet = qtrue; - } - - Cvar_Set( key, value ); - } - // if game folder should not be set and it is set at the client side - if ( !gameSet && *Cvar_VariableString("fs_game") ) { - Cvar_Set( "fs_game", "" ); - } - cl_connectedToPureServer = Cvar_VariableValue( "sv_pure" ); -} - -/* -================== -CL_ParseGamestate -================== -*/ -void CL_ParseGamestate( msg_t *msg ) { - int i; - entityState_t *es; - int newnum; - entityState_t nullstate; - int cmd; - char *s; - - Con_Close(); - - clc.connectPacketCount = 0; - - // wipe local client state - CL_ClearState(); - - // a gamestate always marks a server command sequence - clc.serverCommandSequence = MSG_ReadLong( msg ); - - // parse all the configstrings and baselines - cl.gameState.dataCount = 1; // leave a 0 at the beginning for uninitialized configstrings - while ( 1 ) { - cmd = MSG_ReadByte( msg ); - - if ( cmd == svc_EOF ) { - break; - } - - if ( cmd == svc_configstring ) { - int len; - - i = MSG_ReadShort( msg ); - if ( i < 0 || i >= MAX_CONFIGSTRINGS ) { - Com_Error( ERR_DROP, "configstring > MAX_CONFIGSTRINGS" ); - } - s = MSG_ReadBigString( msg ); - len = strlen( s ); - - if ( len + 1 + cl.gameState.dataCount > MAX_GAMESTATE_CHARS ) { - Com_Error( ERR_DROP, "MAX_GAMESTATE_CHARS exceeded" ); - } - - // append it to the gameState string buffer - cl.gameState.stringOffsets[ i ] = cl.gameState.dataCount; - Com_Memcpy( cl.gameState.stringData + cl.gameState.dataCount, s, len + 1 ); - cl.gameState.dataCount += len + 1; - } else if ( cmd == svc_baseline ) { - newnum = MSG_ReadBits( msg, GENTITYNUM_BITS ); - if ( newnum < 0 || newnum >= MAX_GENTITIES ) { - Com_Error( ERR_DROP, "Baseline number out of range: %i", newnum ); - } - Com_Memset (&nullstate, 0, sizeof(nullstate)); - es = &cl.entityBaselines[ newnum ]; - MSG_ReadDeltaEntity( msg, &nullstate, es, newnum ); - } else { - Com_Error( ERR_DROP, "CL_ParseGamestate: bad command byte" ); - } - } - - clc.clientNum = MSG_ReadLong(msg); - // read the checksum feed - clc.checksumFeed = MSG_ReadLong( msg ); - - // parse serverId and other cvars - CL_SystemInfoChanged(); - - // reinitialize the filesystem if the game directory has changed - FS_ConditionalRestart( clc.checksumFeed ); - - // This used to call CL_StartHunkUsers, but now we enter the download state before loading the - // cgame - CL_InitDownloads(); - - // make sure the game starts - Cvar_Set( "cl_paused", "0" ); -} - - -//===================================================================== - -/* -===================== -CL_ParseDownload - -A download message has been received from the server -===================== -*/ -void CL_ParseDownload ( msg_t *msg ) { - int size; - unsigned char data[MAX_MSGLEN]; - int block; - - // read the data - block = MSG_ReadShort ( msg ); - - if ( !block ) - { - // block zero is special, contains file size - clc.downloadSize = MSG_ReadLong ( msg ); - - Cvar_SetValue( "cl_downloadSize", clc.downloadSize ); - - if (clc.downloadSize < 0) - { - Com_Error(ERR_DROP, MSG_ReadString( msg ) ); - return; - } - } - - size = MSG_ReadShort ( msg ); - if (size > 0) - MSG_ReadData( msg, data, size ); - - if (clc.downloadBlock != block) { - Com_DPrintf( "CL_ParseDownload: Expected block %d, got %d\n", clc.downloadBlock, block); - return; - } - - // open the file if not opened yet - if (!clc.download) - { - if (!*clc.downloadTempName) { - Com_Printf("Server sending download, but no download was requested\n"); - CL_AddReliableCommand( "stopdl" ); - return; - } - - clc.download = FS_SV_FOpenFileWrite( clc.downloadTempName ); - - if (!clc.download) { - Com_Printf( "Could not create %s\n", clc.downloadTempName ); - CL_AddReliableCommand( "stopdl" ); - CL_NextDownload(); - return; - } - } - - if (size) - FS_Write( data, size, clc.download ); - - CL_AddReliableCommand( va("nextdl %d", clc.downloadBlock) ); - clc.downloadBlock++; - - clc.downloadCount += size; - - // So UI gets access to it - Cvar_SetValue( "cl_downloadCount", clc.downloadCount ); - - if (!size) { // A zero length block means EOF - if (clc.download) { - FS_FCloseFile( clc.download ); - clc.download = 0; - - // rename the file - FS_SV_Rename ( clc.downloadTempName, clc.downloadName ); - } - *clc.downloadTempName = *clc.downloadName = 0; - Cvar_Set( "cl_downloadName", "" ); - - // send intentions now - // We need this because without it, we would hold the last nextdl and then start - // loading right away. If we take a while to load, the server is happily trying - // to send us that last block over and over. - // Write it twice to help make sure we acknowledge the download - CL_WritePacket(); - CL_WritePacket(); - - // get another file if needed - CL_NextDownload (); - } -} - -/* -===================== -CL_ParseCommandString - -Command strings are just saved off until cgame asks for them -when it transitions a snapshot -===================== -*/ -void CL_ParseCommandString( msg_t *msg ) { - char *s; - int seq; - int index; - - seq = MSG_ReadLong( msg ); - s = MSG_ReadString( msg ); - - // see if we have already executed stored it off - if ( clc.serverCommandSequence >= seq ) { - return; - } - clc.serverCommandSequence = seq; - - index = seq & (MAX_RELIABLE_COMMANDS-1); - Q_strncpyz( clc.serverCommands[ index ], s, sizeof( clc.serverCommands[ index ] ) ); -} - - -/* -===================== -CL_ParseServerMessage -===================== -*/ -void CL_ParseServerMessage( msg_t *msg ) { - int cmd; - - if ( cl_shownet->integer == 1 ) { - Com_Printf ("%i ",msg->cursize); - } else if ( cl_shownet->integer >= 2 ) { - Com_Printf ("------------------\n"); - } - - MSG_Bitstream(msg); - - // get the reliable sequence acknowledge number - clc.reliableAcknowledge = MSG_ReadLong( msg ); - // - if ( clc.reliableAcknowledge < clc.reliableSequence - MAX_RELIABLE_COMMANDS ) { - clc.reliableAcknowledge = clc.reliableSequence; - } - - // - // parse the message - // - while ( 1 ) { - if ( msg->readcount > msg->cursize ) { - Com_Error (ERR_DROP,"CL_ParseServerMessage: read past end of server message"); - break; - } - - cmd = MSG_ReadByte( msg ); - - if ( cmd == svc_EOF) { - SHOWNET( msg, "END OF MESSAGE" ); - break; - } - - if ( cl_shownet->integer >= 2 ) { - if ( !svc_strings[cmd] ) { - Com_Printf( "%3i:BAD CMD %i\n", msg->readcount-1, cmd ); - } else { - SHOWNET( msg, svc_strings[cmd] ); - } - } - - // other commands - switch ( cmd ) { - default: - Com_Error (ERR_DROP,"CL_ParseServerMessage: Illegible server message\n"); - break; - case svc_nop: - break; - case svc_serverCommand: - CL_ParseCommandString( msg ); - break; - case svc_gamestate: - CL_ParseGamestate( msg ); - break; - case svc_snapshot: - CL_ParseSnapshot( msg ); - break; - case svc_download: - CL_ParseDownload( msg ); - break; - } - } -} - - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// cl_parse.c -- parse a message received from the server + +#include "client.h" + +char *svc_strings[256] = { + "svc_bad", + + "svc_nop", + "svc_gamestate", + "svc_configstring", + "svc_baseline", + "svc_serverCommand", + "svc_download", + "svc_snapshot" +}; + +void SHOWNET( msg_t *msg, char *s) { + if ( cl_shownet->integer >= 2) { + Com_Printf ("%3i:%s\n", msg->readcount-1, s); + } +} + + +/* +========================================================================= + +MESSAGE PARSING + +========================================================================= +*/ + +/* +================== +CL_DeltaEntity + +Parses deltas from the given base and adds the resulting entity +to the current frame +================== +*/ +void CL_DeltaEntity (msg_t *msg, clSnapshot_t *frame, int newnum, entityState_t *old, + qboolean unchanged) { + entityState_t *state; + + // save the parsed entity state into the big circular buffer so + // it can be used as the source for a later delta + state = &cl.parseEntities[cl.parseEntitiesNum & (MAX_PARSE_ENTITIES-1)]; + + if ( unchanged ) { + *state = *old; + } else { + MSG_ReadDeltaEntity( msg, old, state, newnum ); + } + + if ( state->number == (MAX_GENTITIES-1) ) { + return; // entity was delta removed + } + cl.parseEntitiesNum++; + frame->numEntities++; +} + +/* +================== +CL_ParsePacketEntities + +================== +*/ +void CL_ParsePacketEntities( msg_t *msg, clSnapshot_t *oldframe, clSnapshot_t *newframe) { + int newnum; + entityState_t *oldstate; + int oldindex, oldnum; + + newframe->parseEntitiesNum = cl.parseEntitiesNum; + newframe->numEntities = 0; + + // delta from the entities present in oldframe + oldindex = 0; + oldstate = NULL; + if (!oldframe) { + oldnum = 99999; + } else { + if ( oldindex >= oldframe->numEntities ) { + oldnum = 99999; + } else { + oldstate = &cl.parseEntities[ + (oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)]; + oldnum = oldstate->number; + } + } + + while ( 1 ) { + // read the entity index number + newnum = MSG_ReadBits( msg, GENTITYNUM_BITS ); + + if ( newnum == (MAX_GENTITIES-1) ) { + break; + } + + if ( msg->readcount > msg->cursize ) { + Com_Error (ERR_DROP,"CL_ParsePacketEntities: end of message"); + } + + while ( oldnum < newnum ) { + // one or more entities from the old packet are unchanged + if ( cl_shownet->integer == 3 ) { + Com_Printf ("%3i: unchanged: %i\n", msg->readcount, oldnum); + } + CL_DeltaEntity( msg, newframe, oldnum, oldstate, qtrue ); + + oldindex++; + + if ( oldindex >= oldframe->numEntities ) { + oldnum = 99999; + } else { + oldstate = &cl.parseEntities[ + (oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)]; + oldnum = oldstate->number; + } + } + if (oldnum == newnum) { + // delta from previous state + if ( cl_shownet->integer == 3 ) { + Com_Printf ("%3i: delta: %i\n", msg->readcount, newnum); + } + CL_DeltaEntity( msg, newframe, newnum, oldstate, qfalse ); + + oldindex++; + + if ( oldindex >= oldframe->numEntities ) { + oldnum = 99999; + } else { + oldstate = &cl.parseEntities[ + (oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)]; + oldnum = oldstate->number; + } + continue; + } + + if ( oldnum > newnum ) { + // delta from baseline + if ( cl_shownet->integer == 3 ) { + Com_Printf ("%3i: baseline: %i\n", msg->readcount, newnum); + } + CL_DeltaEntity( msg, newframe, newnum, &cl.entityBaselines[newnum], qfalse ); + continue; + } + + } + + // any remaining entities in the old frame are copied over + while ( oldnum != 99999 ) { + // one or more entities from the old packet are unchanged + if ( cl_shownet->integer == 3 ) { + Com_Printf ("%3i: unchanged: %i\n", msg->readcount, oldnum); + } + CL_DeltaEntity( msg, newframe, oldnum, oldstate, qtrue ); + + oldindex++; + + if ( oldindex >= oldframe->numEntities ) { + oldnum = 99999; + } else { + oldstate = &cl.parseEntities[ + (oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)]; + oldnum = oldstate->number; + } + } +} + + +/* +================ +CL_ParseSnapshot + +If the snapshot is parsed properly, it will be copied to +cl.snap and saved in cl.snapshots[]. If the snapshot is invalid +for any reason, no changes to the state will be made at all. +================ +*/ +void CL_ParseSnapshot( msg_t *msg ) { + int len; + clSnapshot_t *old; + clSnapshot_t newSnap; + int deltaNum; + int oldMessageNum; + int i, packetNum; + + // get the reliable sequence acknowledge number + // NOTE: now sent with all server to client messages + //clc.reliableAcknowledge = MSG_ReadLong( msg ); + + // read in the new snapshot to a temporary buffer + // we will only copy to cl.snap if it is valid + Com_Memset (&newSnap, 0, sizeof(newSnap)); + + // we will have read any new server commands in this + // message before we got to svc_snapshot + newSnap.serverCommandNum = clc.serverCommandSequence; + + newSnap.serverTime = MSG_ReadLong( msg ); + + newSnap.messageNum = clc.serverMessageSequence; + + deltaNum = MSG_ReadByte( msg ); + if ( !deltaNum ) { + newSnap.deltaNum = -1; + } else { + newSnap.deltaNum = newSnap.messageNum - deltaNum; + } + newSnap.snapFlags = MSG_ReadByte( msg ); + + // If the frame is delta compressed from data that we + // no longer have available, we must suck up the rest of + // the frame, but not use it, then ask for a non-compressed + // message + if ( newSnap.deltaNum <= 0 ) { + newSnap.valid = qtrue; // uncompressed frame + old = NULL; + clc.demowaiting = qfalse; // we can start recording now + } else { + old = &cl.snapshots[newSnap.deltaNum & PACKET_MASK]; + if ( !old->valid ) { + // should never happen + Com_Printf ("Delta from invalid frame (not supposed to happen!).\n"); + } else if ( old->messageNum != newSnap.deltaNum ) { + // The frame that the server did the delta from + // is too old, so we can't reconstruct it properly. + Com_Printf ("Delta frame too old.\n"); + } else if ( cl.parseEntitiesNum - old->parseEntitiesNum > MAX_PARSE_ENTITIES-128 ) { + Com_Printf ("Delta parseEntitiesNum too old.\n"); + } else { + newSnap.valid = qtrue; // valid delta parse + } + } + + // read areamask + len = MSG_ReadByte( msg ); + MSG_ReadData( msg, &newSnap.areamask, len); + + // read playerinfo + SHOWNET( msg, "playerstate" ); + if ( old ) { + MSG_ReadDeltaPlayerstate( msg, &old->ps, &newSnap.ps ); + } else { + MSG_ReadDeltaPlayerstate( msg, NULL, &newSnap.ps ); + } + + // read packet entities + SHOWNET( msg, "packet entities" ); + CL_ParsePacketEntities( msg, old, &newSnap ); + + // if not valid, dump the entire thing now that it has + // been properly read + if ( !newSnap.valid ) { + return; + } + + // clear the valid flags of any snapshots between the last + // received and this one, so if there was a dropped packet + // it won't look like something valid to delta from next + // time we wrap around in the buffer + oldMessageNum = cl.snap.messageNum + 1; + + if ( newSnap.messageNum - oldMessageNum >= PACKET_BACKUP ) { + oldMessageNum = newSnap.messageNum - ( PACKET_BACKUP - 1 ); + } + for ( ; oldMessageNum < newSnap.messageNum ; oldMessageNum++ ) { + cl.snapshots[oldMessageNum & PACKET_MASK].valid = qfalse; + } + + // copy to the current good spot + cl.snap = newSnap; + cl.snap.ping = 999; + // calculate ping time + for ( i = 0 ; i < PACKET_BACKUP ; i++ ) { + packetNum = ( clc.netchan.outgoingSequence - 1 - i ) & PACKET_MASK; + if ( cl.snap.ps.commandTime >= cl.outPackets[ packetNum ].p_serverTime ) { + cl.snap.ping = cls.realtime - cl.outPackets[ packetNum ].p_realtime; + break; + } + } + // save the frame off in the backup array for later delta comparisons + cl.snapshots[cl.snap.messageNum & PACKET_MASK] = cl.snap; + + if (cl_shownet->integer == 3) { + Com_Printf( " snapshot:%i delta:%i ping:%i\n", cl.snap.messageNum, + cl.snap.deltaNum, cl.snap.ping ); + } + + cl.newSnapshots = qtrue; +} + + +//===================================================================== + +int cl_connectedToPureServer; + +/* +================== +CL_SystemInfoChanged + +The systeminfo configstring has been changed, so parse +new information out of it. This will happen at every +gamestate, and possibly during gameplay. +================== +*/ +void CL_SystemInfoChanged( void ) { + char *systemInfo; + const char *s, *t; + char key[BIG_INFO_KEY]; + char value[BIG_INFO_VALUE]; + qboolean gameSet; + + systemInfo = cl.gameState.stringData + cl.gameState.stringOffsets[ CS_SYSTEMINFO ]; + // NOTE TTimo: + // when the serverId changes, any further messages we send to the server will use this new serverId + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=475 + // in some cases, outdated cp commands might get sent with this news serverId + cl.serverId = atoi( Info_ValueForKey( systemInfo, "sv_serverid" ) ); + + // don't set any vars when playing a demo + if ( clc.demoplaying ) { + return; + } + + s = Info_ValueForKey( systemInfo, "sv_cheats" ); + if ( atoi(s) == 0 ) { + Cvar_SetCheatState(); + } + + // check pure server string + s = Info_ValueForKey( systemInfo, "sv_paks" ); + t = Info_ValueForKey( systemInfo, "sv_pakNames" ); + FS_PureServerSetLoadedPaks( s, t ); + + s = Info_ValueForKey( systemInfo, "sv_referencedPaks" ); + t = Info_ValueForKey( systemInfo, "sv_referencedPakNames" ); + FS_PureServerSetReferencedPaks( s, t ); + + gameSet = qfalse; + // scan through all the variables in the systeminfo and locally set cvars to match + s = systemInfo; + while ( s ) { + Info_NextPair( &s, key, value ); + if ( !key[0] ) { + break; + } + // ehw! + if ( !Q_stricmp( key, "fs_game" ) ) { + gameSet = qtrue; + } + + Cvar_Set( key, value ); + } + // if game folder should not be set and it is set at the client side + if ( !gameSet && *Cvar_VariableString("fs_game") ) { + Cvar_Set( "fs_game", "" ); + } + cl_connectedToPureServer = Cvar_VariableValue( "sv_pure" ); +} + +/* +================== +CL_ParseGamestate +================== +*/ +void CL_ParseGamestate( msg_t *msg ) { + int i; + entityState_t *es; + int newnum; + entityState_t nullstate; + int cmd; + char *s; + + Con_Close(); + + clc.connectPacketCount = 0; + + // wipe local client state + CL_ClearState(); + + // a gamestate always marks a server command sequence + clc.serverCommandSequence = MSG_ReadLong( msg ); + + // parse all the configstrings and baselines + cl.gameState.dataCount = 1; // leave a 0 at the beginning for uninitialized configstrings + while ( 1 ) { + cmd = MSG_ReadByte( msg ); + + if ( cmd == svc_EOF ) { + break; + } + + if ( cmd == svc_configstring ) { + int len; + + i = MSG_ReadShort( msg ); + if ( i < 0 || i >= MAX_CONFIGSTRINGS ) { + Com_Error( ERR_DROP, "configstring > MAX_CONFIGSTRINGS" ); + } + s = MSG_ReadBigString( msg ); + len = strlen( s ); + + if ( len + 1 + cl.gameState.dataCount > MAX_GAMESTATE_CHARS ) { + Com_Error( ERR_DROP, "MAX_GAMESTATE_CHARS exceeded" ); + } + + // append it to the gameState string buffer + cl.gameState.stringOffsets[ i ] = cl.gameState.dataCount; + Com_Memcpy( cl.gameState.stringData + cl.gameState.dataCount, s, len + 1 ); + cl.gameState.dataCount += len + 1; + } else if ( cmd == svc_baseline ) { + newnum = MSG_ReadBits( msg, GENTITYNUM_BITS ); + if ( newnum < 0 || newnum >= MAX_GENTITIES ) { + Com_Error( ERR_DROP, "Baseline number out of range: %i", newnum ); + } + Com_Memset (&nullstate, 0, sizeof(nullstate)); + es = &cl.entityBaselines[ newnum ]; + MSG_ReadDeltaEntity( msg, &nullstate, es, newnum ); + } else { + Com_Error( ERR_DROP, "CL_ParseGamestate: bad command byte" ); + } + } + + clc.clientNum = MSG_ReadLong(msg); + // read the checksum feed + clc.checksumFeed = MSG_ReadLong( msg ); + + // parse serverId and other cvars + CL_SystemInfoChanged(); + + // reinitialize the filesystem if the game directory has changed + FS_ConditionalRestart( clc.checksumFeed ); + + // This used to call CL_StartHunkUsers, but now we enter the download state before loading the + // cgame + CL_InitDownloads(); + + // make sure the game starts + Cvar_Set( "cl_paused", "0" ); +} + + +//===================================================================== + +/* +===================== +CL_ParseDownload + +A download message has been received from the server +===================== +*/ +void CL_ParseDownload ( msg_t *msg ) { + int size; + unsigned char data[MAX_MSGLEN]; + int block; + + // read the data + block = MSG_ReadShort ( msg ); + + if ( !block ) + { + // block zero is special, contains file size + clc.downloadSize = MSG_ReadLong ( msg ); + + Cvar_SetValue( "cl_downloadSize", clc.downloadSize ); + + if (clc.downloadSize < 0) + { + Com_Error(ERR_DROP, MSG_ReadString( msg ) ); + return; + } + } + + size = MSG_ReadShort ( msg ); + if (size > 0) + MSG_ReadData( msg, data, size ); + + if (clc.downloadBlock != block) { + Com_DPrintf( "CL_ParseDownload: Expected block %d, got %d\n", clc.downloadBlock, block); + return; + } + + // open the file if not opened yet + if (!clc.download) + { + if (!*clc.downloadTempName) { + Com_Printf("Server sending download, but no download was requested\n"); + CL_AddReliableCommand( "stopdl" ); + return; + } + + clc.download = FS_SV_FOpenFileWrite( clc.downloadTempName ); + + if (!clc.download) { + Com_Printf( "Could not create %s\n", clc.downloadTempName ); + CL_AddReliableCommand( "stopdl" ); + CL_NextDownload(); + return; + } + } + + if (size) + FS_Write( data, size, clc.download ); + + CL_AddReliableCommand( va("nextdl %d", clc.downloadBlock) ); + clc.downloadBlock++; + + clc.downloadCount += size; + + // So UI gets access to it + Cvar_SetValue( "cl_downloadCount", clc.downloadCount ); + + if (!size) { // A zero length block means EOF + if (clc.download) { + FS_FCloseFile( clc.download ); + clc.download = 0; + + // rename the file + FS_SV_Rename ( clc.downloadTempName, clc.downloadName ); + } + *clc.downloadTempName = *clc.downloadName = 0; + Cvar_Set( "cl_downloadName", "" ); + + // send intentions now + // We need this because without it, we would hold the last nextdl and then start + // loading right away. If we take a while to load, the server is happily trying + // to send us that last block over and over. + // Write it twice to help make sure we acknowledge the download + CL_WritePacket(); + CL_WritePacket(); + + // get another file if needed + CL_NextDownload (); + } +} + +/* +===================== +CL_ParseCommandString + +Command strings are just saved off until cgame asks for them +when it transitions a snapshot +===================== +*/ +void CL_ParseCommandString( msg_t *msg ) { + char *s; + int seq; + int index; + + seq = MSG_ReadLong( msg ); + s = MSG_ReadString( msg ); + + // see if we have already executed stored it off + if ( clc.serverCommandSequence >= seq ) { + return; + } + clc.serverCommandSequence = seq; + + index = seq & (MAX_RELIABLE_COMMANDS-1); + Q_strncpyz( clc.serverCommands[ index ], s, sizeof( clc.serverCommands[ index ] ) ); +} + + +/* +===================== +CL_ParseServerMessage +===================== +*/ +void CL_ParseServerMessage( msg_t *msg ) { + int cmd; + + if ( cl_shownet->integer == 1 ) { + Com_Printf ("%i ",msg->cursize); + } else if ( cl_shownet->integer >= 2 ) { + Com_Printf ("------------------\n"); + } + + MSG_Bitstream(msg); + + // get the reliable sequence acknowledge number + clc.reliableAcknowledge = MSG_ReadLong( msg ); + // + if ( clc.reliableAcknowledge < clc.reliableSequence - MAX_RELIABLE_COMMANDS ) { + clc.reliableAcknowledge = clc.reliableSequence; + } + + // + // parse the message + // + while ( 1 ) { + if ( msg->readcount > msg->cursize ) { + Com_Error (ERR_DROP,"CL_ParseServerMessage: read past end of server message"); + break; + } + + cmd = MSG_ReadByte( msg ); + + if ( cmd == svc_EOF) { + SHOWNET( msg, "END OF MESSAGE" ); + break; + } + + if ( cl_shownet->integer >= 2 ) { + if ( !svc_strings[cmd] ) { + Com_Printf( "%3i:BAD CMD %i\n", msg->readcount-1, cmd ); + } else { + SHOWNET( msg, svc_strings[cmd] ); + } + } + + // other commands + switch ( cmd ) { + default: + Com_Error (ERR_DROP,"CL_ParseServerMessage: Illegible server message\n"); + break; + case svc_nop: + break; + case svc_serverCommand: + CL_ParseCommandString( msg ); + break; + case svc_gamestate: + CL_ParseGamestate( msg ); + break; + case svc_snapshot: + CL_ParseSnapshot( msg ); + break; + case svc_download: + CL_ParseDownload( msg ); + break; + } + } +} + + diff --git a/code/client/cl_scrn.c b/code/client/cl_scrn.c index c4ccb40..a8da4ed 100755 --- a/code/client/cl_scrn.c +++ b/code/client/cl_scrn.c @@ -1,547 +1,547 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// cl_scrn.c -- master for refresh, status bar, console, chat, notify, etc - -#include "client.h" - -qboolean scr_initialized; // ready to draw - -cvar_t *cl_timegraph; -cvar_t *cl_debuggraph; -cvar_t *cl_graphheight; -cvar_t *cl_graphscale; -cvar_t *cl_graphshift; - -/* -================ -SCR_DrawNamedPic - -Coordinates are 640*480 virtual values -================= -*/ -void SCR_DrawNamedPic( float x, float y, float width, float height, const char *picname ) { - qhandle_t hShader; - - assert( width != 0 ); - - hShader = re.RegisterShader( picname ); - SCR_AdjustFrom640( &x, &y, &width, &height ); - re.DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader ); -} - - -/* -================ -SCR_AdjustFrom640 - -Adjusted for resolution and screen aspect ratio -================ -*/ -void SCR_AdjustFrom640( float *x, float *y, float *w, float *h ) { - float xscale; - float yscale; - -#if 0 - // adjust for wide screens - if ( cls.glconfig.vidWidth * 480 > cls.glconfig.vidHeight * 640 ) { - *x += 0.5 * ( cls.glconfig.vidWidth - ( cls.glconfig.vidHeight * 640 / 480 ) ); - } -#endif - - // scale for screen sizes - xscale = cls.glconfig.vidWidth / 640.0; - yscale = cls.glconfig.vidHeight / 480.0; - if ( x ) { - *x *= xscale; - } - if ( y ) { - *y *= yscale; - } - if ( w ) { - *w *= xscale; - } - if ( h ) { - *h *= yscale; - } -} - -/* -================ -SCR_FillRect - -Coordinates are 640*480 virtual values -================= -*/ -void SCR_FillRect( float x, float y, float width, float height, const float *color ) { - re.SetColor( color ); - - SCR_AdjustFrom640( &x, &y, &width, &height ); - re.DrawStretchPic( x, y, width, height, 0, 0, 0, 0, cls.whiteShader ); - - re.SetColor( NULL ); -} - - -/* -================ -SCR_DrawPic - -Coordinates are 640*480 virtual values -================= -*/ -void SCR_DrawPic( float x, float y, float width, float height, qhandle_t hShader ) { - SCR_AdjustFrom640( &x, &y, &width, &height ); - re.DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader ); -} - - - -/* -** SCR_DrawChar -** chars are drawn at 640*480 virtual screen size -*/ -static void SCR_DrawChar( int x, int y, float size, int ch ) { - int row, col; - float frow, fcol; - float ax, ay, aw, ah; - - ch &= 255; - - if ( ch == ' ' ) { - return; - } - - if ( y < -size ) { - return; - } - - ax = x; - ay = y; - aw = size; - ah = size; - SCR_AdjustFrom640( &ax, &ay, &aw, &ah ); - - row = ch>>4; - col = ch&15; - - frow = row*0.0625; - fcol = col*0.0625; - size = 0.0625; - - re.DrawStretchPic( ax, ay, aw, ah, - fcol, frow, - fcol + size, frow + size, - cls.charSetShader ); -} - -/* -** SCR_DrawSmallChar -** small chars are drawn at native screen resolution -*/ -void SCR_DrawSmallChar( int x, int y, int ch ) { - int row, col; - float frow, fcol; - float size; - - ch &= 255; - - if ( ch == ' ' ) { - return; - } - - if ( y < -SMALLCHAR_HEIGHT ) { - return; - } - - row = ch>>4; - col = ch&15; - - frow = row*0.0625; - fcol = col*0.0625; - size = 0.0625; - - re.DrawStretchPic( x, y, SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, - fcol, frow, - fcol + size, frow + size, - cls.charSetShader ); -} - - -/* -================== -SCR_DrawBigString[Color] - -Draws a multi-colored string with a drop shadow, optionally forcing -to a fixed color. - -Coordinates are at 640 by 480 virtual resolution -================== -*/ -void SCR_DrawStringExt( int x, int y, float size, const char *string, float *setColor, qboolean forceColor ) { - vec4_t color; - const char *s; - int xx; - - // draw the drop shadow - color[0] = color[1] = color[2] = 0; - color[3] = setColor[3]; - re.SetColor( color ); - s = string; - xx = x; - while ( *s ) { - if ( Q_IsColorString( s ) ) { - s += 2; - continue; - } - SCR_DrawChar( xx+2, y+2, size, *s ); - xx += size; - s++; - } - - - // draw the colored text - s = string; - xx = x; - re.SetColor( setColor ); - while ( *s ) { - if ( Q_IsColorString( s ) ) { - if ( !forceColor ) { - Com_Memcpy( color, g_color_table[ColorIndex(*(s+1))], sizeof( color ) ); - color[3] = setColor[3]; - re.SetColor( color ); - } - s += 2; - continue; - } - SCR_DrawChar( xx, y, size, *s ); - xx += size; - s++; - } - re.SetColor( NULL ); -} - - -void SCR_DrawBigString( int x, int y, const char *s, float alpha ) { - float color[4]; - - color[0] = color[1] = color[2] = 1.0; - color[3] = alpha; - SCR_DrawStringExt( x, y, BIGCHAR_WIDTH, s, color, qfalse ); -} - -void SCR_DrawBigStringColor( int x, int y, const char *s, vec4_t color ) { - SCR_DrawStringExt( x, y, BIGCHAR_WIDTH, s, color, qtrue ); -} - - -/* -================== -SCR_DrawSmallString[Color] - -Draws a multi-colored string with a drop shadow, optionally forcing -to a fixed color. - -Coordinates are at 640 by 480 virtual resolution -================== -*/ -void SCR_DrawSmallStringExt( int x, int y, const char *string, float *setColor, qboolean forceColor ) { - vec4_t color; - const char *s; - int xx; - - // draw the colored text - s = string; - xx = x; - re.SetColor( setColor ); - while ( *s ) { - if ( Q_IsColorString( s ) ) { - if ( !forceColor ) { - Com_Memcpy( color, g_color_table[ColorIndex(*(s+1))], sizeof( color ) ); - color[3] = setColor[3]; - re.SetColor( color ); - } - s += 2; - continue; - } - SCR_DrawSmallChar( xx, y, *s ); - xx += SMALLCHAR_WIDTH; - s++; - } - re.SetColor( NULL ); -} - - - -/* -** SCR_Strlen -- skips color escape codes -*/ -static int SCR_Strlen( const char *str ) { - const char *s = str; - int count = 0; - - while ( *s ) { - if ( Q_IsColorString( s ) ) { - s += 2; - } else { - count++; - s++; - } - } - - return count; -} - -/* -** SCR_GetBigStringWidth -*/ -int SCR_GetBigStringWidth( const char *str ) { - return SCR_Strlen( str ) * 16; -} - - -//=============================================================================== - -/* -================= -SCR_DrawDemoRecording -================= -*/ -void SCR_DrawDemoRecording( void ) { - char string[1024]; - int pos; - - if ( !clc.demorecording ) { - return; - } - if ( clc.spDemoRecording ) { - return; - } - - pos = FS_FTell( clc.demofile ); - sprintf( string, "RECORDING %s: %ik", clc.demoName, pos / 1024 ); - - SCR_DrawStringExt( 320 - strlen( string ) * 4, 20, 8, string, g_color_table[7], qtrue ); -} - - -/* -=============================================================================== - -DEBUG GRAPH - -=============================================================================== -*/ - -typedef struct -{ - float value; - int color; -} graphsamp_t; - -static int current; -static graphsamp_t values[1024]; - -/* -============== -SCR_DebugGraph -============== -*/ -void SCR_DebugGraph (float value, int color) -{ - values[current&1023].value = value; - values[current&1023].color = color; - current++; -} - -/* -============== -SCR_DrawDebugGraph -============== -*/ -void SCR_DrawDebugGraph (void) -{ - int a, x, y, w, i, h; - float v; - int color; - - // - // draw the graph - // - w = cls.glconfig.vidWidth; - x = 0; - y = cls.glconfig.vidHeight; - re.SetColor( g_color_table[0] ); - re.DrawStretchPic(x, y - cl_graphheight->integer, - w, cl_graphheight->integer, 0, 0, 0, 0, cls.whiteShader ); - re.SetColor( NULL ); - - for (a=0 ; ainteger + cl_graphshift->integer; - - if (v < 0) - v += cl_graphheight->integer * (1+(int)(-v / cl_graphheight->integer)); - h = (int)v % cl_graphheight->integer; - re.DrawStretchPic( x+w-1-a, y - h, 1, h, 0, 0, 0, 0, cls.whiteShader ); - } -} - -//============================================================================= - -/* -================== -SCR_Init -================== -*/ -void SCR_Init( void ) { - cl_timegraph = Cvar_Get ("timegraph", "0", CVAR_CHEAT); - cl_debuggraph = Cvar_Get ("debuggraph", "0", CVAR_CHEAT); - cl_graphheight = Cvar_Get ("graphheight", "32", CVAR_CHEAT); - cl_graphscale = Cvar_Get ("graphscale", "1", CVAR_CHEAT); - cl_graphshift = Cvar_Get ("graphshift", "0", CVAR_CHEAT); - - scr_initialized = qtrue; -} - - -//======================================================= - -/* -================== -SCR_DrawScreenField - -This will be called twice if rendering in stereo mode -================== -*/ -void SCR_DrawScreenField( stereoFrame_t stereoFrame ) { - re.BeginFrame( stereoFrame ); - - // wide aspect ratio screens need to have the sides cleared - // unless they are displaying game renderings - if ( cls.state != CA_ACTIVE ) { - if ( cls.glconfig.vidWidth * 480 > cls.glconfig.vidHeight * 640 ) { - re.SetColor( g_color_table[0] ); - re.DrawStretchPic( 0, 0, cls.glconfig.vidWidth, cls.glconfig.vidHeight, 0, 0, 0, 0, cls.whiteShader ); - re.SetColor( NULL ); - } - } - - if ( !uivm ) { - Com_DPrintf("draw screen without UI loaded\n"); - return; - } - - // if the menu is going to cover the entire screen, we - // don't need to render anything under it - if ( !VM_Call( uivm, UI_IS_FULLSCREEN )) { - switch( cls.state ) { - default: - Com_Error( ERR_FATAL, "SCR_DrawScreenField: bad cls.state" ); - break; - case CA_CINEMATIC: - SCR_DrawCinematic(); - break; - case CA_DISCONNECTED: - // force menu up - S_StopAllSounds(); - VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_MAIN ); - break; - case CA_CONNECTING: - case CA_CHALLENGING: - case CA_CONNECTED: - // connecting clients will only show the connection dialog - // refresh to update the time - VM_Call( uivm, UI_REFRESH, cls.realtime ); - VM_Call( uivm, UI_DRAW_CONNECT_SCREEN, qfalse ); - break; - case CA_LOADING: - case CA_PRIMED: - // draw the game information screen and loading progress - CL_CGameRendering( stereoFrame ); - - // also draw the connection information, so it doesn't - // flash away too briefly on local or lan games - // refresh to update the time - VM_Call( uivm, UI_REFRESH, cls.realtime ); - VM_Call( uivm, UI_DRAW_CONNECT_SCREEN, qtrue ); - break; - case CA_ACTIVE: - CL_CGameRendering( stereoFrame ); - SCR_DrawDemoRecording(); - break; - } - } - - // the menu draws next - if ( cls.keyCatchers & KEYCATCH_UI && uivm ) { - VM_Call( uivm, UI_REFRESH, cls.realtime ); - } - - // console draws next - Con_DrawConsole (); - - // debug graph can be drawn on top of anything - if ( cl_debuggraph->integer || cl_timegraph->integer || cl_debugMove->integer ) { - SCR_DrawDebugGraph (); - } -} - -/* -================== -SCR_UpdateScreen - -This is called every frame, and can also be called explicitly to flush -text to the screen. -================== -*/ -void SCR_UpdateScreen( void ) { - static int recursive; - - if ( !scr_initialized ) { - return; // not initialized yet - } - - if ( ++recursive > 2 ) { - Com_Error( ERR_FATAL, "SCR_UpdateScreen: recursively called" ); - } - recursive = 1; - - // if running in stereo, we need to draw the frame twice - if ( cls.glconfig.stereoEnabled ) { - SCR_DrawScreenField( STEREO_LEFT ); - SCR_DrawScreenField( STEREO_RIGHT ); - } else { - SCR_DrawScreenField( STEREO_CENTER ); - } - - if ( com_speeds->integer ) { - re.EndFrame( &time_frontend, &time_backend ); - } else { - re.EndFrame( NULL, NULL ); - } - - recursive = 0; -} - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// cl_scrn.c -- master for refresh, status bar, console, chat, notify, etc + +#include "client.h" + +qboolean scr_initialized; // ready to draw + +cvar_t *cl_timegraph; +cvar_t *cl_debuggraph; +cvar_t *cl_graphheight; +cvar_t *cl_graphscale; +cvar_t *cl_graphshift; + +/* +================ +SCR_DrawNamedPic + +Coordinates are 640*480 virtual values +================= +*/ +void SCR_DrawNamedPic( float x, float y, float width, float height, const char *picname ) { + qhandle_t hShader; + + assert( width != 0 ); + + hShader = re.RegisterShader( picname ); + SCR_AdjustFrom640( &x, &y, &width, &height ); + re.DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader ); +} + + +/* +================ +SCR_AdjustFrom640 + +Adjusted for resolution and screen aspect ratio +================ +*/ +void SCR_AdjustFrom640( float *x, float *y, float *w, float *h ) { + float xscale; + float yscale; + +#if 0 + // adjust for wide screens + if ( cls.glconfig.vidWidth * 480 > cls.glconfig.vidHeight * 640 ) { + *x += 0.5 * ( cls.glconfig.vidWidth - ( cls.glconfig.vidHeight * 640 / 480 ) ); + } +#endif + + // scale for screen sizes + xscale = cls.glconfig.vidWidth / 640.0; + yscale = cls.glconfig.vidHeight / 480.0; + if ( x ) { + *x *= xscale; + } + if ( y ) { + *y *= yscale; + } + if ( w ) { + *w *= xscale; + } + if ( h ) { + *h *= yscale; + } +} + +/* +================ +SCR_FillRect + +Coordinates are 640*480 virtual values +================= +*/ +void SCR_FillRect( float x, float y, float width, float height, const float *color ) { + re.SetColor( color ); + + SCR_AdjustFrom640( &x, &y, &width, &height ); + re.DrawStretchPic( x, y, width, height, 0, 0, 0, 0, cls.whiteShader ); + + re.SetColor( NULL ); +} + + +/* +================ +SCR_DrawPic + +Coordinates are 640*480 virtual values +================= +*/ +void SCR_DrawPic( float x, float y, float width, float height, qhandle_t hShader ) { + SCR_AdjustFrom640( &x, &y, &width, &height ); + re.DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader ); +} + + + +/* +** SCR_DrawChar +** chars are drawn at 640*480 virtual screen size +*/ +static void SCR_DrawChar( int x, int y, float size, int ch ) { + int row, col; + float frow, fcol; + float ax, ay, aw, ah; + + ch &= 255; + + if ( ch == ' ' ) { + return; + } + + if ( y < -size ) { + return; + } + + ax = x; + ay = y; + aw = size; + ah = size; + SCR_AdjustFrom640( &ax, &ay, &aw, &ah ); + + row = ch>>4; + col = ch&15; + + frow = row*0.0625; + fcol = col*0.0625; + size = 0.0625; + + re.DrawStretchPic( ax, ay, aw, ah, + fcol, frow, + fcol + size, frow + size, + cls.charSetShader ); +} + +/* +** SCR_DrawSmallChar +** small chars are drawn at native screen resolution +*/ +void SCR_DrawSmallChar( int x, int y, int ch ) { + int row, col; + float frow, fcol; + float size; + + ch &= 255; + + if ( ch == ' ' ) { + return; + } + + if ( y < -SMALLCHAR_HEIGHT ) { + return; + } + + row = ch>>4; + col = ch&15; + + frow = row*0.0625; + fcol = col*0.0625; + size = 0.0625; + + re.DrawStretchPic( x, y, SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, + fcol, frow, + fcol + size, frow + size, + cls.charSetShader ); +} + + +/* +================== +SCR_DrawBigString[Color] + +Draws a multi-colored string with a drop shadow, optionally forcing +to a fixed color. + +Coordinates are at 640 by 480 virtual resolution +================== +*/ +void SCR_DrawStringExt( int x, int y, float size, const char *string, float *setColor, qboolean forceColor ) { + vec4_t color; + const char *s; + int xx; + + // draw the drop shadow + color[0] = color[1] = color[2] = 0; + color[3] = setColor[3]; + re.SetColor( color ); + s = string; + xx = x; + while ( *s ) { + if ( Q_IsColorString( s ) ) { + s += 2; + continue; + } + SCR_DrawChar( xx+2, y+2, size, *s ); + xx += size; + s++; + } + + + // draw the colored text + s = string; + xx = x; + re.SetColor( setColor ); + while ( *s ) { + if ( Q_IsColorString( s ) ) { + if ( !forceColor ) { + Com_Memcpy( color, g_color_table[ColorIndex(*(s+1))], sizeof( color ) ); + color[3] = setColor[3]; + re.SetColor( color ); + } + s += 2; + continue; + } + SCR_DrawChar( xx, y, size, *s ); + xx += size; + s++; + } + re.SetColor( NULL ); +} + + +void SCR_DrawBigString( int x, int y, const char *s, float alpha ) { + float color[4]; + + color[0] = color[1] = color[2] = 1.0; + color[3] = alpha; + SCR_DrawStringExt( x, y, BIGCHAR_WIDTH, s, color, qfalse ); +} + +void SCR_DrawBigStringColor( int x, int y, const char *s, vec4_t color ) { + SCR_DrawStringExt( x, y, BIGCHAR_WIDTH, s, color, qtrue ); +} + + +/* +================== +SCR_DrawSmallString[Color] + +Draws a multi-colored string with a drop shadow, optionally forcing +to a fixed color. + +Coordinates are at 640 by 480 virtual resolution +================== +*/ +void SCR_DrawSmallStringExt( int x, int y, const char *string, float *setColor, qboolean forceColor ) { + vec4_t color; + const char *s; + int xx; + + // draw the colored text + s = string; + xx = x; + re.SetColor( setColor ); + while ( *s ) { + if ( Q_IsColorString( s ) ) { + if ( !forceColor ) { + Com_Memcpy( color, g_color_table[ColorIndex(*(s+1))], sizeof( color ) ); + color[3] = setColor[3]; + re.SetColor( color ); + } + s += 2; + continue; + } + SCR_DrawSmallChar( xx, y, *s ); + xx += SMALLCHAR_WIDTH; + s++; + } + re.SetColor( NULL ); +} + + + +/* +** SCR_Strlen -- skips color escape codes +*/ +static int SCR_Strlen( const char *str ) { + const char *s = str; + int count = 0; + + while ( *s ) { + if ( Q_IsColorString( s ) ) { + s += 2; + } else { + count++; + s++; + } + } + + return count; +} + +/* +** SCR_GetBigStringWidth +*/ +int SCR_GetBigStringWidth( const char *str ) { + return SCR_Strlen( str ) * 16; +} + + +//=============================================================================== + +/* +================= +SCR_DrawDemoRecording +================= +*/ +void SCR_DrawDemoRecording( void ) { + char string[1024]; + int pos; + + if ( !clc.demorecording ) { + return; + } + if ( clc.spDemoRecording ) { + return; + } + + pos = FS_FTell( clc.demofile ); + sprintf( string, "RECORDING %s: %ik", clc.demoName, pos / 1024 ); + + SCR_DrawStringExt( 320 - strlen( string ) * 4, 20, 8, string, g_color_table[7], qtrue ); +} + + +/* +=============================================================================== + +DEBUG GRAPH + +=============================================================================== +*/ + +typedef struct +{ + float value; + int color; +} graphsamp_t; + +static int current; +static graphsamp_t values[1024]; + +/* +============== +SCR_DebugGraph +============== +*/ +void SCR_DebugGraph (float value, int color) +{ + values[current&1023].value = value; + values[current&1023].color = color; + current++; +} + +/* +============== +SCR_DrawDebugGraph +============== +*/ +void SCR_DrawDebugGraph (void) +{ + int a, x, y, w, i, h; + float v; + int color; + + // + // draw the graph + // + w = cls.glconfig.vidWidth; + x = 0; + y = cls.glconfig.vidHeight; + re.SetColor( g_color_table[0] ); + re.DrawStretchPic(x, y - cl_graphheight->integer, + w, cl_graphheight->integer, 0, 0, 0, 0, cls.whiteShader ); + re.SetColor( NULL ); + + for (a=0 ; ainteger + cl_graphshift->integer; + + if (v < 0) + v += cl_graphheight->integer * (1+(int)(-v / cl_graphheight->integer)); + h = (int)v % cl_graphheight->integer; + re.DrawStretchPic( x+w-1-a, y - h, 1, h, 0, 0, 0, 0, cls.whiteShader ); + } +} + +//============================================================================= + +/* +================== +SCR_Init +================== +*/ +void SCR_Init( void ) { + cl_timegraph = Cvar_Get ("timegraph", "0", CVAR_CHEAT); + cl_debuggraph = Cvar_Get ("debuggraph", "0", CVAR_CHEAT); + cl_graphheight = Cvar_Get ("graphheight", "32", CVAR_CHEAT); + cl_graphscale = Cvar_Get ("graphscale", "1", CVAR_CHEAT); + cl_graphshift = Cvar_Get ("graphshift", "0", CVAR_CHEAT); + + scr_initialized = qtrue; +} + + +//======================================================= + +/* +================== +SCR_DrawScreenField + +This will be called twice if rendering in stereo mode +================== +*/ +void SCR_DrawScreenField( stereoFrame_t stereoFrame ) { + re.BeginFrame( stereoFrame ); + + // wide aspect ratio screens need to have the sides cleared + // unless they are displaying game renderings + if ( cls.state != CA_ACTIVE ) { + if ( cls.glconfig.vidWidth * 480 > cls.glconfig.vidHeight * 640 ) { + re.SetColor( g_color_table[0] ); + re.DrawStretchPic( 0, 0, cls.glconfig.vidWidth, cls.glconfig.vidHeight, 0, 0, 0, 0, cls.whiteShader ); + re.SetColor( NULL ); + } + } + + if ( !uivm ) { + Com_DPrintf("draw screen without UI loaded\n"); + return; + } + + // if the menu is going to cover the entire screen, we + // don't need to render anything under it + if ( !VM_Call( uivm, UI_IS_FULLSCREEN )) { + switch( cls.state ) { + default: + Com_Error( ERR_FATAL, "SCR_DrawScreenField: bad cls.state" ); + break; + case CA_CINEMATIC: + SCR_DrawCinematic(); + break; + case CA_DISCONNECTED: + // force menu up + S_StopAllSounds(); + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_MAIN ); + break; + case CA_CONNECTING: + case CA_CHALLENGING: + case CA_CONNECTED: + // connecting clients will only show the connection dialog + // refresh to update the time + VM_Call( uivm, UI_REFRESH, cls.realtime ); + VM_Call( uivm, UI_DRAW_CONNECT_SCREEN, qfalse ); + break; + case CA_LOADING: + case CA_PRIMED: + // draw the game information screen and loading progress + CL_CGameRendering( stereoFrame ); + + // also draw the connection information, so it doesn't + // flash away too briefly on local or lan games + // refresh to update the time + VM_Call( uivm, UI_REFRESH, cls.realtime ); + VM_Call( uivm, UI_DRAW_CONNECT_SCREEN, qtrue ); + break; + case CA_ACTIVE: + CL_CGameRendering( stereoFrame ); + SCR_DrawDemoRecording(); + break; + } + } + + // the menu draws next + if ( cls.keyCatchers & KEYCATCH_UI && uivm ) { + VM_Call( uivm, UI_REFRESH, cls.realtime ); + } + + // console draws next + Con_DrawConsole (); + + // debug graph can be drawn on top of anything + if ( cl_debuggraph->integer || cl_timegraph->integer || cl_debugMove->integer ) { + SCR_DrawDebugGraph (); + } +} + +/* +================== +SCR_UpdateScreen + +This is called every frame, and can also be called explicitly to flush +text to the screen. +================== +*/ +void SCR_UpdateScreen( void ) { + static int recursive; + + if ( !scr_initialized ) { + return; // not initialized yet + } + + if ( ++recursive > 2 ) { + Com_Error( ERR_FATAL, "SCR_UpdateScreen: recursively called" ); + } + recursive = 1; + + // if running in stereo, we need to draw the frame twice + if ( cls.glconfig.stereoEnabled ) { + SCR_DrawScreenField( STEREO_LEFT ); + SCR_DrawScreenField( STEREO_RIGHT ); + } else { + SCR_DrawScreenField( STEREO_CENTER ); + } + + if ( com_speeds->integer ) { + re.EndFrame( &time_frontend, &time_backend ); + } else { + re.EndFrame( NULL, NULL ); + } + + recursive = 0; +} + diff --git a/code/client/cl_ui.c b/code/client/cl_ui.c index 80040a8..96cc07e 100755 --- a/code/client/cl_ui.c +++ b/code/client/cl_ui.c @@ -1,1200 +1,1200 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "client.h" - -#include "../game/botlib.h" - -extern botlib_export_t *botlib_export; - -vm_t *uivm; - -/* -==================== -GetClientState -==================== -*/ -static void GetClientState( uiClientState_t *state ) { - state->connectPacketCount = clc.connectPacketCount; - state->connState = cls.state; - Q_strncpyz( state->servername, cls.servername, sizeof( state->servername ) ); - Q_strncpyz( state->updateInfoString, cls.updateInfoString, sizeof( state->updateInfoString ) ); - Q_strncpyz( state->messageString, clc.serverMessage, sizeof( state->messageString ) ); - state->clientNum = cl.snap.ps.clientNum; -} - -/* -==================== -LAN_LoadCachedServers -==================== -*/ -void LAN_LoadCachedServers( ) { - int size; - fileHandle_t fileIn; - cls.numglobalservers = cls.nummplayerservers = cls.numfavoriteservers = 0; - cls.numGlobalServerAddresses = 0; - if (FS_SV_FOpenFileRead("servercache.dat", &fileIn)) { - FS_Read(&cls.numglobalservers, sizeof(int), fileIn); - FS_Read(&cls.nummplayerservers, sizeof(int), fileIn); - FS_Read(&cls.numfavoriteservers, sizeof(int), fileIn); - FS_Read(&size, sizeof(int), fileIn); - if (size == sizeof(cls.globalServers) + sizeof(cls.favoriteServers) + sizeof(cls.mplayerServers)) { - FS_Read(&cls.globalServers, sizeof(cls.globalServers), fileIn); - FS_Read(&cls.mplayerServers, sizeof(cls.mplayerServers), fileIn); - FS_Read(&cls.favoriteServers, sizeof(cls.favoriteServers), fileIn); - } else { - cls.numglobalservers = cls.nummplayerservers = cls.numfavoriteservers = 0; - cls.numGlobalServerAddresses = 0; - } - FS_FCloseFile(fileIn); - } -} - -/* -==================== -LAN_SaveServersToCache -==================== -*/ -void LAN_SaveServersToCache( ) { - int size; - fileHandle_t fileOut = FS_SV_FOpenFileWrite("servercache.dat"); - FS_Write(&cls.numglobalservers, sizeof(int), fileOut); - FS_Write(&cls.nummplayerservers, sizeof(int), fileOut); - FS_Write(&cls.numfavoriteservers, sizeof(int), fileOut); - size = sizeof(cls.globalServers) + sizeof(cls.favoriteServers) + sizeof(cls.mplayerServers); - FS_Write(&size, sizeof(int), fileOut); - FS_Write(&cls.globalServers, sizeof(cls.globalServers), fileOut); - FS_Write(&cls.mplayerServers, sizeof(cls.mplayerServers), fileOut); - FS_Write(&cls.favoriteServers, sizeof(cls.favoriteServers), fileOut); - FS_FCloseFile(fileOut); -} - - -/* -==================== -LAN_ResetPings -==================== -*/ -static void LAN_ResetPings(int source) { - int count,i; - serverInfo_t *servers = NULL; - count = 0; - - switch (source) { - case AS_LOCAL : - servers = &cls.localServers[0]; - count = MAX_OTHER_SERVERS; - break; - case AS_MPLAYER : - servers = &cls.mplayerServers[0]; - count = MAX_OTHER_SERVERS; - break; - case AS_GLOBAL : - servers = &cls.globalServers[0]; - count = MAX_GLOBAL_SERVERS; - break; - case AS_FAVORITES : - servers = &cls.favoriteServers[0]; - count = MAX_OTHER_SERVERS; - break; - } - if (servers) { - for (i = 0; i < count; i++) { - servers[i].ping = -1; - } - } -} - -/* -==================== -LAN_AddServer -==================== -*/ -static int LAN_AddServer(int source, const char *name, const char *address) { - int max, *count, i; - netadr_t adr; - serverInfo_t *servers = NULL; - max = MAX_OTHER_SERVERS; - count = 0; - - switch (source) { - case AS_LOCAL : - count = &cls.numlocalservers; - servers = &cls.localServers[0]; - break; - case AS_MPLAYER : - count = &cls.nummplayerservers; - servers = &cls.mplayerServers[0]; - break; - case AS_GLOBAL : - max = MAX_GLOBAL_SERVERS; - count = &cls.numglobalservers; - servers = &cls.globalServers[0]; - break; - case AS_FAVORITES : - count = &cls.numfavoriteservers; - servers = &cls.favoriteServers[0]; - break; - } - if (servers && *count < max) { - NET_StringToAdr( address, &adr ); - for ( i = 0; i < *count; i++ ) { - if (NET_CompareAdr(servers[i].adr, adr)) { - break; - } - } - if (i >= *count) { - servers[*count].adr = adr; - Q_strncpyz(servers[*count].hostName, name, sizeof(servers[*count].hostName)); - servers[*count].visible = qtrue; - (*count)++; - return 1; - } - return 0; - } - return -1; -} - -/* -==================== -LAN_RemoveServer -==================== -*/ -static void LAN_RemoveServer(int source, const char *addr) { - int *count, i; - serverInfo_t *servers = NULL; - count = 0; - switch (source) { - case AS_LOCAL : - count = &cls.numlocalservers; - servers = &cls.localServers[0]; - break; - case AS_MPLAYER : - count = &cls.nummplayerservers; - servers = &cls.mplayerServers[0]; - break; - case AS_GLOBAL : - count = &cls.numglobalservers; - servers = &cls.globalServers[0]; - break; - case AS_FAVORITES : - count = &cls.numfavoriteservers; - servers = &cls.favoriteServers[0]; - break; - } - if (servers) { - netadr_t comp; - NET_StringToAdr( addr, &comp ); - for (i = 0; i < *count; i++) { - if (NET_CompareAdr( comp, servers[i].adr)) { - int j = i; - while (j < *count - 1) { - Com_Memcpy(&servers[j], &servers[j+1], sizeof(servers[j])); - j++; - } - (*count)--; - break; - } - } - } -} - - -/* -==================== -LAN_GetServerCount -==================== -*/ -static int LAN_GetServerCount( int source ) { - switch (source) { - case AS_LOCAL : - return cls.numlocalservers; - break; - case AS_MPLAYER : - return cls.nummplayerservers; - break; - case AS_GLOBAL : - return cls.numglobalservers; - break; - case AS_FAVORITES : - return cls.numfavoriteservers; - break; - } - return 0; -} - -/* -==================== -LAN_GetLocalServerAddressString -==================== -*/ -static void LAN_GetServerAddressString( int source, int n, char *buf, int buflen ) { - switch (source) { - case AS_LOCAL : - if (n >= 0 && n < MAX_OTHER_SERVERS) { - Q_strncpyz(buf, NET_AdrToString( cls.localServers[n].adr) , buflen ); - return; - } - break; - case AS_MPLAYER : - if (n >= 0 && n < MAX_OTHER_SERVERS) { - Q_strncpyz(buf, NET_AdrToString( cls.mplayerServers[n].adr) , buflen ); - return; - } - break; - case AS_GLOBAL : - if (n >= 0 && n < MAX_GLOBAL_SERVERS) { - Q_strncpyz(buf, NET_AdrToString( cls.globalServers[n].adr) , buflen ); - return; - } - break; - case AS_FAVORITES : - if (n >= 0 && n < MAX_OTHER_SERVERS) { - Q_strncpyz(buf, NET_AdrToString( cls.favoriteServers[n].adr) , buflen ); - return; - } - break; - } - buf[0] = '\0'; -} - -/* -==================== -LAN_GetServerInfo -==================== -*/ -static void LAN_GetServerInfo( int source, int n, char *buf, int buflen ) { - char info[MAX_STRING_CHARS]; - serverInfo_t *server = NULL; - info[0] = '\0'; - switch (source) { - case AS_LOCAL : - if (n >= 0 && n < MAX_OTHER_SERVERS) { - server = &cls.localServers[n]; - } - break; - case AS_MPLAYER : - if (n >= 0 && n < MAX_OTHER_SERVERS) { - server = &cls.mplayerServers[n]; - } - break; - case AS_GLOBAL : - if (n >= 0 && n < MAX_GLOBAL_SERVERS) { - server = &cls.globalServers[n]; - } - break; - case AS_FAVORITES : - if (n >= 0 && n < MAX_OTHER_SERVERS) { - server = &cls.favoriteServers[n]; - } - break; - } - if (server && buf) { - buf[0] = '\0'; - Info_SetValueForKey( info, "hostname", server->hostName); - Info_SetValueForKey( info, "mapname", server->mapName); - Info_SetValueForKey( info, "clients", va("%i",server->clients)); - Info_SetValueForKey( info, "sv_maxclients", va("%i",server->maxClients)); - Info_SetValueForKey( info, "ping", va("%i",server->ping)); - Info_SetValueForKey( info, "minping", va("%i",server->minPing)); - Info_SetValueForKey( info, "maxping", va("%i",server->maxPing)); - Info_SetValueForKey( info, "game", server->game); - Info_SetValueForKey( info, "gametype", va("%i",server->gameType)); - Info_SetValueForKey( info, "nettype", va("%i",server->netType)); - Info_SetValueForKey( info, "addr", NET_AdrToString(server->adr)); - Info_SetValueForKey( info, "punkbuster", va("%i", server->punkbuster)); - Q_strncpyz(buf, info, buflen); - } else { - if (buf) { - buf[0] = '\0'; - } - } -} - -/* -==================== -LAN_GetServerPing -==================== -*/ -static int LAN_GetServerPing( int source, int n ) { - serverInfo_t *server = NULL; - switch (source) { - case AS_LOCAL : - if (n >= 0 && n < MAX_OTHER_SERVERS) { - server = &cls.localServers[n]; - } - break; - case AS_MPLAYER : - if (n >= 0 && n < MAX_OTHER_SERVERS) { - server = &cls.mplayerServers[n]; - } - break; - case AS_GLOBAL : - if (n >= 0 && n < MAX_GLOBAL_SERVERS) { - server = &cls.globalServers[n]; - } - break; - case AS_FAVORITES : - if (n >= 0 && n < MAX_OTHER_SERVERS) { - server = &cls.favoriteServers[n]; - } - break; - } - if (server) { - return server->ping; - } - return -1; -} - -/* -==================== -LAN_GetServerPtr -==================== -*/ -static serverInfo_t *LAN_GetServerPtr( int source, int n ) { - switch (source) { - case AS_LOCAL : - if (n >= 0 && n < MAX_OTHER_SERVERS) { - return &cls.localServers[n]; - } - break; - case AS_MPLAYER : - if (n >= 0 && n < MAX_OTHER_SERVERS) { - return &cls.mplayerServers[n]; - } - break; - case AS_GLOBAL : - if (n >= 0 && n < MAX_GLOBAL_SERVERS) { - return &cls.globalServers[n]; - } - break; - case AS_FAVORITES : - if (n >= 0 && n < MAX_OTHER_SERVERS) { - return &cls.favoriteServers[n]; - } - break; - } - return NULL; -} - -/* -==================== -LAN_CompareServers -==================== -*/ -static int LAN_CompareServers( int source, int sortKey, int sortDir, int s1, int s2 ) { - int res; - serverInfo_t *server1, *server2; - - server1 = LAN_GetServerPtr(source, s1); - server2 = LAN_GetServerPtr(source, s2); - if (!server1 || !server2) { - return 0; - } - - res = 0; - switch( sortKey ) { - case SORT_HOST: - res = Q_stricmp( server1->hostName, server2->hostName ); - break; - - case SORT_MAP: - res = Q_stricmp( server1->mapName, server2->mapName ); - break; - case SORT_CLIENTS: - if (server1->clients < server2->clients) { - res = -1; - } - else if (server1->clients > server2->clients) { - res = 1; - } - else { - res = 0; - } - break; - case SORT_GAME: - if (server1->gameType < server2->gameType) { - res = -1; - } - else if (server1->gameType > server2->gameType) { - res = 1; - } - else { - res = 0; - } - break; - case SORT_PING: - if (server1->ping < server2->ping) { - res = -1; - } - else if (server1->ping > server2->ping) { - res = 1; - } - else { - res = 0; - } - break; - } - - if (sortDir) { - if (res < 0) - return 1; - if (res > 0) - return -1; - return 0; - } - return res; -} - -/* -==================== -LAN_GetPingQueueCount -==================== -*/ -static int LAN_GetPingQueueCount( void ) { - return (CL_GetPingQueueCount()); -} - -/* -==================== -LAN_ClearPing -==================== -*/ -static void LAN_ClearPing( int n ) { - CL_ClearPing( n ); -} - -/* -==================== -LAN_GetPing -==================== -*/ -static void LAN_GetPing( int n, char *buf, int buflen, int *pingtime ) { - CL_GetPing( n, buf, buflen, pingtime ); -} - -/* -==================== -LAN_GetPingInfo -==================== -*/ -static void LAN_GetPingInfo( int n, char *buf, int buflen ) { - CL_GetPingInfo( n, buf, buflen ); -} - -/* -==================== -LAN_MarkServerVisible -==================== -*/ -static void LAN_MarkServerVisible(int source, int n, qboolean visible ) { - if (n == -1) { - int count = MAX_OTHER_SERVERS; - serverInfo_t *server = NULL; - switch (source) { - case AS_LOCAL : - server = &cls.localServers[0]; - break; - case AS_MPLAYER : - server = &cls.mplayerServers[0]; - break; - case AS_GLOBAL : - server = &cls.globalServers[0]; - count = MAX_GLOBAL_SERVERS; - break; - case AS_FAVORITES : - server = &cls.favoriteServers[0]; - break; - } - if (server) { - for (n = 0; n < count; n++) { - server[n].visible = visible; - } - } - - } else { - switch (source) { - case AS_LOCAL : - if (n >= 0 && n < MAX_OTHER_SERVERS) { - cls.localServers[n].visible = visible; - } - break; - case AS_MPLAYER : - if (n >= 0 && n < MAX_OTHER_SERVERS) { - cls.mplayerServers[n].visible = visible; - } - break; - case AS_GLOBAL : - if (n >= 0 && n < MAX_GLOBAL_SERVERS) { - cls.globalServers[n].visible = visible; - } - break; - case AS_FAVORITES : - if (n >= 0 && n < MAX_OTHER_SERVERS) { - cls.favoriteServers[n].visible = visible; - } - break; - } - } -} - - -/* -======================= -LAN_ServerIsVisible -======================= -*/ -static int LAN_ServerIsVisible(int source, int n ) { - switch (source) { - case AS_LOCAL : - if (n >= 0 && n < MAX_OTHER_SERVERS) { - return cls.localServers[n].visible; - } - break; - case AS_MPLAYER : - if (n >= 0 && n < MAX_OTHER_SERVERS) { - return cls.mplayerServers[n].visible; - } - break; - case AS_GLOBAL : - if (n >= 0 && n < MAX_GLOBAL_SERVERS) { - return cls.globalServers[n].visible; - } - break; - case AS_FAVORITES : - if (n >= 0 && n < MAX_OTHER_SERVERS) { - return cls.favoriteServers[n].visible; - } - break; - } - return qfalse; -} - -/* -======================= -LAN_UpdateVisiblePings -======================= -*/ -qboolean LAN_UpdateVisiblePings(int source ) { - return CL_UpdateVisiblePings_f(source); -} - -/* -==================== -LAN_GetServerStatus -==================== -*/ -int LAN_GetServerStatus( char *serverAddress, char *serverStatus, int maxLen ) { - return CL_ServerStatus( serverAddress, serverStatus, maxLen ); -} - -/* -==================== -CL_GetGlConfig -==================== -*/ -static void CL_GetGlconfig( glconfig_t *config ) { - *config = cls.glconfig; -} - -/* -==================== -GetClipboardData -==================== -*/ -static void GetClipboardData( char *buf, int buflen ) { - char *cbd; - - cbd = Sys_GetClipboardData(); - - if ( !cbd ) { - *buf = 0; - return; - } - - Q_strncpyz( buf, cbd, buflen ); - - Z_Free( cbd ); -} - -/* -==================== -Key_KeynumToStringBuf -==================== -*/ -static void Key_KeynumToStringBuf( int keynum, char *buf, int buflen ) { - Q_strncpyz( buf, Key_KeynumToString( keynum ), buflen ); -} - -/* -==================== -Key_GetBindingBuf -==================== -*/ -static void Key_GetBindingBuf( int keynum, char *buf, int buflen ) { - char *value; - - value = Key_GetBinding( keynum ); - if ( value ) { - Q_strncpyz( buf, value, buflen ); - } - else { - *buf = 0; - } -} - -/* -==================== -Key_GetCatcher -==================== -*/ -int Key_GetCatcher( void ) { - return cls.keyCatchers; -} - -/* -==================== -Ket_SetCatcher -==================== -*/ -void Key_SetCatcher( int catcher ) { - cls.keyCatchers = catcher; -} - - -/* -==================== -CLUI_GetCDKey -==================== -*/ -static void CLUI_GetCDKey( char *buf, int buflen ) { - cvar_t *fs; - fs = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO ); - if (UI_usesUniqueCDKey() && fs && fs->string[0] != 0) { - Com_Memcpy( buf, &cl_cdkey[16], 16); - buf[16] = 0; - } else { - Com_Memcpy( buf, cl_cdkey, 16); - buf[16] = 0; - } -} - - -/* -==================== -CLUI_SetCDKey -==================== -*/ -static void CLUI_SetCDKey( char *buf ) { - cvar_t *fs; - fs = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO ); - if (UI_usesUniqueCDKey() && fs && fs->string[0] != 0) { - Com_Memcpy( &cl_cdkey[16], buf, 16 ); - cl_cdkey[32] = 0; - // set the flag so the fle will be written at the next opportunity - cvar_modifiedFlags |= CVAR_ARCHIVE; - } else { - Com_Memcpy( cl_cdkey, buf, 16 ); - // set the flag so the fle will be written at the next opportunity - cvar_modifiedFlags |= CVAR_ARCHIVE; - } -} - -/* -==================== -GetConfigString -==================== -*/ -static int GetConfigString(int index, char *buf, int size) -{ - int offset; - - if (index < 0 || index >= MAX_CONFIGSTRINGS) - return qfalse; - - offset = cl.gameState.stringOffsets[index]; - if (!offset) { - if( size ) { - buf[0] = 0; - } - return qfalse; - } - - Q_strncpyz( buf, cl.gameState.stringData+offset, size); - - return qtrue; -} - -/* -==================== -FloatAsInt -==================== -*/ -static int FloatAsInt( float f ) { - int temp; - - *(float *)&temp = f; - - return temp; -} - -void *VM_ArgPtr( int intValue ); -#define VMA(x) VM_ArgPtr(args[x]) -#define VMF(x) ((float *)args)[x] - -/* -==================== -CL_UISystemCalls - -The ui module is making a system call -==================== -*/ -int CL_UISystemCalls( int *args ) { - switch( args[0] ) { - case UI_ERROR: - Com_Error( ERR_DROP, "%s", VMA(1) ); - return 0; - - case UI_PRINT: - Com_Printf( "%s", VMA(1) ); - return 0; - - case UI_MILLISECONDS: - return Sys_Milliseconds(); - - case UI_CVAR_REGISTER: - Cvar_Register( VMA(1), VMA(2), VMA(3), args[4] ); - return 0; - - case UI_CVAR_UPDATE: - Cvar_Update( VMA(1) ); - return 0; - - case UI_CVAR_SET: - Cvar_Set( VMA(1), VMA(2) ); - return 0; - - case UI_CVAR_VARIABLEVALUE: - return FloatAsInt( Cvar_VariableValue( VMA(1) ) ); - - case UI_CVAR_VARIABLESTRINGBUFFER: - Cvar_VariableStringBuffer( VMA(1), VMA(2), args[3] ); - return 0; - - case UI_CVAR_SETVALUE: - Cvar_SetValue( VMA(1), VMF(2) ); - return 0; - - case UI_CVAR_RESET: - Cvar_Reset( VMA(1) ); - return 0; - - case UI_CVAR_CREATE: - Cvar_Get( VMA(1), VMA(2), args[3] ); - return 0; - - case UI_CVAR_INFOSTRINGBUFFER: - Cvar_InfoStringBuffer( args[1], VMA(2), args[3] ); - return 0; - - case UI_ARGC: - return Cmd_Argc(); - - case UI_ARGV: - Cmd_ArgvBuffer( args[1], VMA(2), args[3] ); - return 0; - - case UI_CMD_EXECUTETEXT: - Cbuf_ExecuteText( args[1], VMA(2) ); - return 0; - - case UI_FS_FOPENFILE: - return FS_FOpenFileByMode( VMA(1), VMA(2), args[3] ); - - case UI_FS_READ: - FS_Read2( VMA(1), args[2], args[3] ); - return 0; - - case UI_FS_WRITE: - FS_Write( VMA(1), args[2], args[3] ); - return 0; - - case UI_FS_FCLOSEFILE: - FS_FCloseFile( args[1] ); - return 0; - - case UI_FS_GETFILELIST: - return FS_GetFileList( VMA(1), VMA(2), VMA(3), args[4] ); - - case UI_FS_SEEK: - return FS_Seek( args[1], args[2], args[3] ); - - case UI_R_REGISTERMODEL: - return re.RegisterModel( VMA(1) ); - - case UI_R_REGISTERSKIN: - return re.RegisterSkin( VMA(1) ); - - case UI_R_REGISTERSHADERNOMIP: - return re.RegisterShaderNoMip( VMA(1) ); - - case UI_R_CLEARSCENE: - re.ClearScene(); - return 0; - - case UI_R_ADDREFENTITYTOSCENE: - re.AddRefEntityToScene( VMA(1) ); - return 0; - - case UI_R_ADDPOLYTOSCENE: - re.AddPolyToScene( args[1], args[2], VMA(3), 1 ); - return 0; - - case UI_R_ADDLIGHTTOSCENE: - re.AddLightToScene( VMA(1), VMF(2), VMF(3), VMF(4), VMF(5) ); - return 0; - - case UI_R_RENDERSCENE: - re.RenderScene( VMA(1) ); - return 0; - - case UI_R_SETCOLOR: - re.SetColor( VMA(1) ); - return 0; - - case UI_R_DRAWSTRETCHPIC: - re.DrawStretchPic( VMF(1), VMF(2), VMF(3), VMF(4), VMF(5), VMF(6), VMF(7), VMF(8), args[9] ); - return 0; - - case UI_R_MODELBOUNDS: - re.ModelBounds( args[1], VMA(2), VMA(3) ); - return 0; - - case UI_UPDATESCREEN: - SCR_UpdateScreen(); - return 0; - - case UI_CM_LERPTAG: - re.LerpTag( VMA(1), args[2], args[3], args[4], VMF(5), VMA(6) ); - return 0; - - case UI_S_REGISTERSOUND: - return S_RegisterSound( VMA(1), args[2] ); - - case UI_S_STARTLOCALSOUND: - S_StartLocalSound( args[1], args[2] ); - return 0; - - case UI_KEY_KEYNUMTOSTRINGBUF: - Key_KeynumToStringBuf( args[1], VMA(2), args[3] ); - return 0; - - case UI_KEY_GETBINDINGBUF: - Key_GetBindingBuf( args[1], VMA(2), args[3] ); - return 0; - - case UI_KEY_SETBINDING: - Key_SetBinding( args[1], VMA(2) ); - return 0; - - case UI_KEY_ISDOWN: - return Key_IsDown( args[1] ); - - case UI_KEY_GETOVERSTRIKEMODE: - return Key_GetOverstrikeMode(); - - case UI_KEY_SETOVERSTRIKEMODE: - Key_SetOverstrikeMode( args[1] ); - return 0; - - case UI_KEY_CLEARSTATES: - Key_ClearStates(); - return 0; - - case UI_KEY_GETCATCHER: - return Key_GetCatcher(); - - case UI_KEY_SETCATCHER: - Key_SetCatcher( args[1] ); - return 0; - - case UI_GETCLIPBOARDDATA: - GetClipboardData( VMA(1), args[2] ); - return 0; - - case UI_GETCLIENTSTATE: - GetClientState( VMA(1) ); - return 0; - - case UI_GETGLCONFIG: - CL_GetGlconfig( VMA(1) ); - return 0; - - case UI_GETCONFIGSTRING: - return GetConfigString( args[1], VMA(2), args[3] ); - - case UI_LAN_LOADCACHEDSERVERS: - LAN_LoadCachedServers(); - return 0; - - case UI_LAN_SAVECACHEDSERVERS: - LAN_SaveServersToCache(); - return 0; - - case UI_LAN_ADDSERVER: - return LAN_AddServer(args[1], VMA(2), VMA(3)); - - case UI_LAN_REMOVESERVER: - LAN_RemoveServer(args[1], VMA(2)); - return 0; - - case UI_LAN_GETPINGQUEUECOUNT: - return LAN_GetPingQueueCount(); - - case UI_LAN_CLEARPING: - LAN_ClearPing( args[1] ); - return 0; - - case UI_LAN_GETPING: - LAN_GetPing( args[1], VMA(2), args[3], VMA(4) ); - return 0; - - case UI_LAN_GETPINGINFO: - LAN_GetPingInfo( args[1], VMA(2), args[3] ); - return 0; - - case UI_LAN_GETSERVERCOUNT: - return LAN_GetServerCount(args[1]); - - case UI_LAN_GETSERVERADDRESSSTRING: - LAN_GetServerAddressString( args[1], args[2], VMA(3), args[4] ); - return 0; - - case UI_LAN_GETSERVERINFO: - LAN_GetServerInfo( args[1], args[2], VMA(3), args[4] ); - return 0; - - case UI_LAN_GETSERVERPING: - return LAN_GetServerPing( args[1], args[2] ); - - case UI_LAN_MARKSERVERVISIBLE: - LAN_MarkServerVisible( args[1], args[2], args[3] ); - return 0; - - case UI_LAN_SERVERISVISIBLE: - return LAN_ServerIsVisible( args[1], args[2] ); - - case UI_LAN_UPDATEVISIBLEPINGS: - return LAN_UpdateVisiblePings( args[1] ); - - case UI_LAN_RESETPINGS: - LAN_ResetPings( args[1] ); - return 0; - - case UI_LAN_SERVERSTATUS: - return LAN_GetServerStatus( VMA(1), VMA(2), args[3] ); - - case UI_LAN_COMPARESERVERS: - return LAN_CompareServers( args[1], args[2], args[3], args[4], args[5] ); - - case UI_MEMORY_REMAINING: - return Hunk_MemoryRemaining(); - - case UI_GET_CDKEY: - CLUI_GetCDKey( VMA(1), args[2] ); - return 0; - - case UI_SET_CDKEY: - CLUI_SetCDKey( VMA(1) ); - return 0; - - case UI_SET_PBCLSTATUS: - return 0; - - case UI_R_REGISTERFONT: - re.RegisterFont( VMA(1), args[2], VMA(3)); - return 0; - - case UI_MEMSET: - Com_Memset( VMA(1), args[2], args[3] ); - return 0; - - case UI_MEMCPY: - Com_Memcpy( VMA(1), VMA(2), args[3] ); - return 0; - - case UI_STRNCPY: - return (int)strncpy( VMA(1), VMA(2), args[3] ); - - case UI_SIN: - return FloatAsInt( sin( VMF(1) ) ); - - case UI_COS: - return FloatAsInt( cos( VMF(1) ) ); - - case UI_ATAN2: - return FloatAsInt( atan2( VMF(1), VMF(2) ) ); - - case UI_SQRT: - return FloatAsInt( sqrt( VMF(1) ) ); - - case UI_FLOOR: - return FloatAsInt( floor( VMF(1) ) ); - - case UI_CEIL: - return FloatAsInt( ceil( VMF(1) ) ); - - case UI_PC_ADD_GLOBAL_DEFINE: - return botlib_export->PC_AddGlobalDefine( VMA(1) ); - case UI_PC_LOAD_SOURCE: - return botlib_export->PC_LoadSourceHandle( VMA(1) ); - case UI_PC_FREE_SOURCE: - return botlib_export->PC_FreeSourceHandle( args[1] ); - case UI_PC_READ_TOKEN: - return botlib_export->PC_ReadTokenHandle( args[1], VMA(2) ); - case UI_PC_SOURCE_FILE_AND_LINE: - return botlib_export->PC_SourceFileAndLine( args[1], VMA(2), VMA(3) ); - - case UI_S_STOPBACKGROUNDTRACK: - S_StopBackgroundTrack(); - return 0; - case UI_S_STARTBACKGROUNDTRACK: - S_StartBackgroundTrack( VMA(1), VMA(2)); - return 0; - - case UI_REAL_TIME: - return Com_RealTime( VMA(1) ); - - case UI_CIN_PLAYCINEMATIC: - Com_DPrintf("UI_CIN_PlayCinematic\n"); - return CIN_PlayCinematic(VMA(1), args[2], args[3], args[4], args[5], args[6]); - - case UI_CIN_STOPCINEMATIC: - return CIN_StopCinematic(args[1]); - - case UI_CIN_RUNCINEMATIC: - return CIN_RunCinematic(args[1]); - - case UI_CIN_DRAWCINEMATIC: - CIN_DrawCinematic(args[1]); - return 0; - - case UI_CIN_SETEXTENTS: - CIN_SetExtents(args[1], args[2], args[3], args[4], args[5]); - return 0; - - case UI_R_REMAP_SHADER: - re.RemapShader( VMA(1), VMA(2), VMA(3) ); - return 0; - - case UI_VERIFY_CDKEY: - return CL_CDKeyValidate(VMA(1), VMA(2)); - - - - default: - Com_Error( ERR_DROP, "Bad UI system trap: %i", args[0] ); - - } - - return 0; -} - -/* -==================== -CL_ShutdownUI -==================== -*/ -void CL_ShutdownUI( void ) { - cls.keyCatchers &= ~KEYCATCH_UI; - cls.uiStarted = qfalse; - if ( !uivm ) { - return; - } - VM_Call( uivm, UI_SHUTDOWN ); - VM_Free( uivm ); - uivm = NULL; -} - -/* -==================== -CL_InitUI -==================== -*/ -#define UI_OLD_API_VERSION 4 - -void CL_InitUI( void ) { - int v; - vmInterpret_t interpret; - - // load the dll or bytecode - if ( cl_connectedToPureServer != 0 ) { - // if sv_pure is set we only allow qvms to be loaded - interpret = VMI_COMPILED; - } - else { - interpret = Cvar_VariableValue( "vm_ui" ); - } - uivm = VM_Create( "ui", CL_UISystemCalls, interpret ); - if ( !uivm ) { - Com_Error( ERR_FATAL, "VM_Create on UI failed" ); - } - - // sanity check - v = VM_Call( uivm, UI_GETAPIVERSION ); - if (v == UI_OLD_API_VERSION) { -// Com_Printf(S_COLOR_YELLOW "WARNING: loading old Quake III Arena User Interface version %d\n", v ); - // init for this gamestate - VM_Call( uivm, UI_INIT, (cls.state >= CA_AUTHORIZING && cls.state < CA_ACTIVE)); - } - else if (v != UI_API_VERSION) { - Com_Error( ERR_DROP, "User Interface is version %d, expected %d", v, UI_API_VERSION ); - cls.uiStarted = qfalse; - } - else { - // init for this gamestate - VM_Call( uivm, UI_INIT, (cls.state >= CA_AUTHORIZING && cls.state < CA_ACTIVE) ); - } -} - -qboolean UI_usesUniqueCDKey() { - if (uivm) { - return (VM_Call( uivm, UI_HASUNIQUECDKEY) == qtrue); - } else { - return qfalse; - } -} - -/* -==================== -UI_GameCommand - -See if the current console command is claimed by the ui -==================== -*/ -qboolean UI_GameCommand( void ) { - if ( !uivm ) { - return qfalse; - } - - return VM_Call( uivm, UI_CONSOLE_COMMAND, cls.realtime ); -} +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "client.h" + +#include "../game/botlib.h" + +extern botlib_export_t *botlib_export; + +vm_t *uivm; + +/* +==================== +GetClientState +==================== +*/ +static void GetClientState( uiClientState_t *state ) { + state->connectPacketCount = clc.connectPacketCount; + state->connState = cls.state; + Q_strncpyz( state->servername, cls.servername, sizeof( state->servername ) ); + Q_strncpyz( state->updateInfoString, cls.updateInfoString, sizeof( state->updateInfoString ) ); + Q_strncpyz( state->messageString, clc.serverMessage, sizeof( state->messageString ) ); + state->clientNum = cl.snap.ps.clientNum; +} + +/* +==================== +LAN_LoadCachedServers +==================== +*/ +void LAN_LoadCachedServers( ) { + int size; + fileHandle_t fileIn; + cls.numglobalservers = cls.nummplayerservers = cls.numfavoriteservers = 0; + cls.numGlobalServerAddresses = 0; + if (FS_SV_FOpenFileRead("servercache.dat", &fileIn)) { + FS_Read(&cls.numglobalservers, sizeof(int), fileIn); + FS_Read(&cls.nummplayerservers, sizeof(int), fileIn); + FS_Read(&cls.numfavoriteservers, sizeof(int), fileIn); + FS_Read(&size, sizeof(int), fileIn); + if (size == sizeof(cls.globalServers) + sizeof(cls.favoriteServers) + sizeof(cls.mplayerServers)) { + FS_Read(&cls.globalServers, sizeof(cls.globalServers), fileIn); + FS_Read(&cls.mplayerServers, sizeof(cls.mplayerServers), fileIn); + FS_Read(&cls.favoriteServers, sizeof(cls.favoriteServers), fileIn); + } else { + cls.numglobalservers = cls.nummplayerservers = cls.numfavoriteservers = 0; + cls.numGlobalServerAddresses = 0; + } + FS_FCloseFile(fileIn); + } +} + +/* +==================== +LAN_SaveServersToCache +==================== +*/ +void LAN_SaveServersToCache( ) { + int size; + fileHandle_t fileOut = FS_SV_FOpenFileWrite("servercache.dat"); + FS_Write(&cls.numglobalservers, sizeof(int), fileOut); + FS_Write(&cls.nummplayerservers, sizeof(int), fileOut); + FS_Write(&cls.numfavoriteservers, sizeof(int), fileOut); + size = sizeof(cls.globalServers) + sizeof(cls.favoriteServers) + sizeof(cls.mplayerServers); + FS_Write(&size, sizeof(int), fileOut); + FS_Write(&cls.globalServers, sizeof(cls.globalServers), fileOut); + FS_Write(&cls.mplayerServers, sizeof(cls.mplayerServers), fileOut); + FS_Write(&cls.favoriteServers, sizeof(cls.favoriteServers), fileOut); + FS_FCloseFile(fileOut); +} + + +/* +==================== +LAN_ResetPings +==================== +*/ +static void LAN_ResetPings(int source) { + int count,i; + serverInfo_t *servers = NULL; + count = 0; + + switch (source) { + case AS_LOCAL : + servers = &cls.localServers[0]; + count = MAX_OTHER_SERVERS; + break; + case AS_MPLAYER : + servers = &cls.mplayerServers[0]; + count = MAX_OTHER_SERVERS; + break; + case AS_GLOBAL : + servers = &cls.globalServers[0]; + count = MAX_GLOBAL_SERVERS; + break; + case AS_FAVORITES : + servers = &cls.favoriteServers[0]; + count = MAX_OTHER_SERVERS; + break; + } + if (servers) { + for (i = 0; i < count; i++) { + servers[i].ping = -1; + } + } +} + +/* +==================== +LAN_AddServer +==================== +*/ +static int LAN_AddServer(int source, const char *name, const char *address) { + int max, *count, i; + netadr_t adr; + serverInfo_t *servers = NULL; + max = MAX_OTHER_SERVERS; + count = 0; + + switch (source) { + case AS_LOCAL : + count = &cls.numlocalservers; + servers = &cls.localServers[0]; + break; + case AS_MPLAYER : + count = &cls.nummplayerservers; + servers = &cls.mplayerServers[0]; + break; + case AS_GLOBAL : + max = MAX_GLOBAL_SERVERS; + count = &cls.numglobalservers; + servers = &cls.globalServers[0]; + break; + case AS_FAVORITES : + count = &cls.numfavoriteservers; + servers = &cls.favoriteServers[0]; + break; + } + if (servers && *count < max) { + NET_StringToAdr( address, &adr ); + for ( i = 0; i < *count; i++ ) { + if (NET_CompareAdr(servers[i].adr, adr)) { + break; + } + } + if (i >= *count) { + servers[*count].adr = adr; + Q_strncpyz(servers[*count].hostName, name, sizeof(servers[*count].hostName)); + servers[*count].visible = qtrue; + (*count)++; + return 1; + } + return 0; + } + return -1; +} + +/* +==================== +LAN_RemoveServer +==================== +*/ +static void LAN_RemoveServer(int source, const char *addr) { + int *count, i; + serverInfo_t *servers = NULL; + count = 0; + switch (source) { + case AS_LOCAL : + count = &cls.numlocalservers; + servers = &cls.localServers[0]; + break; + case AS_MPLAYER : + count = &cls.nummplayerservers; + servers = &cls.mplayerServers[0]; + break; + case AS_GLOBAL : + count = &cls.numglobalservers; + servers = &cls.globalServers[0]; + break; + case AS_FAVORITES : + count = &cls.numfavoriteservers; + servers = &cls.favoriteServers[0]; + break; + } + if (servers) { + netadr_t comp; + NET_StringToAdr( addr, &comp ); + for (i = 0; i < *count; i++) { + if (NET_CompareAdr( comp, servers[i].adr)) { + int j = i; + while (j < *count - 1) { + Com_Memcpy(&servers[j], &servers[j+1], sizeof(servers[j])); + j++; + } + (*count)--; + break; + } + } + } +} + + +/* +==================== +LAN_GetServerCount +==================== +*/ +static int LAN_GetServerCount( int source ) { + switch (source) { + case AS_LOCAL : + return cls.numlocalservers; + break; + case AS_MPLAYER : + return cls.nummplayerservers; + break; + case AS_GLOBAL : + return cls.numglobalservers; + break; + case AS_FAVORITES : + return cls.numfavoriteservers; + break; + } + return 0; +} + +/* +==================== +LAN_GetLocalServerAddressString +==================== +*/ +static void LAN_GetServerAddressString( int source, int n, char *buf, int buflen ) { + switch (source) { + case AS_LOCAL : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + Q_strncpyz(buf, NET_AdrToString( cls.localServers[n].adr) , buflen ); + return; + } + break; + case AS_MPLAYER : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + Q_strncpyz(buf, NET_AdrToString( cls.mplayerServers[n].adr) , buflen ); + return; + } + break; + case AS_GLOBAL : + if (n >= 0 && n < MAX_GLOBAL_SERVERS) { + Q_strncpyz(buf, NET_AdrToString( cls.globalServers[n].adr) , buflen ); + return; + } + break; + case AS_FAVORITES : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + Q_strncpyz(buf, NET_AdrToString( cls.favoriteServers[n].adr) , buflen ); + return; + } + break; + } + buf[0] = '\0'; +} + +/* +==================== +LAN_GetServerInfo +==================== +*/ +static void LAN_GetServerInfo( int source, int n, char *buf, int buflen ) { + char info[MAX_STRING_CHARS]; + serverInfo_t *server = NULL; + info[0] = '\0'; + switch (source) { + case AS_LOCAL : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + server = &cls.localServers[n]; + } + break; + case AS_MPLAYER : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + server = &cls.mplayerServers[n]; + } + break; + case AS_GLOBAL : + if (n >= 0 && n < MAX_GLOBAL_SERVERS) { + server = &cls.globalServers[n]; + } + break; + case AS_FAVORITES : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + server = &cls.favoriteServers[n]; + } + break; + } + if (server && buf) { + buf[0] = '\0'; + Info_SetValueForKey( info, "hostname", server->hostName); + Info_SetValueForKey( info, "mapname", server->mapName); + Info_SetValueForKey( info, "clients", va("%i",server->clients)); + Info_SetValueForKey( info, "sv_maxclients", va("%i",server->maxClients)); + Info_SetValueForKey( info, "ping", va("%i",server->ping)); + Info_SetValueForKey( info, "minping", va("%i",server->minPing)); + Info_SetValueForKey( info, "maxping", va("%i",server->maxPing)); + Info_SetValueForKey( info, "game", server->game); + Info_SetValueForKey( info, "gametype", va("%i",server->gameType)); + Info_SetValueForKey( info, "nettype", va("%i",server->netType)); + Info_SetValueForKey( info, "addr", NET_AdrToString(server->adr)); + Info_SetValueForKey( info, "punkbuster", va("%i", server->punkbuster)); + Q_strncpyz(buf, info, buflen); + } else { + if (buf) { + buf[0] = '\0'; + } + } +} + +/* +==================== +LAN_GetServerPing +==================== +*/ +static int LAN_GetServerPing( int source, int n ) { + serverInfo_t *server = NULL; + switch (source) { + case AS_LOCAL : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + server = &cls.localServers[n]; + } + break; + case AS_MPLAYER : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + server = &cls.mplayerServers[n]; + } + break; + case AS_GLOBAL : + if (n >= 0 && n < MAX_GLOBAL_SERVERS) { + server = &cls.globalServers[n]; + } + break; + case AS_FAVORITES : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + server = &cls.favoriteServers[n]; + } + break; + } + if (server) { + return server->ping; + } + return -1; +} + +/* +==================== +LAN_GetServerPtr +==================== +*/ +static serverInfo_t *LAN_GetServerPtr( int source, int n ) { + switch (source) { + case AS_LOCAL : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + return &cls.localServers[n]; + } + break; + case AS_MPLAYER : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + return &cls.mplayerServers[n]; + } + break; + case AS_GLOBAL : + if (n >= 0 && n < MAX_GLOBAL_SERVERS) { + return &cls.globalServers[n]; + } + break; + case AS_FAVORITES : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + return &cls.favoriteServers[n]; + } + break; + } + return NULL; +} + +/* +==================== +LAN_CompareServers +==================== +*/ +static int LAN_CompareServers( int source, int sortKey, int sortDir, int s1, int s2 ) { + int res; + serverInfo_t *server1, *server2; + + server1 = LAN_GetServerPtr(source, s1); + server2 = LAN_GetServerPtr(source, s2); + if (!server1 || !server2) { + return 0; + } + + res = 0; + switch( sortKey ) { + case SORT_HOST: + res = Q_stricmp( server1->hostName, server2->hostName ); + break; + + case SORT_MAP: + res = Q_stricmp( server1->mapName, server2->mapName ); + break; + case SORT_CLIENTS: + if (server1->clients < server2->clients) { + res = -1; + } + else if (server1->clients > server2->clients) { + res = 1; + } + else { + res = 0; + } + break; + case SORT_GAME: + if (server1->gameType < server2->gameType) { + res = -1; + } + else if (server1->gameType > server2->gameType) { + res = 1; + } + else { + res = 0; + } + break; + case SORT_PING: + if (server1->ping < server2->ping) { + res = -1; + } + else if (server1->ping > server2->ping) { + res = 1; + } + else { + res = 0; + } + break; + } + + if (sortDir) { + if (res < 0) + return 1; + if (res > 0) + return -1; + return 0; + } + return res; +} + +/* +==================== +LAN_GetPingQueueCount +==================== +*/ +static int LAN_GetPingQueueCount( void ) { + return (CL_GetPingQueueCount()); +} + +/* +==================== +LAN_ClearPing +==================== +*/ +static void LAN_ClearPing( int n ) { + CL_ClearPing( n ); +} + +/* +==================== +LAN_GetPing +==================== +*/ +static void LAN_GetPing( int n, char *buf, int buflen, int *pingtime ) { + CL_GetPing( n, buf, buflen, pingtime ); +} + +/* +==================== +LAN_GetPingInfo +==================== +*/ +static void LAN_GetPingInfo( int n, char *buf, int buflen ) { + CL_GetPingInfo( n, buf, buflen ); +} + +/* +==================== +LAN_MarkServerVisible +==================== +*/ +static void LAN_MarkServerVisible(int source, int n, qboolean visible ) { + if (n == -1) { + int count = MAX_OTHER_SERVERS; + serverInfo_t *server = NULL; + switch (source) { + case AS_LOCAL : + server = &cls.localServers[0]; + break; + case AS_MPLAYER : + server = &cls.mplayerServers[0]; + break; + case AS_GLOBAL : + server = &cls.globalServers[0]; + count = MAX_GLOBAL_SERVERS; + break; + case AS_FAVORITES : + server = &cls.favoriteServers[0]; + break; + } + if (server) { + for (n = 0; n < count; n++) { + server[n].visible = visible; + } + } + + } else { + switch (source) { + case AS_LOCAL : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + cls.localServers[n].visible = visible; + } + break; + case AS_MPLAYER : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + cls.mplayerServers[n].visible = visible; + } + break; + case AS_GLOBAL : + if (n >= 0 && n < MAX_GLOBAL_SERVERS) { + cls.globalServers[n].visible = visible; + } + break; + case AS_FAVORITES : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + cls.favoriteServers[n].visible = visible; + } + break; + } + } +} + + +/* +======================= +LAN_ServerIsVisible +======================= +*/ +static int LAN_ServerIsVisible(int source, int n ) { + switch (source) { + case AS_LOCAL : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + return cls.localServers[n].visible; + } + break; + case AS_MPLAYER : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + return cls.mplayerServers[n].visible; + } + break; + case AS_GLOBAL : + if (n >= 0 && n < MAX_GLOBAL_SERVERS) { + return cls.globalServers[n].visible; + } + break; + case AS_FAVORITES : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + return cls.favoriteServers[n].visible; + } + break; + } + return qfalse; +} + +/* +======================= +LAN_UpdateVisiblePings +======================= +*/ +qboolean LAN_UpdateVisiblePings(int source ) { + return CL_UpdateVisiblePings_f(source); +} + +/* +==================== +LAN_GetServerStatus +==================== +*/ +int LAN_GetServerStatus( char *serverAddress, char *serverStatus, int maxLen ) { + return CL_ServerStatus( serverAddress, serverStatus, maxLen ); +} + +/* +==================== +CL_GetGlConfig +==================== +*/ +static void CL_GetGlconfig( glconfig_t *config ) { + *config = cls.glconfig; +} + +/* +==================== +GetClipboardData +==================== +*/ +static void GetClipboardData( char *buf, int buflen ) { + char *cbd; + + cbd = Sys_GetClipboardData(); + + if ( !cbd ) { + *buf = 0; + return; + } + + Q_strncpyz( buf, cbd, buflen ); + + Z_Free( cbd ); +} + +/* +==================== +Key_KeynumToStringBuf +==================== +*/ +static void Key_KeynumToStringBuf( int keynum, char *buf, int buflen ) { + Q_strncpyz( buf, Key_KeynumToString( keynum ), buflen ); +} + +/* +==================== +Key_GetBindingBuf +==================== +*/ +static void Key_GetBindingBuf( int keynum, char *buf, int buflen ) { + char *value; + + value = Key_GetBinding( keynum ); + if ( value ) { + Q_strncpyz( buf, value, buflen ); + } + else { + *buf = 0; + } +} + +/* +==================== +Key_GetCatcher +==================== +*/ +int Key_GetCatcher( void ) { + return cls.keyCatchers; +} + +/* +==================== +Ket_SetCatcher +==================== +*/ +void Key_SetCatcher( int catcher ) { + cls.keyCatchers = catcher; +} + + +/* +==================== +CLUI_GetCDKey +==================== +*/ +static void CLUI_GetCDKey( char *buf, int buflen ) { + cvar_t *fs; + fs = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO ); + if (UI_usesUniqueCDKey() && fs && fs->string[0] != 0) { + Com_Memcpy( buf, &cl_cdkey[16], 16); + buf[16] = 0; + } else { + Com_Memcpy( buf, cl_cdkey, 16); + buf[16] = 0; + } +} + + +/* +==================== +CLUI_SetCDKey +==================== +*/ +static void CLUI_SetCDKey( char *buf ) { + cvar_t *fs; + fs = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO ); + if (UI_usesUniqueCDKey() && fs && fs->string[0] != 0) { + Com_Memcpy( &cl_cdkey[16], buf, 16 ); + cl_cdkey[32] = 0; + // set the flag so the fle will be written at the next opportunity + cvar_modifiedFlags |= CVAR_ARCHIVE; + } else { + Com_Memcpy( cl_cdkey, buf, 16 ); + // set the flag so the fle will be written at the next opportunity + cvar_modifiedFlags |= CVAR_ARCHIVE; + } +} + +/* +==================== +GetConfigString +==================== +*/ +static int GetConfigString(int index, char *buf, int size) +{ + int offset; + + if (index < 0 || index >= MAX_CONFIGSTRINGS) + return qfalse; + + offset = cl.gameState.stringOffsets[index]; + if (!offset) { + if( size ) { + buf[0] = 0; + } + return qfalse; + } + + Q_strncpyz( buf, cl.gameState.stringData+offset, size); + + return qtrue; +} + +/* +==================== +FloatAsInt +==================== +*/ +static int FloatAsInt( float f ) { + int temp; + + *(float *)&temp = f; + + return temp; +} + +void *VM_ArgPtr( int intValue ); +#define VMA(x) VM_ArgPtr(args[x]) +#define VMF(x) ((float *)args)[x] + +/* +==================== +CL_UISystemCalls + +The ui module is making a system call +==================== +*/ +int CL_UISystemCalls( int *args ) { + switch( args[0] ) { + case UI_ERROR: + Com_Error( ERR_DROP, "%s", VMA(1) ); + return 0; + + case UI_PRINT: + Com_Printf( "%s", VMA(1) ); + return 0; + + case UI_MILLISECONDS: + return Sys_Milliseconds(); + + case UI_CVAR_REGISTER: + Cvar_Register( VMA(1), VMA(2), VMA(3), args[4] ); + return 0; + + case UI_CVAR_UPDATE: + Cvar_Update( VMA(1) ); + return 0; + + case UI_CVAR_SET: + Cvar_Set( VMA(1), VMA(2) ); + return 0; + + case UI_CVAR_VARIABLEVALUE: + return FloatAsInt( Cvar_VariableValue( VMA(1) ) ); + + case UI_CVAR_VARIABLESTRINGBUFFER: + Cvar_VariableStringBuffer( VMA(1), VMA(2), args[3] ); + return 0; + + case UI_CVAR_SETVALUE: + Cvar_SetValue( VMA(1), VMF(2) ); + return 0; + + case UI_CVAR_RESET: + Cvar_Reset( VMA(1) ); + return 0; + + case UI_CVAR_CREATE: + Cvar_Get( VMA(1), VMA(2), args[3] ); + return 0; + + case UI_CVAR_INFOSTRINGBUFFER: + Cvar_InfoStringBuffer( args[1], VMA(2), args[3] ); + return 0; + + case UI_ARGC: + return Cmd_Argc(); + + case UI_ARGV: + Cmd_ArgvBuffer( args[1], VMA(2), args[3] ); + return 0; + + case UI_CMD_EXECUTETEXT: + Cbuf_ExecuteText( args[1], VMA(2) ); + return 0; + + case UI_FS_FOPENFILE: + return FS_FOpenFileByMode( VMA(1), VMA(2), args[3] ); + + case UI_FS_READ: + FS_Read2( VMA(1), args[2], args[3] ); + return 0; + + case UI_FS_WRITE: + FS_Write( VMA(1), args[2], args[3] ); + return 0; + + case UI_FS_FCLOSEFILE: + FS_FCloseFile( args[1] ); + return 0; + + case UI_FS_GETFILELIST: + return FS_GetFileList( VMA(1), VMA(2), VMA(3), args[4] ); + + case UI_FS_SEEK: + return FS_Seek( args[1], args[2], args[3] ); + + case UI_R_REGISTERMODEL: + return re.RegisterModel( VMA(1) ); + + case UI_R_REGISTERSKIN: + return re.RegisterSkin( VMA(1) ); + + case UI_R_REGISTERSHADERNOMIP: + return re.RegisterShaderNoMip( VMA(1) ); + + case UI_R_CLEARSCENE: + re.ClearScene(); + return 0; + + case UI_R_ADDREFENTITYTOSCENE: + re.AddRefEntityToScene( VMA(1) ); + return 0; + + case UI_R_ADDPOLYTOSCENE: + re.AddPolyToScene( args[1], args[2], VMA(3), 1 ); + return 0; + + case UI_R_ADDLIGHTTOSCENE: + re.AddLightToScene( VMA(1), VMF(2), VMF(3), VMF(4), VMF(5) ); + return 0; + + case UI_R_RENDERSCENE: + re.RenderScene( VMA(1) ); + return 0; + + case UI_R_SETCOLOR: + re.SetColor( VMA(1) ); + return 0; + + case UI_R_DRAWSTRETCHPIC: + re.DrawStretchPic( VMF(1), VMF(2), VMF(3), VMF(4), VMF(5), VMF(6), VMF(7), VMF(8), args[9] ); + return 0; + + case UI_R_MODELBOUNDS: + re.ModelBounds( args[1], VMA(2), VMA(3) ); + return 0; + + case UI_UPDATESCREEN: + SCR_UpdateScreen(); + return 0; + + case UI_CM_LERPTAG: + re.LerpTag( VMA(1), args[2], args[3], args[4], VMF(5), VMA(6) ); + return 0; + + case UI_S_REGISTERSOUND: + return S_RegisterSound( VMA(1), args[2] ); + + case UI_S_STARTLOCALSOUND: + S_StartLocalSound( args[1], args[2] ); + return 0; + + case UI_KEY_KEYNUMTOSTRINGBUF: + Key_KeynumToStringBuf( args[1], VMA(2), args[3] ); + return 0; + + case UI_KEY_GETBINDINGBUF: + Key_GetBindingBuf( args[1], VMA(2), args[3] ); + return 0; + + case UI_KEY_SETBINDING: + Key_SetBinding( args[1], VMA(2) ); + return 0; + + case UI_KEY_ISDOWN: + return Key_IsDown( args[1] ); + + case UI_KEY_GETOVERSTRIKEMODE: + return Key_GetOverstrikeMode(); + + case UI_KEY_SETOVERSTRIKEMODE: + Key_SetOverstrikeMode( args[1] ); + return 0; + + case UI_KEY_CLEARSTATES: + Key_ClearStates(); + return 0; + + case UI_KEY_GETCATCHER: + return Key_GetCatcher(); + + case UI_KEY_SETCATCHER: + Key_SetCatcher( args[1] ); + return 0; + + case UI_GETCLIPBOARDDATA: + GetClipboardData( VMA(1), args[2] ); + return 0; + + case UI_GETCLIENTSTATE: + GetClientState( VMA(1) ); + return 0; + + case UI_GETGLCONFIG: + CL_GetGlconfig( VMA(1) ); + return 0; + + case UI_GETCONFIGSTRING: + return GetConfigString( args[1], VMA(2), args[3] ); + + case UI_LAN_LOADCACHEDSERVERS: + LAN_LoadCachedServers(); + return 0; + + case UI_LAN_SAVECACHEDSERVERS: + LAN_SaveServersToCache(); + return 0; + + case UI_LAN_ADDSERVER: + return LAN_AddServer(args[1], VMA(2), VMA(3)); + + case UI_LAN_REMOVESERVER: + LAN_RemoveServer(args[1], VMA(2)); + return 0; + + case UI_LAN_GETPINGQUEUECOUNT: + return LAN_GetPingQueueCount(); + + case UI_LAN_CLEARPING: + LAN_ClearPing( args[1] ); + return 0; + + case UI_LAN_GETPING: + LAN_GetPing( args[1], VMA(2), args[3], VMA(4) ); + return 0; + + case UI_LAN_GETPINGINFO: + LAN_GetPingInfo( args[1], VMA(2), args[3] ); + return 0; + + case UI_LAN_GETSERVERCOUNT: + return LAN_GetServerCount(args[1]); + + case UI_LAN_GETSERVERADDRESSSTRING: + LAN_GetServerAddressString( args[1], args[2], VMA(3), args[4] ); + return 0; + + case UI_LAN_GETSERVERINFO: + LAN_GetServerInfo( args[1], args[2], VMA(3), args[4] ); + return 0; + + case UI_LAN_GETSERVERPING: + return LAN_GetServerPing( args[1], args[2] ); + + case UI_LAN_MARKSERVERVISIBLE: + LAN_MarkServerVisible( args[1], args[2], args[3] ); + return 0; + + case UI_LAN_SERVERISVISIBLE: + return LAN_ServerIsVisible( args[1], args[2] ); + + case UI_LAN_UPDATEVISIBLEPINGS: + return LAN_UpdateVisiblePings( args[1] ); + + case UI_LAN_RESETPINGS: + LAN_ResetPings( args[1] ); + return 0; + + case UI_LAN_SERVERSTATUS: + return LAN_GetServerStatus( VMA(1), VMA(2), args[3] ); + + case UI_LAN_COMPARESERVERS: + return LAN_CompareServers( args[1], args[2], args[3], args[4], args[5] ); + + case UI_MEMORY_REMAINING: + return Hunk_MemoryRemaining(); + + case UI_GET_CDKEY: + CLUI_GetCDKey( VMA(1), args[2] ); + return 0; + + case UI_SET_CDKEY: + CLUI_SetCDKey( VMA(1) ); + return 0; + + case UI_SET_PBCLSTATUS: + return 0; + + case UI_R_REGISTERFONT: + re.RegisterFont( VMA(1), args[2], VMA(3)); + return 0; + + case UI_MEMSET: + Com_Memset( VMA(1), args[2], args[3] ); + return 0; + + case UI_MEMCPY: + Com_Memcpy( VMA(1), VMA(2), args[3] ); + return 0; + + case UI_STRNCPY: + return (int)strncpy( VMA(1), VMA(2), args[3] ); + + case UI_SIN: + return FloatAsInt( sin( VMF(1) ) ); + + case UI_COS: + return FloatAsInt( cos( VMF(1) ) ); + + case UI_ATAN2: + return FloatAsInt( atan2( VMF(1), VMF(2) ) ); + + case UI_SQRT: + return FloatAsInt( sqrt( VMF(1) ) ); + + case UI_FLOOR: + return FloatAsInt( floor( VMF(1) ) ); + + case UI_CEIL: + return FloatAsInt( ceil( VMF(1) ) ); + + case UI_PC_ADD_GLOBAL_DEFINE: + return botlib_export->PC_AddGlobalDefine( VMA(1) ); + case UI_PC_LOAD_SOURCE: + return botlib_export->PC_LoadSourceHandle( VMA(1) ); + case UI_PC_FREE_SOURCE: + return botlib_export->PC_FreeSourceHandle( args[1] ); + case UI_PC_READ_TOKEN: + return botlib_export->PC_ReadTokenHandle( args[1], VMA(2) ); + case UI_PC_SOURCE_FILE_AND_LINE: + return botlib_export->PC_SourceFileAndLine( args[1], VMA(2), VMA(3) ); + + case UI_S_STOPBACKGROUNDTRACK: + S_StopBackgroundTrack(); + return 0; + case UI_S_STARTBACKGROUNDTRACK: + S_StartBackgroundTrack( VMA(1), VMA(2)); + return 0; + + case UI_REAL_TIME: + return Com_RealTime( VMA(1) ); + + case UI_CIN_PLAYCINEMATIC: + Com_DPrintf("UI_CIN_PlayCinematic\n"); + return CIN_PlayCinematic(VMA(1), args[2], args[3], args[4], args[5], args[6]); + + case UI_CIN_STOPCINEMATIC: + return CIN_StopCinematic(args[1]); + + case UI_CIN_RUNCINEMATIC: + return CIN_RunCinematic(args[1]); + + case UI_CIN_DRAWCINEMATIC: + CIN_DrawCinematic(args[1]); + return 0; + + case UI_CIN_SETEXTENTS: + CIN_SetExtents(args[1], args[2], args[3], args[4], args[5]); + return 0; + + case UI_R_REMAP_SHADER: + re.RemapShader( VMA(1), VMA(2), VMA(3) ); + return 0; + + case UI_VERIFY_CDKEY: + return CL_CDKeyValidate(VMA(1), VMA(2)); + + + + default: + Com_Error( ERR_DROP, "Bad UI system trap: %i", args[0] ); + + } + + return 0; +} + +/* +==================== +CL_ShutdownUI +==================== +*/ +void CL_ShutdownUI( void ) { + cls.keyCatchers &= ~KEYCATCH_UI; + cls.uiStarted = qfalse; + if ( !uivm ) { + return; + } + VM_Call( uivm, UI_SHUTDOWN ); + VM_Free( uivm ); + uivm = NULL; +} + +/* +==================== +CL_InitUI +==================== +*/ +#define UI_OLD_API_VERSION 4 + +void CL_InitUI( void ) { + int v; + vmInterpret_t interpret; + + // load the dll or bytecode + if ( cl_connectedToPureServer != 0 ) { + // if sv_pure is set we only allow qvms to be loaded + interpret = VMI_COMPILED; + } + else { + interpret = Cvar_VariableValue( "vm_ui" ); + } + uivm = VM_Create( "ui", CL_UISystemCalls, interpret ); + if ( !uivm ) { + Com_Error( ERR_FATAL, "VM_Create on UI failed" ); + } + + // sanity check + v = VM_Call( uivm, UI_GETAPIVERSION ); + if (v == UI_OLD_API_VERSION) { +// Com_Printf(S_COLOR_YELLOW "WARNING: loading old Quake III Arena User Interface version %d\n", v ); + // init for this gamestate + VM_Call( uivm, UI_INIT, (cls.state >= CA_AUTHORIZING && cls.state < CA_ACTIVE)); + } + else if (v != UI_API_VERSION) { + Com_Error( ERR_DROP, "User Interface is version %d, expected %d", v, UI_API_VERSION ); + cls.uiStarted = qfalse; + } + else { + // init for this gamestate + VM_Call( uivm, UI_INIT, (cls.state >= CA_AUTHORIZING && cls.state < CA_ACTIVE) ); + } +} + +qboolean UI_usesUniqueCDKey() { + if (uivm) { + return (VM_Call( uivm, UI_HASUNIQUECDKEY) == qtrue); + } else { + return qfalse; + } +} + +/* +==================== +UI_GameCommand + +See if the current console command is claimed by the ui +==================== +*/ +qboolean UI_GameCommand( void ) { + if ( !uivm ) { + return qfalse; + } + + return VM_Call( uivm, UI_CONSOLE_COMMAND, cls.realtime ); +} diff --git a/code/client/client.h b/code/client/client.h index 09e02a8..f188f4b 100755 --- a/code/client/client.h +++ b/code/client/client.h @@ -1,519 +1,519 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// client.h -- primary header for client - -#include "../game/q_shared.h" -#include "../qcommon/qcommon.h" -#include "../renderer/tr_public.h" -#include "../ui/ui_public.h" -#include "keys.h" -#include "snd_public.h" -#include "../cgame/cg_public.h" -#include "../game/bg_public.h" - -#define RETRANSMIT_TIMEOUT 3000 // time between connection packet retransmits - - -// snapshots are a view of the server at a given time -typedef struct { - qboolean valid; // cleared if delta parsing was invalid - int snapFlags; // rate delayed and dropped commands - - int serverTime; // server time the message is valid for (in msec) - - int messageNum; // copied from netchan->incoming_sequence - int deltaNum; // messageNum the delta is from - int ping; // time from when cmdNum-1 was sent to time packet was reeceived - byte areamask[MAX_MAP_AREA_BYTES]; // portalarea visibility bits - - int cmdNum; // the next cmdNum the server is expecting - playerState_t ps; // complete information about the current player at this time - - int numEntities; // all of the entities that need to be presented - int parseEntitiesNum; // at the time of this snapshot - - int serverCommandNum; // execute all commands up to this before - // making the snapshot current -} clSnapshot_t; - - - -/* -============================================================================= - -the clientActive_t structure is wiped completely at every -new gamestate_t, potentially several times during an established connection - -============================================================================= -*/ - -typedef struct { - int p_cmdNumber; // cl.cmdNumber when packet was sent - int p_serverTime; // usercmd->serverTime when packet was sent - int p_realtime; // cls.realtime when packet was sent -} outPacket_t; - -// the parseEntities array must be large enough to hold PACKET_BACKUP frames of -// entities, so that when a delta compressed message arives from the server -// it can be un-deltad from the original -#define MAX_PARSE_ENTITIES 2048 - -extern int g_console_field_width; - -typedef struct { - int timeoutcount; // it requres several frames in a timeout condition - // to disconnect, preventing debugging breaks from - // causing immediate disconnects on continue - clSnapshot_t snap; // latest received from server - - int serverTime; // may be paused during play - int oldServerTime; // to prevent time from flowing bakcwards - int oldFrameServerTime; // to check tournament restarts - int serverTimeDelta; // cl.serverTime = cls.realtime + cl.serverTimeDelta - // this value changes as net lag varies - qboolean extrapolatedSnapshot; // set if any cgame frame has been forced to extrapolate - // cleared when CL_AdjustTimeDelta looks at it - qboolean newSnapshots; // set on parse of any valid packet - - gameState_t gameState; // configstrings - char mapname[MAX_QPATH]; // extracted from CS_SERVERINFO - - int parseEntitiesNum; // index (not anded off) into cl_parse_entities[] - - int mouseDx[2], mouseDy[2]; // added to by mouse events - int mouseIndex; - int joystickAxis[MAX_JOYSTICK_AXIS]; // set by joystick events - - // cgame communicates a few values to the client system - int cgameUserCmdValue; // current weapon to add to usercmd_t - float cgameSensitivity; - - // cmds[cmdNumber] is the predicted command, [cmdNumber-1] is the last - // properly generated command - usercmd_t cmds[CMD_BACKUP]; // each mesage will send several old cmds - int cmdNumber; // incremented each frame, because multiple - // frames may need to be packed into a single packet - - outPacket_t outPackets[PACKET_BACKUP]; // information about each packet we have sent out - - // the client maintains its own idea of view angles, which are - // sent to the server each frame. It is cleared to 0 upon entering each level. - // the server sends a delta each frame which is added to the locally - // tracked view angles to account for standing on rotating objects, - // and teleport direction changes - vec3_t viewangles; - - int serverId; // included in each client message so the server - // can tell if it is for a prior map_restart - // big stuff at end of structure so most offsets are 15 bits or less - clSnapshot_t snapshots[PACKET_BACKUP]; - - entityState_t entityBaselines[MAX_GENTITIES]; // for delta compression when not in previous frame - - entityState_t parseEntities[MAX_PARSE_ENTITIES]; -} clientActive_t; - -extern clientActive_t cl; - -/* -============================================================================= - -the clientConnection_t structure is wiped when disconnecting from a server, -either to go to a full screen console, play a demo, or connect to a different server - -A connection can be to either a server through the network layer or a -demo through a file. - -============================================================================= -*/ - - -typedef struct { - - int clientNum; - int lastPacketSentTime; // for retransmits during connection - int lastPacketTime; // for timeouts - - netadr_t serverAddress; - int connectTime; // for connection retransmits - int connectPacketCount; // for display on connection dialog - char serverMessage[MAX_STRING_TOKENS]; // for display on connection dialog - - int challenge; // from the server to use for connecting - int checksumFeed; // from the server for checksum calculations - - // these are our reliable messages that go to the server - int reliableSequence; - int reliableAcknowledge; // the last one the server has executed - char reliableCommands[MAX_RELIABLE_COMMANDS][MAX_STRING_CHARS]; - - // server message (unreliable) and command (reliable) sequence - // numbers are NOT cleared at level changes, but continue to - // increase as long as the connection is valid - - // message sequence is used by both the network layer and the - // delta compression layer - int serverMessageSequence; - - // reliable messages received from server - int serverCommandSequence; - int lastExecutedServerCommand; // last server command grabbed or executed with CL_GetServerCommand - char serverCommands[MAX_RELIABLE_COMMANDS][MAX_STRING_CHARS]; - - // file transfer from server - fileHandle_t download; - char downloadTempName[MAX_OSPATH]; - char downloadName[MAX_OSPATH]; - int downloadNumber; - int downloadBlock; // block we are waiting for - int downloadCount; // how many bytes we got - int downloadSize; // how many bytes we got - char downloadList[MAX_INFO_STRING]; // list of paks we need to download - qboolean downloadRestart; // if true, we need to do another FS_Restart because we downloaded a pak - - // demo information - char demoName[MAX_QPATH]; - qboolean spDemoRecording; - qboolean demorecording; - qboolean demoplaying; - qboolean demowaiting; // don't record until a non-delta message is received - qboolean firstDemoFrameSkipped; - fileHandle_t demofile; - - int timeDemoFrames; // counter of rendered frames - int timeDemoStart; // cls.realtime before first frame - int timeDemoBaseTime; // each frame will be at this time + frameNum * 50 - - // big stuff at end of structure so most offsets are 15 bits or less - netchan_t netchan; -} clientConnection_t; - -extern clientConnection_t clc; - -/* -================================================================== - -the clientStatic_t structure is never wiped, and is used even when -no client connection is active at all - -================================================================== -*/ - -typedef struct { - netadr_t adr; - int start; - int time; - char info[MAX_INFO_STRING]; -} ping_t; - -typedef struct { - netadr_t adr; - char hostName[MAX_NAME_LENGTH]; - char mapName[MAX_NAME_LENGTH]; - char game[MAX_NAME_LENGTH]; - int netType; - int gameType; - int clients; - int maxClients; - int minPing; - int maxPing; - int ping; - qboolean visible; - int punkbuster; -} serverInfo_t; - -typedef struct { - byte ip[4]; - unsigned short port; -} serverAddress_t; - -typedef struct { - connstate_t state; // connection status - int keyCatchers; // bit flags - - qboolean cddialog; // bring up the cd needed dialog next frame - - char servername[MAX_OSPATH]; // name of server from original connect (used by reconnect) - - // when the server clears the hunk, all of these must be restarted - qboolean rendererStarted; - qboolean soundStarted; - qboolean soundRegistered; - qboolean uiStarted; - qboolean cgameStarted; - - int framecount; - int frametime; // msec since last frame - - int realtime; // ignores pause - int realFrametime; // ignoring pause, so console always works - - int numlocalservers; - serverInfo_t localServers[MAX_OTHER_SERVERS]; - - int numglobalservers; - serverInfo_t globalServers[MAX_GLOBAL_SERVERS]; - // additional global servers - int numGlobalServerAddresses; - serverAddress_t globalServerAddresses[MAX_GLOBAL_SERVERS]; - - int numfavoriteservers; - serverInfo_t favoriteServers[MAX_OTHER_SERVERS]; - - int nummplayerservers; - serverInfo_t mplayerServers[MAX_OTHER_SERVERS]; - - int pingUpdateSource; // source currently pinging or updating - - int masterNum; - - // update server info - netadr_t updateServer; - char updateChallenge[MAX_TOKEN_CHARS]; - char updateInfoString[MAX_INFO_STRING]; - - netadr_t authorizeServer; - - // rendering info - glconfig_t glconfig; - qhandle_t charSetShader; - qhandle_t whiteShader; - qhandle_t consoleShader; -} clientStatic_t; - -extern clientStatic_t cls; - -//============================================================================= - -extern vm_t *cgvm; // interface to cgame dll or vm -extern vm_t *uivm; // interface to ui dll or vm -extern refexport_t re; // interface to refresh .dll - - -// -// cvars -// -extern cvar_t *cl_nodelta; -extern cvar_t *cl_debugMove; -extern cvar_t *cl_noprint; -extern cvar_t *cl_timegraph; -extern cvar_t *cl_maxpackets; -extern cvar_t *cl_packetdup; -extern cvar_t *cl_shownet; -extern cvar_t *cl_showSend; -extern cvar_t *cl_timeNudge; -extern cvar_t *cl_showTimeDelta; -extern cvar_t *cl_freezeDemo; - -extern cvar_t *cl_yawspeed; -extern cvar_t *cl_pitchspeed; -extern cvar_t *cl_run; -extern cvar_t *cl_anglespeedkey; - -extern cvar_t *cl_sensitivity; -extern cvar_t *cl_freelook; - -extern cvar_t *cl_mouseAccel; -extern cvar_t *cl_showMouseRate; - -extern cvar_t *m_pitch; -extern cvar_t *m_yaw; -extern cvar_t *m_forward; -extern cvar_t *m_side; -extern cvar_t *m_filter; - -extern cvar_t *cl_timedemo; - -extern cvar_t *cl_activeAction; - -extern cvar_t *cl_allowDownload; -extern cvar_t *cl_conXOffset; -extern cvar_t *cl_inGameVideo; - -//================================================= - -// -// cl_main -// - -void CL_Init (void); -void CL_FlushMemory(void); -void CL_ShutdownAll(void); -void CL_AddReliableCommand( const char *cmd ); - -void CL_StartHunkUsers( void ); - -void CL_Disconnect_f (void); -void CL_GetChallengePacket (void); -void CL_Vid_Restart_f( void ); -void CL_Snd_Restart_f (void); -void CL_StartDemoLoop( void ); -void CL_NextDemo( void ); -void CL_ReadDemoMessage( void ); - -void CL_InitDownloads(void); -void CL_NextDownload(void); - -void CL_GetPing( int n, char *buf, int buflen, int *pingtime ); -void CL_GetPingInfo( int n, char *buf, int buflen ); -void CL_ClearPing( int n ); -int CL_GetPingQueueCount( void ); - -void CL_ShutdownRef( void ); -void CL_InitRef( void ); -qboolean CL_CDKeyValidate( const char *key, const char *checksum ); -int CL_ServerStatus( char *serverAddress, char *serverStatusString, int maxLen ); - - -// -// cl_input -// -typedef struct { - int down[2]; // key nums holding it down - unsigned downtime; // msec timestamp - unsigned msec; // msec down this frame if both a down and up happened - qboolean active; // current state - qboolean wasPressed; // set when down, not cleared when up -} kbutton_t; - -extern kbutton_t in_mlook, in_klook; -extern kbutton_t in_strafe; -extern kbutton_t in_speed; - -void CL_InitInput (void); -void CL_SendCmd (void); -void CL_ClearState (void); -void CL_ReadPackets (void); - -void CL_WritePacket( void ); -void IN_CenterView (void); - -void CL_VerifyCode( void ); - -float CL_KeyState (kbutton_t *key); -char *Key_KeynumToString (int keynum); - -// -// cl_parse.c -// -extern int cl_connectedToPureServer; - -void CL_SystemInfoChanged( void ); -void CL_ParseServerMessage( msg_t *msg ); - -//==================================================================== - -void CL_ServerInfoPacket( netadr_t from, msg_t *msg ); -void CL_LocalServers_f( void ); -void CL_GlobalServers_f( void ); -void CL_FavoriteServers_f( void ); -void CL_Ping_f( void ); -qboolean CL_UpdateVisiblePings_f( int source ); - - -// -// console -// -void Con_DrawCharacter (int cx, int line, int num); - -void Con_CheckResize (void); -void Con_Init (void); -void Con_Clear_f (void); -void Con_ToggleConsole_f (void); -void Con_DrawNotify (void); -void Con_ClearNotify (void); -void Con_RunConsole (void); -void Con_DrawConsole (void); -void Con_PageUp( void ); -void Con_PageDown( void ); -void Con_Top( void ); -void Con_Bottom( void ); -void Con_Close( void ); - - -// -// cl_scrn.c -// -void SCR_Init (void); -void SCR_UpdateScreen (void); - -void SCR_DebugGraph (float value, int color); - -int SCR_GetBigStringWidth( const char *str ); // returns in virtual 640x480 coordinates - -void SCR_AdjustFrom640( float *x, float *y, float *w, float *h ); -void SCR_FillRect( float x, float y, float width, float height, - const float *color ); -void SCR_DrawPic( float x, float y, float width, float height, qhandle_t hShader ); -void SCR_DrawNamedPic( float x, float y, float width, float height, const char *picname ); - -void SCR_DrawBigString( int x, int y, const char *s, float alpha ); // draws a string with embedded color control characters with fade -void SCR_DrawBigStringColor( int x, int y, const char *s, vec4_t color ); // ignores embedded color control characters -void SCR_DrawSmallStringExt( int x, int y, const char *string, float *setColor, qboolean forceColor ); -void SCR_DrawSmallChar( int x, int y, int ch ); - - -// -// cl_cin.c -// - -void CL_PlayCinematic_f( void ); -void SCR_DrawCinematic (void); -void SCR_RunCinematic (void); -void SCR_StopCinematic (void); -int CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits); -e_status CIN_StopCinematic(int handle); -e_status CIN_RunCinematic (int handle); -void CIN_DrawCinematic (int handle); -void CIN_SetExtents (int handle, int x, int y, int w, int h); -void CIN_SetLooping (int handle, qboolean loop); -void CIN_UploadCinematic(int handle); -void CIN_CloseAllVideos(void); - -// -// cl_cgame.c -// -void CL_InitCGame( void ); -void CL_ShutdownCGame( void ); -qboolean CL_GameCommand( void ); -void CL_CGameRendering( stereoFrame_t stereo ); -void CL_SetCGameTime( void ); -void CL_FirstSnapshot( void ); -void CL_ShaderStateChanged(void); - -// -// cl_ui.c -// -void CL_InitUI( void ); -void CL_ShutdownUI( void ); -int Key_GetCatcher( void ); -void Key_SetCatcher( int catcher ); -void LAN_LoadCachedServers(); -void LAN_SaveServersToCache(); - - -// -// cl_net_chan.c -// -void CL_Netchan_Transmit( netchan_t *chan, msg_t* msg); //int length, const byte *data ); -void CL_Netchan_TransmitNextFragment( netchan_t *chan ); -qboolean CL_Netchan_Process( netchan_t *chan, msg_t *msg ); +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// client.h -- primary header for client + +#include "../game/q_shared.h" +#include "../qcommon/qcommon.h" +#include "../renderer/tr_public.h" +#include "../ui/ui_public.h" +#include "keys.h" +#include "snd_public.h" +#include "../cgame/cg_public.h" +#include "../game/bg_public.h" + +#define RETRANSMIT_TIMEOUT 3000 // time between connection packet retransmits + + +// snapshots are a view of the server at a given time +typedef struct { + qboolean valid; // cleared if delta parsing was invalid + int snapFlags; // rate delayed and dropped commands + + int serverTime; // server time the message is valid for (in msec) + + int messageNum; // copied from netchan->incoming_sequence + int deltaNum; // messageNum the delta is from + int ping; // time from when cmdNum-1 was sent to time packet was reeceived + byte areamask[MAX_MAP_AREA_BYTES]; // portalarea visibility bits + + int cmdNum; // the next cmdNum the server is expecting + playerState_t ps; // complete information about the current player at this time + + int numEntities; // all of the entities that need to be presented + int parseEntitiesNum; // at the time of this snapshot + + int serverCommandNum; // execute all commands up to this before + // making the snapshot current +} clSnapshot_t; + + + +/* +============================================================================= + +the clientActive_t structure is wiped completely at every +new gamestate_t, potentially several times during an established connection + +============================================================================= +*/ + +typedef struct { + int p_cmdNumber; // cl.cmdNumber when packet was sent + int p_serverTime; // usercmd->serverTime when packet was sent + int p_realtime; // cls.realtime when packet was sent +} outPacket_t; + +// the parseEntities array must be large enough to hold PACKET_BACKUP frames of +// entities, so that when a delta compressed message arives from the server +// it can be un-deltad from the original +#define MAX_PARSE_ENTITIES 2048 + +extern int g_console_field_width; + +typedef struct { + int timeoutcount; // it requres several frames in a timeout condition + // to disconnect, preventing debugging breaks from + // causing immediate disconnects on continue + clSnapshot_t snap; // latest received from server + + int serverTime; // may be paused during play + int oldServerTime; // to prevent time from flowing bakcwards + int oldFrameServerTime; // to check tournament restarts + int serverTimeDelta; // cl.serverTime = cls.realtime + cl.serverTimeDelta + // this value changes as net lag varies + qboolean extrapolatedSnapshot; // set if any cgame frame has been forced to extrapolate + // cleared when CL_AdjustTimeDelta looks at it + qboolean newSnapshots; // set on parse of any valid packet + + gameState_t gameState; // configstrings + char mapname[MAX_QPATH]; // extracted from CS_SERVERINFO + + int parseEntitiesNum; // index (not anded off) into cl_parse_entities[] + + int mouseDx[2], mouseDy[2]; // added to by mouse events + int mouseIndex; + int joystickAxis[MAX_JOYSTICK_AXIS]; // set by joystick events + + // cgame communicates a few values to the client system + int cgameUserCmdValue; // current weapon to add to usercmd_t + float cgameSensitivity; + + // cmds[cmdNumber] is the predicted command, [cmdNumber-1] is the last + // properly generated command + usercmd_t cmds[CMD_BACKUP]; // each mesage will send several old cmds + int cmdNumber; // incremented each frame, because multiple + // frames may need to be packed into a single packet + + outPacket_t outPackets[PACKET_BACKUP]; // information about each packet we have sent out + + // the client maintains its own idea of view angles, which are + // sent to the server each frame. It is cleared to 0 upon entering each level. + // the server sends a delta each frame which is added to the locally + // tracked view angles to account for standing on rotating objects, + // and teleport direction changes + vec3_t viewangles; + + int serverId; // included in each client message so the server + // can tell if it is for a prior map_restart + // big stuff at end of structure so most offsets are 15 bits or less + clSnapshot_t snapshots[PACKET_BACKUP]; + + entityState_t entityBaselines[MAX_GENTITIES]; // for delta compression when not in previous frame + + entityState_t parseEntities[MAX_PARSE_ENTITIES]; +} clientActive_t; + +extern clientActive_t cl; + +/* +============================================================================= + +the clientConnection_t structure is wiped when disconnecting from a server, +either to go to a full screen console, play a demo, or connect to a different server + +A connection can be to either a server through the network layer or a +demo through a file. + +============================================================================= +*/ + + +typedef struct { + + int clientNum; + int lastPacketSentTime; // for retransmits during connection + int lastPacketTime; // for timeouts + + netadr_t serverAddress; + int connectTime; // for connection retransmits + int connectPacketCount; // for display on connection dialog + char serverMessage[MAX_STRING_TOKENS]; // for display on connection dialog + + int challenge; // from the server to use for connecting + int checksumFeed; // from the server for checksum calculations + + // these are our reliable messages that go to the server + int reliableSequence; + int reliableAcknowledge; // the last one the server has executed + char reliableCommands[MAX_RELIABLE_COMMANDS][MAX_STRING_CHARS]; + + // server message (unreliable) and command (reliable) sequence + // numbers are NOT cleared at level changes, but continue to + // increase as long as the connection is valid + + // message sequence is used by both the network layer and the + // delta compression layer + int serverMessageSequence; + + // reliable messages received from server + int serverCommandSequence; + int lastExecutedServerCommand; // last server command grabbed or executed with CL_GetServerCommand + char serverCommands[MAX_RELIABLE_COMMANDS][MAX_STRING_CHARS]; + + // file transfer from server + fileHandle_t download; + char downloadTempName[MAX_OSPATH]; + char downloadName[MAX_OSPATH]; + int downloadNumber; + int downloadBlock; // block we are waiting for + int downloadCount; // how many bytes we got + int downloadSize; // how many bytes we got + char downloadList[MAX_INFO_STRING]; // list of paks we need to download + qboolean downloadRestart; // if true, we need to do another FS_Restart because we downloaded a pak + + // demo information + char demoName[MAX_QPATH]; + qboolean spDemoRecording; + qboolean demorecording; + qboolean demoplaying; + qboolean demowaiting; // don't record until a non-delta message is received + qboolean firstDemoFrameSkipped; + fileHandle_t demofile; + + int timeDemoFrames; // counter of rendered frames + int timeDemoStart; // cls.realtime before first frame + int timeDemoBaseTime; // each frame will be at this time + frameNum * 50 + + // big stuff at end of structure so most offsets are 15 bits or less + netchan_t netchan; +} clientConnection_t; + +extern clientConnection_t clc; + +/* +================================================================== + +the clientStatic_t structure is never wiped, and is used even when +no client connection is active at all + +================================================================== +*/ + +typedef struct { + netadr_t adr; + int start; + int time; + char info[MAX_INFO_STRING]; +} ping_t; + +typedef struct { + netadr_t adr; + char hostName[MAX_NAME_LENGTH]; + char mapName[MAX_NAME_LENGTH]; + char game[MAX_NAME_LENGTH]; + int netType; + int gameType; + int clients; + int maxClients; + int minPing; + int maxPing; + int ping; + qboolean visible; + int punkbuster; +} serverInfo_t; + +typedef struct { + byte ip[4]; + unsigned short port; +} serverAddress_t; + +typedef struct { + connstate_t state; // connection status + int keyCatchers; // bit flags + + qboolean cddialog; // bring up the cd needed dialog next frame + + char servername[MAX_OSPATH]; // name of server from original connect (used by reconnect) + + // when the server clears the hunk, all of these must be restarted + qboolean rendererStarted; + qboolean soundStarted; + qboolean soundRegistered; + qboolean uiStarted; + qboolean cgameStarted; + + int framecount; + int frametime; // msec since last frame + + int realtime; // ignores pause + int realFrametime; // ignoring pause, so console always works + + int numlocalservers; + serverInfo_t localServers[MAX_OTHER_SERVERS]; + + int numglobalservers; + serverInfo_t globalServers[MAX_GLOBAL_SERVERS]; + // additional global servers + int numGlobalServerAddresses; + serverAddress_t globalServerAddresses[MAX_GLOBAL_SERVERS]; + + int numfavoriteservers; + serverInfo_t favoriteServers[MAX_OTHER_SERVERS]; + + int nummplayerservers; + serverInfo_t mplayerServers[MAX_OTHER_SERVERS]; + + int pingUpdateSource; // source currently pinging or updating + + int masterNum; + + // update server info + netadr_t updateServer; + char updateChallenge[MAX_TOKEN_CHARS]; + char updateInfoString[MAX_INFO_STRING]; + + netadr_t authorizeServer; + + // rendering info + glconfig_t glconfig; + qhandle_t charSetShader; + qhandle_t whiteShader; + qhandle_t consoleShader; +} clientStatic_t; + +extern clientStatic_t cls; + +//============================================================================= + +extern vm_t *cgvm; // interface to cgame dll or vm +extern vm_t *uivm; // interface to ui dll or vm +extern refexport_t re; // interface to refresh .dll + + +// +// cvars +// +extern cvar_t *cl_nodelta; +extern cvar_t *cl_debugMove; +extern cvar_t *cl_noprint; +extern cvar_t *cl_timegraph; +extern cvar_t *cl_maxpackets; +extern cvar_t *cl_packetdup; +extern cvar_t *cl_shownet; +extern cvar_t *cl_showSend; +extern cvar_t *cl_timeNudge; +extern cvar_t *cl_showTimeDelta; +extern cvar_t *cl_freezeDemo; + +extern cvar_t *cl_yawspeed; +extern cvar_t *cl_pitchspeed; +extern cvar_t *cl_run; +extern cvar_t *cl_anglespeedkey; + +extern cvar_t *cl_sensitivity; +extern cvar_t *cl_freelook; + +extern cvar_t *cl_mouseAccel; +extern cvar_t *cl_showMouseRate; + +extern cvar_t *m_pitch; +extern cvar_t *m_yaw; +extern cvar_t *m_forward; +extern cvar_t *m_side; +extern cvar_t *m_filter; + +extern cvar_t *cl_timedemo; + +extern cvar_t *cl_activeAction; + +extern cvar_t *cl_allowDownload; +extern cvar_t *cl_conXOffset; +extern cvar_t *cl_inGameVideo; + +//================================================= + +// +// cl_main +// + +void CL_Init (void); +void CL_FlushMemory(void); +void CL_ShutdownAll(void); +void CL_AddReliableCommand( const char *cmd ); + +void CL_StartHunkUsers( void ); + +void CL_Disconnect_f (void); +void CL_GetChallengePacket (void); +void CL_Vid_Restart_f( void ); +void CL_Snd_Restart_f (void); +void CL_StartDemoLoop( void ); +void CL_NextDemo( void ); +void CL_ReadDemoMessage( void ); + +void CL_InitDownloads(void); +void CL_NextDownload(void); + +void CL_GetPing( int n, char *buf, int buflen, int *pingtime ); +void CL_GetPingInfo( int n, char *buf, int buflen ); +void CL_ClearPing( int n ); +int CL_GetPingQueueCount( void ); + +void CL_ShutdownRef( void ); +void CL_InitRef( void ); +qboolean CL_CDKeyValidate( const char *key, const char *checksum ); +int CL_ServerStatus( char *serverAddress, char *serverStatusString, int maxLen ); + + +// +// cl_input +// +typedef struct { + int down[2]; // key nums holding it down + unsigned downtime; // msec timestamp + unsigned msec; // msec down this frame if both a down and up happened + qboolean active; // current state + qboolean wasPressed; // set when down, not cleared when up +} kbutton_t; + +extern kbutton_t in_mlook, in_klook; +extern kbutton_t in_strafe; +extern kbutton_t in_speed; + +void CL_InitInput (void); +void CL_SendCmd (void); +void CL_ClearState (void); +void CL_ReadPackets (void); + +void CL_WritePacket( void ); +void IN_CenterView (void); + +void CL_VerifyCode( void ); + +float CL_KeyState (kbutton_t *key); +char *Key_KeynumToString (int keynum); + +// +// cl_parse.c +// +extern int cl_connectedToPureServer; + +void CL_SystemInfoChanged( void ); +void CL_ParseServerMessage( msg_t *msg ); + +//==================================================================== + +void CL_ServerInfoPacket( netadr_t from, msg_t *msg ); +void CL_LocalServers_f( void ); +void CL_GlobalServers_f( void ); +void CL_FavoriteServers_f( void ); +void CL_Ping_f( void ); +qboolean CL_UpdateVisiblePings_f( int source ); + + +// +// console +// +void Con_DrawCharacter (int cx, int line, int num); + +void Con_CheckResize (void); +void Con_Init (void); +void Con_Clear_f (void); +void Con_ToggleConsole_f (void); +void Con_DrawNotify (void); +void Con_ClearNotify (void); +void Con_RunConsole (void); +void Con_DrawConsole (void); +void Con_PageUp( void ); +void Con_PageDown( void ); +void Con_Top( void ); +void Con_Bottom( void ); +void Con_Close( void ); + + +// +// cl_scrn.c +// +void SCR_Init (void); +void SCR_UpdateScreen (void); + +void SCR_DebugGraph (float value, int color); + +int SCR_GetBigStringWidth( const char *str ); // returns in virtual 640x480 coordinates + +void SCR_AdjustFrom640( float *x, float *y, float *w, float *h ); +void SCR_FillRect( float x, float y, float width, float height, + const float *color ); +void SCR_DrawPic( float x, float y, float width, float height, qhandle_t hShader ); +void SCR_DrawNamedPic( float x, float y, float width, float height, const char *picname ); + +void SCR_DrawBigString( int x, int y, const char *s, float alpha ); // draws a string with embedded color control characters with fade +void SCR_DrawBigStringColor( int x, int y, const char *s, vec4_t color ); // ignores embedded color control characters +void SCR_DrawSmallStringExt( int x, int y, const char *string, float *setColor, qboolean forceColor ); +void SCR_DrawSmallChar( int x, int y, int ch ); + + +// +// cl_cin.c +// + +void CL_PlayCinematic_f( void ); +void SCR_DrawCinematic (void); +void SCR_RunCinematic (void); +void SCR_StopCinematic (void); +int CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits); +e_status CIN_StopCinematic(int handle); +e_status CIN_RunCinematic (int handle); +void CIN_DrawCinematic (int handle); +void CIN_SetExtents (int handle, int x, int y, int w, int h); +void CIN_SetLooping (int handle, qboolean loop); +void CIN_UploadCinematic(int handle); +void CIN_CloseAllVideos(void); + +// +// cl_cgame.c +// +void CL_InitCGame( void ); +void CL_ShutdownCGame( void ); +qboolean CL_GameCommand( void ); +void CL_CGameRendering( stereoFrame_t stereo ); +void CL_SetCGameTime( void ); +void CL_FirstSnapshot( void ); +void CL_ShaderStateChanged(void); + +// +// cl_ui.c +// +void CL_InitUI( void ); +void CL_ShutdownUI( void ); +int Key_GetCatcher( void ); +void Key_SetCatcher( int catcher ); +void LAN_LoadCachedServers(); +void LAN_SaveServersToCache(); + + +// +// cl_net_chan.c +// +void CL_Netchan_Transmit( netchan_t *chan, msg_t* msg); //int length, const byte *data ); +void CL_Netchan_TransmitNextFragment( netchan_t *chan ); +qboolean CL_Netchan_Process( netchan_t *chan, msg_t *msg ); diff --git a/code/client/keys.h b/code/client/keys.h index bc598f4..c852f5a 100755 --- a/code/client/keys.h +++ b/code/client/keys.h @@ -1,57 +1,57 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -#include "../ui/keycodes.h" - -#define MAX_KEYS 256 - -typedef struct { - qboolean down; - int repeats; // if > 1, it is autorepeating - char *binding; -} qkey_t; - -extern qboolean key_overstrikeMode; -extern qkey_t keys[MAX_KEYS]; - -// NOTE TTimo the declaration of field_t and Field_Clear is now in qcommon/qcommon.h -void Field_KeyDownEvent( field_t *edit, int key ); -void Field_CharEvent( field_t *edit, int ch ); -void Field_Draw( field_t *edit, int x, int y, int width, qboolean showCursor ); -void Field_BigDraw( field_t *edit, int x, int y, int width, qboolean showCursor ); - -#define COMMAND_HISTORY 32 -extern field_t historyEditLines[COMMAND_HISTORY]; - -extern field_t g_consoleField; -extern field_t chatField; -extern qboolean anykeydown; -extern qboolean chat_team; -extern int chat_playerNum; - -void Key_WriteBindings( fileHandle_t f ); -void Key_SetBinding( int keynum, const char *binding ); -char *Key_GetBinding( int keynum ); -qboolean Key_IsDown( int keynum ); -qboolean Key_GetOverstrikeMode( void ); -void Key_SetOverstrikeMode( qboolean state ); -void Key_ClearStates( void ); -int Key_GetKey(const char *binding); +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +#include "../ui/keycodes.h" + +#define MAX_KEYS 256 + +typedef struct { + qboolean down; + int repeats; // if > 1, it is autorepeating + char *binding; +} qkey_t; + +extern qboolean key_overstrikeMode; +extern qkey_t keys[MAX_KEYS]; + +// NOTE TTimo the declaration of field_t and Field_Clear is now in qcommon/qcommon.h +void Field_KeyDownEvent( field_t *edit, int key ); +void Field_CharEvent( field_t *edit, int ch ); +void Field_Draw( field_t *edit, int x, int y, int width, qboolean showCursor ); +void Field_BigDraw( field_t *edit, int x, int y, int width, qboolean showCursor ); + +#define COMMAND_HISTORY 32 +extern field_t historyEditLines[COMMAND_HISTORY]; + +extern field_t g_consoleField; +extern field_t chatField; +extern qboolean anykeydown; +extern qboolean chat_team; +extern int chat_playerNum; + +void Key_WriteBindings( fileHandle_t f ); +void Key_SetBinding( int keynum, const char *binding ); +char *Key_GetBinding( int keynum ); +qboolean Key_IsDown( int keynum ); +qboolean Key_GetOverstrikeMode( void ); +void Key_SetOverstrikeMode( qboolean state ); +void Key_ClearStates( void ); +int Key_GetKey(const char *binding); diff --git a/code/client/snd_adpcm.c b/code/client/snd_adpcm.c index bc52ad4..3e335fb 100755 --- a/code/client/snd_adpcm.c +++ b/code/client/snd_adpcm.c @@ -1,330 +1,330 @@ -/*********************************************************** -Copyright 1992 by Stichting Mathematisch Centrum, Amsterdam, The -Netherlands. - - All Rights Reserved - -Permission to use, copy, modify, and distribute this software and its -documentation for any purpose and without fee is hereby granted, -provided that the above copyright notice appear in all copies and that -both that copyright notice and this permission notice appear in -supporting documentation, and that the names of Stichting Mathematisch -Centrum or CWI not be used in advertising or publicity pertaining to -distribution of the software without specific, written prior permission. - -STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO -THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM 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. - -******************************************************************/ - -/* -** Intel/DVI ADPCM coder/decoder. -** -** The algorithm for this coder was taken from the IMA Compatability Project -** proceedings, Vol 2, Number 2; May 1992. -** -** Version 1.2, 18-Dec-92. -*/ - -#include "snd_local.h" - - -/* Intel ADPCM step variation table */ -static int indexTable[16] = { - -1, -1, -1, -1, 2, 4, 6, 8, - -1, -1, -1, -1, 2, 4, 6, 8, -}; - -static int stepsizeTable[89] = { - 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, - 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, - 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, - 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, - 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, - 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, - 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, - 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, - 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 -}; - - -void S_AdpcmEncode( short indata[], char outdata[], int len, struct adpcm_state *state ) { - short *inp; /* Input buffer pointer */ - signed char *outp; /* output buffer pointer */ - int val; /* Current input sample value */ - int sign; /* Current adpcm sign bit */ - int delta; /* Current adpcm output value */ - int diff; /* Difference between val and sample */ - int step; /* Stepsize */ - int valpred; /* Predicted output value */ - int vpdiff; /* Current change to valpred */ - int index; /* Current step change index */ - int outputbuffer; /* place to keep previous 4-bit value */ - int bufferstep; /* toggle between outputbuffer/output */ - - outp = (signed char *)outdata; - inp = indata; - - valpred = state->sample; - index = state->index; - step = stepsizeTable[index]; - - outputbuffer = 0; // quiet a compiler warning - bufferstep = 1; - - for ( ; len > 0 ; len-- ) { - val = *inp++; - - /* Step 1 - compute difference with previous value */ - diff = val - valpred; - sign = (diff < 0) ? 8 : 0; - if ( sign ) diff = (-diff); - - /* Step 2 - Divide and clamp */ - /* Note: - ** This code *approximately* computes: - ** delta = diff*4/step; - ** vpdiff = (delta+0.5)*step/4; - ** but in shift step bits are dropped. The net result of this is - ** that even if you have fast mul/div hardware you cannot put it to - ** good use since the fixup would be too expensive. - */ - delta = 0; - vpdiff = (step >> 3); - - if ( diff >= step ) { - delta = 4; - diff -= step; - vpdiff += step; - } - step >>= 1; - if ( diff >= step ) { - delta |= 2; - diff -= step; - vpdiff += step; - } - step >>= 1; - if ( diff >= step ) { - delta |= 1; - vpdiff += step; - } - - /* Step 3 - Update previous value */ - if ( sign ) - valpred -= vpdiff; - else - valpred += vpdiff; - - /* Step 4 - Clamp previous value to 16 bits */ - if ( valpred > 32767 ) - valpred = 32767; - else if ( valpred < -32768 ) - valpred = -32768; - - /* Step 5 - Assemble value, update index and step values */ - delta |= sign; - - index += indexTable[delta]; - if ( index < 0 ) index = 0; - if ( index > 88 ) index = 88; - step = stepsizeTable[index]; - - /* Step 6 - Output value */ - if ( bufferstep ) { - outputbuffer = (delta << 4) & 0xf0; - } else { - *outp++ = (delta & 0x0f) | outputbuffer; - } - bufferstep = !bufferstep; - } - - /* Output last step, if needed */ - if ( !bufferstep ) - *outp++ = outputbuffer; - - state->sample = valpred; - state->index = index; -} - - -/* static */ void S_AdpcmDecode( const char indata[], short *outdata, int len, struct adpcm_state *state ) { - signed char *inp; /* Input buffer pointer */ - int outp; /* output buffer pointer */ - int sign; /* Current adpcm sign bit */ - int delta; /* Current adpcm output value */ - int step; /* Stepsize */ - int valpred; /* Predicted value */ - int vpdiff; /* Current change to valpred */ - int index; /* Current step change index */ - int inputbuffer; /* place to keep next 4-bit value */ - int bufferstep; /* toggle between inputbuffer/input */ - - outp = 0; - inp = (signed char *)indata; - - valpred = state->sample; - index = state->index; - step = stepsizeTable[index]; - - bufferstep = 0; - inputbuffer = 0; // quiet a compiler warning - for ( ; len > 0 ; len-- ) { - - /* Step 1 - get the delta value */ - if ( bufferstep ) { - delta = inputbuffer & 0xf; - } else { - inputbuffer = *inp++; - delta = (inputbuffer >> 4) & 0xf; - } - bufferstep = !bufferstep; - - /* Step 2 - Find new index value (for later) */ - index += indexTable[delta]; - if ( index < 0 ) index = 0; - if ( index > 88 ) index = 88; - - /* Step 3 - Separate sign and magnitude */ - sign = delta & 8; - delta = delta & 7; - - /* Step 4 - Compute difference and new predicted value */ - /* - ** Computes 'vpdiff = (delta+0.5)*step/4', but see comment - ** in adpcm_coder. - */ - vpdiff = step >> 3; - if ( delta & 4 ) vpdiff += step; - if ( delta & 2 ) vpdiff += step>>1; - if ( delta & 1 ) vpdiff += step>>2; - - if ( sign ) - valpred -= vpdiff; - else - valpred += vpdiff; - - /* Step 5 - clamp output value */ - if ( valpred > 32767 ) - valpred = 32767; - else if ( valpred < -32768 ) - valpred = -32768; - - /* Step 6 - Update step value */ - step = stepsizeTable[index]; - - /* Step 7 - Output value */ - outdata[outp] = valpred; - outp++; - } - - state->sample = valpred; - state->index = index; -} - - -/* -==================== -S_AdpcmMemoryNeeded - -Returns the amount of memory (in bytes) needed to store the samples in out internal adpcm format -==================== -*/ -int S_AdpcmMemoryNeeded( const wavinfo_t *info ) { - float scale; - int scaledSampleCount; - int sampleMemory; - int blockCount; - int headerMemory; - - // determine scale to convert from input sampling rate to desired sampling rate - scale = (float)info->rate / dma.speed; - - // calc number of samples at playback sampling rate - scaledSampleCount = info->samples / scale; - - // calc memory need to store those samples using ADPCM at 4 bits per sample - sampleMemory = scaledSampleCount / 2; - - // calc number of sample blocks needed of PAINTBUFFER_SIZE - blockCount = scaledSampleCount / PAINTBUFFER_SIZE; - if( scaledSampleCount % PAINTBUFFER_SIZE ) { - blockCount++; - } - - // calc memory needed to store the block headers - headerMemory = blockCount * sizeof(adpcm_state_t); - - return sampleMemory + headerMemory; -} - - -/* -==================== -S_AdpcmGetSamples -==================== -*/ -void S_AdpcmGetSamples(sndBuffer *chunk, short *to) { - adpcm_state_t state; - byte *out; - - // get the starting state from the block header - state.index = chunk->adpcm.index; - state.sample = chunk->adpcm.sample; - - out = (byte *)chunk->sndChunk; - // get samples - S_AdpcmDecode( out, to, SND_CHUNK_SIZE_BYTE*2, &state ); -} - - -/* -==================== -S_AdpcmEncodeSound -==================== -*/ -void S_AdpcmEncodeSound( sfx_t *sfx, short *samples ) { - adpcm_state_t state; - int inOffset; - int count; - int n; - sndBuffer *newchunk, *chunk; - byte *out; - - inOffset = 0; - count = sfx->soundLength; - state.index = 0; - state.sample = samples[0]; - - chunk = NULL; - while( count ) { - n = count; - if( n > SND_CHUNK_SIZE_BYTE*2 ) { - n = SND_CHUNK_SIZE_BYTE*2; - } - - newchunk = SND_malloc(); - if (sfx->soundData == NULL) { - sfx->soundData = newchunk; - } else { - chunk->next = newchunk; - } - chunk = newchunk; - - // output the header - chunk->adpcm.index = state.index; - chunk->adpcm.sample = state.sample; - - out = (byte *)chunk->sndChunk; - - // encode the samples - S_AdpcmEncode( samples + inOffset, out, n, &state ); - - inOffset += n; - count -= n; - } -} +/*********************************************************** +Copyright 1992 by Stichting Mathematisch Centrum, Amsterdam, The +Netherlands. + + All Rights Reserved + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the names of Stichting Mathematisch +Centrum or CWI not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior permission. + +STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO +THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM 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. + +******************************************************************/ + +/* +** Intel/DVI ADPCM coder/decoder. +** +** The algorithm for this coder was taken from the IMA Compatability Project +** proceedings, Vol 2, Number 2; May 1992. +** +** Version 1.2, 18-Dec-92. +*/ + +#include "snd_local.h" + + +/* Intel ADPCM step variation table */ +static int indexTable[16] = { + -1, -1, -1, -1, 2, 4, 6, 8, + -1, -1, -1, -1, 2, 4, 6, 8, +}; + +static int stepsizeTable[89] = { + 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, + 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, + 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, + 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, + 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, + 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, + 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, + 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, + 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 +}; + + +void S_AdpcmEncode( short indata[], char outdata[], int len, struct adpcm_state *state ) { + short *inp; /* Input buffer pointer */ + signed char *outp; /* output buffer pointer */ + int val; /* Current input sample value */ + int sign; /* Current adpcm sign bit */ + int delta; /* Current adpcm output value */ + int diff; /* Difference between val and sample */ + int step; /* Stepsize */ + int valpred; /* Predicted output value */ + int vpdiff; /* Current change to valpred */ + int index; /* Current step change index */ + int outputbuffer; /* place to keep previous 4-bit value */ + int bufferstep; /* toggle between outputbuffer/output */ + + outp = (signed char *)outdata; + inp = indata; + + valpred = state->sample; + index = state->index; + step = stepsizeTable[index]; + + outputbuffer = 0; // quiet a compiler warning + bufferstep = 1; + + for ( ; len > 0 ; len-- ) { + val = *inp++; + + /* Step 1 - compute difference with previous value */ + diff = val - valpred; + sign = (diff < 0) ? 8 : 0; + if ( sign ) diff = (-diff); + + /* Step 2 - Divide and clamp */ + /* Note: + ** This code *approximately* computes: + ** delta = diff*4/step; + ** vpdiff = (delta+0.5)*step/4; + ** but in shift step bits are dropped. The net result of this is + ** that even if you have fast mul/div hardware you cannot put it to + ** good use since the fixup would be too expensive. + */ + delta = 0; + vpdiff = (step >> 3); + + if ( diff >= step ) { + delta = 4; + diff -= step; + vpdiff += step; + } + step >>= 1; + if ( diff >= step ) { + delta |= 2; + diff -= step; + vpdiff += step; + } + step >>= 1; + if ( diff >= step ) { + delta |= 1; + vpdiff += step; + } + + /* Step 3 - Update previous value */ + if ( sign ) + valpred -= vpdiff; + else + valpred += vpdiff; + + /* Step 4 - Clamp previous value to 16 bits */ + if ( valpred > 32767 ) + valpred = 32767; + else if ( valpred < -32768 ) + valpred = -32768; + + /* Step 5 - Assemble value, update index and step values */ + delta |= sign; + + index += indexTable[delta]; + if ( index < 0 ) index = 0; + if ( index > 88 ) index = 88; + step = stepsizeTable[index]; + + /* Step 6 - Output value */ + if ( bufferstep ) { + outputbuffer = (delta << 4) & 0xf0; + } else { + *outp++ = (delta & 0x0f) | outputbuffer; + } + bufferstep = !bufferstep; + } + + /* Output last step, if needed */ + if ( !bufferstep ) + *outp++ = outputbuffer; + + state->sample = valpred; + state->index = index; +} + + +/* static */ void S_AdpcmDecode( const char indata[], short *outdata, int len, struct adpcm_state *state ) { + signed char *inp; /* Input buffer pointer */ + int outp; /* output buffer pointer */ + int sign; /* Current adpcm sign bit */ + int delta; /* Current adpcm output value */ + int step; /* Stepsize */ + int valpred; /* Predicted value */ + int vpdiff; /* Current change to valpred */ + int index; /* Current step change index */ + int inputbuffer; /* place to keep next 4-bit value */ + int bufferstep; /* toggle between inputbuffer/input */ + + outp = 0; + inp = (signed char *)indata; + + valpred = state->sample; + index = state->index; + step = stepsizeTable[index]; + + bufferstep = 0; + inputbuffer = 0; // quiet a compiler warning + for ( ; len > 0 ; len-- ) { + + /* Step 1 - get the delta value */ + if ( bufferstep ) { + delta = inputbuffer & 0xf; + } else { + inputbuffer = *inp++; + delta = (inputbuffer >> 4) & 0xf; + } + bufferstep = !bufferstep; + + /* Step 2 - Find new index value (for later) */ + index += indexTable[delta]; + if ( index < 0 ) index = 0; + if ( index > 88 ) index = 88; + + /* Step 3 - Separate sign and magnitude */ + sign = delta & 8; + delta = delta & 7; + + /* Step 4 - Compute difference and new predicted value */ + /* + ** Computes 'vpdiff = (delta+0.5)*step/4', but see comment + ** in adpcm_coder. + */ + vpdiff = step >> 3; + if ( delta & 4 ) vpdiff += step; + if ( delta & 2 ) vpdiff += step>>1; + if ( delta & 1 ) vpdiff += step>>2; + + if ( sign ) + valpred -= vpdiff; + else + valpred += vpdiff; + + /* Step 5 - clamp output value */ + if ( valpred > 32767 ) + valpred = 32767; + else if ( valpred < -32768 ) + valpred = -32768; + + /* Step 6 - Update step value */ + step = stepsizeTable[index]; + + /* Step 7 - Output value */ + outdata[outp] = valpred; + outp++; + } + + state->sample = valpred; + state->index = index; +} + + +/* +==================== +S_AdpcmMemoryNeeded + +Returns the amount of memory (in bytes) needed to store the samples in out internal adpcm format +==================== +*/ +int S_AdpcmMemoryNeeded( const wavinfo_t *info ) { + float scale; + int scaledSampleCount; + int sampleMemory; + int blockCount; + int headerMemory; + + // determine scale to convert from input sampling rate to desired sampling rate + scale = (float)info->rate / dma.speed; + + // calc number of samples at playback sampling rate + scaledSampleCount = info->samples / scale; + + // calc memory need to store those samples using ADPCM at 4 bits per sample + sampleMemory = scaledSampleCount / 2; + + // calc number of sample blocks needed of PAINTBUFFER_SIZE + blockCount = scaledSampleCount / PAINTBUFFER_SIZE; + if( scaledSampleCount % PAINTBUFFER_SIZE ) { + blockCount++; + } + + // calc memory needed to store the block headers + headerMemory = blockCount * sizeof(adpcm_state_t); + + return sampleMemory + headerMemory; +} + + +/* +==================== +S_AdpcmGetSamples +==================== +*/ +void S_AdpcmGetSamples(sndBuffer *chunk, short *to) { + adpcm_state_t state; + byte *out; + + // get the starting state from the block header + state.index = chunk->adpcm.index; + state.sample = chunk->adpcm.sample; + + out = (byte *)chunk->sndChunk; + // get samples + S_AdpcmDecode( out, to, SND_CHUNK_SIZE_BYTE*2, &state ); +} + + +/* +==================== +S_AdpcmEncodeSound +==================== +*/ +void S_AdpcmEncodeSound( sfx_t *sfx, short *samples ) { + adpcm_state_t state; + int inOffset; + int count; + int n; + sndBuffer *newchunk, *chunk; + byte *out; + + inOffset = 0; + count = sfx->soundLength; + state.index = 0; + state.sample = samples[0]; + + chunk = NULL; + while( count ) { + n = count; + if( n > SND_CHUNK_SIZE_BYTE*2 ) { + n = SND_CHUNK_SIZE_BYTE*2; + } + + newchunk = SND_malloc(); + if (sfx->soundData == NULL) { + sfx->soundData = newchunk; + } else { + chunk->next = newchunk; + } + chunk = newchunk; + + // output the header + chunk->adpcm.index = state.index; + chunk->adpcm.sample = state.sample; + + out = (byte *)chunk->sndChunk; + + // encode the samples + S_AdpcmEncode( samples + inOffset, out, n, &state ); + + inOffset += n; + count -= n; + } +} diff --git a/code/client/snd_dma.c b/code/client/snd_dma.c index 6ecff18..aae87ec 100755 --- a/code/client/snd_dma.c +++ b/code/client/snd_dma.c @@ -1,1636 +1,1636 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: snd_dma.c - * - * desc: main control for any streaming sound output device - * - * $Archive: /MissionPack/code/client/snd_dma.c $ - * - *****************************************************************************/ - -#include "snd_local.h" -#include "client.h" - -void S_Play_f(void); -void S_SoundList_f(void); -void S_Music_f(void); - -void S_Update_(); -void S_StopAllSounds(void); -void S_UpdateBackgroundTrack( void ); - -static fileHandle_t s_backgroundFile; -static wavinfo_t s_backgroundInfo; -//int s_nextWavChunk; -static int s_backgroundSamples; -static char s_backgroundLoop[MAX_QPATH]; -//static char s_backgroundMusic[MAX_QPATH]; //TTimo: unused - - -// ======================================================================= -// Internal sound data & structures -// ======================================================================= - -// only begin attenuating sound volumes when outside the FULLVOLUME range -#define SOUND_FULLVOLUME 80 - -#define SOUND_ATTENUATE 0.0008f - -channel_t s_channels[MAX_CHANNELS]; -channel_t loop_channels[MAX_CHANNELS]; -int numLoopChannels; - -static int s_soundStarted; -static qboolean s_soundMuted; - -dma_t dma; - -static int listener_number; -static vec3_t listener_origin; -static vec3_t listener_axis[3]; - -int s_soundtime; // sample PAIRS -int s_paintedtime; // sample PAIRS - -// MAX_SFX may be larger than MAX_SOUNDS because -// of custom player sounds -#define MAX_SFX 4096 -sfx_t s_knownSfx[MAX_SFX]; -int s_numSfx = 0; - -#define LOOP_HASH 128 -static sfx_t *sfxHash[LOOP_HASH]; - -cvar_t *s_volume; -cvar_t *s_testsound; -cvar_t *s_khz; -cvar_t *s_show; -cvar_t *s_mixahead; -cvar_t *s_mixPreStep; -cvar_t *s_musicVolume; -cvar_t *s_separation; -cvar_t *s_doppler; - -static loopSound_t loopSounds[MAX_GENTITIES]; -static channel_t *freelist = NULL; - -int s_rawend; -portable_samplepair_t s_rawsamples[MAX_RAW_SAMPLES]; - - -// ==================================================================== -// User-setable variables -// ==================================================================== - - -void S_SoundInfo_f(void) { - Com_Printf("----- Sound Info -----\n" ); - if (!s_soundStarted) { - Com_Printf ("sound system not started\n"); - } else { - if ( s_soundMuted ) { - Com_Printf ("sound system is muted\n"); - } - - Com_Printf("%5d stereo\n", dma.channels - 1); - Com_Printf("%5d samples\n", dma.samples); - Com_Printf("%5d samplebits\n", dma.samplebits); - Com_Printf("%5d submission_chunk\n", dma.submission_chunk); - Com_Printf("%5d speed\n", dma.speed); - Com_Printf("0x%x dma buffer\n", dma.buffer); - if ( s_backgroundFile ) { - Com_Printf("Background file: %s\n", s_backgroundLoop ); - } else { - Com_Printf("No background file.\n" ); - } - - } - Com_Printf("----------------------\n" ); -} - - - -/* -================ -S_Init -================ -*/ -void S_Init( void ) { - cvar_t *cv; - qboolean r; - - Com_Printf("\n------- sound initialization -------\n"); - - s_volume = Cvar_Get ("s_volume", "0.8", CVAR_ARCHIVE); - s_musicVolume = Cvar_Get ("s_musicvolume", "0.25", CVAR_ARCHIVE); - s_separation = Cvar_Get ("s_separation", "0.5", CVAR_ARCHIVE); - s_doppler = Cvar_Get ("s_doppler", "1", CVAR_ARCHIVE); - s_khz = Cvar_Get ("s_khz", "22", CVAR_ARCHIVE); - s_mixahead = Cvar_Get ("s_mixahead", "0.2", CVAR_ARCHIVE); - - s_mixPreStep = Cvar_Get ("s_mixPreStep", "0.05", CVAR_ARCHIVE); - s_show = Cvar_Get ("s_show", "0", CVAR_CHEAT); - s_testsound = Cvar_Get ("s_testsound", "0", CVAR_CHEAT); - - cv = Cvar_Get ("s_initsound", "1", 0); - if ( !cv->integer ) { - Com_Printf ("not initializing.\n"); - Com_Printf("------------------------------------\n"); - return; - } - - Cmd_AddCommand("play", S_Play_f); - Cmd_AddCommand("music", S_Music_f); - Cmd_AddCommand("s_list", S_SoundList_f); - Cmd_AddCommand("s_info", S_SoundInfo_f); - Cmd_AddCommand("s_stop", S_StopAllSounds); - - r = SNDDMA_Init(); - Com_Printf("------------------------------------\n"); - - if ( r ) { - s_soundStarted = 1; - s_soundMuted = 1; -// s_numSfx = 0; - - Com_Memset(sfxHash, 0, sizeof(sfx_t *)*LOOP_HASH); - - s_soundtime = 0; - s_paintedtime = 0; - - S_StopAllSounds (); - - S_SoundInfo_f(); - } - -} - - -void S_ChannelFree(channel_t *v) { - v->thesfx = NULL; - *(channel_t **)v = freelist; - freelist = (channel_t*)v; -} - -channel_t* S_ChannelMalloc() { - channel_t *v; - if (freelist == NULL) { - return NULL; - } - v = freelist; - freelist = *(channel_t **)freelist; - v->allocTime = Com_Milliseconds(); - return v; -} - -void S_ChannelSetup() { - channel_t *p, *q; - - // clear all the sounds so they don't - Com_Memset( s_channels, 0, sizeof( s_channels ) ); - - p = s_channels;; - q = p + MAX_CHANNELS; - while (--q > p) { - *(channel_t **)q = q-1; - } - - *(channel_t **)q = NULL; - freelist = p + MAX_CHANNELS - 1; - Com_DPrintf("Channel memory manager started\n"); -} - -// ======================================================================= -// Shutdown sound engine -// ======================================================================= - -void S_Shutdown( void ) { - if ( !s_soundStarted ) { - return; - } - - SNDDMA_Shutdown(); - - s_soundStarted = 0; - - Cmd_RemoveCommand("play"); - Cmd_RemoveCommand("music"); - Cmd_RemoveCommand("stopsound"); - Cmd_RemoveCommand("soundlist"); - Cmd_RemoveCommand("soundinfo"); -} - - -// ======================================================================= -// Load a sound -// ======================================================================= - -/* -================ -return a hash value for the sfx name -================ -*/ -static long S_HashSFXName(const char *name) { - int i; - long hash; - char letter; - - hash = 0; - i = 0; - while (name[i] != '\0') { - letter = tolower(name[i]); - if (letter =='.') break; // don't include extension - if (letter =='\\') letter = '/'; // damn path names - hash+=(long)(letter)*(i+119); - i++; - } - hash &= (LOOP_HASH-1); - return hash; -} - -/* -================== -S_FindName - -Will allocate a new sfx if it isn't found -================== -*/ -static sfx_t *S_FindName( const char *name ) { - int i; - int hash; - - sfx_t *sfx; - - if (!name) { - Com_Error (ERR_FATAL, "S_FindName: NULL\n"); - } - if (!name[0]) { - Com_Error (ERR_FATAL, "S_FindName: empty name\n"); - } - - if (strlen(name) >= MAX_QPATH) { - Com_Error (ERR_FATAL, "Sound name too long: %s", name); - } - - hash = S_HashSFXName(name); - - sfx = sfxHash[hash]; - // see if already loaded - while (sfx) { - if (!Q_stricmp(sfx->soundName, name) ) { - return sfx; - } - sfx = sfx->next; - } - - // find a free sfx - for (i=0 ; i < s_numSfx ; i++) { - if (!s_knownSfx[i].soundName[0]) { - break; - } - } - - if (i == s_numSfx) { - if (s_numSfx == MAX_SFX) { - Com_Error (ERR_FATAL, "S_FindName: out of sfx_t"); - } - s_numSfx++; - } - - sfx = &s_knownSfx[i]; - Com_Memset (sfx, 0, sizeof(*sfx)); - strcpy (sfx->soundName, name); - - sfx->next = sfxHash[hash]; - sfxHash[hash] = sfx; - - return sfx; -} - -/* -================= -S_DefaultSound -================= -*/ -void S_DefaultSound( sfx_t *sfx ) { - - int i; - - sfx->soundLength = 512; - sfx->soundData = SND_malloc(); - sfx->soundData->next = NULL; - - - for ( i = 0 ; i < sfx->soundLength ; i++ ) { - sfx->soundData->sndChunk[i] = i; - } -} - -/* -=================== -S_DisableSounds - -Disables sounds until the next S_BeginRegistration. -This is called when the hunk is cleared and the sounds -are no longer valid. -=================== -*/ -void S_DisableSounds( void ) { - S_StopAllSounds(); - s_soundMuted = qtrue; -} - -/* -===================== -S_BeginRegistration - -===================== -*/ -void S_BeginRegistration( void ) { - s_soundMuted = qfalse; // we can play again - - if (s_numSfx == 0) { - SND_setup(); - - s_numSfx = 0; - Com_Memset( s_knownSfx, 0, sizeof( s_knownSfx ) ); - Com_Memset(sfxHash, 0, sizeof(sfx_t *)*LOOP_HASH); - - S_RegisterSound("sound/feedback/hit.wav", qfalse); // changed to a sound in baseq3 - } -} - - -/* -================== -S_RegisterSound - -Creates a default buzz sound if the file can't be loaded -================== -*/ -sfxHandle_t S_RegisterSound( const char *name, qboolean compressed ) { - sfx_t *sfx; - - compressed = qfalse; - if (!s_soundStarted) { - return 0; - } - - if ( strlen( name ) >= MAX_QPATH ) { - Com_Printf( "Sound name exceeds MAX_QPATH\n" ); - return 0; - } - - sfx = S_FindName( name ); - if ( sfx->soundData ) { - if ( sfx->defaultSound ) { - Com_Printf( S_COLOR_YELLOW "WARNING: could not find %s - using default\n", sfx->soundName ); - return 0; - } - return sfx - s_knownSfx; - } - - sfx->inMemory = qfalse; - sfx->soundCompressed = compressed; - - S_memoryLoad(sfx); - - if ( sfx->defaultSound ) { - Com_Printf( S_COLOR_YELLOW "WARNING: could not find %s - using default\n", sfx->soundName ); - return 0; - } - - return sfx - s_knownSfx; -} - -void S_memoryLoad(sfx_t *sfx) { - // load the sound file - if ( !S_LoadSound ( sfx ) ) { -// Com_Printf( S_COLOR_YELLOW "WARNING: couldn't load sound: %s\n", sfx->soundName ); - sfx->defaultSound = qtrue; - } - sfx->inMemory = qtrue; -} - -//============================================================================= - -/* -================= -S_SpatializeOrigin - -Used for spatializing s_channels -================= -*/ -void S_SpatializeOrigin (vec3_t origin, int master_vol, int *left_vol, int *right_vol) -{ - vec_t dot; - vec_t dist; - vec_t lscale, rscale, scale; - vec3_t source_vec; - vec3_t vec; - - const float dist_mult = SOUND_ATTENUATE; - - // calculate stereo seperation and distance attenuation - VectorSubtract(origin, listener_origin, source_vec); - - dist = VectorNormalize(source_vec); - dist -= SOUND_FULLVOLUME; - if (dist < 0) - dist = 0; // close enough to be at full volume - dist *= dist_mult; // different attenuation levels - - VectorRotate( source_vec, listener_axis, vec ); - - dot = -vec[1]; - - if (dma.channels == 1) - { // no attenuation = no spatialization - rscale = 1.0; - lscale = 1.0; - } - else - { - rscale = 0.5 * (1.0 + dot); - lscale = 0.5 * (1.0 - dot); - //rscale = s_separation->value + ( 1.0 - s_separation->value ) * dot; - //lscale = s_separation->value - ( 1.0 - s_separation->value ) * dot; - if ( rscale < 0 ) { - rscale = 0; - } - if ( lscale < 0 ) { - lscale = 0; - } - } - - // add in distance effect - scale = (1.0 - dist) * rscale; - *right_vol = (master_vol * scale); - if (*right_vol < 0) - *right_vol = 0; - - scale = (1.0 - dist) * lscale; - *left_vol = (master_vol * scale); - if (*left_vol < 0) - *left_vol = 0; -} - -// ======================================================================= -// Start a sound effect -// ======================================================================= - -/* -==================== -S_StartSound - -Validates the parms and ques the sound up -if pos is NULL, the sound will be dynamically sourced from the entity -Entchannel 0 will never override a playing sound -==================== -*/ -void S_StartSound(vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfxHandle ) { - channel_t *ch; - sfx_t *sfx; - int i, oldest, chosen, time; - int inplay, allowed; - - if ( !s_soundStarted || s_soundMuted ) { - return; - } - - if ( !origin && ( entityNum < 0 || entityNum > MAX_GENTITIES ) ) { - Com_Error( ERR_DROP, "S_StartSound: bad entitynum %i", entityNum ); - } - - if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { - Com_Printf( S_COLOR_YELLOW, "S_StartSound: handle %i out of range\n", sfxHandle ); - return; - } - - sfx = &s_knownSfx[ sfxHandle ]; - - if (sfx->inMemory == qfalse) { - S_memoryLoad(sfx); - } - - if ( s_show->integer == 1 ) { - Com_Printf( "%i : %s\n", s_paintedtime, sfx->soundName ); - } - - time = Com_Milliseconds(); - -// Com_Printf("playing %s\n", sfx->soundName); - // pick a channel to play on - - allowed = 4; - if (entityNum == listener_number) { - allowed = 8; - } - - ch = s_channels; - inplay = 0; - for ( i = 0; i < MAX_CHANNELS ; i++, ch++ ) { - if (ch[i].entnum == entityNum && ch[i].thesfx == sfx) { - if (time - ch[i].allocTime < 50) { -// if (Cvar_VariableValue( "cg_showmiss" )) { -// Com_Printf("double sound start\n"); -// } - return; - } - inplay++; - } - } - - if (inplay>allowed) { - return; - } - - sfx->lastTimeUsed = time; - - ch = S_ChannelMalloc(); // entityNum, entchannel); - if (!ch) { - ch = s_channels; - - oldest = sfx->lastTimeUsed; - chosen = -1; - for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) { - if (ch->entnum != listener_number && ch->entnum == entityNum && ch->allocTimeentchannel != CHAN_ANNOUNCER) { - oldest = ch->allocTime; - chosen = i; - } - } - if (chosen == -1) { - ch = s_channels; - for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) { - if (ch->entnum != listener_number && ch->allocTimeentchannel != CHAN_ANNOUNCER) { - oldest = ch->allocTime; - chosen = i; - } - } - if (chosen == -1) { - if (ch->entnum == listener_number) { - for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) { - if (ch->allocTimeallocTime; - chosen = i; - } - } - } - if (chosen == -1) { - Com_Printf("dropping sound\n"); - return; - } - } - } - ch = &s_channels[chosen]; - ch->allocTime = sfx->lastTimeUsed; - } - - if (origin) { - VectorCopy (origin, ch->origin); - ch->fixed_origin = qtrue; - } else { - ch->fixed_origin = qfalse; - } - - ch->master_vol = 127; - ch->entnum = entityNum; - ch->thesfx = sfx; - ch->startSample = START_SAMPLE_IMMEDIATE; - ch->entchannel = entchannel; - ch->leftvol = ch->master_vol; // these will get calced at next spatialize - ch->rightvol = ch->master_vol; // unless the game isn't running - ch->doppler = qfalse; -} - - -/* -================== -S_StartLocalSound -================== -*/ -void S_StartLocalSound( sfxHandle_t sfxHandle, int channelNum ) { - if ( !s_soundStarted || s_soundMuted ) { - return; - } - - if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { - Com_Printf( S_COLOR_YELLOW, "S_StartLocalSound: handle %i out of range\n", sfxHandle ); - return; - } - - S_StartSound (NULL, listener_number, channelNum, sfxHandle ); -} - - -/* -================== -S_ClearSoundBuffer - -If we are about to perform file access, clear the buffer -so sound doesn't stutter. -================== -*/ -void S_ClearSoundBuffer( void ) { - int clear; - - if (!s_soundStarted) - return; - - // stop looping sounds - Com_Memset(loopSounds, 0, MAX_GENTITIES*sizeof(loopSound_t)); - Com_Memset(loop_channels, 0, MAX_CHANNELS*sizeof(channel_t)); - numLoopChannels = 0; - - S_ChannelSetup(); - - s_rawend = 0; - - if (dma.samplebits == 8) - clear = 0x80; - else - clear = 0; - - SNDDMA_BeginPainting (); - if (dma.buffer) - // TTimo: due to a particular bug workaround in linux sound code, - // have to optionally use a custom C implementation of Com_Memset - // not affecting win32, we have #define Snd_Memset Com_Memset - // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=371 - Snd_Memset(dma.buffer, clear, dma.samples * dma.samplebits/8); - SNDDMA_Submit (); -} - -/* -================== -S_StopAllSounds -================== -*/ -void S_StopAllSounds(void) { - if ( !s_soundStarted ) { - return; - } - - // stop the background music - S_StopBackgroundTrack(); - - S_ClearSoundBuffer (); -} - -/* -============================================================== - -continuous looping sounds are added each frame - -============================================================== -*/ - -void S_StopLoopingSound(int entityNum) { - loopSounds[entityNum].active = qfalse; -// loopSounds[entityNum].sfx = 0; - loopSounds[entityNum].kill = qfalse; -} - -/* -================== -S_ClearLoopingSounds - -================== -*/ -void S_ClearLoopingSounds( qboolean killall ) { - int i; - for ( i = 0 ; i < MAX_GENTITIES ; i++) { - if (killall || loopSounds[i].kill == qtrue || (loopSounds[i].sfx && loopSounds[i].sfx->soundLength == 0)) { - loopSounds[i].kill = qfalse; - S_StopLoopingSound(i); - } - } - numLoopChannels = 0; -} - -/* -================== -S_AddLoopingSound - -Called during entity generation for a frame -Include velocity in case I get around to doing doppler... -================== -*/ -void S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfxHandle ) { - sfx_t *sfx; - - if ( !s_soundStarted || s_soundMuted ) { - return; - } - - if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { - Com_Printf( S_COLOR_YELLOW, "S_AddLoopingSound: handle %i out of range\n", sfxHandle ); - return; - } - - sfx = &s_knownSfx[ sfxHandle ]; - - if (sfx->inMemory == qfalse) { - S_memoryLoad(sfx); - } - - if ( !sfx->soundLength ) { - Com_Error( ERR_DROP, "%s has length 0", sfx->soundName ); - } - - VectorCopy( origin, loopSounds[entityNum].origin ); - VectorCopy( velocity, loopSounds[entityNum].velocity ); - loopSounds[entityNum].active = qtrue; - loopSounds[entityNum].kill = qtrue; - loopSounds[entityNum].doppler = qfalse; - loopSounds[entityNum].oldDopplerScale = 1.0; - loopSounds[entityNum].dopplerScale = 1.0; - loopSounds[entityNum].sfx = sfx; - - if (s_doppler->integer && VectorLengthSquared(velocity)>0.0) { - vec3_t out; - float lena, lenb; - - loopSounds[entityNum].doppler = qtrue; - lena = DistanceSquared(loopSounds[listener_number].origin, loopSounds[entityNum].origin); - VectorAdd(loopSounds[entityNum].origin, loopSounds[entityNum].velocity, out); - lenb = DistanceSquared(loopSounds[listener_number].origin, out); - if ((loopSounds[entityNum].framenum+1) != cls.framecount) { - loopSounds[entityNum].oldDopplerScale = 1.0; - } else { - loopSounds[entityNum].oldDopplerScale = loopSounds[entityNum].dopplerScale; - } - loopSounds[entityNum].dopplerScale = lenb/(lena*100); - if (loopSounds[entityNum].dopplerScale<=1.0) { - loopSounds[entityNum].doppler = qfalse; // don't bother doing the math - } - } - - loopSounds[entityNum].framenum = cls.framecount; -} - -/* -================== -S_AddLoopingSound - -Called during entity generation for a frame -Include velocity in case I get around to doing doppler... -================== -*/ -void S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfxHandle ) { - sfx_t *sfx; - - if ( !s_soundStarted || s_soundMuted ) { - return; - } - - if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { - Com_Printf( S_COLOR_YELLOW, "S_AddRealLoopingSound: handle %i out of range\n", sfxHandle ); - return; - } - - sfx = &s_knownSfx[ sfxHandle ]; - - if (sfx->inMemory == qfalse) { - S_memoryLoad(sfx); - } - - if ( !sfx->soundLength ) { - Com_Error( ERR_DROP, "%s has length 0", sfx->soundName ); - } - VectorCopy( origin, loopSounds[entityNum].origin ); - VectorCopy( velocity, loopSounds[entityNum].velocity ); - loopSounds[entityNum].sfx = sfx; - loopSounds[entityNum].active = qtrue; - loopSounds[entityNum].kill = qfalse; - loopSounds[entityNum].doppler = qfalse; -} - - - -/* -================== -S_AddLoopSounds - -Spatialize all of the looping sounds. -All sounds are on the same cycle, so any duplicates can just -sum up the channel multipliers. -================== -*/ -void S_AddLoopSounds (void) { - int i, j, time; - int left_total, right_total, left, right; - channel_t *ch; - loopSound_t *loop, *loop2; - static int loopFrame; - - - numLoopChannels = 0; - - time = Com_Milliseconds(); - - loopFrame++; - for ( i = 0 ; i < MAX_GENTITIES ; i++) { - loop = &loopSounds[i]; - if ( !loop->active || loop->mergeFrame == loopFrame ) { - continue; // already merged into an earlier sound - } - - if (loop->kill) { - S_SpatializeOrigin( loop->origin, 127, &left_total, &right_total); // 3d - } else { - S_SpatializeOrigin( loop->origin, 90, &left_total, &right_total); // sphere - } - - loop->sfx->lastTimeUsed = time; - - for (j=(i+1); j< MAX_GENTITIES ; j++) { - loop2 = &loopSounds[j]; - if ( !loop2->active || loop2->doppler || loop2->sfx != loop->sfx) { - continue; - } - loop2->mergeFrame = loopFrame; - - if (loop2->kill) { - S_SpatializeOrigin( loop2->origin, 127, &left, &right); // 3d - } else { - S_SpatializeOrigin( loop2->origin, 90, &left, &right); // sphere - } - - loop2->sfx->lastTimeUsed = time; - left_total += left; - right_total += right; - } - if (left_total == 0 && right_total == 0) { - continue; // not audible - } - - // allocate a channel - ch = &loop_channels[numLoopChannels]; - - if (left_total > 255) { - left_total = 255; - } - if (right_total > 255) { - right_total = 255; - } - - ch->master_vol = 127; - ch->leftvol = left_total; - ch->rightvol = right_total; - ch->thesfx = loop->sfx; - ch->doppler = loop->doppler; - ch->dopplerScale = loop->dopplerScale; - ch->oldDopplerScale = loop->oldDopplerScale; - numLoopChannels++; - if (numLoopChannels == MAX_CHANNELS) { - return; - } - } -} - -//============================================================================= - -/* -================= -S_ByteSwapRawSamples - -If raw data has been loaded in little endien binary form, this must be done. -If raw data was calculated, as with ADPCM, this should not be called. -================= -*/ -void S_ByteSwapRawSamples( int samples, int width, int s_channels, const byte *data ) { - int i; - - if ( width != 2 ) { - return; - } - if ( LittleShort( 256 ) == 256 ) { - return; - } - - if ( s_channels == 2 ) { - samples <<= 1; - } - for ( i = 0 ; i < samples ; i++ ) { - ((short *)data)[i] = LittleShort( ((short *)data)[i] ); - } -} - -portable_samplepair_t *S_GetRawSamplePointer() { - return s_rawsamples; -} - -/* -============ -S_RawSamples - -Music streaming -============ -*/ -void S_RawSamples( int samples, int rate, int width, int s_channels, const byte *data, float volume ) { - int i; - int src, dst; - float scale; - int intVolume; - - if ( !s_soundStarted || s_soundMuted ) { - return; - } - - intVolume = 256 * volume; - - if ( s_rawend < s_soundtime ) { - Com_DPrintf( "S_RawSamples: resetting minimum: %i < %i\n", s_rawend, s_soundtime ); - s_rawend = s_soundtime; - } - - scale = (float)rate / dma.speed; - -//Com_Printf ("%i < %i < %i\n", s_soundtime, s_paintedtime, s_rawend); - if (s_channels == 2 && width == 2) - { - if (scale == 1.0) - { // optimized case - for (i=0 ; i= samples) - break; - dst = s_rawend&(MAX_RAW_SAMPLES-1); - s_rawend++; - s_rawsamples[dst].left = ((short *)data)[src*2] * intVolume; - s_rawsamples[dst].right = ((short *)data)[src*2+1] * intVolume; - } - } - } - else if (s_channels == 1 && width == 2) - { - for (i=0 ; ; i++) - { - src = i*scale; - if (src >= samples) - break; - dst = s_rawend&(MAX_RAW_SAMPLES-1); - s_rawend++; - s_rawsamples[dst].left = ((short *)data)[src] * intVolume; - s_rawsamples[dst].right = ((short *)data)[src] * intVolume; - } - } - else if (s_channels == 2 && width == 1) - { - intVolume *= 256; - - for (i=0 ; ; i++) - { - src = i*scale; - if (src >= samples) - break; - dst = s_rawend&(MAX_RAW_SAMPLES-1); - s_rawend++; - s_rawsamples[dst].left = ((char *)data)[src*2] * intVolume; - s_rawsamples[dst].right = ((char *)data)[src*2+1] * intVolume; - } - } - else if (s_channels == 1 && width == 1) - { - intVolume *= 256; - - for (i=0 ; ; i++) - { - src = i*scale; - if (src >= samples) - break; - dst = s_rawend&(MAX_RAW_SAMPLES-1); - s_rawend++; - s_rawsamples[dst].left = (((byte *)data)[src]-128) * intVolume; - s_rawsamples[dst].right = (((byte *)data)[src]-128) * intVolume; - } - } - - if ( s_rawend > s_soundtime + MAX_RAW_SAMPLES ) { - Com_DPrintf( "S_RawSamples: overflowed %i > %i\n", s_rawend, s_soundtime ); - } -} - -//============================================================================= - -/* -===================== -S_UpdateEntityPosition - -let the sound system know where an entity currently is -====================== -*/ -void S_UpdateEntityPosition( int entityNum, const vec3_t origin ) { - if ( entityNum < 0 || entityNum > MAX_GENTITIES ) { - Com_Error( ERR_DROP, "S_UpdateEntityPosition: bad entitynum %i", entityNum ); - } - VectorCopy( origin, loopSounds[entityNum].origin ); -} - - -/* -============ -S_Respatialize - -Change the volumes of all the playing sounds for changes in their positions -============ -*/ -void S_Respatialize( int entityNum, const vec3_t head, vec3_t axis[3], int inwater ) { - int i; - channel_t *ch; - vec3_t origin; - - if ( !s_soundStarted || s_soundMuted ) { - return; - } - - listener_number = entityNum; - VectorCopy(head, listener_origin); - VectorCopy(axis[0], listener_axis[0]); - VectorCopy(axis[1], listener_axis[1]); - VectorCopy(axis[2], listener_axis[2]); - - // update spatialization for dynamic sounds - ch = s_channels; - for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) { - if ( !ch->thesfx ) { - continue; - } - // anything coming from the view entity will always be full volume - if (ch->entnum == listener_number) { - ch->leftvol = ch->master_vol; - ch->rightvol = ch->master_vol; - } else { - if (ch->fixed_origin) { - VectorCopy( ch->origin, origin ); - } else { - VectorCopy( loopSounds[ ch->entnum ].origin, origin ); - } - - S_SpatializeOrigin (origin, ch->master_vol, &ch->leftvol, &ch->rightvol); - } - } - - // add loopsounds - S_AddLoopSounds (); -} - - -/* -======================== -S_ScanChannelStarts - -Returns qtrue if any new sounds were started since the last mix -======================== -*/ -qboolean S_ScanChannelStarts( void ) { - channel_t *ch; - int i; - qboolean newSamples; - - newSamples = qfalse; - ch = s_channels; - - for (i=0; ithesfx ) { - continue; - } - // if this channel was just started this frame, - // set the sample count to it begins mixing - // into the very first sample - if ( ch->startSample == START_SAMPLE_IMMEDIATE ) { - ch->startSample = s_paintedtime; - newSamples = qtrue; - continue; - } - - // if it is completely finished by now, clear it - if ( ch->startSample + (ch->thesfx->soundLength) <= s_paintedtime ) { - S_ChannelFree(ch); - } - } - - return newSamples; -} - -/* -============ -S_Update - -Called once each time through the main loop -============ -*/ -void S_Update( void ) { - int i; - int total; - channel_t *ch; - - if ( !s_soundStarted || s_soundMuted ) { - Com_DPrintf ("not started or muted\n"); - return; - } - - // - // debugging output - // - if ( s_show->integer == 2 ) { - total = 0; - ch = s_channels; - for (i=0 ; ithesfx && (ch->leftvol || ch->rightvol) ) { - Com_Printf ("%f %f %s\n", ch->leftvol, ch->rightvol, ch->thesfx->soundName); - total++; - } - } - - Com_Printf ("----(%i)---- painted: %i\n", total, s_paintedtime); - } - - // add raw data from streamed samples - S_UpdateBackgroundTrack(); - - // mix some sound - S_Update_(); -} - -void S_GetSoundtime(void) -{ - int samplepos; - static int buffers; - static int oldsamplepos; - int fullsamples; - - fullsamples = dma.samples / dma.channels; - - // it is possible to miscount buffers if it has wrapped twice between - // calls to S_Update. Oh well. - samplepos = SNDDMA_GetDMAPos(); - if (samplepos < oldsamplepos) - { - buffers++; // buffer wrapped - - if (s_paintedtime > 0x40000000) - { // time to chop things off to avoid 32 bit limits - buffers = 0; - s_paintedtime = fullsamples; - S_StopAllSounds (); - } - } - oldsamplepos = samplepos; - - s_soundtime = buffers*fullsamples + samplepos/dma.channels; - -#if 0 -// check to make sure that we haven't overshot - if (s_paintedtime < s_soundtime) - { - Com_DPrintf ("S_Update_ : overflow\n"); - s_paintedtime = s_soundtime; - } -#endif - - if ( dma.submission_chunk < 256 ) { - s_paintedtime = s_soundtime + s_mixPreStep->value * dma.speed; - } else { - s_paintedtime = s_soundtime + dma.submission_chunk; - } -} - - -void S_Update_(void) { - unsigned endtime; - int samps; - static float lastTime = 0.0f; - float ma, op; - float thisTime, sane; - static int ot = -1; - - if ( !s_soundStarted || s_soundMuted ) { - return; - } - - thisTime = Com_Milliseconds(); - - // Updates s_soundtime - S_GetSoundtime(); - - if (s_soundtime == ot) { - return; - } - ot = s_soundtime; - - // clear any sound effects that end before the current time, - // and start any new sounds - S_ScanChannelStarts(); - - sane = thisTime - lastTime; - if (sane<11) { - sane = 11; // 85hz - } - - ma = s_mixahead->value * dma.speed; - op = s_mixPreStep->value + sane*dma.speed*0.01; - - if (op < ma) { - ma = op; - } - - // mix ahead of current position - endtime = s_soundtime + ma; - - // mix to an even submission block size - endtime = (endtime + dma.submission_chunk-1) - & ~(dma.submission_chunk-1); - - // never mix more than the complete buffer - samps = dma.samples >> (dma.channels-1); - if (endtime - s_soundtime > samps) - endtime = s_soundtime + samps; - - - - SNDDMA_BeginPainting (); - - S_PaintChannels (endtime); - - SNDDMA_Submit (); - - lastTime = thisTime; -} - -/* -=============================================================================== - -console functions - -=============================================================================== -*/ - -void S_Play_f( void ) { - int i; - sfxHandle_t h; - char name[256]; - - i = 1; - while ( i [loopfile]\n"); - return; - } - -} - -void S_SoundList_f( void ) { - int i; - sfx_t *sfx; - int size, total; - char type[4][16]; - char mem[2][16]; - - strcpy(type[0], "16bit"); - strcpy(type[1], "adpcm"); - strcpy(type[2], "daub4"); - strcpy(type[3], "mulaw"); - strcpy(mem[0], "paged out"); - strcpy(mem[1], "resident "); - total = 0; - for (sfx=s_knownSfx, i=0 ; isoundLength; - total += size; - Com_Printf("%6i[%s] : %s[%s]\n", size, type[sfx->soundCompressionMethod], sfx->soundName, mem[sfx->inMemory] ); - } - Com_Printf ("Total resident: %i\n", total); - S_DisplayFreeMemory(); -} - - -/* -=============================================================================== - -background music functions - -=============================================================================== -*/ - -int FGetLittleLong( fileHandle_t f ) { - int v; - - FS_Read( &v, sizeof(v), f ); - - return LittleLong( v); -} - -int FGetLittleShort( fileHandle_t f ) { - short v; - - FS_Read( &v, sizeof(v), f ); - - return LittleShort( v); -} - -// returns the length of the data in the chunk, or 0 if not found -int S_FindWavChunk( fileHandle_t f, char *chunk ) { - char name[5]; - int len; - int r; - - name[4] = 0; - len = 0; - r = FS_Read( name, 4, f ); - if ( r != 4 ) { - return 0; - } - len = FGetLittleLong( f ); - if ( len < 0 || len > 0xfffffff ) { - len = 0; - return 0; - } - len = (len + 1 ) & ~1; // pad to word boundary -// s_nextWavChunk += len + 8; - - if ( strcmp( name, chunk ) ) { - return 0; - } - - return len; -} - -/* -====================== -S_StopBackgroundTrack -====================== -*/ -void S_StopBackgroundTrack( void ) { - if ( !s_backgroundFile ) { - return; - } - Sys_EndStreamedFile( s_backgroundFile ); - FS_FCloseFile( s_backgroundFile ); - s_backgroundFile = 0; - s_rawend = 0; -} - -/* -====================== -S_StartBackgroundTrack -====================== -*/ -void S_StartBackgroundTrack( const char *intro, const char *loop ){ - int len; - char dump[16]; - char name[MAX_QPATH]; - - if ( !intro ) { - intro = ""; - } - if ( !loop || !loop[0] ) { - loop = intro; - } - Com_DPrintf( "S_StartBackgroundTrack( %s, %s )\n", intro, loop ); - - Q_strncpyz( name, intro, sizeof( name ) - 4 ); - COM_DefaultExtension( name, sizeof( name ), ".wav" ); - - if ( !intro[0] ) { - return; - } - - Q_strncpyz( s_backgroundLoop, loop, sizeof( s_backgroundLoop ) ); - - // close the background track, but DON'T reset s_rawend - // if restarting the same back ground track - if ( s_backgroundFile ) { - Sys_EndStreamedFile( s_backgroundFile ); - FS_FCloseFile( s_backgroundFile ); - s_backgroundFile = 0; - } - - // - // open up a wav file and get all the info - // - FS_FOpenFileRead( name, &s_backgroundFile, qtrue ); - if ( !s_backgroundFile ) { - Com_Printf( S_COLOR_YELLOW "WARNING: couldn't open music file %s\n", name ); - return; - } - - // skip the riff wav header - - FS_Read(dump, 12, s_backgroundFile); - - if ( !S_FindWavChunk( s_backgroundFile, "fmt " ) ) { - Com_Printf( "No fmt chunk in %s\n", name ); - FS_FCloseFile( s_backgroundFile ); - s_backgroundFile = 0; - return; - } - - // save name for soundinfo - s_backgroundInfo.format = FGetLittleShort( s_backgroundFile ); - s_backgroundInfo.channels = FGetLittleShort( s_backgroundFile ); - s_backgroundInfo.rate = FGetLittleLong( s_backgroundFile ); - FGetLittleLong( s_backgroundFile ); - FGetLittleShort( s_backgroundFile ); - s_backgroundInfo.width = FGetLittleShort( s_backgroundFile ) / 8; - - if ( s_backgroundInfo.format != WAV_FORMAT_PCM ) { - FS_FCloseFile( s_backgroundFile ); - s_backgroundFile = 0; - Com_Printf("Not a microsoft PCM format wav: %s\n", name); - return; - } - - if ( s_backgroundInfo.channels != 2 || s_backgroundInfo.rate != 22050 ) { - Com_Printf(S_COLOR_YELLOW "WARNING: music file %s is not 22k stereo\n", name ); - } - - if ( ( len = S_FindWavChunk( s_backgroundFile, "data" ) ) == 0 ) { - FS_FCloseFile( s_backgroundFile ); - s_backgroundFile = 0; - Com_Printf("No data chunk in %s\n", name); - return; - } - - s_backgroundInfo.samples = len / (s_backgroundInfo.width * s_backgroundInfo.channels); - - s_backgroundSamples = s_backgroundInfo.samples; - - // - // start the background streaming - // - Sys_BeginStreamedFile( s_backgroundFile, 0x10000 ); -} - -/* -====================== -S_UpdateBackgroundTrack -====================== -*/ -void S_UpdateBackgroundTrack( void ) { - int bufferSamples; - int fileSamples; - byte raw[30000]; // just enough to fit in a mac stack frame - int fileBytes; - int r; - static float musicVolume = 0.5f; - - if ( !s_backgroundFile ) { - return; - } - - // graeme see if this is OK - musicVolume = (musicVolume + (s_musicVolume->value * 2))/4.0f; - - // don't bother playing anything if musicvolume is 0 - if ( musicVolume <= 0 ) { - return; - } - - // see how many samples should be copied into the raw buffer - if ( s_rawend < s_soundtime ) { - s_rawend = s_soundtime; - } - - while ( s_rawend < s_soundtime + MAX_RAW_SAMPLES ) { - bufferSamples = MAX_RAW_SAMPLES - (s_rawend - s_soundtime); - - // decide how much data needs to be read from the file - fileSamples = bufferSamples * s_backgroundInfo.rate / dma.speed; - - // don't try and read past the end of the file - if ( fileSamples > s_backgroundSamples ) { - fileSamples = s_backgroundSamples; - } - - // our max buffer size - fileBytes = fileSamples * (s_backgroundInfo.width * s_backgroundInfo.channels); - if ( fileBytes > sizeof(raw) ) { - fileBytes = sizeof(raw); - fileSamples = fileBytes / (s_backgroundInfo.width * s_backgroundInfo.channels); - } - - r = Sys_StreamedRead( raw, 1, fileBytes, s_backgroundFile ); - if ( r != fileBytes ) { - Com_Printf("StreamedRead failure on music track\n"); - S_StopBackgroundTrack(); - return; - } - - // byte swap if needed - S_ByteSwapRawSamples( fileSamples, s_backgroundInfo.width, s_backgroundInfo.channels, raw ); - - // add to raw buffer - S_RawSamples( fileSamples, s_backgroundInfo.rate, - s_backgroundInfo.width, s_backgroundInfo.channels, raw, musicVolume ); - - s_backgroundSamples -= fileSamples; - if ( !s_backgroundSamples ) { - // loop - if (s_backgroundLoop[0]) { - Sys_EndStreamedFile( s_backgroundFile ); - FS_FCloseFile( s_backgroundFile ); - s_backgroundFile = 0; - S_StartBackgroundTrack( s_backgroundLoop, s_backgroundLoop ); - if ( !s_backgroundFile ) { - return; // loop failed to restart - } - } else { - s_backgroundFile = 0; - return; - } - } - } -} - - -/* -====================== -S_FreeOldestSound -====================== -*/ - -void S_FreeOldestSound() { - int i, oldest, used; - sfx_t *sfx; - sndBuffer *buffer, *nbuffer; - - oldest = Com_Milliseconds(); - used = 0; - - for (i=1 ; i < s_numSfx ; i++) { - sfx = &s_knownSfx[i]; - if (sfx->inMemory && sfx->lastTimeUsedlastTimeUsed; - } - } - - sfx = &s_knownSfx[used]; - - Com_DPrintf("S_FreeOldestSound: freeing sound %s\n", sfx->soundName); - - buffer = sfx->soundData; - while(buffer != NULL) { - nbuffer = buffer->next; - SND_free(buffer); - buffer = nbuffer; - } - sfx->inMemory = qfalse; - sfx->soundData = NULL; -} +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: snd_dma.c + * + * desc: main control for any streaming sound output device + * + * $Archive: /MissionPack/code/client/snd_dma.c $ + * + *****************************************************************************/ + +#include "snd_local.h" +#include "client.h" + +void S_Play_f(void); +void S_SoundList_f(void); +void S_Music_f(void); + +void S_Update_(); +void S_StopAllSounds(void); +void S_UpdateBackgroundTrack( void ); + +static fileHandle_t s_backgroundFile; +static wavinfo_t s_backgroundInfo; +//int s_nextWavChunk; +static int s_backgroundSamples; +static char s_backgroundLoop[MAX_QPATH]; +//static char s_backgroundMusic[MAX_QPATH]; //TTimo: unused + + +// ======================================================================= +// Internal sound data & structures +// ======================================================================= + +// only begin attenuating sound volumes when outside the FULLVOLUME range +#define SOUND_FULLVOLUME 80 + +#define SOUND_ATTENUATE 0.0008f + +channel_t s_channels[MAX_CHANNELS]; +channel_t loop_channels[MAX_CHANNELS]; +int numLoopChannels; + +static int s_soundStarted; +static qboolean s_soundMuted; + +dma_t dma; + +static int listener_number; +static vec3_t listener_origin; +static vec3_t listener_axis[3]; + +int s_soundtime; // sample PAIRS +int s_paintedtime; // sample PAIRS + +// MAX_SFX may be larger than MAX_SOUNDS because +// of custom player sounds +#define MAX_SFX 4096 +sfx_t s_knownSfx[MAX_SFX]; +int s_numSfx = 0; + +#define LOOP_HASH 128 +static sfx_t *sfxHash[LOOP_HASH]; + +cvar_t *s_volume; +cvar_t *s_testsound; +cvar_t *s_khz; +cvar_t *s_show; +cvar_t *s_mixahead; +cvar_t *s_mixPreStep; +cvar_t *s_musicVolume; +cvar_t *s_separation; +cvar_t *s_doppler; + +static loopSound_t loopSounds[MAX_GENTITIES]; +static channel_t *freelist = NULL; + +int s_rawend; +portable_samplepair_t s_rawsamples[MAX_RAW_SAMPLES]; + + +// ==================================================================== +// User-setable variables +// ==================================================================== + + +void S_SoundInfo_f(void) { + Com_Printf("----- Sound Info -----\n" ); + if (!s_soundStarted) { + Com_Printf ("sound system not started\n"); + } else { + if ( s_soundMuted ) { + Com_Printf ("sound system is muted\n"); + } + + Com_Printf("%5d stereo\n", dma.channels - 1); + Com_Printf("%5d samples\n", dma.samples); + Com_Printf("%5d samplebits\n", dma.samplebits); + Com_Printf("%5d submission_chunk\n", dma.submission_chunk); + Com_Printf("%5d speed\n", dma.speed); + Com_Printf("0x%x dma buffer\n", dma.buffer); + if ( s_backgroundFile ) { + Com_Printf("Background file: %s\n", s_backgroundLoop ); + } else { + Com_Printf("No background file.\n" ); + } + + } + Com_Printf("----------------------\n" ); +} + + + +/* +================ +S_Init +================ +*/ +void S_Init( void ) { + cvar_t *cv; + qboolean r; + + Com_Printf("\n------- sound initialization -------\n"); + + s_volume = Cvar_Get ("s_volume", "0.8", CVAR_ARCHIVE); + s_musicVolume = Cvar_Get ("s_musicvolume", "0.25", CVAR_ARCHIVE); + s_separation = Cvar_Get ("s_separation", "0.5", CVAR_ARCHIVE); + s_doppler = Cvar_Get ("s_doppler", "1", CVAR_ARCHIVE); + s_khz = Cvar_Get ("s_khz", "22", CVAR_ARCHIVE); + s_mixahead = Cvar_Get ("s_mixahead", "0.2", CVAR_ARCHIVE); + + s_mixPreStep = Cvar_Get ("s_mixPreStep", "0.05", CVAR_ARCHIVE); + s_show = Cvar_Get ("s_show", "0", CVAR_CHEAT); + s_testsound = Cvar_Get ("s_testsound", "0", CVAR_CHEAT); + + cv = Cvar_Get ("s_initsound", "1", 0); + if ( !cv->integer ) { + Com_Printf ("not initializing.\n"); + Com_Printf("------------------------------------\n"); + return; + } + + Cmd_AddCommand("play", S_Play_f); + Cmd_AddCommand("music", S_Music_f); + Cmd_AddCommand("s_list", S_SoundList_f); + Cmd_AddCommand("s_info", S_SoundInfo_f); + Cmd_AddCommand("s_stop", S_StopAllSounds); + + r = SNDDMA_Init(); + Com_Printf("------------------------------------\n"); + + if ( r ) { + s_soundStarted = 1; + s_soundMuted = 1; +// s_numSfx = 0; + + Com_Memset(sfxHash, 0, sizeof(sfx_t *)*LOOP_HASH); + + s_soundtime = 0; + s_paintedtime = 0; + + S_StopAllSounds (); + + S_SoundInfo_f(); + } + +} + + +void S_ChannelFree(channel_t *v) { + v->thesfx = NULL; + *(channel_t **)v = freelist; + freelist = (channel_t*)v; +} + +channel_t* S_ChannelMalloc() { + channel_t *v; + if (freelist == NULL) { + return NULL; + } + v = freelist; + freelist = *(channel_t **)freelist; + v->allocTime = Com_Milliseconds(); + return v; +} + +void S_ChannelSetup() { + channel_t *p, *q; + + // clear all the sounds so they don't + Com_Memset( s_channels, 0, sizeof( s_channels ) ); + + p = s_channels;; + q = p + MAX_CHANNELS; + while (--q > p) { + *(channel_t **)q = q-1; + } + + *(channel_t **)q = NULL; + freelist = p + MAX_CHANNELS - 1; + Com_DPrintf("Channel memory manager started\n"); +} + +// ======================================================================= +// Shutdown sound engine +// ======================================================================= + +void S_Shutdown( void ) { + if ( !s_soundStarted ) { + return; + } + + SNDDMA_Shutdown(); + + s_soundStarted = 0; + + Cmd_RemoveCommand("play"); + Cmd_RemoveCommand("music"); + Cmd_RemoveCommand("stopsound"); + Cmd_RemoveCommand("soundlist"); + Cmd_RemoveCommand("soundinfo"); +} + + +// ======================================================================= +// Load a sound +// ======================================================================= + +/* +================ +return a hash value for the sfx name +================ +*/ +static long S_HashSFXName(const char *name) { + int i; + long hash; + char letter; + + hash = 0; + i = 0; + while (name[i] != '\0') { + letter = tolower(name[i]); + if (letter =='.') break; // don't include extension + if (letter =='\\') letter = '/'; // damn path names + hash+=(long)(letter)*(i+119); + i++; + } + hash &= (LOOP_HASH-1); + return hash; +} + +/* +================== +S_FindName + +Will allocate a new sfx if it isn't found +================== +*/ +static sfx_t *S_FindName( const char *name ) { + int i; + int hash; + + sfx_t *sfx; + + if (!name) { + Com_Error (ERR_FATAL, "S_FindName: NULL\n"); + } + if (!name[0]) { + Com_Error (ERR_FATAL, "S_FindName: empty name\n"); + } + + if (strlen(name) >= MAX_QPATH) { + Com_Error (ERR_FATAL, "Sound name too long: %s", name); + } + + hash = S_HashSFXName(name); + + sfx = sfxHash[hash]; + // see if already loaded + while (sfx) { + if (!Q_stricmp(sfx->soundName, name) ) { + return sfx; + } + sfx = sfx->next; + } + + // find a free sfx + for (i=0 ; i < s_numSfx ; i++) { + if (!s_knownSfx[i].soundName[0]) { + break; + } + } + + if (i == s_numSfx) { + if (s_numSfx == MAX_SFX) { + Com_Error (ERR_FATAL, "S_FindName: out of sfx_t"); + } + s_numSfx++; + } + + sfx = &s_knownSfx[i]; + Com_Memset (sfx, 0, sizeof(*sfx)); + strcpy (sfx->soundName, name); + + sfx->next = sfxHash[hash]; + sfxHash[hash] = sfx; + + return sfx; +} + +/* +================= +S_DefaultSound +================= +*/ +void S_DefaultSound( sfx_t *sfx ) { + + int i; + + sfx->soundLength = 512; + sfx->soundData = SND_malloc(); + sfx->soundData->next = NULL; + + + for ( i = 0 ; i < sfx->soundLength ; i++ ) { + sfx->soundData->sndChunk[i] = i; + } +} + +/* +=================== +S_DisableSounds + +Disables sounds until the next S_BeginRegistration. +This is called when the hunk is cleared and the sounds +are no longer valid. +=================== +*/ +void S_DisableSounds( void ) { + S_StopAllSounds(); + s_soundMuted = qtrue; +} + +/* +===================== +S_BeginRegistration + +===================== +*/ +void S_BeginRegistration( void ) { + s_soundMuted = qfalse; // we can play again + + if (s_numSfx == 0) { + SND_setup(); + + s_numSfx = 0; + Com_Memset( s_knownSfx, 0, sizeof( s_knownSfx ) ); + Com_Memset(sfxHash, 0, sizeof(sfx_t *)*LOOP_HASH); + + S_RegisterSound("sound/feedback/hit.wav", qfalse); // changed to a sound in baseq3 + } +} + + +/* +================== +S_RegisterSound + +Creates a default buzz sound if the file can't be loaded +================== +*/ +sfxHandle_t S_RegisterSound( const char *name, qboolean compressed ) { + sfx_t *sfx; + + compressed = qfalse; + if (!s_soundStarted) { + return 0; + } + + if ( strlen( name ) >= MAX_QPATH ) { + Com_Printf( "Sound name exceeds MAX_QPATH\n" ); + return 0; + } + + sfx = S_FindName( name ); + if ( sfx->soundData ) { + if ( sfx->defaultSound ) { + Com_Printf( S_COLOR_YELLOW "WARNING: could not find %s - using default\n", sfx->soundName ); + return 0; + } + return sfx - s_knownSfx; + } + + sfx->inMemory = qfalse; + sfx->soundCompressed = compressed; + + S_memoryLoad(sfx); + + if ( sfx->defaultSound ) { + Com_Printf( S_COLOR_YELLOW "WARNING: could not find %s - using default\n", sfx->soundName ); + return 0; + } + + return sfx - s_knownSfx; +} + +void S_memoryLoad(sfx_t *sfx) { + // load the sound file + if ( !S_LoadSound ( sfx ) ) { +// Com_Printf( S_COLOR_YELLOW "WARNING: couldn't load sound: %s\n", sfx->soundName ); + sfx->defaultSound = qtrue; + } + sfx->inMemory = qtrue; +} + +//============================================================================= + +/* +================= +S_SpatializeOrigin + +Used for spatializing s_channels +================= +*/ +void S_SpatializeOrigin (vec3_t origin, int master_vol, int *left_vol, int *right_vol) +{ + vec_t dot; + vec_t dist; + vec_t lscale, rscale, scale; + vec3_t source_vec; + vec3_t vec; + + const float dist_mult = SOUND_ATTENUATE; + + // calculate stereo seperation and distance attenuation + VectorSubtract(origin, listener_origin, source_vec); + + dist = VectorNormalize(source_vec); + dist -= SOUND_FULLVOLUME; + if (dist < 0) + dist = 0; // close enough to be at full volume + dist *= dist_mult; // different attenuation levels + + VectorRotate( source_vec, listener_axis, vec ); + + dot = -vec[1]; + + if (dma.channels == 1) + { // no attenuation = no spatialization + rscale = 1.0; + lscale = 1.0; + } + else + { + rscale = 0.5 * (1.0 + dot); + lscale = 0.5 * (1.0 - dot); + //rscale = s_separation->value + ( 1.0 - s_separation->value ) * dot; + //lscale = s_separation->value - ( 1.0 - s_separation->value ) * dot; + if ( rscale < 0 ) { + rscale = 0; + } + if ( lscale < 0 ) { + lscale = 0; + } + } + + // add in distance effect + scale = (1.0 - dist) * rscale; + *right_vol = (master_vol * scale); + if (*right_vol < 0) + *right_vol = 0; + + scale = (1.0 - dist) * lscale; + *left_vol = (master_vol * scale); + if (*left_vol < 0) + *left_vol = 0; +} + +// ======================================================================= +// Start a sound effect +// ======================================================================= + +/* +==================== +S_StartSound + +Validates the parms and ques the sound up +if pos is NULL, the sound will be dynamically sourced from the entity +Entchannel 0 will never override a playing sound +==================== +*/ +void S_StartSound(vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfxHandle ) { + channel_t *ch; + sfx_t *sfx; + int i, oldest, chosen, time; + int inplay, allowed; + + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + if ( !origin && ( entityNum < 0 || entityNum > MAX_GENTITIES ) ) { + Com_Error( ERR_DROP, "S_StartSound: bad entitynum %i", entityNum ); + } + + if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { + Com_Printf( S_COLOR_YELLOW, "S_StartSound: handle %i out of range\n", sfxHandle ); + return; + } + + sfx = &s_knownSfx[ sfxHandle ]; + + if (sfx->inMemory == qfalse) { + S_memoryLoad(sfx); + } + + if ( s_show->integer == 1 ) { + Com_Printf( "%i : %s\n", s_paintedtime, sfx->soundName ); + } + + time = Com_Milliseconds(); + +// Com_Printf("playing %s\n", sfx->soundName); + // pick a channel to play on + + allowed = 4; + if (entityNum == listener_number) { + allowed = 8; + } + + ch = s_channels; + inplay = 0; + for ( i = 0; i < MAX_CHANNELS ; i++, ch++ ) { + if (ch[i].entnum == entityNum && ch[i].thesfx == sfx) { + if (time - ch[i].allocTime < 50) { +// if (Cvar_VariableValue( "cg_showmiss" )) { +// Com_Printf("double sound start\n"); +// } + return; + } + inplay++; + } + } + + if (inplay>allowed) { + return; + } + + sfx->lastTimeUsed = time; + + ch = S_ChannelMalloc(); // entityNum, entchannel); + if (!ch) { + ch = s_channels; + + oldest = sfx->lastTimeUsed; + chosen = -1; + for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) { + if (ch->entnum != listener_number && ch->entnum == entityNum && ch->allocTimeentchannel != CHAN_ANNOUNCER) { + oldest = ch->allocTime; + chosen = i; + } + } + if (chosen == -1) { + ch = s_channels; + for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) { + if (ch->entnum != listener_number && ch->allocTimeentchannel != CHAN_ANNOUNCER) { + oldest = ch->allocTime; + chosen = i; + } + } + if (chosen == -1) { + if (ch->entnum == listener_number) { + for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) { + if (ch->allocTimeallocTime; + chosen = i; + } + } + } + if (chosen == -1) { + Com_Printf("dropping sound\n"); + return; + } + } + } + ch = &s_channels[chosen]; + ch->allocTime = sfx->lastTimeUsed; + } + + if (origin) { + VectorCopy (origin, ch->origin); + ch->fixed_origin = qtrue; + } else { + ch->fixed_origin = qfalse; + } + + ch->master_vol = 127; + ch->entnum = entityNum; + ch->thesfx = sfx; + ch->startSample = START_SAMPLE_IMMEDIATE; + ch->entchannel = entchannel; + ch->leftvol = ch->master_vol; // these will get calced at next spatialize + ch->rightvol = ch->master_vol; // unless the game isn't running + ch->doppler = qfalse; +} + + +/* +================== +S_StartLocalSound +================== +*/ +void S_StartLocalSound( sfxHandle_t sfxHandle, int channelNum ) { + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { + Com_Printf( S_COLOR_YELLOW, "S_StartLocalSound: handle %i out of range\n", sfxHandle ); + return; + } + + S_StartSound (NULL, listener_number, channelNum, sfxHandle ); +} + + +/* +================== +S_ClearSoundBuffer + +If we are about to perform file access, clear the buffer +so sound doesn't stutter. +================== +*/ +void S_ClearSoundBuffer( void ) { + int clear; + + if (!s_soundStarted) + return; + + // stop looping sounds + Com_Memset(loopSounds, 0, MAX_GENTITIES*sizeof(loopSound_t)); + Com_Memset(loop_channels, 0, MAX_CHANNELS*sizeof(channel_t)); + numLoopChannels = 0; + + S_ChannelSetup(); + + s_rawend = 0; + + if (dma.samplebits == 8) + clear = 0x80; + else + clear = 0; + + SNDDMA_BeginPainting (); + if (dma.buffer) + // TTimo: due to a particular bug workaround in linux sound code, + // have to optionally use a custom C implementation of Com_Memset + // not affecting win32, we have #define Snd_Memset Com_Memset + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=371 + Snd_Memset(dma.buffer, clear, dma.samples * dma.samplebits/8); + SNDDMA_Submit (); +} + +/* +================== +S_StopAllSounds +================== +*/ +void S_StopAllSounds(void) { + if ( !s_soundStarted ) { + return; + } + + // stop the background music + S_StopBackgroundTrack(); + + S_ClearSoundBuffer (); +} + +/* +============================================================== + +continuous looping sounds are added each frame + +============================================================== +*/ + +void S_StopLoopingSound(int entityNum) { + loopSounds[entityNum].active = qfalse; +// loopSounds[entityNum].sfx = 0; + loopSounds[entityNum].kill = qfalse; +} + +/* +================== +S_ClearLoopingSounds + +================== +*/ +void S_ClearLoopingSounds( qboolean killall ) { + int i; + for ( i = 0 ; i < MAX_GENTITIES ; i++) { + if (killall || loopSounds[i].kill == qtrue || (loopSounds[i].sfx && loopSounds[i].sfx->soundLength == 0)) { + loopSounds[i].kill = qfalse; + S_StopLoopingSound(i); + } + } + numLoopChannels = 0; +} + +/* +================== +S_AddLoopingSound + +Called during entity generation for a frame +Include velocity in case I get around to doing doppler... +================== +*/ +void S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfxHandle ) { + sfx_t *sfx; + + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { + Com_Printf( S_COLOR_YELLOW, "S_AddLoopingSound: handle %i out of range\n", sfxHandle ); + return; + } + + sfx = &s_knownSfx[ sfxHandle ]; + + if (sfx->inMemory == qfalse) { + S_memoryLoad(sfx); + } + + if ( !sfx->soundLength ) { + Com_Error( ERR_DROP, "%s has length 0", sfx->soundName ); + } + + VectorCopy( origin, loopSounds[entityNum].origin ); + VectorCopy( velocity, loopSounds[entityNum].velocity ); + loopSounds[entityNum].active = qtrue; + loopSounds[entityNum].kill = qtrue; + loopSounds[entityNum].doppler = qfalse; + loopSounds[entityNum].oldDopplerScale = 1.0; + loopSounds[entityNum].dopplerScale = 1.0; + loopSounds[entityNum].sfx = sfx; + + if (s_doppler->integer && VectorLengthSquared(velocity)>0.0) { + vec3_t out; + float lena, lenb; + + loopSounds[entityNum].doppler = qtrue; + lena = DistanceSquared(loopSounds[listener_number].origin, loopSounds[entityNum].origin); + VectorAdd(loopSounds[entityNum].origin, loopSounds[entityNum].velocity, out); + lenb = DistanceSquared(loopSounds[listener_number].origin, out); + if ((loopSounds[entityNum].framenum+1) != cls.framecount) { + loopSounds[entityNum].oldDopplerScale = 1.0; + } else { + loopSounds[entityNum].oldDopplerScale = loopSounds[entityNum].dopplerScale; + } + loopSounds[entityNum].dopplerScale = lenb/(lena*100); + if (loopSounds[entityNum].dopplerScale<=1.0) { + loopSounds[entityNum].doppler = qfalse; // don't bother doing the math + } + } + + loopSounds[entityNum].framenum = cls.framecount; +} + +/* +================== +S_AddLoopingSound + +Called during entity generation for a frame +Include velocity in case I get around to doing doppler... +================== +*/ +void S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfxHandle ) { + sfx_t *sfx; + + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { + Com_Printf( S_COLOR_YELLOW, "S_AddRealLoopingSound: handle %i out of range\n", sfxHandle ); + return; + } + + sfx = &s_knownSfx[ sfxHandle ]; + + if (sfx->inMemory == qfalse) { + S_memoryLoad(sfx); + } + + if ( !sfx->soundLength ) { + Com_Error( ERR_DROP, "%s has length 0", sfx->soundName ); + } + VectorCopy( origin, loopSounds[entityNum].origin ); + VectorCopy( velocity, loopSounds[entityNum].velocity ); + loopSounds[entityNum].sfx = sfx; + loopSounds[entityNum].active = qtrue; + loopSounds[entityNum].kill = qfalse; + loopSounds[entityNum].doppler = qfalse; +} + + + +/* +================== +S_AddLoopSounds + +Spatialize all of the looping sounds. +All sounds are on the same cycle, so any duplicates can just +sum up the channel multipliers. +================== +*/ +void S_AddLoopSounds (void) { + int i, j, time; + int left_total, right_total, left, right; + channel_t *ch; + loopSound_t *loop, *loop2; + static int loopFrame; + + + numLoopChannels = 0; + + time = Com_Milliseconds(); + + loopFrame++; + for ( i = 0 ; i < MAX_GENTITIES ; i++) { + loop = &loopSounds[i]; + if ( !loop->active || loop->mergeFrame == loopFrame ) { + continue; // already merged into an earlier sound + } + + if (loop->kill) { + S_SpatializeOrigin( loop->origin, 127, &left_total, &right_total); // 3d + } else { + S_SpatializeOrigin( loop->origin, 90, &left_total, &right_total); // sphere + } + + loop->sfx->lastTimeUsed = time; + + for (j=(i+1); j< MAX_GENTITIES ; j++) { + loop2 = &loopSounds[j]; + if ( !loop2->active || loop2->doppler || loop2->sfx != loop->sfx) { + continue; + } + loop2->mergeFrame = loopFrame; + + if (loop2->kill) { + S_SpatializeOrigin( loop2->origin, 127, &left, &right); // 3d + } else { + S_SpatializeOrigin( loop2->origin, 90, &left, &right); // sphere + } + + loop2->sfx->lastTimeUsed = time; + left_total += left; + right_total += right; + } + if (left_total == 0 && right_total == 0) { + continue; // not audible + } + + // allocate a channel + ch = &loop_channels[numLoopChannels]; + + if (left_total > 255) { + left_total = 255; + } + if (right_total > 255) { + right_total = 255; + } + + ch->master_vol = 127; + ch->leftvol = left_total; + ch->rightvol = right_total; + ch->thesfx = loop->sfx; + ch->doppler = loop->doppler; + ch->dopplerScale = loop->dopplerScale; + ch->oldDopplerScale = loop->oldDopplerScale; + numLoopChannels++; + if (numLoopChannels == MAX_CHANNELS) { + return; + } + } +} + +//============================================================================= + +/* +================= +S_ByteSwapRawSamples + +If raw data has been loaded in little endien binary form, this must be done. +If raw data was calculated, as with ADPCM, this should not be called. +================= +*/ +void S_ByteSwapRawSamples( int samples, int width, int s_channels, const byte *data ) { + int i; + + if ( width != 2 ) { + return; + } + if ( LittleShort( 256 ) == 256 ) { + return; + } + + if ( s_channels == 2 ) { + samples <<= 1; + } + for ( i = 0 ; i < samples ; i++ ) { + ((short *)data)[i] = LittleShort( ((short *)data)[i] ); + } +} + +portable_samplepair_t *S_GetRawSamplePointer() { + return s_rawsamples; +} + +/* +============ +S_RawSamples + +Music streaming +============ +*/ +void S_RawSamples( int samples, int rate, int width, int s_channels, const byte *data, float volume ) { + int i; + int src, dst; + float scale; + int intVolume; + + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + intVolume = 256 * volume; + + if ( s_rawend < s_soundtime ) { + Com_DPrintf( "S_RawSamples: resetting minimum: %i < %i\n", s_rawend, s_soundtime ); + s_rawend = s_soundtime; + } + + scale = (float)rate / dma.speed; + +//Com_Printf ("%i < %i < %i\n", s_soundtime, s_paintedtime, s_rawend); + if (s_channels == 2 && width == 2) + { + if (scale == 1.0) + { // optimized case + for (i=0 ; i= samples) + break; + dst = s_rawend&(MAX_RAW_SAMPLES-1); + s_rawend++; + s_rawsamples[dst].left = ((short *)data)[src*2] * intVolume; + s_rawsamples[dst].right = ((short *)data)[src*2+1] * intVolume; + } + } + } + else if (s_channels == 1 && width == 2) + { + for (i=0 ; ; i++) + { + src = i*scale; + if (src >= samples) + break; + dst = s_rawend&(MAX_RAW_SAMPLES-1); + s_rawend++; + s_rawsamples[dst].left = ((short *)data)[src] * intVolume; + s_rawsamples[dst].right = ((short *)data)[src] * intVolume; + } + } + else if (s_channels == 2 && width == 1) + { + intVolume *= 256; + + for (i=0 ; ; i++) + { + src = i*scale; + if (src >= samples) + break; + dst = s_rawend&(MAX_RAW_SAMPLES-1); + s_rawend++; + s_rawsamples[dst].left = ((char *)data)[src*2] * intVolume; + s_rawsamples[dst].right = ((char *)data)[src*2+1] * intVolume; + } + } + else if (s_channels == 1 && width == 1) + { + intVolume *= 256; + + for (i=0 ; ; i++) + { + src = i*scale; + if (src >= samples) + break; + dst = s_rawend&(MAX_RAW_SAMPLES-1); + s_rawend++; + s_rawsamples[dst].left = (((byte *)data)[src]-128) * intVolume; + s_rawsamples[dst].right = (((byte *)data)[src]-128) * intVolume; + } + } + + if ( s_rawend > s_soundtime + MAX_RAW_SAMPLES ) { + Com_DPrintf( "S_RawSamples: overflowed %i > %i\n", s_rawend, s_soundtime ); + } +} + +//============================================================================= + +/* +===================== +S_UpdateEntityPosition + +let the sound system know where an entity currently is +====================== +*/ +void S_UpdateEntityPosition( int entityNum, const vec3_t origin ) { + if ( entityNum < 0 || entityNum > MAX_GENTITIES ) { + Com_Error( ERR_DROP, "S_UpdateEntityPosition: bad entitynum %i", entityNum ); + } + VectorCopy( origin, loopSounds[entityNum].origin ); +} + + +/* +============ +S_Respatialize + +Change the volumes of all the playing sounds for changes in their positions +============ +*/ +void S_Respatialize( int entityNum, const vec3_t head, vec3_t axis[3], int inwater ) { + int i; + channel_t *ch; + vec3_t origin; + + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + listener_number = entityNum; + VectorCopy(head, listener_origin); + VectorCopy(axis[0], listener_axis[0]); + VectorCopy(axis[1], listener_axis[1]); + VectorCopy(axis[2], listener_axis[2]); + + // update spatialization for dynamic sounds + ch = s_channels; + for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) { + if ( !ch->thesfx ) { + continue; + } + // anything coming from the view entity will always be full volume + if (ch->entnum == listener_number) { + ch->leftvol = ch->master_vol; + ch->rightvol = ch->master_vol; + } else { + if (ch->fixed_origin) { + VectorCopy( ch->origin, origin ); + } else { + VectorCopy( loopSounds[ ch->entnum ].origin, origin ); + } + + S_SpatializeOrigin (origin, ch->master_vol, &ch->leftvol, &ch->rightvol); + } + } + + // add loopsounds + S_AddLoopSounds (); +} + + +/* +======================== +S_ScanChannelStarts + +Returns qtrue if any new sounds were started since the last mix +======================== +*/ +qboolean S_ScanChannelStarts( void ) { + channel_t *ch; + int i; + qboolean newSamples; + + newSamples = qfalse; + ch = s_channels; + + for (i=0; ithesfx ) { + continue; + } + // if this channel was just started this frame, + // set the sample count to it begins mixing + // into the very first sample + if ( ch->startSample == START_SAMPLE_IMMEDIATE ) { + ch->startSample = s_paintedtime; + newSamples = qtrue; + continue; + } + + // if it is completely finished by now, clear it + if ( ch->startSample + (ch->thesfx->soundLength) <= s_paintedtime ) { + S_ChannelFree(ch); + } + } + + return newSamples; +} + +/* +============ +S_Update + +Called once each time through the main loop +============ +*/ +void S_Update( void ) { + int i; + int total; + channel_t *ch; + + if ( !s_soundStarted || s_soundMuted ) { + Com_DPrintf ("not started or muted\n"); + return; + } + + // + // debugging output + // + if ( s_show->integer == 2 ) { + total = 0; + ch = s_channels; + for (i=0 ; ithesfx && (ch->leftvol || ch->rightvol) ) { + Com_Printf ("%f %f %s\n", ch->leftvol, ch->rightvol, ch->thesfx->soundName); + total++; + } + } + + Com_Printf ("----(%i)---- painted: %i\n", total, s_paintedtime); + } + + // add raw data from streamed samples + S_UpdateBackgroundTrack(); + + // mix some sound + S_Update_(); +} + +void S_GetSoundtime(void) +{ + int samplepos; + static int buffers; + static int oldsamplepos; + int fullsamples; + + fullsamples = dma.samples / dma.channels; + + // it is possible to miscount buffers if it has wrapped twice between + // calls to S_Update. Oh well. + samplepos = SNDDMA_GetDMAPos(); + if (samplepos < oldsamplepos) + { + buffers++; // buffer wrapped + + if (s_paintedtime > 0x40000000) + { // time to chop things off to avoid 32 bit limits + buffers = 0; + s_paintedtime = fullsamples; + S_StopAllSounds (); + } + } + oldsamplepos = samplepos; + + s_soundtime = buffers*fullsamples + samplepos/dma.channels; + +#if 0 +// check to make sure that we haven't overshot + if (s_paintedtime < s_soundtime) + { + Com_DPrintf ("S_Update_ : overflow\n"); + s_paintedtime = s_soundtime; + } +#endif + + if ( dma.submission_chunk < 256 ) { + s_paintedtime = s_soundtime + s_mixPreStep->value * dma.speed; + } else { + s_paintedtime = s_soundtime + dma.submission_chunk; + } +} + + +void S_Update_(void) { + unsigned endtime; + int samps; + static float lastTime = 0.0f; + float ma, op; + float thisTime, sane; + static int ot = -1; + + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + thisTime = Com_Milliseconds(); + + // Updates s_soundtime + S_GetSoundtime(); + + if (s_soundtime == ot) { + return; + } + ot = s_soundtime; + + // clear any sound effects that end before the current time, + // and start any new sounds + S_ScanChannelStarts(); + + sane = thisTime - lastTime; + if (sane<11) { + sane = 11; // 85hz + } + + ma = s_mixahead->value * dma.speed; + op = s_mixPreStep->value + sane*dma.speed*0.01; + + if (op < ma) { + ma = op; + } + + // mix ahead of current position + endtime = s_soundtime + ma; + + // mix to an even submission block size + endtime = (endtime + dma.submission_chunk-1) + & ~(dma.submission_chunk-1); + + // never mix more than the complete buffer + samps = dma.samples >> (dma.channels-1); + if (endtime - s_soundtime > samps) + endtime = s_soundtime + samps; + + + + SNDDMA_BeginPainting (); + + S_PaintChannels (endtime); + + SNDDMA_Submit (); + + lastTime = thisTime; +} + +/* +=============================================================================== + +console functions + +=============================================================================== +*/ + +void S_Play_f( void ) { + int i; + sfxHandle_t h; + char name[256]; + + i = 1; + while ( i [loopfile]\n"); + return; + } + +} + +void S_SoundList_f( void ) { + int i; + sfx_t *sfx; + int size, total; + char type[4][16]; + char mem[2][16]; + + strcpy(type[0], "16bit"); + strcpy(type[1], "adpcm"); + strcpy(type[2], "daub4"); + strcpy(type[3], "mulaw"); + strcpy(mem[0], "paged out"); + strcpy(mem[1], "resident "); + total = 0; + for (sfx=s_knownSfx, i=0 ; isoundLength; + total += size; + Com_Printf("%6i[%s] : %s[%s]\n", size, type[sfx->soundCompressionMethod], sfx->soundName, mem[sfx->inMemory] ); + } + Com_Printf ("Total resident: %i\n", total); + S_DisplayFreeMemory(); +} + + +/* +=============================================================================== + +background music functions + +=============================================================================== +*/ + +int FGetLittleLong( fileHandle_t f ) { + int v; + + FS_Read( &v, sizeof(v), f ); + + return LittleLong( v); +} + +int FGetLittleShort( fileHandle_t f ) { + short v; + + FS_Read( &v, sizeof(v), f ); + + return LittleShort( v); +} + +// returns the length of the data in the chunk, or 0 if not found +int S_FindWavChunk( fileHandle_t f, char *chunk ) { + char name[5]; + int len; + int r; + + name[4] = 0; + len = 0; + r = FS_Read( name, 4, f ); + if ( r != 4 ) { + return 0; + } + len = FGetLittleLong( f ); + if ( len < 0 || len > 0xfffffff ) { + len = 0; + return 0; + } + len = (len + 1 ) & ~1; // pad to word boundary +// s_nextWavChunk += len + 8; + + if ( strcmp( name, chunk ) ) { + return 0; + } + + return len; +} + +/* +====================== +S_StopBackgroundTrack +====================== +*/ +void S_StopBackgroundTrack( void ) { + if ( !s_backgroundFile ) { + return; + } + Sys_EndStreamedFile( s_backgroundFile ); + FS_FCloseFile( s_backgroundFile ); + s_backgroundFile = 0; + s_rawend = 0; +} + +/* +====================== +S_StartBackgroundTrack +====================== +*/ +void S_StartBackgroundTrack( const char *intro, const char *loop ){ + int len; + char dump[16]; + char name[MAX_QPATH]; + + if ( !intro ) { + intro = ""; + } + if ( !loop || !loop[0] ) { + loop = intro; + } + Com_DPrintf( "S_StartBackgroundTrack( %s, %s )\n", intro, loop ); + + Q_strncpyz( name, intro, sizeof( name ) - 4 ); + COM_DefaultExtension( name, sizeof( name ), ".wav" ); + + if ( !intro[0] ) { + return; + } + + Q_strncpyz( s_backgroundLoop, loop, sizeof( s_backgroundLoop ) ); + + // close the background track, but DON'T reset s_rawend + // if restarting the same back ground track + if ( s_backgroundFile ) { + Sys_EndStreamedFile( s_backgroundFile ); + FS_FCloseFile( s_backgroundFile ); + s_backgroundFile = 0; + } + + // + // open up a wav file and get all the info + // + FS_FOpenFileRead( name, &s_backgroundFile, qtrue ); + if ( !s_backgroundFile ) { + Com_Printf( S_COLOR_YELLOW "WARNING: couldn't open music file %s\n", name ); + return; + } + + // skip the riff wav header + + FS_Read(dump, 12, s_backgroundFile); + + if ( !S_FindWavChunk( s_backgroundFile, "fmt " ) ) { + Com_Printf( "No fmt chunk in %s\n", name ); + FS_FCloseFile( s_backgroundFile ); + s_backgroundFile = 0; + return; + } + + // save name for soundinfo + s_backgroundInfo.format = FGetLittleShort( s_backgroundFile ); + s_backgroundInfo.channels = FGetLittleShort( s_backgroundFile ); + s_backgroundInfo.rate = FGetLittleLong( s_backgroundFile ); + FGetLittleLong( s_backgroundFile ); + FGetLittleShort( s_backgroundFile ); + s_backgroundInfo.width = FGetLittleShort( s_backgroundFile ) / 8; + + if ( s_backgroundInfo.format != WAV_FORMAT_PCM ) { + FS_FCloseFile( s_backgroundFile ); + s_backgroundFile = 0; + Com_Printf("Not a microsoft PCM format wav: %s\n", name); + return; + } + + if ( s_backgroundInfo.channels != 2 || s_backgroundInfo.rate != 22050 ) { + Com_Printf(S_COLOR_YELLOW "WARNING: music file %s is not 22k stereo\n", name ); + } + + if ( ( len = S_FindWavChunk( s_backgroundFile, "data" ) ) == 0 ) { + FS_FCloseFile( s_backgroundFile ); + s_backgroundFile = 0; + Com_Printf("No data chunk in %s\n", name); + return; + } + + s_backgroundInfo.samples = len / (s_backgroundInfo.width * s_backgroundInfo.channels); + + s_backgroundSamples = s_backgroundInfo.samples; + + // + // start the background streaming + // + Sys_BeginStreamedFile( s_backgroundFile, 0x10000 ); +} + +/* +====================== +S_UpdateBackgroundTrack +====================== +*/ +void S_UpdateBackgroundTrack( void ) { + int bufferSamples; + int fileSamples; + byte raw[30000]; // just enough to fit in a mac stack frame + int fileBytes; + int r; + static float musicVolume = 0.5f; + + if ( !s_backgroundFile ) { + return; + } + + // graeme see if this is OK + musicVolume = (musicVolume + (s_musicVolume->value * 2))/4.0f; + + // don't bother playing anything if musicvolume is 0 + if ( musicVolume <= 0 ) { + return; + } + + // see how many samples should be copied into the raw buffer + if ( s_rawend < s_soundtime ) { + s_rawend = s_soundtime; + } + + while ( s_rawend < s_soundtime + MAX_RAW_SAMPLES ) { + bufferSamples = MAX_RAW_SAMPLES - (s_rawend - s_soundtime); + + // decide how much data needs to be read from the file + fileSamples = bufferSamples * s_backgroundInfo.rate / dma.speed; + + // don't try and read past the end of the file + if ( fileSamples > s_backgroundSamples ) { + fileSamples = s_backgroundSamples; + } + + // our max buffer size + fileBytes = fileSamples * (s_backgroundInfo.width * s_backgroundInfo.channels); + if ( fileBytes > sizeof(raw) ) { + fileBytes = sizeof(raw); + fileSamples = fileBytes / (s_backgroundInfo.width * s_backgroundInfo.channels); + } + + r = Sys_StreamedRead( raw, 1, fileBytes, s_backgroundFile ); + if ( r != fileBytes ) { + Com_Printf("StreamedRead failure on music track\n"); + S_StopBackgroundTrack(); + return; + } + + // byte swap if needed + S_ByteSwapRawSamples( fileSamples, s_backgroundInfo.width, s_backgroundInfo.channels, raw ); + + // add to raw buffer + S_RawSamples( fileSamples, s_backgroundInfo.rate, + s_backgroundInfo.width, s_backgroundInfo.channels, raw, musicVolume ); + + s_backgroundSamples -= fileSamples; + if ( !s_backgroundSamples ) { + // loop + if (s_backgroundLoop[0]) { + Sys_EndStreamedFile( s_backgroundFile ); + FS_FCloseFile( s_backgroundFile ); + s_backgroundFile = 0; + S_StartBackgroundTrack( s_backgroundLoop, s_backgroundLoop ); + if ( !s_backgroundFile ) { + return; // loop failed to restart + } + } else { + s_backgroundFile = 0; + return; + } + } + } +} + + +/* +====================== +S_FreeOldestSound +====================== +*/ + +void S_FreeOldestSound() { + int i, oldest, used; + sfx_t *sfx; + sndBuffer *buffer, *nbuffer; + + oldest = Com_Milliseconds(); + used = 0; + + for (i=1 ; i < s_numSfx ; i++) { + sfx = &s_knownSfx[i]; + if (sfx->inMemory && sfx->lastTimeUsedlastTimeUsed; + } + } + + sfx = &s_knownSfx[used]; + + Com_DPrintf("S_FreeOldestSound: freeing sound %s\n", sfx->soundName); + + buffer = sfx->soundData; + while(buffer != NULL) { + nbuffer = buffer->next; + SND_free(buffer); + buffer = nbuffer; + } + sfx->inMemory = qfalse; + sfx->soundData = NULL; +} diff --git a/code/client/snd_local.h b/code/client/snd_local.h index 5b0c97a..466c52b 100755 --- a/code/client/snd_local.h +++ b/code/client/snd_local.h @@ -1,204 +1,204 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// snd_local.h -- private sound definations - - -#include "../game/q_shared.h" -#include "../qcommon/qcommon.h" -#include "snd_public.h" - -#define PAINTBUFFER_SIZE 4096 // this is in samples - -#define SND_CHUNK_SIZE 1024 // samples -#define SND_CHUNK_SIZE_FLOAT (SND_CHUNK_SIZE/2) // floats -#define SND_CHUNK_SIZE_BYTE (SND_CHUNK_SIZE*2) // floats - -typedef struct { - int left; // the final values will be clamped to +/- 0x00ffff00 and shifted down - int right; -} portable_samplepair_t; - -typedef struct adpcm_state { - short sample; /* Previous output value */ - char index; /* Index into stepsize table */ -} adpcm_state_t; - -typedef struct sndBuffer_s { - short sndChunk[SND_CHUNK_SIZE]; - struct sndBuffer_s *next; - int size; - adpcm_state_t adpcm; -} sndBuffer; - -typedef struct sfx_s { - sndBuffer *soundData; - qboolean defaultSound; // couldn't be loaded, so use buzz - qboolean inMemory; // not in Memory - qboolean soundCompressed; // not in Memory - int soundCompressionMethod; - int soundLength; - char soundName[MAX_QPATH]; - int lastTimeUsed; - struct sfx_s *next; -} sfx_t; - -typedef struct { - int channels; - int samples; // mono samples in buffer - int submission_chunk; // don't mix less than this # - int samplebits; - int speed; - byte *buffer; -} dma_t; - -#define START_SAMPLE_IMMEDIATE 0x7fffffff - -typedef struct loopSound_s { - vec3_t origin; - vec3_t velocity; - sfx_t *sfx; - int mergeFrame; - qboolean active; - qboolean kill; - qboolean doppler; - float dopplerScale; - float oldDopplerScale; - int framenum; -} loopSound_t; - -typedef struct -{ - int allocTime; - int startSample; // START_SAMPLE_IMMEDIATE = set immediately on next mix - int entnum; // to allow overriding a specific sound - int entchannel; // to allow overriding a specific sound - int leftvol; // 0-255 volume after spatialization - int rightvol; // 0-255 volume after spatialization - int master_vol; // 0-255 volume before spatialization - float dopplerScale; - float oldDopplerScale; - vec3_t origin; // only use if fixed_origin is set - qboolean fixed_origin; // use origin instead of fetching entnum's origin - sfx_t *thesfx; // sfx structure - qboolean doppler; -} channel_t; - - -#define WAV_FORMAT_PCM 1 - - -typedef struct { - int format; - int rate; - int width; - int channels; - int samples; - int dataofs; // chunk starts this many bytes from file start -} wavinfo_t; - - -/* -==================================================================== - - SYSTEM SPECIFIC FUNCTIONS - -==================================================================== -*/ - -// initializes cycling through a DMA buffer and returns information on it -qboolean SNDDMA_Init(void); - -// gets the current DMA position -int SNDDMA_GetDMAPos(void); - -// shutdown the DMA xfer. -void SNDDMA_Shutdown(void); - -void SNDDMA_BeginPainting (void); - -void SNDDMA_Submit(void); - -//==================================================================== - -#define MAX_CHANNELS 96 - -extern channel_t s_channels[MAX_CHANNELS]; -extern channel_t loop_channels[MAX_CHANNELS]; -extern int numLoopChannels; - -extern int s_paintedtime; -extern int s_rawend; -extern vec3_t listener_forward; -extern vec3_t listener_right; -extern vec3_t listener_up; -extern dma_t dma; - -#define MAX_RAW_SAMPLES 16384 -extern portable_samplepair_t s_rawsamples[MAX_RAW_SAMPLES]; - -extern cvar_t *s_volume; -extern cvar_t *s_nosound; -extern cvar_t *s_khz; -extern cvar_t *s_show; -extern cvar_t *s_mixahead; - -extern cvar_t *s_testsound; -extern cvar_t *s_separation; - -qboolean S_LoadSound( sfx_t *sfx ); - -void SND_free(sndBuffer *v); -sndBuffer* SND_malloc(); -void SND_setup(); - -void S_PaintChannels(int endtime); - -void S_memoryLoad(sfx_t *sfx); -portable_samplepair_t *S_GetRawSamplePointer(); - -// spatializes a channel -void S_Spatialize(channel_t *ch); - -// adpcm functions -int S_AdpcmMemoryNeeded( const wavinfo_t *info ); -void S_AdpcmEncodeSound( sfx_t *sfx, short *samples ); -void S_AdpcmGetSamples(sndBuffer *chunk, short *to); - -// wavelet function - -#define SENTINEL_MULAW_ZERO_RUN 127 -#define SENTINEL_MULAW_FOUR_BIT_RUN 126 - -void S_FreeOldestSound(); - -#define NXStream byte - -void encodeWavelet(sfx_t *sfx, short *packets); -void decodeWavelet( sndBuffer *stream, short *packets); - -void encodeMuLaw( sfx_t *sfx, short *packets); -extern short mulawToShort[256]; - -extern short *sfxScratchBuffer; -extern sfx_t *sfxScratchPointer; -extern int sfxScratchIndex; - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// snd_local.h -- private sound definations + + +#include "../game/q_shared.h" +#include "../qcommon/qcommon.h" +#include "snd_public.h" + +#define PAINTBUFFER_SIZE 4096 // this is in samples + +#define SND_CHUNK_SIZE 1024 // samples +#define SND_CHUNK_SIZE_FLOAT (SND_CHUNK_SIZE/2) // floats +#define SND_CHUNK_SIZE_BYTE (SND_CHUNK_SIZE*2) // floats + +typedef struct { + int left; // the final values will be clamped to +/- 0x00ffff00 and shifted down + int right; +} portable_samplepair_t; + +typedef struct adpcm_state { + short sample; /* Previous output value */ + char index; /* Index into stepsize table */ +} adpcm_state_t; + +typedef struct sndBuffer_s { + short sndChunk[SND_CHUNK_SIZE]; + struct sndBuffer_s *next; + int size; + adpcm_state_t adpcm; +} sndBuffer; + +typedef struct sfx_s { + sndBuffer *soundData; + qboolean defaultSound; // couldn't be loaded, so use buzz + qboolean inMemory; // not in Memory + qboolean soundCompressed; // not in Memory + int soundCompressionMethod; + int soundLength; + char soundName[MAX_QPATH]; + int lastTimeUsed; + struct sfx_s *next; +} sfx_t; + +typedef struct { + int channels; + int samples; // mono samples in buffer + int submission_chunk; // don't mix less than this # + int samplebits; + int speed; + byte *buffer; +} dma_t; + +#define START_SAMPLE_IMMEDIATE 0x7fffffff + +typedef struct loopSound_s { + vec3_t origin; + vec3_t velocity; + sfx_t *sfx; + int mergeFrame; + qboolean active; + qboolean kill; + qboolean doppler; + float dopplerScale; + float oldDopplerScale; + int framenum; +} loopSound_t; + +typedef struct +{ + int allocTime; + int startSample; // START_SAMPLE_IMMEDIATE = set immediately on next mix + int entnum; // to allow overriding a specific sound + int entchannel; // to allow overriding a specific sound + int leftvol; // 0-255 volume after spatialization + int rightvol; // 0-255 volume after spatialization + int master_vol; // 0-255 volume before spatialization + float dopplerScale; + float oldDopplerScale; + vec3_t origin; // only use if fixed_origin is set + qboolean fixed_origin; // use origin instead of fetching entnum's origin + sfx_t *thesfx; // sfx structure + qboolean doppler; +} channel_t; + + +#define WAV_FORMAT_PCM 1 + + +typedef struct { + int format; + int rate; + int width; + int channels; + int samples; + int dataofs; // chunk starts this many bytes from file start +} wavinfo_t; + + +/* +==================================================================== + + SYSTEM SPECIFIC FUNCTIONS + +==================================================================== +*/ + +// initializes cycling through a DMA buffer and returns information on it +qboolean SNDDMA_Init(void); + +// gets the current DMA position +int SNDDMA_GetDMAPos(void); + +// shutdown the DMA xfer. +void SNDDMA_Shutdown(void); + +void SNDDMA_BeginPainting (void); + +void SNDDMA_Submit(void); + +//==================================================================== + +#define MAX_CHANNELS 96 + +extern channel_t s_channels[MAX_CHANNELS]; +extern channel_t loop_channels[MAX_CHANNELS]; +extern int numLoopChannels; + +extern int s_paintedtime; +extern int s_rawend; +extern vec3_t listener_forward; +extern vec3_t listener_right; +extern vec3_t listener_up; +extern dma_t dma; + +#define MAX_RAW_SAMPLES 16384 +extern portable_samplepair_t s_rawsamples[MAX_RAW_SAMPLES]; + +extern cvar_t *s_volume; +extern cvar_t *s_nosound; +extern cvar_t *s_khz; +extern cvar_t *s_show; +extern cvar_t *s_mixahead; + +extern cvar_t *s_testsound; +extern cvar_t *s_separation; + +qboolean S_LoadSound( sfx_t *sfx ); + +void SND_free(sndBuffer *v); +sndBuffer* SND_malloc(); +void SND_setup(); + +void S_PaintChannels(int endtime); + +void S_memoryLoad(sfx_t *sfx); +portable_samplepair_t *S_GetRawSamplePointer(); + +// spatializes a channel +void S_Spatialize(channel_t *ch); + +// adpcm functions +int S_AdpcmMemoryNeeded( const wavinfo_t *info ); +void S_AdpcmEncodeSound( sfx_t *sfx, short *samples ); +void S_AdpcmGetSamples(sndBuffer *chunk, short *to); + +// wavelet function + +#define SENTINEL_MULAW_ZERO_RUN 127 +#define SENTINEL_MULAW_FOUR_BIT_RUN 126 + +void S_FreeOldestSound(); + +#define NXStream byte + +void encodeWavelet(sfx_t *sfx, short *packets); +void decodeWavelet( sndBuffer *stream, short *packets); + +void encodeMuLaw( sfx_t *sfx, short *packets); +extern short mulawToShort[256]; + +extern short *sfxScratchBuffer; +extern sfx_t *sfxScratchPointer; +extern int sfxScratchIndex; + diff --git a/code/client/snd_mem.c b/code/client/snd_mem.c index 0e400d5..23c413d 100755 --- a/code/client/snd_mem.c +++ b/code/client/snd_mem.c @@ -1,404 +1,404 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -/***************************************************************************** - * name: snd_mem.c - * - * desc: sound caching - * - * $Archive: /MissionPack/code/client/snd_mem.c $ - * - *****************************************************************************/ - -#include "snd_local.h" - -#define DEF_COMSOUNDMEGS "8" - -/* -=============================================================================== - -memory management - -=============================================================================== -*/ - -static sndBuffer *buffer = NULL; -static sndBuffer *freelist = NULL; -static int inUse = 0; -static int totalInUse = 0; - -short *sfxScratchBuffer = NULL; -sfx_t *sfxScratchPointer = NULL; -int sfxScratchIndex = 0; - -void SND_free(sndBuffer *v) { - *(sndBuffer **)v = freelist; - freelist = (sndBuffer*)v; - inUse += sizeof(sndBuffer); -} - -sndBuffer* SND_malloc() { - sndBuffer *v; -redo: - if (freelist == NULL) { - S_FreeOldestSound(); - goto redo; - } - - inUse -= sizeof(sndBuffer); - totalInUse += sizeof(sndBuffer); - - v = freelist; - freelist = *(sndBuffer **)freelist; - v->next = NULL; - return v; -} - -void SND_setup() { - sndBuffer *p, *q; - cvar_t *cv; - int scs; - - cv = Cvar_Get( "com_soundMegs", DEF_COMSOUNDMEGS, CVAR_LATCH | CVAR_ARCHIVE ); - - scs = (cv->integer*1536); - - buffer = malloc(scs*sizeof(sndBuffer) ); - // allocate the stack based hunk allocator - sfxScratchBuffer = malloc(SND_CHUNK_SIZE * sizeof(short) * 4); //Hunk_Alloc(SND_CHUNK_SIZE * sizeof(short) * 4); - sfxScratchPointer = NULL; - - inUse = scs*sizeof(sndBuffer); - p = buffer;; - q = p + scs; - while (--q > p) - *(sndBuffer **)q = q-1; - - *(sndBuffer **)q = NULL; - freelist = p + scs - 1; - - Com_Printf("Sound memory manager started\n"); -} - -/* -=============================================================================== - -WAV loading - -=============================================================================== -*/ - -static byte *data_p; -static byte *iff_end; -static byte *last_chunk; -static byte *iff_data; -static int iff_chunk_len; - -static short GetLittleShort(void) -{ - short val = 0; - val = *data_p; - val = val + (*(data_p+1)<<8); - data_p += 2; - return val; -} - -static int GetLittleLong(void) -{ - int val = 0; - val = *data_p; - val = val + (*(data_p+1)<<8); - val = val + (*(data_p+2)<<16); - val = val + (*(data_p+3)<<24); - data_p += 4; - return val; -} - -static void FindNextChunk(char *name) -{ - while (1) - { - data_p=last_chunk; - - if (data_p >= iff_end) - { // didn't find the chunk - data_p = NULL; - return; - } - - data_p += 4; - iff_chunk_len = GetLittleLong(); - if (iff_chunk_len < 0) - { - data_p = NULL; - return; - } - data_p -= 8; - last_chunk = data_p + 8 + ( (iff_chunk_len + 1) & ~1 ); - if (!strncmp((char *)data_p, name, 4)) - return; - } -} - -static void FindChunk(char *name) -{ - last_chunk = iff_data; - FindNextChunk (name); -} - -/* -============ -GetWavinfo -============ -*/ -static wavinfo_t GetWavinfo (char *name, byte *wav, int wavlength) -{ - wavinfo_t info; - - Com_Memset (&info, 0, sizeof(info)); - - if (!wav) - return info; - - iff_data = wav; - iff_end = wav + wavlength; - -// find "RIFF" chunk - FindChunk("RIFF"); - if (!(data_p && !strncmp((char *)data_p+8, "WAVE", 4))) - { - Com_Printf("Missing RIFF/WAVE chunks\n"); - return info; - } - -// get "fmt " chunk - iff_data = data_p + 12; -// DumpChunks (); - - FindChunk("fmt "); - if (!data_p) - { - Com_Printf("Missing fmt chunk\n"); - return info; - } - data_p += 8; - info.format = GetLittleShort(); - info.channels = GetLittleShort(); - info.rate = GetLittleLong(); - data_p += 4+2; - info.width = GetLittleShort() / 8; - - if (info.format != 1) - { - Com_Printf("Microsoft PCM format only\n"); - return info; - } - - -// find data chunk - FindChunk("data"); - if (!data_p) - { - Com_Printf("Missing data chunk\n"); - return info; - } - - data_p += 4; - info.samples = GetLittleLong () / info.width; - info.dataofs = data_p - wav; - - return info; -} - - -/* -================ -ResampleSfx - -resample / decimate to the current source rate -================ -*/ -static void ResampleSfx( sfx_t *sfx, int inrate, int inwidth, byte *data, qboolean compressed ) { - int outcount; - int srcsample; - float stepscale; - int i; - int sample, samplefrac, fracstep; - int part; - sndBuffer *chunk; - - stepscale = (float)inrate / dma.speed; // this is usually 0.5, 1, or 2 - - outcount = sfx->soundLength / stepscale; - sfx->soundLength = outcount; - - samplefrac = 0; - fracstep = stepscale * 256; - chunk = sfx->soundData; - - for (i=0 ; i> 8; - samplefrac += fracstep; - if( inwidth == 2 ) { - sample = LittleShort ( ((short *)data)[srcsample] ); - } else { - sample = (int)( (unsigned char)(data[srcsample]) - 128) << 8; - } - part = (i&(SND_CHUNK_SIZE-1)); - if (part == 0) { - sndBuffer *newchunk; - newchunk = SND_malloc(); - if (chunk == NULL) { - sfx->soundData = newchunk; - } else { - chunk->next = newchunk; - } - chunk = newchunk; - } - - chunk->sndChunk[part] = sample; - } -} - -/* -================ -ResampleSfx - -resample / decimate to the current source rate -================ -*/ -static int ResampleSfxRaw( short *sfx, int inrate, int inwidth, int samples, byte *data ) { - int outcount; - int srcsample; - float stepscale; - int i; - int sample, samplefrac, fracstep; - - stepscale = (float)inrate / dma.speed; // this is usually 0.5, 1, or 2 - - outcount = samples / stepscale; - - samplefrac = 0; - fracstep = stepscale * 256; - - for (i=0 ; i> 8; - samplefrac += fracstep; - if( inwidth == 2 ) { - sample = LittleShort ( ((short *)data)[srcsample] ); - } else { - sample = (int)( (unsigned char)(data[srcsample]) - 128) << 8; - } - sfx[i] = sample; - } - return outcount; -} - - -//============================================================================= - -/* -============== -S_LoadSound - -The filename may be different than sfx->name in the case -of a forced fallback of a player specific sound -============== -*/ -qboolean S_LoadSound( sfx_t *sfx ) -{ - byte *data; - short *samples; - wavinfo_t info; - int size; - - // player specific sounds are never directly loaded - if ( sfx->soundName[0] == '*') { - return qfalse; - } - - // load it in - size = FS_ReadFile( sfx->soundName, (void **)&data ); - if ( !data ) { - return qfalse; - } - - info = GetWavinfo( sfx->soundName, data, size ); - if ( info.channels != 1 ) { - Com_Printf ("%s is a stereo wav file\n", sfx->soundName); - FS_FreeFile (data); - return qfalse; - } - - if ( info.width == 1 ) { - Com_DPrintf(S_COLOR_YELLOW "WARNING: %s is a 8 bit wav file\n", sfx->soundName); - } - - if ( info.rate != 22050 ) { - Com_DPrintf(S_COLOR_YELLOW "WARNING: %s is not a 22kHz wav file\n", sfx->soundName); - } - - samples = Hunk_AllocateTempMemory(info.samples * sizeof(short) * 2); - - sfx->lastTimeUsed = Com_Milliseconds()+1; - - // each of these compression schemes works just fine - // but the 16bit quality is much nicer and with a local - // install assured we can rely upon the sound memory - // manager to do the right thing for us and page - // sound in as needed - - if( sfx->soundCompressed == qtrue) { - sfx->soundCompressionMethod = 1; - sfx->soundData = NULL; - sfx->soundLength = ResampleSfxRaw( samples, info.rate, info.width, info.samples, (data + info.dataofs) ); - S_AdpcmEncodeSound(sfx, samples); -#if 0 - } else if (info.samples>(SND_CHUNK_SIZE*16) && info.width >1) { - sfx->soundCompressionMethod = 3; - sfx->soundData = NULL; - sfx->soundLength = ResampleSfxRaw( samples, info.rate, info.width, info.samples, (data + info.dataofs) ); - encodeMuLaw( sfx, samples); - } else if (info.samples>(SND_CHUNK_SIZE*6400) && info.width >1) { - sfx->soundCompressionMethod = 2; - sfx->soundData = NULL; - sfx->soundLength = ResampleSfxRaw( samples, info.rate, info.width, info.samples, (data + info.dataofs) ); - encodeWavelet( sfx, samples); -#endif - } else { - sfx->soundCompressionMethod = 0; - sfx->soundLength = info.samples; - sfx->soundData = NULL; - ResampleSfx( sfx, info.rate, info.width, data + info.dataofs, qfalse ); - } - - Hunk_FreeTempMemory(samples); - FS_FreeFile( data ); - - return qtrue; -} - -void S_DisplayFreeMemory() { - Com_Printf("%d bytes free sound buffer memory, %d total used\n", inUse, totalInUse); -} +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: snd_mem.c + * + * desc: sound caching + * + * $Archive: /MissionPack/code/client/snd_mem.c $ + * + *****************************************************************************/ + +#include "snd_local.h" + +#define DEF_COMSOUNDMEGS "8" + +/* +=============================================================================== + +memory management + +=============================================================================== +*/ + +static sndBuffer *buffer = NULL; +static sndBuffer *freelist = NULL; +static int inUse = 0; +static int totalInUse = 0; + +short *sfxScratchBuffer = NULL; +sfx_t *sfxScratchPointer = NULL; +int sfxScratchIndex = 0; + +void SND_free(sndBuffer *v) { + *(sndBuffer **)v = freelist; + freelist = (sndBuffer*)v; + inUse += sizeof(sndBuffer); +} + +sndBuffer* SND_malloc() { + sndBuffer *v; +redo: + if (freelist == NULL) { + S_FreeOldestSound(); + goto redo; + } + + inUse -= sizeof(sndBuffer); + totalInUse += sizeof(sndBuffer); + + v = freelist; + freelist = *(sndBuffer **)freelist; + v->next = NULL; + return v; +} + +void SND_setup() { + sndBuffer *p, *q; + cvar_t *cv; + int scs; + + cv = Cvar_Get( "com_soundMegs", DEF_COMSOUNDMEGS, CVAR_LATCH | CVAR_ARCHIVE ); + + scs = (cv->integer*1536); + + buffer = malloc(scs*sizeof(sndBuffer) ); + // allocate the stack based hunk allocator + sfxScratchBuffer = malloc(SND_CHUNK_SIZE * sizeof(short) * 4); //Hunk_Alloc(SND_CHUNK_SIZE * sizeof(short) * 4); + sfxScratchPointer = NULL; + + inUse = scs*sizeof(sndBuffer); + p = buffer;; + q = p + scs; + while (--q > p) + *(sndBuffer **)q = q-1; + + *(sndBuffer **)q = NULL; + freelist = p + scs - 1; + + Com_Printf("Sound memory manager started\n"); +} + +/* +=============================================================================== + +WAV loading + +=============================================================================== +*/ + +static byte *data_p; +static byte *iff_end; +static byte *last_chunk; +static byte *iff_data; +static int iff_chunk_len; + +static short GetLittleShort(void) +{ + short val = 0; + val = *data_p; + val = val + (*(data_p+1)<<8); + data_p += 2; + return val; +} + +static int GetLittleLong(void) +{ + int val = 0; + val = *data_p; + val = val + (*(data_p+1)<<8); + val = val + (*(data_p+2)<<16); + val = val + (*(data_p+3)<<24); + data_p += 4; + return val; +} + +static void FindNextChunk(char *name) +{ + while (1) + { + data_p=last_chunk; + + if (data_p >= iff_end) + { // didn't find the chunk + data_p = NULL; + return; + } + + data_p += 4; + iff_chunk_len = GetLittleLong(); + if (iff_chunk_len < 0) + { + data_p = NULL; + return; + } + data_p -= 8; + last_chunk = data_p + 8 + ( (iff_chunk_len + 1) & ~1 ); + if (!strncmp((char *)data_p, name, 4)) + return; + } +} + +static void FindChunk(char *name) +{ + last_chunk = iff_data; + FindNextChunk (name); +} + +/* +============ +GetWavinfo +============ +*/ +static wavinfo_t GetWavinfo (char *name, byte *wav, int wavlength) +{ + wavinfo_t info; + + Com_Memset (&info, 0, sizeof(info)); + + if (!wav) + return info; + + iff_data = wav; + iff_end = wav + wavlength; + +// find "RIFF" chunk + FindChunk("RIFF"); + if (!(data_p && !strncmp((char *)data_p+8, "WAVE", 4))) + { + Com_Printf("Missing RIFF/WAVE chunks\n"); + return info; + } + +// get "fmt " chunk + iff_data = data_p + 12; +// DumpChunks (); + + FindChunk("fmt "); + if (!data_p) + { + Com_Printf("Missing fmt chunk\n"); + return info; + } + data_p += 8; + info.format = GetLittleShort(); + info.channels = GetLittleShort(); + info.rate = GetLittleLong(); + data_p += 4+2; + info.width = GetLittleShort() / 8; + + if (info.format != 1) + { + Com_Printf("Microsoft PCM format only\n"); + return info; + } + + +// find data chunk + FindChunk("data"); + if (!data_p) + { + Com_Printf("Missing data chunk\n"); + return info; + } + + data_p += 4; + info.samples = GetLittleLong () / info.width; + info.dataofs = data_p - wav; + + return info; +} + + +/* +================ +ResampleSfx + +resample / decimate to the current source rate +================ +*/ +static void ResampleSfx( sfx_t *sfx, int inrate, int inwidth, byte *data, qboolean compressed ) { + int outcount; + int srcsample; + float stepscale; + int i; + int sample, samplefrac, fracstep; + int part; + sndBuffer *chunk; + + stepscale = (float)inrate / dma.speed; // this is usually 0.5, 1, or 2 + + outcount = sfx->soundLength / stepscale; + sfx->soundLength = outcount; + + samplefrac = 0; + fracstep = stepscale * 256; + chunk = sfx->soundData; + + for (i=0 ; i> 8; + samplefrac += fracstep; + if( inwidth == 2 ) { + sample = LittleShort ( ((short *)data)[srcsample] ); + } else { + sample = (int)( (unsigned char)(data[srcsample]) - 128) << 8; + } + part = (i&(SND_CHUNK_SIZE-1)); + if (part == 0) { + sndBuffer *newchunk; + newchunk = SND_malloc(); + if (chunk == NULL) { + sfx->soundData = newchunk; + } else { + chunk->next = newchunk; + } + chunk = newchunk; + } + + chunk->sndChunk[part] = sample; + } +} + +/* +================ +ResampleSfx + +resample / decimate to the current source rate +================ +*/ +static int ResampleSfxRaw( short *sfx, int inrate, int inwidth, int samples, byte *data ) { + int outcount; + int srcsample; + float stepscale; + int i; + int sample, samplefrac, fracstep; + + stepscale = (float)inrate / dma.speed; // this is usually 0.5, 1, or 2 + + outcount = samples / stepscale; + + samplefrac = 0; + fracstep = stepscale * 256; + + for (i=0 ; i> 8; + samplefrac += fracstep; + if( inwidth == 2 ) { + sample = LittleShort ( ((short *)data)[srcsample] ); + } else { + sample = (int)( (unsigned char)(data[srcsample]) - 128) << 8; + } + sfx[i] = sample; + } + return outcount; +} + + +//============================================================================= + +/* +============== +S_LoadSound + +The filename may be different than sfx->name in the case +of a forced fallback of a player specific sound +============== +*/ +qboolean S_LoadSound( sfx_t *sfx ) +{ + byte *data; + short *samples; + wavinfo_t info; + int size; + + // player specific sounds are never directly loaded + if ( sfx->soundName[0] == '*') { + return qfalse; + } + + // load it in + size = FS_ReadFile( sfx->soundName, (void **)&data ); + if ( !data ) { + return qfalse; + } + + info = GetWavinfo( sfx->soundName, data, size ); + if ( info.channels != 1 ) { + Com_Printf ("%s is a stereo wav file\n", sfx->soundName); + FS_FreeFile (data); + return qfalse; + } + + if ( info.width == 1 ) { + Com_DPrintf(S_COLOR_YELLOW "WARNING: %s is a 8 bit wav file\n", sfx->soundName); + } + + if ( info.rate != 22050 ) { + Com_DPrintf(S_COLOR_YELLOW "WARNING: %s is not a 22kHz wav file\n", sfx->soundName); + } + + samples = Hunk_AllocateTempMemory(info.samples * sizeof(short) * 2); + + sfx->lastTimeUsed = Com_Milliseconds()+1; + + // each of these compression schemes works just fine + // but the 16bit quality is much nicer and with a local + // install assured we can rely upon the sound memory + // manager to do the right thing for us and page + // sound in as needed + + if( sfx->soundCompressed == qtrue) { + sfx->soundCompressionMethod = 1; + sfx->soundData = NULL; + sfx->soundLength = ResampleSfxRaw( samples, info.rate, info.width, info.samples, (data + info.dataofs) ); + S_AdpcmEncodeSound(sfx, samples); +#if 0 + } else if (info.samples>(SND_CHUNK_SIZE*16) && info.width >1) { + sfx->soundCompressionMethod = 3; + sfx->soundData = NULL; + sfx->soundLength = ResampleSfxRaw( samples, info.rate, info.width, info.samples, (data + info.dataofs) ); + encodeMuLaw( sfx, samples); + } else if (info.samples>(SND_CHUNK_SIZE*6400) && info.width >1) { + sfx->soundCompressionMethod = 2; + sfx->soundData = NULL; + sfx->soundLength = ResampleSfxRaw( samples, info.rate, info.width, info.samples, (data + info.dataofs) ); + encodeWavelet( sfx, samples); +#endif + } else { + sfx->soundCompressionMethod = 0; + sfx->soundLength = info.samples; + sfx->soundData = NULL; + ResampleSfx( sfx, info.rate, info.width, data + info.dataofs, qfalse ); + } + + Hunk_FreeTempMemory(samples); + FS_FreeFile( data ); + + return qtrue; +} + +void S_DisplayFreeMemory() { + Com_Printf("%d bytes free sound buffer memory, %d total used\n", inUse, totalInUse); +} diff --git a/code/client/snd_mix.c b/code/client/snd_mix.c index ec983b9..02be59c 100755 --- a/code/client/snd_mix.c +++ b/code/client/snd_mix.c @@ -1,681 +1,681 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// snd_mix.c -- portable code to mix sounds for snd_dma.c - -#include "snd_local.h" - -static portable_samplepair_t paintbuffer[PAINTBUFFER_SIZE]; -static int snd_vol; - -// bk001119 - these not static, required by unix/snd_mixa.s -int* snd_p; -int snd_linear_count; -short* snd_out; - -#if !( (defined __linux__ || defined __FreeBSD__ ) && (defined __i386__) ) // rb010123 -#if !id386 - -void S_WriteLinearBlastStereo16 (void) -{ - int i; - int val; - - for (i=0 ; i>8; - if (val > 0x7fff) - snd_out[i] = 0x7fff; - else if (val < -32768) - snd_out[i] = -32768; - else - snd_out[i] = val; - - val = snd_p[i+1]>>8; - if (val > 0x7fff) - snd_out[i+1] = 0x7fff; - else if (val < -32768) - snd_out[i+1] = -32768; - else - snd_out[i+1] = val; - } -} -#else - -__declspec( naked ) void S_WriteLinearBlastStereo16 (void) -{ - __asm { - - push edi - push ebx - mov ecx,ds:dword ptr[snd_linear_count] - mov ebx,ds:dword ptr[snd_p] - mov edi,ds:dword ptr[snd_out] -LWLBLoopTop: - mov eax,ds:dword ptr[-8+ebx+ecx*4] - sar eax,8 - cmp eax,07FFFh - jg LClampHigh - cmp eax,0FFFF8000h - jnl LClampDone - mov eax,0FFFF8000h - jmp LClampDone -LClampHigh: - mov eax,07FFFh -LClampDone: - mov edx,ds:dword ptr[-4+ebx+ecx*4] - sar edx,8 - cmp edx,07FFFh - jg LClampHigh2 - cmp edx,0FFFF8000h - jnl LClampDone2 - mov edx,0FFFF8000h - jmp LClampDone2 -LClampHigh2: - mov edx,07FFFh -LClampDone2: - shl edx,16 - and eax,0FFFFh - or edx,eax - mov ds:dword ptr[-4+edi+ecx*2],edx - sub ecx,2 - jnz LWLBLoopTop - pop ebx - pop edi - ret - } -} - -#endif -#else -// forward declare, implementation somewhere else -void S_WriteLinearBlastStereo16 (void); -#endif - -void S_TransferStereo16 (unsigned long *pbuf, int endtime) -{ - int lpos; - int ls_paintedtime; - - snd_p = (int *) paintbuffer; - ls_paintedtime = s_paintedtime; - - while (ls_paintedtime < endtime) - { - // handle recirculating buffer issues - lpos = ls_paintedtime & ((dma.samples>>1)-1); - - snd_out = (short *) pbuf + (lpos<<1); - - snd_linear_count = (dma.samples>>1) - lpos; - if (ls_paintedtime + snd_linear_count > endtime) - snd_linear_count = endtime - ls_paintedtime; - - snd_linear_count <<= 1; - - // write a linear blast of samples - S_WriteLinearBlastStereo16 (); - - snd_p += snd_linear_count; - ls_paintedtime += (snd_linear_count>>1); - } -} - -/* -=================== -S_TransferPaintBuffer - -=================== -*/ -void S_TransferPaintBuffer(int endtime) -{ - int out_idx; - int count; - int out_mask; - int *p; - int step; - int val; - unsigned long *pbuf; - - pbuf = (unsigned long *)dma.buffer; - - - if ( s_testsound->integer ) { - int i; - int count; - - // write a fixed sine wave - count = (endtime - s_paintedtime); - for (i=0 ; i> 8; - p+= step; - if (val > 0x7fff) - val = 0x7fff; - else if (val < -32768) - val = -32768; - out[out_idx] = val; - out_idx = (out_idx + 1) & out_mask; - } - } - else if (dma.samplebits == 8) - { - unsigned char *out = (unsigned char *) pbuf; - while (count--) - { - val = *p >> 8; - p+= step; - if (val > 0x7fff) - val = 0x7fff; - else if (val < -32768) - val = -32768; - out[out_idx] = (val>>8) + 128; - out_idx = (out_idx + 1) & out_mask; - } - } - } -} - - -/* -=============================================================================== - -CHANNEL MIXING - -=============================================================================== -*/ - -static void S_PaintChannelFrom16( channel_t *ch, const sfx_t *sc, int count, int sampleOffset, int bufferOffset ) { - int data, aoff, boff; - int leftvol, rightvol; - int i, j; - portable_samplepair_t *samp; - sndBuffer *chunk; - short *samples; - float ooff, fdata, fdiv, fleftvol, frightvol; - - samp = &paintbuffer[ bufferOffset ]; - - if (ch->doppler) { - sampleOffset = sampleOffset*ch->oldDopplerScale; - } - - chunk = sc->soundData; - while (sampleOffset>=SND_CHUNK_SIZE) { - chunk = chunk->next; - sampleOffset -= SND_CHUNK_SIZE; - if (!chunk) { - chunk = sc->soundData; - } - } - - if (!ch->doppler || ch->dopplerScale==1.0f) { -#if idppc_altivec - vector signed short volume_vec; - vector unsigned int volume_shift; - int vectorCount, samplesLeft, chunkSamplesLeft; -#endif - leftvol = ch->leftvol*snd_vol; - rightvol = ch->rightvol*snd_vol; - samples = chunk->sndChunk; -#if idppc_altivec - ((short *)&volume_vec)[0] = leftvol; - ((short *)&volume_vec)[1] = leftvol; - ((short *)&volume_vec)[4] = leftvol; - ((short *)&volume_vec)[5] = leftvol; - ((short *)&volume_vec)[2] = rightvol; - ((short *)&volume_vec)[3] = rightvol; - ((short *)&volume_vec)[6] = rightvol; - ((short *)&volume_vec)[7] = rightvol; - volume_shift = vec_splat_u32(8); - i = 0; - - while(i < count) { - /* Try to align destination to 16-byte boundary */ - while(i < count && (((unsigned long)&samp[i] & 0x1f) || ((count-i) < 8) || ((SND_CHUNK_SIZE - sampleOffset) < 8))) { - data = samples[sampleOffset++]; - samp[i].left += (data * leftvol)>>8; - samp[i].right += (data * rightvol)>>8; - - if (sampleOffset == SND_CHUNK_SIZE) { - chunk = chunk->next; - samples = chunk->sndChunk; - sampleOffset = 0; - } - i++; - } - /* Destination is now aligned. Process as many 8-sample - chunks as we can before we run out of room from the current - sound chunk. We do 8 per loop to avoid extra source data reads. */ - samplesLeft = count - i; - chunkSamplesLeft = SND_CHUNK_SIZE - sampleOffset; - if(samplesLeft > chunkSamplesLeft) - samplesLeft = chunkSamplesLeft; - - vectorCount = samplesLeft / 8; - - if(vectorCount) - { - vector unsigned char tmp; - vector short s0, s1, sampleData0, sampleData1; - vector short samples0, samples1; - vector signed int left0, right0; - vector signed int merge0, merge1; - vector signed int d0, d1, d2, d3; - vector unsigned char samplePermute0 = - (vector unsigned char)(0, 1, 4, 5, 0, 1, 4, 5, 2, 3, 6, 7, 2, 3, 6, 7); - vector unsigned char samplePermute1 = - (vector unsigned char)(8, 9, 12, 13, 8, 9, 12, 13, 10, 11, 14, 15, 10, 11, 14, 15); - vector unsigned char loadPermute0, loadPermute1; - - // Rather than permute the vectors after we load them to do the sample - // replication and rearrangement, we permute the alignment vector so - // we do everything in one step below and avoid data shuffling. - tmp = vec_lvsl(0,&samples[sampleOffset]); - loadPermute0 = vec_perm(tmp,tmp,samplePermute0); - loadPermute1 = vec_perm(tmp,tmp,samplePermute1); - - s0 = *(vector short *)&samples[sampleOffset]; - while(vectorCount) - { - /* Load up source (16-bit) sample data */ - s1 = *(vector short *)&samples[sampleOffset+7]; - - /* Load up destination sample data */ - d0 = *(vector signed int *)&samp[i]; - d1 = *(vector signed int *)&samp[i+2]; - d2 = *(vector signed int *)&samp[i+4]; - d3 = *(vector signed int *)&samp[i+6]; - - sampleData0 = vec_perm(s0,s1,loadPermute0); - sampleData1 = vec_perm(s0,s1,loadPermute1); - - merge0 = vec_mule(sampleData0,volume_vec); - merge0 = vec_sra(merge0,volume_shift); /* Shift down to proper range */ - - merge1 = vec_mulo(sampleData0,volume_vec); - merge1 = vec_sra(merge1,volume_shift); - - d0 = vec_add(merge0,d0); - d1 = vec_add(merge1,d1); - - merge0 = vec_mule(sampleData1,volume_vec); - merge0 = vec_sra(merge0,volume_shift); /* Shift down to proper range */ - - merge1 = vec_mulo(sampleData1,volume_vec); - merge1 = vec_sra(merge1,volume_shift); - - d2 = vec_add(merge0,d2); - d3 = vec_add(merge1,d3); - - /* Store destination sample data */ - *(vector signed int *)&samp[i] = d0; - *(vector signed int *)&samp[i+2] = d1; - *(vector signed int *)&samp[i+4] = d2; - *(vector signed int *)&samp[i+6] = d3; - - i += 8; - vectorCount--; - s0 = s1; - sampleOffset += 8; - } - if (sampleOffset == SND_CHUNK_SIZE) { - chunk = chunk->next; - samples = chunk->sndChunk; - sampleOffset = 0; - } - } - } -#else - for ( i=0 ; i>8; - samp[i].right += (data * rightvol)>>8; - - if (sampleOffset == SND_CHUNK_SIZE) { - chunk = chunk->next; - samples = chunk->sndChunk; - sampleOffset = 0; - } - } -#endif - } else { - fleftvol = ch->leftvol*snd_vol; - frightvol = ch->rightvol*snd_vol; - - ooff = sampleOffset; - samples = chunk->sndChunk; - - - - - for ( i=0 ; idopplerScale; - boff = ooff; - fdata = 0; - for (j=aoff; jnext; - if (!chunk) { - chunk = sc->soundData; - } - samples = chunk->sndChunk; - ooff -= SND_CHUNK_SIZE; - } - fdata += samples[j&(SND_CHUNK_SIZE-1)]; - } - fdiv = 256 * (boff-aoff); - samp[i].left += (fdata * fleftvol)/fdiv; - samp[i].right += (fdata * frightvol)/fdiv; - } - } -} - -void S_PaintChannelFromWavelet( channel_t *ch, sfx_t *sc, int count, int sampleOffset, int bufferOffset ) { - int data; - int leftvol, rightvol; - int i; - portable_samplepair_t *samp; - sndBuffer *chunk; - short *samples; - - leftvol = ch->leftvol*snd_vol; - rightvol = ch->rightvol*snd_vol; - - i = 0; - samp = &paintbuffer[ bufferOffset ]; - chunk = sc->soundData; - while (sampleOffset>=(SND_CHUNK_SIZE_FLOAT*4)) { - chunk = chunk->next; - sampleOffset -= (SND_CHUNK_SIZE_FLOAT*4); - i++; - } - - if (i!=sfxScratchIndex || sfxScratchPointer != sc) { - S_AdpcmGetSamples( chunk, sfxScratchBuffer ); - sfxScratchIndex = i; - sfxScratchPointer = sc; - } - - samples = sfxScratchBuffer; - - for ( i=0 ; i>8; - samp[i].right += (data * rightvol)>>8; - - if (sampleOffset == SND_CHUNK_SIZE*2) { - chunk = chunk->next; - decodeWavelet(chunk, sfxScratchBuffer); - sfxScratchIndex++; - sampleOffset = 0; - } - } -} - -void S_PaintChannelFromADPCM( channel_t *ch, sfx_t *sc, int count, int sampleOffset, int bufferOffset ) { - int data; - int leftvol, rightvol; - int i; - portable_samplepair_t *samp; - sndBuffer *chunk; - short *samples; - - leftvol = ch->leftvol*snd_vol; - rightvol = ch->rightvol*snd_vol; - - i = 0; - samp = &paintbuffer[ bufferOffset ]; - chunk = sc->soundData; - - if (ch->doppler) { - sampleOffset = sampleOffset*ch->oldDopplerScale; - } - - while (sampleOffset>=(SND_CHUNK_SIZE*4)) { - chunk = chunk->next; - sampleOffset -= (SND_CHUNK_SIZE*4); - i++; - } - - if (i!=sfxScratchIndex || sfxScratchPointer != sc) { - S_AdpcmGetSamples( chunk, sfxScratchBuffer ); - sfxScratchIndex = i; - sfxScratchPointer = sc; - } - - samples = sfxScratchBuffer; - - for ( i=0 ; i>8; - samp[i].right += (data * rightvol)>>8; - - if (sampleOffset == SND_CHUNK_SIZE*4) { - chunk = chunk->next; - S_AdpcmGetSamples( chunk, sfxScratchBuffer); - sampleOffset = 0; - sfxScratchIndex++; - } - } -} - -void S_PaintChannelFromMuLaw( channel_t *ch, sfx_t *sc, int count, int sampleOffset, int bufferOffset ) { - int data; - int leftvol, rightvol; - int i; - portable_samplepair_t *samp; - sndBuffer *chunk; - byte *samples; - float ooff; - - leftvol = ch->leftvol*snd_vol; - rightvol = ch->rightvol*snd_vol; - - samp = &paintbuffer[ bufferOffset ]; - chunk = sc->soundData; - while (sampleOffset>=(SND_CHUNK_SIZE*2)) { - chunk = chunk->next; - sampleOffset -= (SND_CHUNK_SIZE*2); - if (!chunk) { - chunk = sc->soundData; - } - } - - if (!ch->doppler) { - samples = (byte *)chunk->sndChunk + sampleOffset; - for ( i=0 ; i>8; - samp[i].right += (data * rightvol)>>8; - samples++; - if (samples == (byte *)chunk->sndChunk+(SND_CHUNK_SIZE*2)) { - chunk = chunk->next; - samples = (byte *)chunk->sndChunk; - } - } - } else { - ooff = sampleOffset; - samples = (byte *)chunk->sndChunk; - for ( i=0 ; idopplerScale; - samp[i].left += (data * leftvol)>>8; - samp[i].right += (data * rightvol)>>8; - if (ooff >= SND_CHUNK_SIZE*2) { - chunk = chunk->next; - if (!chunk) { - chunk = sc->soundData; - } - samples = (byte *)chunk->sndChunk; - ooff = 0.0; - } - } - } -} - -/* -=================== -S_PaintChannels -=================== -*/ -void S_PaintChannels( int endtime ) { - int i; - int end; - channel_t *ch; - sfx_t *sc; - int ltime, count; - int sampleOffset; - - - snd_vol = s_volume->value*255; - -//Com_Printf ("%i to %i\n", s_paintedtime, endtime); - while ( s_paintedtime < endtime ) { - // if paintbuffer is smaller than DMA buffer - // we may need to fill it multiple times - end = endtime; - if ( endtime - s_paintedtime > PAINTBUFFER_SIZE ) { - end = s_paintedtime + PAINTBUFFER_SIZE; - } - - // clear the paint buffer to either music or zeros - if ( s_rawend < s_paintedtime ) { - if ( s_rawend ) { - //Com_DPrintf ("background sound underrun\n"); - } - Com_Memset(paintbuffer, 0, (end - s_paintedtime) * sizeof(portable_samplepair_t)); - } else { - // copy from the streaming sound source - int s; - int stop; - - stop = (end < s_rawend) ? end : s_rawend; - - for ( i = s_paintedtime ; i < stop ; i++ ) { - s = i&(MAX_RAW_SAMPLES-1); - paintbuffer[i-s_paintedtime] = s_rawsamples[s]; - } -// if (i != end) -// Com_Printf ("partial stream\n"); -// else -// Com_Printf ("full stream\n"); - for ( ; i < end ; i++ ) { - paintbuffer[i-s_paintedtime].left = - paintbuffer[i-s_paintedtime].right = 0; - } - } - - // paint in the channels. - ch = s_channels; - for ( i = 0; i < MAX_CHANNELS ; i++, ch++ ) { - if ( !ch->thesfx || (ch->leftvol<0.25 && ch->rightvol<0.25 )) { - continue; - } - - ltime = s_paintedtime; - sc = ch->thesfx; - - sampleOffset = ltime - ch->startSample; - count = end - ltime; - if ( sampleOffset + count > sc->soundLength ) { - count = sc->soundLength - sampleOffset; - } - - if ( count > 0 ) { - if( sc->soundCompressionMethod == 1) { - S_PaintChannelFromADPCM (ch, sc, count, sampleOffset, ltime - s_paintedtime); - } else if( sc->soundCompressionMethod == 2) { - S_PaintChannelFromWavelet (ch, sc, count, sampleOffset, ltime - s_paintedtime); - } else if( sc->soundCompressionMethod == 3) { - S_PaintChannelFromMuLaw (ch, sc, count, sampleOffset, ltime - s_paintedtime); - } else { - S_PaintChannelFrom16 (ch, sc, count, sampleOffset, ltime - s_paintedtime); - } - } - } - - // paint in the looped channels. - ch = loop_channels; - for ( i = 0; i < numLoopChannels ; i++, ch++ ) { - if ( !ch->thesfx || (!ch->leftvol && !ch->rightvol )) { - continue; - } - - ltime = s_paintedtime; - sc = ch->thesfx; - - if (sc->soundData==NULL || sc->soundLength==0) { - continue; - } - // we might have to make two passes if it - // is a looping sound effect and the end of - // the sample is hit - do { - sampleOffset = (ltime % sc->soundLength); - - count = end - ltime; - if ( sampleOffset + count > sc->soundLength ) { - count = sc->soundLength - sampleOffset; - } - - if ( count > 0 ) { - if( sc->soundCompressionMethod == 1) { - S_PaintChannelFromADPCM (ch, sc, count, sampleOffset, ltime - s_paintedtime); - } else if( sc->soundCompressionMethod == 2) { - S_PaintChannelFromWavelet (ch, sc, count, sampleOffset, ltime - s_paintedtime); - } else if( sc->soundCompressionMethod == 3) { - S_PaintChannelFromMuLaw (ch, sc, count, sampleOffset, ltime - s_paintedtime); - } else { - S_PaintChannelFrom16 (ch, sc, count, sampleOffset, ltime - s_paintedtime); - } - ltime += count; - } - } while ( ltime < end); - } - - // transfer out according to DMA format - S_TransferPaintBuffer( end ); - s_paintedtime = end; - } -} +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// snd_mix.c -- portable code to mix sounds for snd_dma.c + +#include "snd_local.h" + +static portable_samplepair_t paintbuffer[PAINTBUFFER_SIZE]; +static int snd_vol; + +// bk001119 - these not static, required by unix/snd_mixa.s +int* snd_p; +int snd_linear_count; +short* snd_out; + +#if !( (defined __linux__ || defined __FreeBSD__ ) && (defined __i386__) ) // rb010123 +#if !id386 + +void S_WriteLinearBlastStereo16 (void) +{ + int i; + int val; + + for (i=0 ; i>8; + if (val > 0x7fff) + snd_out[i] = 0x7fff; + else if (val < -32768) + snd_out[i] = -32768; + else + snd_out[i] = val; + + val = snd_p[i+1]>>8; + if (val > 0x7fff) + snd_out[i+1] = 0x7fff; + else if (val < -32768) + snd_out[i+1] = -32768; + else + snd_out[i+1] = val; + } +} +#else + +__declspec( naked ) void S_WriteLinearBlastStereo16 (void) +{ + __asm { + + push edi + push ebx + mov ecx,ds:dword ptr[snd_linear_count] + mov ebx,ds:dword ptr[snd_p] + mov edi,ds:dword ptr[snd_out] +LWLBLoopTop: + mov eax,ds:dword ptr[-8+ebx+ecx*4] + sar eax,8 + cmp eax,07FFFh + jg LClampHigh + cmp eax,0FFFF8000h + jnl LClampDone + mov eax,0FFFF8000h + jmp LClampDone +LClampHigh: + mov eax,07FFFh +LClampDone: + mov edx,ds:dword ptr[-4+ebx+ecx*4] + sar edx,8 + cmp edx,07FFFh + jg LClampHigh2 + cmp edx,0FFFF8000h + jnl LClampDone2 + mov edx,0FFFF8000h + jmp LClampDone2 +LClampHigh2: + mov edx,07FFFh +LClampDone2: + shl edx,16 + and eax,0FFFFh + or edx,eax + mov ds:dword ptr[-4+edi+ecx*2],edx + sub ecx,2 + jnz LWLBLoopTop + pop ebx + pop edi + ret + } +} + +#endif +#else +// forward declare, implementation somewhere else +void S_WriteLinearBlastStereo16 (void); +#endif + +void S_TransferStereo16 (unsigned long *pbuf, int endtime) +{ + int lpos; + int ls_paintedtime; + + snd_p = (int *) paintbuffer; + ls_paintedtime = s_paintedtime; + + while (ls_paintedtime < endtime) + { + // handle recirculating buffer issues + lpos = ls_paintedtime & ((dma.samples>>1)-1); + + snd_out = (short *) pbuf + (lpos<<1); + + snd_linear_count = (dma.samples>>1) - lpos; + if (ls_paintedtime + snd_linear_count > endtime) + snd_linear_count = endtime - ls_paintedtime; + + snd_linear_count <<= 1; + + // write a linear blast of samples + S_WriteLinearBlastStereo16 (); + + snd_p += snd_linear_count; + ls_paintedtime += (snd_linear_count>>1); + } +} + +/* +=================== +S_TransferPaintBuffer + +=================== +*/ +void S_TransferPaintBuffer(int endtime) +{ + int out_idx; + int count; + int out_mask; + int *p; + int step; + int val; + unsigned long *pbuf; + + pbuf = (unsigned long *)dma.buffer; + + + if ( s_testsound->integer ) { + int i; + int count; + + // write a fixed sine wave + count = (endtime - s_paintedtime); + for (i=0 ; i> 8; + p+= step; + if (val > 0x7fff) + val = 0x7fff; + else if (val < -32768) + val = -32768; + out[out_idx] = val; + out_idx = (out_idx + 1) & out_mask; + } + } + else if (dma.samplebits == 8) + { + unsigned char *out = (unsigned char *) pbuf; + while (count--) + { + val = *p >> 8; + p+= step; + if (val > 0x7fff) + val = 0x7fff; + else if (val < -32768) + val = -32768; + out[out_idx] = (val>>8) + 128; + out_idx = (out_idx + 1) & out_mask; + } + } + } +} + + +/* +=============================================================================== + +CHANNEL MIXING + +=============================================================================== +*/ + +static void S_PaintChannelFrom16( channel_t *ch, const sfx_t *sc, int count, int sampleOffset, int bufferOffset ) { + int data, aoff, boff; + int leftvol, rightvol; + int i, j; + portable_samplepair_t *samp; + sndBuffer *chunk; + short *samples; + float ooff, fdata, fdiv, fleftvol, frightvol; + + samp = &paintbuffer[ bufferOffset ]; + + if (ch->doppler) { + sampleOffset = sampleOffset*ch->oldDopplerScale; + } + + chunk = sc->soundData; + while (sampleOffset>=SND_CHUNK_SIZE) { + chunk = chunk->next; + sampleOffset -= SND_CHUNK_SIZE; + if (!chunk) { + chunk = sc->soundData; + } + } + + if (!ch->doppler || ch->dopplerScale==1.0f) { +#if idppc_altivec + vector signed short volume_vec; + vector unsigned int volume_shift; + int vectorCount, samplesLeft, chunkSamplesLeft; +#endif + leftvol = ch->leftvol*snd_vol; + rightvol = ch->rightvol*snd_vol; + samples = chunk->sndChunk; +#if idppc_altivec + ((short *)&volume_vec)[0] = leftvol; + ((short *)&volume_vec)[1] = leftvol; + ((short *)&volume_vec)[4] = leftvol; + ((short *)&volume_vec)[5] = leftvol; + ((short *)&volume_vec)[2] = rightvol; + ((short *)&volume_vec)[3] = rightvol; + ((short *)&volume_vec)[6] = rightvol; + ((short *)&volume_vec)[7] = rightvol; + volume_shift = vec_splat_u32(8); + i = 0; + + while(i < count) { + /* Try to align destination to 16-byte boundary */ + while(i < count && (((unsigned long)&samp[i] & 0x1f) || ((count-i) < 8) || ((SND_CHUNK_SIZE - sampleOffset) < 8))) { + data = samples[sampleOffset++]; + samp[i].left += (data * leftvol)>>8; + samp[i].right += (data * rightvol)>>8; + + if (sampleOffset == SND_CHUNK_SIZE) { + chunk = chunk->next; + samples = chunk->sndChunk; + sampleOffset = 0; + } + i++; + } + /* Destination is now aligned. Process as many 8-sample + chunks as we can before we run out of room from the current + sound chunk. We do 8 per loop to avoid extra source data reads. */ + samplesLeft = count - i; + chunkSamplesLeft = SND_CHUNK_SIZE - sampleOffset; + if(samplesLeft > chunkSamplesLeft) + samplesLeft = chunkSamplesLeft; + + vectorCount = samplesLeft / 8; + + if(vectorCount) + { + vector unsigned char tmp; + vector short s0, s1, sampleData0, sampleData1; + vector short samples0, samples1; + vector signed int left0, right0; + vector signed int merge0, merge1; + vector signed int d0, d1, d2, d3; + vector unsigned char samplePermute0 = + (vector unsigned char)(0, 1, 4, 5, 0, 1, 4, 5, 2, 3, 6, 7, 2, 3, 6, 7); + vector unsigned char samplePermute1 = + (vector unsigned char)(8, 9, 12, 13, 8, 9, 12, 13, 10, 11, 14, 15, 10, 11, 14, 15); + vector unsigned char loadPermute0, loadPermute1; + + // Rather than permute the vectors after we load them to do the sample + // replication and rearrangement, we permute the alignment vector so + // we do everything in one step below and avoid data shuffling. + tmp = vec_lvsl(0,&samples[sampleOffset]); + loadPermute0 = vec_perm(tmp,tmp,samplePermute0); + loadPermute1 = vec_perm(tmp,tmp,samplePermute1); + + s0 = *(vector short *)&samples[sampleOffset]; + while(vectorCount) + { + /* Load up source (16-bit) sample data */ + s1 = *(vector short *)&samples[sampleOffset+7]; + + /* Load up destination sample data */ + d0 = *(vector signed int *)&samp[i]; + d1 = *(vector signed int *)&samp[i+2]; + d2 = *(vector signed int *)&samp[i+4]; + d3 = *(vector signed int *)&samp[i+6]; + + sampleData0 = vec_perm(s0,s1,loadPermute0); + sampleData1 = vec_perm(s0,s1,loadPermute1); + + merge0 = vec_mule(sampleData0,volume_vec); + merge0 = vec_sra(merge0,volume_shift); /* Shift down to proper range */ + + merge1 = vec_mulo(sampleData0,volume_vec); + merge1 = vec_sra(merge1,volume_shift); + + d0 = vec_add(merge0,d0); + d1 = vec_add(merge1,d1); + + merge0 = vec_mule(sampleData1,volume_vec); + merge0 = vec_sra(merge0,volume_shift); /* Shift down to proper range */ + + merge1 = vec_mulo(sampleData1,volume_vec); + merge1 = vec_sra(merge1,volume_shift); + + d2 = vec_add(merge0,d2); + d3 = vec_add(merge1,d3); + + /* Store destination sample data */ + *(vector signed int *)&samp[i] = d0; + *(vector signed int *)&samp[i+2] = d1; + *(vector signed int *)&samp[i+4] = d2; + *(vector signed int *)&samp[i+6] = d3; + + i += 8; + vectorCount--; + s0 = s1; + sampleOffset += 8; + } + if (sampleOffset == SND_CHUNK_SIZE) { + chunk = chunk->next; + samples = chunk->sndChunk; + sampleOffset = 0; + } + } + } +#else + for ( i=0 ; i>8; + samp[i].right += (data * rightvol)>>8; + + if (sampleOffset == SND_CHUNK_SIZE) { + chunk = chunk->next; + samples = chunk->sndChunk; + sampleOffset = 0; + } + } +#endif + } else { + fleftvol = ch->leftvol*snd_vol; + frightvol = ch->rightvol*snd_vol; + + ooff = sampleOffset; + samples = chunk->sndChunk; + + + + + for ( i=0 ; idopplerScale; + boff = ooff; + fdata = 0; + for (j=aoff; jnext; + if (!chunk) { + chunk = sc->soundData; + } + samples = chunk->sndChunk; + ooff -= SND_CHUNK_SIZE; + } + fdata += samples[j&(SND_CHUNK_SIZE-1)]; + } + fdiv = 256 * (boff-aoff); + samp[i].left += (fdata * fleftvol)/fdiv; + samp[i].right += (fdata * frightvol)/fdiv; + } + } +} + +void S_PaintChannelFromWavelet( channel_t *ch, sfx_t *sc, int count, int sampleOffset, int bufferOffset ) { + int data; + int leftvol, rightvol; + int i; + portable_samplepair_t *samp; + sndBuffer *chunk; + short *samples; + + leftvol = ch->leftvol*snd_vol; + rightvol = ch->rightvol*snd_vol; + + i = 0; + samp = &paintbuffer[ bufferOffset ]; + chunk = sc->soundData; + while (sampleOffset>=(SND_CHUNK_SIZE_FLOAT*4)) { + chunk = chunk->next; + sampleOffset -= (SND_CHUNK_SIZE_FLOAT*4); + i++; + } + + if (i!=sfxScratchIndex || sfxScratchPointer != sc) { + S_AdpcmGetSamples( chunk, sfxScratchBuffer ); + sfxScratchIndex = i; + sfxScratchPointer = sc; + } + + samples = sfxScratchBuffer; + + for ( i=0 ; i>8; + samp[i].right += (data * rightvol)>>8; + + if (sampleOffset == SND_CHUNK_SIZE*2) { + chunk = chunk->next; + decodeWavelet(chunk, sfxScratchBuffer); + sfxScratchIndex++; + sampleOffset = 0; + } + } +} + +void S_PaintChannelFromADPCM( channel_t *ch, sfx_t *sc, int count, int sampleOffset, int bufferOffset ) { + int data; + int leftvol, rightvol; + int i; + portable_samplepair_t *samp; + sndBuffer *chunk; + short *samples; + + leftvol = ch->leftvol*snd_vol; + rightvol = ch->rightvol*snd_vol; + + i = 0; + samp = &paintbuffer[ bufferOffset ]; + chunk = sc->soundData; + + if (ch->doppler) { + sampleOffset = sampleOffset*ch->oldDopplerScale; + } + + while (sampleOffset>=(SND_CHUNK_SIZE*4)) { + chunk = chunk->next; + sampleOffset -= (SND_CHUNK_SIZE*4); + i++; + } + + if (i!=sfxScratchIndex || sfxScratchPointer != sc) { + S_AdpcmGetSamples( chunk, sfxScratchBuffer ); + sfxScratchIndex = i; + sfxScratchPointer = sc; + } + + samples = sfxScratchBuffer; + + for ( i=0 ; i>8; + samp[i].right += (data * rightvol)>>8; + + if (sampleOffset == SND_CHUNK_SIZE*4) { + chunk = chunk->next; + S_AdpcmGetSamples( chunk, sfxScratchBuffer); + sampleOffset = 0; + sfxScratchIndex++; + } + } +} + +void S_PaintChannelFromMuLaw( channel_t *ch, sfx_t *sc, int count, int sampleOffset, int bufferOffset ) { + int data; + int leftvol, rightvol; + int i; + portable_samplepair_t *samp; + sndBuffer *chunk; + byte *samples; + float ooff; + + leftvol = ch->leftvol*snd_vol; + rightvol = ch->rightvol*snd_vol; + + samp = &paintbuffer[ bufferOffset ]; + chunk = sc->soundData; + while (sampleOffset>=(SND_CHUNK_SIZE*2)) { + chunk = chunk->next; + sampleOffset -= (SND_CHUNK_SIZE*2); + if (!chunk) { + chunk = sc->soundData; + } + } + + if (!ch->doppler) { + samples = (byte *)chunk->sndChunk + sampleOffset; + for ( i=0 ; i>8; + samp[i].right += (data * rightvol)>>8; + samples++; + if (samples == (byte *)chunk->sndChunk+(SND_CHUNK_SIZE*2)) { + chunk = chunk->next; + samples = (byte *)chunk->sndChunk; + } + } + } else { + ooff = sampleOffset; + samples = (byte *)chunk->sndChunk; + for ( i=0 ; idopplerScale; + samp[i].left += (data * leftvol)>>8; + samp[i].right += (data * rightvol)>>8; + if (ooff >= SND_CHUNK_SIZE*2) { + chunk = chunk->next; + if (!chunk) { + chunk = sc->soundData; + } + samples = (byte *)chunk->sndChunk; + ooff = 0.0; + } + } + } +} + +/* +=================== +S_PaintChannels +=================== +*/ +void S_PaintChannels( int endtime ) { + int i; + int end; + channel_t *ch; + sfx_t *sc; + int ltime, count; + int sampleOffset; + + + snd_vol = s_volume->value*255; + +//Com_Printf ("%i to %i\n", s_paintedtime, endtime); + while ( s_paintedtime < endtime ) { + // if paintbuffer is smaller than DMA buffer + // we may need to fill it multiple times + end = endtime; + if ( endtime - s_paintedtime > PAINTBUFFER_SIZE ) { + end = s_paintedtime + PAINTBUFFER_SIZE; + } + + // clear the paint buffer to either music or zeros + if ( s_rawend < s_paintedtime ) { + if ( s_rawend ) { + //Com_DPrintf ("background sound underrun\n"); + } + Com_Memset(paintbuffer, 0, (end - s_paintedtime) * sizeof(portable_samplepair_t)); + } else { + // copy from the streaming sound source + int s; + int stop; + + stop = (end < s_rawend) ? end : s_rawend; + + for ( i = s_paintedtime ; i < stop ; i++ ) { + s = i&(MAX_RAW_SAMPLES-1); + paintbuffer[i-s_paintedtime] = s_rawsamples[s]; + } +// if (i != end) +// Com_Printf ("partial stream\n"); +// else +// Com_Printf ("full stream\n"); + for ( ; i < end ; i++ ) { + paintbuffer[i-s_paintedtime].left = + paintbuffer[i-s_paintedtime].right = 0; + } + } + + // paint in the channels. + ch = s_channels; + for ( i = 0; i < MAX_CHANNELS ; i++, ch++ ) { + if ( !ch->thesfx || (ch->leftvol<0.25 && ch->rightvol<0.25 )) { + continue; + } + + ltime = s_paintedtime; + sc = ch->thesfx; + + sampleOffset = ltime - ch->startSample; + count = end - ltime; + if ( sampleOffset + count > sc->soundLength ) { + count = sc->soundLength - sampleOffset; + } + + if ( count > 0 ) { + if( sc->soundCompressionMethod == 1) { + S_PaintChannelFromADPCM (ch, sc, count, sampleOffset, ltime - s_paintedtime); + } else if( sc->soundCompressionMethod == 2) { + S_PaintChannelFromWavelet (ch, sc, count, sampleOffset, ltime - s_paintedtime); + } else if( sc->soundCompressionMethod == 3) { + S_PaintChannelFromMuLaw (ch, sc, count, sampleOffset, ltime - s_paintedtime); + } else { + S_PaintChannelFrom16 (ch, sc, count, sampleOffset, ltime - s_paintedtime); + } + } + } + + // paint in the looped channels. + ch = loop_channels; + for ( i = 0; i < numLoopChannels ; i++, ch++ ) { + if ( !ch->thesfx || (!ch->leftvol && !ch->rightvol )) { + continue; + } + + ltime = s_paintedtime; + sc = ch->thesfx; + + if (sc->soundData==NULL || sc->soundLength==0) { + continue; + } + // we might have to make two passes if it + // is a looping sound effect and the end of + // the sample is hit + do { + sampleOffset = (ltime % sc->soundLength); + + count = end - ltime; + if ( sampleOffset + count > sc->soundLength ) { + count = sc->soundLength - sampleOffset; + } + + if ( count > 0 ) { + if( sc->soundCompressionMethod == 1) { + S_PaintChannelFromADPCM (ch, sc, count, sampleOffset, ltime - s_paintedtime); + } else if( sc->soundCompressionMethod == 2) { + S_PaintChannelFromWavelet (ch, sc, count, sampleOffset, ltime - s_paintedtime); + } else if( sc->soundCompressionMethod == 3) { + S_PaintChannelFromMuLaw (ch, sc, count, sampleOffset, ltime - s_paintedtime); + } else { + S_PaintChannelFrom16 (ch, sc, count, sampleOffset, ltime - s_paintedtime); + } + ltime += count; + } + } while ( ltime < end); + } + + // transfer out according to DMA format + S_TransferPaintBuffer( end ); + s_paintedtime = end; + } +} diff --git a/code/client/snd_public.h b/code/client/snd_public.h index 438fc53..659d235 100755 --- a/code/client/snd_public.h +++ b/code/client/snd_public.h @@ -1,72 +1,72 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - - -void S_Init( void ); -void S_Shutdown( void ); - -// if origin is NULL, the sound will be dynamically sourced from the entity -void S_StartSound( vec3_t origin, int entnum, int entchannel, sfxHandle_t sfx ); -void S_StartLocalSound( sfxHandle_t sfx, int channelNum ); - -void S_StartBackgroundTrack( const char *intro, const char *loop ); -void S_StopBackgroundTrack( void ); - -// cinematics and voice-over-network will send raw samples -// 1.0 volume will be direct output of source samples -void S_RawSamples (int samples, int rate, int width, int channels, - const byte *data, float volume); - -// stop all sounds and the background track -void S_StopAllSounds( void ); - -// all continuous looping sounds must be added before calling S_Update -void S_ClearLoopingSounds( qboolean killall ); -void S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); -void S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); -void S_StopLoopingSound(int entityNum ); - -// recompute the reletive volumes for all running sounds -// reletive to the given entityNum / orientation -void S_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ); - -// let the sound system know where an entity currently is -void S_UpdateEntityPosition( int entityNum, const vec3_t origin ); - -void S_Update( void ); - -void S_DisableSounds( void ); - -void S_BeginRegistration( void ); - -// RegisterSound will allways return a valid sample, even if it -// has to create a placeholder. This prevents continuous filesystem -// checks for missing files -sfxHandle_t S_RegisterSound( const char *sample, qboolean compressed ); - -void S_DisplayFreeMemory(void); - -void S_ClearSoundBuffer( void ); - -void SNDDMA_Activate( void ); - -void S_UpdateBackgroundTrack( void ); +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + + +void S_Init( void ); +void S_Shutdown( void ); + +// if origin is NULL, the sound will be dynamically sourced from the entity +void S_StartSound( vec3_t origin, int entnum, int entchannel, sfxHandle_t sfx ); +void S_StartLocalSound( sfxHandle_t sfx, int channelNum ); + +void S_StartBackgroundTrack( const char *intro, const char *loop ); +void S_StopBackgroundTrack( void ); + +// cinematics and voice-over-network will send raw samples +// 1.0 volume will be direct output of source samples +void S_RawSamples (int samples, int rate, int width, int channels, + const byte *data, float volume); + +// stop all sounds and the background track +void S_StopAllSounds( void ); + +// all continuous looping sounds must be added before calling S_Update +void S_ClearLoopingSounds( qboolean killall ); +void S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); +void S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); +void S_StopLoopingSound(int entityNum ); + +// recompute the reletive volumes for all running sounds +// reletive to the given entityNum / orientation +void S_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ); + +// let the sound system know where an entity currently is +void S_UpdateEntityPosition( int entityNum, const vec3_t origin ); + +void S_Update( void ); + +void S_DisableSounds( void ); + +void S_BeginRegistration( void ); + +// RegisterSound will allways return a valid sample, even if it +// has to create a placeholder. This prevents continuous filesystem +// checks for missing files +sfxHandle_t S_RegisterSound( const char *sample, qboolean compressed ); + +void S_DisplayFreeMemory(void); + +void S_ClearSoundBuffer( void ); + +void SNDDMA_Activate( void ); + +void S_UpdateBackgroundTrack( void ); diff --git a/code/client/snd_wavelet.c b/code/client/snd_wavelet.c index 9b07ee1..29b2608 100755 --- a/code/client/snd_wavelet.c +++ b/code/client/snd_wavelet.c @@ -1,253 +1,253 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "snd_local.h" - -long myftol( float f ); - -#define C0 0.4829629131445341 -#define C1 0.8365163037378079 -#define C2 0.2241438680420134 -#define C3 -0.1294095225512604 - -void daub4(float b[], unsigned long n, int isign) -{ - float wksp[4097]; - float *a=b-1; // numerical recipies so a[1] = b[0] - - unsigned long nh,nh1,i,j; - - if (n < 4) return; - - nh1=(nh=n >> 1)+1; - if (isign >= 0) { - for (i=1,j=1;j<=n-3;j+=2,i++) { - wksp[i] = C0*a[j]+C1*a[j+1]+C2*a[j+2]+C3*a[j+3]; - wksp[i+nh] = C3*a[j]-C2*a[j+1]+C1*a[j+2]-C0*a[j+3]; - } - wksp[i ] = C0*a[n-1]+C1*a[n]+C2*a[1]+C3*a[2]; - wksp[i+nh] = C3*a[n-1]-C2*a[n]+C1*a[1]-C0*a[2]; - } else { - wksp[1] = C2*a[nh]+C1*a[n]+C0*a[1]+C3*a[nh1]; - wksp[2] = C3*a[nh]-C0*a[n]+C1*a[1]-C2*a[nh1]; - for (i=1,j=3;i= 0) { - for (nn=n;nn>=inverseStartLength;nn>>=1) daub4(a,nn,isign); - } else { - for (nn=inverseStartLength;nn<=n;nn<<=1) daub4(a,nn,isign); - } -} - -/* The number of bits required by each value */ -static unsigned char numBits[] = { - 0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, - 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, - 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, - 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, -}; - -byte MuLawEncode(short s) { - unsigned long adjusted; - byte sign, exponent, mantissa; - - sign = (s<0)?0:0x80; - - if (s<0) s=-s; - adjusted = (long)s << (16-sizeof(short)*8); - adjusted += 128L + 4L; - if (adjusted > 32767) adjusted = 32767; - exponent = numBits[(adjusted>>7)&0xff] - 1; - mantissa = (adjusted>>(exponent+3))&0xf; - return ~(sign | (exponent<<4) | mantissa); -} - -short MuLawDecode(byte uLaw) { - signed long adjusted; - byte exponent, mantissa; - - uLaw = ~uLaw; - exponent = (uLaw>>4) & 0x7; - mantissa = (uLaw&0xf) + 16; - adjusted = (mantissa << (exponent +3)) - 128 - 4; - - return (uLaw & 0x80)? adjusted : -adjusted; -} - -short mulawToShort[256]; -static qboolean madeTable = qfalse; - -static int NXStreamCount; - -void NXPutc(NXStream *stream, char out) { - stream[NXStreamCount++] = out; -} - - -void encodeWavelet( sfx_t *sfx, short *packets) { - float wksp[4097], temp; - int i, samples, size; - sndBuffer *newchunk, *chunk; - byte *out; - - if (!madeTable) { - for (i=0;i<256;i++) { - mulawToShort[i] = (float)MuLawDecode((byte)i); - } - madeTable = qtrue; - } - chunk = NULL; - - samples = sfx->soundLength; - while(samples>0) { - size = samples; - if (size>(SND_CHUNK_SIZE*2)) { - size = (SND_CHUNK_SIZE*2); - } - - if (size<4) { - size = 4; - } - - newchunk = SND_malloc(); - if (sfx->soundData == NULL) { - sfx->soundData = newchunk; - } else { - chunk->next = newchunk; - } - chunk = newchunk; - for(i=0; isndChunk; - - for(i=0;i 32767) temp = 32767; else if (temp<-32768) temp = -32768; - out[i] = MuLawEncode((short)temp); - } - - chunk->size = size; - samples -= size; - } -} - -void decodeWavelet(sndBuffer *chunk, short *to) { - float wksp[4097]; - int i; - byte *out; - - int size = chunk->size; - - out = (byte *)chunk->sndChunk; - for(i=0;isoundLength; - grade = 0; - - while(samples>0) { - size = samples; - if (size>(SND_CHUNK_SIZE*2)) { - size = (SND_CHUNK_SIZE*2); - } - - newchunk = SND_malloc(); - if (sfx->soundData == NULL) { - sfx->soundData = newchunk; - } else { - chunk->next = newchunk; - } - chunk = newchunk; - out = (byte *)chunk->sndChunk; - for(i=0; i32767) { - poop = 32767; - } else if (poop<-32768) { - poop = -32768; - } - out[i] = MuLawEncode((short)poop); - grade = poop - mulawToShort[out[i]]; - packets++; - } - chunk->size = size; - samples -= size; - } -} - -void decodeMuLaw(sndBuffer *chunk, short *to) { - int i; - byte *out; - - int size = chunk->size; - - out = (byte *)chunk->sndChunk; - for(i=0;i> 1)+1; + if (isign >= 0) { + for (i=1,j=1;j<=n-3;j+=2,i++) { + wksp[i] = C0*a[j]+C1*a[j+1]+C2*a[j+2]+C3*a[j+3]; + wksp[i+nh] = C3*a[j]-C2*a[j+1]+C1*a[j+2]-C0*a[j+3]; + } + wksp[i ] = C0*a[n-1]+C1*a[n]+C2*a[1]+C3*a[2]; + wksp[i+nh] = C3*a[n-1]-C2*a[n]+C1*a[1]-C0*a[2]; + } else { + wksp[1] = C2*a[nh]+C1*a[n]+C0*a[1]+C3*a[nh1]; + wksp[2] = C3*a[nh]-C0*a[n]+C1*a[1]-C2*a[nh1]; + for (i=1,j=3;i= 0) { + for (nn=n;nn>=inverseStartLength;nn>>=1) daub4(a,nn,isign); + } else { + for (nn=inverseStartLength;nn<=n;nn<<=1) daub4(a,nn,isign); + } +} + +/* The number of bits required by each value */ +static unsigned char numBits[] = { + 0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, +}; + +byte MuLawEncode(short s) { + unsigned long adjusted; + byte sign, exponent, mantissa; + + sign = (s<0)?0:0x80; + + if (s<0) s=-s; + adjusted = (long)s << (16-sizeof(short)*8); + adjusted += 128L + 4L; + if (adjusted > 32767) adjusted = 32767; + exponent = numBits[(adjusted>>7)&0xff] - 1; + mantissa = (adjusted>>(exponent+3))&0xf; + return ~(sign | (exponent<<4) | mantissa); +} + +short MuLawDecode(byte uLaw) { + signed long adjusted; + byte exponent, mantissa; + + uLaw = ~uLaw; + exponent = (uLaw>>4) & 0x7; + mantissa = (uLaw&0xf) + 16; + adjusted = (mantissa << (exponent +3)) - 128 - 4; + + return (uLaw & 0x80)? adjusted : -adjusted; +} + +short mulawToShort[256]; +static qboolean madeTable = qfalse; + +static int NXStreamCount; + +void NXPutc(NXStream *stream, char out) { + stream[NXStreamCount++] = out; +} + + +void encodeWavelet( sfx_t *sfx, short *packets) { + float wksp[4097], temp; + int i, samples, size; + sndBuffer *newchunk, *chunk; + byte *out; + + if (!madeTable) { + for (i=0;i<256;i++) { + mulawToShort[i] = (float)MuLawDecode((byte)i); + } + madeTable = qtrue; + } + chunk = NULL; + + samples = sfx->soundLength; + while(samples>0) { + size = samples; + if (size>(SND_CHUNK_SIZE*2)) { + size = (SND_CHUNK_SIZE*2); + } + + if (size<4) { + size = 4; + } + + newchunk = SND_malloc(); + if (sfx->soundData == NULL) { + sfx->soundData = newchunk; + } else { + chunk->next = newchunk; + } + chunk = newchunk; + for(i=0; isndChunk; + + for(i=0;i 32767) temp = 32767; else if (temp<-32768) temp = -32768; + out[i] = MuLawEncode((short)temp); + } + + chunk->size = size; + samples -= size; + } +} + +void decodeWavelet(sndBuffer *chunk, short *to) { + float wksp[4097]; + int i; + byte *out; + + int size = chunk->size; + + out = (byte *)chunk->sndChunk; + for(i=0;isoundLength; + grade = 0; + + while(samples>0) { + size = samples; + if (size>(SND_CHUNK_SIZE*2)) { + size = (SND_CHUNK_SIZE*2); + } + + newchunk = SND_malloc(); + if (sfx->soundData == NULL) { + sfx->soundData = newchunk; + } else { + chunk->next = newchunk; + } + chunk = newchunk; + out = (byte *)chunk->sndChunk; + for(i=0; i32767) { + poop = 32767; + } else if (poop<-32768) { + poop = -32768; + } + out[i] = MuLawEncode((short)poop); + grade = poop - mulawToShort[out[i]]; + packets++; + } + chunk->size = size; + samples -= size; + } +} + +void decodeMuLaw(sndBuffer *chunk, short *to) { + int i; + byte *out; + + int size = chunk->size; + + out = (byte *)chunk->sndChunk; + for(i=0;i '#cgame:#game:#q3_ui', - CC => $CC, - CXX => $CXX, - LINK => $LINK, - ENV => { PATH => $ENV{PATH}, HOME => $ENV{HOME} }, - CFLAGS => $BASE_CFLAGS . '-fPIC', - LDFLAGS => '-shared -ldl -lm' -); - -# for TA, use -DMISSIONPACK -%ta_env_hash = $env->copy( - CPPPATH => '#cgame:#game:#ui' - ); -$ta_env_hash{CFLAGS} = '-DMISSIONPACK ' . $ta_env_hash{CFLAGS}; -$ta_env = new cons(%ta_env_hash); - -# qvm building -# we heavily customize the cons environment -$vm_env = new cons( - # the code has the very bad habit of doing things like #include "../ui/ui_shared.h" - # this seems to confuse the dependency analysis, explicit toplevel includes seem to fix - CPPPATH => '#cgame:#game:#q3_ui', - CC => 'q3lcc', - CCCOM => '%CC %CFLAGS %_IFLAGS -c %< -o %>', - SUFOBJ => '.asm', - LINK => 'q3asm', - CFLAGS => '-DQ3_VM -S -Wf-target=bytecode -Wf-g', - # need to know where to find the compiler tools - ENV => { PATH => $ENV{PATH} . ":./qvmtools", }, -); - -# TA qvm building -%vm_ta_env_hash = $vm_env->copy( - CPPPATH => '#cgame:#game:#ui' - ); -$vm_ta_env_hash{CFLAGS} = '-DMISSIONPACK ' . $vm_ta_env_hash{CFLAGS}; -$vm_ta_env = new cons(%vm_ta_env_hash); - -# the file with vmMain function MUST be the first one of the list -@FILES = qw( - g_main.c - ai_chat.c - ai_cmd.c - ai_dmnet.c - ai_dmq3.c - ai_main.c - ai_team.c - ai_vcmd.c - bg_misc.c - bg_pmove.c - bg_slidemove.c - g_active.c - g_arenas.c - g_bot.c - g_client.c - g_cmds.c - g_combat.c - g_items.c - g_mem.c - g_misc.c - g_missile.c - g_mover.c - g_session.c - g_spawn.c - g_svcmds.c - g_target.c - g_team.c - g_trigger.c - g_utils.c - g_weapon.c - q_math.c - q_shared.c - ); -$FILESREF = \@FILES; - -# only in .so -# (VM uses a custom .asm with equ stubs) -@SO_FILES = qw( - g_syscalls.c - ); -$SO_FILESREF = \@SO_FILES; - -# only for VM -@VM_FILES = qw( - bg_lib.c - g_syscalls.asm - ); -$VM_FILESREF = \@VM_FILES; - -# FIXME CPU string? -# NOTE: $env $ta_env and $vm_env $vm_ta_env may not be necessary -# we could alter the $env and $ta_env based on $TARGET_DIR -# doing it this way to ensure homogeneity with cgame building -if ($TARGET_DIR eq 'Q3') -{ - if ($NO_SO eq 0) - { - Program $env 'qagamei386.so', @$FILESREF, @$SO_FILESREF; - Install $env $INSTALL_DIR, 'qagamei386.so'; - } - if ($NO_VM eq 0) - { - Depends $vm_env 'qagame.qvm', '#qvmtools/q3lcc'; - Depends $vm_env 'qagame.qvm', '#qvmtools/q3asm'; - Program $vm_env 'qagame.qvm', @$FILESREF, @$VM_FILESREF; - Install $vm_env $INSTALL_DIR . '/vm', 'qagame.qvm'; - } -} -else -{ - if ($NO_SO eq 0) - { - Program $ta_env 'qagamei386.so', @$FILESREF, @$SO_FILESREF; - Install $ta_env $INSTALL_DIR, 'qagamei386.so'; - } - if ($NO_VM eq 0) - { - Depends $vm_env 'qagame.qvm', '#qvmtools/q3lcc'; - Depends $vm_env 'qagame.qvm', '#qvmtools/q3asm'; - Program $vm_ta_env 'qagame.qvm', @$FILESREF, @$VM_FILESREF; - Install $vm_ta_env $INSTALL_DIR . '/vm', 'qagame.qvm'; - } -} +# game building +# builds the game for vanilla Q3 and TA + +# there are slight differences between Q3 and TA build: +# -DMISSIONPACK +# the config is passed in the imported variable TARGET_DIR + +# qvm building against native: +# only native has g_syscalls.c +# only qvm has ../game/bg_lib.c +# qvm uses a custom g_syscalls.asm with equ stubs + +Import qw( BASE_CFLAGS TARGET_DIR INSTALL_DIR NO_VM NO_SO CC CXX LINK ); + +$env = new cons( + # the code has the very bad habit of doing things like #include "../ui/ui_shared.h" + # this seems to confuse the dependency analysis, explicit toplevel includes seem to fix + CPPPATH => '#cgame:#game:#q3_ui', + CC => $CC, + CXX => $CXX, + LINK => $LINK, + ENV => { PATH => $ENV{PATH}, HOME => $ENV{HOME} }, + CFLAGS => $BASE_CFLAGS . '-fPIC', + LDFLAGS => '-shared -ldl -lm' +); + +# for TA, use -DMISSIONPACK +%ta_env_hash = $env->copy( + CPPPATH => '#cgame:#game:#ui' + ); +$ta_env_hash{CFLAGS} = '-DMISSIONPACK ' . $ta_env_hash{CFLAGS}; +$ta_env = new cons(%ta_env_hash); + +# qvm building +# we heavily customize the cons environment +$vm_env = new cons( + # the code has the very bad habit of doing things like #include "../ui/ui_shared.h" + # this seems to confuse the dependency analysis, explicit toplevel includes seem to fix + CPPPATH => '#cgame:#game:#q3_ui', + CC => 'q3lcc', + CCCOM => '%CC %CFLAGS %_IFLAGS -c %< -o %>', + SUFOBJ => '.asm', + LINK => 'q3asm', + CFLAGS => '-DQ3_VM -S -Wf-target=bytecode -Wf-g', + # need to know where to find the compiler tools + ENV => { PATH => $ENV{PATH} . ":./qvmtools", }, +); + +# TA qvm building +%vm_ta_env_hash = $vm_env->copy( + CPPPATH => '#cgame:#game:#ui' + ); +$vm_ta_env_hash{CFLAGS} = '-DMISSIONPACK ' . $vm_ta_env_hash{CFLAGS}; +$vm_ta_env = new cons(%vm_ta_env_hash); + +# the file with vmMain function MUST be the first one of the list +@FILES = qw( + g_main.c + ai_chat.c + ai_cmd.c + ai_dmnet.c + ai_dmq3.c + ai_main.c + ai_team.c + ai_vcmd.c + bg_misc.c + bg_pmove.c + bg_slidemove.c + g_active.c + g_arenas.c + g_bot.c + g_client.c + g_cmds.c + g_combat.c + g_items.c + g_mem.c + g_misc.c + g_missile.c + g_mover.c + g_session.c + g_spawn.c + g_svcmds.c + g_target.c + g_team.c + g_trigger.c + g_utils.c + g_weapon.c + q_math.c + q_shared.c + ); +$FILESREF = \@FILES; + +# only in .so +# (VM uses a custom .asm with equ stubs) +@SO_FILES = qw( + g_syscalls.c + ); +$SO_FILESREF = \@SO_FILES; + +# only for VM +@VM_FILES = qw( + bg_lib.c + g_syscalls.asm + ); +$VM_FILESREF = \@VM_FILES; + +# FIXME CPU string? +# NOTE: $env $ta_env and $vm_env $vm_ta_env may not be necessary +# we could alter the $env and $ta_env based on $TARGET_DIR +# doing it this way to ensure homogeneity with cgame building +if ($TARGET_DIR eq 'Q3') +{ + if ($NO_SO eq 0) + { + Program $env 'qagamei386.so', @$FILESREF, @$SO_FILESREF; + Install $env $INSTALL_DIR, 'qagamei386.so'; + } + if ($NO_VM eq 0) + { + Depends $vm_env 'qagame.qvm', '#qvmtools/q3lcc'; + Depends $vm_env 'qagame.qvm', '#qvmtools/q3asm'; + Program $vm_env 'qagame.qvm', @$FILESREF, @$VM_FILESREF; + Install $vm_env $INSTALL_DIR . '/vm', 'qagame.qvm'; + } +} +else +{ + if ($NO_SO eq 0) + { + Program $ta_env 'qagamei386.so', @$FILESREF, @$SO_FILESREF; + Install $ta_env $INSTALL_DIR, 'qagamei386.so'; + } + if ($NO_VM eq 0) + { + Depends $vm_env 'qagame.qvm', '#qvmtools/q3lcc'; + Depends $vm_env 'qagame.qvm', '#qvmtools/q3asm'; + Program $vm_ta_env 'qagame.qvm', @$FILESREF, @$VM_FILESREF; + Install $vm_ta_env $INSTALL_DIR . '/vm', 'qagame.qvm'; + } +} diff --git a/code/game/ai_chat.c b/code/game/ai_chat.c index b5485e9..ae2554e 100755 --- a/code/game/ai_chat.c +++ b/code/game/ai_chat.c @@ -1,1226 +1,1226 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -/***************************************************************************** - * name: ai_chat.c - * - * desc: Quake3 bot AI - * - * $Archive: /MissionPack/code/game/ai_chat.c $ - * - *****************************************************************************/ - -#include "g_local.h" -#include "botlib.h" -#include "be_aas.h" -#include "be_ea.h" -#include "be_ai_char.h" -#include "be_ai_chat.h" -#include "be_ai_gen.h" -#include "be_ai_goal.h" -#include "be_ai_move.h" -#include "be_ai_weap.h" -// -#include "ai_main.h" -#include "ai_dmq3.h" -#include "ai_chat.h" -#include "ai_cmd.h" -#include "ai_dmnet.h" -// -#include "chars.h" //characteristics -#include "inv.h" //indexes into the inventory -#include "syn.h" //synonyms -#include "match.h" //string matching types and vars - -// for the voice chats -#ifdef MISSIONPACK // bk001205 -#include "../../ui/menudef.h" -#endif - -#define TIME_BETWEENCHATTING 25 - - -/* -================== -BotNumActivePlayers -================== -*/ -int BotNumActivePlayers(void) { - int i, num; - char buf[MAX_INFO_STRING]; - static int maxclients; - - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - - num = 0; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); - //if no config string or no name - if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; - //skip spectators - if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; - // - num++; - } - return num; -} - -/* -================== -BotIsFirstInRankings -================== -*/ -int BotIsFirstInRankings(bot_state_t *bs) { - int i, score; - char buf[MAX_INFO_STRING]; - static int maxclients; - playerState_t ps; - - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - - score = bs->cur_ps.persistant[PERS_SCORE]; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); - //if no config string or no name - if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; - //skip spectators - if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; - // - BotAI_GetClientState(i, &ps); - if (score < ps.persistant[PERS_SCORE]) return qfalse; - } - return qtrue; -} - -/* -================== -BotIsLastInRankings -================== -*/ -int BotIsLastInRankings(bot_state_t *bs) { - int i, score; - char buf[MAX_INFO_STRING]; - static int maxclients; - playerState_t ps; - - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - - score = bs->cur_ps.persistant[PERS_SCORE]; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); - //if no config string or no name - if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; - //skip spectators - if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; - // - BotAI_GetClientState(i, &ps); - if (score > ps.persistant[PERS_SCORE]) return qfalse; - } - return qtrue; -} - -/* -================== -BotFirstClientInRankings -================== -*/ -char *BotFirstClientInRankings(void) { - int i, bestscore, bestclient; - char buf[MAX_INFO_STRING]; - static char name[32]; - static int maxclients; - playerState_t ps; - - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - - bestscore = -999999; - bestclient = 0; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); - //if no config string or no name - if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; - //skip spectators - if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; - // - BotAI_GetClientState(i, &ps); - if (ps.persistant[PERS_SCORE] > bestscore) { - bestscore = ps.persistant[PERS_SCORE]; - bestclient = i; - } - } - EasyClientName(bestclient, name, 32); - return name; -} - -/* -================== -BotLastClientInRankings -================== -*/ -char *BotLastClientInRankings(void) { - int i, worstscore, bestclient; - char buf[MAX_INFO_STRING]; - static char name[32]; - static int maxclients; - playerState_t ps; - - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - - worstscore = 999999; - bestclient = 0; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); - //if no config string or no name - if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; - //skip spectators - if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; - // - BotAI_GetClientState(i, &ps); - if (ps.persistant[PERS_SCORE] < worstscore) { - worstscore = ps.persistant[PERS_SCORE]; - bestclient = i; - } - } - EasyClientName(bestclient, name, 32); - return name; -} - -/* -================== -BotRandomOpponentName -================== -*/ -char *BotRandomOpponentName(bot_state_t *bs) { - int i, count; - char buf[MAX_INFO_STRING]; - int opponents[MAX_CLIENTS], numopponents; - static int maxclients; - static char name[32]; - - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - - numopponents = 0; - opponents[0] = 0; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - if (i == bs->client) continue; - // - trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); - //if no config string or no name - if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; - //skip spectators - if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; - //skip team mates - if (BotSameTeam(bs, i)) continue; - // - opponents[numopponents] = i; - numopponents++; - } - count = random() * numopponents; - for (i = 0; i < numopponents; i++) { - count--; - if (count <= 0) { - EasyClientName(opponents[i], name, sizeof(name)); - return name; - } - } - EasyClientName(opponents[0], name, sizeof(name)); - return name; -} - -/* -================== -BotMapTitle -================== -*/ - -char *BotMapTitle(void) { - char info[1024]; - static char mapname[128]; - - trap_GetServerinfo(info, sizeof(info)); - - strncpy(mapname, Info_ValueForKey( info, "mapname" ), sizeof(mapname)-1); - mapname[sizeof(mapname)-1] = '\0'; - - return mapname; -} - - -/* -================== -BotWeaponNameForMeansOfDeath -================== -*/ - -char *BotWeaponNameForMeansOfDeath(int mod) { - switch(mod) { - case MOD_SHOTGUN: return "Shotgun"; - case MOD_GAUNTLET: return "Gauntlet"; - case MOD_MACHINEGUN: return "Machinegun"; - case MOD_GRENADE: - case MOD_GRENADE_SPLASH: return "Grenade Launcher"; - case MOD_ROCKET: - case MOD_ROCKET_SPLASH: return "Rocket Launcher"; - case MOD_PLASMA: - case MOD_PLASMA_SPLASH: return "Plasmagun"; - case MOD_RAILGUN: return "Railgun"; - case MOD_LIGHTNING: return "Lightning Gun"; - case MOD_BFG: - case MOD_BFG_SPLASH: return "BFG10K"; -#ifdef MISSIONPACK - case MOD_NAIL: return "Nailgun"; - case MOD_CHAINGUN: return "Chaingun"; - case MOD_PROXIMITY_MINE: return "Proximity Launcher"; - case MOD_KAMIKAZE: return "Kamikaze"; - case MOD_JUICED: return "Prox mine"; -#endif - case MOD_GRAPPLE: return "Grapple"; - default: return "[unknown weapon]"; - } -} - -/* -================== -BotRandomWeaponName -================== -*/ -char *BotRandomWeaponName(void) { - int rnd; - -#ifdef MISSIONPACK - rnd = random() * 11.9; -#else - rnd = random() * 8.9; -#endif - switch(rnd) { - case 0: return "Gauntlet"; - case 1: return "Shotgun"; - case 2: return "Machinegun"; - case 3: return "Grenade Launcher"; - case 4: return "Rocket Launcher"; - case 5: return "Plasmagun"; - case 6: return "Railgun"; - case 7: return "Lightning Gun"; -#ifdef MISSIONPACK - case 8: return "Nailgun"; - case 9: return "Chaingun"; - case 10: return "Proximity Launcher"; -#endif - default: return "BFG10K"; - } -} - -/* -================== -BotVisibleEnemies -================== -*/ -int BotVisibleEnemies(bot_state_t *bs) { - float vis; - int i; - aas_entityinfo_t entinfo; - - for (i = 0; i < MAX_CLIENTS; i++) { - - if (i == bs->client) continue; - // - BotEntityInfo(i, &entinfo); - // - if (!entinfo.valid) continue; - //if the enemy isn't dead and the enemy isn't the bot self - if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue; - //if the enemy is invisible and not shooting - if (EntityIsInvisible(&entinfo) && !EntityIsShooting(&entinfo)) { - continue; - } - //if on the same team - if (BotSameTeam(bs, i)) continue; - //check if the enemy is visible - vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); - if (vis > 0) return qtrue; - } - return qfalse; -} - -/* -================== -BotValidChatPosition -================== -*/ -int BotValidChatPosition(bot_state_t *bs) { - vec3_t point, start, end, mins, maxs; - bsp_trace_t trace; - - //if the bot is dead all positions are valid - if (BotIsDead(bs)) return qtrue; - //never start chatting with a powerup - if (bs->inventory[INVENTORY_QUAD] || - bs->inventory[INVENTORY_HASTE] || - bs->inventory[INVENTORY_INVISIBILITY] || - bs->inventory[INVENTORY_REGEN] || - bs->inventory[INVENTORY_FLIGHT]) return qfalse; - //must be on the ground - //if (bs->cur_ps.groundEntityNum != ENTITYNUM_NONE) return qfalse; - //do not chat if in lava or slime - VectorCopy(bs->origin, point); - point[2] -= 24; - if (trap_PointContents(point,bs->entitynum) & (CONTENTS_LAVA|CONTENTS_SLIME)) return qfalse; - //do not chat if under water - VectorCopy(bs->origin, point); - point[2] += 32; - if (trap_PointContents(point,bs->entitynum) & MASK_WATER) return qfalse; - //must be standing on the world entity - VectorCopy(bs->origin, start); - VectorCopy(bs->origin, end); - start[2] += 1; - end[2] -= 10; - trap_AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, mins, maxs); - BotAI_Trace(&trace, start, mins, maxs, end, bs->client, MASK_SOLID); - if (trace.ent != ENTITYNUM_WORLD) return qfalse; - //the bot is in a position where it can chat - return qtrue; -} - -/* -================== -BotChat_EnterGame -================== -*/ -int BotChat_EnterGame(bot_state_t *bs) { - char name[32]; - float rnd; - - if (bot_nochat.integer) return qfalse; - if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; - //don't chat in teamplay - if (TeamPlayIsOn()) return qfalse; - // don't chat in tournament mode - if (gametype == GT_TOURNAMENT) return qfalse; - rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_ENTEREXITGAME, 0, 1); - if (!bot_fastchat.integer) { - if (random() > rnd) return qfalse; - } - if (BotNumActivePlayers() <= 1) return qfalse; - if (!BotValidChatPosition(bs)) return qfalse; - BotAI_BotInitialChat(bs, "game_enter", - EasyClientName(bs->client, name, 32), // 0 - BotRandomOpponentName(bs), // 1 - "[invalid var]", // 2 - "[invalid var]", // 3 - BotMapTitle(), // 4 - NULL); - bs->lastchat_time = FloatTime(); - bs->chatto = CHAT_ALL; - return qtrue; -} - -/* -================== -BotChat_ExitGame -================== -*/ -int BotChat_ExitGame(bot_state_t *bs) { - char name[32]; - float rnd; - - if (bot_nochat.integer) return qfalse; - if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; - //don't chat in teamplay - if (TeamPlayIsOn()) return qfalse; - // don't chat in tournament mode - if (gametype == GT_TOURNAMENT) return qfalse; - rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_ENTEREXITGAME, 0, 1); - if (!bot_fastchat.integer) { - if (random() > rnd) return qfalse; - } - if (BotNumActivePlayers() <= 1) return qfalse; - // - BotAI_BotInitialChat(bs, "game_exit", - EasyClientName(bs->client, name, 32), // 0 - BotRandomOpponentName(bs), // 1 - "[invalid var]", // 2 - "[invalid var]", // 3 - BotMapTitle(), // 4 - NULL); - bs->lastchat_time = FloatTime(); - bs->chatto = CHAT_ALL; - return qtrue; -} - -/* -================== -BotChat_StartLevel -================== -*/ -int BotChat_StartLevel(bot_state_t *bs) { - char name[32]; - float rnd; - - if (bot_nochat.integer) return qfalse; - if (BotIsObserver(bs)) return qfalse; - if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; - //don't chat in teamplay - if (TeamPlayIsOn()) { - trap_EA_Command(bs->client, "vtaunt"); - return qfalse; - } - // don't chat in tournament mode - if (gametype == GT_TOURNAMENT) return qfalse; - rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_STARTENDLEVEL, 0, 1); - if (!bot_fastchat.integer) { - if (random() > rnd) return qfalse; - } - if (BotNumActivePlayers() <= 1) return qfalse; - BotAI_BotInitialChat(bs, "level_start", - EasyClientName(bs->client, name, 32), // 0 - NULL); - bs->lastchat_time = FloatTime(); - bs->chatto = CHAT_ALL; - return qtrue; -} - -/* -================== -BotChat_EndLevel -================== -*/ -int BotChat_EndLevel(bot_state_t *bs) { - char name[32]; - float rnd; - - if (bot_nochat.integer) return qfalse; - if (BotIsObserver(bs)) return qfalse; - if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; - // teamplay - if (TeamPlayIsOn()) - { - if (BotIsFirstInRankings(bs)) { - trap_EA_Command(bs->client, "vtaunt"); - } - return qtrue; - } - // don't chat in tournament mode - if (gametype == GT_TOURNAMENT) return qfalse; - rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_STARTENDLEVEL, 0, 1); - if (!bot_fastchat.integer) { - if (random() > rnd) return qfalse; - } - if (BotNumActivePlayers() <= 1) return qfalse; - // - if (BotIsFirstInRankings(bs)) { - BotAI_BotInitialChat(bs, "level_end_victory", - EasyClientName(bs->client, name, 32), // 0 - BotRandomOpponentName(bs), // 1 - "[invalid var]", // 2 - BotLastClientInRankings(), // 3 - BotMapTitle(), // 4 - NULL); - } - else if (BotIsLastInRankings(bs)) { - BotAI_BotInitialChat(bs, "level_end_lose", - EasyClientName(bs->client, name, 32), // 0 - BotRandomOpponentName(bs), // 1 - BotFirstClientInRankings(), // 2 - "[invalid var]", // 3 - BotMapTitle(), // 4 - NULL); - } - else { - BotAI_BotInitialChat(bs, "level_end", - EasyClientName(bs->client, name, 32), // 0 - BotRandomOpponentName(bs), // 1 - BotFirstClientInRankings(), // 2 - BotLastClientInRankings(), // 3 - BotMapTitle(), // 4 - NULL); - } - bs->lastchat_time = FloatTime(); - bs->chatto = CHAT_ALL; - return qtrue; -} - -/* -================== -BotChat_Death -================== -*/ -int BotChat_Death(bot_state_t *bs) { - char name[32]; - float rnd; - - if (bot_nochat.integer) return qfalse; - if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; - rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_DEATH, 0, 1); - // don't chat in tournament mode - if (gametype == GT_TOURNAMENT) return qfalse; - //if fast chatting is off - if (!bot_fastchat.integer) { - if (random() > rnd) return qfalse; - } - if (BotNumActivePlayers() <= 1) return qfalse; - // - if (bs->lastkilledby >= 0 && bs->lastkilledby < MAX_CLIENTS) - EasyClientName(bs->lastkilledby, name, 32); - else - strcpy(name, "[world]"); - // - if (TeamPlayIsOn() && BotSameTeam(bs, bs->lastkilledby)) { - if (bs->lastkilledby == bs->client) return qfalse; - BotAI_BotInitialChat(bs, "death_teammate", name, NULL); - bs->chatto = CHAT_TEAM; - } - else - { - //teamplay - if (TeamPlayIsOn()) { - trap_EA_Command(bs->client, "vtaunt"); - return qtrue; - } - // - if (bs->botdeathtype == MOD_WATER) - BotAI_BotInitialChat(bs, "death_drown", BotRandomOpponentName(bs), NULL); - else if (bs->botdeathtype == MOD_SLIME) - BotAI_BotInitialChat(bs, "death_slime", BotRandomOpponentName(bs), NULL); - else if (bs->botdeathtype == MOD_LAVA) - BotAI_BotInitialChat(bs, "death_lava", BotRandomOpponentName(bs), NULL); - else if (bs->botdeathtype == MOD_FALLING) - BotAI_BotInitialChat(bs, "death_cratered", BotRandomOpponentName(bs), NULL); - else if (bs->botsuicide || //all other suicides by own weapon - bs->botdeathtype == MOD_CRUSH || - bs->botdeathtype == MOD_SUICIDE || - bs->botdeathtype == MOD_TARGET_LASER || - bs->botdeathtype == MOD_TRIGGER_HURT || - bs->botdeathtype == MOD_UNKNOWN) - BotAI_BotInitialChat(bs, "death_suicide", BotRandomOpponentName(bs), NULL); - else if (bs->botdeathtype == MOD_TELEFRAG) - BotAI_BotInitialChat(bs, "death_telefrag", name, NULL); -#ifdef MISSIONPACK - else if (bs->botdeathtype == MOD_KAMIKAZE && trap_BotNumInitialChats(bs->cs, "death_kamikaze")) - BotAI_BotInitialChat(bs, "death_kamikaze", name, NULL); -#endif - else { - if ((bs->botdeathtype == MOD_GAUNTLET || - bs->botdeathtype == MOD_RAILGUN || - bs->botdeathtype == MOD_BFG || - bs->botdeathtype == MOD_BFG_SPLASH) && random() < 0.5) { - - if (bs->botdeathtype == MOD_GAUNTLET) - BotAI_BotInitialChat(bs, "death_gauntlet", - name, // 0 - BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 - NULL); - else if (bs->botdeathtype == MOD_RAILGUN) - BotAI_BotInitialChat(bs, "death_rail", - name, // 0 - BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 - NULL); - else - BotAI_BotInitialChat(bs, "death_bfg", - name, // 0 - BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 - NULL); - } - //choose between insult and praise - else if (random() < trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_INSULT, 0, 1)) { - BotAI_BotInitialChat(bs, "death_insult", - name, // 0 - BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 - NULL); - } - else { - BotAI_BotInitialChat(bs, "death_praise", - name, // 0 - BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 - NULL); - } - } - bs->chatto = CHAT_ALL; - } - bs->lastchat_time = FloatTime(); - return qtrue; -} - -/* -================== -BotChat_Kill -================== -*/ -int BotChat_Kill(bot_state_t *bs) { - char name[32]; - float rnd; - - if (bot_nochat.integer) return qfalse; - if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; - rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_KILL, 0, 1); - // don't chat in tournament mode - if (gametype == GT_TOURNAMENT) return qfalse; - //if fast chat is off - if (!bot_fastchat.integer) { - if (random() > rnd) return qfalse; - } - if (bs->lastkilledplayer == bs->client) return qfalse; - if (BotNumActivePlayers() <= 1) return qfalse; - if (!BotValidChatPosition(bs)) return qfalse; - // - if (BotVisibleEnemies(bs)) return qfalse; - // - EasyClientName(bs->lastkilledplayer, name, 32); - // - bs->chatto = CHAT_ALL; - if (TeamPlayIsOn() && BotSameTeam(bs, bs->lastkilledplayer)) { - BotAI_BotInitialChat(bs, "kill_teammate", name, NULL); - bs->chatto = CHAT_TEAM; - } - else - { - //don't chat in teamplay - if (TeamPlayIsOn()) { - trap_EA_Command(bs->client, "vtaunt"); - return qfalse; // don't wait - } - // - if (bs->enemydeathtype == MOD_GAUNTLET) { - BotAI_BotInitialChat(bs, "kill_gauntlet", name, NULL); - } - else if (bs->enemydeathtype == MOD_RAILGUN) { - BotAI_BotInitialChat(bs, "kill_rail", name, NULL); - } - else if (bs->enemydeathtype == MOD_TELEFRAG) { - BotAI_BotInitialChat(bs, "kill_telefrag", name, NULL); - } -#ifdef MISSIONPACK - else if (bs->botdeathtype == MOD_KAMIKAZE && trap_BotNumInitialChats(bs->cs, "kill_kamikaze")) - BotAI_BotInitialChat(bs, "kill_kamikaze", name, NULL); -#endif - //choose between insult and praise - else if (random() < trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_INSULT, 0, 1)) { - BotAI_BotInitialChat(bs, "kill_insult", name, NULL); - } - else { - BotAI_BotInitialChat(bs, "kill_praise", name, NULL); - } - } - bs->lastchat_time = FloatTime(); - return qtrue; -} - -/* -================== -BotChat_EnemySuicide -================== -*/ -int BotChat_EnemySuicide(bot_state_t *bs) { - char name[32]; - float rnd; - - if (bot_nochat.integer) return qfalse; - if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; - if (BotNumActivePlayers() <= 1) return qfalse; - // - rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_KILL, 0, 1); - //don't chat in teamplay - if (TeamPlayIsOn()) return qfalse; - // don't chat in tournament mode - if (gametype == GT_TOURNAMENT) return qfalse; - //if fast chat is off - if (!bot_fastchat.integer) { - if (random() > rnd) return qfalse; - } - if (!BotValidChatPosition(bs)) return qfalse; - // - if (BotVisibleEnemies(bs)) return qfalse; - // - if (bs->enemy >= 0) EasyClientName(bs->enemy, name, 32); - else strcpy(name, ""); - BotAI_BotInitialChat(bs, "enemy_suicide", name, NULL); - bs->lastchat_time = FloatTime(); - bs->chatto = CHAT_ALL; - return qtrue; -} - -/* -================== -BotChat_HitTalking -================== -*/ -int BotChat_HitTalking(bot_state_t *bs) { - char name[32], *weap; - int lasthurt_client; - float rnd; - - if (bot_nochat.integer) return qfalse; - if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; - if (BotNumActivePlayers() <= 1) return qfalse; - lasthurt_client = g_entities[bs->client].client->lasthurt_client; - if (!lasthurt_client) return qfalse; - if (lasthurt_client == bs->client) return qfalse; - // - if (lasthurt_client < 0 || lasthurt_client >= MAX_CLIENTS) return qfalse; - // - rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_HITTALKING, 0, 1); - //don't chat in teamplay - if (TeamPlayIsOn()) return qfalse; - // don't chat in tournament mode - if (gametype == GT_TOURNAMENT) return qfalse; - //if fast chat is off - if (!bot_fastchat.integer) { - if (random() > rnd * 0.5) return qfalse; - } - if (!BotValidChatPosition(bs)) return qfalse; - // - ClientName(g_entities[bs->client].client->lasthurt_client, name, sizeof(name)); - weap = BotWeaponNameForMeansOfDeath(g_entities[bs->client].client->lasthurt_client); - // - BotAI_BotInitialChat(bs, "hit_talking", name, weap, NULL); - bs->lastchat_time = FloatTime(); - bs->chatto = CHAT_ALL; - return qtrue; -} - -/* -================== -BotChat_HitNoDeath -================== -*/ -int BotChat_HitNoDeath(bot_state_t *bs) { - char name[32], *weap; - float rnd; - int lasthurt_client; - aas_entityinfo_t entinfo; - - lasthurt_client = g_entities[bs->client].client->lasthurt_client; - if (!lasthurt_client) return qfalse; - if (lasthurt_client == bs->client) return qfalse; - // - if (lasthurt_client < 0 || lasthurt_client >= MAX_CLIENTS) return qfalse; - // - if (bot_nochat.integer) return qfalse; - if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; - if (BotNumActivePlayers() <= 1) return qfalse; - rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_HITNODEATH, 0, 1); - //don't chat in teamplay - if (TeamPlayIsOn()) return qfalse; - // don't chat in tournament mode - if (gametype == GT_TOURNAMENT) return qfalse; - //if fast chat is off - if (!bot_fastchat.integer) { - if (random() > rnd * 0.5) return qfalse; - } - if (!BotValidChatPosition(bs)) return qfalse; - // - if (BotVisibleEnemies(bs)) return qfalse; - // - BotEntityInfo(bs->enemy, &entinfo); - if (EntityIsShooting(&entinfo)) return qfalse; - // - ClientName(lasthurt_client, name, sizeof(name)); - weap = BotWeaponNameForMeansOfDeath(g_entities[bs->client].client->lasthurt_mod); - // - BotAI_BotInitialChat(bs, "hit_nodeath", name, weap, NULL); - bs->lastchat_time = FloatTime(); - bs->chatto = CHAT_ALL; - return qtrue; -} - -/* -================== -BotChat_HitNoKill -================== -*/ -int BotChat_HitNoKill(bot_state_t *bs) { - char name[32], *weap; - float rnd; - aas_entityinfo_t entinfo; - - if (bot_nochat.integer) return qfalse; - if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; - if (BotNumActivePlayers() <= 1) return qfalse; - rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_HITNOKILL, 0, 1); - //don't chat in teamplay - if (TeamPlayIsOn()) return qfalse; - // don't chat in tournament mode - if (gametype == GT_TOURNAMENT) return qfalse; - //if fast chat is off - if (!bot_fastchat.integer) { - if (random() > rnd * 0.5) return qfalse; - } - if (!BotValidChatPosition(bs)) return qfalse; - // - if (BotVisibleEnemies(bs)) return qfalse; - // - BotEntityInfo(bs->enemy, &entinfo); - if (EntityIsShooting(&entinfo)) return qfalse; - // - ClientName(bs->enemy, name, sizeof(name)); - weap = BotWeaponNameForMeansOfDeath(g_entities[bs->enemy].client->lasthurt_mod); - // - BotAI_BotInitialChat(bs, "hit_nokill", name, weap, NULL); - bs->lastchat_time = FloatTime(); - bs->chatto = CHAT_ALL; - return qtrue; -} - -/* -================== -BotChat_Random -================== -*/ -int BotChat_Random(bot_state_t *bs) { - float rnd; - char name[32]; - - if (bot_nochat.integer) return qfalse; - if (BotIsObserver(bs)) return qfalse; - if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; - // don't chat in tournament mode - if (gametype == GT_TOURNAMENT) return qfalse; - //don't chat when doing something important :) - if (bs->ltgtype == LTG_TEAMHELP || - bs->ltgtype == LTG_TEAMACCOMPANY || - bs->ltgtype == LTG_RUSHBASE) return qfalse; - // - rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_RANDOM, 0, 1); - if (random() > bs->thinktime * 0.1) return qfalse; - if (!bot_fastchat.integer) { - if (random() > rnd) return qfalse; - if (random() > 0.25) return qfalse; - } - if (BotNumActivePlayers() <= 1) return qfalse; - // - if (!BotValidChatPosition(bs)) return qfalse; - // - if (BotVisibleEnemies(bs)) return qfalse; - // - if (bs->lastkilledplayer == bs->client) { - strcpy(name, BotRandomOpponentName(bs)); - } - else { - EasyClientName(bs->lastkilledplayer, name, sizeof(name)); - } - if (TeamPlayIsOn()) { - trap_EA_Command(bs->client, "vtaunt"); - return qfalse; // don't wait - } - // - if (random() < trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_MISC, 0, 1)) { - BotAI_BotInitialChat(bs, "random_misc", - BotRandomOpponentName(bs), // 0 - name, // 1 - "[invalid var]", // 2 - "[invalid var]", // 3 - BotMapTitle(), // 4 - BotRandomWeaponName(), // 5 - NULL); - } - else { - BotAI_BotInitialChat(bs, "random_insult", - BotRandomOpponentName(bs), // 0 - name, // 1 - "[invalid var]", // 2 - "[invalid var]", // 3 - BotMapTitle(), // 4 - BotRandomWeaponName(), // 5 - NULL); - } - bs->lastchat_time = FloatTime(); - bs->chatto = CHAT_ALL; - return qtrue; -} - -/* -================== -BotChatTime -================== -*/ -float BotChatTime(bot_state_t *bs) { - int cpm; - - cpm = trap_Characteristic_BInteger(bs->character, CHARACTERISTIC_CHAT_CPM, 1, 4000); - - return 2.0; //(float) trap_BotChatLength(bs->cs) * 30 / cpm; -} - -/* -================== -BotChatTest -================== -*/ -void BotChatTest(bot_state_t *bs) { - - char name[32]; - char *weap; - int num, i; - - num = trap_BotNumInitialChats(bs->cs, "game_enter"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "game_enter", - EasyClientName(bs->client, name, 32), // 0 - BotRandomOpponentName(bs), // 1 - "[invalid var]", // 2 - "[invalid var]", // 3 - BotMapTitle(), // 4 - NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "game_exit"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "game_exit", - EasyClientName(bs->client, name, 32), // 0 - BotRandomOpponentName(bs), // 1 - "[invalid var]", // 2 - "[invalid var]", // 3 - BotMapTitle(), // 4 - NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "level_start"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "level_start", - EasyClientName(bs->client, name, 32), // 0 - NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "level_end_victory"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "level_end_victory", - EasyClientName(bs->client, name, 32), // 0 - BotRandomOpponentName(bs), // 1 - BotFirstClientInRankings(), // 2 - BotLastClientInRankings(), // 3 - BotMapTitle(), // 4 - NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "level_end_lose"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "level_end_lose", - EasyClientName(bs->client, name, 32), // 0 - BotRandomOpponentName(bs), // 1 - BotFirstClientInRankings(), // 2 - BotLastClientInRankings(), // 3 - BotMapTitle(), // 4 - NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "level_end"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "level_end", - EasyClientName(bs->client, name, 32), // 0 - BotRandomOpponentName(bs), // 1 - BotFirstClientInRankings(), // 2 - BotLastClientInRankings(), // 3 - BotMapTitle(), // 4 - NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - EasyClientName(bs->lastkilledby, name, sizeof(name)); - num = trap_BotNumInitialChats(bs->cs, "death_drown"); - for (i = 0; i < num; i++) - { - // - BotAI_BotInitialChat(bs, "death_drown", name, NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "death_slime"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "death_slime", name, NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "death_lava"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "death_lava", name, NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "death_cratered"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "death_cratered", name, NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "death_suicide"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "death_suicide", name, NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "death_telefrag"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "death_telefrag", name, NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "death_gauntlet"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "death_gauntlet", - name, // 0 - BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 - NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "death_rail"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "death_rail", - name, // 0 - BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 - NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "death_bfg"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "death_bfg", - name, // 0 - BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 - NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "death_insult"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "death_insult", - name, // 0 - BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 - NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "death_praise"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "death_praise", - name, // 0 - BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 - NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - // - EasyClientName(bs->lastkilledplayer, name, 32); - // - num = trap_BotNumInitialChats(bs->cs, "kill_gauntlet"); - for (i = 0; i < num; i++) - { - // - BotAI_BotInitialChat(bs, "kill_gauntlet", name, NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "kill_rail"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "kill_rail", name, NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "kill_telefrag"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "kill_telefrag", name, NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "kill_insult"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "kill_insult", name, NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "kill_praise"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "kill_praise", name, NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "enemy_suicide"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "enemy_suicide", name, NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - ClientName(g_entities[bs->client].client->lasthurt_client, name, sizeof(name)); - weap = BotWeaponNameForMeansOfDeath(g_entities[bs->client].client->lasthurt_client); - num = trap_BotNumInitialChats(bs->cs, "hit_talking"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "hit_talking", name, weap, NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "hit_nodeath"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "hit_nodeath", name, weap, NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "hit_nokill"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "hit_nokill", name, weap, NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - // - if (bs->lastkilledplayer == bs->client) { - strcpy(name, BotRandomOpponentName(bs)); - } - else { - EasyClientName(bs->lastkilledplayer, name, sizeof(name)); - } - // - num = trap_BotNumInitialChats(bs->cs, "random_misc"); - for (i = 0; i < num; i++) - { - // - BotAI_BotInitialChat(bs, "random_misc", - BotRandomOpponentName(bs), // 0 - name, // 1 - "[invalid var]", // 2 - "[invalid var]", // 3 - BotMapTitle(), // 4 - BotRandomWeaponName(), // 5 - NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "random_insult"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "random_insult", - BotRandomOpponentName(bs), // 0 - name, // 1 - "[invalid var]", // 2 - "[invalid var]", // 3 - BotMapTitle(), // 4 - BotRandomWeaponName(), // 5 - NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } -} +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: ai_chat.c + * + * desc: Quake3 bot AI + * + * $Archive: /MissionPack/code/game/ai_chat.c $ + * + *****************************************************************************/ + +#include "g_local.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_ea.h" +#include "be_ai_char.h" +#include "be_ai_chat.h" +#include "be_ai_gen.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" +#include "be_ai_weap.h" +// +#include "ai_main.h" +#include "ai_dmq3.h" +#include "ai_chat.h" +#include "ai_cmd.h" +#include "ai_dmnet.h" +// +#include "chars.h" //characteristics +#include "inv.h" //indexes into the inventory +#include "syn.h" //synonyms +#include "match.h" //string matching types and vars + +// for the voice chats +#ifdef MISSIONPACK // bk001205 +#include "../../ui/menudef.h" +#endif + +#define TIME_BETWEENCHATTING 25 + + +/* +================== +BotNumActivePlayers +================== +*/ +int BotNumActivePlayers(void) { + int i, num; + char buf[MAX_INFO_STRING]; + static int maxclients; + + if (!maxclients) + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); + + num = 0; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + // + num++; + } + return num; +} + +/* +================== +BotIsFirstInRankings +================== +*/ +int BotIsFirstInRankings(bot_state_t *bs) { + int i, score; + char buf[MAX_INFO_STRING]; + static int maxclients; + playerState_t ps; + + if (!maxclients) + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); + + score = bs->cur_ps.persistant[PERS_SCORE]; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + // + BotAI_GetClientState(i, &ps); + if (score < ps.persistant[PERS_SCORE]) return qfalse; + } + return qtrue; +} + +/* +================== +BotIsLastInRankings +================== +*/ +int BotIsLastInRankings(bot_state_t *bs) { + int i, score; + char buf[MAX_INFO_STRING]; + static int maxclients; + playerState_t ps; + + if (!maxclients) + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); + + score = bs->cur_ps.persistant[PERS_SCORE]; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + // + BotAI_GetClientState(i, &ps); + if (score > ps.persistant[PERS_SCORE]) return qfalse; + } + return qtrue; +} + +/* +================== +BotFirstClientInRankings +================== +*/ +char *BotFirstClientInRankings(void) { + int i, bestscore, bestclient; + char buf[MAX_INFO_STRING]; + static char name[32]; + static int maxclients; + playerState_t ps; + + if (!maxclients) + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); + + bestscore = -999999; + bestclient = 0; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + // + BotAI_GetClientState(i, &ps); + if (ps.persistant[PERS_SCORE] > bestscore) { + bestscore = ps.persistant[PERS_SCORE]; + bestclient = i; + } + } + EasyClientName(bestclient, name, 32); + return name; +} + +/* +================== +BotLastClientInRankings +================== +*/ +char *BotLastClientInRankings(void) { + int i, worstscore, bestclient; + char buf[MAX_INFO_STRING]; + static char name[32]; + static int maxclients; + playerState_t ps; + + if (!maxclients) + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); + + worstscore = 999999; + bestclient = 0; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + // + BotAI_GetClientState(i, &ps); + if (ps.persistant[PERS_SCORE] < worstscore) { + worstscore = ps.persistant[PERS_SCORE]; + bestclient = i; + } + } + EasyClientName(bestclient, name, 32); + return name; +} + +/* +================== +BotRandomOpponentName +================== +*/ +char *BotRandomOpponentName(bot_state_t *bs) { + int i, count; + char buf[MAX_INFO_STRING]; + int opponents[MAX_CLIENTS], numopponents; + static int maxclients; + static char name[32]; + + if (!maxclients) + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); + + numopponents = 0; + opponents[0] = 0; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + if (i == bs->client) continue; + // + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + //skip team mates + if (BotSameTeam(bs, i)) continue; + // + opponents[numopponents] = i; + numopponents++; + } + count = random() * numopponents; + for (i = 0; i < numopponents; i++) { + count--; + if (count <= 0) { + EasyClientName(opponents[i], name, sizeof(name)); + return name; + } + } + EasyClientName(opponents[0], name, sizeof(name)); + return name; +} + +/* +================== +BotMapTitle +================== +*/ + +char *BotMapTitle(void) { + char info[1024]; + static char mapname[128]; + + trap_GetServerinfo(info, sizeof(info)); + + strncpy(mapname, Info_ValueForKey( info, "mapname" ), sizeof(mapname)-1); + mapname[sizeof(mapname)-1] = '\0'; + + return mapname; +} + + +/* +================== +BotWeaponNameForMeansOfDeath +================== +*/ + +char *BotWeaponNameForMeansOfDeath(int mod) { + switch(mod) { + case MOD_SHOTGUN: return "Shotgun"; + case MOD_GAUNTLET: return "Gauntlet"; + case MOD_MACHINEGUN: return "Machinegun"; + case MOD_GRENADE: + case MOD_GRENADE_SPLASH: return "Grenade Launcher"; + case MOD_ROCKET: + case MOD_ROCKET_SPLASH: return "Rocket Launcher"; + case MOD_PLASMA: + case MOD_PLASMA_SPLASH: return "Plasmagun"; + case MOD_RAILGUN: return "Railgun"; + case MOD_LIGHTNING: return "Lightning Gun"; + case MOD_BFG: + case MOD_BFG_SPLASH: return "BFG10K"; +#ifdef MISSIONPACK + case MOD_NAIL: return "Nailgun"; + case MOD_CHAINGUN: return "Chaingun"; + case MOD_PROXIMITY_MINE: return "Proximity Launcher"; + case MOD_KAMIKAZE: return "Kamikaze"; + case MOD_JUICED: return "Prox mine"; +#endif + case MOD_GRAPPLE: return "Grapple"; + default: return "[unknown weapon]"; + } +} + +/* +================== +BotRandomWeaponName +================== +*/ +char *BotRandomWeaponName(void) { + int rnd; + +#ifdef MISSIONPACK + rnd = random() * 11.9; +#else + rnd = random() * 8.9; +#endif + switch(rnd) { + case 0: return "Gauntlet"; + case 1: return "Shotgun"; + case 2: return "Machinegun"; + case 3: return "Grenade Launcher"; + case 4: return "Rocket Launcher"; + case 5: return "Plasmagun"; + case 6: return "Railgun"; + case 7: return "Lightning Gun"; +#ifdef MISSIONPACK + case 8: return "Nailgun"; + case 9: return "Chaingun"; + case 10: return "Proximity Launcher"; +#endif + default: return "BFG10K"; + } +} + +/* +================== +BotVisibleEnemies +================== +*/ +int BotVisibleEnemies(bot_state_t *bs) { + float vis; + int i; + aas_entityinfo_t entinfo; + + for (i = 0; i < MAX_CLIENTS; i++) { + + if (i == bs->client) continue; + // + BotEntityInfo(i, &entinfo); + // + if (!entinfo.valid) continue; + //if the enemy isn't dead and the enemy isn't the bot self + if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue; + //if the enemy is invisible and not shooting + if (EntityIsInvisible(&entinfo) && !EntityIsShooting(&entinfo)) { + continue; + } + //if on the same team + if (BotSameTeam(bs, i)) continue; + //check if the enemy is visible + vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); + if (vis > 0) return qtrue; + } + return qfalse; +} + +/* +================== +BotValidChatPosition +================== +*/ +int BotValidChatPosition(bot_state_t *bs) { + vec3_t point, start, end, mins, maxs; + bsp_trace_t trace; + + //if the bot is dead all positions are valid + if (BotIsDead(bs)) return qtrue; + //never start chatting with a powerup + if (bs->inventory[INVENTORY_QUAD] || + bs->inventory[INVENTORY_HASTE] || + bs->inventory[INVENTORY_INVISIBILITY] || + bs->inventory[INVENTORY_REGEN] || + bs->inventory[INVENTORY_FLIGHT]) return qfalse; + //must be on the ground + //if (bs->cur_ps.groundEntityNum != ENTITYNUM_NONE) return qfalse; + //do not chat if in lava or slime + VectorCopy(bs->origin, point); + point[2] -= 24; + if (trap_PointContents(point,bs->entitynum) & (CONTENTS_LAVA|CONTENTS_SLIME)) return qfalse; + //do not chat if under water + VectorCopy(bs->origin, point); + point[2] += 32; + if (trap_PointContents(point,bs->entitynum) & MASK_WATER) return qfalse; + //must be standing on the world entity + VectorCopy(bs->origin, start); + VectorCopy(bs->origin, end); + start[2] += 1; + end[2] -= 10; + trap_AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, mins, maxs); + BotAI_Trace(&trace, start, mins, maxs, end, bs->client, MASK_SOLID); + if (trace.ent != ENTITYNUM_WORLD) return qfalse; + //the bot is in a position where it can chat + return qtrue; +} + +/* +================== +BotChat_EnterGame +================== +*/ +int BotChat_EnterGame(bot_state_t *bs) { + char name[32]; + float rnd; + + if (bot_nochat.integer) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + //don't chat in teamplay + if (TeamPlayIsOn()) return qfalse; + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_ENTEREXITGAME, 0, 1); + if (!bot_fastchat.integer) { + if (random() > rnd) return qfalse; + } + if (BotNumActivePlayers() <= 1) return qfalse; + if (!BotValidChatPosition(bs)) return qfalse; + BotAI_BotInitialChat(bs, "game_enter", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + NULL); + bs->lastchat_time = FloatTime(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_ExitGame +================== +*/ +int BotChat_ExitGame(bot_state_t *bs) { + char name[32]; + float rnd; + + if (bot_nochat.integer) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + //don't chat in teamplay + if (TeamPlayIsOn()) return qfalse; + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_ENTEREXITGAME, 0, 1); + if (!bot_fastchat.integer) { + if (random() > rnd) return qfalse; + } + if (BotNumActivePlayers() <= 1) return qfalse; + // + BotAI_BotInitialChat(bs, "game_exit", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + NULL); + bs->lastchat_time = FloatTime(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_StartLevel +================== +*/ +int BotChat_StartLevel(bot_state_t *bs) { + char name[32]; + float rnd; + + if (bot_nochat.integer) return qfalse; + if (BotIsObserver(bs)) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + //don't chat in teamplay + if (TeamPlayIsOn()) { + trap_EA_Command(bs->client, "vtaunt"); + return qfalse; + } + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_STARTENDLEVEL, 0, 1); + if (!bot_fastchat.integer) { + if (random() > rnd) return qfalse; + } + if (BotNumActivePlayers() <= 1) return qfalse; + BotAI_BotInitialChat(bs, "level_start", + EasyClientName(bs->client, name, 32), // 0 + NULL); + bs->lastchat_time = FloatTime(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_EndLevel +================== +*/ +int BotChat_EndLevel(bot_state_t *bs) { + char name[32]; + float rnd; + + if (bot_nochat.integer) return qfalse; + if (BotIsObserver(bs)) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + // teamplay + if (TeamPlayIsOn()) + { + if (BotIsFirstInRankings(bs)) { + trap_EA_Command(bs->client, "vtaunt"); + } + return qtrue; + } + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_STARTENDLEVEL, 0, 1); + if (!bot_fastchat.integer) { + if (random() > rnd) return qfalse; + } + if (BotNumActivePlayers() <= 1) return qfalse; + // + if (BotIsFirstInRankings(bs)) { + BotAI_BotInitialChat(bs, "level_end_victory", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + "[invalid var]", // 2 + BotLastClientInRankings(), // 3 + BotMapTitle(), // 4 + NULL); + } + else if (BotIsLastInRankings(bs)) { + BotAI_BotInitialChat(bs, "level_end_lose", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + BotFirstClientInRankings(), // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + NULL); + } + else { + BotAI_BotInitialChat(bs, "level_end", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + BotFirstClientInRankings(), // 2 + BotLastClientInRankings(), // 3 + BotMapTitle(), // 4 + NULL); + } + bs->lastchat_time = FloatTime(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_Death +================== +*/ +int BotChat_Death(bot_state_t *bs) { + char name[32]; + float rnd; + + if (bot_nochat.integer) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_DEATH, 0, 1); + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + //if fast chatting is off + if (!bot_fastchat.integer) { + if (random() > rnd) return qfalse; + } + if (BotNumActivePlayers() <= 1) return qfalse; + // + if (bs->lastkilledby >= 0 && bs->lastkilledby < MAX_CLIENTS) + EasyClientName(bs->lastkilledby, name, 32); + else + strcpy(name, "[world]"); + // + if (TeamPlayIsOn() && BotSameTeam(bs, bs->lastkilledby)) { + if (bs->lastkilledby == bs->client) return qfalse; + BotAI_BotInitialChat(bs, "death_teammate", name, NULL); + bs->chatto = CHAT_TEAM; + } + else + { + //teamplay + if (TeamPlayIsOn()) { + trap_EA_Command(bs->client, "vtaunt"); + return qtrue; + } + // + if (bs->botdeathtype == MOD_WATER) + BotAI_BotInitialChat(bs, "death_drown", BotRandomOpponentName(bs), NULL); + else if (bs->botdeathtype == MOD_SLIME) + BotAI_BotInitialChat(bs, "death_slime", BotRandomOpponentName(bs), NULL); + else if (bs->botdeathtype == MOD_LAVA) + BotAI_BotInitialChat(bs, "death_lava", BotRandomOpponentName(bs), NULL); + else if (bs->botdeathtype == MOD_FALLING) + BotAI_BotInitialChat(bs, "death_cratered", BotRandomOpponentName(bs), NULL); + else if (bs->botsuicide || //all other suicides by own weapon + bs->botdeathtype == MOD_CRUSH || + bs->botdeathtype == MOD_SUICIDE || + bs->botdeathtype == MOD_TARGET_LASER || + bs->botdeathtype == MOD_TRIGGER_HURT || + bs->botdeathtype == MOD_UNKNOWN) + BotAI_BotInitialChat(bs, "death_suicide", BotRandomOpponentName(bs), NULL); + else if (bs->botdeathtype == MOD_TELEFRAG) + BotAI_BotInitialChat(bs, "death_telefrag", name, NULL); +#ifdef MISSIONPACK + else if (bs->botdeathtype == MOD_KAMIKAZE && trap_BotNumInitialChats(bs->cs, "death_kamikaze")) + BotAI_BotInitialChat(bs, "death_kamikaze", name, NULL); +#endif + else { + if ((bs->botdeathtype == MOD_GAUNTLET || + bs->botdeathtype == MOD_RAILGUN || + bs->botdeathtype == MOD_BFG || + bs->botdeathtype == MOD_BFG_SPLASH) && random() < 0.5) { + + if (bs->botdeathtype == MOD_GAUNTLET) + BotAI_BotInitialChat(bs, "death_gauntlet", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + else if (bs->botdeathtype == MOD_RAILGUN) + BotAI_BotInitialChat(bs, "death_rail", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + else + BotAI_BotInitialChat(bs, "death_bfg", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + } + //choose between insult and praise + else if (random() < trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_INSULT, 0, 1)) { + BotAI_BotInitialChat(bs, "death_insult", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + } + else { + BotAI_BotInitialChat(bs, "death_praise", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + } + } + bs->chatto = CHAT_ALL; + } + bs->lastchat_time = FloatTime(); + return qtrue; +} + +/* +================== +BotChat_Kill +================== +*/ +int BotChat_Kill(bot_state_t *bs) { + char name[32]; + float rnd; + + if (bot_nochat.integer) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_KILL, 0, 1); + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + //if fast chat is off + if (!bot_fastchat.integer) { + if (random() > rnd) return qfalse; + } + if (bs->lastkilledplayer == bs->client) return qfalse; + if (BotNumActivePlayers() <= 1) return qfalse; + if (!BotValidChatPosition(bs)) return qfalse; + // + if (BotVisibleEnemies(bs)) return qfalse; + // + EasyClientName(bs->lastkilledplayer, name, 32); + // + bs->chatto = CHAT_ALL; + if (TeamPlayIsOn() && BotSameTeam(bs, bs->lastkilledplayer)) { + BotAI_BotInitialChat(bs, "kill_teammate", name, NULL); + bs->chatto = CHAT_TEAM; + } + else + { + //don't chat in teamplay + if (TeamPlayIsOn()) { + trap_EA_Command(bs->client, "vtaunt"); + return qfalse; // don't wait + } + // + if (bs->enemydeathtype == MOD_GAUNTLET) { + BotAI_BotInitialChat(bs, "kill_gauntlet", name, NULL); + } + else if (bs->enemydeathtype == MOD_RAILGUN) { + BotAI_BotInitialChat(bs, "kill_rail", name, NULL); + } + else if (bs->enemydeathtype == MOD_TELEFRAG) { + BotAI_BotInitialChat(bs, "kill_telefrag", name, NULL); + } +#ifdef MISSIONPACK + else if (bs->botdeathtype == MOD_KAMIKAZE && trap_BotNumInitialChats(bs->cs, "kill_kamikaze")) + BotAI_BotInitialChat(bs, "kill_kamikaze", name, NULL); +#endif + //choose between insult and praise + else if (random() < trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_INSULT, 0, 1)) { + BotAI_BotInitialChat(bs, "kill_insult", name, NULL); + } + else { + BotAI_BotInitialChat(bs, "kill_praise", name, NULL); + } + } + bs->lastchat_time = FloatTime(); + return qtrue; +} + +/* +================== +BotChat_EnemySuicide +================== +*/ +int BotChat_EnemySuicide(bot_state_t *bs) { + char name[32]; + float rnd; + + if (bot_nochat.integer) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + if (BotNumActivePlayers() <= 1) return qfalse; + // + rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_KILL, 0, 1); + //don't chat in teamplay + if (TeamPlayIsOn()) return qfalse; + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + //if fast chat is off + if (!bot_fastchat.integer) { + if (random() > rnd) return qfalse; + } + if (!BotValidChatPosition(bs)) return qfalse; + // + if (BotVisibleEnemies(bs)) return qfalse; + // + if (bs->enemy >= 0) EasyClientName(bs->enemy, name, 32); + else strcpy(name, ""); + BotAI_BotInitialChat(bs, "enemy_suicide", name, NULL); + bs->lastchat_time = FloatTime(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_HitTalking +================== +*/ +int BotChat_HitTalking(bot_state_t *bs) { + char name[32], *weap; + int lasthurt_client; + float rnd; + + if (bot_nochat.integer) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + if (BotNumActivePlayers() <= 1) return qfalse; + lasthurt_client = g_entities[bs->client].client->lasthurt_client; + if (!lasthurt_client) return qfalse; + if (lasthurt_client == bs->client) return qfalse; + // + if (lasthurt_client < 0 || lasthurt_client >= MAX_CLIENTS) return qfalse; + // + rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_HITTALKING, 0, 1); + //don't chat in teamplay + if (TeamPlayIsOn()) return qfalse; + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + //if fast chat is off + if (!bot_fastchat.integer) { + if (random() > rnd * 0.5) return qfalse; + } + if (!BotValidChatPosition(bs)) return qfalse; + // + ClientName(g_entities[bs->client].client->lasthurt_client, name, sizeof(name)); + weap = BotWeaponNameForMeansOfDeath(g_entities[bs->client].client->lasthurt_client); + // + BotAI_BotInitialChat(bs, "hit_talking", name, weap, NULL); + bs->lastchat_time = FloatTime(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_HitNoDeath +================== +*/ +int BotChat_HitNoDeath(bot_state_t *bs) { + char name[32], *weap; + float rnd; + int lasthurt_client; + aas_entityinfo_t entinfo; + + lasthurt_client = g_entities[bs->client].client->lasthurt_client; + if (!lasthurt_client) return qfalse; + if (lasthurt_client == bs->client) return qfalse; + // + if (lasthurt_client < 0 || lasthurt_client >= MAX_CLIENTS) return qfalse; + // + if (bot_nochat.integer) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + if (BotNumActivePlayers() <= 1) return qfalse; + rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_HITNODEATH, 0, 1); + //don't chat in teamplay + if (TeamPlayIsOn()) return qfalse; + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + //if fast chat is off + if (!bot_fastchat.integer) { + if (random() > rnd * 0.5) return qfalse; + } + if (!BotValidChatPosition(bs)) return qfalse; + // + if (BotVisibleEnemies(bs)) return qfalse; + // + BotEntityInfo(bs->enemy, &entinfo); + if (EntityIsShooting(&entinfo)) return qfalse; + // + ClientName(lasthurt_client, name, sizeof(name)); + weap = BotWeaponNameForMeansOfDeath(g_entities[bs->client].client->lasthurt_mod); + // + BotAI_BotInitialChat(bs, "hit_nodeath", name, weap, NULL); + bs->lastchat_time = FloatTime(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_HitNoKill +================== +*/ +int BotChat_HitNoKill(bot_state_t *bs) { + char name[32], *weap; + float rnd; + aas_entityinfo_t entinfo; + + if (bot_nochat.integer) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + if (BotNumActivePlayers() <= 1) return qfalse; + rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_HITNOKILL, 0, 1); + //don't chat in teamplay + if (TeamPlayIsOn()) return qfalse; + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + //if fast chat is off + if (!bot_fastchat.integer) { + if (random() > rnd * 0.5) return qfalse; + } + if (!BotValidChatPosition(bs)) return qfalse; + // + if (BotVisibleEnemies(bs)) return qfalse; + // + BotEntityInfo(bs->enemy, &entinfo); + if (EntityIsShooting(&entinfo)) return qfalse; + // + ClientName(bs->enemy, name, sizeof(name)); + weap = BotWeaponNameForMeansOfDeath(g_entities[bs->enemy].client->lasthurt_mod); + // + BotAI_BotInitialChat(bs, "hit_nokill", name, weap, NULL); + bs->lastchat_time = FloatTime(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_Random +================== +*/ +int BotChat_Random(bot_state_t *bs) { + float rnd; + char name[32]; + + if (bot_nochat.integer) return qfalse; + if (BotIsObserver(bs)) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + //don't chat when doing something important :) + if (bs->ltgtype == LTG_TEAMHELP || + bs->ltgtype == LTG_TEAMACCOMPANY || + bs->ltgtype == LTG_RUSHBASE) return qfalse; + // + rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_RANDOM, 0, 1); + if (random() > bs->thinktime * 0.1) return qfalse; + if (!bot_fastchat.integer) { + if (random() > rnd) return qfalse; + if (random() > 0.25) return qfalse; + } + if (BotNumActivePlayers() <= 1) return qfalse; + // + if (!BotValidChatPosition(bs)) return qfalse; + // + if (BotVisibleEnemies(bs)) return qfalse; + // + if (bs->lastkilledplayer == bs->client) { + strcpy(name, BotRandomOpponentName(bs)); + } + else { + EasyClientName(bs->lastkilledplayer, name, sizeof(name)); + } + if (TeamPlayIsOn()) { + trap_EA_Command(bs->client, "vtaunt"); + return qfalse; // don't wait + } + // + if (random() < trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_MISC, 0, 1)) { + BotAI_BotInitialChat(bs, "random_misc", + BotRandomOpponentName(bs), // 0 + name, // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + BotRandomWeaponName(), // 5 + NULL); + } + else { + BotAI_BotInitialChat(bs, "random_insult", + BotRandomOpponentName(bs), // 0 + name, // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + BotRandomWeaponName(), // 5 + NULL); + } + bs->lastchat_time = FloatTime(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChatTime +================== +*/ +float BotChatTime(bot_state_t *bs) { + int cpm; + + cpm = trap_Characteristic_BInteger(bs->character, CHARACTERISTIC_CHAT_CPM, 1, 4000); + + return 2.0; //(float) trap_BotChatLength(bs->cs) * 30 / cpm; +} + +/* +================== +BotChatTest +================== +*/ +void BotChatTest(bot_state_t *bs) { + + char name[32]; + char *weap; + int num, i; + + num = trap_BotNumInitialChats(bs->cs, "game_enter"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "game_enter", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "game_exit"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "game_exit", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "level_start"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "level_start", + EasyClientName(bs->client, name, 32), // 0 + NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "level_end_victory"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "level_end_victory", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + BotFirstClientInRankings(), // 2 + BotLastClientInRankings(), // 3 + BotMapTitle(), // 4 + NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "level_end_lose"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "level_end_lose", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + BotFirstClientInRankings(), // 2 + BotLastClientInRankings(), // 3 + BotMapTitle(), // 4 + NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "level_end"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "level_end", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + BotFirstClientInRankings(), // 2 + BotLastClientInRankings(), // 3 + BotMapTitle(), // 4 + NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + EasyClientName(bs->lastkilledby, name, sizeof(name)); + num = trap_BotNumInitialChats(bs->cs, "death_drown"); + for (i = 0; i < num; i++) + { + // + BotAI_BotInitialChat(bs, "death_drown", name, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "death_slime"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_slime", name, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "death_lava"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_lava", name, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "death_cratered"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_cratered", name, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "death_suicide"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_suicide", name, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "death_telefrag"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_telefrag", name, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "death_gauntlet"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_gauntlet", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "death_rail"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_rail", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "death_bfg"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_bfg", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "death_insult"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_insult", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "death_praise"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_praise", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + // + EasyClientName(bs->lastkilledplayer, name, 32); + // + num = trap_BotNumInitialChats(bs->cs, "kill_gauntlet"); + for (i = 0; i < num; i++) + { + // + BotAI_BotInitialChat(bs, "kill_gauntlet", name, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "kill_rail"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "kill_rail", name, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "kill_telefrag"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "kill_telefrag", name, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "kill_insult"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "kill_insult", name, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "kill_praise"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "kill_praise", name, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "enemy_suicide"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "enemy_suicide", name, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + ClientName(g_entities[bs->client].client->lasthurt_client, name, sizeof(name)); + weap = BotWeaponNameForMeansOfDeath(g_entities[bs->client].client->lasthurt_client); + num = trap_BotNumInitialChats(bs->cs, "hit_talking"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "hit_talking", name, weap, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "hit_nodeath"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "hit_nodeath", name, weap, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "hit_nokill"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "hit_nokill", name, weap, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + // + if (bs->lastkilledplayer == bs->client) { + strcpy(name, BotRandomOpponentName(bs)); + } + else { + EasyClientName(bs->lastkilledplayer, name, sizeof(name)); + } + // + num = trap_BotNumInitialChats(bs->cs, "random_misc"); + for (i = 0; i < num; i++) + { + // + BotAI_BotInitialChat(bs, "random_misc", + BotRandomOpponentName(bs), // 0 + name, // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + BotRandomWeaponName(), // 5 + NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "random_insult"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "random_insult", + BotRandomOpponentName(bs), // 0 + name, // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + BotRandomWeaponName(), // 5 + NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } +} diff --git a/code/game/ai_chat.h b/code/game/ai_chat.h index 98711c4..3f520e6 100755 --- a/code/game/ai_chat.h +++ b/code/game/ai_chat.h @@ -1,61 +1,61 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -/***************************************************************************** - * name: ai_chat.h - * - * desc: Quake3 bot AI - * - * $Archive: /source/code/botai/ai_chat.c $ - * - *****************************************************************************/ - -// -int BotChat_EnterGame(bot_state_t *bs); -// -int BotChat_ExitGame(bot_state_t *bs); -// -int BotChat_StartLevel(bot_state_t *bs); -// -int BotChat_EndLevel(bot_state_t *bs); -// -int BotChat_HitTalking(bot_state_t *bs); -// -int BotChat_HitNoDeath(bot_state_t *bs); -// -int BotChat_HitNoKill(bot_state_t *bs); -// -int BotChat_Death(bot_state_t *bs); -// -int BotChat_Kill(bot_state_t *bs); -// -int BotChat_EnemySuicide(bot_state_t *bs); -// -int BotChat_Random(bot_state_t *bs); -// time the selected chat takes to type in -float BotChatTime(bot_state_t *bs); -// returns true if the bot can chat at the current position -int BotValidChatPosition(bot_state_t *bs); -// test the initial bot chats -void BotChatTest(bot_state_t *bs); - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: ai_chat.h + * + * desc: Quake3 bot AI + * + * $Archive: /source/code/botai/ai_chat.c $ + * + *****************************************************************************/ + +// +int BotChat_EnterGame(bot_state_t *bs); +// +int BotChat_ExitGame(bot_state_t *bs); +// +int BotChat_StartLevel(bot_state_t *bs); +// +int BotChat_EndLevel(bot_state_t *bs); +// +int BotChat_HitTalking(bot_state_t *bs); +// +int BotChat_HitNoDeath(bot_state_t *bs); +// +int BotChat_HitNoKill(bot_state_t *bs); +// +int BotChat_Death(bot_state_t *bs); +// +int BotChat_Kill(bot_state_t *bs); +// +int BotChat_EnemySuicide(bot_state_t *bs); +// +int BotChat_Random(bot_state_t *bs); +// time the selected chat takes to type in +float BotChatTime(bot_state_t *bs); +// returns true if the bot can chat at the current position +int BotValidChatPosition(bot_state_t *bs); +// test the initial bot chats +void BotChatTest(bot_state_t *bs); + diff --git a/code/game/ai_cmd.c b/code/game/ai_cmd.c index 802d7d8..fadf07f 100755 --- a/code/game/ai_cmd.c +++ b/code/game/ai_cmd.c @@ -1,1992 +1,1992 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -/***************************************************************************** - * name: ai_cmd.c - * - * desc: Quake3 bot AI - * - * $Archive: /MissionPack/code/game/ai_cmd.c $ - * - *****************************************************************************/ - -#include "g_local.h" -#include "botlib.h" -#include "be_aas.h" -#include "be_ea.h" -#include "be_ai_char.h" -#include "be_ai_chat.h" -#include "be_ai_gen.h" -#include "be_ai_goal.h" -#include "be_ai_move.h" -#include "be_ai_weap.h" -// -#include "ai_main.h" -#include "ai_dmq3.h" -#include "ai_chat.h" -#include "ai_cmd.h" -#include "ai_dmnet.h" -#include "ai_team.h" -// -#include "chars.h" //characteristics -#include "inv.h" //indexes into the inventory -#include "syn.h" //synonyms -#include "match.h" //string matching types and vars - -// for the voice chats -#include "../../ui/menudef.h" - -int notleader[MAX_CLIENTS]; - -#ifdef DEBUG -/* -================== -BotPrintTeamGoal -================== -*/ -void BotPrintTeamGoal(bot_state_t *bs) { - char netname[MAX_NETNAME]; - float t; - - ClientName(bs->client, netname, sizeof(netname)); - t = bs->teamgoal_time - FloatTime(); - switch(bs->ltgtype) { - case LTG_TEAMHELP: - { - BotAI_Print(PRT_MESSAGE, "%s: I'm gonna help a team mate for %1.0f secs\n", netname, t); - break; - } - case LTG_TEAMACCOMPANY: - { - BotAI_Print(PRT_MESSAGE, "%s: I'm gonna accompany a team mate for %1.0f secs\n", netname, t); - break; - } - case LTG_GETFLAG: - { - BotAI_Print(PRT_MESSAGE, "%s: I'm gonna get the flag for %1.0f secs\n", netname, t); - break; - } - case LTG_RUSHBASE: - { - BotAI_Print(PRT_MESSAGE, "%s: I'm gonna rush to the base for %1.0f secs\n", netname, t); - break; - } - case LTG_RETURNFLAG: - { - BotAI_Print(PRT_MESSAGE, "%s: I'm gonna try to return the flag for %1.0f secs\n", netname, t); - break; - } -#ifdef MISSIONPACK - case LTG_ATTACKENEMYBASE: - { - BotAI_Print(PRT_MESSAGE, "%s: I'm gonna attack the enemy base for %1.0f secs\n", netname, t); - break; - } - case LTG_HARVEST: - { - BotAI_Print(PRT_MESSAGE, "%s: I'm gonna harvest for %1.0f secs\n", netname, t); - break; - } -#endif - case LTG_DEFENDKEYAREA: - { - BotAI_Print(PRT_MESSAGE, "%s: I'm gonna defend a key area for %1.0f secs\n", netname, t); - break; - } - case LTG_GETITEM: - { - BotAI_Print(PRT_MESSAGE, "%s: I'm gonna get an item for %1.0f secs\n", netname, t); - break; - } - case LTG_KILL: - { - BotAI_Print(PRT_MESSAGE, "%s: I'm gonna kill someone for %1.0f secs\n", netname, t); - break; - } - case LTG_CAMP: - case LTG_CAMPORDER: - { - BotAI_Print(PRT_MESSAGE, "%s: I'm gonna camp for %1.0f secs\n", netname, t); - break; - } - case LTG_PATROL: - { - BotAI_Print(PRT_MESSAGE, "%s: I'm gonna patrol for %1.0f secs\n", netname, t); - break; - } - default: - { - if (bs->ctfroam_time > FloatTime()) { - t = bs->ctfroam_time - FloatTime(); - BotAI_Print(PRT_MESSAGE, "%s: I'm gonna roam for %1.0f secs\n", netname, t); - } - else { - BotAI_Print(PRT_MESSAGE, "%s: I've got a regular goal\n", netname); - } - } - } -} -#endif //DEBUG - -/* -================== -BotGetItemTeamGoal - -FIXME: add stuff like "upper rocket launcher" -"the rl near the railgun", "lower grenade launcher" etc. -================== -*/ -int BotGetItemTeamGoal(char *goalname, bot_goal_t *goal) { - int i; - - if (!strlen(goalname)) return qfalse; - i = -1; - do { - i = trap_BotGetLevelItemGoal(i, goalname, goal); - if (i > 0) { - //do NOT defend dropped items - if (goal->flags & GFL_DROPPED) - continue; - return qtrue; - } - } while(i > 0); - return qfalse; -} - -/* -================== -BotGetMessageTeamGoal -================== -*/ -int BotGetMessageTeamGoal(bot_state_t *bs, char *goalname, bot_goal_t *goal) { - bot_waypoint_t *cp; - - if (BotGetItemTeamGoal(goalname, goal)) return qtrue; - - cp = BotFindWayPoint(bs->checkpoints, goalname); - if (cp) { - memcpy(goal, &cp->goal, sizeof(bot_goal_t)); - return qtrue; - } - return qfalse; -} - -/* -================== -BotGetTime -================== -*/ -float BotGetTime(bot_match_t *match) { - bot_match_t timematch; - char timestring[MAX_MESSAGE_SIZE]; - float t; - - //if the matched string has a time - if (match->subtype & ST_TIME) { - //get the time string - trap_BotMatchVariable(match, TIME, timestring, MAX_MESSAGE_SIZE); - //match it to find out if the time is in seconds or minutes - if (trap_BotFindMatch(timestring, &timematch, MTCONTEXT_TIME)) { - if (timematch.type == MSG_FOREVER) { - t = 99999999.0f; - } - else if (timematch.type == MSG_FORAWHILE) { - t = 10 * 60; // 10 minutes - } - else if (timematch.type == MSG_FORALONGTIME) { - t = 30 * 60; // 30 minutes - } - else { - trap_BotMatchVariable(&timematch, TIME, timestring, MAX_MESSAGE_SIZE); - if (timematch.type == MSG_MINUTES) t = atof(timestring) * 60; - else if (timematch.type == MSG_SECONDS) t = atof(timestring); - else t = 0; - } - //if there's a valid time - if (t > 0) return FloatTime() + t; - } - } - return 0; -} - -/* -================== -FindClientByName -================== -*/ -int FindClientByName(char *name) { - int i; - char buf[MAX_INFO_STRING]; - static int maxclients; - - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - ClientName(i, buf, sizeof(buf)); - if (!Q_stricmp(buf, name)) return i; - } - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - ClientName(i, buf, sizeof(buf)); - if (stristr(buf, name)) return i; - } - return -1; -} - -/* -================== -FindEnemyByName -================== -*/ -int FindEnemyByName(bot_state_t *bs, char *name) { - int i; - char buf[MAX_INFO_STRING]; - static int maxclients; - - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - if (BotSameTeam(bs, i)) continue; - ClientName(i, buf, sizeof(buf)); - if (!Q_stricmp(buf, name)) return i; - } - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - if (BotSameTeam(bs, i)) continue; - ClientName(i, buf, sizeof(buf)); - if (stristr(buf, name)) return i; - } - return -1; -} - -/* -================== -NumPlayersOnSameTeam -================== -*/ -int NumPlayersOnSameTeam(bot_state_t *bs) { - int i, num; - char buf[MAX_INFO_STRING]; - static int maxclients; - - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - - num = 0; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - trap_GetConfigstring(CS_PLAYERS+i, buf, MAX_INFO_STRING); - if (strlen(buf)) { - if (BotSameTeam(bs, i+1)) num++; - } - } - return num; -} - -/* -================== -TeamPlayIsOn -================== -*/ -int BotGetPatrolWaypoints(bot_state_t *bs, bot_match_t *match) { - char keyarea[MAX_MESSAGE_SIZE]; - int patrolflags; - bot_waypoint_t *wp, *newwp, *newpatrolpoints; - bot_match_t keyareamatch; - bot_goal_t goal; - - newpatrolpoints = NULL; - patrolflags = 0; - // - trap_BotMatchVariable(match, KEYAREA, keyarea, MAX_MESSAGE_SIZE); - // - while(1) { - if (!trap_BotFindMatch(keyarea, &keyareamatch, MTCONTEXT_PATROLKEYAREA)) { - trap_EA_SayTeam(bs->client, "what do you say?"); - BotFreeWaypoints(newpatrolpoints); - bs->patrolpoints = NULL; - return qfalse; - } - trap_BotMatchVariable(&keyareamatch, KEYAREA, keyarea, MAX_MESSAGE_SIZE); - if (!BotGetMessageTeamGoal(bs, keyarea, &goal)) { - //BotAI_BotInitialChat(bs, "cannotfind", keyarea, NULL); - //trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); - BotFreeWaypoints(newpatrolpoints); - bs->patrolpoints = NULL; - return qfalse; - } - //create a new waypoint - newwp = BotCreateWayPoint(keyarea, goal.origin, goal.areanum); - if (!newwp) - break; - //add the waypoint to the patrol points - newwp->next = NULL; - for (wp = newpatrolpoints; wp && wp->next; wp = wp->next); - if (!wp) { - newpatrolpoints = newwp; - newwp->prev = NULL; - } - else { - wp->next = newwp; - newwp->prev = wp; - } - // - if (keyareamatch.subtype & ST_BACK) { - patrolflags = PATROL_LOOP; - break; - } - else if (keyareamatch.subtype & ST_REVERSE) { - patrolflags = PATROL_REVERSE; - break; - } - else if (keyareamatch.subtype & ST_MORE) { - trap_BotMatchVariable(&keyareamatch, MORE, keyarea, MAX_MESSAGE_SIZE); - } - else { - break; - } - } - // - if (!newpatrolpoints || !newpatrolpoints->next) { - trap_EA_SayTeam(bs->client, "I need more key points to patrol\n"); - BotFreeWaypoints(newpatrolpoints); - newpatrolpoints = NULL; - return qfalse; - } - // - BotFreeWaypoints(bs->patrolpoints); - bs->patrolpoints = newpatrolpoints; - // - bs->curpatrolpoint = bs->patrolpoints; - bs->patrolflags = patrolflags; - // - return qtrue; -} - -/* -================== -BotAddressedToBot -================== -*/ -int BotAddressedToBot(bot_state_t *bs, bot_match_t *match) { - char addressedto[MAX_MESSAGE_SIZE]; - char netname[MAX_MESSAGE_SIZE]; - char name[MAX_MESSAGE_SIZE]; - char botname[128]; - int client; - bot_match_t addresseematch; - - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - client = ClientOnSameTeamFromName(bs, netname); - if (client < 0) return qfalse; - //if the message is addressed to someone - if (match->subtype & ST_ADDRESSED) { - trap_BotMatchVariable(match, ADDRESSEE, addressedto, sizeof(addressedto)); - //the name of this bot - ClientName(bs->client, botname, 128); - // - while(trap_BotFindMatch(addressedto, &addresseematch, MTCONTEXT_ADDRESSEE)) { - if (addresseematch.type == MSG_EVERYONE) { - return qtrue; - } - else if (addresseematch.type == MSG_MULTIPLENAMES) { - trap_BotMatchVariable(&addresseematch, TEAMMATE, name, sizeof(name)); - if (strlen(name)) { - if (stristr(botname, name)) return qtrue; - if (stristr(bs->subteam, name)) return qtrue; - } - trap_BotMatchVariable(&addresseematch, MORE, addressedto, MAX_MESSAGE_SIZE); - } - else { - trap_BotMatchVariable(&addresseematch, TEAMMATE, name, MAX_MESSAGE_SIZE); - if (strlen(name)) { - if (stristr(botname, name)) return qtrue; - if (stristr(bs->subteam, name)) return qtrue; - } - break; - } - } - //Com_sprintf(buf, sizeof(buf), "not addressed to me but %s", addressedto); - //trap_EA_Say(bs->client, buf); - return qfalse; - } - else { - bot_match_t tellmatch; - - tellmatch.type = 0; - //if this message wasn't directed solely to this bot - if (!trap_BotFindMatch(match->string, &tellmatch, MTCONTEXT_REPLYCHAT) || - tellmatch.type != MSG_CHATTELL) { - //make sure not everyone reacts to this message - if (random() > (float ) 1.0 / (NumPlayersOnSameTeam(bs)-1)) return qfalse; - } - } - return qtrue; -} - -/* -================== -BotGPSToPosition -================== -*/ -int BotGPSToPosition(char *buf, vec3_t position) { - int i, j = 0; - int num, sign; - - for (i = 0; i < 3; i++) { - num = 0; - while(buf[j] == ' ') j++; - if (buf[j] == '-') { - j++; - sign = -1; - } - else { - sign = 1; - } - while (buf[j]) { - if (buf[j] >= '0' && buf[j] <= '9') { - num = num * 10 + buf[j] - '0'; - j++; - } - else { - j++; - break; - } - } - BotAI_Print(PRT_MESSAGE, "%d\n", sign * num); - position[i] = (float) sign * num; - } - return qtrue; -} - -/* -================== -BotMatch_HelpAccompany -================== -*/ -void BotMatch_HelpAccompany(bot_state_t *bs, bot_match_t *match) { - int client, other, areanum; - char teammate[MAX_MESSAGE_SIZE]; - char netname[MAX_MESSAGE_SIZE]; - char itemname[MAX_MESSAGE_SIZE]; - bot_match_t teammatematch; - aas_entityinfo_t entinfo; - - if (!TeamPlayIsOn()) return; - //if not addressed to this bot - if (!BotAddressedToBot(bs, match)) return; - //get the team mate name - trap_BotMatchVariable(match, TEAMMATE, teammate, sizeof(teammate)); - //get the client to help - if (trap_BotFindMatch(teammate, &teammatematch, MTCONTEXT_TEAMMATE) && - //if someone asks for him or herself - teammatematch.type == MSG_ME) { - //get the netname - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - client = ClientFromName(netname); - other = qfalse; - } - else { - //asked for someone else - client = FindClientByName(teammate); - //if this is the bot self - if (client == bs->client) { - other = qfalse; - } - else if (!BotSameTeam(bs, client)) { - //FIXME: say "I don't help the enemy" - return; - } - else { - other = qtrue; - } - } - //if the bot doesn't know who to help (FindClientByName returned -1) - if (client < 0) { - if (other) BotAI_BotInitialChat(bs, "whois", teammate, NULL); - else BotAI_BotInitialChat(bs, "whois", netname, NULL); - client = ClientFromName(netname); - trap_BotEnterChat(bs->cs, client, CHAT_TELL); - return; - } - //don't help or accompany yourself - if (client == bs->client) { - return; - } - // - bs->teamgoal.entitynum = -1; - BotEntityInfo(client, &entinfo); - //if info is valid (in PVS) - if (entinfo.valid) { - areanum = BotPointAreaNum(entinfo.origin); - if (areanum) {// && trap_AAS_AreaReachability(areanum)) { - bs->teamgoal.entitynum = client; - bs->teamgoal.areanum = areanum; - VectorCopy(entinfo.origin, bs->teamgoal.origin); - VectorSet(bs->teamgoal.mins, -8, -8, -8); - VectorSet(bs->teamgoal.maxs, 8, 8, 8); - } - } - //if no teamgoal yet - if (bs->teamgoal.entitynum < 0) { - //if near an item - if (match->subtype & ST_NEARITEM) { - //get the match variable - trap_BotMatchVariable(match, ITEM, itemname, sizeof(itemname)); - // - if (!BotGetMessageTeamGoal(bs, itemname, &bs->teamgoal)) { - //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL); - //trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); - return; - } - } - } - // - if (bs->teamgoal.entitynum < 0) { - if (other) BotAI_BotInitialChat(bs, "whereis", teammate, NULL); - else BotAI_BotInitialChat(bs, "whereareyou", netname, NULL); - client = ClientFromName(netname); - trap_BotEnterChat(bs->cs, client, CHAT_TEAM); - return; - } - //the team mate - bs->teammate = client; - // - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - // - client = ClientFromName(netname); - //the team mate who ordered - bs->decisionmaker = client; - bs->ordered = qtrue; - bs->order_time = FloatTime(); - //last time the team mate was assumed visible - bs->teammatevisible_time = FloatTime(); - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - //get the team goal time - bs->teamgoal_time = BotGetTime(match); - //set the ltg type - if (match->type == MSG_HELP) { - bs->ltgtype = LTG_TEAMHELP; - if (!bs->teamgoal_time) bs->teamgoal_time = FloatTime() + TEAM_HELP_TIME; - } - else { - bs->ltgtype = LTG_TEAMACCOMPANY; - if (!bs->teamgoal_time) bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; - bs->formation_dist = 3.5 * 32; //3.5 meter - bs->arrive_time = 0; - // - BotSetTeamStatus(bs); - // remember last ordered task - BotRememberLastOrderedTask(bs); - } -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} - -/* -================== -BotMatch_DefendKeyArea -================== -*/ -void BotMatch_DefendKeyArea(bot_state_t *bs, bot_match_t *match) { - char itemname[MAX_MESSAGE_SIZE]; - char netname[MAX_MESSAGE_SIZE]; - int client; - - if (!TeamPlayIsOn()) return; - //if not addressed to this bot - if (!BotAddressedToBot(bs, match)) return; - //get the match variable - trap_BotMatchVariable(match, KEYAREA, itemname, sizeof(itemname)); - // - if (!BotGetMessageTeamGoal(bs, itemname, &bs->teamgoal)) { - //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL); - //trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); - return; - } - // - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - // - client = ClientFromName(netname); - //the team mate who ordered - bs->decisionmaker = client; - bs->ordered = qtrue; - bs->order_time = FloatTime(); - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - //set the ltg type - bs->ltgtype = LTG_DEFENDKEYAREA; - //get the team goal time - bs->teamgoal_time = BotGetTime(match); - //set the team goal time - if (!bs->teamgoal_time) bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; - //away from defending - bs->defendaway_time = 0; - // - BotSetTeamStatus(bs); - // remember last ordered task - BotRememberLastOrderedTask(bs); -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} - -/* -================== -BotMatch_GetItem -================== -*/ -void BotMatch_GetItem(bot_state_t *bs, bot_match_t *match) { - char itemname[MAX_MESSAGE_SIZE]; - char netname[MAX_MESSAGE_SIZE]; - int client; - - if (!TeamPlayIsOn()) return; - //if not addressed to this bot - if (!BotAddressedToBot(bs, match)) return; - //get the match variable - trap_BotMatchVariable(match, ITEM, itemname, sizeof(itemname)); - // - if (!BotGetMessageTeamGoal(bs, itemname, &bs->teamgoal)) { - //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL); - //trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); - return; - } - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - client = ClientOnSameTeamFromName(bs, netname); - // - bs->decisionmaker = client; - bs->ordered = qtrue; - bs->order_time = FloatTime(); - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - //set the ltg type - bs->ltgtype = LTG_GETITEM; - //set the team goal time - bs->teamgoal_time = FloatTime() + TEAM_GETITEM_TIME; - // - BotSetTeamStatus(bs); -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} - -/* -================== -BotMatch_Camp -================== -*/ -void BotMatch_Camp(bot_state_t *bs, bot_match_t *match) { - int client, areanum; - char netname[MAX_MESSAGE_SIZE]; - char itemname[MAX_MESSAGE_SIZE]; - aas_entityinfo_t entinfo; - - if (!TeamPlayIsOn()) return; - //if not addressed to this bot - if (!BotAddressedToBot(bs, match)) return; - // - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - //asked for someone else - client = FindClientByName(netname); - //if there's no valid client with this name - if (client < 0) { - BotAI_BotInitialChat(bs, "whois", netname, NULL); - trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); - return; - } - //get the match variable - trap_BotMatchVariable(match, KEYAREA, itemname, sizeof(itemname)); - //in CTF it could be the base - if (match->subtype & ST_THERE) { - //camp at the spot the bot is currently standing - bs->teamgoal.entitynum = bs->entitynum; - bs->teamgoal.areanum = bs->areanum; - VectorCopy(bs->origin, bs->teamgoal.origin); - VectorSet(bs->teamgoal.mins, -8, -8, -8); - VectorSet(bs->teamgoal.maxs, 8, 8, 8); - } - else if (match->subtype & ST_HERE) { - //if this is the bot self - if (client == bs->client) return; - // - bs->teamgoal.entitynum = -1; - BotEntityInfo(client, &entinfo); - //if info is valid (in PVS) - if (entinfo.valid) { - areanum = BotPointAreaNum(entinfo.origin); - if (areanum) {// && trap_AAS_AreaReachability(areanum)) { - //NOTE: just assume the bot knows where the person is - //if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, client)) { - bs->teamgoal.entitynum = client; - bs->teamgoal.areanum = areanum; - VectorCopy(entinfo.origin, bs->teamgoal.origin); - VectorSet(bs->teamgoal.mins, -8, -8, -8); - VectorSet(bs->teamgoal.maxs, 8, 8, 8); - //} - } - } - //if the other is not visible - if (bs->teamgoal.entitynum < 0) { - BotAI_BotInitialChat(bs, "whereareyou", netname, NULL); - client = ClientFromName(netname); - trap_BotEnterChat(bs->cs, client, CHAT_TELL); - return; - } - } - else if (!BotGetMessageTeamGoal(bs, itemname, &bs->teamgoal)) { - //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL); - //client = ClientFromName(netname); - //trap_BotEnterChat(bs->cs, client, CHAT_TELL); - return; - } - // - bs->decisionmaker = client; - bs->ordered = qtrue; - bs->order_time = FloatTime(); - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - //set the ltg type - bs->ltgtype = LTG_CAMPORDER; - //get the team goal time - bs->teamgoal_time = BotGetTime(match); - //set the team goal time - if (!bs->teamgoal_time) bs->teamgoal_time = FloatTime() + TEAM_CAMP_TIME; - //not arrived yet - bs->arrive_time = 0; - // - BotSetTeamStatus(bs); - // remember last ordered task - BotRememberLastOrderedTask(bs); -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} - -/* -================== -BotMatch_Patrol -================== -*/ -void BotMatch_Patrol(bot_state_t *bs, bot_match_t *match) { - char netname[MAX_MESSAGE_SIZE]; - int client; - - if (!TeamPlayIsOn()) return; - //if not addressed to this bot - if (!BotAddressedToBot(bs, match)) return; - //get the patrol waypoints - if (!BotGetPatrolWaypoints(bs, match)) return; - // - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - // - client = FindClientByName(netname); - // - bs->decisionmaker = client; - bs->ordered = qtrue; - bs->order_time = FloatTime(); - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - //set the ltg type - bs->ltgtype = LTG_PATROL; - //get the team goal time - bs->teamgoal_time = BotGetTime(match); - //set the team goal time if not set already - if (!bs->teamgoal_time) bs->teamgoal_time = FloatTime() + TEAM_PATROL_TIME; - // - BotSetTeamStatus(bs); - // remember last ordered task - BotRememberLastOrderedTask(bs); -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} - -/* -================== -BotMatch_GetFlag -================== -*/ -void BotMatch_GetFlag(bot_state_t *bs, bot_match_t *match) { - char netname[MAX_MESSAGE_SIZE]; - int client; - - if (gametype == GT_CTF) { - if (!ctf_redflag.areanum || !ctf_blueflag.areanum) - return; - } -#ifdef MISSIONPACK - else if (gametype == GT_1FCTF) { - if (!ctf_neutralflag.areanum || !ctf_redflag.areanum || !ctf_blueflag.areanum) - return; - } -#endif - else { - return; - } - //if not addressed to this bot - if (!BotAddressedToBot(bs, match)) return; - // - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - // - client = FindClientByName(netname); - // - bs->decisionmaker = client; - bs->ordered = qtrue; - bs->order_time = FloatTime(); - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - //set the ltg type - bs->ltgtype = LTG_GETFLAG; - //set the team goal time - bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME; - // get an alternate route in ctf - if (gametype == GT_CTF) { - //get an alternative route goal towards the enemy base - BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); - } - // - BotSetTeamStatus(bs); - // remember last ordered task - BotRememberLastOrderedTask(bs); -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} - -/* -================== -BotMatch_AttackEnemyBase -================== -*/ -void BotMatch_AttackEnemyBase(bot_state_t *bs, bot_match_t *match) { - char netname[MAX_MESSAGE_SIZE]; - int client; - - if (gametype == GT_CTF) { - BotMatch_GetFlag(bs, match); - } -#ifdef MISSIONPACK - else if (gametype == GT_1FCTF || gametype == GT_OBELISK || gametype == GT_HARVESTER) { - if (!redobelisk.areanum || !blueobelisk.areanum) - return; - } -#endif - else { - return; - } - //if not addressed to this bot - if (!BotAddressedToBot(bs, match)) return; - // - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - // - client = FindClientByName(netname); - // - bs->decisionmaker = client; - bs->ordered = qtrue; - bs->order_time = FloatTime(); - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - //set the ltg type - bs->ltgtype = LTG_ATTACKENEMYBASE; - //set the team goal time - bs->teamgoal_time = FloatTime() + TEAM_ATTACKENEMYBASE_TIME; - bs->attackaway_time = 0; - // - BotSetTeamStatus(bs); - // remember last ordered task - BotRememberLastOrderedTask(bs); -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} - -#ifdef MISSIONPACK -/* -================== -BotMatch_Harvest -================== -*/ -void BotMatch_Harvest(bot_state_t *bs, bot_match_t *match) { - char netname[MAX_MESSAGE_SIZE]; - int client; - - if (gametype == GT_HARVESTER) { - if (!neutralobelisk.areanum || !redobelisk.areanum || !blueobelisk.areanum) - return; - } - else { - return; - } - //if not addressed to this bot - if (!BotAddressedToBot(bs, match)) return; - // - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - // - client = FindClientByName(netname); - // - bs->decisionmaker = client; - bs->ordered = qtrue; - bs->order_time = FloatTime(); - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - //set the ltg type - bs->ltgtype = LTG_HARVEST; - //set the team goal time - bs->teamgoal_time = FloatTime() + TEAM_HARVEST_TIME; - bs->harvestaway_time = 0; - // - BotSetTeamStatus(bs); - // remember last ordered task - BotRememberLastOrderedTask(bs); -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} -#endif - -/* -================== -BotMatch_RushBase -================== -*/ -void BotMatch_RushBase(bot_state_t *bs, bot_match_t *match) { - char netname[MAX_MESSAGE_SIZE]; - int client; - - if (gametype == GT_CTF) { - if (!ctf_redflag.areanum || !ctf_blueflag.areanum) - return; - } -#ifdef MISSIONPACK - else if (gametype == GT_1FCTF || gametype == GT_HARVESTER) { - if (!redobelisk.areanum || !blueobelisk.areanum) - return; - } -#endif - else { - return; - } - //if not addressed to this bot - if (!BotAddressedToBot(bs, match)) return; - // - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - // - client = FindClientByName(netname); - // - bs->decisionmaker = client; - bs->ordered = qtrue; - bs->order_time = FloatTime(); - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - //set the ltg type - bs->ltgtype = LTG_RUSHBASE; - //set the team goal time - bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; - bs->rushbaseaway_time = 0; - // - BotSetTeamStatus(bs); -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} - -/* -================== -BotMatch_TaskPreference -================== -*/ -void BotMatch_TaskPreference(bot_state_t *bs, bot_match_t *match) { - char netname[MAX_NETNAME]; - char teammatename[MAX_MESSAGE_SIZE]; - int teammate, preference; - - ClientName(bs->client, netname, sizeof(netname)); - if (Q_stricmp(netname, bs->teamleader) != 0) return; - - trap_BotMatchVariable(match, NETNAME, teammatename, sizeof(teammatename)); - teammate = ClientFromName(teammatename); - if (teammate < 0) return; - - preference = BotGetTeamMateTaskPreference(bs, teammate); - switch(match->subtype) - { - case ST_DEFENDER: - { - preference &= ~TEAMTP_ATTACKER; - preference |= TEAMTP_DEFENDER; - break; - } - case ST_ATTACKER: - { - preference &= ~TEAMTP_DEFENDER; - preference |= TEAMTP_ATTACKER; - break; - } - case ST_ROAMER: - { - preference &= ~(TEAMTP_ATTACKER|TEAMTP_DEFENDER); - break; - } - } - BotSetTeamMateTaskPreference(bs, teammate, preference); - // - EasyClientName(teammate, teammatename, sizeof(teammatename)); - BotAI_BotInitialChat(bs, "keepinmind", teammatename, NULL); - trap_BotEnterChat(bs->cs, teammate, CHAT_TELL); - BotVoiceChatOnly(bs, teammate, VOICECHAT_YES); - trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); -} - -/* -================== -BotMatch_ReturnFlag -================== -*/ -void BotMatch_ReturnFlag(bot_state_t *bs, bot_match_t *match) { - char netname[MAX_MESSAGE_SIZE]; - int client; - - //if not in CTF mode - if ( - gametype != GT_CTF -#ifdef MISSIONPACK - && gametype != GT_1FCTF -#endif - ) - return; - //if not addressed to this bot - if (!BotAddressedToBot(bs, match)) - return; - // - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - // - client = FindClientByName(netname); - // - bs->decisionmaker = client; - bs->ordered = qtrue; - bs->order_time = FloatTime(); - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - //set the ltg type - bs->ltgtype = LTG_RETURNFLAG; - //set the team goal time - bs->teamgoal_time = FloatTime() + CTF_RETURNFLAG_TIME; - bs->rushbaseaway_time = 0; - // - BotSetTeamStatus(bs); -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} - -/* -================== -BotMatch_JoinSubteam -================== -*/ -void BotMatch_JoinSubteam(bot_state_t *bs, bot_match_t *match) { - char teammate[MAX_MESSAGE_SIZE]; - char netname[MAX_MESSAGE_SIZE]; - int client; - - if (!TeamPlayIsOn()) return; - //if not addressed to this bot - if (!BotAddressedToBot(bs, match)) return; - //get the sub team name - trap_BotMatchVariable(match, TEAMNAME, teammate, sizeof(teammate)); - //set the sub team name - strncpy(bs->subteam, teammate, 32); - bs->subteam[31] = '\0'; - // - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - BotAI_BotInitialChat(bs, "joinedteam", teammate, NULL); - client = ClientFromName(netname); - trap_BotEnterChat(bs->cs, client, CHAT_TELL); -} - -/* -================== -BotMatch_LeaveSubteam -================== -*/ -void BotMatch_LeaveSubteam(bot_state_t *bs, bot_match_t *match) { - char netname[MAX_MESSAGE_SIZE]; - int client; - - if (!TeamPlayIsOn()) return; - //if not addressed to this bot - if (!BotAddressedToBot(bs, match)) return; - // - if (strlen(bs->subteam)) - { - BotAI_BotInitialChat(bs, "leftteam", bs->subteam, NULL); - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - client = ClientFromName(netname); - trap_BotEnterChat(bs->cs, client, CHAT_TELL); - } //end if - strcpy(bs->subteam, ""); -} - -/* -================== -BotMatch_LeaveSubteam -================== -*/ -void BotMatch_WhichTeam(bot_state_t *bs, bot_match_t *match) { - if (!TeamPlayIsOn()) return; - //if not addressed to this bot - if (!BotAddressedToBot(bs, match)) return; - // - if (strlen(bs->subteam)) { - BotAI_BotInitialChat(bs, "inteam", bs->subteam, NULL); - } - else { - BotAI_BotInitialChat(bs, "noteam", NULL); - } - trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); -} - -/* -================== -BotMatch_CheckPoint -================== -*/ -void BotMatch_CheckPoint(bot_state_t *bs, bot_match_t *match) { - int areanum, client; - char buf[MAX_MESSAGE_SIZE]; - char netname[MAX_MESSAGE_SIZE]; - vec3_t position; - bot_waypoint_t *cp; - - if (!TeamPlayIsOn()) return; - // - trap_BotMatchVariable(match, POSITION, buf, MAX_MESSAGE_SIZE); - VectorClear(position); - // - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - client = ClientFromName(netname); - //BotGPSToPosition(buf, position); - sscanf(buf, "%f %f %f", &position[0], &position[1], &position[2]); - position[2] += 0.5; - areanum = BotPointAreaNum(position); - if (!areanum) { - if (BotAddressedToBot(bs, match)) { - BotAI_BotInitialChat(bs, "checkpoint_invalid", NULL); - trap_BotEnterChat(bs->cs, client, CHAT_TELL); - } - return; - } - // - trap_BotMatchVariable(match, NAME, buf, MAX_MESSAGE_SIZE); - //check if there already exists a checkpoint with this name - cp = BotFindWayPoint(bs->checkpoints, buf); - if (cp) { - if (cp->next) cp->next->prev = cp->prev; - if (cp->prev) cp->prev->next = cp->next; - else bs->checkpoints = cp->next; - cp->inuse = qfalse; - } - //create a new check point - cp = BotCreateWayPoint(buf, position, areanum); - //add the check point to the bot's known chech points - cp->next = bs->checkpoints; - if (bs->checkpoints) bs->checkpoints->prev = cp; - bs->checkpoints = cp; - // - if (BotAddressedToBot(bs, match)) { - Com_sprintf(buf, sizeof(buf), "%1.0f %1.0f %1.0f", cp->goal.origin[0], - cp->goal.origin[1], - cp->goal.origin[2]); - - BotAI_BotInitialChat(bs, "checkpoint_confirm", cp->name, buf, NULL); - trap_BotEnterChat(bs->cs, client, CHAT_TELL); - } -} - -/* -================== -BotMatch_FormationSpace -================== -*/ -void BotMatch_FormationSpace(bot_state_t *bs, bot_match_t *match) { - char buf[MAX_MESSAGE_SIZE]; - float space; - - if (!TeamPlayIsOn()) return; - //if not addressed to this bot - if (!BotAddressedToBot(bs, match)) return; - // - trap_BotMatchVariable(match, NUMBER, buf, MAX_MESSAGE_SIZE); - //if it's the distance in feet - if (match->subtype & ST_FEET) space = 0.3048 * 32 * atof(buf); - //else it's in meters - else space = 32 * atof(buf); - //check if the formation intervening space is valid - if (space < 48 || space > 500) space = 100; - bs->formation_dist = space; -} - -/* -================== -BotMatch_Dismiss -================== -*/ -void BotMatch_Dismiss(bot_state_t *bs, bot_match_t *match) { - char netname[MAX_MESSAGE_SIZE]; - int client; - - if (!TeamPlayIsOn()) return; - //if not addressed to this bot - if (!BotAddressedToBot(bs, match)) return; - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - client = ClientFromName(netname); - // - bs->decisionmaker = client; - // - bs->ltgtype = 0; - bs->lead_time = 0; - bs->lastgoal_ltgtype = 0; - // - BotAI_BotInitialChat(bs, "dismissed", NULL); - trap_BotEnterChat(bs->cs, client, CHAT_TELL); -} - -/* -================== -BotMatch_Suicide -================== -*/ -void BotMatch_Suicide(bot_state_t *bs, bot_match_t *match) { - char netname[MAX_MESSAGE_SIZE]; - int client; - - if (!TeamPlayIsOn()) return; - //if not addressed to this bot - if (!BotAddressedToBot(bs, match)) return; - // - trap_EA_Command(bs->client, "kill"); - // - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - client = ClientFromName(netname); - // - BotVoiceChat(bs, client, VOICECHAT_TAUNT); - trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); -} - -/* -================== -BotMatch_StartTeamLeaderShip -================== -*/ -void BotMatch_StartTeamLeaderShip(bot_state_t *bs, bot_match_t *match) { - int client; - char teammate[MAX_MESSAGE_SIZE]; - - if (!TeamPlayIsOn()) return; - //if chats for him or herself - if (match->subtype & ST_I) { - //get the team mate that will be the team leader - trap_BotMatchVariable(match, NETNAME, teammate, sizeof(teammate)); - strncpy(bs->teamleader, teammate, sizeof(bs->teamleader)); - bs->teamleader[sizeof(bs->teamleader)] = '\0'; - } - //chats for someone else - else { - //get the team mate that will be the team leader - trap_BotMatchVariable(match, TEAMMATE, teammate, sizeof(teammate)); - client = FindClientByName(teammate); - if (client >= 0) ClientName(client, bs->teamleader, sizeof(bs->teamleader)); - } -} - -/* -================== -BotMatch_StopTeamLeaderShip -================== -*/ -void BotMatch_StopTeamLeaderShip(bot_state_t *bs, bot_match_t *match) { - int client; - char teammate[MAX_MESSAGE_SIZE]; - char netname[MAX_MESSAGE_SIZE]; - - if (!TeamPlayIsOn()) return; - //get the team mate that stops being the team leader - trap_BotMatchVariable(match, TEAMMATE, teammate, sizeof(teammate)); - //if chats for him or herself - if (match->subtype & ST_I) { - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - client = FindClientByName(netname); - } - //chats for someone else - else { - client = FindClientByName(teammate); - } //end else - if (client >= 0) { - if (!Q_stricmp(bs->teamleader, ClientName(client, netname, sizeof(netname)))) { - bs->teamleader[0] = '\0'; - notleader[client] = qtrue; - } - } -} - -/* -================== -BotMatch_WhoIsTeamLeader -================== -*/ -void BotMatch_WhoIsTeamLeader(bot_state_t *bs, bot_match_t *match) { - char netname[MAX_MESSAGE_SIZE]; - - if (!TeamPlayIsOn()) return; - - ClientName(bs->client, netname, sizeof(netname)); - //if this bot IS the team leader - if (!Q_stricmp(netname, bs->teamleader)) { - trap_EA_SayTeam(bs->client, "I'm the team leader\n"); - } -} - -/* -================== -BotMatch_WhatAreYouDoing -================== -*/ -void BotMatch_WhatAreYouDoing(bot_state_t *bs, bot_match_t *match) { - char netname[MAX_MESSAGE_SIZE]; - char goalname[MAX_MESSAGE_SIZE]; - int client; - - //if not addressed to this bot - if (!BotAddressedToBot(bs, match)) return; - // - switch(bs->ltgtype) { - case LTG_TEAMHELP: - { - EasyClientName(bs->teammate, netname, sizeof(netname)); - BotAI_BotInitialChat(bs, "helping", netname, NULL); - break; - } - case LTG_TEAMACCOMPANY: - { - EasyClientName(bs->teammate, netname, sizeof(netname)); - BotAI_BotInitialChat(bs, "accompanying", netname, NULL); - break; - } - case LTG_DEFENDKEYAREA: - { - trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); - BotAI_BotInitialChat(bs, "defending", goalname, NULL); - break; - } - case LTG_GETITEM: - { - trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); - BotAI_BotInitialChat(bs, "gettingitem", goalname, NULL); - break; - } - case LTG_KILL: - { - ClientName(bs->teamgoal.entitynum, netname, sizeof(netname)); - BotAI_BotInitialChat(bs, "killing", netname, NULL); - break; - } - case LTG_CAMP: - case LTG_CAMPORDER: - { - BotAI_BotInitialChat(bs, "camping", NULL); - break; - } - case LTG_PATROL: - { - BotAI_BotInitialChat(bs, "patrolling", NULL); - break; - } - case LTG_GETFLAG: - { - BotAI_BotInitialChat(bs, "capturingflag", NULL); - break; - } - case LTG_RUSHBASE: - { - BotAI_BotInitialChat(bs, "rushingbase", NULL); - break; - } - case LTG_RETURNFLAG: - { - BotAI_BotInitialChat(bs, "returningflag", NULL); - break; - } -#ifdef MISSIONPACK - case LTG_ATTACKENEMYBASE: - { - BotAI_BotInitialChat(bs, "attackingenemybase", NULL); - break; - } - case LTG_HARVEST: - { - BotAI_BotInitialChat(bs, "harvesting", NULL); - break; - } -#endif - default: - { - BotAI_BotInitialChat(bs, "roaming", NULL); - break; - } - } - //chat what the bot is doing - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - client = ClientFromName(netname); - trap_BotEnterChat(bs->cs, client, CHAT_TELL); -} - -/* -================== -BotMatch_WhatIsMyCommand -================== -*/ -void BotMatch_WhatIsMyCommand(bot_state_t *bs, bot_match_t *match) { - char netname[MAX_NETNAME]; - - ClientName(bs->client, netname, sizeof(netname)); - if (Q_stricmp(netname, bs->teamleader) != 0) return; - bs->forceorders = qtrue; -} - -/* -================== -BotNearestVisibleItem -================== -*/ -float BotNearestVisibleItem(bot_state_t *bs, char *itemname, bot_goal_t *goal) { - int i; - char name[64]; - bot_goal_t tmpgoal; - float dist, bestdist; - vec3_t dir; - bsp_trace_t trace; - - bestdist = 999999; - i = -1; - do { - i = trap_BotGetLevelItemGoal(i, itemname, &tmpgoal); - trap_BotGoalName(tmpgoal.number, name, sizeof(name)); - if (Q_stricmp(itemname, name) != 0) - continue; - VectorSubtract(tmpgoal.origin, bs->origin, dir); - dist = VectorLength(dir); - if (dist < bestdist) { - //trace from start to end - BotAI_Trace(&trace, bs->eye, NULL, NULL, tmpgoal.origin, bs->client, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); - if (trace.fraction >= 1.0) { - bestdist = dist; - memcpy(goal, &tmpgoal, sizeof(bot_goal_t)); - } - } - } while(i > 0); - return bestdist; -} - -/* -================== -BotMatch_WhereAreYou -================== -*/ -void BotMatch_WhereAreYou(bot_state_t *bs, bot_match_t *match) { - float dist, bestdist; - int i, bestitem, redtt, bluett, client; - bot_goal_t goal; - char netname[MAX_MESSAGE_SIZE]; - char *nearbyitems[] = { - "Shotgun", - "Grenade Launcher", - "Rocket Launcher", - "Plasmagun", - "Railgun", - "Lightning Gun", - "BFG10K", - "Quad Damage", - "Regeneration", - "Battle Suit", - "Speed", - "Invisibility", - "Flight", - "Armor", - "Heavy Armor", - "Red Flag", - "Blue Flag", -#ifdef MISSIONPACK - "Nailgun", - "Prox Launcher", - "Chaingun", - "Scout", - "Guard", - "Doubler", - "Ammo Regen", - "Neutral Flag", - "Red Obelisk", - "Blue Obelisk", - "Neutral Obelisk", -#endif - NULL - }; - // - if (!TeamPlayIsOn()) - return; - //if not addressed to this bot - if (!BotAddressedToBot(bs, match)) - return; - - bestitem = -1; - bestdist = 999999; - for (i = 0; nearbyitems[i]; i++) { - dist = BotNearestVisibleItem(bs, nearbyitems[i], &goal); - if (dist < bestdist) { - bestdist = dist; - bestitem = i; - } - } - if (bestitem != -1) { - if (gametype == GT_CTF -#ifdef MISSIONPACK - || gametype == GT_1FCTF -#endif - ) { - redtt = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, ctf_redflag.areanum, TFL_DEFAULT); - bluett = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, ctf_blueflag.areanum, TFL_DEFAULT); - if (redtt < (redtt + bluett) * 0.4) { - BotAI_BotInitialChat(bs, "teamlocation", nearbyitems[bestitem], "red", NULL); - } - else if (bluett < (redtt + bluett) * 0.4) { - BotAI_BotInitialChat(bs, "teamlocation", nearbyitems[bestitem], "blue", NULL); - } - else { - BotAI_BotInitialChat(bs, "location", nearbyitems[bestitem], NULL); - } - } -#ifdef MISSIONPACK - else if (gametype == GT_OBELISK || gametype == GT_HARVESTER) { - redtt = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, redobelisk.areanum, TFL_DEFAULT); - bluett = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, blueobelisk.areanum, TFL_DEFAULT); - if (redtt < (redtt + bluett) * 0.4) { - BotAI_BotInitialChat(bs, "teamlocation", nearbyitems[bestitem], "red", NULL); - } - else if (bluett < (redtt + bluett) * 0.4) { - BotAI_BotInitialChat(bs, "teamlocation", nearbyitems[bestitem], "blue", NULL); - } - else { - BotAI_BotInitialChat(bs, "location", nearbyitems[bestitem], NULL); - } - } -#endif - else { - BotAI_BotInitialChat(bs, "location", nearbyitems[bestitem], NULL); - } - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - client = ClientFromName(netname); - trap_BotEnterChat(bs->cs, client, CHAT_TELL); - } -} - -/* -================== -BotMatch_LeadTheWay -================== -*/ -void BotMatch_LeadTheWay(bot_state_t *bs, bot_match_t *match) { - aas_entityinfo_t entinfo; - char netname[MAX_MESSAGE_SIZE], teammate[MAX_MESSAGE_SIZE]; - int client, areanum, other; - - if (!TeamPlayIsOn()) return; - //if not addressed to this bot - if (!BotAddressedToBot(bs, match)) return; - //if someone asks for someone else - if (match->subtype & ST_SOMEONE) { - //get the team mate name - trap_BotMatchVariable(match, TEAMMATE, teammate, sizeof(teammate)); - client = FindClientByName(teammate); - //if this is the bot self - if (client == bs->client) { - other = qfalse; - } - else if (!BotSameTeam(bs, client)) { - //FIXME: say "I don't help the enemy" - return; - } - else { - other = qtrue; - } - } - else { - //get the netname - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - client = ClientFromName(netname); - other = qfalse; - } - //if the bot doesn't know who to help (FindClientByName returned -1) - if (client < 0) { - BotAI_BotInitialChat(bs, "whois", netname, NULL); - trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); - return; - } - // - bs->lead_teamgoal.entitynum = -1; - BotEntityInfo(client, &entinfo); - //if info is valid (in PVS) - if (entinfo.valid) { - areanum = BotPointAreaNum(entinfo.origin); - if (areanum) { // && trap_AAS_AreaReachability(areanum)) { - bs->lead_teamgoal.entitynum = client; - bs->lead_teamgoal.areanum = areanum; - VectorCopy(entinfo.origin, bs->lead_teamgoal.origin); - VectorSet(bs->lead_teamgoal.mins, -8, -8, -8); - VectorSet(bs->lead_teamgoal.maxs, 8, 8, 8); - } - } - - if (bs->teamgoal.entitynum < 0) { - if (other) BotAI_BotInitialChat(bs, "whereis", teammate, NULL); - else BotAI_BotInitialChat(bs, "whereareyou", netname, NULL); - trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); - return; - } - bs->lead_teammate = client; - bs->lead_time = FloatTime() + TEAM_LEAD_TIME; - bs->leadvisible_time = 0; - bs->leadmessage_time = -(FloatTime() + 2 * random()); -} - -/* -================== -BotMatch_Kill -================== -*/ -void BotMatch_Kill(bot_state_t *bs, bot_match_t *match) { - char enemy[MAX_MESSAGE_SIZE]; - char netname[MAX_MESSAGE_SIZE]; - int client; - - if (!TeamPlayIsOn()) return; - //if not addressed to this bot - if (!BotAddressedToBot(bs, match)) return; - - trap_BotMatchVariable(match, ENEMY, enemy, sizeof(enemy)); - // - client = FindEnemyByName(bs, enemy); - if (client < 0) { - BotAI_BotInitialChat(bs, "whois", enemy, NULL); - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - client = ClientFromName(netname); - trap_BotEnterChat(bs->cs, client, CHAT_TELL); - return; - } - bs->teamgoal.entitynum = client; - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - //set the ltg type - bs->ltgtype = LTG_KILL; - //set the team goal time - bs->teamgoal_time = FloatTime() + TEAM_KILL_SOMEONE; - // - BotSetTeamStatus(bs); -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} - -/* -================== -BotMatch_CTF -================== -*/ -void BotMatch_CTF(bot_state_t *bs, bot_match_t *match) { - - char flag[128], netname[MAX_NETNAME]; - - if (gametype == GT_CTF) { - trap_BotMatchVariable(match, FLAG, flag, sizeof(flag)); - if (match->subtype & ST_GOTFLAG) { - if (!Q_stricmp(flag, "red")) { - bs->redflagstatus = 1; - if (BotTeam(bs) == TEAM_BLUE) { - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - bs->flagcarrier = ClientFromName(netname); - } - } - else { - bs->blueflagstatus = 1; - if (BotTeam(bs) == TEAM_RED) { - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - bs->flagcarrier = ClientFromName(netname); - } - } - bs->flagstatuschanged = 1; - bs->lastflagcapture_time = FloatTime(); - } - else if (match->subtype & ST_CAPTUREDFLAG) { - bs->redflagstatus = 0; - bs->blueflagstatus = 0; - bs->flagcarrier = 0; - bs->flagstatuschanged = 1; - } - else if (match->subtype & ST_RETURNEDFLAG) { - if (!Q_stricmp(flag, "red")) bs->redflagstatus = 0; - else bs->blueflagstatus = 0; - bs->flagstatuschanged = 1; - } - } -#ifdef MISSIONPACK - else if (gametype == GT_1FCTF) { - if (match->subtype & ST_1FCTFGOTFLAG) { - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - bs->flagcarrier = ClientFromName(netname); - } - } -#endif -} - -void BotMatch_EnterGame(bot_state_t *bs, bot_match_t *match) { - int client; - char netname[MAX_NETNAME]; - - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - client = FindClientByName(netname); - if (client >= 0) { - notleader[client] = qfalse; - } - //NOTE: eliza chats will catch this - //Com_sprintf(buf, sizeof(buf), "heya %s", netname); - //EA_Say(bs->client, buf); -} - -void BotMatch_NewLeader(bot_state_t *bs, bot_match_t *match) { - int client; - char netname[MAX_NETNAME]; - - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - client = FindClientByName(netname); - if (!BotSameTeam(bs, client)) - return; - Q_strncpyz(bs->teamleader, netname, sizeof(bs->teamleader)); -} - -/* -================== -BotMatchMessage -================== -*/ -int BotMatchMessage(bot_state_t *bs, char *message) { - bot_match_t match; - - match.type = 0; - //if it is an unknown message - if (!trap_BotFindMatch(message, &match, MTCONTEXT_MISC - |MTCONTEXT_INITIALTEAMCHAT - |MTCONTEXT_CTF)) { - return qfalse; - } - //react to the found message - switch(match.type) - { - case MSG_HELP: //someone calling for help - case MSG_ACCOMPANY: //someone calling for company - { - BotMatch_HelpAccompany(bs, &match); - break; - } - case MSG_DEFENDKEYAREA: //teamplay defend a key area - { - BotMatch_DefendKeyArea(bs, &match); - break; - } - case MSG_CAMP: //camp somewhere - { - BotMatch_Camp(bs, &match); - break; - } - case MSG_PATROL: //patrol between several key areas - { - BotMatch_Patrol(bs, &match); - break; - } - //CTF & 1FCTF - case MSG_GETFLAG: //ctf get the enemy flag - { - BotMatch_GetFlag(bs, &match); - break; - } -#ifdef MISSIONPACK - //CTF & 1FCTF & Obelisk & Harvester - case MSG_ATTACKENEMYBASE: - { - BotMatch_AttackEnemyBase(bs, &match); - break; - } - //Harvester - case MSG_HARVEST: - { - BotMatch_Harvest(bs, &match); - break; - } -#endif - //CTF & 1FCTF & Harvester - case MSG_RUSHBASE: //ctf rush to the base - { - BotMatch_RushBase(bs, &match); - break; - } - //CTF & 1FCTF - case MSG_RETURNFLAG: - { - BotMatch_ReturnFlag(bs, &match); - break; - } - //CTF & 1FCTF & Obelisk & Harvester - case MSG_TASKPREFERENCE: - { - BotMatch_TaskPreference(bs, &match); - break; - } - //CTF & 1FCTF - case MSG_CTF: - { - BotMatch_CTF(bs, &match); - break; - } - case MSG_GETITEM: - { - BotMatch_GetItem(bs, &match); - break; - } - case MSG_JOINSUBTEAM: //join a sub team - { - BotMatch_JoinSubteam(bs, &match); - break; - } - case MSG_LEAVESUBTEAM: //leave a sub team - { - BotMatch_LeaveSubteam(bs, &match); - break; - } - case MSG_WHICHTEAM: - { - BotMatch_WhichTeam(bs, &match); - break; - } - case MSG_CHECKPOINT: //remember a check point - { - BotMatch_CheckPoint(bs, &match); - break; - } - case MSG_CREATENEWFORMATION: //start the creation of a new formation - { - trap_EA_SayTeam(bs->client, "the part of my brain to create formations has been damaged"); - break; - } - case MSG_FORMATIONPOSITION: //tell someone his/her position in the formation - { - trap_EA_SayTeam(bs->client, "the part of my brain to create formations has been damaged"); - break; - } - case MSG_FORMATIONSPACE: //set the formation space - { - BotMatch_FormationSpace(bs, &match); - break; - } - case MSG_DOFORMATION: //form a certain formation - { - break; - } - case MSG_DISMISS: //dismiss someone - { - BotMatch_Dismiss(bs, &match); - break; - } - case MSG_STARTTEAMLEADERSHIP: //someone will become the team leader - { - BotMatch_StartTeamLeaderShip(bs, &match); - break; - } - case MSG_STOPTEAMLEADERSHIP: //someone will stop being the team leader - { - BotMatch_StopTeamLeaderShip(bs, &match); - break; - } - case MSG_WHOISTEAMLAEDER: - { - BotMatch_WhoIsTeamLeader(bs, &match); - break; - } - case MSG_WHATAREYOUDOING: //ask a bot what he/she is doing - { - BotMatch_WhatAreYouDoing(bs, &match); - break; - } - case MSG_WHATISMYCOMMAND: - { - BotMatch_WhatIsMyCommand(bs, &match); - break; - } - case MSG_WHEREAREYOU: - { - BotMatch_WhereAreYou(bs, &match); - break; - } - case MSG_LEADTHEWAY: - { - BotMatch_LeadTheWay(bs, &match); - break; - } - case MSG_KILL: - { - BotMatch_Kill(bs, &match); - break; - } - case MSG_ENTERGAME: //someone entered the game - { - BotMatch_EnterGame(bs, &match); - break; - } - case MSG_NEWLEADER: - { - BotMatch_NewLeader(bs, &match); - break; - } - case MSG_WAIT: - { - break; - } - case MSG_SUICIDE: - { - BotMatch_Suicide(bs, &match); - break; - } - default: - { - BotAI_Print(PRT_MESSAGE, "unknown match type\n"); - break; - } - } - return qtrue; -} +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: ai_cmd.c + * + * desc: Quake3 bot AI + * + * $Archive: /MissionPack/code/game/ai_cmd.c $ + * + *****************************************************************************/ + +#include "g_local.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_ea.h" +#include "be_ai_char.h" +#include "be_ai_chat.h" +#include "be_ai_gen.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" +#include "be_ai_weap.h" +// +#include "ai_main.h" +#include "ai_dmq3.h" +#include "ai_chat.h" +#include "ai_cmd.h" +#include "ai_dmnet.h" +#include "ai_team.h" +// +#include "chars.h" //characteristics +#include "inv.h" //indexes into the inventory +#include "syn.h" //synonyms +#include "match.h" //string matching types and vars + +// for the voice chats +#include "../../ui/menudef.h" + +int notleader[MAX_CLIENTS]; + +#ifdef DEBUG +/* +================== +BotPrintTeamGoal +================== +*/ +void BotPrintTeamGoal(bot_state_t *bs) { + char netname[MAX_NETNAME]; + float t; + + ClientName(bs->client, netname, sizeof(netname)); + t = bs->teamgoal_time - FloatTime(); + switch(bs->ltgtype) { + case LTG_TEAMHELP: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna help a team mate for %1.0f secs\n", netname, t); + break; + } + case LTG_TEAMACCOMPANY: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna accompany a team mate for %1.0f secs\n", netname, t); + break; + } + case LTG_GETFLAG: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna get the flag for %1.0f secs\n", netname, t); + break; + } + case LTG_RUSHBASE: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna rush to the base for %1.0f secs\n", netname, t); + break; + } + case LTG_RETURNFLAG: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna try to return the flag for %1.0f secs\n", netname, t); + break; + } +#ifdef MISSIONPACK + case LTG_ATTACKENEMYBASE: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna attack the enemy base for %1.0f secs\n", netname, t); + break; + } + case LTG_HARVEST: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna harvest for %1.0f secs\n", netname, t); + break; + } +#endif + case LTG_DEFENDKEYAREA: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna defend a key area for %1.0f secs\n", netname, t); + break; + } + case LTG_GETITEM: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna get an item for %1.0f secs\n", netname, t); + break; + } + case LTG_KILL: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna kill someone for %1.0f secs\n", netname, t); + break; + } + case LTG_CAMP: + case LTG_CAMPORDER: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna camp for %1.0f secs\n", netname, t); + break; + } + case LTG_PATROL: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna patrol for %1.0f secs\n", netname, t); + break; + } + default: + { + if (bs->ctfroam_time > FloatTime()) { + t = bs->ctfroam_time - FloatTime(); + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna roam for %1.0f secs\n", netname, t); + } + else { + BotAI_Print(PRT_MESSAGE, "%s: I've got a regular goal\n", netname); + } + } + } +} +#endif //DEBUG + +/* +================== +BotGetItemTeamGoal + +FIXME: add stuff like "upper rocket launcher" +"the rl near the railgun", "lower grenade launcher" etc. +================== +*/ +int BotGetItemTeamGoal(char *goalname, bot_goal_t *goal) { + int i; + + if (!strlen(goalname)) return qfalse; + i = -1; + do { + i = trap_BotGetLevelItemGoal(i, goalname, goal); + if (i > 0) { + //do NOT defend dropped items + if (goal->flags & GFL_DROPPED) + continue; + return qtrue; + } + } while(i > 0); + return qfalse; +} + +/* +================== +BotGetMessageTeamGoal +================== +*/ +int BotGetMessageTeamGoal(bot_state_t *bs, char *goalname, bot_goal_t *goal) { + bot_waypoint_t *cp; + + if (BotGetItemTeamGoal(goalname, goal)) return qtrue; + + cp = BotFindWayPoint(bs->checkpoints, goalname); + if (cp) { + memcpy(goal, &cp->goal, sizeof(bot_goal_t)); + return qtrue; + } + return qfalse; +} + +/* +================== +BotGetTime +================== +*/ +float BotGetTime(bot_match_t *match) { + bot_match_t timematch; + char timestring[MAX_MESSAGE_SIZE]; + float t; + + //if the matched string has a time + if (match->subtype & ST_TIME) { + //get the time string + trap_BotMatchVariable(match, TIME, timestring, MAX_MESSAGE_SIZE); + //match it to find out if the time is in seconds or minutes + if (trap_BotFindMatch(timestring, &timematch, MTCONTEXT_TIME)) { + if (timematch.type == MSG_FOREVER) { + t = 99999999.0f; + } + else if (timematch.type == MSG_FORAWHILE) { + t = 10 * 60; // 10 minutes + } + else if (timematch.type == MSG_FORALONGTIME) { + t = 30 * 60; // 30 minutes + } + else { + trap_BotMatchVariable(&timematch, TIME, timestring, MAX_MESSAGE_SIZE); + if (timematch.type == MSG_MINUTES) t = atof(timestring) * 60; + else if (timematch.type == MSG_SECONDS) t = atof(timestring); + else t = 0; + } + //if there's a valid time + if (t > 0) return FloatTime() + t; + } + } + return 0; +} + +/* +================== +FindClientByName +================== +*/ +int FindClientByName(char *name) { + int i; + char buf[MAX_INFO_STRING]; + static int maxclients; + + if (!maxclients) + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + ClientName(i, buf, sizeof(buf)); + if (!Q_stricmp(buf, name)) return i; + } + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + ClientName(i, buf, sizeof(buf)); + if (stristr(buf, name)) return i; + } + return -1; +} + +/* +================== +FindEnemyByName +================== +*/ +int FindEnemyByName(bot_state_t *bs, char *name) { + int i; + char buf[MAX_INFO_STRING]; + static int maxclients; + + if (!maxclients) + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + if (BotSameTeam(bs, i)) continue; + ClientName(i, buf, sizeof(buf)); + if (!Q_stricmp(buf, name)) return i; + } + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + if (BotSameTeam(bs, i)) continue; + ClientName(i, buf, sizeof(buf)); + if (stristr(buf, name)) return i; + } + return -1; +} + +/* +================== +NumPlayersOnSameTeam +================== +*/ +int NumPlayersOnSameTeam(bot_state_t *bs) { + int i, num; + char buf[MAX_INFO_STRING]; + static int maxclients; + + if (!maxclients) + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); + + num = 0; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + trap_GetConfigstring(CS_PLAYERS+i, buf, MAX_INFO_STRING); + if (strlen(buf)) { + if (BotSameTeam(bs, i+1)) num++; + } + } + return num; +} + +/* +================== +TeamPlayIsOn +================== +*/ +int BotGetPatrolWaypoints(bot_state_t *bs, bot_match_t *match) { + char keyarea[MAX_MESSAGE_SIZE]; + int patrolflags; + bot_waypoint_t *wp, *newwp, *newpatrolpoints; + bot_match_t keyareamatch; + bot_goal_t goal; + + newpatrolpoints = NULL; + patrolflags = 0; + // + trap_BotMatchVariable(match, KEYAREA, keyarea, MAX_MESSAGE_SIZE); + // + while(1) { + if (!trap_BotFindMatch(keyarea, &keyareamatch, MTCONTEXT_PATROLKEYAREA)) { + trap_EA_SayTeam(bs->client, "what do you say?"); + BotFreeWaypoints(newpatrolpoints); + bs->patrolpoints = NULL; + return qfalse; + } + trap_BotMatchVariable(&keyareamatch, KEYAREA, keyarea, MAX_MESSAGE_SIZE); + if (!BotGetMessageTeamGoal(bs, keyarea, &goal)) { + //BotAI_BotInitialChat(bs, "cannotfind", keyarea, NULL); + //trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotFreeWaypoints(newpatrolpoints); + bs->patrolpoints = NULL; + return qfalse; + } + //create a new waypoint + newwp = BotCreateWayPoint(keyarea, goal.origin, goal.areanum); + if (!newwp) + break; + //add the waypoint to the patrol points + newwp->next = NULL; + for (wp = newpatrolpoints; wp && wp->next; wp = wp->next); + if (!wp) { + newpatrolpoints = newwp; + newwp->prev = NULL; + } + else { + wp->next = newwp; + newwp->prev = wp; + } + // + if (keyareamatch.subtype & ST_BACK) { + patrolflags = PATROL_LOOP; + break; + } + else if (keyareamatch.subtype & ST_REVERSE) { + patrolflags = PATROL_REVERSE; + break; + } + else if (keyareamatch.subtype & ST_MORE) { + trap_BotMatchVariable(&keyareamatch, MORE, keyarea, MAX_MESSAGE_SIZE); + } + else { + break; + } + } + // + if (!newpatrolpoints || !newpatrolpoints->next) { + trap_EA_SayTeam(bs->client, "I need more key points to patrol\n"); + BotFreeWaypoints(newpatrolpoints); + newpatrolpoints = NULL; + return qfalse; + } + // + BotFreeWaypoints(bs->patrolpoints); + bs->patrolpoints = newpatrolpoints; + // + bs->curpatrolpoint = bs->patrolpoints; + bs->patrolflags = patrolflags; + // + return qtrue; +} + +/* +================== +BotAddressedToBot +================== +*/ +int BotAddressedToBot(bot_state_t *bs, bot_match_t *match) { + char addressedto[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + char name[MAX_MESSAGE_SIZE]; + char botname[128]; + int client; + bot_match_t addresseematch; + + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientOnSameTeamFromName(bs, netname); + if (client < 0) return qfalse; + //if the message is addressed to someone + if (match->subtype & ST_ADDRESSED) { + trap_BotMatchVariable(match, ADDRESSEE, addressedto, sizeof(addressedto)); + //the name of this bot + ClientName(bs->client, botname, 128); + // + while(trap_BotFindMatch(addressedto, &addresseematch, MTCONTEXT_ADDRESSEE)) { + if (addresseematch.type == MSG_EVERYONE) { + return qtrue; + } + else if (addresseematch.type == MSG_MULTIPLENAMES) { + trap_BotMatchVariable(&addresseematch, TEAMMATE, name, sizeof(name)); + if (strlen(name)) { + if (stristr(botname, name)) return qtrue; + if (stristr(bs->subteam, name)) return qtrue; + } + trap_BotMatchVariable(&addresseematch, MORE, addressedto, MAX_MESSAGE_SIZE); + } + else { + trap_BotMatchVariable(&addresseematch, TEAMMATE, name, MAX_MESSAGE_SIZE); + if (strlen(name)) { + if (stristr(botname, name)) return qtrue; + if (stristr(bs->subteam, name)) return qtrue; + } + break; + } + } + //Com_sprintf(buf, sizeof(buf), "not addressed to me but %s", addressedto); + //trap_EA_Say(bs->client, buf); + return qfalse; + } + else { + bot_match_t tellmatch; + + tellmatch.type = 0; + //if this message wasn't directed solely to this bot + if (!trap_BotFindMatch(match->string, &tellmatch, MTCONTEXT_REPLYCHAT) || + tellmatch.type != MSG_CHATTELL) { + //make sure not everyone reacts to this message + if (random() > (float ) 1.0 / (NumPlayersOnSameTeam(bs)-1)) return qfalse; + } + } + return qtrue; +} + +/* +================== +BotGPSToPosition +================== +*/ +int BotGPSToPosition(char *buf, vec3_t position) { + int i, j = 0; + int num, sign; + + for (i = 0; i < 3; i++) { + num = 0; + while(buf[j] == ' ') j++; + if (buf[j] == '-') { + j++; + sign = -1; + } + else { + sign = 1; + } + while (buf[j]) { + if (buf[j] >= '0' && buf[j] <= '9') { + num = num * 10 + buf[j] - '0'; + j++; + } + else { + j++; + break; + } + } + BotAI_Print(PRT_MESSAGE, "%d\n", sign * num); + position[i] = (float) sign * num; + } + return qtrue; +} + +/* +================== +BotMatch_HelpAccompany +================== +*/ +void BotMatch_HelpAccompany(bot_state_t *bs, bot_match_t *match) { + int client, other, areanum; + char teammate[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + char itemname[MAX_MESSAGE_SIZE]; + bot_match_t teammatematch; + aas_entityinfo_t entinfo; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + //get the team mate name + trap_BotMatchVariable(match, TEAMMATE, teammate, sizeof(teammate)); + //get the client to help + if (trap_BotFindMatch(teammate, &teammatematch, MTCONTEXT_TEAMMATE) && + //if someone asks for him or herself + teammatematch.type == MSG_ME) { + //get the netname + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientFromName(netname); + other = qfalse; + } + else { + //asked for someone else + client = FindClientByName(teammate); + //if this is the bot self + if (client == bs->client) { + other = qfalse; + } + else if (!BotSameTeam(bs, client)) { + //FIXME: say "I don't help the enemy" + return; + } + else { + other = qtrue; + } + } + //if the bot doesn't know who to help (FindClientByName returned -1) + if (client < 0) { + if (other) BotAI_BotInitialChat(bs, "whois", teammate, NULL); + else BotAI_BotInitialChat(bs, "whois", netname, NULL); + client = ClientFromName(netname); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); + return; + } + //don't help or accompany yourself + if (client == bs->client) { + return; + } + // + bs->teamgoal.entitynum = -1; + BotEntityInfo(client, &entinfo); + //if info is valid (in PVS) + if (entinfo.valid) { + areanum = BotPointAreaNum(entinfo.origin); + if (areanum) {// && trap_AAS_AreaReachability(areanum)) { + bs->teamgoal.entitynum = client; + bs->teamgoal.areanum = areanum; + VectorCopy(entinfo.origin, bs->teamgoal.origin); + VectorSet(bs->teamgoal.mins, -8, -8, -8); + VectorSet(bs->teamgoal.maxs, 8, 8, 8); + } + } + //if no teamgoal yet + if (bs->teamgoal.entitynum < 0) { + //if near an item + if (match->subtype & ST_NEARITEM) { + //get the match variable + trap_BotMatchVariable(match, ITEM, itemname, sizeof(itemname)); + // + if (!BotGetMessageTeamGoal(bs, itemname, &bs->teamgoal)) { + //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL); + //trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + return; + } + } + } + // + if (bs->teamgoal.entitynum < 0) { + if (other) BotAI_BotInitialChat(bs, "whereis", teammate, NULL); + else BotAI_BotInitialChat(bs, "whereareyou", netname, NULL); + client = ClientFromName(netname); + trap_BotEnterChat(bs->cs, client, CHAT_TEAM); + return; + } + //the team mate + bs->teammate = client; + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + // + client = ClientFromName(netname); + //the team mate who ordered + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //last time the team mate was assumed visible + bs->teammatevisible_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //get the team goal time + bs->teamgoal_time = BotGetTime(match); + //set the ltg type + if (match->type == MSG_HELP) { + bs->ltgtype = LTG_TEAMHELP; + if (!bs->teamgoal_time) bs->teamgoal_time = FloatTime() + TEAM_HELP_TIME; + } + else { + bs->ltgtype = LTG_TEAMACCOMPANY; + if (!bs->teamgoal_time) bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; + bs->formation_dist = 3.5 * 32; //3.5 meter + bs->arrive_time = 0; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); + } +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_DefendKeyArea +================== +*/ +void BotMatch_DefendKeyArea(bot_state_t *bs, bot_match_t *match) { + char itemname[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + //get the match variable + trap_BotMatchVariable(match, KEYAREA, itemname, sizeof(itemname)); + // + if (!BotGetMessageTeamGoal(bs, itemname, &bs->teamgoal)) { + //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL); + //trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + return; + } + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + // + client = ClientFromName(netname); + //the team mate who ordered + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_DEFENDKEYAREA; + //get the team goal time + bs->teamgoal_time = BotGetTime(match); + //set the team goal time + if (!bs->teamgoal_time) bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; + //away from defending + bs->defendaway_time = 0; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_GetItem +================== +*/ +void BotMatch_GetItem(bot_state_t *bs, bot_match_t *match) { + char itemname[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + //get the match variable + trap_BotMatchVariable(match, ITEM, itemname, sizeof(itemname)); + // + if (!BotGetMessageTeamGoal(bs, itemname, &bs->teamgoal)) { + //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL); + //trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + return; + } + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientOnSameTeamFromName(bs, netname); + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_GETITEM; + //set the team goal time + bs->teamgoal_time = FloatTime() + TEAM_GETITEM_TIME; + // + BotSetTeamStatus(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_Camp +================== +*/ +void BotMatch_Camp(bot_state_t *bs, bot_match_t *match) { + int client, areanum; + char netname[MAX_MESSAGE_SIZE]; + char itemname[MAX_MESSAGE_SIZE]; + aas_entityinfo_t entinfo; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + //asked for someone else + client = FindClientByName(netname); + //if there's no valid client with this name + if (client < 0) { + BotAI_BotInitialChat(bs, "whois", netname, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + return; + } + //get the match variable + trap_BotMatchVariable(match, KEYAREA, itemname, sizeof(itemname)); + //in CTF it could be the base + if (match->subtype & ST_THERE) { + //camp at the spot the bot is currently standing + bs->teamgoal.entitynum = bs->entitynum; + bs->teamgoal.areanum = bs->areanum; + VectorCopy(bs->origin, bs->teamgoal.origin); + VectorSet(bs->teamgoal.mins, -8, -8, -8); + VectorSet(bs->teamgoal.maxs, 8, 8, 8); + } + else if (match->subtype & ST_HERE) { + //if this is the bot self + if (client == bs->client) return; + // + bs->teamgoal.entitynum = -1; + BotEntityInfo(client, &entinfo); + //if info is valid (in PVS) + if (entinfo.valid) { + areanum = BotPointAreaNum(entinfo.origin); + if (areanum) {// && trap_AAS_AreaReachability(areanum)) { + //NOTE: just assume the bot knows where the person is + //if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, client)) { + bs->teamgoal.entitynum = client; + bs->teamgoal.areanum = areanum; + VectorCopy(entinfo.origin, bs->teamgoal.origin); + VectorSet(bs->teamgoal.mins, -8, -8, -8); + VectorSet(bs->teamgoal.maxs, 8, 8, 8); + //} + } + } + //if the other is not visible + if (bs->teamgoal.entitynum < 0) { + BotAI_BotInitialChat(bs, "whereareyou", netname, NULL); + client = ClientFromName(netname); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); + return; + } + } + else if (!BotGetMessageTeamGoal(bs, itemname, &bs->teamgoal)) { + //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL); + //client = ClientFromName(netname); + //trap_BotEnterChat(bs->cs, client, CHAT_TELL); + return; + } + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_CAMPORDER; + //get the team goal time + bs->teamgoal_time = BotGetTime(match); + //set the team goal time + if (!bs->teamgoal_time) bs->teamgoal_time = FloatTime() + TEAM_CAMP_TIME; + //not arrived yet + bs->arrive_time = 0; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_Patrol +================== +*/ +void BotMatch_Patrol(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + //get the patrol waypoints + if (!BotGetPatrolWaypoints(bs, match)) return; + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + // + client = FindClientByName(netname); + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_PATROL; + //get the team goal time + bs->teamgoal_time = BotGetTime(match); + //set the team goal time if not set already + if (!bs->teamgoal_time) bs->teamgoal_time = FloatTime() + TEAM_PATROL_TIME; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_GetFlag +================== +*/ +void BotMatch_GetFlag(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (gametype == GT_CTF) { + if (!ctf_redflag.areanum || !ctf_blueflag.areanum) + return; + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + if (!ctf_neutralflag.areanum || !ctf_redflag.areanum || !ctf_blueflag.areanum) + return; + } +#endif + else { + return; + } + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + // + client = FindClientByName(netname); + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_GETFLAG; + //set the team goal time + bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME; + // get an alternate route in ctf + if (gametype == GT_CTF) { + //get an alternative route goal towards the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + } + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_AttackEnemyBase +================== +*/ +void BotMatch_AttackEnemyBase(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (gametype == GT_CTF) { + BotMatch_GetFlag(bs, match); + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF || gametype == GT_OBELISK || gametype == GT_HARVESTER) { + if (!redobelisk.areanum || !blueobelisk.areanum) + return; + } +#endif + else { + return; + } + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + // + client = FindClientByName(netname); + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_ATTACKENEMYBASE; + //set the team goal time + bs->teamgoal_time = FloatTime() + TEAM_ATTACKENEMYBASE_TIME; + bs->attackaway_time = 0; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +#ifdef MISSIONPACK +/* +================== +BotMatch_Harvest +================== +*/ +void BotMatch_Harvest(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (gametype == GT_HARVESTER) { + if (!neutralobelisk.areanum || !redobelisk.areanum || !blueobelisk.areanum) + return; + } + else { + return; + } + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + // + client = FindClientByName(netname); + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_HARVEST; + //set the team goal time + bs->teamgoal_time = FloatTime() + TEAM_HARVEST_TIME; + bs->harvestaway_time = 0; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} +#endif + +/* +================== +BotMatch_RushBase +================== +*/ +void BotMatch_RushBase(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (gametype == GT_CTF) { + if (!ctf_redflag.areanum || !ctf_blueflag.areanum) + return; + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF || gametype == GT_HARVESTER) { + if (!redobelisk.areanum || !blueobelisk.areanum) + return; + } +#endif + else { + return; + } + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + // + client = FindClientByName(netname); + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_RUSHBASE; + //set the team goal time + bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; + bs->rushbaseaway_time = 0; + // + BotSetTeamStatus(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_TaskPreference +================== +*/ +void BotMatch_TaskPreference(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_NETNAME]; + char teammatename[MAX_MESSAGE_SIZE]; + int teammate, preference; + + ClientName(bs->client, netname, sizeof(netname)); + if (Q_stricmp(netname, bs->teamleader) != 0) return; + + trap_BotMatchVariable(match, NETNAME, teammatename, sizeof(teammatename)); + teammate = ClientFromName(teammatename); + if (teammate < 0) return; + + preference = BotGetTeamMateTaskPreference(bs, teammate); + switch(match->subtype) + { + case ST_DEFENDER: + { + preference &= ~TEAMTP_ATTACKER; + preference |= TEAMTP_DEFENDER; + break; + } + case ST_ATTACKER: + { + preference &= ~TEAMTP_DEFENDER; + preference |= TEAMTP_ATTACKER; + break; + } + case ST_ROAMER: + { + preference &= ~(TEAMTP_ATTACKER|TEAMTP_DEFENDER); + break; + } + } + BotSetTeamMateTaskPreference(bs, teammate, preference); + // + EasyClientName(teammate, teammatename, sizeof(teammatename)); + BotAI_BotInitialChat(bs, "keepinmind", teammatename, NULL); + trap_BotEnterChat(bs->cs, teammate, CHAT_TELL); + BotVoiceChatOnly(bs, teammate, VOICECHAT_YES); + trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); +} + +/* +================== +BotMatch_ReturnFlag +================== +*/ +void BotMatch_ReturnFlag(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + int client; + + //if not in CTF mode + if ( + gametype != GT_CTF +#ifdef MISSIONPACK + && gametype != GT_1FCTF +#endif + ) + return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) + return; + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + // + client = FindClientByName(netname); + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_RETURNFLAG; + //set the team goal time + bs->teamgoal_time = FloatTime() + CTF_RETURNFLAG_TIME; + bs->rushbaseaway_time = 0; + // + BotSetTeamStatus(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_JoinSubteam +================== +*/ +void BotMatch_JoinSubteam(bot_state_t *bs, bot_match_t *match) { + char teammate[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + //get the sub team name + trap_BotMatchVariable(match, TEAMNAME, teammate, sizeof(teammate)); + //set the sub team name + strncpy(bs->subteam, teammate, 32); + bs->subteam[31] = '\0'; + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + BotAI_BotInitialChat(bs, "joinedteam", teammate, NULL); + client = ClientFromName(netname); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); +} + +/* +================== +BotMatch_LeaveSubteam +================== +*/ +void BotMatch_LeaveSubteam(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + if (strlen(bs->subteam)) + { + BotAI_BotInitialChat(bs, "leftteam", bs->subteam, NULL); + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientFromName(netname); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); + } //end if + strcpy(bs->subteam, ""); +} + +/* +================== +BotMatch_LeaveSubteam +================== +*/ +void BotMatch_WhichTeam(bot_state_t *bs, bot_match_t *match) { + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + if (strlen(bs->subteam)) { + BotAI_BotInitialChat(bs, "inteam", bs->subteam, NULL); + } + else { + BotAI_BotInitialChat(bs, "noteam", NULL); + } + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); +} + +/* +================== +BotMatch_CheckPoint +================== +*/ +void BotMatch_CheckPoint(bot_state_t *bs, bot_match_t *match) { + int areanum, client; + char buf[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + vec3_t position; + bot_waypoint_t *cp; + + if (!TeamPlayIsOn()) return; + // + trap_BotMatchVariable(match, POSITION, buf, MAX_MESSAGE_SIZE); + VectorClear(position); + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientFromName(netname); + //BotGPSToPosition(buf, position); + sscanf(buf, "%f %f %f", &position[0], &position[1], &position[2]); + position[2] += 0.5; + areanum = BotPointAreaNum(position); + if (!areanum) { + if (BotAddressedToBot(bs, match)) { + BotAI_BotInitialChat(bs, "checkpoint_invalid", NULL); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); + } + return; + } + // + trap_BotMatchVariable(match, NAME, buf, MAX_MESSAGE_SIZE); + //check if there already exists a checkpoint with this name + cp = BotFindWayPoint(bs->checkpoints, buf); + if (cp) { + if (cp->next) cp->next->prev = cp->prev; + if (cp->prev) cp->prev->next = cp->next; + else bs->checkpoints = cp->next; + cp->inuse = qfalse; + } + //create a new check point + cp = BotCreateWayPoint(buf, position, areanum); + //add the check point to the bot's known chech points + cp->next = bs->checkpoints; + if (bs->checkpoints) bs->checkpoints->prev = cp; + bs->checkpoints = cp; + // + if (BotAddressedToBot(bs, match)) { + Com_sprintf(buf, sizeof(buf), "%1.0f %1.0f %1.0f", cp->goal.origin[0], + cp->goal.origin[1], + cp->goal.origin[2]); + + BotAI_BotInitialChat(bs, "checkpoint_confirm", cp->name, buf, NULL); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); + } +} + +/* +================== +BotMatch_FormationSpace +================== +*/ +void BotMatch_FormationSpace(bot_state_t *bs, bot_match_t *match) { + char buf[MAX_MESSAGE_SIZE]; + float space; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + trap_BotMatchVariable(match, NUMBER, buf, MAX_MESSAGE_SIZE); + //if it's the distance in feet + if (match->subtype & ST_FEET) space = 0.3048 * 32 * atof(buf); + //else it's in meters + else space = 32 * atof(buf); + //check if the formation intervening space is valid + if (space < 48 || space > 500) space = 100; + bs->formation_dist = space; +} + +/* +================== +BotMatch_Dismiss +================== +*/ +void BotMatch_Dismiss(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientFromName(netname); + // + bs->decisionmaker = client; + // + bs->ltgtype = 0; + bs->lead_time = 0; + bs->lastgoal_ltgtype = 0; + // + BotAI_BotInitialChat(bs, "dismissed", NULL); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); +} + +/* +================== +BotMatch_Suicide +================== +*/ +void BotMatch_Suicide(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + trap_EA_Command(bs->client, "kill"); + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientFromName(netname); + // + BotVoiceChat(bs, client, VOICECHAT_TAUNT); + trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); +} + +/* +================== +BotMatch_StartTeamLeaderShip +================== +*/ +void BotMatch_StartTeamLeaderShip(bot_state_t *bs, bot_match_t *match) { + int client; + char teammate[MAX_MESSAGE_SIZE]; + + if (!TeamPlayIsOn()) return; + //if chats for him or herself + if (match->subtype & ST_I) { + //get the team mate that will be the team leader + trap_BotMatchVariable(match, NETNAME, teammate, sizeof(teammate)); + strncpy(bs->teamleader, teammate, sizeof(bs->teamleader)); + bs->teamleader[sizeof(bs->teamleader)] = '\0'; + } + //chats for someone else + else { + //get the team mate that will be the team leader + trap_BotMatchVariable(match, TEAMMATE, teammate, sizeof(teammate)); + client = FindClientByName(teammate); + if (client >= 0) ClientName(client, bs->teamleader, sizeof(bs->teamleader)); + } +} + +/* +================== +BotMatch_StopTeamLeaderShip +================== +*/ +void BotMatch_StopTeamLeaderShip(bot_state_t *bs, bot_match_t *match) { + int client; + char teammate[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + + if (!TeamPlayIsOn()) return; + //get the team mate that stops being the team leader + trap_BotMatchVariable(match, TEAMMATE, teammate, sizeof(teammate)); + //if chats for him or herself + if (match->subtype & ST_I) { + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = FindClientByName(netname); + } + //chats for someone else + else { + client = FindClientByName(teammate); + } //end else + if (client >= 0) { + if (!Q_stricmp(bs->teamleader, ClientName(client, netname, sizeof(netname)))) { + bs->teamleader[0] = '\0'; + notleader[client] = qtrue; + } + } +} + +/* +================== +BotMatch_WhoIsTeamLeader +================== +*/ +void BotMatch_WhoIsTeamLeader(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + + if (!TeamPlayIsOn()) return; + + ClientName(bs->client, netname, sizeof(netname)); + //if this bot IS the team leader + if (!Q_stricmp(netname, bs->teamleader)) { + trap_EA_SayTeam(bs->client, "I'm the team leader\n"); + } +} + +/* +================== +BotMatch_WhatAreYouDoing +================== +*/ +void BotMatch_WhatAreYouDoing(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + char goalname[MAX_MESSAGE_SIZE]; + int client; + + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + switch(bs->ltgtype) { + case LTG_TEAMHELP: + { + EasyClientName(bs->teammate, netname, sizeof(netname)); + BotAI_BotInitialChat(bs, "helping", netname, NULL); + break; + } + case LTG_TEAMACCOMPANY: + { + EasyClientName(bs->teammate, netname, sizeof(netname)); + BotAI_BotInitialChat(bs, "accompanying", netname, NULL); + break; + } + case LTG_DEFENDKEYAREA: + { + trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); + BotAI_BotInitialChat(bs, "defending", goalname, NULL); + break; + } + case LTG_GETITEM: + { + trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); + BotAI_BotInitialChat(bs, "gettingitem", goalname, NULL); + break; + } + case LTG_KILL: + { + ClientName(bs->teamgoal.entitynum, netname, sizeof(netname)); + BotAI_BotInitialChat(bs, "killing", netname, NULL); + break; + } + case LTG_CAMP: + case LTG_CAMPORDER: + { + BotAI_BotInitialChat(bs, "camping", NULL); + break; + } + case LTG_PATROL: + { + BotAI_BotInitialChat(bs, "patrolling", NULL); + break; + } + case LTG_GETFLAG: + { + BotAI_BotInitialChat(bs, "capturingflag", NULL); + break; + } + case LTG_RUSHBASE: + { + BotAI_BotInitialChat(bs, "rushingbase", NULL); + break; + } + case LTG_RETURNFLAG: + { + BotAI_BotInitialChat(bs, "returningflag", NULL); + break; + } +#ifdef MISSIONPACK + case LTG_ATTACKENEMYBASE: + { + BotAI_BotInitialChat(bs, "attackingenemybase", NULL); + break; + } + case LTG_HARVEST: + { + BotAI_BotInitialChat(bs, "harvesting", NULL); + break; + } +#endif + default: + { + BotAI_BotInitialChat(bs, "roaming", NULL); + break; + } + } + //chat what the bot is doing + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientFromName(netname); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); +} + +/* +================== +BotMatch_WhatIsMyCommand +================== +*/ +void BotMatch_WhatIsMyCommand(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_NETNAME]; + + ClientName(bs->client, netname, sizeof(netname)); + if (Q_stricmp(netname, bs->teamleader) != 0) return; + bs->forceorders = qtrue; +} + +/* +================== +BotNearestVisibleItem +================== +*/ +float BotNearestVisibleItem(bot_state_t *bs, char *itemname, bot_goal_t *goal) { + int i; + char name[64]; + bot_goal_t tmpgoal; + float dist, bestdist; + vec3_t dir; + bsp_trace_t trace; + + bestdist = 999999; + i = -1; + do { + i = trap_BotGetLevelItemGoal(i, itemname, &tmpgoal); + trap_BotGoalName(tmpgoal.number, name, sizeof(name)); + if (Q_stricmp(itemname, name) != 0) + continue; + VectorSubtract(tmpgoal.origin, bs->origin, dir); + dist = VectorLength(dir); + if (dist < bestdist) { + //trace from start to end + BotAI_Trace(&trace, bs->eye, NULL, NULL, tmpgoal.origin, bs->client, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); + if (trace.fraction >= 1.0) { + bestdist = dist; + memcpy(goal, &tmpgoal, sizeof(bot_goal_t)); + } + } + } while(i > 0); + return bestdist; +} + +/* +================== +BotMatch_WhereAreYou +================== +*/ +void BotMatch_WhereAreYou(bot_state_t *bs, bot_match_t *match) { + float dist, bestdist; + int i, bestitem, redtt, bluett, client; + bot_goal_t goal; + char netname[MAX_MESSAGE_SIZE]; + char *nearbyitems[] = { + "Shotgun", + "Grenade Launcher", + "Rocket Launcher", + "Plasmagun", + "Railgun", + "Lightning Gun", + "BFG10K", + "Quad Damage", + "Regeneration", + "Battle Suit", + "Speed", + "Invisibility", + "Flight", + "Armor", + "Heavy Armor", + "Red Flag", + "Blue Flag", +#ifdef MISSIONPACK + "Nailgun", + "Prox Launcher", + "Chaingun", + "Scout", + "Guard", + "Doubler", + "Ammo Regen", + "Neutral Flag", + "Red Obelisk", + "Blue Obelisk", + "Neutral Obelisk", +#endif + NULL + }; + // + if (!TeamPlayIsOn()) + return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) + return; + + bestitem = -1; + bestdist = 999999; + for (i = 0; nearbyitems[i]; i++) { + dist = BotNearestVisibleItem(bs, nearbyitems[i], &goal); + if (dist < bestdist) { + bestdist = dist; + bestitem = i; + } + } + if (bestitem != -1) { + if (gametype == GT_CTF +#ifdef MISSIONPACK + || gametype == GT_1FCTF +#endif + ) { + redtt = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, ctf_redflag.areanum, TFL_DEFAULT); + bluett = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, ctf_blueflag.areanum, TFL_DEFAULT); + if (redtt < (redtt + bluett) * 0.4) { + BotAI_BotInitialChat(bs, "teamlocation", nearbyitems[bestitem], "red", NULL); + } + else if (bluett < (redtt + bluett) * 0.4) { + BotAI_BotInitialChat(bs, "teamlocation", nearbyitems[bestitem], "blue", NULL); + } + else { + BotAI_BotInitialChat(bs, "location", nearbyitems[bestitem], NULL); + } + } +#ifdef MISSIONPACK + else if (gametype == GT_OBELISK || gametype == GT_HARVESTER) { + redtt = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, redobelisk.areanum, TFL_DEFAULT); + bluett = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, blueobelisk.areanum, TFL_DEFAULT); + if (redtt < (redtt + bluett) * 0.4) { + BotAI_BotInitialChat(bs, "teamlocation", nearbyitems[bestitem], "red", NULL); + } + else if (bluett < (redtt + bluett) * 0.4) { + BotAI_BotInitialChat(bs, "teamlocation", nearbyitems[bestitem], "blue", NULL); + } + else { + BotAI_BotInitialChat(bs, "location", nearbyitems[bestitem], NULL); + } + } +#endif + else { + BotAI_BotInitialChat(bs, "location", nearbyitems[bestitem], NULL); + } + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientFromName(netname); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); + } +} + +/* +================== +BotMatch_LeadTheWay +================== +*/ +void BotMatch_LeadTheWay(bot_state_t *bs, bot_match_t *match) { + aas_entityinfo_t entinfo; + char netname[MAX_MESSAGE_SIZE], teammate[MAX_MESSAGE_SIZE]; + int client, areanum, other; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + //if someone asks for someone else + if (match->subtype & ST_SOMEONE) { + //get the team mate name + trap_BotMatchVariable(match, TEAMMATE, teammate, sizeof(teammate)); + client = FindClientByName(teammate); + //if this is the bot self + if (client == bs->client) { + other = qfalse; + } + else if (!BotSameTeam(bs, client)) { + //FIXME: say "I don't help the enemy" + return; + } + else { + other = qtrue; + } + } + else { + //get the netname + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientFromName(netname); + other = qfalse; + } + //if the bot doesn't know who to help (FindClientByName returned -1) + if (client < 0) { + BotAI_BotInitialChat(bs, "whois", netname, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + return; + } + // + bs->lead_teamgoal.entitynum = -1; + BotEntityInfo(client, &entinfo); + //if info is valid (in PVS) + if (entinfo.valid) { + areanum = BotPointAreaNum(entinfo.origin); + if (areanum) { // && trap_AAS_AreaReachability(areanum)) { + bs->lead_teamgoal.entitynum = client; + bs->lead_teamgoal.areanum = areanum; + VectorCopy(entinfo.origin, bs->lead_teamgoal.origin); + VectorSet(bs->lead_teamgoal.mins, -8, -8, -8); + VectorSet(bs->lead_teamgoal.maxs, 8, 8, 8); + } + } + + if (bs->teamgoal.entitynum < 0) { + if (other) BotAI_BotInitialChat(bs, "whereis", teammate, NULL); + else BotAI_BotInitialChat(bs, "whereareyou", netname, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + return; + } + bs->lead_teammate = client; + bs->lead_time = FloatTime() + TEAM_LEAD_TIME; + bs->leadvisible_time = 0; + bs->leadmessage_time = -(FloatTime() + 2 * random()); +} + +/* +================== +BotMatch_Kill +================== +*/ +void BotMatch_Kill(bot_state_t *bs, bot_match_t *match) { + char enemy[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + + trap_BotMatchVariable(match, ENEMY, enemy, sizeof(enemy)); + // + client = FindEnemyByName(bs, enemy); + if (client < 0) { + BotAI_BotInitialChat(bs, "whois", enemy, NULL); + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientFromName(netname); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); + return; + } + bs->teamgoal.entitynum = client; + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_KILL; + //set the team goal time + bs->teamgoal_time = FloatTime() + TEAM_KILL_SOMEONE; + // + BotSetTeamStatus(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_CTF +================== +*/ +void BotMatch_CTF(bot_state_t *bs, bot_match_t *match) { + + char flag[128], netname[MAX_NETNAME]; + + if (gametype == GT_CTF) { + trap_BotMatchVariable(match, FLAG, flag, sizeof(flag)); + if (match->subtype & ST_GOTFLAG) { + if (!Q_stricmp(flag, "red")) { + bs->redflagstatus = 1; + if (BotTeam(bs) == TEAM_BLUE) { + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + bs->flagcarrier = ClientFromName(netname); + } + } + else { + bs->blueflagstatus = 1; + if (BotTeam(bs) == TEAM_RED) { + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + bs->flagcarrier = ClientFromName(netname); + } + } + bs->flagstatuschanged = 1; + bs->lastflagcapture_time = FloatTime(); + } + else if (match->subtype & ST_CAPTUREDFLAG) { + bs->redflagstatus = 0; + bs->blueflagstatus = 0; + bs->flagcarrier = 0; + bs->flagstatuschanged = 1; + } + else if (match->subtype & ST_RETURNEDFLAG) { + if (!Q_stricmp(flag, "red")) bs->redflagstatus = 0; + else bs->blueflagstatus = 0; + bs->flagstatuschanged = 1; + } + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + if (match->subtype & ST_1FCTFGOTFLAG) { + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + bs->flagcarrier = ClientFromName(netname); + } + } +#endif +} + +void BotMatch_EnterGame(bot_state_t *bs, bot_match_t *match) { + int client; + char netname[MAX_NETNAME]; + + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = FindClientByName(netname); + if (client >= 0) { + notleader[client] = qfalse; + } + //NOTE: eliza chats will catch this + //Com_sprintf(buf, sizeof(buf), "heya %s", netname); + //EA_Say(bs->client, buf); +} + +void BotMatch_NewLeader(bot_state_t *bs, bot_match_t *match) { + int client; + char netname[MAX_NETNAME]; + + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = FindClientByName(netname); + if (!BotSameTeam(bs, client)) + return; + Q_strncpyz(bs->teamleader, netname, sizeof(bs->teamleader)); +} + +/* +================== +BotMatchMessage +================== +*/ +int BotMatchMessage(bot_state_t *bs, char *message) { + bot_match_t match; + + match.type = 0; + //if it is an unknown message + if (!trap_BotFindMatch(message, &match, MTCONTEXT_MISC + |MTCONTEXT_INITIALTEAMCHAT + |MTCONTEXT_CTF)) { + return qfalse; + } + //react to the found message + switch(match.type) + { + case MSG_HELP: //someone calling for help + case MSG_ACCOMPANY: //someone calling for company + { + BotMatch_HelpAccompany(bs, &match); + break; + } + case MSG_DEFENDKEYAREA: //teamplay defend a key area + { + BotMatch_DefendKeyArea(bs, &match); + break; + } + case MSG_CAMP: //camp somewhere + { + BotMatch_Camp(bs, &match); + break; + } + case MSG_PATROL: //patrol between several key areas + { + BotMatch_Patrol(bs, &match); + break; + } + //CTF & 1FCTF + case MSG_GETFLAG: //ctf get the enemy flag + { + BotMatch_GetFlag(bs, &match); + break; + } +#ifdef MISSIONPACK + //CTF & 1FCTF & Obelisk & Harvester + case MSG_ATTACKENEMYBASE: + { + BotMatch_AttackEnemyBase(bs, &match); + break; + } + //Harvester + case MSG_HARVEST: + { + BotMatch_Harvest(bs, &match); + break; + } +#endif + //CTF & 1FCTF & Harvester + case MSG_RUSHBASE: //ctf rush to the base + { + BotMatch_RushBase(bs, &match); + break; + } + //CTF & 1FCTF + case MSG_RETURNFLAG: + { + BotMatch_ReturnFlag(bs, &match); + break; + } + //CTF & 1FCTF & Obelisk & Harvester + case MSG_TASKPREFERENCE: + { + BotMatch_TaskPreference(bs, &match); + break; + } + //CTF & 1FCTF + case MSG_CTF: + { + BotMatch_CTF(bs, &match); + break; + } + case MSG_GETITEM: + { + BotMatch_GetItem(bs, &match); + break; + } + case MSG_JOINSUBTEAM: //join a sub team + { + BotMatch_JoinSubteam(bs, &match); + break; + } + case MSG_LEAVESUBTEAM: //leave a sub team + { + BotMatch_LeaveSubteam(bs, &match); + break; + } + case MSG_WHICHTEAM: + { + BotMatch_WhichTeam(bs, &match); + break; + } + case MSG_CHECKPOINT: //remember a check point + { + BotMatch_CheckPoint(bs, &match); + break; + } + case MSG_CREATENEWFORMATION: //start the creation of a new formation + { + trap_EA_SayTeam(bs->client, "the part of my brain to create formations has been damaged"); + break; + } + case MSG_FORMATIONPOSITION: //tell someone his/her position in the formation + { + trap_EA_SayTeam(bs->client, "the part of my brain to create formations has been damaged"); + break; + } + case MSG_FORMATIONSPACE: //set the formation space + { + BotMatch_FormationSpace(bs, &match); + break; + } + case MSG_DOFORMATION: //form a certain formation + { + break; + } + case MSG_DISMISS: //dismiss someone + { + BotMatch_Dismiss(bs, &match); + break; + } + case MSG_STARTTEAMLEADERSHIP: //someone will become the team leader + { + BotMatch_StartTeamLeaderShip(bs, &match); + break; + } + case MSG_STOPTEAMLEADERSHIP: //someone will stop being the team leader + { + BotMatch_StopTeamLeaderShip(bs, &match); + break; + } + case MSG_WHOISTEAMLAEDER: + { + BotMatch_WhoIsTeamLeader(bs, &match); + break; + } + case MSG_WHATAREYOUDOING: //ask a bot what he/she is doing + { + BotMatch_WhatAreYouDoing(bs, &match); + break; + } + case MSG_WHATISMYCOMMAND: + { + BotMatch_WhatIsMyCommand(bs, &match); + break; + } + case MSG_WHEREAREYOU: + { + BotMatch_WhereAreYou(bs, &match); + break; + } + case MSG_LEADTHEWAY: + { + BotMatch_LeadTheWay(bs, &match); + break; + } + case MSG_KILL: + { + BotMatch_Kill(bs, &match); + break; + } + case MSG_ENTERGAME: //someone entered the game + { + BotMatch_EnterGame(bs, &match); + break; + } + case MSG_NEWLEADER: + { + BotMatch_NewLeader(bs, &match); + break; + } + case MSG_WAIT: + { + break; + } + case MSG_SUICIDE: + { + BotMatch_Suicide(bs, &match); + break; + } + default: + { + BotAI_Print(PRT_MESSAGE, "unknown match type\n"); + break; + } + } + return qtrue; +} diff --git a/code/game/ai_cmd.h b/code/game/ai_cmd.h index 511c57a..f5447f7 100755 --- a/code/game/ai_cmd.h +++ b/code/game/ai_cmd.h @@ -1,37 +1,37 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -/***************************************************************************** - * name: ai_cmd.h - * - * desc: Quake3 bot AI - * - * $Archive: /source/code/botai/ai_chat.c $ - * - *****************************************************************************/ - -extern int notleader[MAX_CLIENTS]; - -int BotMatchMessage(bot_state_t *bs, char *message); -void BotPrintTeamGoal(bot_state_t *bs); - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: ai_cmd.h + * + * desc: Quake3 bot AI + * + * $Archive: /source/code/botai/ai_chat.c $ + * + *****************************************************************************/ + +extern int notleader[MAX_CLIENTS]; + +int BotMatchMessage(bot_state_t *bs, char *message); +void BotPrintTeamGoal(bot_state_t *bs); + diff --git a/code/game/ai_dmnet.c b/code/game/ai_dmnet.c index 3bf3a8a..a791edd 100755 --- a/code/game/ai_dmnet.c +++ b/code/game/ai_dmnet.c @@ -1,2610 +1,2610 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -/***************************************************************************** - * name: ai_dmnet.c - * - * desc: Quake3 bot AI - * - * $Archive: /MissionPack/code/game/ai_dmnet.c $ - * - *****************************************************************************/ - -#include "g_local.h" -#include "botlib.h" -#include "be_aas.h" -#include "be_ea.h" -#include "be_ai_char.h" -#include "be_ai_chat.h" -#include "be_ai_gen.h" -#include "be_ai_goal.h" -#include "be_ai_move.h" -#include "be_ai_weap.h" -// -#include "ai_main.h" -#include "ai_dmq3.h" -#include "ai_chat.h" -#include "ai_cmd.h" -#include "ai_dmnet.h" -#include "ai_team.h" -//data file headers -#include "chars.h" //characteristics -#include "inv.h" //indexes into the inventory -#include "syn.h" //synonyms -#include "match.h" //string matching types and vars - -// for the voice chats -#include "../../ui/menudef.h" - -//goal flag, see be_ai_goal.h for the other GFL_* -#define GFL_AIR 128 - -int numnodeswitches; -char nodeswitch[MAX_NODESWITCHES+1][144]; - -#define LOOKAHEAD_DISTANCE 300 - -/* -================== -BotResetNodeSwitches -================== -*/ -void BotResetNodeSwitches(void) { - numnodeswitches = 0; -} - -/* -================== -BotDumpNodeSwitches -================== -*/ -void BotDumpNodeSwitches(bot_state_t *bs) { - int i; - char netname[MAX_NETNAME]; - - ClientName(bs->client, netname, sizeof(netname)); - BotAI_Print(PRT_MESSAGE, "%s at %1.1f switched more than %d AI nodes\n", netname, FloatTime(), MAX_NODESWITCHES); - for (i = 0; i < numnodeswitches; i++) { - BotAI_Print(PRT_MESSAGE, nodeswitch[i]); - } - BotAI_Print(PRT_FATAL, ""); -} - -/* -================== -BotRecordNodeSwitch -================== -*/ -void BotRecordNodeSwitch(bot_state_t *bs, char *node, char *str, char *s) { - char netname[MAX_NETNAME]; - - ClientName(bs->client, netname, sizeof(netname)); - Com_sprintf(nodeswitch[numnodeswitches], 144, "%s at %2.1f entered %s: %s from %s\n", netname, FloatTime(), node, str, s); -#ifdef DEBUG - if (0) { - BotAI_Print(PRT_MESSAGE, nodeswitch[numnodeswitches]); - } -#endif //DEBUG - numnodeswitches++; -} - -/* -================== -BotGetAirGoal -================== -*/ -int BotGetAirGoal(bot_state_t *bs, bot_goal_t *goal) { - bsp_trace_t bsptrace; - vec3_t end, mins = {-15, -15, -2}, maxs = {15, 15, 2}; - int areanum; - - //trace up until we hit solid - VectorCopy(bs->origin, end); - end[2] += 1000; - BotAI_Trace(&bsptrace, bs->origin, mins, maxs, end, bs->entitynum, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); - //trace down until we hit water - VectorCopy(bsptrace.endpos, end); - BotAI_Trace(&bsptrace, end, mins, maxs, bs->origin, bs->entitynum, CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA); - //if we found the water surface - if (bsptrace.fraction > 0) { - areanum = BotPointAreaNum(bsptrace.endpos); - if (areanum) { - VectorCopy(bsptrace.endpos, goal->origin); - goal->origin[2] -= 2; - goal->areanum = areanum; - goal->mins[0] = -15; - goal->mins[1] = -15; - goal->mins[2] = -1; - goal->maxs[0] = 15; - goal->maxs[1] = 15; - goal->maxs[2] = 1; - goal->flags = GFL_AIR; - goal->number = 0; - goal->iteminfo = 0; - goal->entitynum = 0; - return qtrue; - } - } - return qfalse; -} - -/* -================== -BotGoForAir -================== -*/ -int BotGoForAir(bot_state_t *bs, int tfl, bot_goal_t *ltg, float range) { - bot_goal_t goal; - - //if the bot needs air - if (bs->lastair_time < FloatTime() - 6) { - // -#ifdef DEBUG - //BotAI_Print(PRT_MESSAGE, "going for air\n"); -#endif //DEBUG - //if we can find an air goal - if (BotGetAirGoal(bs, &goal)) { - trap_BotPushGoal(bs->gs, &goal); - return qtrue; - } - else { - //get a nearby goal outside the water - while(trap_BotChooseNBGItem(bs->gs, bs->origin, bs->inventory, tfl, ltg, range)) { - trap_BotGetTopGoal(bs->gs, &goal); - //if the goal is not in water - if (!(trap_AAS_PointContents(goal.origin) & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA))) { - return qtrue; - } - trap_BotPopGoal(bs->gs); - } - trap_BotResetAvoidGoals(bs->gs); - } - } - return qfalse; -} - -/* -================== -BotNearbyGoal -================== -*/ -int BotNearbyGoal(bot_state_t *bs, int tfl, bot_goal_t *ltg, float range) { - int ret; - - //check if the bot should go for air - if (BotGoForAir(bs, tfl, ltg, range)) return qtrue; - //if the bot is carrying the enemy flag - if (BotCTFCarryingFlag(bs)) { - //if the bot is just a few secs away from the base - if (trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, - bs->teamgoal.areanum, TFL_DEFAULT) < 300) { - //make the range really small - range = 50; - } - } - // - ret = trap_BotChooseNBGItem(bs->gs, bs->origin, bs->inventory, tfl, ltg, range); - /* - if (ret) - { - char buf[128]; - //get the goal at the top of the stack - trap_BotGetTopGoal(bs->gs, &goal); - trap_BotGoalName(goal.number, buf, sizeof(buf)); - BotAI_Print(PRT_MESSAGE, "%1.1f: new nearby goal %s\n", FloatTime(), buf); - } - */ - return ret; -} - -/* -================== -BotReachedGoal -================== -*/ -int BotReachedGoal(bot_state_t *bs, bot_goal_t *goal) { - if (goal->flags & GFL_ITEM) { - //if touching the goal - if (trap_BotTouchingGoal(bs->origin, goal)) { - if (!(goal->flags & GFL_DROPPED)) { - trap_BotSetAvoidGoalTime(bs->gs, goal->number, -1); - } - return qtrue; - } - //if the goal isn't there - if (trap_BotItemGoalInVisButNotVisible(bs->entitynum, bs->eye, bs->viewangles, goal)) { - /* - float avoidtime; - int t; - - avoidtime = trap_BotAvoidGoalTime(bs->gs, goal->number); - if (avoidtime > 0) { - t = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, goal->areanum, bs->tfl); - if ((float) t * 0.009 < avoidtime) - return qtrue; - } - */ - return qtrue; - } - //if in the goal area and below or above the goal and not swimming - if (bs->areanum == goal->areanum) { - if (bs->origin[0] > goal->origin[0] + goal->mins[0] && bs->origin[0] < goal->origin[0] + goal->maxs[0]) { - if (bs->origin[1] > goal->origin[1] + goal->mins[1] && bs->origin[1] < goal->origin[1] + goal->maxs[1]) { - if (!trap_AAS_Swimming(bs->origin)) { - return qtrue; - } - } - } - } - } - else if (goal->flags & GFL_AIR) { - //if touching the goal - if (trap_BotTouchingGoal(bs->origin, goal)) return qtrue; - //if the bot got air - if (bs->lastair_time > FloatTime() - 1) return qtrue; - } - else { - //if touching the goal - if (trap_BotTouchingGoal(bs->origin, goal)) return qtrue; - } - return qfalse; -} - -/* -================== -BotGetItemLongTermGoal -================== -*/ -int BotGetItemLongTermGoal(bot_state_t *bs, int tfl, bot_goal_t *goal) { - //if the bot has no goal - if (!trap_BotGetTopGoal(bs->gs, goal)) { - //BotAI_Print(PRT_MESSAGE, "no ltg on stack\n"); - bs->ltg_time = 0; - } - //if the bot touches the current goal - else if (BotReachedGoal(bs, goal)) { - BotChooseWeapon(bs); - bs->ltg_time = 0; - } - //if it is time to find a new long term goal - if (bs->ltg_time < FloatTime()) { - //pop the current goal from the stack - trap_BotPopGoal(bs->gs); - //BotAI_Print(PRT_MESSAGE, "%s: choosing new ltg\n", ClientName(bs->client, netname, sizeof(netname))); - //choose a new goal - //BotAI_Print(PRT_MESSAGE, "%6.1f client %d: BotChooseLTGItem\n", FloatTime(), bs->client); - if (trap_BotChooseLTGItem(bs->gs, bs->origin, bs->inventory, tfl)) { - /* - char buf[128]; - //get the goal at the top of the stack - trap_BotGetTopGoal(bs->gs, goal); - trap_BotGoalName(goal->number, buf, sizeof(buf)); - BotAI_Print(PRT_MESSAGE, "%1.1f: new long term goal %s\n", FloatTime(), buf); - */ - bs->ltg_time = FloatTime() + 20; - } - else {//the bot gets sorta stuck with all the avoid timings, shouldn't happen though - // -#ifdef DEBUG - char netname[128]; - - BotAI_Print(PRT_MESSAGE, "%s: no valid ltg (probably stuck)\n", ClientName(bs->client, netname, sizeof(netname))); -#endif - //trap_BotDumpAvoidGoals(bs->gs); - //reset the avoid goals and the avoid reach - trap_BotResetAvoidGoals(bs->gs); - trap_BotResetAvoidReach(bs->ms); - } - //get the goal at the top of the stack - return trap_BotGetTopGoal(bs->gs, goal); - } - return qtrue; -} - -/* -================== -BotGetLongTermGoal - -we could also create a seperate AI node for every long term goal type -however this saves us a lot of code -================== -*/ -int BotGetLongTermGoal(bot_state_t *bs, int tfl, int retreat, bot_goal_t *goal) { - vec3_t target, dir, dir2; - char netname[MAX_NETNAME]; - char buf[MAX_MESSAGE_SIZE]; - int areanum; - float croucher; - aas_entityinfo_t entinfo, botinfo; - bot_waypoint_t *wp; - - if (bs->ltgtype == LTG_TEAMHELP && !retreat) { - //check for bot typing status message - if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { - BotAI_BotInitialChat(bs, "help_start", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); - trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); - BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES); - trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); - bs->teammessage_time = 0; - } - //if trying to help the team mate for more than a minute - if (bs->teamgoal_time < FloatTime()) - bs->ltgtype = 0; - //if the team mate IS visible for quite some time - if (bs->teammatevisible_time < FloatTime() - 10) bs->ltgtype = 0; - //get entity information of the companion - BotEntityInfo(bs->teammate, &entinfo); - //if the team mate is visible - if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->teammate)) { - //if close just stand still there - VectorSubtract(entinfo.origin, bs->origin, dir); - if (VectorLengthSquared(dir) < Square(100)) { - trap_BotResetAvoidReach(bs->ms); - return qfalse; - } - } - else { - //last time the bot was NOT visible - bs->teammatevisible_time = FloatTime(); - } - //if the entity information is valid (entity in PVS) - if (entinfo.valid) { - areanum = BotPointAreaNum(entinfo.origin); - if (areanum && trap_AAS_AreaReachability(areanum)) { - //update team goal - bs->teamgoal.entitynum = bs->teammate; - bs->teamgoal.areanum = areanum; - VectorCopy(entinfo.origin, bs->teamgoal.origin); - VectorSet(bs->teamgoal.mins, -8, -8, -8); - VectorSet(bs->teamgoal.maxs, 8, 8, 8); - } - } - memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t)); - return qtrue; - } - //if the bot accompanies someone - if (bs->ltgtype == LTG_TEAMACCOMPANY && !retreat) { - //check for bot typing status message - if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { - BotAI_BotInitialChat(bs, "accompany_start", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); - trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); - BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES); - trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); - bs->teammessage_time = 0; - } - //if accompanying the companion for 3 minutes - if (bs->teamgoal_time < FloatTime()) { - BotAI_BotInitialChat(bs, "accompany_stop", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); - trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); - bs->ltgtype = 0; - } - //get entity information of the companion - BotEntityInfo(bs->teammate, &entinfo); - //if the companion is visible - if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->teammate)) { - //update visible time - bs->teammatevisible_time = FloatTime(); - VectorSubtract(entinfo.origin, bs->origin, dir); - if (VectorLengthSquared(dir) < Square(bs->formation_dist)) { - // - // if the client being followed bumps into this bot then - // the bot should back up - BotEntityInfo(bs->entitynum, &botinfo); - // if the followed client is not standing ontop of the bot - if (botinfo.origin[2] + botinfo.maxs[2] > entinfo.origin[2] + entinfo.mins[2]) { - // if the bounding boxes touch each other - if (botinfo.origin[0] + botinfo.maxs[0] > entinfo.origin[0] + entinfo.mins[0] - 4&& - botinfo.origin[0] + botinfo.mins[0] < entinfo.origin[0] + entinfo.maxs[0] + 4) { - if (botinfo.origin[1] + botinfo.maxs[1] > entinfo.origin[1] + entinfo.mins[1] - 4 && - botinfo.origin[1] + botinfo.mins[1] < entinfo.origin[1] + entinfo.maxs[1] + 4) { - if (botinfo.origin[2] + botinfo.maxs[2] > entinfo.origin[2] + entinfo.mins[2] - 4 && - botinfo.origin[2] + botinfo.mins[2] < entinfo.origin[2] + entinfo.maxs[2] + 4) { - // if the followed client looks in the direction of this bot - AngleVectors(entinfo.angles, dir, NULL, NULL); - dir[2] = 0; - VectorNormalize(dir); - //VectorSubtract(entinfo.origin, entinfo.lastvisorigin, dir); - VectorSubtract(bs->origin, entinfo.origin, dir2); - VectorNormalize(dir2); - if (DotProduct(dir, dir2) > 0.7) { - // back up - BotSetupForMovement(bs); - trap_BotMoveInDirection(bs->ms, dir2, 400, MOVE_WALK); - } - } - } - } - } - //check if the bot wants to crouch - //don't crouch if crouched less than 5 seconds ago - if (bs->attackcrouch_time < FloatTime() - 5) { - croucher = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CROUCHER, 0, 1); - if (random() < bs->thinktime * croucher) { - bs->attackcrouch_time = FloatTime() + 5 + croucher * 15; - } - } - //don't crouch when swimming - if (trap_AAS_Swimming(bs->origin)) bs->attackcrouch_time = FloatTime() - 1; - //if not arrived yet or arived some time ago - if (bs->arrive_time < FloatTime() - 2) { - //if not arrived yet - if (!bs->arrive_time) { - trap_EA_Gesture(bs->client); - BotAI_BotInitialChat(bs, "accompany_arrive", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); - trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); - bs->arrive_time = FloatTime(); - } - //if the bot wants to crouch - else if (bs->attackcrouch_time > FloatTime()) { - trap_EA_Crouch(bs->client); - } - //else do some model taunts - else if (random() < bs->thinktime * 0.05) { - //do a gesture :) - trap_EA_Gesture(bs->client); - } - } - //if just arrived look at the companion - if (bs->arrive_time > FloatTime() - 2) { - VectorSubtract(entinfo.origin, bs->origin, dir); - vectoangles(dir, bs->ideal_viewangles); - bs->ideal_viewangles[2] *= 0.5; - } - //else look strategically around for enemies - else if (random() < bs->thinktime * 0.8) { - BotRoamGoal(bs, target); - VectorSubtract(target, bs->origin, dir); - vectoangles(dir, bs->ideal_viewangles); - bs->ideal_viewangles[2] *= 0.5; - } - //check if the bot wants to go for air - if (BotGoForAir(bs, bs->tfl, &bs->teamgoal, 400)) { - trap_BotResetLastAvoidReach(bs->ms); - //get the goal at the top of the stack - //trap_BotGetTopGoal(bs->gs, &tmpgoal); - //trap_BotGoalName(tmpgoal.number, buf, 144); - //BotAI_Print(PRT_MESSAGE, "new nearby goal %s\n", buf); - //time the bot gets to pick up the nearby goal item - bs->nbg_time = FloatTime() + 8; - AIEnter_Seek_NBG(bs, "BotLongTermGoal: go for air"); - return qfalse; - } - // - trap_BotResetAvoidReach(bs->ms); - return qfalse; - } - } - //if the entity information is valid (entity in PVS) - if (entinfo.valid) { - areanum = BotPointAreaNum(entinfo.origin); - if (areanum && trap_AAS_AreaReachability(areanum)) { - //update team goal - bs->teamgoal.entitynum = bs->teammate; - bs->teamgoal.areanum = areanum; - VectorCopy(entinfo.origin, bs->teamgoal.origin); - VectorSet(bs->teamgoal.mins, -8, -8, -8); - VectorSet(bs->teamgoal.maxs, 8, 8, 8); - } - } - //the goal the bot should go for - memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t)); - //if the companion is NOT visible for too long - if (bs->teammatevisible_time < FloatTime() - 60) { - BotAI_BotInitialChat(bs, "accompany_cannotfind", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); - trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); - bs->ltgtype = 0; - // just to make sure the bot won't spam this message - bs->teammatevisible_time = FloatTime(); - } - return qtrue; - } - // - if (bs->ltgtype == LTG_DEFENDKEYAREA) { - if (trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, - bs->teamgoal.areanum, TFL_DEFAULT) > bs->defendaway_range) { - bs->defendaway_time = 0; - } - } - //if defending a key area - if (bs->ltgtype == LTG_DEFENDKEYAREA && !retreat && - bs->defendaway_time < FloatTime()) { - //check for bot typing status message - if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { - trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf)); - BotAI_BotInitialChat(bs, "defend_start", buf, NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); - BotVoiceChatOnly(bs, -1, VOICECHAT_ONDEFENSE); - bs->teammessage_time = 0; - } - //set the bot goal - memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t)); - //stop after 2 minutes - if (bs->teamgoal_time < FloatTime()) { - trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf)); - BotAI_BotInitialChat(bs, "defend_stop", buf, NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); - bs->ltgtype = 0; - } - //if very close... go away for some time - VectorSubtract(goal->origin, bs->origin, dir); - if (VectorLengthSquared(dir) < Square(70)) { - trap_BotResetAvoidReach(bs->ms); - bs->defendaway_time = FloatTime() + 3 + 3 * random(); - if (BotHasPersistantPowerupAndWeapon(bs)) { - bs->defendaway_range = 100; - } - else { - bs->defendaway_range = 350; - } - } - return qtrue; - } - //going to kill someone - if (bs->ltgtype == LTG_KILL && !retreat) { - //check for bot typing status message - if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { - EasyClientName(bs->teamgoal.entitynum, buf, sizeof(buf)); - BotAI_BotInitialChat(bs, "kill_start", buf, NULL); - trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); - bs->teammessage_time = 0; - } - // - if (bs->lastkilledplayer == bs->teamgoal.entitynum) { - EasyClientName(bs->teamgoal.entitynum, buf, sizeof(buf)); - BotAI_BotInitialChat(bs, "kill_done", buf, NULL); - trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); - bs->lastkilledplayer = -1; - bs->ltgtype = 0; - } - // - if (bs->teamgoal_time < FloatTime()) { - bs->ltgtype = 0; - } - //just roam around - return BotGetItemLongTermGoal(bs, tfl, goal); - } - //get an item - if (bs->ltgtype == LTG_GETITEM && !retreat) { - //check for bot typing status message - if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { - trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf)); - BotAI_BotInitialChat(bs, "getitem_start", buf, NULL); - trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); - BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES); - trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); - bs->teammessage_time = 0; - } - //set the bot goal - memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t)); - //stop after some time - if (bs->teamgoal_time < FloatTime()) { - bs->ltgtype = 0; - } - // - if (trap_BotItemGoalInVisButNotVisible(bs->entitynum, bs->eye, bs->viewangles, goal)) { - trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf)); - BotAI_BotInitialChat(bs, "getitem_notthere", buf, NULL); - trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); - bs->ltgtype = 0; - } - else if (BotReachedGoal(bs, goal)) { - trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf)); - BotAI_BotInitialChat(bs, "getitem_gotit", buf, NULL); - trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); - bs->ltgtype = 0; - } - return qtrue; - } - //if camping somewhere - if ((bs->ltgtype == LTG_CAMP || bs->ltgtype == LTG_CAMPORDER) && !retreat) { - //check for bot typing status message - if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { - if (bs->ltgtype == LTG_CAMPORDER) { - BotAI_BotInitialChat(bs, "camp_start", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); - trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); - BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES); - trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); - } - bs->teammessage_time = 0; - } - //set the bot goal - memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t)); - // - if (bs->teamgoal_time < FloatTime()) { - if (bs->ltgtype == LTG_CAMPORDER) { - BotAI_BotInitialChat(bs, "camp_stop", NULL); - trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); - } - bs->ltgtype = 0; - } - //if really near the camp spot - VectorSubtract(goal->origin, bs->origin, dir); - if (VectorLengthSquared(dir) < Square(60)) - { - //if not arrived yet - if (!bs->arrive_time) { - if (bs->ltgtype == LTG_CAMPORDER) { - BotAI_BotInitialChat(bs, "camp_arrive", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); - trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); - BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_INPOSITION); - } - bs->arrive_time = FloatTime(); - } - //look strategically around for enemies - if (random() < bs->thinktime * 0.8) { - BotRoamGoal(bs, target); - VectorSubtract(target, bs->origin, dir); - vectoangles(dir, bs->ideal_viewangles); - bs->ideal_viewangles[2] *= 0.5; - } - //check if the bot wants to crouch - //don't crouch if crouched less than 5 seconds ago - if (bs->attackcrouch_time < FloatTime() - 5) { - croucher = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CROUCHER, 0, 1); - if (random() < bs->thinktime * croucher) { - bs->attackcrouch_time = FloatTime() + 5 + croucher * 15; - } - } - //if the bot wants to crouch - if (bs->attackcrouch_time > FloatTime()) { - trap_EA_Crouch(bs->client); - } - //don't crouch when swimming - if (trap_AAS_Swimming(bs->origin)) bs->attackcrouch_time = FloatTime() - 1; - //make sure the bot is not gonna drown - if (trap_PointContents(bs->eye,bs->entitynum) & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA)) { - if (bs->ltgtype == LTG_CAMPORDER) { - BotAI_BotInitialChat(bs, "camp_stop", NULL); - trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); - // - if (bs->lastgoal_ltgtype == LTG_CAMPORDER) { - bs->lastgoal_ltgtype = 0; - } - } - bs->ltgtype = 0; - } - // - if (bs->camp_range > 0) { - //FIXME: move around a bit - } - // - trap_BotResetAvoidReach(bs->ms); - return qfalse; - } - return qtrue; - } - //patrolling along several waypoints - if (bs->ltgtype == LTG_PATROL && !retreat) { - //check for bot typing status message - if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { - strcpy(buf, ""); - for (wp = bs->patrolpoints; wp; wp = wp->next) { - strcat(buf, wp->name); - if (wp->next) strcat(buf, " to "); - } - BotAI_BotInitialChat(bs, "patrol_start", buf, NULL); - trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); - BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES); - trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); - bs->teammessage_time = 0; - } - // - if (!bs->curpatrolpoint) { - bs->ltgtype = 0; - return qfalse; - } - //if the bot touches the current goal - if (trap_BotTouchingGoal(bs->origin, &bs->curpatrolpoint->goal)) { - if (bs->patrolflags & PATROL_BACK) { - if (bs->curpatrolpoint->prev) { - bs->curpatrolpoint = bs->curpatrolpoint->prev; - } - else { - bs->curpatrolpoint = bs->curpatrolpoint->next; - bs->patrolflags &= ~PATROL_BACK; - } - } - else { - if (bs->curpatrolpoint->next) { - bs->curpatrolpoint = bs->curpatrolpoint->next; - } - else { - bs->curpatrolpoint = bs->curpatrolpoint->prev; - bs->patrolflags |= PATROL_BACK; - } - } - } - //stop after 5 minutes - if (bs->teamgoal_time < FloatTime()) { - BotAI_BotInitialChat(bs, "patrol_stop", NULL); - trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); - bs->ltgtype = 0; - } - if (!bs->curpatrolpoint) { - bs->ltgtype = 0; - return qfalse; - } - memcpy(goal, &bs->curpatrolpoint->goal, sizeof(bot_goal_t)); - return qtrue; - } -#ifdef CTF - if (gametype == GT_CTF) { - //if going for enemy flag - if (bs->ltgtype == LTG_GETFLAG) { - //check for bot typing status message - if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { - BotAI_BotInitialChat(bs, "captureflag_start", NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); - BotVoiceChatOnly(bs, -1, VOICECHAT_ONGETFLAG); - bs->teammessage_time = 0; - } - // - switch(BotTeam(bs)) { - case TEAM_RED: memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t)); break; - case TEAM_BLUE: memcpy(goal, &ctf_redflag, sizeof(bot_goal_t)); break; - default: bs->ltgtype = 0; return qfalse; - } - //if touching the flag - if (trap_BotTouchingGoal(bs->origin, goal)) { - // make sure the bot knows the flag isn't there anymore - switch(BotTeam(bs)) { - case TEAM_RED: bs->blueflagstatus = 1; break; - case TEAM_BLUE: bs->redflagstatus = 1; break; - } - bs->ltgtype = 0; - } - //stop after 3 minutes - if (bs->teamgoal_time < FloatTime()) { - bs->ltgtype = 0; - } - BotAlternateRoute(bs, goal); - return qtrue; - } - //if rushing to the base - if (bs->ltgtype == LTG_RUSHBASE && bs->rushbaseaway_time < FloatTime()) { - switch(BotTeam(bs)) { - case TEAM_RED: memcpy(goal, &ctf_redflag, sizeof(bot_goal_t)); break; - case TEAM_BLUE: memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t)); break; - default: bs->ltgtype = 0; return qfalse; - } - //if not carrying the flag anymore - if (!BotCTFCarryingFlag(bs)) bs->ltgtype = 0; - //quit rushing after 2 minutes - if (bs->teamgoal_time < FloatTime()) bs->ltgtype = 0; - //if touching the base flag the bot should loose the enemy flag - if (trap_BotTouchingGoal(bs->origin, goal)) { - //if the bot is still carrying the enemy flag then the - //base flag is gone, now just walk near the base a bit - if (BotCTFCarryingFlag(bs)) { - trap_BotResetAvoidReach(bs->ms); - bs->rushbaseaway_time = FloatTime() + 5 + 10 * random(); - //FIXME: add chat to tell the others to get back the flag - } - else { - bs->ltgtype = 0; - } - } - BotAlternateRoute(bs, goal); - return qtrue; - } - //returning flag - if (bs->ltgtype == LTG_RETURNFLAG) { - //check for bot typing status message - if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { - BotAI_BotInitialChat(bs, "returnflag_start", NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); - BotVoiceChatOnly(bs, -1, VOICECHAT_ONRETURNFLAG); - bs->teammessage_time = 0; - } - // - switch(BotTeam(bs)) { - case TEAM_RED: memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t)); break; - case TEAM_BLUE: memcpy(goal, &ctf_redflag, sizeof(bot_goal_t)); break; - default: bs->ltgtype = 0; return qfalse; - } - //if touching the flag - if (trap_BotTouchingGoal(bs->origin, goal)) bs->ltgtype = 0; - //stop after 3 minutes - if (bs->teamgoal_time < FloatTime()) { - bs->ltgtype = 0; - } - BotAlternateRoute(bs, goal); - return qtrue; - } - } -#endif //CTF -#ifdef MISSIONPACK - else if (gametype == GT_1FCTF) { - if (bs->ltgtype == LTG_GETFLAG) { - //check for bot typing status message - if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { - BotAI_BotInitialChat(bs, "captureflag_start", NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); - BotVoiceChatOnly(bs, -1, VOICECHAT_ONGETFLAG); - bs->teammessage_time = 0; - } - memcpy(goal, &ctf_neutralflag, sizeof(bot_goal_t)); - //if touching the flag - if (trap_BotTouchingGoal(bs->origin, goal)) { - bs->ltgtype = 0; - } - //stop after 3 minutes - if (bs->teamgoal_time < FloatTime()) { - bs->ltgtype = 0; - } - return qtrue; - } - //if rushing to the base - if (bs->ltgtype == LTG_RUSHBASE) { - switch(BotTeam(bs)) { - case TEAM_RED: memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t)); break; - case TEAM_BLUE: memcpy(goal, &ctf_redflag, sizeof(bot_goal_t)); break; - default: bs->ltgtype = 0; return qfalse; - } - //if not carrying the flag anymore - if (!Bot1FCTFCarryingFlag(bs)) { - bs->ltgtype = 0; - } - //quit rushing after 2 minutes - if (bs->teamgoal_time < FloatTime()) { - bs->ltgtype = 0; - } - //if touching the base flag the bot should loose the enemy flag - if (trap_BotTouchingGoal(bs->origin, goal)) { - bs->ltgtype = 0; - } - BotAlternateRoute(bs, goal); - return qtrue; - } - //attack the enemy base - if (bs->ltgtype == LTG_ATTACKENEMYBASE && - bs->attackaway_time < FloatTime()) { - //check for bot typing status message - if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { - BotAI_BotInitialChat(bs, "attackenemybase_start", NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); - BotVoiceChatOnly(bs, -1, VOICECHAT_ONOFFENSE); - bs->teammessage_time = 0; - } - switch(BotTeam(bs)) { - case TEAM_RED: memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t)); break; - case TEAM_BLUE: memcpy(goal, &ctf_redflag, sizeof(bot_goal_t)); break; - default: bs->ltgtype = 0; return qfalse; - } - //quit rushing after 2 minutes - if (bs->teamgoal_time < FloatTime()) { - bs->ltgtype = 0; - } - //if touching the base flag the bot should loose the enemy flag - if (trap_BotTouchingGoal(bs->origin, goal)) { - bs->attackaway_time = FloatTime() + 2 + 5 * random(); - } - return qtrue; - } - //returning flag - if (bs->ltgtype == LTG_RETURNFLAG) { - //check for bot typing status message - if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { - BotAI_BotInitialChat(bs, "returnflag_start", NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); - BotVoiceChatOnly(bs, -1, VOICECHAT_ONRETURNFLAG); - bs->teammessage_time = 0; - } - // - if (bs->teamgoal_time < FloatTime()) { - bs->ltgtype = 0; - } - //just roam around - return BotGetItemLongTermGoal(bs, tfl, goal); - } - } - else if (gametype == GT_OBELISK) { - if (bs->ltgtype == LTG_ATTACKENEMYBASE && - bs->attackaway_time < FloatTime()) { - - //check for bot typing status message - if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { - BotAI_BotInitialChat(bs, "attackenemybase_start", NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); - BotVoiceChatOnly(bs, -1, VOICECHAT_ONOFFENSE); - bs->teammessage_time = 0; - } - switch(BotTeam(bs)) { - case TEAM_RED: memcpy(goal, &blueobelisk, sizeof(bot_goal_t)); break; - case TEAM_BLUE: memcpy(goal, &redobelisk, sizeof(bot_goal_t)); break; - default: bs->ltgtype = 0; return qfalse; - } - //if the bot no longer wants to attack the obelisk - if (BotFeelingBad(bs) > 50) { - return BotGetItemLongTermGoal(bs, tfl, goal); - } - //if touching the obelisk - if (trap_BotTouchingGoal(bs->origin, goal)) { - bs->attackaway_time = FloatTime() + 3 + 5 * random(); - } - // or very close to the obelisk - VectorSubtract(bs->origin, goal->origin, dir); - if (VectorLengthSquared(dir) < Square(60)) { - bs->attackaway_time = FloatTime() + 3 + 5 * random(); - } - //quit rushing after 2 minutes - if (bs->teamgoal_time < FloatTime()) { - bs->ltgtype = 0; - } - BotAlternateRoute(bs, goal); - //just move towards the obelisk - return qtrue; - } - } - else if (gametype == GT_HARVESTER) { - //if rushing to the base - if (bs->ltgtype == LTG_RUSHBASE) { - switch(BotTeam(bs)) { - case TEAM_RED: memcpy(goal, &blueobelisk, sizeof(bot_goal_t)); break; - case TEAM_BLUE: memcpy(goal, &redobelisk, sizeof(bot_goal_t)); break; - default: BotGoHarvest(bs); return qfalse; - } - //if not carrying any cubes - if (!BotHarvesterCarryingCubes(bs)) { - BotGoHarvest(bs); - return qfalse; - } - //quit rushing after 2 minutes - if (bs->teamgoal_time < FloatTime()) { - BotGoHarvest(bs); - return qfalse; - } - //if touching the base flag the bot should loose the enemy flag - if (trap_BotTouchingGoal(bs->origin, goal)) { - BotGoHarvest(bs); - return qfalse; - } - BotAlternateRoute(bs, goal); - return qtrue; - } - //attack the enemy base - if (bs->ltgtype == LTG_ATTACKENEMYBASE && - bs->attackaway_time < FloatTime()) { - //check for bot typing status message - if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { - BotAI_BotInitialChat(bs, "attackenemybase_start", NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); - BotVoiceChatOnly(bs, -1, VOICECHAT_ONOFFENSE); - bs->teammessage_time = 0; - } - switch(BotTeam(bs)) { - case TEAM_RED: memcpy(goal, &blueobelisk, sizeof(bot_goal_t)); break; - case TEAM_BLUE: memcpy(goal, &redobelisk, sizeof(bot_goal_t)); break; - default: bs->ltgtype = 0; return qfalse; - } - //quit rushing after 2 minutes - if (bs->teamgoal_time < FloatTime()) { - bs->ltgtype = 0; - } - //if touching the base flag the bot should loose the enemy flag - if (trap_BotTouchingGoal(bs->origin, goal)) { - bs->attackaway_time = FloatTime() + 2 + 5 * random(); - } - return qtrue; - } - //harvest cubes - if (bs->ltgtype == LTG_HARVEST && - bs->harvestaway_time < FloatTime()) { - //check for bot typing status message - if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { - BotAI_BotInitialChat(bs, "harvest_start", NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); - BotVoiceChatOnly(bs, -1, VOICECHAT_ONOFFENSE); - bs->teammessage_time = 0; - } - memcpy(goal, &neutralobelisk, sizeof(bot_goal_t)); - // - if (bs->teamgoal_time < FloatTime()) { - bs->ltgtype = 0; - } - // - if (trap_BotTouchingGoal(bs->origin, goal)) { - bs->harvestaway_time = FloatTime() + 4 + 3 * random(); - } - return qtrue; - } - } -#endif - //normal goal stuff - return BotGetItemLongTermGoal(bs, tfl, goal); -} - -/* -================== -BotLongTermGoal -================== -*/ -int BotLongTermGoal(bot_state_t *bs, int tfl, int retreat, bot_goal_t *goal) { - aas_entityinfo_t entinfo; - char teammate[MAX_MESSAGE_SIZE]; - float squaredist; - int areanum; - vec3_t dir; - - //FIXME: also have air long term goals? - // - //if the bot is leading someone and not retreating - if (bs->lead_time > 0 && !retreat) { - if (bs->lead_time < FloatTime()) { - BotAI_BotInitialChat(bs, "lead_stop", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL); - trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); - bs->lead_time = 0; - return BotGetLongTermGoal(bs, tfl, retreat, goal); - } - // - if (bs->leadmessage_time < 0 && -bs->leadmessage_time < FloatTime()) { - BotAI_BotInitialChat(bs, "followme", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL); - trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); - bs->leadmessage_time = FloatTime(); - } - //get entity information of the companion - BotEntityInfo(bs->lead_teammate, &entinfo); - // - if (entinfo.valid) { - areanum = BotPointAreaNum(entinfo.origin); - if (areanum && trap_AAS_AreaReachability(areanum)) { - //update team goal - bs->lead_teamgoal.entitynum = bs->lead_teammate; - bs->lead_teamgoal.areanum = areanum; - VectorCopy(entinfo.origin, bs->lead_teamgoal.origin); - VectorSet(bs->lead_teamgoal.mins, -8, -8, -8); - VectorSet(bs->lead_teamgoal.maxs, 8, 8, 8); - } - } - //if the team mate is visible - if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->lead_teammate)) { - bs->leadvisible_time = FloatTime(); - } - //if the team mate is not visible for 1 seconds - if (bs->leadvisible_time < FloatTime() - 1) { - bs->leadbackup_time = FloatTime() + 2; - } - //distance towards the team mate - VectorSubtract(bs->origin, bs->lead_teamgoal.origin, dir); - squaredist = VectorLengthSquared(dir); - //if backing up towards the team mate - if (bs->leadbackup_time > FloatTime()) { - if (bs->leadmessage_time < FloatTime() - 20) { - BotAI_BotInitialChat(bs, "followme", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL); - trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); - bs->leadmessage_time = FloatTime(); - } - //if very close to the team mate - if (squaredist < Square(100)) { - bs->leadbackup_time = 0; - } - //the bot should go back to the team mate - memcpy(goal, &bs->lead_teamgoal, sizeof(bot_goal_t)); - return qtrue; - } - else { - //if quite distant from the team mate - if (squaredist > Square(500)) { - if (bs->leadmessage_time < FloatTime() - 20) { - BotAI_BotInitialChat(bs, "followme", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL); - trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); - bs->leadmessage_time = FloatTime(); - } - //look at the team mate - VectorSubtract(entinfo.origin, bs->origin, dir); - vectoangles(dir, bs->ideal_viewangles); - bs->ideal_viewangles[2] *= 0.5; - //just wait for the team mate - return qfalse; - } - } - } - return BotGetLongTermGoal(bs, tfl, retreat, goal); -} - -/* -================== -AIEnter_Intermission -================== -*/ -void AIEnter_Intermission(bot_state_t *bs, char *s) { - BotRecordNodeSwitch(bs, "intermission", "", s); - //reset the bot state - BotResetState(bs); - //check for end level chat - if (BotChat_EndLevel(bs)) { - trap_BotEnterChat(bs->cs, 0, bs->chatto); - } - bs->ainode = AINode_Intermission; -} - -/* -================== -AINode_Intermission -================== -*/ -int AINode_Intermission(bot_state_t *bs) { - //if the intermission ended - if (!BotIntermission(bs)) { - if (BotChat_StartLevel(bs)) { - bs->stand_time = FloatTime() + BotChatTime(bs); - } - else { - bs->stand_time = FloatTime() + 2; - } - AIEnter_Stand(bs, "intermission: chat"); - } - return qtrue; -} - -/* -================== -AIEnter_Observer -================== -*/ -void AIEnter_Observer(bot_state_t *bs, char *s) { - BotRecordNodeSwitch(bs, "observer", "", s); - //reset the bot state - BotResetState(bs); - bs->ainode = AINode_Observer; -} - -/* -================== -AINode_Observer -================== -*/ -int AINode_Observer(bot_state_t *bs) { - //if the bot left observer mode - if (!BotIsObserver(bs)) { - AIEnter_Stand(bs, "observer: left observer"); - } - return qtrue; -} - -/* -================== -AIEnter_Stand -================== -*/ -void AIEnter_Stand(bot_state_t *bs, char *s) { - BotRecordNodeSwitch(bs, "stand", "", s); - bs->standfindenemy_time = FloatTime() + 1; - bs->ainode = AINode_Stand; -} - -/* -================== -AINode_Stand -================== -*/ -int AINode_Stand(bot_state_t *bs) { - - //if the bot's health decreased - if (bs->lastframe_health > bs->inventory[INVENTORY_HEALTH]) { - if (BotChat_HitTalking(bs)) { - bs->standfindenemy_time = FloatTime() + BotChatTime(bs) + 0.1; - bs->stand_time = FloatTime() + BotChatTime(bs) + 0.1; - } - } - if (bs->standfindenemy_time < FloatTime()) { - if (BotFindEnemy(bs, -1)) { - AIEnter_Battle_Fight(bs, "stand: found enemy"); - return qfalse; - } - bs->standfindenemy_time = FloatTime() + 1; - } - // put up chat icon - trap_EA_Talk(bs->client); - // when done standing - if (bs->stand_time < FloatTime()) { - trap_BotEnterChat(bs->cs, 0, bs->chatto); - AIEnter_Seek_LTG(bs, "stand: time out"); - return qfalse; - } - // - return qtrue; -} - -/* -================== -AIEnter_Respawn -================== -*/ -void AIEnter_Respawn(bot_state_t *bs, char *s) { - BotRecordNodeSwitch(bs, "respawn", "", s); - //reset some states - trap_BotResetMoveState(bs->ms); - trap_BotResetGoalState(bs->gs); - trap_BotResetAvoidGoals(bs->gs); - trap_BotResetAvoidReach(bs->ms); - //if the bot wants to chat - if (BotChat_Death(bs)) { - bs->respawn_time = FloatTime() + BotChatTime(bs); - bs->respawnchat_time = FloatTime(); - } - else { - bs->respawn_time = FloatTime() + 1 + random(); - bs->respawnchat_time = 0; - } - //set respawn state - bs->respawn_wait = qfalse; - bs->ainode = AINode_Respawn; -} - -/* -================== -AINode_Respawn -================== -*/ -int AINode_Respawn(bot_state_t *bs) { - // if waiting for the actual respawn - if (bs->respawn_wait) { - if (!BotIsDead(bs)) { - AIEnter_Seek_LTG(bs, "respawn: respawned"); - } - else { - trap_EA_Respawn(bs->client); - } - } - else if (bs->respawn_time < FloatTime()) { - // wait until respawned - bs->respawn_wait = qtrue; - // elementary action respawn - trap_EA_Respawn(bs->client); - // - if (bs->respawnchat_time) { - trap_BotEnterChat(bs->cs, 0, bs->chatto); - bs->enemy = -1; - } - } - if (bs->respawnchat_time && bs->respawnchat_time < FloatTime() - 0.5) { - trap_EA_Talk(bs->client); - } - // - return qtrue; -} - -/* -================== -BotSelectActivateWeapon -================== -*/ -int BotSelectActivateWeapon(bot_state_t *bs) { - // - if (bs->inventory[INVENTORY_MACHINEGUN] > 0 && bs->inventory[INVENTORY_BULLETS] > 0) - return WEAPONINDEX_MACHINEGUN; - else if (bs->inventory[INVENTORY_SHOTGUN] > 0 && bs->inventory[INVENTORY_SHELLS] > 0) - return WEAPONINDEX_SHOTGUN; - else if (bs->inventory[INVENTORY_PLASMAGUN] > 0 && bs->inventory[INVENTORY_CELLS] > 0) - return WEAPONINDEX_PLASMAGUN; - else if (bs->inventory[INVENTORY_LIGHTNING] > 0 && bs->inventory[INVENTORY_LIGHTNINGAMMO] > 0) - return WEAPONINDEX_LIGHTNING; -#ifdef MISSIONPACK - else if (bs->inventory[INVENTORY_CHAINGUN] > 0 && bs->inventory[INVENTORY_BELT] > 0) - return WEAPONINDEX_CHAINGUN; - else if (bs->inventory[INVENTORY_NAILGUN] > 0 && bs->inventory[INVENTORY_NAILS] > 0) - return WEAPONINDEX_NAILGUN; -#endif - else if (bs->inventory[INVENTORY_RAILGUN] > 0 && bs->inventory[INVENTORY_SLUGS] > 0) - return WEAPONINDEX_RAILGUN; - else if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && bs->inventory[INVENTORY_ROCKETS] > 0) - return WEAPONINDEX_ROCKET_LAUNCHER; - else if (bs->inventory[INVENTORY_BFG10K] > 0 && bs->inventory[INVENTORY_BFGAMMO] > 0) - return WEAPONINDEX_BFG; - else { - return -1; - } -} - -/* -================== -BotClearPath - - try to deactivate obstacles like proximity mines on the bot's path -================== -*/ -void BotClearPath(bot_state_t *bs, bot_moveresult_t *moveresult) { - int i, bestmine; - float dist, bestdist; - vec3_t target, dir; - bsp_trace_t bsptrace; - entityState_t state; - - // if there is a dead body wearing kamikze nearby - if (bs->kamikazebody) { - // if the bot's view angles and weapon are not used for movement - if ( !(moveresult->flags & (MOVERESULT_MOVEMENTVIEW | MOVERESULT_MOVEMENTWEAPON)) ) { - // - BotAI_GetEntityState(bs->kamikazebody, &state); - VectorCopy(state.pos.trBase, target); - target[2] += 8; - VectorSubtract(target, bs->eye, dir); - vectoangles(dir, moveresult->ideal_viewangles); - // - moveresult->weapon = BotSelectActivateWeapon(bs); - if (moveresult->weapon == -1) { - // FIXME: run away! - moveresult->weapon = 0; - } - if (moveresult->weapon) { - // - moveresult->flags |= MOVERESULT_MOVEMENTWEAPON | MOVERESULT_MOVEMENTVIEW; - // if holding the right weapon - if (bs->cur_ps.weapon == moveresult->weapon) { - // if the bot is pretty close with it's aim - if (InFieldOfVision(bs->viewangles, 20, moveresult->ideal_viewangles)) { - // - BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, target, bs->entitynum, MASK_SHOT); - // if the mine is visible from the current position - if (bsptrace.fraction >= 1.0 || bsptrace.ent == state.number) { - // shoot at the mine - trap_EA_Attack(bs->client); - } - } - } - } - } - } - if (moveresult->flags & MOVERESULT_BLOCKEDBYAVOIDSPOT) { - bs->blockedbyavoidspot_time = FloatTime() + 5; - } - // if blocked by an avoid spot and the view angles and weapon are used for movement - if (bs->blockedbyavoidspot_time > FloatTime() && - !(moveresult->flags & (MOVERESULT_MOVEMENTVIEW | MOVERESULT_MOVEMENTWEAPON)) ) { - bestdist = 300; - bestmine = -1; - for (i = 0; i < bs->numproxmines; i++) { - BotAI_GetEntityState(bs->proxmines[i], &state); - VectorSubtract(state.pos.trBase, bs->origin, dir); - dist = VectorLength(dir); - if (dist < bestdist) { - bestdist = dist; - bestmine = i; - } - } - if (bestmine != -1) { - // - // state->generic1 == TEAM_RED || state->generic1 == TEAM_BLUE - // - // deactivate prox mines in the bot's path by shooting - // rockets or plasma cells etc. at them - BotAI_GetEntityState(bs->proxmines[bestmine], &state); - VectorCopy(state.pos.trBase, target); - target[2] += 2; - VectorSubtract(target, bs->eye, dir); - vectoangles(dir, moveresult->ideal_viewangles); - // if the bot has a weapon that does splash damage - if (bs->inventory[INVENTORY_PLASMAGUN] > 0 && bs->inventory[INVENTORY_CELLS] > 0) - moveresult->weapon = WEAPONINDEX_PLASMAGUN; - else if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && bs->inventory[INVENTORY_ROCKETS] > 0) - moveresult->weapon = WEAPONINDEX_ROCKET_LAUNCHER; - else if (bs->inventory[INVENTORY_BFG10K] > 0 && bs->inventory[INVENTORY_BFGAMMO] > 0) - moveresult->weapon = WEAPONINDEX_BFG; - else { - moveresult->weapon = 0; - } - if (moveresult->weapon) { - // - moveresult->flags |= MOVERESULT_MOVEMENTWEAPON | MOVERESULT_MOVEMENTVIEW; - // if holding the right weapon - if (bs->cur_ps.weapon == moveresult->weapon) { - // if the bot is pretty close with it's aim - if (InFieldOfVision(bs->viewangles, 20, moveresult->ideal_viewangles)) { - // - BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, target, bs->entitynum, MASK_SHOT); - // if the mine is visible from the current position - if (bsptrace.fraction >= 1.0 || bsptrace.ent == state.number) { - // shoot at the mine - trap_EA_Attack(bs->client); - } - } - } - } - } - } -} - -/* -================== -AIEnter_Seek_ActivateEntity -================== -*/ -void AIEnter_Seek_ActivateEntity(bot_state_t *bs, char *s) { - BotRecordNodeSwitch(bs, "activate entity", "", s); - bs->ainode = AINode_Seek_ActivateEntity; -} - -/* -================== -AINode_Seek_Activate_Entity -================== -*/ -int AINode_Seek_ActivateEntity(bot_state_t *bs) { - bot_goal_t *goal; - vec3_t target, dir, ideal_viewangles; - bot_moveresult_t moveresult; - int targetvisible; - bsp_trace_t bsptrace; - aas_entityinfo_t entinfo; - - if (BotIsObserver(bs)) { - BotClearActivateGoalStack(bs); - AIEnter_Observer(bs, "active entity: observer"); - return qfalse; - } - //if in the intermission - if (BotIntermission(bs)) { - BotClearActivateGoalStack(bs); - AIEnter_Intermission(bs, "activate entity: intermission"); - return qfalse; - } - //respawn if dead - if (BotIsDead(bs)) { - BotClearActivateGoalStack(bs); - AIEnter_Respawn(bs, "activate entity: bot dead"); - return qfalse; - } - // - bs->tfl = TFL_DEFAULT; - if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; - // if in lava or slime the bot should be able to get out - if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; - // map specific code - BotMapScripts(bs); - // no enemy - bs->enemy = -1; - // if the bot has no activate goal - if (!bs->activatestack) { - BotClearActivateGoalStack(bs); - AIEnter_Seek_NBG(bs, "activate entity: no goal"); - return qfalse; - } - // - goal = &bs->activatestack->goal; - // initialize target being visible to false - targetvisible = qfalse; - // if the bot has to shoot at a target to activate something - if (bs->activatestack->shoot) { - // - BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, bs->activatestack->target, bs->entitynum, MASK_SHOT); - // if the shootable entity is visible from the current position - if (bsptrace.fraction >= 1.0 || bsptrace.ent == goal->entitynum) { - targetvisible = qtrue; - // if holding the right weapon - if (bs->cur_ps.weapon == bs->activatestack->weapon) { - VectorSubtract(bs->activatestack->target, bs->eye, dir); - vectoangles(dir, ideal_viewangles); - // if the bot is pretty close with it's aim - if (InFieldOfVision(bs->viewangles, 20, ideal_viewangles)) { - trap_EA_Attack(bs->client); - } - } - } - } - // if the shoot target is visible - if (targetvisible) { - // get the entity info of the entity the bot is shooting at - BotEntityInfo(goal->entitynum, &entinfo); - // if the entity the bot shoots at moved - if (!VectorCompare(bs->activatestack->origin, entinfo.origin)) { -#ifdef DEBUG - BotAI_Print(PRT_MESSAGE, "hit shootable button or trigger\n"); -#endif //DEBUG - bs->activatestack->time = 0; - } - // if the activate goal has been activated or the bot takes too long - if (bs->activatestack->time < FloatTime()) { - BotPopFromActivateGoalStack(bs); - // if there are more activate goals on the stack - if (bs->activatestack) { - bs->activatestack->time = FloatTime() + 10; - return qfalse; - } - AIEnter_Seek_NBG(bs, "activate entity: time out"); - return qfalse; - } - memset(&moveresult, 0, sizeof(bot_moveresult_t)); - } - else { - // if the bot has no goal - if (!goal) { - bs->activatestack->time = 0; - } - // if the bot does not have a shoot goal - else if (!bs->activatestack->shoot) { - //if the bot touches the current goal - if (trap_BotTouchingGoal(bs->origin, goal)) { -#ifdef DEBUG - BotAI_Print(PRT_MESSAGE, "touched button or trigger\n"); -#endif //DEBUG - bs->activatestack->time = 0; - } - } - // if the activate goal has been activated or the bot takes too long - if (bs->activatestack->time < FloatTime()) { - BotPopFromActivateGoalStack(bs); - // if there are more activate goals on the stack - if (bs->activatestack) { - bs->activatestack->time = FloatTime() + 10; - return qfalse; - } - AIEnter_Seek_NBG(bs, "activate entity: activated"); - return qfalse; - } - //predict obstacles - if (BotAIPredictObstacles(bs, goal)) - return qfalse; - //initialize the movement state - BotSetupForMovement(bs); - //move towards the goal - trap_BotMoveToGoal(&moveresult, bs->ms, goal, bs->tfl); - //if the movement failed - if (moveresult.failure) { - //reset the avoid reach, otherwise bot is stuck in current area - trap_BotResetAvoidReach(bs->ms); - // - bs->activatestack->time = 0; - } - //check if the bot is blocked - BotAIBlocked(bs, &moveresult, qtrue); - } - // - BotClearPath(bs, &moveresult); - // if the bot has to shoot to activate - if (bs->activatestack->shoot) { - // if the view angles aren't yet used for the movement - if (!(moveresult.flags & MOVERESULT_MOVEMENTVIEW)) { - VectorSubtract(bs->activatestack->target, bs->eye, dir); - vectoangles(dir, moveresult.ideal_viewangles); - moveresult.flags |= MOVERESULT_MOVEMENTVIEW; - } - // if there's no weapon yet used for the movement - if (!(moveresult.flags & MOVERESULT_MOVEMENTWEAPON)) { - moveresult.flags |= MOVERESULT_MOVEMENTWEAPON; - // - bs->activatestack->weapon = BotSelectActivateWeapon(bs); - if (bs->activatestack->weapon == -1) { - //FIXME: find a decent weapon first - bs->activatestack->weapon = 0; - } - moveresult.weapon = bs->activatestack->weapon; - } - } - // if the ideal view angles are set for movement - if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { - VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); - } - // if waiting for something - else if (moveresult.flags & MOVERESULT_WAITING) { - if (random() < bs->thinktime * 0.8) { - BotRoamGoal(bs, target); - VectorSubtract(target, bs->origin, dir); - vectoangles(dir, bs->ideal_viewangles); - bs->ideal_viewangles[2] *= 0.5; - } - } - else if (!(bs->flags & BFL_IDEALVIEWSET)) { - if (trap_BotMovementViewTarget(bs->ms, goal, bs->tfl, 300, target)) { - VectorSubtract(target, bs->origin, dir); - vectoangles(dir, bs->ideal_viewangles); - } - else { - vectoangles(moveresult.movedir, bs->ideal_viewangles); - } - bs->ideal_viewangles[2] *= 0.5; - } - // if the weapon is used for the bot movement - if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) - bs->weaponnum = moveresult.weapon; - // if there is an enemy - if (BotFindEnemy(bs, -1)) { - if (BotWantsToRetreat(bs)) { - //keep the current long term goal and retreat - AIEnter_Battle_NBG(bs, "activate entity: found enemy"); - } - else { - trap_BotResetLastAvoidReach(bs->ms); - //empty the goal stack - trap_BotEmptyGoalStack(bs->gs); - //go fight - AIEnter_Battle_Fight(bs, "activate entity: found enemy"); - } - BotClearActivateGoalStack(bs); - } - return qtrue; -} - -/* -================== -AIEnter_Seek_NBG -================== -*/ -void AIEnter_Seek_NBG(bot_state_t *bs, char *s) { - bot_goal_t goal; - char buf[144]; - - if (trap_BotGetTopGoal(bs->gs, &goal)) { - trap_BotGoalName(goal.number, buf, 144); - BotRecordNodeSwitch(bs, "seek NBG", buf, s); - } - else { - BotRecordNodeSwitch(bs, "seek NBG", "no goal", s); - } - bs->ainode = AINode_Seek_NBG; -} - -/* -================== -AINode_Seek_NBG -================== -*/ -int AINode_Seek_NBG(bot_state_t *bs) { - bot_goal_t goal; - vec3_t target, dir; - bot_moveresult_t moveresult; - - if (BotIsObserver(bs)) { - AIEnter_Observer(bs, "seek nbg: observer"); - return qfalse; - } - //if in the intermission - if (BotIntermission(bs)) { - AIEnter_Intermission(bs, "seek nbg: intermision"); - return qfalse; - } - //respawn if dead - if (BotIsDead(bs)) { - AIEnter_Respawn(bs, "seek nbg: bot dead"); - return qfalse; - } - // - bs->tfl = TFL_DEFAULT; - if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; - //if in lava or slime the bot should be able to get out - if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; - // - if (BotCanAndWantsToRocketJump(bs)) { - bs->tfl |= TFL_ROCKETJUMP; - } - //map specific code - BotMapScripts(bs); - //no enemy - bs->enemy = -1; - //if the bot has no goal - if (!trap_BotGetTopGoal(bs->gs, &goal)) bs->nbg_time = 0; - //if the bot touches the current goal - else if (BotReachedGoal(bs, &goal)) { - BotChooseWeapon(bs); - bs->nbg_time = 0; - } - // - if (bs->nbg_time < FloatTime()) { - //pop the current goal from the stack - trap_BotPopGoal(bs->gs); - //check for new nearby items right away - //NOTE: we canNOT reset the check_time to zero because it would create an endless loop of node switches - bs->check_time = FloatTime() + 0.05; - //go back to seek ltg - AIEnter_Seek_LTG(bs, "seek nbg: time out"); - return qfalse; - } - //predict obstacles - if (BotAIPredictObstacles(bs, &goal)) - return qfalse; - //initialize the movement state - BotSetupForMovement(bs); - //move towards the goal - trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl); - //if the movement failed - if (moveresult.failure) { - //reset the avoid reach, otherwise bot is stuck in current area - trap_BotResetAvoidReach(bs->ms); - bs->nbg_time = 0; - } - //check if the bot is blocked - BotAIBlocked(bs, &moveresult, qtrue); - // - BotClearPath(bs, &moveresult); - //if the viewangles are used for the movement - if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { - VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); - } - //if waiting for something - else if (moveresult.flags & MOVERESULT_WAITING) { - if (random() < bs->thinktime * 0.8) { - BotRoamGoal(bs, target); - VectorSubtract(target, bs->origin, dir); - vectoangles(dir, bs->ideal_viewangles); - bs->ideal_viewangles[2] *= 0.5; - } - } - else if (!(bs->flags & BFL_IDEALVIEWSET)) { - if (!trap_BotGetSecondGoal(bs->gs, &goal)) trap_BotGetTopGoal(bs->gs, &goal); - if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) { - VectorSubtract(target, bs->origin, dir); - vectoangles(dir, bs->ideal_viewangles); - } - //FIXME: look at cluster portals? - else vectoangles(moveresult.movedir, bs->ideal_viewangles); - bs->ideal_viewangles[2] *= 0.5; - } - //if the weapon is used for the bot movement - if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; - //if there is an enemy - if (BotFindEnemy(bs, -1)) { - if (BotWantsToRetreat(bs)) { - //keep the current long term goal and retreat - AIEnter_Battle_NBG(bs, "seek nbg: found enemy"); - } - else { - trap_BotResetLastAvoidReach(bs->ms); - //empty the goal stack - trap_BotEmptyGoalStack(bs->gs); - //go fight - AIEnter_Battle_Fight(bs, "seek nbg: found enemy"); - } - } - return qtrue; -} - -/* -================== -AIEnter_Seek_LTG -================== -*/ -void AIEnter_Seek_LTG(bot_state_t *bs, char *s) { - bot_goal_t goal; - char buf[144]; - - if (trap_BotGetTopGoal(bs->gs, &goal)) { - trap_BotGoalName(goal.number, buf, 144); - BotRecordNodeSwitch(bs, "seek LTG", buf, s); - } - else { - BotRecordNodeSwitch(bs, "seek LTG", "no goal", s); - } - bs->ainode = AINode_Seek_LTG; -} - -/* -================== -AINode_Seek_LTG -================== -*/ -int AINode_Seek_LTG(bot_state_t *bs) -{ - bot_goal_t goal; - vec3_t target, dir; - bot_moveresult_t moveresult; - int range; - //char buf[128]; - //bot_goal_t tmpgoal; - - if (BotIsObserver(bs)) { - AIEnter_Observer(bs, "seek ltg: observer"); - return qfalse; - } - //if in the intermission - if (BotIntermission(bs)) { - AIEnter_Intermission(bs, "seek ltg: intermission"); - return qfalse; - } - //respawn if dead - if (BotIsDead(bs)) { - AIEnter_Respawn(bs, "seek ltg: bot dead"); - return qfalse; - } - // - if (BotChat_Random(bs)) { - bs->stand_time = FloatTime() + BotChatTime(bs); - AIEnter_Stand(bs, "seek ltg: random chat"); - return qfalse; - } - // - bs->tfl = TFL_DEFAULT; - if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; - //if in lava or slime the bot should be able to get out - if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; - // - if (BotCanAndWantsToRocketJump(bs)) { - bs->tfl |= TFL_ROCKETJUMP; - } - //map specific code - BotMapScripts(bs); - //no enemy - bs->enemy = -1; - // - if (bs->killedenemy_time > FloatTime() - 2) { - if (random() < bs->thinktime * 1) { - trap_EA_Gesture(bs->client); - } - } - //if there is an enemy - if (BotFindEnemy(bs, -1)) { - if (BotWantsToRetreat(bs)) { - //keep the current long term goal and retreat - AIEnter_Battle_Retreat(bs, "seek ltg: found enemy"); - return qfalse; - } - else { - trap_BotResetLastAvoidReach(bs->ms); - //empty the goal stack - trap_BotEmptyGoalStack(bs->gs); - //go fight - AIEnter_Battle_Fight(bs, "seek ltg: found enemy"); - return qfalse; - } - } - // - BotTeamGoals(bs, qfalse); - //get the current long term goal - if (!BotLongTermGoal(bs, bs->tfl, qfalse, &goal)) { - return qtrue; - } - //check for nearby goals periodicly - if (bs->check_time < FloatTime()) { - bs->check_time = FloatTime() + 0.5; - //check if the bot wants to camp - BotWantsToCamp(bs); - // - if (bs->ltgtype == LTG_DEFENDKEYAREA) range = 400; - else range = 150; - // -#ifdef CTF - if (gametype == GT_CTF) { - //if carrying a flag the bot shouldn't be distracted too much - if (BotCTFCarryingFlag(bs)) - range = 50; - } -#endif //CTF -#ifdef MISSIONPACK - else if (gametype == GT_1FCTF) { - if (Bot1FCTFCarryingFlag(bs)) - range = 50; - } - else if (gametype == GT_HARVESTER) { - if (BotHarvesterCarryingCubes(bs)) - range = 80; - } -#endif - // - if (BotNearbyGoal(bs, bs->tfl, &goal, range)) { - trap_BotResetLastAvoidReach(bs->ms); - //get the goal at the top of the stack - //trap_BotGetTopGoal(bs->gs, &tmpgoal); - //trap_BotGoalName(tmpgoal.number, buf, 144); - //BotAI_Print(PRT_MESSAGE, "new nearby goal %s\n", buf); - //time the bot gets to pick up the nearby goal item - bs->nbg_time = FloatTime() + 4 + range * 0.01; - AIEnter_Seek_NBG(bs, "ltg seek: nbg"); - return qfalse; - } - } - //predict obstacles - if (BotAIPredictObstacles(bs, &goal)) - return qfalse; - //initialize the movement state - BotSetupForMovement(bs); - //move towards the goal - trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl); - //if the movement failed - if (moveresult.failure) { - //reset the avoid reach, otherwise bot is stuck in current area - trap_BotResetAvoidReach(bs->ms); - //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); - bs->ltg_time = 0; - } - // - BotAIBlocked(bs, &moveresult, qtrue); - // - BotClearPath(bs, &moveresult); - //if the viewangles are used for the movement - if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { - VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); - } - //if waiting for something - else if (moveresult.flags & MOVERESULT_WAITING) { - if (random() < bs->thinktime * 0.8) { - BotRoamGoal(bs, target); - VectorSubtract(target, bs->origin, dir); - vectoangles(dir, bs->ideal_viewangles); - bs->ideal_viewangles[2] *= 0.5; - } - } - else if (!(bs->flags & BFL_IDEALVIEWSET)) { - if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) { - VectorSubtract(target, bs->origin, dir); - vectoangles(dir, bs->ideal_viewangles); - } - //FIXME: look at cluster portals? - else if (VectorLengthSquared(moveresult.movedir)) { - vectoangles(moveresult.movedir, bs->ideal_viewangles); - } - else if (random() < bs->thinktime * 0.8) { - BotRoamGoal(bs, target); - VectorSubtract(target, bs->origin, dir); - vectoangles(dir, bs->ideal_viewangles); - bs->ideal_viewangles[2] *= 0.5; - } - bs->ideal_viewangles[2] *= 0.5; - } - //if the weapon is used for the bot movement - if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; - // - return qtrue; -} - -/* -================== -AIEnter_Battle_Fight -================== -*/ -void AIEnter_Battle_Fight(bot_state_t *bs, char *s) { - BotRecordNodeSwitch(bs, "battle fight", "", s); - trap_BotResetLastAvoidReach(bs->ms); - bs->ainode = AINode_Battle_Fight; -} - -/* -================== -AIEnter_Battle_Fight -================== -*/ -void AIEnter_Battle_SuicidalFight(bot_state_t *bs, char *s) { - BotRecordNodeSwitch(bs, "battle fight", "", s); - trap_BotResetLastAvoidReach(bs->ms); - bs->ainode = AINode_Battle_Fight; - bs->flags |= BFL_FIGHTSUICIDAL; -} - -/* -================== -AINode_Battle_Fight -================== -*/ -int AINode_Battle_Fight(bot_state_t *bs) { - int areanum; - vec3_t target; - aas_entityinfo_t entinfo; - bot_moveresult_t moveresult; - - if (BotIsObserver(bs)) { - AIEnter_Observer(bs, "battle fight: observer"); - return qfalse; - } - - //if in the intermission - if (BotIntermission(bs)) { - AIEnter_Intermission(bs, "battle fight: intermission"); - return qfalse; - } - //respawn if dead - if (BotIsDead(bs)) { - AIEnter_Respawn(bs, "battle fight: bot dead"); - return qfalse; - } - //if there is another better enemy - if (BotFindEnemy(bs, bs->enemy)) { -#ifdef DEBUG - BotAI_Print(PRT_MESSAGE, "found new better enemy\n"); -#endif - } - //if no enemy - if (bs->enemy < 0) { - AIEnter_Seek_LTG(bs, "battle fight: no enemy"); - return qfalse; - } - // - BotEntityInfo(bs->enemy, &entinfo); - //if the enemy is dead - if (bs->enemydeath_time) { - if (bs->enemydeath_time < FloatTime() - 1.0) { - bs->enemydeath_time = 0; - if (bs->enemysuicide) { - BotChat_EnemySuicide(bs); - } - if (bs->lastkilledplayer == bs->enemy && BotChat_Kill(bs)) { - bs->stand_time = FloatTime() + BotChatTime(bs); - AIEnter_Stand(bs, "battle fight: enemy dead"); - } - else { - bs->ltg_time = 0; - AIEnter_Seek_LTG(bs, "battle fight: enemy dead"); - } - return qfalse; - } - } - else { - if (EntityIsDead(&entinfo)) { - bs->enemydeath_time = FloatTime(); - } - } - //if the enemy is invisible and not shooting the bot looses track easily - if (EntityIsInvisible(&entinfo) && !EntityIsShooting(&entinfo)) { - if (random() < 0.2) { - AIEnter_Seek_LTG(bs, "battle fight: invisible"); - return qfalse; - } - } - // - VectorCopy(entinfo.origin, target); - // if not a player enemy - if (bs->enemy >= MAX_CLIENTS) { -#ifdef MISSIONPACK - // if attacking an obelisk - if ( bs->enemy == redobelisk.entitynum || - bs->enemy == blueobelisk.entitynum ) { - target[2] += 16; - } -#endif - } - //update the reachability area and origin if possible - areanum = BotPointAreaNum(target); - if (areanum && trap_AAS_AreaReachability(areanum)) { - VectorCopy(target, bs->lastenemyorigin); - bs->lastenemyareanum = areanum; - } - //update the attack inventory values - BotUpdateBattleInventory(bs, bs->enemy); - //if the bot's health decreased - if (bs->lastframe_health > bs->inventory[INVENTORY_HEALTH]) { - if (BotChat_HitNoDeath(bs)) { - bs->stand_time = FloatTime() + BotChatTime(bs); - AIEnter_Stand(bs, "battle fight: chat health decreased"); - return qfalse; - } - } - //if the bot hit someone - if (bs->cur_ps.persistant[PERS_HITS] > bs->lasthitcount) { - if (BotChat_HitNoKill(bs)) { - bs->stand_time = FloatTime() + BotChatTime(bs); - AIEnter_Stand(bs, "battle fight: chat hit someone"); - return qfalse; - } - } - //if the enemy is not visible - if (!BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) { - if (BotWantsToChase(bs)) { - AIEnter_Battle_Chase(bs, "battle fight: enemy out of sight"); - return qfalse; - } - else { - AIEnter_Seek_LTG(bs, "battle fight: enemy out of sight"); - return qfalse; - } - } - //use holdable items - BotBattleUseItems(bs); - // - bs->tfl = TFL_DEFAULT; - if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; - //if in lava or slime the bot should be able to get out - if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; - // - if (BotCanAndWantsToRocketJump(bs)) { - bs->tfl |= TFL_ROCKETJUMP; - } - //choose the best weapon to fight with - BotChooseWeapon(bs); - //do attack movements - moveresult = BotAttackMove(bs, bs->tfl); - //if the movement failed - if (moveresult.failure) { - //reset the avoid reach, otherwise bot is stuck in current area - trap_BotResetAvoidReach(bs->ms); - //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); - bs->ltg_time = 0; - } - // - BotAIBlocked(bs, &moveresult, qfalse); - //aim at the enemy - BotAimAtEnemy(bs); - //attack the enemy if possible - BotCheckAttack(bs); - //if the bot wants to retreat - if (!(bs->flags & BFL_FIGHTSUICIDAL)) { - if (BotWantsToRetreat(bs)) { - AIEnter_Battle_Retreat(bs, "battle fight: wants to retreat"); - return qtrue; - } - } - return qtrue; -} - -/* -================== -AIEnter_Battle_Chase -================== -*/ -void AIEnter_Battle_Chase(bot_state_t *bs, char *s) { - BotRecordNodeSwitch(bs, "battle chase", "", s); - bs->chase_time = FloatTime(); - bs->ainode = AINode_Battle_Chase; -} - -/* -================== -AINode_Battle_Chase -================== -*/ -int AINode_Battle_Chase(bot_state_t *bs) -{ - bot_goal_t goal; - vec3_t target, dir; - bot_moveresult_t moveresult; - float range; - - if (BotIsObserver(bs)) { - AIEnter_Observer(bs, "battle chase: observer"); - return qfalse; - } - //if in the intermission - if (BotIntermission(bs)) { - AIEnter_Intermission(bs, "battle chase: intermission"); - return qfalse; - } - //respawn if dead - if (BotIsDead(bs)) { - AIEnter_Respawn(bs, "battle chase: bot dead"); - return qfalse; - } - //if no enemy - if (bs->enemy < 0) { - AIEnter_Seek_LTG(bs, "battle chase: no enemy"); - return qfalse; - } - //if the enemy is visible - if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) { - AIEnter_Battle_Fight(bs, "battle chase"); - return qfalse; - } - //if there is another enemy - if (BotFindEnemy(bs, -1)) { - AIEnter_Battle_Fight(bs, "battle chase: better enemy"); - return qfalse; - } - //there is no last enemy area - if (!bs->lastenemyareanum) { - AIEnter_Seek_LTG(bs, "battle chase: no enemy area"); - return qfalse; - } - // - bs->tfl = TFL_DEFAULT; - if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; - //if in lava or slime the bot should be able to get out - if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; - // - if (BotCanAndWantsToRocketJump(bs)) { - bs->tfl |= TFL_ROCKETJUMP; - } - //map specific code - BotMapScripts(bs); - //create the chase goal - goal.entitynum = bs->enemy; - goal.areanum = bs->lastenemyareanum; - VectorCopy(bs->lastenemyorigin, goal.origin); - VectorSet(goal.mins, -8, -8, -8); - VectorSet(goal.maxs, 8, 8, 8); - //if the last seen enemy spot is reached the enemy could not be found - if (trap_BotTouchingGoal(bs->origin, &goal)) bs->chase_time = 0; - //if there's no chase time left - if (!bs->chase_time || bs->chase_time < FloatTime() - 10) { - AIEnter_Seek_LTG(bs, "battle chase: time out"); - return qfalse; - } - //check for nearby goals periodicly - if (bs->check_time < FloatTime()) { - bs->check_time = FloatTime() + 1; - range = 150; - // - if (BotNearbyGoal(bs, bs->tfl, &goal, range)) { - //the bot gets 5 seconds to pick up the nearby goal item - bs->nbg_time = FloatTime() + 0.1 * range + 1; - trap_BotResetLastAvoidReach(bs->ms); - AIEnter_Battle_NBG(bs, "battle chase: nbg"); - return qfalse; - } - } - // - BotUpdateBattleInventory(bs, bs->enemy); - //initialize the movement state - BotSetupForMovement(bs); - //move towards the goal - trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl); - //if the movement failed - if (moveresult.failure) { - //reset the avoid reach, otherwise bot is stuck in current area - trap_BotResetAvoidReach(bs->ms); - //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); - bs->ltg_time = 0; - } - // - BotAIBlocked(bs, &moveresult, qfalse); - // - if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { - VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); - } - else if (!(bs->flags & BFL_IDEALVIEWSET)) { - if (bs->chase_time > FloatTime() - 2) { - BotAimAtEnemy(bs); - } - else { - if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) { - VectorSubtract(target, bs->origin, dir); - vectoangles(dir, bs->ideal_viewangles); - } - else { - vectoangles(moveresult.movedir, bs->ideal_viewangles); - } - } - bs->ideal_viewangles[2] *= 0.5; - } - //if the weapon is used for the bot movement - if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; - //if the bot is in the area the enemy was last seen in - if (bs->areanum == bs->lastenemyareanum) bs->chase_time = 0; - //if the bot wants to retreat (the bot could have been damage during the chase) - if (BotWantsToRetreat(bs)) { - AIEnter_Battle_Retreat(bs, "battle chase: wants to retreat"); - return qtrue; - } - return qtrue; -} - -/* -================== -AIEnter_Battle_Retreat -================== -*/ -void AIEnter_Battle_Retreat(bot_state_t *bs, char *s) { - BotRecordNodeSwitch(bs, "battle retreat", "", s); - bs->ainode = AINode_Battle_Retreat; -} - -/* -================== -AINode_Battle_Retreat -================== -*/ -int AINode_Battle_Retreat(bot_state_t *bs) { - bot_goal_t goal; - aas_entityinfo_t entinfo; - bot_moveresult_t moveresult; - vec3_t target, dir; - float attack_skill, range; - int areanum; - - if (BotIsObserver(bs)) { - AIEnter_Observer(bs, "battle retreat: observer"); - return qfalse; - } - //if in the intermission - if (BotIntermission(bs)) { - AIEnter_Intermission(bs, "battle retreat: intermission"); - return qfalse; - } - //respawn if dead - if (BotIsDead(bs)) { - AIEnter_Respawn(bs, "battle retreat: bot dead"); - return qfalse; - } - //if no enemy - if (bs->enemy < 0) { - AIEnter_Seek_LTG(bs, "battle retreat: no enemy"); - return qfalse; - } - // - BotEntityInfo(bs->enemy, &entinfo); - if (EntityIsDead(&entinfo)) { - AIEnter_Seek_LTG(bs, "battle retreat: enemy dead"); - return qfalse; - } - //if there is another better enemy - if (BotFindEnemy(bs, bs->enemy)) { -#ifdef DEBUG - BotAI_Print(PRT_MESSAGE, "found new better enemy\n"); -#endif - } - // - bs->tfl = TFL_DEFAULT; - if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; - //if in lava or slime the bot should be able to get out - if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; - //map specific code - BotMapScripts(bs); - //update the attack inventory values - BotUpdateBattleInventory(bs, bs->enemy); - //if the bot doesn't want to retreat anymore... probably picked up some nice items - if (BotWantsToChase(bs)) { - //empty the goal stack, when chasing, only the enemy is the goal - trap_BotEmptyGoalStack(bs->gs); - //go chase the enemy - AIEnter_Battle_Chase(bs, "battle retreat: wants to chase"); - return qfalse; - } - //update the last time the enemy was visible - if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) { - bs->enemyvisible_time = FloatTime(); - VectorCopy(entinfo.origin, target); - // if not a player enemy - if (bs->enemy >= MAX_CLIENTS) { -#ifdef MISSIONPACK - // if attacking an obelisk - if ( bs->enemy == redobelisk.entitynum || - bs->enemy == blueobelisk.entitynum ) { - target[2] += 16; - } -#endif - } - //update the reachability area and origin if possible - areanum = BotPointAreaNum(target); - if (areanum && trap_AAS_AreaReachability(areanum)) { - VectorCopy(target, bs->lastenemyorigin); - bs->lastenemyareanum = areanum; - } - } - //if the enemy is NOT visible for 4 seconds - if (bs->enemyvisible_time < FloatTime() - 4) { - AIEnter_Seek_LTG(bs, "battle retreat: lost enemy"); - return qfalse; - } - //else if the enemy is NOT visible - else if (bs->enemyvisible_time < FloatTime()) { - //if there is another enemy - if (BotFindEnemy(bs, -1)) { - AIEnter_Battle_Fight(bs, "battle retreat: another enemy"); - return qfalse; - } - } - // - BotTeamGoals(bs, qtrue); - //use holdable items - BotBattleUseItems(bs); - //get the current long term goal while retreating - if (!BotLongTermGoal(bs, bs->tfl, qtrue, &goal)) { - AIEnter_Battle_SuicidalFight(bs, "battle retreat: no way out"); - return qfalse; - } - //check for nearby goals periodicly - if (bs->check_time < FloatTime()) { - bs->check_time = FloatTime() + 1; - range = 150; -#ifdef CTF - if (gametype == GT_CTF) { - //if carrying a flag the bot shouldn't be distracted too much - if (BotCTFCarryingFlag(bs)) - range = 50; - } -#endif //CTF -#ifdef MISSIONPACK - else if (gametype == GT_1FCTF) { - if (Bot1FCTFCarryingFlag(bs)) - range = 50; - } - else if (gametype == GT_HARVESTER) { - if (BotHarvesterCarryingCubes(bs)) - range = 80; - } -#endif - // - if (BotNearbyGoal(bs, bs->tfl, &goal, range)) { - trap_BotResetLastAvoidReach(bs->ms); - //time the bot gets to pick up the nearby goal item - bs->nbg_time = FloatTime() + range / 100 + 1; - AIEnter_Battle_NBG(bs, "battle retreat: nbg"); - return qfalse; - } - } - //initialize the movement state - BotSetupForMovement(bs); - //move towards the goal - trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl); - //if the movement failed - if (moveresult.failure) { - //reset the avoid reach, otherwise bot is stuck in current area - trap_BotResetAvoidReach(bs->ms); - //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); - bs->ltg_time = 0; - } - // - BotAIBlocked(bs, &moveresult, qfalse); - //choose the best weapon to fight with - BotChooseWeapon(bs); - //if the view is fixed for the movement - if (moveresult.flags & (MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { - VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); - } - else if (!(moveresult.flags & MOVERESULT_MOVEMENTVIEWSET) - && !(bs->flags & BFL_IDEALVIEWSET) ) { - attack_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ATTACK_SKILL, 0, 1); - //if the bot is skilled anough - if (attack_skill > 0.3) { - BotAimAtEnemy(bs); - } - else { - if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) { - VectorSubtract(target, bs->origin, dir); - vectoangles(dir, bs->ideal_viewangles); - } - else { - vectoangles(moveresult.movedir, bs->ideal_viewangles); - } - bs->ideal_viewangles[2] *= 0.5; - } - } - //if the weapon is used for the bot movement - if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; - //attack the enemy if possible - BotCheckAttack(bs); - // - return qtrue; -} - -/* -================== -AIEnter_Battle_NBG -================== -*/ -void AIEnter_Battle_NBG(bot_state_t *bs, char *s) { - BotRecordNodeSwitch(bs, "battle NBG", "", s); - bs->ainode = AINode_Battle_NBG; -} - -/* -================== -AINode_Battle_NBG -================== -*/ -int AINode_Battle_NBG(bot_state_t *bs) { - int areanum; - bot_goal_t goal; - aas_entityinfo_t entinfo; - bot_moveresult_t moveresult; - float attack_skill; - vec3_t target, dir; - - if (BotIsObserver(bs)) { - AIEnter_Observer(bs, "battle nbg: observer"); - return qfalse; - } - //if in the intermission - if (BotIntermission(bs)) { - AIEnter_Intermission(bs, "battle nbg: intermission"); - return qfalse; - } - //respawn if dead - if (BotIsDead(bs)) { - AIEnter_Respawn(bs, "battle nbg: bot dead"); - return qfalse; - } - //if no enemy - if (bs->enemy < 0) { - AIEnter_Seek_NBG(bs, "battle nbg: no enemy"); - return qfalse; - } - // - BotEntityInfo(bs->enemy, &entinfo); - if (EntityIsDead(&entinfo)) { - AIEnter_Seek_NBG(bs, "battle nbg: enemy dead"); - return qfalse; - } - // - bs->tfl = TFL_DEFAULT; - if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; - //if in lava or slime the bot should be able to get out - if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; - // - if (BotCanAndWantsToRocketJump(bs)) { - bs->tfl |= TFL_ROCKETJUMP; - } - //map specific code - BotMapScripts(bs); - //update the last time the enemy was visible - if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) { - bs->enemyvisible_time = FloatTime(); - VectorCopy(entinfo.origin, target); - // if not a player enemy - if (bs->enemy >= MAX_CLIENTS) { -#ifdef MISSIONPACK - // if attacking an obelisk - if ( bs->enemy == redobelisk.entitynum || - bs->enemy == blueobelisk.entitynum ) { - target[2] += 16; - } -#endif - } - //update the reachability area and origin if possible - areanum = BotPointAreaNum(target); - if (areanum && trap_AAS_AreaReachability(areanum)) { - VectorCopy(target, bs->lastenemyorigin); - bs->lastenemyareanum = areanum; - } - } - //if the bot has no goal or touches the current goal - if (!trap_BotGetTopGoal(bs->gs, &goal)) { - bs->nbg_time = 0; - } - else if (BotReachedGoal(bs, &goal)) { - bs->nbg_time = 0; - } - // - if (bs->nbg_time < FloatTime()) { - //pop the current goal from the stack - trap_BotPopGoal(bs->gs); - //if the bot still has a goal - if (trap_BotGetTopGoal(bs->gs, &goal)) - AIEnter_Battle_Retreat(bs, "battle nbg: time out"); - else - AIEnter_Battle_Fight(bs, "battle nbg: time out"); - // - return qfalse; - } - //initialize the movement state - BotSetupForMovement(bs); - //move towards the goal - trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl); - //if the movement failed - if (moveresult.failure) { - //reset the avoid reach, otherwise bot is stuck in current area - trap_BotResetAvoidReach(bs->ms); - //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); - bs->nbg_time = 0; - } - // - BotAIBlocked(bs, &moveresult, qfalse); - //update the attack inventory values - BotUpdateBattleInventory(bs, bs->enemy); - //choose the best weapon to fight with - BotChooseWeapon(bs); - //if the view is fixed for the movement - if (moveresult.flags & (MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { - VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); - } - else if (!(moveresult.flags & MOVERESULT_MOVEMENTVIEWSET) - && !(bs->flags & BFL_IDEALVIEWSET)) { - attack_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ATTACK_SKILL, 0, 1); - //if the bot is skilled anough and the enemy is visible - if (attack_skill > 0.3) { - //&& BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy) - BotAimAtEnemy(bs); - } - else { - if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) { - VectorSubtract(target, bs->origin, dir); - vectoangles(dir, bs->ideal_viewangles); - } - else { - vectoangles(moveresult.movedir, bs->ideal_viewangles); - } - bs->ideal_viewangles[2] *= 0.5; - } - } - //if the weapon is used for the bot movement - if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; - //attack the enemy if possible - BotCheckAttack(bs); - // - return qtrue; -} - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: ai_dmnet.c + * + * desc: Quake3 bot AI + * + * $Archive: /MissionPack/code/game/ai_dmnet.c $ + * + *****************************************************************************/ + +#include "g_local.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_ea.h" +#include "be_ai_char.h" +#include "be_ai_chat.h" +#include "be_ai_gen.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" +#include "be_ai_weap.h" +// +#include "ai_main.h" +#include "ai_dmq3.h" +#include "ai_chat.h" +#include "ai_cmd.h" +#include "ai_dmnet.h" +#include "ai_team.h" +//data file headers +#include "chars.h" //characteristics +#include "inv.h" //indexes into the inventory +#include "syn.h" //synonyms +#include "match.h" //string matching types and vars + +// for the voice chats +#include "../../ui/menudef.h" + +//goal flag, see be_ai_goal.h for the other GFL_* +#define GFL_AIR 128 + +int numnodeswitches; +char nodeswitch[MAX_NODESWITCHES+1][144]; + +#define LOOKAHEAD_DISTANCE 300 + +/* +================== +BotResetNodeSwitches +================== +*/ +void BotResetNodeSwitches(void) { + numnodeswitches = 0; +} + +/* +================== +BotDumpNodeSwitches +================== +*/ +void BotDumpNodeSwitches(bot_state_t *bs) { + int i; + char netname[MAX_NETNAME]; + + ClientName(bs->client, netname, sizeof(netname)); + BotAI_Print(PRT_MESSAGE, "%s at %1.1f switched more than %d AI nodes\n", netname, FloatTime(), MAX_NODESWITCHES); + for (i = 0; i < numnodeswitches; i++) { + BotAI_Print(PRT_MESSAGE, nodeswitch[i]); + } + BotAI_Print(PRT_FATAL, ""); +} + +/* +================== +BotRecordNodeSwitch +================== +*/ +void BotRecordNodeSwitch(bot_state_t *bs, char *node, char *str, char *s) { + char netname[MAX_NETNAME]; + + ClientName(bs->client, netname, sizeof(netname)); + Com_sprintf(nodeswitch[numnodeswitches], 144, "%s at %2.1f entered %s: %s from %s\n", netname, FloatTime(), node, str, s); +#ifdef DEBUG + if (0) { + BotAI_Print(PRT_MESSAGE, nodeswitch[numnodeswitches]); + } +#endif //DEBUG + numnodeswitches++; +} + +/* +================== +BotGetAirGoal +================== +*/ +int BotGetAirGoal(bot_state_t *bs, bot_goal_t *goal) { + bsp_trace_t bsptrace; + vec3_t end, mins = {-15, -15, -2}, maxs = {15, 15, 2}; + int areanum; + + //trace up until we hit solid + VectorCopy(bs->origin, end); + end[2] += 1000; + BotAI_Trace(&bsptrace, bs->origin, mins, maxs, end, bs->entitynum, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); + //trace down until we hit water + VectorCopy(bsptrace.endpos, end); + BotAI_Trace(&bsptrace, end, mins, maxs, bs->origin, bs->entitynum, CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA); + //if we found the water surface + if (bsptrace.fraction > 0) { + areanum = BotPointAreaNum(bsptrace.endpos); + if (areanum) { + VectorCopy(bsptrace.endpos, goal->origin); + goal->origin[2] -= 2; + goal->areanum = areanum; + goal->mins[0] = -15; + goal->mins[1] = -15; + goal->mins[2] = -1; + goal->maxs[0] = 15; + goal->maxs[1] = 15; + goal->maxs[2] = 1; + goal->flags = GFL_AIR; + goal->number = 0; + goal->iteminfo = 0; + goal->entitynum = 0; + return qtrue; + } + } + return qfalse; +} + +/* +================== +BotGoForAir +================== +*/ +int BotGoForAir(bot_state_t *bs, int tfl, bot_goal_t *ltg, float range) { + bot_goal_t goal; + + //if the bot needs air + if (bs->lastair_time < FloatTime() - 6) { + // +#ifdef DEBUG + //BotAI_Print(PRT_MESSAGE, "going for air\n"); +#endif //DEBUG + //if we can find an air goal + if (BotGetAirGoal(bs, &goal)) { + trap_BotPushGoal(bs->gs, &goal); + return qtrue; + } + else { + //get a nearby goal outside the water + while(trap_BotChooseNBGItem(bs->gs, bs->origin, bs->inventory, tfl, ltg, range)) { + trap_BotGetTopGoal(bs->gs, &goal); + //if the goal is not in water + if (!(trap_AAS_PointContents(goal.origin) & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA))) { + return qtrue; + } + trap_BotPopGoal(bs->gs); + } + trap_BotResetAvoidGoals(bs->gs); + } + } + return qfalse; +} + +/* +================== +BotNearbyGoal +================== +*/ +int BotNearbyGoal(bot_state_t *bs, int tfl, bot_goal_t *ltg, float range) { + int ret; + + //check if the bot should go for air + if (BotGoForAir(bs, tfl, ltg, range)) return qtrue; + //if the bot is carrying the enemy flag + if (BotCTFCarryingFlag(bs)) { + //if the bot is just a few secs away from the base + if (trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, + bs->teamgoal.areanum, TFL_DEFAULT) < 300) { + //make the range really small + range = 50; + } + } + // + ret = trap_BotChooseNBGItem(bs->gs, bs->origin, bs->inventory, tfl, ltg, range); + /* + if (ret) + { + char buf[128]; + //get the goal at the top of the stack + trap_BotGetTopGoal(bs->gs, &goal); + trap_BotGoalName(goal.number, buf, sizeof(buf)); + BotAI_Print(PRT_MESSAGE, "%1.1f: new nearby goal %s\n", FloatTime(), buf); + } + */ + return ret; +} + +/* +================== +BotReachedGoal +================== +*/ +int BotReachedGoal(bot_state_t *bs, bot_goal_t *goal) { + if (goal->flags & GFL_ITEM) { + //if touching the goal + if (trap_BotTouchingGoal(bs->origin, goal)) { + if (!(goal->flags & GFL_DROPPED)) { + trap_BotSetAvoidGoalTime(bs->gs, goal->number, -1); + } + return qtrue; + } + //if the goal isn't there + if (trap_BotItemGoalInVisButNotVisible(bs->entitynum, bs->eye, bs->viewangles, goal)) { + /* + float avoidtime; + int t; + + avoidtime = trap_BotAvoidGoalTime(bs->gs, goal->number); + if (avoidtime > 0) { + t = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, goal->areanum, bs->tfl); + if ((float) t * 0.009 < avoidtime) + return qtrue; + } + */ + return qtrue; + } + //if in the goal area and below or above the goal and not swimming + if (bs->areanum == goal->areanum) { + if (bs->origin[0] > goal->origin[0] + goal->mins[0] && bs->origin[0] < goal->origin[0] + goal->maxs[0]) { + if (bs->origin[1] > goal->origin[1] + goal->mins[1] && bs->origin[1] < goal->origin[1] + goal->maxs[1]) { + if (!trap_AAS_Swimming(bs->origin)) { + return qtrue; + } + } + } + } + } + else if (goal->flags & GFL_AIR) { + //if touching the goal + if (trap_BotTouchingGoal(bs->origin, goal)) return qtrue; + //if the bot got air + if (bs->lastair_time > FloatTime() - 1) return qtrue; + } + else { + //if touching the goal + if (trap_BotTouchingGoal(bs->origin, goal)) return qtrue; + } + return qfalse; +} + +/* +================== +BotGetItemLongTermGoal +================== +*/ +int BotGetItemLongTermGoal(bot_state_t *bs, int tfl, bot_goal_t *goal) { + //if the bot has no goal + if (!trap_BotGetTopGoal(bs->gs, goal)) { + //BotAI_Print(PRT_MESSAGE, "no ltg on stack\n"); + bs->ltg_time = 0; + } + //if the bot touches the current goal + else if (BotReachedGoal(bs, goal)) { + BotChooseWeapon(bs); + bs->ltg_time = 0; + } + //if it is time to find a new long term goal + if (bs->ltg_time < FloatTime()) { + //pop the current goal from the stack + trap_BotPopGoal(bs->gs); + //BotAI_Print(PRT_MESSAGE, "%s: choosing new ltg\n", ClientName(bs->client, netname, sizeof(netname))); + //choose a new goal + //BotAI_Print(PRT_MESSAGE, "%6.1f client %d: BotChooseLTGItem\n", FloatTime(), bs->client); + if (trap_BotChooseLTGItem(bs->gs, bs->origin, bs->inventory, tfl)) { + /* + char buf[128]; + //get the goal at the top of the stack + trap_BotGetTopGoal(bs->gs, goal); + trap_BotGoalName(goal->number, buf, sizeof(buf)); + BotAI_Print(PRT_MESSAGE, "%1.1f: new long term goal %s\n", FloatTime(), buf); + */ + bs->ltg_time = FloatTime() + 20; + } + else {//the bot gets sorta stuck with all the avoid timings, shouldn't happen though + // +#ifdef DEBUG + char netname[128]; + + BotAI_Print(PRT_MESSAGE, "%s: no valid ltg (probably stuck)\n", ClientName(bs->client, netname, sizeof(netname))); +#endif + //trap_BotDumpAvoidGoals(bs->gs); + //reset the avoid goals and the avoid reach + trap_BotResetAvoidGoals(bs->gs); + trap_BotResetAvoidReach(bs->ms); + } + //get the goal at the top of the stack + return trap_BotGetTopGoal(bs->gs, goal); + } + return qtrue; +} + +/* +================== +BotGetLongTermGoal + +we could also create a seperate AI node for every long term goal type +however this saves us a lot of code +================== +*/ +int BotGetLongTermGoal(bot_state_t *bs, int tfl, int retreat, bot_goal_t *goal) { + vec3_t target, dir, dir2; + char netname[MAX_NETNAME]; + char buf[MAX_MESSAGE_SIZE]; + int areanum; + float croucher; + aas_entityinfo_t entinfo, botinfo; + bot_waypoint_t *wp; + + if (bs->ltgtype == LTG_TEAMHELP && !retreat) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "help_start", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); + trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES); + trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); + bs->teammessage_time = 0; + } + //if trying to help the team mate for more than a minute + if (bs->teamgoal_time < FloatTime()) + bs->ltgtype = 0; + //if the team mate IS visible for quite some time + if (bs->teammatevisible_time < FloatTime() - 10) bs->ltgtype = 0; + //get entity information of the companion + BotEntityInfo(bs->teammate, &entinfo); + //if the team mate is visible + if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->teammate)) { + //if close just stand still there + VectorSubtract(entinfo.origin, bs->origin, dir); + if (VectorLengthSquared(dir) < Square(100)) { + trap_BotResetAvoidReach(bs->ms); + return qfalse; + } + } + else { + //last time the bot was NOT visible + bs->teammatevisible_time = FloatTime(); + } + //if the entity information is valid (entity in PVS) + if (entinfo.valid) { + areanum = BotPointAreaNum(entinfo.origin); + if (areanum && trap_AAS_AreaReachability(areanum)) { + //update team goal + bs->teamgoal.entitynum = bs->teammate; + bs->teamgoal.areanum = areanum; + VectorCopy(entinfo.origin, bs->teamgoal.origin); + VectorSet(bs->teamgoal.mins, -8, -8, -8); + VectorSet(bs->teamgoal.maxs, 8, 8, 8); + } + } + memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t)); + return qtrue; + } + //if the bot accompanies someone + if (bs->ltgtype == LTG_TEAMACCOMPANY && !retreat) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "accompany_start", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); + trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES); + trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); + bs->teammessage_time = 0; + } + //if accompanying the companion for 3 minutes + if (bs->teamgoal_time < FloatTime()) { + BotAI_BotInitialChat(bs, "accompany_stop", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); + trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); + bs->ltgtype = 0; + } + //get entity information of the companion + BotEntityInfo(bs->teammate, &entinfo); + //if the companion is visible + if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->teammate)) { + //update visible time + bs->teammatevisible_time = FloatTime(); + VectorSubtract(entinfo.origin, bs->origin, dir); + if (VectorLengthSquared(dir) < Square(bs->formation_dist)) { + // + // if the client being followed bumps into this bot then + // the bot should back up + BotEntityInfo(bs->entitynum, &botinfo); + // if the followed client is not standing ontop of the bot + if (botinfo.origin[2] + botinfo.maxs[2] > entinfo.origin[2] + entinfo.mins[2]) { + // if the bounding boxes touch each other + if (botinfo.origin[0] + botinfo.maxs[0] > entinfo.origin[0] + entinfo.mins[0] - 4&& + botinfo.origin[0] + botinfo.mins[0] < entinfo.origin[0] + entinfo.maxs[0] + 4) { + if (botinfo.origin[1] + botinfo.maxs[1] > entinfo.origin[1] + entinfo.mins[1] - 4 && + botinfo.origin[1] + botinfo.mins[1] < entinfo.origin[1] + entinfo.maxs[1] + 4) { + if (botinfo.origin[2] + botinfo.maxs[2] > entinfo.origin[2] + entinfo.mins[2] - 4 && + botinfo.origin[2] + botinfo.mins[2] < entinfo.origin[2] + entinfo.maxs[2] + 4) { + // if the followed client looks in the direction of this bot + AngleVectors(entinfo.angles, dir, NULL, NULL); + dir[2] = 0; + VectorNormalize(dir); + //VectorSubtract(entinfo.origin, entinfo.lastvisorigin, dir); + VectorSubtract(bs->origin, entinfo.origin, dir2); + VectorNormalize(dir2); + if (DotProduct(dir, dir2) > 0.7) { + // back up + BotSetupForMovement(bs); + trap_BotMoveInDirection(bs->ms, dir2, 400, MOVE_WALK); + } + } + } + } + } + //check if the bot wants to crouch + //don't crouch if crouched less than 5 seconds ago + if (bs->attackcrouch_time < FloatTime() - 5) { + croucher = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CROUCHER, 0, 1); + if (random() < bs->thinktime * croucher) { + bs->attackcrouch_time = FloatTime() + 5 + croucher * 15; + } + } + //don't crouch when swimming + if (trap_AAS_Swimming(bs->origin)) bs->attackcrouch_time = FloatTime() - 1; + //if not arrived yet or arived some time ago + if (bs->arrive_time < FloatTime() - 2) { + //if not arrived yet + if (!bs->arrive_time) { + trap_EA_Gesture(bs->client); + BotAI_BotInitialChat(bs, "accompany_arrive", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); + trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); + bs->arrive_time = FloatTime(); + } + //if the bot wants to crouch + else if (bs->attackcrouch_time > FloatTime()) { + trap_EA_Crouch(bs->client); + } + //else do some model taunts + else if (random() < bs->thinktime * 0.05) { + //do a gesture :) + trap_EA_Gesture(bs->client); + } + } + //if just arrived look at the companion + if (bs->arrive_time > FloatTime() - 2) { + VectorSubtract(entinfo.origin, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + } + //else look strategically around for enemies + else if (random() < bs->thinktime * 0.8) { + BotRoamGoal(bs, target); + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + } + //check if the bot wants to go for air + if (BotGoForAir(bs, bs->tfl, &bs->teamgoal, 400)) { + trap_BotResetLastAvoidReach(bs->ms); + //get the goal at the top of the stack + //trap_BotGetTopGoal(bs->gs, &tmpgoal); + //trap_BotGoalName(tmpgoal.number, buf, 144); + //BotAI_Print(PRT_MESSAGE, "new nearby goal %s\n", buf); + //time the bot gets to pick up the nearby goal item + bs->nbg_time = FloatTime() + 8; + AIEnter_Seek_NBG(bs, "BotLongTermGoal: go for air"); + return qfalse; + } + // + trap_BotResetAvoidReach(bs->ms); + return qfalse; + } + } + //if the entity information is valid (entity in PVS) + if (entinfo.valid) { + areanum = BotPointAreaNum(entinfo.origin); + if (areanum && trap_AAS_AreaReachability(areanum)) { + //update team goal + bs->teamgoal.entitynum = bs->teammate; + bs->teamgoal.areanum = areanum; + VectorCopy(entinfo.origin, bs->teamgoal.origin); + VectorSet(bs->teamgoal.mins, -8, -8, -8); + VectorSet(bs->teamgoal.maxs, 8, 8, 8); + } + } + //the goal the bot should go for + memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t)); + //if the companion is NOT visible for too long + if (bs->teammatevisible_time < FloatTime() - 60) { + BotAI_BotInitialChat(bs, "accompany_cannotfind", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); + trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); + bs->ltgtype = 0; + // just to make sure the bot won't spam this message + bs->teammatevisible_time = FloatTime(); + } + return qtrue; + } + // + if (bs->ltgtype == LTG_DEFENDKEYAREA) { + if (trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, + bs->teamgoal.areanum, TFL_DEFAULT) > bs->defendaway_range) { + bs->defendaway_time = 0; + } + } + //if defending a key area + if (bs->ltgtype == LTG_DEFENDKEYAREA && !retreat && + bs->defendaway_time < FloatTime()) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf)); + BotAI_BotInitialChat(bs, "defend_start", buf, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotVoiceChatOnly(bs, -1, VOICECHAT_ONDEFENSE); + bs->teammessage_time = 0; + } + //set the bot goal + memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t)); + //stop after 2 minutes + if (bs->teamgoal_time < FloatTime()) { + trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf)); + BotAI_BotInitialChat(bs, "defend_stop", buf, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + bs->ltgtype = 0; + } + //if very close... go away for some time + VectorSubtract(goal->origin, bs->origin, dir); + if (VectorLengthSquared(dir) < Square(70)) { + trap_BotResetAvoidReach(bs->ms); + bs->defendaway_time = FloatTime() + 3 + 3 * random(); + if (BotHasPersistantPowerupAndWeapon(bs)) { + bs->defendaway_range = 100; + } + else { + bs->defendaway_range = 350; + } + } + return qtrue; + } + //going to kill someone + if (bs->ltgtype == LTG_KILL && !retreat) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + EasyClientName(bs->teamgoal.entitynum, buf, sizeof(buf)); + BotAI_BotInitialChat(bs, "kill_start", buf, NULL); + trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + bs->teammessage_time = 0; + } + // + if (bs->lastkilledplayer == bs->teamgoal.entitynum) { + EasyClientName(bs->teamgoal.entitynum, buf, sizeof(buf)); + BotAI_BotInitialChat(bs, "kill_done", buf, NULL); + trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + bs->lastkilledplayer = -1; + bs->ltgtype = 0; + } + // + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + //just roam around + return BotGetItemLongTermGoal(bs, tfl, goal); + } + //get an item + if (bs->ltgtype == LTG_GETITEM && !retreat) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf)); + BotAI_BotInitialChat(bs, "getitem_start", buf, NULL); + trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES); + trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); + bs->teammessage_time = 0; + } + //set the bot goal + memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t)); + //stop after some time + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + // + if (trap_BotItemGoalInVisButNotVisible(bs->entitynum, bs->eye, bs->viewangles, goal)) { + trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf)); + BotAI_BotInitialChat(bs, "getitem_notthere", buf, NULL); + trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + bs->ltgtype = 0; + } + else if (BotReachedGoal(bs, goal)) { + trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf)); + BotAI_BotInitialChat(bs, "getitem_gotit", buf, NULL); + trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + bs->ltgtype = 0; + } + return qtrue; + } + //if camping somewhere + if ((bs->ltgtype == LTG_CAMP || bs->ltgtype == LTG_CAMPORDER) && !retreat) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + if (bs->ltgtype == LTG_CAMPORDER) { + BotAI_BotInitialChat(bs, "camp_start", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); + trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES); + trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); + } + bs->teammessage_time = 0; + } + //set the bot goal + memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t)); + // + if (bs->teamgoal_time < FloatTime()) { + if (bs->ltgtype == LTG_CAMPORDER) { + BotAI_BotInitialChat(bs, "camp_stop", NULL); + trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + } + bs->ltgtype = 0; + } + //if really near the camp spot + VectorSubtract(goal->origin, bs->origin, dir); + if (VectorLengthSquared(dir) < Square(60)) + { + //if not arrived yet + if (!bs->arrive_time) { + if (bs->ltgtype == LTG_CAMPORDER) { + BotAI_BotInitialChat(bs, "camp_arrive", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); + trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_INPOSITION); + } + bs->arrive_time = FloatTime(); + } + //look strategically around for enemies + if (random() < bs->thinktime * 0.8) { + BotRoamGoal(bs, target); + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + } + //check if the bot wants to crouch + //don't crouch if crouched less than 5 seconds ago + if (bs->attackcrouch_time < FloatTime() - 5) { + croucher = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CROUCHER, 0, 1); + if (random() < bs->thinktime * croucher) { + bs->attackcrouch_time = FloatTime() + 5 + croucher * 15; + } + } + //if the bot wants to crouch + if (bs->attackcrouch_time > FloatTime()) { + trap_EA_Crouch(bs->client); + } + //don't crouch when swimming + if (trap_AAS_Swimming(bs->origin)) bs->attackcrouch_time = FloatTime() - 1; + //make sure the bot is not gonna drown + if (trap_PointContents(bs->eye,bs->entitynum) & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA)) { + if (bs->ltgtype == LTG_CAMPORDER) { + BotAI_BotInitialChat(bs, "camp_stop", NULL); + trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + // + if (bs->lastgoal_ltgtype == LTG_CAMPORDER) { + bs->lastgoal_ltgtype = 0; + } + } + bs->ltgtype = 0; + } + // + if (bs->camp_range > 0) { + //FIXME: move around a bit + } + // + trap_BotResetAvoidReach(bs->ms); + return qfalse; + } + return qtrue; + } + //patrolling along several waypoints + if (bs->ltgtype == LTG_PATROL && !retreat) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + strcpy(buf, ""); + for (wp = bs->patrolpoints; wp; wp = wp->next) { + strcat(buf, wp->name); + if (wp->next) strcat(buf, " to "); + } + BotAI_BotInitialChat(bs, "patrol_start", buf, NULL); + trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES); + trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); + bs->teammessage_time = 0; + } + // + if (!bs->curpatrolpoint) { + bs->ltgtype = 0; + return qfalse; + } + //if the bot touches the current goal + if (trap_BotTouchingGoal(bs->origin, &bs->curpatrolpoint->goal)) { + if (bs->patrolflags & PATROL_BACK) { + if (bs->curpatrolpoint->prev) { + bs->curpatrolpoint = bs->curpatrolpoint->prev; + } + else { + bs->curpatrolpoint = bs->curpatrolpoint->next; + bs->patrolflags &= ~PATROL_BACK; + } + } + else { + if (bs->curpatrolpoint->next) { + bs->curpatrolpoint = bs->curpatrolpoint->next; + } + else { + bs->curpatrolpoint = bs->curpatrolpoint->prev; + bs->patrolflags |= PATROL_BACK; + } + } + } + //stop after 5 minutes + if (bs->teamgoal_time < FloatTime()) { + BotAI_BotInitialChat(bs, "patrol_stop", NULL); + trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + bs->ltgtype = 0; + } + if (!bs->curpatrolpoint) { + bs->ltgtype = 0; + return qfalse; + } + memcpy(goal, &bs->curpatrolpoint->goal, sizeof(bot_goal_t)); + return qtrue; + } +#ifdef CTF + if (gametype == GT_CTF) { + //if going for enemy flag + if (bs->ltgtype == LTG_GETFLAG) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "captureflag_start", NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotVoiceChatOnly(bs, -1, VOICECHAT_ONGETFLAG); + bs->teammessage_time = 0; + } + // + switch(BotTeam(bs)) { + case TEAM_RED: memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t)); break; + case TEAM_BLUE: memcpy(goal, &ctf_redflag, sizeof(bot_goal_t)); break; + default: bs->ltgtype = 0; return qfalse; + } + //if touching the flag + if (trap_BotTouchingGoal(bs->origin, goal)) { + // make sure the bot knows the flag isn't there anymore + switch(BotTeam(bs)) { + case TEAM_RED: bs->blueflagstatus = 1; break; + case TEAM_BLUE: bs->redflagstatus = 1; break; + } + bs->ltgtype = 0; + } + //stop after 3 minutes + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + BotAlternateRoute(bs, goal); + return qtrue; + } + //if rushing to the base + if (bs->ltgtype == LTG_RUSHBASE && bs->rushbaseaway_time < FloatTime()) { + switch(BotTeam(bs)) { + case TEAM_RED: memcpy(goal, &ctf_redflag, sizeof(bot_goal_t)); break; + case TEAM_BLUE: memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t)); break; + default: bs->ltgtype = 0; return qfalse; + } + //if not carrying the flag anymore + if (!BotCTFCarryingFlag(bs)) bs->ltgtype = 0; + //quit rushing after 2 minutes + if (bs->teamgoal_time < FloatTime()) bs->ltgtype = 0; + //if touching the base flag the bot should loose the enemy flag + if (trap_BotTouchingGoal(bs->origin, goal)) { + //if the bot is still carrying the enemy flag then the + //base flag is gone, now just walk near the base a bit + if (BotCTFCarryingFlag(bs)) { + trap_BotResetAvoidReach(bs->ms); + bs->rushbaseaway_time = FloatTime() + 5 + 10 * random(); + //FIXME: add chat to tell the others to get back the flag + } + else { + bs->ltgtype = 0; + } + } + BotAlternateRoute(bs, goal); + return qtrue; + } + //returning flag + if (bs->ltgtype == LTG_RETURNFLAG) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "returnflag_start", NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotVoiceChatOnly(bs, -1, VOICECHAT_ONRETURNFLAG); + bs->teammessage_time = 0; + } + // + switch(BotTeam(bs)) { + case TEAM_RED: memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t)); break; + case TEAM_BLUE: memcpy(goal, &ctf_redflag, sizeof(bot_goal_t)); break; + default: bs->ltgtype = 0; return qfalse; + } + //if touching the flag + if (trap_BotTouchingGoal(bs->origin, goal)) bs->ltgtype = 0; + //stop after 3 minutes + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + BotAlternateRoute(bs, goal); + return qtrue; + } + } +#endif //CTF +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + if (bs->ltgtype == LTG_GETFLAG) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "captureflag_start", NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotVoiceChatOnly(bs, -1, VOICECHAT_ONGETFLAG); + bs->teammessage_time = 0; + } + memcpy(goal, &ctf_neutralflag, sizeof(bot_goal_t)); + //if touching the flag + if (trap_BotTouchingGoal(bs->origin, goal)) { + bs->ltgtype = 0; + } + //stop after 3 minutes + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + return qtrue; + } + //if rushing to the base + if (bs->ltgtype == LTG_RUSHBASE) { + switch(BotTeam(bs)) { + case TEAM_RED: memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t)); break; + case TEAM_BLUE: memcpy(goal, &ctf_redflag, sizeof(bot_goal_t)); break; + default: bs->ltgtype = 0; return qfalse; + } + //if not carrying the flag anymore + if (!Bot1FCTFCarryingFlag(bs)) { + bs->ltgtype = 0; + } + //quit rushing after 2 minutes + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + //if touching the base flag the bot should loose the enemy flag + if (trap_BotTouchingGoal(bs->origin, goal)) { + bs->ltgtype = 0; + } + BotAlternateRoute(bs, goal); + return qtrue; + } + //attack the enemy base + if (bs->ltgtype == LTG_ATTACKENEMYBASE && + bs->attackaway_time < FloatTime()) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "attackenemybase_start", NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotVoiceChatOnly(bs, -1, VOICECHAT_ONOFFENSE); + bs->teammessage_time = 0; + } + switch(BotTeam(bs)) { + case TEAM_RED: memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t)); break; + case TEAM_BLUE: memcpy(goal, &ctf_redflag, sizeof(bot_goal_t)); break; + default: bs->ltgtype = 0; return qfalse; + } + //quit rushing after 2 minutes + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + //if touching the base flag the bot should loose the enemy flag + if (trap_BotTouchingGoal(bs->origin, goal)) { + bs->attackaway_time = FloatTime() + 2 + 5 * random(); + } + return qtrue; + } + //returning flag + if (bs->ltgtype == LTG_RETURNFLAG) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "returnflag_start", NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotVoiceChatOnly(bs, -1, VOICECHAT_ONRETURNFLAG); + bs->teammessage_time = 0; + } + // + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + //just roam around + return BotGetItemLongTermGoal(bs, tfl, goal); + } + } + else if (gametype == GT_OBELISK) { + if (bs->ltgtype == LTG_ATTACKENEMYBASE && + bs->attackaway_time < FloatTime()) { + + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "attackenemybase_start", NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotVoiceChatOnly(bs, -1, VOICECHAT_ONOFFENSE); + bs->teammessage_time = 0; + } + switch(BotTeam(bs)) { + case TEAM_RED: memcpy(goal, &blueobelisk, sizeof(bot_goal_t)); break; + case TEAM_BLUE: memcpy(goal, &redobelisk, sizeof(bot_goal_t)); break; + default: bs->ltgtype = 0; return qfalse; + } + //if the bot no longer wants to attack the obelisk + if (BotFeelingBad(bs) > 50) { + return BotGetItemLongTermGoal(bs, tfl, goal); + } + //if touching the obelisk + if (trap_BotTouchingGoal(bs->origin, goal)) { + bs->attackaway_time = FloatTime() + 3 + 5 * random(); + } + // or very close to the obelisk + VectorSubtract(bs->origin, goal->origin, dir); + if (VectorLengthSquared(dir) < Square(60)) { + bs->attackaway_time = FloatTime() + 3 + 5 * random(); + } + //quit rushing after 2 minutes + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + BotAlternateRoute(bs, goal); + //just move towards the obelisk + return qtrue; + } + } + else if (gametype == GT_HARVESTER) { + //if rushing to the base + if (bs->ltgtype == LTG_RUSHBASE) { + switch(BotTeam(bs)) { + case TEAM_RED: memcpy(goal, &blueobelisk, sizeof(bot_goal_t)); break; + case TEAM_BLUE: memcpy(goal, &redobelisk, sizeof(bot_goal_t)); break; + default: BotGoHarvest(bs); return qfalse; + } + //if not carrying any cubes + if (!BotHarvesterCarryingCubes(bs)) { + BotGoHarvest(bs); + return qfalse; + } + //quit rushing after 2 minutes + if (bs->teamgoal_time < FloatTime()) { + BotGoHarvest(bs); + return qfalse; + } + //if touching the base flag the bot should loose the enemy flag + if (trap_BotTouchingGoal(bs->origin, goal)) { + BotGoHarvest(bs); + return qfalse; + } + BotAlternateRoute(bs, goal); + return qtrue; + } + //attack the enemy base + if (bs->ltgtype == LTG_ATTACKENEMYBASE && + bs->attackaway_time < FloatTime()) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "attackenemybase_start", NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotVoiceChatOnly(bs, -1, VOICECHAT_ONOFFENSE); + bs->teammessage_time = 0; + } + switch(BotTeam(bs)) { + case TEAM_RED: memcpy(goal, &blueobelisk, sizeof(bot_goal_t)); break; + case TEAM_BLUE: memcpy(goal, &redobelisk, sizeof(bot_goal_t)); break; + default: bs->ltgtype = 0; return qfalse; + } + //quit rushing after 2 minutes + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + //if touching the base flag the bot should loose the enemy flag + if (trap_BotTouchingGoal(bs->origin, goal)) { + bs->attackaway_time = FloatTime() + 2 + 5 * random(); + } + return qtrue; + } + //harvest cubes + if (bs->ltgtype == LTG_HARVEST && + bs->harvestaway_time < FloatTime()) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "harvest_start", NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotVoiceChatOnly(bs, -1, VOICECHAT_ONOFFENSE); + bs->teammessage_time = 0; + } + memcpy(goal, &neutralobelisk, sizeof(bot_goal_t)); + // + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + // + if (trap_BotTouchingGoal(bs->origin, goal)) { + bs->harvestaway_time = FloatTime() + 4 + 3 * random(); + } + return qtrue; + } + } +#endif + //normal goal stuff + return BotGetItemLongTermGoal(bs, tfl, goal); +} + +/* +================== +BotLongTermGoal +================== +*/ +int BotLongTermGoal(bot_state_t *bs, int tfl, int retreat, bot_goal_t *goal) { + aas_entityinfo_t entinfo; + char teammate[MAX_MESSAGE_SIZE]; + float squaredist; + int areanum; + vec3_t dir; + + //FIXME: also have air long term goals? + // + //if the bot is leading someone and not retreating + if (bs->lead_time > 0 && !retreat) { + if (bs->lead_time < FloatTime()) { + BotAI_BotInitialChat(bs, "lead_stop", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL); + trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); + bs->lead_time = 0; + return BotGetLongTermGoal(bs, tfl, retreat, goal); + } + // + if (bs->leadmessage_time < 0 && -bs->leadmessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "followme", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL); + trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); + bs->leadmessage_time = FloatTime(); + } + //get entity information of the companion + BotEntityInfo(bs->lead_teammate, &entinfo); + // + if (entinfo.valid) { + areanum = BotPointAreaNum(entinfo.origin); + if (areanum && trap_AAS_AreaReachability(areanum)) { + //update team goal + bs->lead_teamgoal.entitynum = bs->lead_teammate; + bs->lead_teamgoal.areanum = areanum; + VectorCopy(entinfo.origin, bs->lead_teamgoal.origin); + VectorSet(bs->lead_teamgoal.mins, -8, -8, -8); + VectorSet(bs->lead_teamgoal.maxs, 8, 8, 8); + } + } + //if the team mate is visible + if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->lead_teammate)) { + bs->leadvisible_time = FloatTime(); + } + //if the team mate is not visible for 1 seconds + if (bs->leadvisible_time < FloatTime() - 1) { + bs->leadbackup_time = FloatTime() + 2; + } + //distance towards the team mate + VectorSubtract(bs->origin, bs->lead_teamgoal.origin, dir); + squaredist = VectorLengthSquared(dir); + //if backing up towards the team mate + if (bs->leadbackup_time > FloatTime()) { + if (bs->leadmessage_time < FloatTime() - 20) { + BotAI_BotInitialChat(bs, "followme", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL); + trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); + bs->leadmessage_time = FloatTime(); + } + //if very close to the team mate + if (squaredist < Square(100)) { + bs->leadbackup_time = 0; + } + //the bot should go back to the team mate + memcpy(goal, &bs->lead_teamgoal, sizeof(bot_goal_t)); + return qtrue; + } + else { + //if quite distant from the team mate + if (squaredist > Square(500)) { + if (bs->leadmessage_time < FloatTime() - 20) { + BotAI_BotInitialChat(bs, "followme", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL); + trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); + bs->leadmessage_time = FloatTime(); + } + //look at the team mate + VectorSubtract(entinfo.origin, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + //just wait for the team mate + return qfalse; + } + } + } + return BotGetLongTermGoal(bs, tfl, retreat, goal); +} + +/* +================== +AIEnter_Intermission +================== +*/ +void AIEnter_Intermission(bot_state_t *bs, char *s) { + BotRecordNodeSwitch(bs, "intermission", "", s); + //reset the bot state + BotResetState(bs); + //check for end level chat + if (BotChat_EndLevel(bs)) { + trap_BotEnterChat(bs->cs, 0, bs->chatto); + } + bs->ainode = AINode_Intermission; +} + +/* +================== +AINode_Intermission +================== +*/ +int AINode_Intermission(bot_state_t *bs) { + //if the intermission ended + if (!BotIntermission(bs)) { + if (BotChat_StartLevel(bs)) { + bs->stand_time = FloatTime() + BotChatTime(bs); + } + else { + bs->stand_time = FloatTime() + 2; + } + AIEnter_Stand(bs, "intermission: chat"); + } + return qtrue; +} + +/* +================== +AIEnter_Observer +================== +*/ +void AIEnter_Observer(bot_state_t *bs, char *s) { + BotRecordNodeSwitch(bs, "observer", "", s); + //reset the bot state + BotResetState(bs); + bs->ainode = AINode_Observer; +} + +/* +================== +AINode_Observer +================== +*/ +int AINode_Observer(bot_state_t *bs) { + //if the bot left observer mode + if (!BotIsObserver(bs)) { + AIEnter_Stand(bs, "observer: left observer"); + } + return qtrue; +} + +/* +================== +AIEnter_Stand +================== +*/ +void AIEnter_Stand(bot_state_t *bs, char *s) { + BotRecordNodeSwitch(bs, "stand", "", s); + bs->standfindenemy_time = FloatTime() + 1; + bs->ainode = AINode_Stand; +} + +/* +================== +AINode_Stand +================== +*/ +int AINode_Stand(bot_state_t *bs) { + + //if the bot's health decreased + if (bs->lastframe_health > bs->inventory[INVENTORY_HEALTH]) { + if (BotChat_HitTalking(bs)) { + bs->standfindenemy_time = FloatTime() + BotChatTime(bs) + 0.1; + bs->stand_time = FloatTime() + BotChatTime(bs) + 0.1; + } + } + if (bs->standfindenemy_time < FloatTime()) { + if (BotFindEnemy(bs, -1)) { + AIEnter_Battle_Fight(bs, "stand: found enemy"); + return qfalse; + } + bs->standfindenemy_time = FloatTime() + 1; + } + // put up chat icon + trap_EA_Talk(bs->client); + // when done standing + if (bs->stand_time < FloatTime()) { + trap_BotEnterChat(bs->cs, 0, bs->chatto); + AIEnter_Seek_LTG(bs, "stand: time out"); + return qfalse; + } + // + return qtrue; +} + +/* +================== +AIEnter_Respawn +================== +*/ +void AIEnter_Respawn(bot_state_t *bs, char *s) { + BotRecordNodeSwitch(bs, "respawn", "", s); + //reset some states + trap_BotResetMoveState(bs->ms); + trap_BotResetGoalState(bs->gs); + trap_BotResetAvoidGoals(bs->gs); + trap_BotResetAvoidReach(bs->ms); + //if the bot wants to chat + if (BotChat_Death(bs)) { + bs->respawn_time = FloatTime() + BotChatTime(bs); + bs->respawnchat_time = FloatTime(); + } + else { + bs->respawn_time = FloatTime() + 1 + random(); + bs->respawnchat_time = 0; + } + //set respawn state + bs->respawn_wait = qfalse; + bs->ainode = AINode_Respawn; +} + +/* +================== +AINode_Respawn +================== +*/ +int AINode_Respawn(bot_state_t *bs) { + // if waiting for the actual respawn + if (bs->respawn_wait) { + if (!BotIsDead(bs)) { + AIEnter_Seek_LTG(bs, "respawn: respawned"); + } + else { + trap_EA_Respawn(bs->client); + } + } + else if (bs->respawn_time < FloatTime()) { + // wait until respawned + bs->respawn_wait = qtrue; + // elementary action respawn + trap_EA_Respawn(bs->client); + // + if (bs->respawnchat_time) { + trap_BotEnterChat(bs->cs, 0, bs->chatto); + bs->enemy = -1; + } + } + if (bs->respawnchat_time && bs->respawnchat_time < FloatTime() - 0.5) { + trap_EA_Talk(bs->client); + } + // + return qtrue; +} + +/* +================== +BotSelectActivateWeapon +================== +*/ +int BotSelectActivateWeapon(bot_state_t *bs) { + // + if (bs->inventory[INVENTORY_MACHINEGUN] > 0 && bs->inventory[INVENTORY_BULLETS] > 0) + return WEAPONINDEX_MACHINEGUN; + else if (bs->inventory[INVENTORY_SHOTGUN] > 0 && bs->inventory[INVENTORY_SHELLS] > 0) + return WEAPONINDEX_SHOTGUN; + else if (bs->inventory[INVENTORY_PLASMAGUN] > 0 && bs->inventory[INVENTORY_CELLS] > 0) + return WEAPONINDEX_PLASMAGUN; + else if (bs->inventory[INVENTORY_LIGHTNING] > 0 && bs->inventory[INVENTORY_LIGHTNINGAMMO] > 0) + return WEAPONINDEX_LIGHTNING; +#ifdef MISSIONPACK + else if (bs->inventory[INVENTORY_CHAINGUN] > 0 && bs->inventory[INVENTORY_BELT] > 0) + return WEAPONINDEX_CHAINGUN; + else if (bs->inventory[INVENTORY_NAILGUN] > 0 && bs->inventory[INVENTORY_NAILS] > 0) + return WEAPONINDEX_NAILGUN; +#endif + else if (bs->inventory[INVENTORY_RAILGUN] > 0 && bs->inventory[INVENTORY_SLUGS] > 0) + return WEAPONINDEX_RAILGUN; + else if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && bs->inventory[INVENTORY_ROCKETS] > 0) + return WEAPONINDEX_ROCKET_LAUNCHER; + else if (bs->inventory[INVENTORY_BFG10K] > 0 && bs->inventory[INVENTORY_BFGAMMO] > 0) + return WEAPONINDEX_BFG; + else { + return -1; + } +} + +/* +================== +BotClearPath + + try to deactivate obstacles like proximity mines on the bot's path +================== +*/ +void BotClearPath(bot_state_t *bs, bot_moveresult_t *moveresult) { + int i, bestmine; + float dist, bestdist; + vec3_t target, dir; + bsp_trace_t bsptrace; + entityState_t state; + + // if there is a dead body wearing kamikze nearby + if (bs->kamikazebody) { + // if the bot's view angles and weapon are not used for movement + if ( !(moveresult->flags & (MOVERESULT_MOVEMENTVIEW | MOVERESULT_MOVEMENTWEAPON)) ) { + // + BotAI_GetEntityState(bs->kamikazebody, &state); + VectorCopy(state.pos.trBase, target); + target[2] += 8; + VectorSubtract(target, bs->eye, dir); + vectoangles(dir, moveresult->ideal_viewangles); + // + moveresult->weapon = BotSelectActivateWeapon(bs); + if (moveresult->weapon == -1) { + // FIXME: run away! + moveresult->weapon = 0; + } + if (moveresult->weapon) { + // + moveresult->flags |= MOVERESULT_MOVEMENTWEAPON | MOVERESULT_MOVEMENTVIEW; + // if holding the right weapon + if (bs->cur_ps.weapon == moveresult->weapon) { + // if the bot is pretty close with it's aim + if (InFieldOfVision(bs->viewangles, 20, moveresult->ideal_viewangles)) { + // + BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, target, bs->entitynum, MASK_SHOT); + // if the mine is visible from the current position + if (bsptrace.fraction >= 1.0 || bsptrace.ent == state.number) { + // shoot at the mine + trap_EA_Attack(bs->client); + } + } + } + } + } + } + if (moveresult->flags & MOVERESULT_BLOCKEDBYAVOIDSPOT) { + bs->blockedbyavoidspot_time = FloatTime() + 5; + } + // if blocked by an avoid spot and the view angles and weapon are used for movement + if (bs->blockedbyavoidspot_time > FloatTime() && + !(moveresult->flags & (MOVERESULT_MOVEMENTVIEW | MOVERESULT_MOVEMENTWEAPON)) ) { + bestdist = 300; + bestmine = -1; + for (i = 0; i < bs->numproxmines; i++) { + BotAI_GetEntityState(bs->proxmines[i], &state); + VectorSubtract(state.pos.trBase, bs->origin, dir); + dist = VectorLength(dir); + if (dist < bestdist) { + bestdist = dist; + bestmine = i; + } + } + if (bestmine != -1) { + // + // state->generic1 == TEAM_RED || state->generic1 == TEAM_BLUE + // + // deactivate prox mines in the bot's path by shooting + // rockets or plasma cells etc. at them + BotAI_GetEntityState(bs->proxmines[bestmine], &state); + VectorCopy(state.pos.trBase, target); + target[2] += 2; + VectorSubtract(target, bs->eye, dir); + vectoangles(dir, moveresult->ideal_viewangles); + // if the bot has a weapon that does splash damage + if (bs->inventory[INVENTORY_PLASMAGUN] > 0 && bs->inventory[INVENTORY_CELLS] > 0) + moveresult->weapon = WEAPONINDEX_PLASMAGUN; + else if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && bs->inventory[INVENTORY_ROCKETS] > 0) + moveresult->weapon = WEAPONINDEX_ROCKET_LAUNCHER; + else if (bs->inventory[INVENTORY_BFG10K] > 0 && bs->inventory[INVENTORY_BFGAMMO] > 0) + moveresult->weapon = WEAPONINDEX_BFG; + else { + moveresult->weapon = 0; + } + if (moveresult->weapon) { + // + moveresult->flags |= MOVERESULT_MOVEMENTWEAPON | MOVERESULT_MOVEMENTVIEW; + // if holding the right weapon + if (bs->cur_ps.weapon == moveresult->weapon) { + // if the bot is pretty close with it's aim + if (InFieldOfVision(bs->viewangles, 20, moveresult->ideal_viewangles)) { + // + BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, target, bs->entitynum, MASK_SHOT); + // if the mine is visible from the current position + if (bsptrace.fraction >= 1.0 || bsptrace.ent == state.number) { + // shoot at the mine + trap_EA_Attack(bs->client); + } + } + } + } + } + } +} + +/* +================== +AIEnter_Seek_ActivateEntity +================== +*/ +void AIEnter_Seek_ActivateEntity(bot_state_t *bs, char *s) { + BotRecordNodeSwitch(bs, "activate entity", "", s); + bs->ainode = AINode_Seek_ActivateEntity; +} + +/* +================== +AINode_Seek_Activate_Entity +================== +*/ +int AINode_Seek_ActivateEntity(bot_state_t *bs) { + bot_goal_t *goal; + vec3_t target, dir, ideal_viewangles; + bot_moveresult_t moveresult; + int targetvisible; + bsp_trace_t bsptrace; + aas_entityinfo_t entinfo; + + if (BotIsObserver(bs)) { + BotClearActivateGoalStack(bs); + AIEnter_Observer(bs, "active entity: observer"); + return qfalse; + } + //if in the intermission + if (BotIntermission(bs)) { + BotClearActivateGoalStack(bs); + AIEnter_Intermission(bs, "activate entity: intermission"); + return qfalse; + } + //respawn if dead + if (BotIsDead(bs)) { + BotClearActivateGoalStack(bs); + AIEnter_Respawn(bs, "activate entity: bot dead"); + return qfalse; + } + // + bs->tfl = TFL_DEFAULT; + if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; + // if in lava or slime the bot should be able to get out + if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; + // map specific code + BotMapScripts(bs); + // no enemy + bs->enemy = -1; + // if the bot has no activate goal + if (!bs->activatestack) { + BotClearActivateGoalStack(bs); + AIEnter_Seek_NBG(bs, "activate entity: no goal"); + return qfalse; + } + // + goal = &bs->activatestack->goal; + // initialize target being visible to false + targetvisible = qfalse; + // if the bot has to shoot at a target to activate something + if (bs->activatestack->shoot) { + // + BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, bs->activatestack->target, bs->entitynum, MASK_SHOT); + // if the shootable entity is visible from the current position + if (bsptrace.fraction >= 1.0 || bsptrace.ent == goal->entitynum) { + targetvisible = qtrue; + // if holding the right weapon + if (bs->cur_ps.weapon == bs->activatestack->weapon) { + VectorSubtract(bs->activatestack->target, bs->eye, dir); + vectoangles(dir, ideal_viewangles); + // if the bot is pretty close with it's aim + if (InFieldOfVision(bs->viewangles, 20, ideal_viewangles)) { + trap_EA_Attack(bs->client); + } + } + } + } + // if the shoot target is visible + if (targetvisible) { + // get the entity info of the entity the bot is shooting at + BotEntityInfo(goal->entitynum, &entinfo); + // if the entity the bot shoots at moved + if (!VectorCompare(bs->activatestack->origin, entinfo.origin)) { +#ifdef DEBUG + BotAI_Print(PRT_MESSAGE, "hit shootable button or trigger\n"); +#endif //DEBUG + bs->activatestack->time = 0; + } + // if the activate goal has been activated or the bot takes too long + if (bs->activatestack->time < FloatTime()) { + BotPopFromActivateGoalStack(bs); + // if there are more activate goals on the stack + if (bs->activatestack) { + bs->activatestack->time = FloatTime() + 10; + return qfalse; + } + AIEnter_Seek_NBG(bs, "activate entity: time out"); + return qfalse; + } + memset(&moveresult, 0, sizeof(bot_moveresult_t)); + } + else { + // if the bot has no goal + if (!goal) { + bs->activatestack->time = 0; + } + // if the bot does not have a shoot goal + else if (!bs->activatestack->shoot) { + //if the bot touches the current goal + if (trap_BotTouchingGoal(bs->origin, goal)) { +#ifdef DEBUG + BotAI_Print(PRT_MESSAGE, "touched button or trigger\n"); +#endif //DEBUG + bs->activatestack->time = 0; + } + } + // if the activate goal has been activated or the bot takes too long + if (bs->activatestack->time < FloatTime()) { + BotPopFromActivateGoalStack(bs); + // if there are more activate goals on the stack + if (bs->activatestack) { + bs->activatestack->time = FloatTime() + 10; + return qfalse; + } + AIEnter_Seek_NBG(bs, "activate entity: activated"); + return qfalse; + } + //predict obstacles + if (BotAIPredictObstacles(bs, goal)) + return qfalse; + //initialize the movement state + BotSetupForMovement(bs); + //move towards the goal + trap_BotMoveToGoal(&moveresult, bs->ms, goal, bs->tfl); + //if the movement failed + if (moveresult.failure) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach(bs->ms); + // + bs->activatestack->time = 0; + } + //check if the bot is blocked + BotAIBlocked(bs, &moveresult, qtrue); + } + // + BotClearPath(bs, &moveresult); + // if the bot has to shoot to activate + if (bs->activatestack->shoot) { + // if the view angles aren't yet used for the movement + if (!(moveresult.flags & MOVERESULT_MOVEMENTVIEW)) { + VectorSubtract(bs->activatestack->target, bs->eye, dir); + vectoangles(dir, moveresult.ideal_viewangles); + moveresult.flags |= MOVERESULT_MOVEMENTVIEW; + } + // if there's no weapon yet used for the movement + if (!(moveresult.flags & MOVERESULT_MOVEMENTWEAPON)) { + moveresult.flags |= MOVERESULT_MOVEMENTWEAPON; + // + bs->activatestack->weapon = BotSelectActivateWeapon(bs); + if (bs->activatestack->weapon == -1) { + //FIXME: find a decent weapon first + bs->activatestack->weapon = 0; + } + moveresult.weapon = bs->activatestack->weapon; + } + } + // if the ideal view angles are set for movement + if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { + VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); + } + // if waiting for something + else if (moveresult.flags & MOVERESULT_WAITING) { + if (random() < bs->thinktime * 0.8) { + BotRoamGoal(bs, target); + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + } + } + else if (!(bs->flags & BFL_IDEALVIEWSET)) { + if (trap_BotMovementViewTarget(bs->ms, goal, bs->tfl, 300, target)) { + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + } + else { + vectoangles(moveresult.movedir, bs->ideal_viewangles); + } + bs->ideal_viewangles[2] *= 0.5; + } + // if the weapon is used for the bot movement + if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) + bs->weaponnum = moveresult.weapon; + // if there is an enemy + if (BotFindEnemy(bs, -1)) { + if (BotWantsToRetreat(bs)) { + //keep the current long term goal and retreat + AIEnter_Battle_NBG(bs, "activate entity: found enemy"); + } + else { + trap_BotResetLastAvoidReach(bs->ms); + //empty the goal stack + trap_BotEmptyGoalStack(bs->gs); + //go fight + AIEnter_Battle_Fight(bs, "activate entity: found enemy"); + } + BotClearActivateGoalStack(bs); + } + return qtrue; +} + +/* +================== +AIEnter_Seek_NBG +================== +*/ +void AIEnter_Seek_NBG(bot_state_t *bs, char *s) { + bot_goal_t goal; + char buf[144]; + + if (trap_BotGetTopGoal(bs->gs, &goal)) { + trap_BotGoalName(goal.number, buf, 144); + BotRecordNodeSwitch(bs, "seek NBG", buf, s); + } + else { + BotRecordNodeSwitch(bs, "seek NBG", "no goal", s); + } + bs->ainode = AINode_Seek_NBG; +} + +/* +================== +AINode_Seek_NBG +================== +*/ +int AINode_Seek_NBG(bot_state_t *bs) { + bot_goal_t goal; + vec3_t target, dir; + bot_moveresult_t moveresult; + + if (BotIsObserver(bs)) { + AIEnter_Observer(bs, "seek nbg: observer"); + return qfalse; + } + //if in the intermission + if (BotIntermission(bs)) { + AIEnter_Intermission(bs, "seek nbg: intermision"); + return qfalse; + } + //respawn if dead + if (BotIsDead(bs)) { + AIEnter_Respawn(bs, "seek nbg: bot dead"); + return qfalse; + } + // + bs->tfl = TFL_DEFAULT; + if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; + //if in lava or slime the bot should be able to get out + if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; + // + if (BotCanAndWantsToRocketJump(bs)) { + bs->tfl |= TFL_ROCKETJUMP; + } + //map specific code + BotMapScripts(bs); + //no enemy + bs->enemy = -1; + //if the bot has no goal + if (!trap_BotGetTopGoal(bs->gs, &goal)) bs->nbg_time = 0; + //if the bot touches the current goal + else if (BotReachedGoal(bs, &goal)) { + BotChooseWeapon(bs); + bs->nbg_time = 0; + } + // + if (bs->nbg_time < FloatTime()) { + //pop the current goal from the stack + trap_BotPopGoal(bs->gs); + //check for new nearby items right away + //NOTE: we canNOT reset the check_time to zero because it would create an endless loop of node switches + bs->check_time = FloatTime() + 0.05; + //go back to seek ltg + AIEnter_Seek_LTG(bs, "seek nbg: time out"); + return qfalse; + } + //predict obstacles + if (BotAIPredictObstacles(bs, &goal)) + return qfalse; + //initialize the movement state + BotSetupForMovement(bs); + //move towards the goal + trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl); + //if the movement failed + if (moveresult.failure) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach(bs->ms); + bs->nbg_time = 0; + } + //check if the bot is blocked + BotAIBlocked(bs, &moveresult, qtrue); + // + BotClearPath(bs, &moveresult); + //if the viewangles are used for the movement + if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { + VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); + } + //if waiting for something + else if (moveresult.flags & MOVERESULT_WAITING) { + if (random() < bs->thinktime * 0.8) { + BotRoamGoal(bs, target); + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + } + } + else if (!(bs->flags & BFL_IDEALVIEWSET)) { + if (!trap_BotGetSecondGoal(bs->gs, &goal)) trap_BotGetTopGoal(bs->gs, &goal); + if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) { + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + } + //FIXME: look at cluster portals? + else vectoangles(moveresult.movedir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + } + //if the weapon is used for the bot movement + if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; + //if there is an enemy + if (BotFindEnemy(bs, -1)) { + if (BotWantsToRetreat(bs)) { + //keep the current long term goal and retreat + AIEnter_Battle_NBG(bs, "seek nbg: found enemy"); + } + else { + trap_BotResetLastAvoidReach(bs->ms); + //empty the goal stack + trap_BotEmptyGoalStack(bs->gs); + //go fight + AIEnter_Battle_Fight(bs, "seek nbg: found enemy"); + } + } + return qtrue; +} + +/* +================== +AIEnter_Seek_LTG +================== +*/ +void AIEnter_Seek_LTG(bot_state_t *bs, char *s) { + bot_goal_t goal; + char buf[144]; + + if (trap_BotGetTopGoal(bs->gs, &goal)) { + trap_BotGoalName(goal.number, buf, 144); + BotRecordNodeSwitch(bs, "seek LTG", buf, s); + } + else { + BotRecordNodeSwitch(bs, "seek LTG", "no goal", s); + } + bs->ainode = AINode_Seek_LTG; +} + +/* +================== +AINode_Seek_LTG +================== +*/ +int AINode_Seek_LTG(bot_state_t *bs) +{ + bot_goal_t goal; + vec3_t target, dir; + bot_moveresult_t moveresult; + int range; + //char buf[128]; + //bot_goal_t tmpgoal; + + if (BotIsObserver(bs)) { + AIEnter_Observer(bs, "seek ltg: observer"); + return qfalse; + } + //if in the intermission + if (BotIntermission(bs)) { + AIEnter_Intermission(bs, "seek ltg: intermission"); + return qfalse; + } + //respawn if dead + if (BotIsDead(bs)) { + AIEnter_Respawn(bs, "seek ltg: bot dead"); + return qfalse; + } + // + if (BotChat_Random(bs)) { + bs->stand_time = FloatTime() + BotChatTime(bs); + AIEnter_Stand(bs, "seek ltg: random chat"); + return qfalse; + } + // + bs->tfl = TFL_DEFAULT; + if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; + //if in lava or slime the bot should be able to get out + if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; + // + if (BotCanAndWantsToRocketJump(bs)) { + bs->tfl |= TFL_ROCKETJUMP; + } + //map specific code + BotMapScripts(bs); + //no enemy + bs->enemy = -1; + // + if (bs->killedenemy_time > FloatTime() - 2) { + if (random() < bs->thinktime * 1) { + trap_EA_Gesture(bs->client); + } + } + //if there is an enemy + if (BotFindEnemy(bs, -1)) { + if (BotWantsToRetreat(bs)) { + //keep the current long term goal and retreat + AIEnter_Battle_Retreat(bs, "seek ltg: found enemy"); + return qfalse; + } + else { + trap_BotResetLastAvoidReach(bs->ms); + //empty the goal stack + trap_BotEmptyGoalStack(bs->gs); + //go fight + AIEnter_Battle_Fight(bs, "seek ltg: found enemy"); + return qfalse; + } + } + // + BotTeamGoals(bs, qfalse); + //get the current long term goal + if (!BotLongTermGoal(bs, bs->tfl, qfalse, &goal)) { + return qtrue; + } + //check for nearby goals periodicly + if (bs->check_time < FloatTime()) { + bs->check_time = FloatTime() + 0.5; + //check if the bot wants to camp + BotWantsToCamp(bs); + // + if (bs->ltgtype == LTG_DEFENDKEYAREA) range = 400; + else range = 150; + // +#ifdef CTF + if (gametype == GT_CTF) { + //if carrying a flag the bot shouldn't be distracted too much + if (BotCTFCarryingFlag(bs)) + range = 50; + } +#endif //CTF +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + if (Bot1FCTFCarryingFlag(bs)) + range = 50; + } + else if (gametype == GT_HARVESTER) { + if (BotHarvesterCarryingCubes(bs)) + range = 80; + } +#endif + // + if (BotNearbyGoal(bs, bs->tfl, &goal, range)) { + trap_BotResetLastAvoidReach(bs->ms); + //get the goal at the top of the stack + //trap_BotGetTopGoal(bs->gs, &tmpgoal); + //trap_BotGoalName(tmpgoal.number, buf, 144); + //BotAI_Print(PRT_MESSAGE, "new nearby goal %s\n", buf); + //time the bot gets to pick up the nearby goal item + bs->nbg_time = FloatTime() + 4 + range * 0.01; + AIEnter_Seek_NBG(bs, "ltg seek: nbg"); + return qfalse; + } + } + //predict obstacles + if (BotAIPredictObstacles(bs, &goal)) + return qfalse; + //initialize the movement state + BotSetupForMovement(bs); + //move towards the goal + trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl); + //if the movement failed + if (moveresult.failure) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach(bs->ms); + //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); + bs->ltg_time = 0; + } + // + BotAIBlocked(bs, &moveresult, qtrue); + // + BotClearPath(bs, &moveresult); + //if the viewangles are used for the movement + if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { + VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); + } + //if waiting for something + else if (moveresult.flags & MOVERESULT_WAITING) { + if (random() < bs->thinktime * 0.8) { + BotRoamGoal(bs, target); + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + } + } + else if (!(bs->flags & BFL_IDEALVIEWSET)) { + if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) { + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + } + //FIXME: look at cluster portals? + else if (VectorLengthSquared(moveresult.movedir)) { + vectoangles(moveresult.movedir, bs->ideal_viewangles); + } + else if (random() < bs->thinktime * 0.8) { + BotRoamGoal(bs, target); + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + } + bs->ideal_viewangles[2] *= 0.5; + } + //if the weapon is used for the bot movement + if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; + // + return qtrue; +} + +/* +================== +AIEnter_Battle_Fight +================== +*/ +void AIEnter_Battle_Fight(bot_state_t *bs, char *s) { + BotRecordNodeSwitch(bs, "battle fight", "", s); + trap_BotResetLastAvoidReach(bs->ms); + bs->ainode = AINode_Battle_Fight; +} + +/* +================== +AIEnter_Battle_Fight +================== +*/ +void AIEnter_Battle_SuicidalFight(bot_state_t *bs, char *s) { + BotRecordNodeSwitch(bs, "battle fight", "", s); + trap_BotResetLastAvoidReach(bs->ms); + bs->ainode = AINode_Battle_Fight; + bs->flags |= BFL_FIGHTSUICIDAL; +} + +/* +================== +AINode_Battle_Fight +================== +*/ +int AINode_Battle_Fight(bot_state_t *bs) { + int areanum; + vec3_t target; + aas_entityinfo_t entinfo; + bot_moveresult_t moveresult; + + if (BotIsObserver(bs)) { + AIEnter_Observer(bs, "battle fight: observer"); + return qfalse; + } + + //if in the intermission + if (BotIntermission(bs)) { + AIEnter_Intermission(bs, "battle fight: intermission"); + return qfalse; + } + //respawn if dead + if (BotIsDead(bs)) { + AIEnter_Respawn(bs, "battle fight: bot dead"); + return qfalse; + } + //if there is another better enemy + if (BotFindEnemy(bs, bs->enemy)) { +#ifdef DEBUG + BotAI_Print(PRT_MESSAGE, "found new better enemy\n"); +#endif + } + //if no enemy + if (bs->enemy < 0) { + AIEnter_Seek_LTG(bs, "battle fight: no enemy"); + return qfalse; + } + // + BotEntityInfo(bs->enemy, &entinfo); + //if the enemy is dead + if (bs->enemydeath_time) { + if (bs->enemydeath_time < FloatTime() - 1.0) { + bs->enemydeath_time = 0; + if (bs->enemysuicide) { + BotChat_EnemySuicide(bs); + } + if (bs->lastkilledplayer == bs->enemy && BotChat_Kill(bs)) { + bs->stand_time = FloatTime() + BotChatTime(bs); + AIEnter_Stand(bs, "battle fight: enemy dead"); + } + else { + bs->ltg_time = 0; + AIEnter_Seek_LTG(bs, "battle fight: enemy dead"); + } + return qfalse; + } + } + else { + if (EntityIsDead(&entinfo)) { + bs->enemydeath_time = FloatTime(); + } + } + //if the enemy is invisible and not shooting the bot looses track easily + if (EntityIsInvisible(&entinfo) && !EntityIsShooting(&entinfo)) { + if (random() < 0.2) { + AIEnter_Seek_LTG(bs, "battle fight: invisible"); + return qfalse; + } + } + // + VectorCopy(entinfo.origin, target); + // if not a player enemy + if (bs->enemy >= MAX_CLIENTS) { +#ifdef MISSIONPACK + // if attacking an obelisk + if ( bs->enemy == redobelisk.entitynum || + bs->enemy == blueobelisk.entitynum ) { + target[2] += 16; + } +#endif + } + //update the reachability area and origin if possible + areanum = BotPointAreaNum(target); + if (areanum && trap_AAS_AreaReachability(areanum)) { + VectorCopy(target, bs->lastenemyorigin); + bs->lastenemyareanum = areanum; + } + //update the attack inventory values + BotUpdateBattleInventory(bs, bs->enemy); + //if the bot's health decreased + if (bs->lastframe_health > bs->inventory[INVENTORY_HEALTH]) { + if (BotChat_HitNoDeath(bs)) { + bs->stand_time = FloatTime() + BotChatTime(bs); + AIEnter_Stand(bs, "battle fight: chat health decreased"); + return qfalse; + } + } + //if the bot hit someone + if (bs->cur_ps.persistant[PERS_HITS] > bs->lasthitcount) { + if (BotChat_HitNoKill(bs)) { + bs->stand_time = FloatTime() + BotChatTime(bs); + AIEnter_Stand(bs, "battle fight: chat hit someone"); + return qfalse; + } + } + //if the enemy is not visible + if (!BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) { + if (BotWantsToChase(bs)) { + AIEnter_Battle_Chase(bs, "battle fight: enemy out of sight"); + return qfalse; + } + else { + AIEnter_Seek_LTG(bs, "battle fight: enemy out of sight"); + return qfalse; + } + } + //use holdable items + BotBattleUseItems(bs); + // + bs->tfl = TFL_DEFAULT; + if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; + //if in lava or slime the bot should be able to get out + if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; + // + if (BotCanAndWantsToRocketJump(bs)) { + bs->tfl |= TFL_ROCKETJUMP; + } + //choose the best weapon to fight with + BotChooseWeapon(bs); + //do attack movements + moveresult = BotAttackMove(bs, bs->tfl); + //if the movement failed + if (moveresult.failure) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach(bs->ms); + //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); + bs->ltg_time = 0; + } + // + BotAIBlocked(bs, &moveresult, qfalse); + //aim at the enemy + BotAimAtEnemy(bs); + //attack the enemy if possible + BotCheckAttack(bs); + //if the bot wants to retreat + if (!(bs->flags & BFL_FIGHTSUICIDAL)) { + if (BotWantsToRetreat(bs)) { + AIEnter_Battle_Retreat(bs, "battle fight: wants to retreat"); + return qtrue; + } + } + return qtrue; +} + +/* +================== +AIEnter_Battle_Chase +================== +*/ +void AIEnter_Battle_Chase(bot_state_t *bs, char *s) { + BotRecordNodeSwitch(bs, "battle chase", "", s); + bs->chase_time = FloatTime(); + bs->ainode = AINode_Battle_Chase; +} + +/* +================== +AINode_Battle_Chase +================== +*/ +int AINode_Battle_Chase(bot_state_t *bs) +{ + bot_goal_t goal; + vec3_t target, dir; + bot_moveresult_t moveresult; + float range; + + if (BotIsObserver(bs)) { + AIEnter_Observer(bs, "battle chase: observer"); + return qfalse; + } + //if in the intermission + if (BotIntermission(bs)) { + AIEnter_Intermission(bs, "battle chase: intermission"); + return qfalse; + } + //respawn if dead + if (BotIsDead(bs)) { + AIEnter_Respawn(bs, "battle chase: bot dead"); + return qfalse; + } + //if no enemy + if (bs->enemy < 0) { + AIEnter_Seek_LTG(bs, "battle chase: no enemy"); + return qfalse; + } + //if the enemy is visible + if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) { + AIEnter_Battle_Fight(bs, "battle chase"); + return qfalse; + } + //if there is another enemy + if (BotFindEnemy(bs, -1)) { + AIEnter_Battle_Fight(bs, "battle chase: better enemy"); + return qfalse; + } + //there is no last enemy area + if (!bs->lastenemyareanum) { + AIEnter_Seek_LTG(bs, "battle chase: no enemy area"); + return qfalse; + } + // + bs->tfl = TFL_DEFAULT; + if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; + //if in lava or slime the bot should be able to get out + if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; + // + if (BotCanAndWantsToRocketJump(bs)) { + bs->tfl |= TFL_ROCKETJUMP; + } + //map specific code + BotMapScripts(bs); + //create the chase goal + goal.entitynum = bs->enemy; + goal.areanum = bs->lastenemyareanum; + VectorCopy(bs->lastenemyorigin, goal.origin); + VectorSet(goal.mins, -8, -8, -8); + VectorSet(goal.maxs, 8, 8, 8); + //if the last seen enemy spot is reached the enemy could not be found + if (trap_BotTouchingGoal(bs->origin, &goal)) bs->chase_time = 0; + //if there's no chase time left + if (!bs->chase_time || bs->chase_time < FloatTime() - 10) { + AIEnter_Seek_LTG(bs, "battle chase: time out"); + return qfalse; + } + //check for nearby goals periodicly + if (bs->check_time < FloatTime()) { + bs->check_time = FloatTime() + 1; + range = 150; + // + if (BotNearbyGoal(bs, bs->tfl, &goal, range)) { + //the bot gets 5 seconds to pick up the nearby goal item + bs->nbg_time = FloatTime() + 0.1 * range + 1; + trap_BotResetLastAvoidReach(bs->ms); + AIEnter_Battle_NBG(bs, "battle chase: nbg"); + return qfalse; + } + } + // + BotUpdateBattleInventory(bs, bs->enemy); + //initialize the movement state + BotSetupForMovement(bs); + //move towards the goal + trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl); + //if the movement failed + if (moveresult.failure) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach(bs->ms); + //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); + bs->ltg_time = 0; + } + // + BotAIBlocked(bs, &moveresult, qfalse); + // + if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { + VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); + } + else if (!(bs->flags & BFL_IDEALVIEWSET)) { + if (bs->chase_time > FloatTime() - 2) { + BotAimAtEnemy(bs); + } + else { + if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) { + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + } + else { + vectoangles(moveresult.movedir, bs->ideal_viewangles); + } + } + bs->ideal_viewangles[2] *= 0.5; + } + //if the weapon is used for the bot movement + if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; + //if the bot is in the area the enemy was last seen in + if (bs->areanum == bs->lastenemyareanum) bs->chase_time = 0; + //if the bot wants to retreat (the bot could have been damage during the chase) + if (BotWantsToRetreat(bs)) { + AIEnter_Battle_Retreat(bs, "battle chase: wants to retreat"); + return qtrue; + } + return qtrue; +} + +/* +================== +AIEnter_Battle_Retreat +================== +*/ +void AIEnter_Battle_Retreat(bot_state_t *bs, char *s) { + BotRecordNodeSwitch(bs, "battle retreat", "", s); + bs->ainode = AINode_Battle_Retreat; +} + +/* +================== +AINode_Battle_Retreat +================== +*/ +int AINode_Battle_Retreat(bot_state_t *bs) { + bot_goal_t goal; + aas_entityinfo_t entinfo; + bot_moveresult_t moveresult; + vec3_t target, dir; + float attack_skill, range; + int areanum; + + if (BotIsObserver(bs)) { + AIEnter_Observer(bs, "battle retreat: observer"); + return qfalse; + } + //if in the intermission + if (BotIntermission(bs)) { + AIEnter_Intermission(bs, "battle retreat: intermission"); + return qfalse; + } + //respawn if dead + if (BotIsDead(bs)) { + AIEnter_Respawn(bs, "battle retreat: bot dead"); + return qfalse; + } + //if no enemy + if (bs->enemy < 0) { + AIEnter_Seek_LTG(bs, "battle retreat: no enemy"); + return qfalse; + } + // + BotEntityInfo(bs->enemy, &entinfo); + if (EntityIsDead(&entinfo)) { + AIEnter_Seek_LTG(bs, "battle retreat: enemy dead"); + return qfalse; + } + //if there is another better enemy + if (BotFindEnemy(bs, bs->enemy)) { +#ifdef DEBUG + BotAI_Print(PRT_MESSAGE, "found new better enemy\n"); +#endif + } + // + bs->tfl = TFL_DEFAULT; + if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; + //if in lava or slime the bot should be able to get out + if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; + //map specific code + BotMapScripts(bs); + //update the attack inventory values + BotUpdateBattleInventory(bs, bs->enemy); + //if the bot doesn't want to retreat anymore... probably picked up some nice items + if (BotWantsToChase(bs)) { + //empty the goal stack, when chasing, only the enemy is the goal + trap_BotEmptyGoalStack(bs->gs); + //go chase the enemy + AIEnter_Battle_Chase(bs, "battle retreat: wants to chase"); + return qfalse; + } + //update the last time the enemy was visible + if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) { + bs->enemyvisible_time = FloatTime(); + VectorCopy(entinfo.origin, target); + // if not a player enemy + if (bs->enemy >= MAX_CLIENTS) { +#ifdef MISSIONPACK + // if attacking an obelisk + if ( bs->enemy == redobelisk.entitynum || + bs->enemy == blueobelisk.entitynum ) { + target[2] += 16; + } +#endif + } + //update the reachability area and origin if possible + areanum = BotPointAreaNum(target); + if (areanum && trap_AAS_AreaReachability(areanum)) { + VectorCopy(target, bs->lastenemyorigin); + bs->lastenemyareanum = areanum; + } + } + //if the enemy is NOT visible for 4 seconds + if (bs->enemyvisible_time < FloatTime() - 4) { + AIEnter_Seek_LTG(bs, "battle retreat: lost enemy"); + return qfalse; + } + //else if the enemy is NOT visible + else if (bs->enemyvisible_time < FloatTime()) { + //if there is another enemy + if (BotFindEnemy(bs, -1)) { + AIEnter_Battle_Fight(bs, "battle retreat: another enemy"); + return qfalse; + } + } + // + BotTeamGoals(bs, qtrue); + //use holdable items + BotBattleUseItems(bs); + //get the current long term goal while retreating + if (!BotLongTermGoal(bs, bs->tfl, qtrue, &goal)) { + AIEnter_Battle_SuicidalFight(bs, "battle retreat: no way out"); + return qfalse; + } + //check for nearby goals periodicly + if (bs->check_time < FloatTime()) { + bs->check_time = FloatTime() + 1; + range = 150; +#ifdef CTF + if (gametype == GT_CTF) { + //if carrying a flag the bot shouldn't be distracted too much + if (BotCTFCarryingFlag(bs)) + range = 50; + } +#endif //CTF +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + if (Bot1FCTFCarryingFlag(bs)) + range = 50; + } + else if (gametype == GT_HARVESTER) { + if (BotHarvesterCarryingCubes(bs)) + range = 80; + } +#endif + // + if (BotNearbyGoal(bs, bs->tfl, &goal, range)) { + trap_BotResetLastAvoidReach(bs->ms); + //time the bot gets to pick up the nearby goal item + bs->nbg_time = FloatTime() + range / 100 + 1; + AIEnter_Battle_NBG(bs, "battle retreat: nbg"); + return qfalse; + } + } + //initialize the movement state + BotSetupForMovement(bs); + //move towards the goal + trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl); + //if the movement failed + if (moveresult.failure) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach(bs->ms); + //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); + bs->ltg_time = 0; + } + // + BotAIBlocked(bs, &moveresult, qfalse); + //choose the best weapon to fight with + BotChooseWeapon(bs); + //if the view is fixed for the movement + if (moveresult.flags & (MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { + VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); + } + else if (!(moveresult.flags & MOVERESULT_MOVEMENTVIEWSET) + && !(bs->flags & BFL_IDEALVIEWSET) ) { + attack_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ATTACK_SKILL, 0, 1); + //if the bot is skilled anough + if (attack_skill > 0.3) { + BotAimAtEnemy(bs); + } + else { + if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) { + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + } + else { + vectoangles(moveresult.movedir, bs->ideal_viewangles); + } + bs->ideal_viewangles[2] *= 0.5; + } + } + //if the weapon is used for the bot movement + if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; + //attack the enemy if possible + BotCheckAttack(bs); + // + return qtrue; +} + +/* +================== +AIEnter_Battle_NBG +================== +*/ +void AIEnter_Battle_NBG(bot_state_t *bs, char *s) { + BotRecordNodeSwitch(bs, "battle NBG", "", s); + bs->ainode = AINode_Battle_NBG; +} + +/* +================== +AINode_Battle_NBG +================== +*/ +int AINode_Battle_NBG(bot_state_t *bs) { + int areanum; + bot_goal_t goal; + aas_entityinfo_t entinfo; + bot_moveresult_t moveresult; + float attack_skill; + vec3_t target, dir; + + if (BotIsObserver(bs)) { + AIEnter_Observer(bs, "battle nbg: observer"); + return qfalse; + } + //if in the intermission + if (BotIntermission(bs)) { + AIEnter_Intermission(bs, "battle nbg: intermission"); + return qfalse; + } + //respawn if dead + if (BotIsDead(bs)) { + AIEnter_Respawn(bs, "battle nbg: bot dead"); + return qfalse; + } + //if no enemy + if (bs->enemy < 0) { + AIEnter_Seek_NBG(bs, "battle nbg: no enemy"); + return qfalse; + } + // + BotEntityInfo(bs->enemy, &entinfo); + if (EntityIsDead(&entinfo)) { + AIEnter_Seek_NBG(bs, "battle nbg: enemy dead"); + return qfalse; + } + // + bs->tfl = TFL_DEFAULT; + if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; + //if in lava or slime the bot should be able to get out + if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; + // + if (BotCanAndWantsToRocketJump(bs)) { + bs->tfl |= TFL_ROCKETJUMP; + } + //map specific code + BotMapScripts(bs); + //update the last time the enemy was visible + if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) { + bs->enemyvisible_time = FloatTime(); + VectorCopy(entinfo.origin, target); + // if not a player enemy + if (bs->enemy >= MAX_CLIENTS) { +#ifdef MISSIONPACK + // if attacking an obelisk + if ( bs->enemy == redobelisk.entitynum || + bs->enemy == blueobelisk.entitynum ) { + target[2] += 16; + } +#endif + } + //update the reachability area and origin if possible + areanum = BotPointAreaNum(target); + if (areanum && trap_AAS_AreaReachability(areanum)) { + VectorCopy(target, bs->lastenemyorigin); + bs->lastenemyareanum = areanum; + } + } + //if the bot has no goal or touches the current goal + if (!trap_BotGetTopGoal(bs->gs, &goal)) { + bs->nbg_time = 0; + } + else if (BotReachedGoal(bs, &goal)) { + bs->nbg_time = 0; + } + // + if (bs->nbg_time < FloatTime()) { + //pop the current goal from the stack + trap_BotPopGoal(bs->gs); + //if the bot still has a goal + if (trap_BotGetTopGoal(bs->gs, &goal)) + AIEnter_Battle_Retreat(bs, "battle nbg: time out"); + else + AIEnter_Battle_Fight(bs, "battle nbg: time out"); + // + return qfalse; + } + //initialize the movement state + BotSetupForMovement(bs); + //move towards the goal + trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl); + //if the movement failed + if (moveresult.failure) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach(bs->ms); + //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); + bs->nbg_time = 0; + } + // + BotAIBlocked(bs, &moveresult, qfalse); + //update the attack inventory values + BotUpdateBattleInventory(bs, bs->enemy); + //choose the best weapon to fight with + BotChooseWeapon(bs); + //if the view is fixed for the movement + if (moveresult.flags & (MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { + VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); + } + else if (!(moveresult.flags & MOVERESULT_MOVEMENTVIEWSET) + && !(bs->flags & BFL_IDEALVIEWSET)) { + attack_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ATTACK_SKILL, 0, 1); + //if the bot is skilled anough and the enemy is visible + if (attack_skill > 0.3) { + //&& BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy) + BotAimAtEnemy(bs); + } + else { + if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) { + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + } + else { + vectoangles(moveresult.movedir, bs->ideal_viewangles); + } + bs->ideal_viewangles[2] *= 0.5; + } + } + //if the weapon is used for the bot movement + if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; + //attack the enemy if possible + BotCheckAttack(bs); + // + return qtrue; +} + diff --git a/code/game/ai_dmnet.h b/code/game/ai_dmnet.h index a5c4c9a..0dbd6de 100755 --- a/code/game/ai_dmnet.h +++ b/code/game/ai_dmnet.h @@ -1,61 +1,61 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -/***************************************************************************** - * name: ai_dmnet.h - * - * desc: Quake3 bot AI - * - * $Archive: /source/code/botai/ai_chat.c $ - * - *****************************************************************************/ - -#define MAX_NODESWITCHES 50 - -void AIEnter_Intermission(bot_state_t *bs, char *s); -void AIEnter_Observer(bot_state_t *bs, char *s); -void AIEnter_Respawn(bot_state_t *bs, char *s); -void AIEnter_Stand(bot_state_t *bs, char *s); -void AIEnter_Seek_ActivateEntity(bot_state_t *bs, char *s); -void AIEnter_Seek_NBG(bot_state_t *bs, char *s); -void AIEnter_Seek_LTG(bot_state_t *bs, char *s); -void AIEnter_Seek_Camp(bot_state_t *bs, char *s); -void AIEnter_Battle_Fight(bot_state_t *bs, char *s); -void AIEnter_Battle_Chase(bot_state_t *bs, char *s); -void AIEnter_Battle_Retreat(bot_state_t *bs, char *s); -void AIEnter_Battle_NBG(bot_state_t *bs, char *s); -int AINode_Intermission(bot_state_t *bs); -int AINode_Observer(bot_state_t *bs); -int AINode_Respawn(bot_state_t *bs); -int AINode_Stand(bot_state_t *bs); -int AINode_Seek_ActivateEntity(bot_state_t *bs); -int AINode_Seek_NBG(bot_state_t *bs); -int AINode_Seek_LTG(bot_state_t *bs); -int AINode_Battle_Fight(bot_state_t *bs); -int AINode_Battle_Chase(bot_state_t *bs); -int AINode_Battle_Retreat(bot_state_t *bs); -int AINode_Battle_NBG(bot_state_t *bs); - -void BotResetNodeSwitches(void); -void BotDumpNodeSwitches(bot_state_t *bs); - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: ai_dmnet.h + * + * desc: Quake3 bot AI + * + * $Archive: /source/code/botai/ai_chat.c $ + * + *****************************************************************************/ + +#define MAX_NODESWITCHES 50 + +void AIEnter_Intermission(bot_state_t *bs, char *s); +void AIEnter_Observer(bot_state_t *bs, char *s); +void AIEnter_Respawn(bot_state_t *bs, char *s); +void AIEnter_Stand(bot_state_t *bs, char *s); +void AIEnter_Seek_ActivateEntity(bot_state_t *bs, char *s); +void AIEnter_Seek_NBG(bot_state_t *bs, char *s); +void AIEnter_Seek_LTG(bot_state_t *bs, char *s); +void AIEnter_Seek_Camp(bot_state_t *bs, char *s); +void AIEnter_Battle_Fight(bot_state_t *bs, char *s); +void AIEnter_Battle_Chase(bot_state_t *bs, char *s); +void AIEnter_Battle_Retreat(bot_state_t *bs, char *s); +void AIEnter_Battle_NBG(bot_state_t *bs, char *s); +int AINode_Intermission(bot_state_t *bs); +int AINode_Observer(bot_state_t *bs); +int AINode_Respawn(bot_state_t *bs); +int AINode_Stand(bot_state_t *bs); +int AINode_Seek_ActivateEntity(bot_state_t *bs); +int AINode_Seek_NBG(bot_state_t *bs); +int AINode_Seek_LTG(bot_state_t *bs); +int AINode_Battle_Fight(bot_state_t *bs); +int AINode_Battle_Chase(bot_state_t *bs); +int AINode_Battle_Retreat(bot_state_t *bs); +int AINode_Battle_NBG(bot_state_t *bs); + +void BotResetNodeSwitches(void); +void BotDumpNodeSwitches(bot_state_t *bs); + diff --git a/code/game/ai_dmq3.c b/code/game/ai_dmq3.c index ed7f4c1..c373df8 100755 --- a/code/game/ai_dmq3.c +++ b/code/game/ai_dmq3.c @@ -1,5461 +1,5461 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -/***************************************************************************** - * name: ai_dmq3.c - * - * desc: Quake3 bot AI - * - * $Archive: /MissionPack/code/game/ai_dmq3.c $ - * - *****************************************************************************/ - - -#include "g_local.h" -#include "botlib.h" -#include "be_aas.h" -#include "be_ea.h" -#include "be_ai_char.h" -#include "be_ai_chat.h" -#include "be_ai_gen.h" -#include "be_ai_goal.h" -#include "be_ai_move.h" -#include "be_ai_weap.h" -// -#include "ai_main.h" -#include "ai_dmq3.h" -#include "ai_chat.h" -#include "ai_cmd.h" -#include "ai_dmnet.h" -#include "ai_team.h" -// -#include "chars.h" //characteristics -#include "inv.h" //indexes into the inventory -#include "syn.h" //synonyms -#include "match.h" //string matching types and vars - -// for the voice chats -#include "../../ui/menudef.h" // sos001205 - for q3_ui also - -// from aasfile.h -#define AREACONTENTS_MOVER 1024 -#define AREACONTENTS_MODELNUMSHIFT 24 -#define AREACONTENTS_MAXMODELNUM 0xFF -#define AREACONTENTS_MODELNUM (AREACONTENTS_MAXMODELNUM << AREACONTENTS_MODELNUMSHIFT) - -#define IDEAL_ATTACKDIST 140 - -#define MAX_WAYPOINTS 128 -// -bot_waypoint_t botai_waypoints[MAX_WAYPOINTS]; -bot_waypoint_t *botai_freewaypoints; - -//NOTE: not using a cvars which can be updated because the game should be reloaded anyway -int gametype; //game type -int maxclients; //maximum number of clients - -vmCvar_t bot_grapple; -vmCvar_t bot_rocketjump; -vmCvar_t bot_fastchat; -vmCvar_t bot_nochat; -vmCvar_t bot_testrchat; -vmCvar_t bot_challenge; -vmCvar_t bot_predictobstacles; -vmCvar_t g_spSkill; - -extern vmCvar_t bot_developer; - -vec3_t lastteleport_origin; //last teleport event origin -float lastteleport_time; //last teleport event time -int max_bspmodelindex; //maximum BSP model index - -//CTF flag goals -bot_goal_t ctf_redflag; -bot_goal_t ctf_blueflag; -#ifdef MISSIONPACK -bot_goal_t ctf_neutralflag; -bot_goal_t redobelisk; -bot_goal_t blueobelisk; -bot_goal_t neutralobelisk; -#endif - -#define MAX_ALTROUTEGOALS 32 - -int altroutegoals_setup; -aas_altroutegoal_t red_altroutegoals[MAX_ALTROUTEGOALS]; -int red_numaltroutegoals; -aas_altroutegoal_t blue_altroutegoals[MAX_ALTROUTEGOALS]; -int blue_numaltroutegoals; - - -/* -================== -BotSetUserInfo -================== -*/ -void BotSetUserInfo(bot_state_t *bs, char *key, char *value) { - char userinfo[MAX_INFO_STRING]; - - trap_GetUserinfo(bs->client, userinfo, sizeof(userinfo)); - Info_SetValueForKey(userinfo, key, value); - trap_SetUserinfo(bs->client, userinfo); - ClientUserinfoChanged( bs->client ); -} - -/* -================== -BotCTFCarryingFlag -================== -*/ -int BotCTFCarryingFlag(bot_state_t *bs) { - if (gametype != GT_CTF) return CTF_FLAG_NONE; - - if (bs->inventory[INVENTORY_REDFLAG] > 0) return CTF_FLAG_RED; - else if (bs->inventory[INVENTORY_BLUEFLAG] > 0) return CTF_FLAG_BLUE; - return CTF_FLAG_NONE; -} - -/* -================== -BotTeam -================== -*/ -int BotTeam(bot_state_t *bs) { - char info[1024]; - - if (bs->client < 0 || bs->client >= MAX_CLIENTS) { - //BotAI_Print(PRT_ERROR, "BotCTFTeam: client out of range\n"); - return qfalse; - } - trap_GetConfigstring(CS_PLAYERS+bs->client, info, sizeof(info)); - // - if (atoi(Info_ValueForKey(info, "t")) == TEAM_RED) return TEAM_RED; - else if (atoi(Info_ValueForKey(info, "t")) == TEAM_BLUE) return TEAM_BLUE; - return TEAM_FREE; -} - -/* -================== -BotOppositeTeam -================== -*/ -int BotOppositeTeam(bot_state_t *bs) { - switch(BotTeam(bs)) { - case TEAM_RED: return TEAM_BLUE; - case TEAM_BLUE: return TEAM_RED; - default: return TEAM_FREE; - } -} - -/* -================== -BotEnemyFlag -================== -*/ -bot_goal_t *BotEnemyFlag(bot_state_t *bs) { - if (BotTeam(bs) == TEAM_RED) { - return &ctf_blueflag; - } - else { - return &ctf_redflag; - } -} - -/* -================== -BotTeamFlag -================== -*/ -bot_goal_t *BotTeamFlag(bot_state_t *bs) { - if (BotTeam(bs) == TEAM_RED) { - return &ctf_redflag; - } - else { - return &ctf_blueflag; - } -} - - -/* -================== -EntityIsDead -================== -*/ -qboolean EntityIsDead(aas_entityinfo_t *entinfo) { - playerState_t ps; - - if (entinfo->number >= 0 && entinfo->number < MAX_CLIENTS) { - //retrieve the current client state - BotAI_GetClientState( entinfo->number, &ps ); - if (ps.pm_type != PM_NORMAL) return qtrue; - } - return qfalse; -} - -/* -================== -EntityCarriesFlag -================== -*/ -qboolean EntityCarriesFlag(aas_entityinfo_t *entinfo) { - if ( entinfo->powerups & ( 1 << PW_REDFLAG ) ) - return qtrue; - if ( entinfo->powerups & ( 1 << PW_BLUEFLAG ) ) - return qtrue; -#ifdef MISSIONPACK - if ( entinfo->powerups & ( 1 << PW_NEUTRALFLAG ) ) - return qtrue; -#endif - return qfalse; -} - -/* -================== -EntityIsInvisible -================== -*/ -qboolean EntityIsInvisible(aas_entityinfo_t *entinfo) { - // the flag is always visible - if (EntityCarriesFlag(entinfo)) { - return qfalse; - } - if (entinfo->powerups & (1 << PW_INVIS)) { - return qtrue; - } - return qfalse; -} - -/* -================== -EntityIsShooting -================== -*/ -qboolean EntityIsShooting(aas_entityinfo_t *entinfo) { - if (entinfo->flags & EF_FIRING) { - return qtrue; - } - return qfalse; -} - -/* -================== -EntityIsChatting -================== -*/ -qboolean EntityIsChatting(aas_entityinfo_t *entinfo) { - if (entinfo->flags & EF_TALK) { - return qtrue; - } - return qfalse; -} - -/* -================== -EntityHasQuad -================== -*/ -qboolean EntityHasQuad(aas_entityinfo_t *entinfo) { - if (entinfo->powerups & (1 << PW_QUAD)) { - return qtrue; - } - return qfalse; -} - -#ifdef MISSIONPACK -/* -================== -EntityHasKamikze -================== -*/ -qboolean EntityHasKamikaze(aas_entityinfo_t *entinfo) { - if (entinfo->flags & EF_KAMIKAZE) { - return qtrue; - } - return qfalse; -} - -/* -================== -EntityCarriesCubes -================== -*/ -qboolean EntityCarriesCubes(aas_entityinfo_t *entinfo) { - entityState_t state; - - if (gametype != GT_HARVESTER) - return qfalse; - //FIXME: get this info from the aas_entityinfo_t ? - BotAI_GetEntityState(entinfo->number, &state); - if (state.generic1 > 0) - return qtrue; - return qfalse; -} - -/* -================== -Bot1FCTFCarryingFlag -================== -*/ -int Bot1FCTFCarryingFlag(bot_state_t *bs) { - if (gametype != GT_1FCTF) return qfalse; - - if (bs->inventory[INVENTORY_NEUTRALFLAG] > 0) return qtrue; - return qfalse; -} - -/* -================== -BotHarvesterCarryingCubes -================== -*/ -int BotHarvesterCarryingCubes(bot_state_t *bs) { - if (gametype != GT_HARVESTER) return qfalse; - - if (bs->inventory[INVENTORY_REDCUBE] > 0) return qtrue; - if (bs->inventory[INVENTORY_BLUECUBE] > 0) return qtrue; - return qfalse; -} -#endif - -/* -================== -BotRememberLastOrderedTask -================== -*/ -void BotRememberLastOrderedTask(bot_state_t *bs) { - if (!bs->ordered) { - return; - } - bs->lastgoal_decisionmaker = bs->decisionmaker; - bs->lastgoal_ltgtype = bs->ltgtype; - memcpy(&bs->lastgoal_teamgoal, &bs->teamgoal, sizeof(bot_goal_t)); - bs->lastgoal_teammate = bs->teammate; -} - -/* -================== -BotSetTeamStatus -================== -*/ -void BotSetTeamStatus(bot_state_t *bs) { -#ifdef MISSIONPACK - int teamtask; - aas_entityinfo_t entinfo; - - teamtask = TEAMTASK_PATROL; - - switch(bs->ltgtype) { - case LTG_TEAMHELP: - break; - case LTG_TEAMACCOMPANY: - BotEntityInfo(bs->teammate, &entinfo); - if ( ( (gametype == GT_CTF || gametype == GT_1FCTF) && EntityCarriesFlag(&entinfo)) - || ( gametype == GT_HARVESTER && EntityCarriesCubes(&entinfo)) ) { - teamtask = TEAMTASK_ESCORT; - } - else { - teamtask = TEAMTASK_FOLLOW; - } - break; - case LTG_DEFENDKEYAREA: - teamtask = TEAMTASK_DEFENSE; - break; - case LTG_GETFLAG: - teamtask = TEAMTASK_OFFENSE; - break; - case LTG_RUSHBASE: - teamtask = TEAMTASK_DEFENSE; - break; - case LTG_RETURNFLAG: - teamtask = TEAMTASK_RETRIEVE; - break; - case LTG_CAMP: - case LTG_CAMPORDER: - teamtask = TEAMTASK_CAMP; - break; - case LTG_PATROL: - teamtask = TEAMTASK_PATROL; - break; - case LTG_GETITEM: - teamtask = TEAMTASK_PATROL; - break; - case LTG_KILL: - teamtask = TEAMTASK_PATROL; - break; - case LTG_HARVEST: - teamtask = TEAMTASK_OFFENSE; - break; - case LTG_ATTACKENEMYBASE: - teamtask = TEAMTASK_OFFENSE; - break; - default: - teamtask = TEAMTASK_PATROL; - break; - } - BotSetUserInfo(bs, "teamtask", va("%d", teamtask)); -#endif -} - -/* -================== -BotSetLastOrderedTask -================== -*/ -int BotSetLastOrderedTask(bot_state_t *bs) { - - if (gametype == GT_CTF) { - // don't go back to returning the flag if it's at the base - if ( bs->lastgoal_ltgtype == LTG_RETURNFLAG ) { - if ( BotTeam(bs) == TEAM_RED ) { - if ( bs->redflagstatus == 0 ) { - bs->lastgoal_ltgtype = 0; - } - } - else { - if ( bs->blueflagstatus == 0 ) { - bs->lastgoal_ltgtype = 0; - } - } - } - } - - if ( bs->lastgoal_ltgtype ) { - bs->decisionmaker = bs->lastgoal_decisionmaker; - bs->ordered = qtrue; - bs->ltgtype = bs->lastgoal_ltgtype; - memcpy(&bs->teamgoal, &bs->lastgoal_teamgoal, sizeof(bot_goal_t)); - bs->teammate = bs->lastgoal_teammate; - bs->teamgoal_time = FloatTime() + 300; - BotSetTeamStatus(bs); - // - if ( gametype == GT_CTF ) { - if ( bs->ltgtype == LTG_GETFLAG ) { - bot_goal_t *tb, *eb; - int tt, et; - - tb = BotTeamFlag(bs); - eb = BotEnemyFlag(bs); - tt = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, tb->areanum, TFL_DEFAULT); - et = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, eb->areanum, TFL_DEFAULT); - // if the travel time towards the enemy base is larger than towards our base - if (et > tt) { - //get an alternative route goal towards the enemy base - BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); - } - } - } - return qtrue; - } - return qfalse; -} - -/* -================== -BotRefuseOrder -================== -*/ -void BotRefuseOrder(bot_state_t *bs) { - if (!bs->ordered) - return; - // if the bot was ordered to do something - if ( bs->order_time && bs->order_time > FloatTime() - 10 ) { - trap_EA_Action(bs->client, ACTION_NEGATIVE); - BotVoiceChat(bs, bs->decisionmaker, VOICECHAT_NO); - bs->order_time = 0; - } -} - -/* -================== -BotCTFSeekGoals -================== -*/ -void BotCTFSeekGoals(bot_state_t *bs) { - float rnd, l1, l2; - int flagstatus, c; - vec3_t dir; - aas_entityinfo_t entinfo; - - //when carrying a flag in ctf the bot should rush to the base - if (BotCTFCarryingFlag(bs)) { - //if not already rushing to the base - if (bs->ltgtype != LTG_RUSHBASE) { - BotRefuseOrder(bs); - bs->ltgtype = LTG_RUSHBASE; - bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; - bs->rushbaseaway_time = 0; - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - // - switch(BotTeam(bs)) { - case TEAM_RED: VectorSubtract(bs->origin, ctf_blueflag.origin, dir); break; - case TEAM_BLUE: VectorSubtract(bs->origin, ctf_redflag.origin, dir); break; - default: VectorSet(dir, 999, 999, 999); break; - } - // if the bot picked up the flag very close to the enemy base - if ( VectorLength(dir) < 128 ) { - // get an alternative route goal through the enemy base - BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); - } else { - // don't use any alt route goal, just get the hell out of the base - bs->altroutegoal.areanum = 0; - } - BotSetUserInfo(bs, "teamtask", va("%d", TEAMTASK_OFFENSE)); - BotVoiceChat(bs, -1, VOICECHAT_IHAVEFLAG); - } - else if (bs->rushbaseaway_time > FloatTime()) { - if (BotTeam(bs) == TEAM_RED) flagstatus = bs->redflagstatus; - else flagstatus = bs->blueflagstatus; - //if the flag is back - if (flagstatus == 0) { - bs->rushbaseaway_time = 0; - } - } - return; - } - // if the bot decided to follow someone - if ( bs->ltgtype == LTG_TEAMACCOMPANY && !bs->ordered ) { - // if the team mate being accompanied no longer carries the flag - BotEntityInfo(bs->teammate, &entinfo); - if (!EntityCarriesFlag(&entinfo)) { - bs->ltgtype = 0; - } - } - // - if (BotTeam(bs) == TEAM_RED) flagstatus = bs->redflagstatus * 2 + bs->blueflagstatus; - else flagstatus = bs->blueflagstatus * 2 + bs->redflagstatus; - //if our team has the enemy flag and our flag is at the base - if (flagstatus == 1) { - // - if (bs->owndecision_time < FloatTime()) { - //if Not defending the base already - if (!(bs->ltgtype == LTG_DEFENDKEYAREA && - (bs->teamgoal.number == ctf_redflag.number || - bs->teamgoal.number == ctf_blueflag.number))) { - //if there is a visible team mate flag carrier - c = BotTeamFlagCarrierVisible(bs); - if (c >= 0 && - // and not already following the team mate flag carrier - (bs->ltgtype != LTG_TEAMACCOMPANY || bs->teammate != c)) { - // - BotRefuseOrder(bs); - //follow the flag carrier - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - //the team mate - bs->teammate = c; - //last time the team mate was visible - bs->teammatevisible_time = FloatTime(); - //no message - bs->teammessage_time = 0; - //no arrive message - bs->arrive_time = 1; - // - BotVoiceChat(bs, bs->teammate, VOICECHAT_ONFOLLOW); - //get the team goal time - bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; - bs->ltgtype = LTG_TEAMACCOMPANY; - bs->formation_dist = 3.5 * 32; //3.5 meter - BotSetTeamStatus(bs); - bs->owndecision_time = FloatTime() + 5; - } - } - } - return; - } - //if the enemy has our flag - else if (flagstatus == 2) { - // - if (bs->owndecision_time < FloatTime()) { - //if enemy flag carrier is visible - c = BotEnemyFlagCarrierVisible(bs); - if (c >= 0) { - //FIXME: fight enemy flag carrier - } - //if not already doing something important - if (bs->ltgtype != LTG_GETFLAG && - bs->ltgtype != LTG_RETURNFLAG && - bs->ltgtype != LTG_TEAMHELP && - bs->ltgtype != LTG_TEAMACCOMPANY && - bs->ltgtype != LTG_CAMPORDER && - bs->ltgtype != LTG_PATROL && - bs->ltgtype != LTG_GETITEM) { - - BotRefuseOrder(bs); - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - // - if (random() < 0.5) { - //go for the enemy flag - bs->ltgtype = LTG_GETFLAG; - } - else { - bs->ltgtype = LTG_RETURNFLAG; - } - //no team message - bs->teammessage_time = 0; - //set the time the bot will stop getting the flag - bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME; - //get an alternative route goal towards the enemy base - BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); - // - BotSetTeamStatus(bs); - bs->owndecision_time = FloatTime() + 5; - } - } - return; - } - //if both flags Not at their bases - else if (flagstatus == 3) { - // - if (bs->owndecision_time < FloatTime()) { - // if not trying to return the flag and not following the team flag carrier - if ( bs->ltgtype != LTG_RETURNFLAG && bs->ltgtype != LTG_TEAMACCOMPANY ) { - // - c = BotTeamFlagCarrierVisible(bs); - // if there is a visible team mate flag carrier - if (c >= 0) { - BotRefuseOrder(bs); - //follow the flag carrier - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - //the team mate - bs->teammate = c; - //last time the team mate was visible - bs->teammatevisible_time = FloatTime(); - //no message - bs->teammessage_time = 0; - //no arrive message - bs->arrive_time = 1; - // - BotVoiceChat(bs, bs->teammate, VOICECHAT_ONFOLLOW); - //get the team goal time - bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; - bs->ltgtype = LTG_TEAMACCOMPANY; - bs->formation_dist = 3.5 * 32; //3.5 meter - // - BotSetTeamStatus(bs); - bs->owndecision_time = FloatTime() + 5; - } - else { - BotRefuseOrder(bs); - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - //get the enemy flag - bs->teammessage_time = FloatTime() + 2 * random(); - //get the flag - bs->ltgtype = LTG_RETURNFLAG; - //set the time the bot will stop getting the flag - bs->teamgoal_time = FloatTime() + CTF_RETURNFLAG_TIME; - //get an alternative route goal towards the enemy base - BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); - // - BotSetTeamStatus(bs); - bs->owndecision_time = FloatTime() + 5; - } - } - } - return; - } - // don't just do something wait for the bot team leader to give orders - if (BotTeamLeader(bs)) { - return; - } - // if the bot is ordered to do something - if ( bs->lastgoal_ltgtype ) { - bs->teamgoal_time += 60; - } - // if the bot decided to do something on it's own and has a last ordered goal - if ( !bs->ordered && bs->lastgoal_ltgtype ) { - bs->ltgtype = 0; - } - //if already a CTF or team goal - if (bs->ltgtype == LTG_TEAMHELP || - bs->ltgtype == LTG_TEAMACCOMPANY || - bs->ltgtype == LTG_DEFENDKEYAREA || - bs->ltgtype == LTG_GETFLAG || - bs->ltgtype == LTG_RUSHBASE || - bs->ltgtype == LTG_RETURNFLAG || - bs->ltgtype == LTG_CAMPORDER || - bs->ltgtype == LTG_PATROL || - bs->ltgtype == LTG_GETITEM || - bs->ltgtype == LTG_MAKELOVE_UNDER || - bs->ltgtype == LTG_MAKELOVE_ONTOP) { - return; - } - // - if (BotSetLastOrderedTask(bs)) - return; - // - if (bs->owndecision_time > FloatTime()) - return;; - //if the bot is roaming - if (bs->ctfroam_time > FloatTime()) - return; - //if the bot has anough aggression to decide what to do - if (BotAggression(bs) < 50) - return; - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - // - if (bs->teamtaskpreference & (TEAMTP_ATTACKER|TEAMTP_DEFENDER)) { - if (bs->teamtaskpreference & TEAMTP_ATTACKER) { - l1 = 0.7f; - } - else { - l1 = 0.2f; - } - l2 = 0.9f; - } - else { - l1 = 0.4f; - l2 = 0.7f; - } - //get the flag or defend the base - rnd = random(); - if (rnd < l1 && ctf_redflag.areanum && ctf_blueflag.areanum) { - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - bs->ltgtype = LTG_GETFLAG; - //set the time the bot will stop getting the flag - bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME; - //get an alternative route goal towards the enemy base - BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); - BotSetTeamStatus(bs); - } - else if (rnd < l2 && ctf_redflag.areanum && ctf_blueflag.areanum) { - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - // - if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); - else memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); - //set the ltg type - bs->ltgtype = LTG_DEFENDKEYAREA; - //set the time the bot stops defending the base - bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; - bs->defendaway_time = 0; - BotSetTeamStatus(bs); - } - else { - bs->ltgtype = 0; - //set the time the bot will stop roaming - bs->ctfroam_time = FloatTime() + CTF_ROAM_TIME; - BotSetTeamStatus(bs); - } - bs->owndecision_time = FloatTime() + 5; -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} - -/* -================== -BotCTFRetreatGoals -================== -*/ -void BotCTFRetreatGoals(bot_state_t *bs) { - //when carrying a flag in ctf the bot should rush to the base - if (BotCTFCarryingFlag(bs)) { - //if not already rushing to the base - if (bs->ltgtype != LTG_RUSHBASE) { - BotRefuseOrder(bs); - bs->ltgtype = LTG_RUSHBASE; - bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; - bs->rushbaseaway_time = 0; - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - BotSetTeamStatus(bs); - } - } -} - -#ifdef MISSIONPACK -/* -================== -Bot1FCTFSeekGoals -================== -*/ -void Bot1FCTFSeekGoals(bot_state_t *bs) { - aas_entityinfo_t entinfo; - float rnd, l1, l2; - int c; - - //when carrying a flag in ctf the bot should rush to the base - if (Bot1FCTFCarryingFlag(bs)) { - //if not already rushing to the base - if (bs->ltgtype != LTG_RUSHBASE) { - BotRefuseOrder(bs); - bs->ltgtype = LTG_RUSHBASE; - bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; - bs->rushbaseaway_time = 0; - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - //get an alternative route goal towards the enemy base - BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); - // - BotSetTeamStatus(bs); - BotVoiceChat(bs, -1, VOICECHAT_IHAVEFLAG); - } - return; - } - // if the bot decided to follow someone - if ( bs->ltgtype == LTG_TEAMACCOMPANY && !bs->ordered ) { - // if the team mate being accompanied no longer carries the flag - BotEntityInfo(bs->teammate, &entinfo); - if (!EntityCarriesFlag(&entinfo)) { - bs->ltgtype = 0; - } - } - //our team has the flag - if (bs->neutralflagstatus == 1) { - if (bs->owndecision_time < FloatTime()) { - // if not already following someone - if (bs->ltgtype != LTG_TEAMACCOMPANY) { - //if there is a visible team mate flag carrier - c = BotTeamFlagCarrierVisible(bs); - if (c >= 0) { - BotRefuseOrder(bs); - //follow the flag carrier - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - //the team mate - bs->teammate = c; - //last time the team mate was visible - bs->teammatevisible_time = FloatTime(); - //no message - bs->teammessage_time = 0; - //no arrive message - bs->arrive_time = 1; - // - BotVoiceChat(bs, bs->teammate, VOICECHAT_ONFOLLOW); - //get the team goal time - bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; - bs->ltgtype = LTG_TEAMACCOMPANY; - bs->formation_dist = 3.5 * 32; //3.5 meter - BotSetTeamStatus(bs); - bs->owndecision_time = FloatTime() + 5; - return; - } - } - //if already a CTF or team goal - if (bs->ltgtype == LTG_TEAMHELP || - bs->ltgtype == LTG_TEAMACCOMPANY || - bs->ltgtype == LTG_DEFENDKEYAREA || - bs->ltgtype == LTG_GETFLAG || - bs->ltgtype == LTG_RUSHBASE || - bs->ltgtype == LTG_CAMPORDER || - bs->ltgtype == LTG_PATROL || - bs->ltgtype == LTG_ATTACKENEMYBASE || - bs->ltgtype == LTG_GETITEM || - bs->ltgtype == LTG_MAKELOVE_UNDER || - bs->ltgtype == LTG_MAKELOVE_ONTOP) { - return; - } - //if not already attacking the enemy base - if (bs->ltgtype != LTG_ATTACKENEMYBASE) { - BotRefuseOrder(bs); - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - // - if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); - else memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); - //set the ltg type - bs->ltgtype = LTG_ATTACKENEMYBASE; - //set the time the bot will stop getting the flag - bs->teamgoal_time = FloatTime() + TEAM_ATTACKENEMYBASE_TIME; - BotSetTeamStatus(bs); - bs->owndecision_time = FloatTime() + 5; - } - } - return; - } - //enemy team has the flag - else if (bs->neutralflagstatus == 2) { - if (bs->owndecision_time < FloatTime()) { - c = BotEnemyFlagCarrierVisible(bs); - if (c >= 0) { - //FIXME: attack enemy flag carrier - } - //if already a CTF or team goal - if (bs->ltgtype == LTG_TEAMHELP || - bs->ltgtype == LTG_TEAMACCOMPANY || - bs->ltgtype == LTG_CAMPORDER || - bs->ltgtype == LTG_PATROL || - bs->ltgtype == LTG_GETITEM) { - return; - } - // if not already defending the base - if (bs->ltgtype != LTG_DEFENDKEYAREA) { - BotRefuseOrder(bs); - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - // - if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); - else memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); - //set the ltg type - bs->ltgtype = LTG_DEFENDKEYAREA; - //set the time the bot stops defending the base - bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; - bs->defendaway_time = 0; - BotSetTeamStatus(bs); - bs->owndecision_time = FloatTime() + 5; - } - } - return; - } - // don't just do something wait for the bot team leader to give orders - if (BotTeamLeader(bs)) { - return; - } - // if the bot is ordered to do something - if ( bs->lastgoal_ltgtype ) { - bs->teamgoal_time += 60; - } - // if the bot decided to do something on it's own and has a last ordered goal - if ( !bs->ordered && bs->lastgoal_ltgtype ) { - bs->ltgtype = 0; - } - //if already a CTF or team goal - if (bs->ltgtype == LTG_TEAMHELP || - bs->ltgtype == LTG_TEAMACCOMPANY || - bs->ltgtype == LTG_DEFENDKEYAREA || - bs->ltgtype == LTG_GETFLAG || - bs->ltgtype == LTG_RUSHBASE || - bs->ltgtype == LTG_RETURNFLAG || - bs->ltgtype == LTG_CAMPORDER || - bs->ltgtype == LTG_PATROL || - bs->ltgtype == LTG_ATTACKENEMYBASE || - bs->ltgtype == LTG_GETITEM || - bs->ltgtype == LTG_MAKELOVE_UNDER || - bs->ltgtype == LTG_MAKELOVE_ONTOP) { - return; - } - // - if (BotSetLastOrderedTask(bs)) - return; - // - if (bs->owndecision_time > FloatTime()) - return;; - //if the bot is roaming - if (bs->ctfroam_time > FloatTime()) - return; - //if the bot has anough aggression to decide what to do - if (BotAggression(bs) < 50) - return; - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - // - if (bs->teamtaskpreference & (TEAMTP_ATTACKER|TEAMTP_DEFENDER)) { - if (bs->teamtaskpreference & TEAMTP_ATTACKER) { - l1 = 0.7f; - } - else { - l1 = 0.2f; - } - l2 = 0.9f; - } - else { - l1 = 0.4f; - l2 = 0.7f; - } - //get the flag or defend the base - rnd = random(); - if (rnd < l1 && ctf_neutralflag.areanum) { - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - bs->ltgtype = LTG_GETFLAG; - //set the time the bot will stop getting the flag - bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME; - BotSetTeamStatus(bs); - } - else if (rnd < l2 && ctf_redflag.areanum && ctf_blueflag.areanum) { - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - // - if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); - else memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); - //set the ltg type - bs->ltgtype = LTG_DEFENDKEYAREA; - //set the time the bot stops defending the base - bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; - bs->defendaway_time = 0; - BotSetTeamStatus(bs); - } - else { - bs->ltgtype = 0; - //set the time the bot will stop roaming - bs->ctfroam_time = FloatTime() + CTF_ROAM_TIME; - BotSetTeamStatus(bs); - } - bs->owndecision_time = FloatTime() + 5; -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} - -/* -================== -Bot1FCTFRetreatGoals -================== -*/ -void Bot1FCTFRetreatGoals(bot_state_t *bs) { - //when carrying a flag in ctf the bot should rush to the enemy base - if (Bot1FCTFCarryingFlag(bs)) { - //if not already rushing to the base - if (bs->ltgtype != LTG_RUSHBASE) { - BotRefuseOrder(bs); - bs->ltgtype = LTG_RUSHBASE; - bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; - bs->rushbaseaway_time = 0; - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - //get an alternative route goal towards the enemy base - BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); - BotSetTeamStatus(bs); - } - } -} - -/* -================== -BotObeliskSeekGoals -================== -*/ -void BotObeliskSeekGoals(bot_state_t *bs) { - float rnd, l1, l2; - - // don't just do something wait for the bot team leader to give orders - if (BotTeamLeader(bs)) { - return; - } - // if the bot is ordered to do something - if ( bs->lastgoal_ltgtype ) { - bs->teamgoal_time += 60; - } - //if already a team goal - if (bs->ltgtype == LTG_TEAMHELP || - bs->ltgtype == LTG_TEAMACCOMPANY || - bs->ltgtype == LTG_DEFENDKEYAREA || - bs->ltgtype == LTG_GETFLAG || - bs->ltgtype == LTG_RUSHBASE || - bs->ltgtype == LTG_RETURNFLAG || - bs->ltgtype == LTG_CAMPORDER || - bs->ltgtype == LTG_PATROL || - bs->ltgtype == LTG_ATTACKENEMYBASE || - bs->ltgtype == LTG_GETITEM || - bs->ltgtype == LTG_MAKELOVE_UNDER || - bs->ltgtype == LTG_MAKELOVE_ONTOP) { - return; - } - // - if (BotSetLastOrderedTask(bs)) - return; - //if the bot is roaming - if (bs->ctfroam_time > FloatTime()) - return; - //if the bot has anough aggression to decide what to do - if (BotAggression(bs) < 50) - return; - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - // - if (bs->teamtaskpreference & (TEAMTP_ATTACKER|TEAMTP_DEFENDER)) { - if (bs->teamtaskpreference & TEAMTP_ATTACKER) { - l1 = 0.7f; - } - else { - l1 = 0.2f; - } - l2 = 0.9f; - } - else { - l1 = 0.4f; - l2 = 0.7f; - } - //get the flag or defend the base - rnd = random(); - if (rnd < l1 && redobelisk.areanum && blueobelisk.areanum) { - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - // - if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t)); - else memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t)); - //set the ltg type - bs->ltgtype = LTG_ATTACKENEMYBASE; - //set the time the bot will stop attacking the enemy base - bs->teamgoal_time = FloatTime() + TEAM_ATTACKENEMYBASE_TIME; - //get an alternate route goal towards the enemy base - BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); - BotSetTeamStatus(bs); - } - else if (rnd < l2 && redobelisk.areanum && blueobelisk.areanum) { - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - // - if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t)); - else memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t)); - //set the ltg type - bs->ltgtype = LTG_DEFENDKEYAREA; - //set the time the bot stops defending the base - bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; - bs->defendaway_time = 0; - BotSetTeamStatus(bs); - } - else { - bs->ltgtype = 0; - //set the time the bot will stop roaming - bs->ctfroam_time = FloatTime() + CTF_ROAM_TIME; - BotSetTeamStatus(bs); - } -} - -/* -================== -BotGoHarvest -================== -*/ -void BotGoHarvest(bot_state_t *bs) { - // - if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t)); - else memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t)); - //set the ltg type - bs->ltgtype = LTG_HARVEST; - //set the time the bot will stop harvesting - bs->teamgoal_time = FloatTime() + TEAM_HARVEST_TIME; - bs->harvestaway_time = 0; - BotSetTeamStatus(bs); -} - -/* -================== -BotObeliskRetreatGoals -================== -*/ -void BotObeliskRetreatGoals(bot_state_t *bs) { - //nothing special -} - -/* -================== -BotHarvesterSeekGoals -================== -*/ -void BotHarvesterSeekGoals(bot_state_t *bs) { - aas_entityinfo_t entinfo; - float rnd, l1, l2; - int c; - - //when carrying cubes in harvester the bot should rush to the base - if (BotHarvesterCarryingCubes(bs)) { - //if not already rushing to the base - if (bs->ltgtype != LTG_RUSHBASE) { - BotRefuseOrder(bs); - bs->ltgtype = LTG_RUSHBASE; - bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; - bs->rushbaseaway_time = 0; - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - //get an alternative route goal towards the enemy base - BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); - // - BotSetTeamStatus(bs); - } - return; - } - // don't just do something wait for the bot team leader to give orders - if (BotTeamLeader(bs)) { - return; - } - // if the bot decided to follow someone - if ( bs->ltgtype == LTG_TEAMACCOMPANY && !bs->ordered ) { - // if the team mate being accompanied no longer carries the flag - BotEntityInfo(bs->teammate, &entinfo); - if (!EntityCarriesCubes(&entinfo)) { - bs->ltgtype = 0; - } - } - // if the bot is ordered to do something - if ( bs->lastgoal_ltgtype ) { - bs->teamgoal_time += 60; - } - //if not yet doing something - if (bs->ltgtype == LTG_TEAMHELP || - bs->ltgtype == LTG_TEAMACCOMPANY || - bs->ltgtype == LTG_DEFENDKEYAREA || - bs->ltgtype == LTG_GETFLAG || - bs->ltgtype == LTG_CAMPORDER || - bs->ltgtype == LTG_PATROL || - bs->ltgtype == LTG_ATTACKENEMYBASE || - bs->ltgtype == LTG_HARVEST || - bs->ltgtype == LTG_GETITEM || - bs->ltgtype == LTG_MAKELOVE_UNDER || - bs->ltgtype == LTG_MAKELOVE_ONTOP) { - return; - } - // - if (BotSetLastOrderedTask(bs)) - return; - //if the bot is roaming - if (bs->ctfroam_time > FloatTime()) - return; - //if the bot has anough aggression to decide what to do - if (BotAggression(bs) < 50) - return; - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - // - c = BotEnemyCubeCarrierVisible(bs); - if (c >= 0) { - //FIXME: attack enemy cube carrier - } - if (bs->ltgtype != LTG_TEAMACCOMPANY) { - //if there is a visible team mate carrying cubes - c = BotTeamCubeCarrierVisible(bs); - if (c >= 0) { - //follow the team mate carrying cubes - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - //the team mate - bs->teammate = c; - //last time the team mate was visible - bs->teammatevisible_time = FloatTime(); - //no message - bs->teammessage_time = 0; - //no arrive message - bs->arrive_time = 1; - // - BotVoiceChat(bs, bs->teammate, VOICECHAT_ONFOLLOW); - //get the team goal time - bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; - bs->ltgtype = LTG_TEAMACCOMPANY; - bs->formation_dist = 3.5 * 32; //3.5 meter - BotSetTeamStatus(bs); - return; - } - } - // - if (bs->teamtaskpreference & (TEAMTP_ATTACKER|TEAMTP_DEFENDER)) { - if (bs->teamtaskpreference & TEAMTP_ATTACKER) { - l1 = 0.7f; - } - else { - l1 = 0.2f; - } - l2 = 0.9f; - } - else { - l1 = 0.4f; - l2 = 0.7f; - } - // - rnd = random(); - if (rnd < l1 && redobelisk.areanum && blueobelisk.areanum) { - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - BotGoHarvest(bs); - } - else if (rnd < l2 && redobelisk.areanum && blueobelisk.areanum) { - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - // - if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t)); - else memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t)); - //set the ltg type - bs->ltgtype = LTG_DEFENDKEYAREA; - //set the time the bot stops defending the base - bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; - bs->defendaway_time = 0; - BotSetTeamStatus(bs); - } - else { - bs->ltgtype = 0; - //set the time the bot will stop roaming - bs->ctfroam_time = FloatTime() + CTF_ROAM_TIME; - BotSetTeamStatus(bs); - } -} - -/* -================== -BotHarvesterRetreatGoals -================== -*/ -void BotHarvesterRetreatGoals(bot_state_t *bs) { - //when carrying cubes in harvester the bot should rush to the base - if (BotHarvesterCarryingCubes(bs)) { - //if not already rushing to the base - if (bs->ltgtype != LTG_RUSHBASE) { - BotRefuseOrder(bs); - bs->ltgtype = LTG_RUSHBASE; - bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; - bs->rushbaseaway_time = 0; - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - BotSetTeamStatus(bs); - } - return; - } -} -#endif - -/* -================== -BotTeamGoals -================== -*/ -void BotTeamGoals(bot_state_t *bs, int retreat) { - - if ( retreat ) { - if (gametype == GT_CTF) { - BotCTFRetreatGoals(bs); - } -#ifdef MISSIONPACK - else if (gametype == GT_1FCTF) { - Bot1FCTFRetreatGoals(bs); - } - else if (gametype == GT_OBELISK) { - BotObeliskRetreatGoals(bs); - } - else if (gametype == GT_HARVESTER) { - BotHarvesterRetreatGoals(bs); - } -#endif - } - else { - if (gametype == GT_CTF) { - //decide what to do in CTF mode - BotCTFSeekGoals(bs); - } -#ifdef MISSIONPACK - else if (gametype == GT_1FCTF) { - Bot1FCTFSeekGoals(bs); - } - else if (gametype == GT_OBELISK) { - BotObeliskSeekGoals(bs); - } - else if (gametype == GT_HARVESTER) { - BotHarvesterSeekGoals(bs); - } -#endif - } - // reset the order time which is used to see if - // we decided to refuse an order - bs->order_time = 0; -} - -/* -================== -BotPointAreaNum -================== -*/ -int BotPointAreaNum(vec3_t origin) { - int areanum, numareas, areas[10]; - vec3_t end; - - areanum = trap_AAS_PointAreaNum(origin); - if (areanum) return areanum; - VectorCopy(origin, end); - end[2] += 10; - numareas = trap_AAS_TraceAreas(origin, end, areas, NULL, 10); - if (numareas > 0) return areas[0]; - return 0; -} - -/* -================== -ClientName -================== -*/ -char *ClientName(int client, char *name, int size) { - char buf[MAX_INFO_STRING]; - - if (client < 0 || client >= MAX_CLIENTS) { - BotAI_Print(PRT_ERROR, "ClientName: client out of range\n"); - return "[client out of range]"; - } - trap_GetConfigstring(CS_PLAYERS+client, buf, sizeof(buf)); - strncpy(name, Info_ValueForKey(buf, "n"), size-1); - name[size-1] = '\0'; - Q_CleanStr( name ); - return name; -} - -/* -================== -ClientSkin -================== -*/ -char *ClientSkin(int client, char *skin, int size) { - char buf[MAX_INFO_STRING]; - - if (client < 0 || client >= MAX_CLIENTS) { - BotAI_Print(PRT_ERROR, "ClientSkin: client out of range\n"); - return "[client out of range]"; - } - trap_GetConfigstring(CS_PLAYERS+client, buf, sizeof(buf)); - strncpy(skin, Info_ValueForKey(buf, "model"), size-1); - skin[size-1] = '\0'; - return skin; -} - -/* -================== -ClientFromName -================== -*/ -int ClientFromName(char *name) { - int i; - char buf[MAX_INFO_STRING]; - static int maxclients; - - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); - Q_CleanStr( buf ); - if (!Q_stricmp(Info_ValueForKey(buf, "n"), name)) return i; - } - return -1; -} - -/* -================== -ClientOnSameTeamFromName -================== -*/ -int ClientOnSameTeamFromName(bot_state_t *bs, char *name) { - int i; - char buf[MAX_INFO_STRING]; - static int maxclients; - - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - if (!BotSameTeam(bs, i)) - continue; - trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); - Q_CleanStr( buf ); - if (!Q_stricmp(Info_ValueForKey(buf, "n"), name)) return i; - } - return -1; -} - -/* -================== -stristr -================== -*/ -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; -} - -/* -================== -EasyClientName -================== -*/ -char *EasyClientName(int client, char *buf, int size) { - int i; - char *str1, *str2, *ptr, c; - char name[128]; - - strcpy(name, ClientName(client, name, sizeof(name))); - for (i = 0; name[i]; i++) name[i] &= 127; - //remove all spaces - for (ptr = strstr(name, " "); ptr; ptr = strstr(name, " ")) { - memmove(ptr, ptr+1, strlen(ptr+1)+1); - } - //check for [x] and ]x[ clan names - str1 = strstr(name, "["); - str2 = strstr(name, "]"); - if (str1 && str2) { - if (str2 > str1) memmove(str1, str2+1, strlen(str2+1)+1); - else memmove(str2, str1+1, strlen(str1+1)+1); - } - //remove Mr prefix - if ((name[0] == 'm' || name[0] == 'M') && - (name[1] == 'r' || name[1] == 'R')) { - memmove(name, name+2, strlen(name+2)+1); - } - //only allow lower case alphabet characters - ptr = name; - while(*ptr) { - c = *ptr; - if ((c >= 'a' && c <= 'z') || - (c >= '0' && c <= '9') || c == '_') { - ptr++; - } - else if (c >= 'A' && c <= 'Z') { - *ptr += 'a' - 'A'; - ptr++; - } - else { - memmove(ptr, ptr+1, strlen(ptr + 1)+1); - } - } - strncpy(buf, name, size-1); - buf[size-1] = '\0'; - return buf; -} - -/* -================== -BotSynonymContext -================== -*/ -int BotSynonymContext(bot_state_t *bs) { - int context; - - context = CONTEXT_NORMAL|CONTEXT_NEARBYITEM|CONTEXT_NAMES; - // - if (gametype == GT_CTF -#ifdef MISSIONPACK - || gametype == GT_1FCTF -#endif - ) { - if (BotTeam(bs) == TEAM_RED) context |= CONTEXT_CTFREDTEAM; - else context |= CONTEXT_CTFBLUETEAM; - } -#ifdef MISSIONPACK - else if (gametype == GT_OBELISK) { - if (BotTeam(bs) == TEAM_RED) context |= CONTEXT_OBELISKREDTEAM; - else context |= CONTEXT_OBELISKBLUETEAM; - } - else if (gametype == GT_HARVESTER) { - if (BotTeam(bs) == TEAM_RED) context |= CONTEXT_HARVESTERREDTEAM; - else context |= CONTEXT_HARVESTERBLUETEAM; - } -#endif - return context; -} - -/* -================== -BotChooseWeapon -================== -*/ -void BotChooseWeapon(bot_state_t *bs) { - int newweaponnum; - - if (bs->cur_ps.weaponstate == WEAPON_RAISING || - bs->cur_ps.weaponstate == WEAPON_DROPPING) { - trap_EA_SelectWeapon(bs->client, bs->weaponnum); - } - else { - newweaponnum = trap_BotChooseBestFightWeapon(bs->ws, bs->inventory); - if (bs->weaponnum != newweaponnum) bs->weaponchange_time = FloatTime(); - bs->weaponnum = newweaponnum; - //BotAI_Print(PRT_MESSAGE, "bs->weaponnum = %d\n", bs->weaponnum); - trap_EA_SelectWeapon(bs->client, bs->weaponnum); - } -} - -/* -================== -BotSetupForMovement -================== -*/ -void BotSetupForMovement(bot_state_t *bs) { - bot_initmove_t initmove; - - memset(&initmove, 0, sizeof(bot_initmove_t)); - VectorCopy(bs->cur_ps.origin, initmove.origin); - VectorCopy(bs->cur_ps.velocity, initmove.velocity); - VectorClear(initmove.viewoffset); - initmove.viewoffset[2] += bs->cur_ps.viewheight; - initmove.entitynum = bs->entitynum; - initmove.client = bs->client; - initmove.thinktime = bs->thinktime; - //set the onground flag - if (bs->cur_ps.groundEntityNum != ENTITYNUM_NONE) initmove.or_moveflags |= MFL_ONGROUND; - //set the teleported flag - if ((bs->cur_ps.pm_flags & PMF_TIME_KNOCKBACK) && (bs->cur_ps.pm_time > 0)) { - initmove.or_moveflags |= MFL_TELEPORTED; - } - //set the waterjump flag - if ((bs->cur_ps.pm_flags & PMF_TIME_WATERJUMP) && (bs->cur_ps.pm_time > 0)) { - initmove.or_moveflags |= MFL_WATERJUMP; - } - //set presence type - if (bs->cur_ps.pm_flags & PMF_DUCKED) initmove.presencetype = PRESENCE_CROUCH; - else initmove.presencetype = PRESENCE_NORMAL; - // - if (bs->walker > 0.5) initmove.or_moveflags |= MFL_WALK; - // - VectorCopy(bs->viewangles, initmove.viewangles); - // - trap_BotInitMoveState(bs->ms, &initmove); -} - -/* -================== -BotCheckItemPickup -================== -*/ -void BotCheckItemPickup(bot_state_t *bs, int *oldinventory) { -#ifdef MISSIONPACK - int offence, leader; - - if (gametype <= GT_TEAM) - return; - - offence = -1; - // go into offence if picked up the kamikaze or invulnerability - if (!oldinventory[INVENTORY_KAMIKAZE] && bs->inventory[INVENTORY_KAMIKAZE] >= 1) { - offence = qtrue; - } - if (!oldinventory[INVENTORY_INVULNERABILITY] && bs->inventory[INVENTORY_INVULNERABILITY] >= 1) { - offence = qtrue; - } - // if not already wearing the kamikaze or invulnerability - if (!bs->inventory[INVENTORY_KAMIKAZE] && !bs->inventory[INVENTORY_INVULNERABILITY]) { - if (!oldinventory[INVENTORY_SCOUT] && bs->inventory[INVENTORY_SCOUT] >= 1) { - offence = qtrue; - } - if (!oldinventory[INVENTORY_GUARD] && bs->inventory[INVENTORY_GUARD] >= 1) { - offence = qtrue; - } - if (!oldinventory[INVENTORY_DOUBLER] && bs->inventory[INVENTORY_DOUBLER] >= 1) { - offence = qfalse; - } - if (!oldinventory[INVENTORY_AMMOREGEN] && bs->inventory[INVENTORY_AMMOREGEN] >= 1) { - offence = qfalse; - } - } - - if (offence >= 0) { - leader = ClientFromName(bs->teamleader); - if (offence) { - if (!(bs->teamtaskpreference & TEAMTP_ATTACKER)) { - // if we have a bot team leader - if (BotTeamLeader(bs)) { - // tell the leader we want to be on offence - BotVoiceChat(bs, leader, VOICECHAT_WANTONOFFENSE); - //BotAI_BotInitialChat(bs, "wantoffence", NULL); - //trap_BotEnterChat(bs->cs, leader, CHAT_TELL); - } - else if (g_spSkill.integer <= 3) { - if ( bs->ltgtype != LTG_GETFLAG && - bs->ltgtype != LTG_ATTACKENEMYBASE && - bs->ltgtype != LTG_HARVEST ) { - // - if ((gametype != GT_CTF || (bs->redflagstatus == 0 && bs->blueflagstatus == 0)) && - (gametype != GT_1FCTF || bs->neutralflagstatus == 0) ) { - // tell the leader we want to be on offence - BotVoiceChat(bs, leader, VOICECHAT_WANTONOFFENSE); - //BotAI_BotInitialChat(bs, "wantoffence", NULL); - //trap_BotEnterChat(bs->cs, leader, CHAT_TELL); - } - } - bs->teamtaskpreference |= TEAMTP_ATTACKER; - } - } - bs->teamtaskpreference &= ~TEAMTP_DEFENDER; - } - else { - if (!(bs->teamtaskpreference & TEAMTP_DEFENDER)) { - // if we have a bot team leader - if (BotTeamLeader(bs)) { - // tell the leader we want to be on defense - BotVoiceChat(bs, -1, VOICECHAT_WANTONDEFENSE); - //BotAI_BotInitialChat(bs, "wantdefence", NULL); - //trap_BotEnterChat(bs->cs, leader, CHAT_TELL); - } - else if (g_spSkill.integer <= 3) { - if ( bs->ltgtype != LTG_DEFENDKEYAREA ) { - // - if ((gametype != GT_CTF || (bs->redflagstatus == 0 && bs->blueflagstatus == 0)) && - (gametype != GT_1FCTF || bs->neutralflagstatus == 0) ) { - // tell the leader we want to be on defense - BotVoiceChat(bs, -1, VOICECHAT_WANTONDEFENSE); - //BotAI_BotInitialChat(bs, "wantdefence", NULL); - //trap_BotEnterChat(bs->cs, leader, CHAT_TELL); - } - } - } - bs->teamtaskpreference |= TEAMTP_DEFENDER; - } - bs->teamtaskpreference &= ~TEAMTP_ATTACKER; - } - } -#endif -} - -/* -================== -BotUpdateInventory -================== -*/ -void BotUpdateInventory(bot_state_t *bs) { - int oldinventory[MAX_ITEMS]; - - memcpy(oldinventory, bs->inventory, sizeof(oldinventory)); - //armor - bs->inventory[INVENTORY_ARMOR] = bs->cur_ps.stats[STAT_ARMOR]; - //weapons - bs->inventory[INVENTORY_GAUNTLET] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GAUNTLET)) != 0; - bs->inventory[INVENTORY_SHOTGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_SHOTGUN)) != 0; - bs->inventory[INVENTORY_MACHINEGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_MACHINEGUN)) != 0; - bs->inventory[INVENTORY_GRENADELAUNCHER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GRENADE_LAUNCHER)) != 0; - bs->inventory[INVENTORY_ROCKETLAUNCHER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_ROCKET_LAUNCHER)) != 0; - bs->inventory[INVENTORY_LIGHTNING] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_LIGHTNING)) != 0; - bs->inventory[INVENTORY_RAILGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_RAILGUN)) != 0; - bs->inventory[INVENTORY_PLASMAGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_PLASMAGUN)) != 0; - bs->inventory[INVENTORY_BFG10K] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_BFG)) != 0; - bs->inventory[INVENTORY_GRAPPLINGHOOK] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GRAPPLING_HOOK)) != 0; -#ifdef MISSIONPACK - bs->inventory[INVENTORY_NAILGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_NAILGUN)) != 0;; - bs->inventory[INVENTORY_PROXLAUNCHER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_PROX_LAUNCHER)) != 0;; - bs->inventory[INVENTORY_CHAINGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_CHAINGUN)) != 0;; -#endif - //ammo - bs->inventory[INVENTORY_SHELLS] = bs->cur_ps.ammo[WP_SHOTGUN]; - bs->inventory[INVENTORY_BULLETS] = bs->cur_ps.ammo[WP_MACHINEGUN]; - bs->inventory[INVENTORY_GRENADES] = bs->cur_ps.ammo[WP_GRENADE_LAUNCHER]; - bs->inventory[INVENTORY_CELLS] = bs->cur_ps.ammo[WP_PLASMAGUN]; - bs->inventory[INVENTORY_LIGHTNINGAMMO] = bs->cur_ps.ammo[WP_LIGHTNING]; - bs->inventory[INVENTORY_ROCKETS] = bs->cur_ps.ammo[WP_ROCKET_LAUNCHER]; - bs->inventory[INVENTORY_SLUGS] = bs->cur_ps.ammo[WP_RAILGUN]; - bs->inventory[INVENTORY_BFGAMMO] = bs->cur_ps.ammo[WP_BFG]; -#ifdef MISSIONPACK - bs->inventory[INVENTORY_NAILS] = bs->cur_ps.ammo[WP_NAILGUN]; - bs->inventory[INVENTORY_MINES] = bs->cur_ps.ammo[WP_PROX_LAUNCHER]; - bs->inventory[INVENTORY_BELT] = bs->cur_ps.ammo[WP_CHAINGUN]; -#endif - //powerups - bs->inventory[INVENTORY_HEALTH] = bs->cur_ps.stats[STAT_HEALTH]; - bs->inventory[INVENTORY_TELEPORTER] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_TELEPORTER; - bs->inventory[INVENTORY_MEDKIT] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_MEDKIT; -#ifdef MISSIONPACK - bs->inventory[INVENTORY_KAMIKAZE] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_KAMIKAZE; - bs->inventory[INVENTORY_PORTAL] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_PORTAL; - bs->inventory[INVENTORY_INVULNERABILITY] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_INVULNERABILITY; -#endif - bs->inventory[INVENTORY_QUAD] = bs->cur_ps.powerups[PW_QUAD] != 0; - bs->inventory[INVENTORY_ENVIRONMENTSUIT] = bs->cur_ps.powerups[PW_BATTLESUIT] != 0; - bs->inventory[INVENTORY_HASTE] = bs->cur_ps.powerups[PW_HASTE] != 0; - bs->inventory[INVENTORY_INVISIBILITY] = bs->cur_ps.powerups[PW_INVIS] != 0; - bs->inventory[INVENTORY_REGEN] = bs->cur_ps.powerups[PW_REGEN] != 0; - bs->inventory[INVENTORY_FLIGHT] = bs->cur_ps.powerups[PW_FLIGHT] != 0; -#ifdef MISSIONPACK - bs->inventory[INVENTORY_SCOUT] = bs->cur_ps.stats[STAT_PERSISTANT_POWERUP] == MODELINDEX_SCOUT; - bs->inventory[INVENTORY_GUARD] = bs->cur_ps.stats[STAT_PERSISTANT_POWERUP] == MODELINDEX_GUARD; - bs->inventory[INVENTORY_DOUBLER] = bs->cur_ps.stats[STAT_PERSISTANT_POWERUP] == MODELINDEX_DOUBLER; - bs->inventory[INVENTORY_AMMOREGEN] = bs->cur_ps.stats[STAT_PERSISTANT_POWERUP] == MODELINDEX_AMMOREGEN; -#endif - bs->inventory[INVENTORY_REDFLAG] = bs->cur_ps.powerups[PW_REDFLAG] != 0; - bs->inventory[INVENTORY_BLUEFLAG] = bs->cur_ps.powerups[PW_BLUEFLAG] != 0; -#ifdef MISSIONPACK - bs->inventory[INVENTORY_NEUTRALFLAG] = bs->cur_ps.powerups[PW_NEUTRALFLAG] != 0; - if (BotTeam(bs) == TEAM_RED) { - bs->inventory[INVENTORY_REDCUBE] = bs->cur_ps.generic1; - bs->inventory[INVENTORY_BLUECUBE] = 0; - } - else { - bs->inventory[INVENTORY_REDCUBE] = 0; - bs->inventory[INVENTORY_BLUECUBE] = bs->cur_ps.generic1; - } -#endif - BotCheckItemPickup(bs, oldinventory); -} - -/* -================== -BotUpdateBattleInventory -================== -*/ -void BotUpdateBattleInventory(bot_state_t *bs, int enemy) { - vec3_t dir; - aas_entityinfo_t entinfo; - - BotEntityInfo(enemy, &entinfo); - VectorSubtract(entinfo.origin, bs->origin, dir); - bs->inventory[ENEMY_HEIGHT] = (int) dir[2]; - dir[2] = 0; - bs->inventory[ENEMY_HORIZONTAL_DIST] = (int) VectorLength(dir); - //FIXME: add num visible enemies and num visible team mates to the inventory -} - -#ifdef MISSIONPACK -/* -================== -BotUseKamikaze -================== -*/ -#define KAMIKAZE_DIST 1024 - -void BotUseKamikaze(bot_state_t *bs) { - int c, teammates, enemies; - aas_entityinfo_t entinfo; - vec3_t dir, target; - bot_goal_t *goal; - bsp_trace_t trace; - - //if the bot has no kamikaze - if (bs->inventory[INVENTORY_KAMIKAZE] <= 0) - return; - if (bs->kamikaze_time > FloatTime()) - return; - bs->kamikaze_time = FloatTime() + 0.2; - if (gametype == GT_CTF) { - //never use kamikaze if the team flag carrier is visible - if (BotCTFCarryingFlag(bs)) - return; - c = BotTeamFlagCarrierVisible(bs); - if (c >= 0) { - BotEntityInfo(c, &entinfo); - VectorSubtract(entinfo.origin, bs->origin, dir); - if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) - return; - } - c = BotEnemyFlagCarrierVisible(bs); - if (c >= 0) { - BotEntityInfo(c, &entinfo); - VectorSubtract(entinfo.origin, bs->origin, dir); - if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) { - trap_EA_Use(bs->client); - return; - } - } - } - else if (gametype == GT_1FCTF) { - //never use kamikaze if the team flag carrier is visible - if (Bot1FCTFCarryingFlag(bs)) - return; - c = BotTeamFlagCarrierVisible(bs); - if (c >= 0) { - BotEntityInfo(c, &entinfo); - VectorSubtract(entinfo.origin, bs->origin, dir); - if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) - return; - } - c = BotEnemyFlagCarrierVisible(bs); - if (c >= 0) { - BotEntityInfo(c, &entinfo); - VectorSubtract(entinfo.origin, bs->origin, dir); - if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) { - trap_EA_Use(bs->client); - return; - } - } - } - else if (gametype == GT_OBELISK) { - switch(BotTeam(bs)) { - case TEAM_RED: goal = &blueobelisk; break; - default: goal = &redobelisk; break; - } - //if the obelisk is visible - VectorCopy(goal->origin, target); - target[2] += 1; - VectorSubtract(bs->origin, target, dir); - if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST * 0.9)) { - BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); - if (trace.fraction >= 1 || trace.ent == goal->entitynum) { - trap_EA_Use(bs->client); - return; - } - } - } - else if (gametype == GT_HARVESTER) { - // - if (BotHarvesterCarryingCubes(bs)) - return; - //never use kamikaze if a team mate carrying cubes is visible - c = BotTeamCubeCarrierVisible(bs); - if (c >= 0) { - BotEntityInfo(c, &entinfo); - VectorSubtract(entinfo.origin, bs->origin, dir); - if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) - return; - } - c = BotEnemyCubeCarrierVisible(bs); - if (c >= 0) { - BotEntityInfo(c, &entinfo); - VectorSubtract(entinfo.origin, bs->origin, dir); - if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) { - trap_EA_Use(bs->client); - return; - } - } - } - // - BotVisibleTeamMatesAndEnemies(bs, &teammates, &enemies, KAMIKAZE_DIST); - // - if (enemies > 2 && enemies > teammates+1) { - trap_EA_Use(bs->client); - return; - } -} - -/* -================== -BotUseInvulnerability -================== -*/ -void BotUseInvulnerability(bot_state_t *bs) { - int c; - vec3_t dir, target; - bot_goal_t *goal; - bsp_trace_t trace; - - //if the bot has no invulnerability - if (bs->inventory[INVENTORY_INVULNERABILITY] <= 0) - return; - if (bs->invulnerability_time > FloatTime()) - return; - bs->invulnerability_time = FloatTime() + 0.2; - if (gametype == GT_CTF) { - //never use kamikaze if the team flag carrier is visible - if (BotCTFCarryingFlag(bs)) - return; - c = BotEnemyFlagCarrierVisible(bs); - if (c >= 0) - return; - //if near enemy flag and the flag is visible - switch(BotTeam(bs)) { - case TEAM_RED: goal = &ctf_blueflag; break; - default: goal = &ctf_redflag; break; - } - //if the obelisk is visible - VectorCopy(goal->origin, target); - target[2] += 1; - VectorSubtract(bs->origin, target, dir); - if (VectorLengthSquared(dir) < Square(200)) { - BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); - if (trace.fraction >= 1 || trace.ent == goal->entitynum) { - trap_EA_Use(bs->client); - return; - } - } - } - else if (gametype == GT_1FCTF) { - //never use kamikaze if the team flag carrier is visible - if (Bot1FCTFCarryingFlag(bs)) - return; - c = BotEnemyFlagCarrierVisible(bs); - if (c >= 0) - return; - //if near enemy flag and the flag is visible - switch(BotTeam(bs)) { - case TEAM_RED: goal = &ctf_blueflag; break; - default: goal = &ctf_redflag; break; - } - //if the obelisk is visible - VectorCopy(goal->origin, target); - target[2] += 1; - VectorSubtract(bs->origin, target, dir); - if (VectorLengthSquared(dir) < Square(200)) { - BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); - if (trace.fraction >= 1 || trace.ent == goal->entitynum) { - trap_EA_Use(bs->client); - return; - } - } - } - else if (gametype == GT_OBELISK) { - switch(BotTeam(bs)) { - case TEAM_RED: goal = &blueobelisk; break; - default: goal = &redobelisk; break; - } - //if the obelisk is visible - VectorCopy(goal->origin, target); - target[2] += 1; - VectorSubtract(bs->origin, target, dir); - if (VectorLengthSquared(dir) < Square(300)) { - BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); - if (trace.fraction >= 1 || trace.ent == goal->entitynum) { - trap_EA_Use(bs->client); - return; - } - } - } - else if (gametype == GT_HARVESTER) { - // - if (BotHarvesterCarryingCubes(bs)) - return; - c = BotEnemyCubeCarrierVisible(bs); - if (c >= 0) - return; - //if near enemy base and enemy base is visible - switch(BotTeam(bs)) { - case TEAM_RED: goal = &blueobelisk; break; - default: goal = &redobelisk; break; - } - //if the obelisk is visible - VectorCopy(goal->origin, target); - target[2] += 1; - VectorSubtract(bs->origin, target, dir); - if (VectorLengthSquared(dir) < Square(200)) { - BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); - if (trace.fraction >= 1 || trace.ent == goal->entitynum) { - trap_EA_Use(bs->client); - return; - } - } - } -} -#endif - -/* -================== -BotBattleUseItems -================== -*/ -void BotBattleUseItems(bot_state_t *bs) { - if (bs->inventory[INVENTORY_HEALTH] < 40) { - if (bs->inventory[INVENTORY_TELEPORTER] > 0) { - if (!BotCTFCarryingFlag(bs) -#ifdef MISSIONPACK - && !Bot1FCTFCarryingFlag(bs) - && !BotHarvesterCarryingCubes(bs) -#endif - ) { - trap_EA_Use(bs->client); - } - } - } - if (bs->inventory[INVENTORY_HEALTH] < 60) { - if (bs->inventory[INVENTORY_MEDKIT] > 0) { - trap_EA_Use(bs->client); - } - } -#ifdef MISSIONPACK - BotUseKamikaze(bs); - BotUseInvulnerability(bs); -#endif -} - -/* -================== -BotSetTeleportTime -================== -*/ -void BotSetTeleportTime(bot_state_t *bs) { - if ((bs->cur_ps.eFlags ^ bs->last_eFlags) & EF_TELEPORT_BIT) { - bs->teleport_time = FloatTime(); - } - bs->last_eFlags = bs->cur_ps.eFlags; -} - -/* -================== -BotIsDead -================== -*/ -qboolean BotIsDead(bot_state_t *bs) { - return (bs->cur_ps.pm_type == PM_DEAD); -} - -/* -================== -BotIsObserver -================== -*/ -qboolean BotIsObserver(bot_state_t *bs) { - char buf[MAX_INFO_STRING]; - if (bs->cur_ps.pm_type == PM_SPECTATOR) return qtrue; - trap_GetConfigstring(CS_PLAYERS+bs->client, buf, sizeof(buf)); - if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) return qtrue; - return qfalse; -} - -/* -================== -BotIntermission -================== -*/ -qboolean BotIntermission(bot_state_t *bs) { - //NOTE: we shouldn't be looking at the game code... - if (level.intermissiontime) return qtrue; - return (bs->cur_ps.pm_type == PM_FREEZE || bs->cur_ps.pm_type == PM_INTERMISSION); -} - -/* -================== -BotInLavaOrSlime -================== -*/ -qboolean BotInLavaOrSlime(bot_state_t *bs) { - vec3_t feet; - - VectorCopy(bs->origin, feet); - feet[2] -= 23; - return (trap_AAS_PointContents(feet) & (CONTENTS_LAVA|CONTENTS_SLIME)); -} - -/* -================== -BotCreateWayPoint -================== -*/ -bot_waypoint_t *BotCreateWayPoint(char *name, vec3_t origin, int areanum) { - bot_waypoint_t *wp; - vec3_t waypointmins = {-8, -8, -8}, waypointmaxs = {8, 8, 8}; - - wp = botai_freewaypoints; - if ( !wp ) { - BotAI_Print( PRT_WARNING, "BotCreateWayPoint: Out of waypoints\n" ); - return NULL; - } - botai_freewaypoints = botai_freewaypoints->next; - - Q_strncpyz( wp->name, name, sizeof(wp->name) ); - VectorCopy(origin, wp->goal.origin); - VectorCopy(waypointmins, wp->goal.mins); - VectorCopy(waypointmaxs, wp->goal.maxs); - wp->goal.areanum = areanum; - wp->next = NULL; - wp->prev = NULL; - return wp; -} - -/* -================== -BotFindWayPoint -================== -*/ -bot_waypoint_t *BotFindWayPoint(bot_waypoint_t *waypoints, char *name) { - bot_waypoint_t *wp; - - for (wp = waypoints; wp; wp = wp->next) { - if (!Q_stricmp(wp->name, name)) return wp; - } - return NULL; -} - -/* -================== -BotFreeWaypoints -================== -*/ -void BotFreeWaypoints(bot_waypoint_t *wp) { - bot_waypoint_t *nextwp; - - for (; wp; wp = nextwp) { - nextwp = wp->next; - wp->next = botai_freewaypoints; - botai_freewaypoints = wp; - } -} - -/* -================== -BotInitWaypoints -================== -*/ -void BotInitWaypoints(void) { - int i; - - botai_freewaypoints = NULL; - for (i = 0; i < MAX_WAYPOINTS; i++) { - botai_waypoints[i].next = botai_freewaypoints; - botai_freewaypoints = &botai_waypoints[i]; - } -} - -/* -================== -TeamPlayIsOn -================== -*/ -int TeamPlayIsOn(void) { - return ( gametype >= GT_TEAM ); -} - -/* -================== -BotAggression -================== -*/ -float BotAggression(bot_state_t *bs) { - //if the bot has quad - if (bs->inventory[INVENTORY_QUAD]) { - //if the bot is not holding the gauntlet or the enemy is really nearby - if (bs->weaponnum != WP_GAUNTLET || - bs->inventory[ENEMY_HORIZONTAL_DIST] < 80) { - return 70; - } - } - //if the enemy is located way higher than the bot - if (bs->inventory[ENEMY_HEIGHT] > 200) return 0; - //if the bot is very low on health - if (bs->inventory[INVENTORY_HEALTH] < 60) return 0; - //if the bot is low on health - if (bs->inventory[INVENTORY_HEALTH] < 80) { - //if the bot has insufficient armor - if (bs->inventory[INVENTORY_ARMOR] < 40) return 0; - } - //if the bot can use the bfg - if (bs->inventory[INVENTORY_BFG10K] > 0 && - bs->inventory[INVENTORY_BFGAMMO] > 7) return 100; - //if the bot can use the railgun - if (bs->inventory[INVENTORY_RAILGUN] > 0 && - bs->inventory[INVENTORY_SLUGS] > 5) return 95; - //if the bot can use the lightning gun - if (bs->inventory[INVENTORY_LIGHTNING] > 0 && - bs->inventory[INVENTORY_LIGHTNINGAMMO] > 50) return 90; - //if the bot can use the rocketlauncher - if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && - bs->inventory[INVENTORY_ROCKETS] > 5) return 90; - //if the bot can use the plasmagun - if (bs->inventory[INVENTORY_PLASMAGUN] > 0 && - bs->inventory[INVENTORY_CELLS] > 40) return 85; - //if the bot can use the grenade launcher - if (bs->inventory[INVENTORY_GRENADELAUNCHER] > 0 && - bs->inventory[INVENTORY_GRENADES] > 10) return 80; - //if the bot can use the shotgun - if (bs->inventory[INVENTORY_SHOTGUN] > 0 && - bs->inventory[INVENTORY_SHELLS] > 10) return 50; - //otherwise the bot is not feeling too good - return 0; -} - -/* -================== -BotFeelingBad -================== -*/ -float BotFeelingBad(bot_state_t *bs) { - if (bs->weaponnum == WP_GAUNTLET) { - return 100; - } - if (bs->inventory[INVENTORY_HEALTH] < 40) { - return 100; - } - if (bs->weaponnum == WP_MACHINEGUN) { - return 90; - } - if (bs->inventory[INVENTORY_HEALTH] < 60) { - return 80; - } - return 0; -} - -/* -================== -BotWantsToRetreat -================== -*/ -int BotWantsToRetreat(bot_state_t *bs) { - aas_entityinfo_t entinfo; - - if (gametype == GT_CTF) { - //always retreat when carrying a CTF flag - if (BotCTFCarryingFlag(bs)) - return qtrue; - } -#ifdef MISSIONPACK - else if (gametype == GT_1FCTF) { - //if carrying the flag then always retreat - if (Bot1FCTFCarryingFlag(bs)) - return qtrue; - } - else if (gametype == GT_OBELISK) { - //the bots should be dedicated to attacking the enemy obelisk - if (bs->ltgtype == LTG_ATTACKENEMYBASE) { - if (bs->enemy != redobelisk.entitynum || - bs->enemy != blueobelisk.entitynum) { - return qtrue; - } - } - if (BotFeelingBad(bs) > 50) { - return qtrue; - } - return qfalse; - } - else if (gametype == GT_HARVESTER) { - //if carrying cubes then always retreat - if (BotHarvesterCarryingCubes(bs)) return qtrue; - } -#endif - // - if (bs->enemy >= 0) { - //if the enemy is carrying a flag - BotEntityInfo(bs->enemy, &entinfo); - if (EntityCarriesFlag(&entinfo)) - return qfalse; - } - //if the bot is getting the flag - if (bs->ltgtype == LTG_GETFLAG) - return qtrue; - // - if (BotAggression(bs) < 50) - return qtrue; - return qfalse; -} - -/* -================== -BotWantsToChase -================== -*/ -int BotWantsToChase(bot_state_t *bs) { - aas_entityinfo_t entinfo; - - if (gametype == GT_CTF) { - //never chase when carrying a CTF flag - if (BotCTFCarryingFlag(bs)) - return qfalse; - //always chase if the enemy is carrying a flag - BotEntityInfo(bs->enemy, &entinfo); - if (EntityCarriesFlag(&entinfo)) - return qtrue; - } -#ifdef MISSIONPACK - else if (gametype == GT_1FCTF) { - //never chase if carrying the flag - if (Bot1FCTFCarryingFlag(bs)) - return qfalse; - //always chase if the enemy is carrying a flag - BotEntityInfo(bs->enemy, &entinfo); - if (EntityCarriesFlag(&entinfo)) - return qtrue; - } - else if (gametype == GT_OBELISK) { - //the bots should be dedicated to attacking the enemy obelisk - if (bs->ltgtype == LTG_ATTACKENEMYBASE) { - if (bs->enemy != redobelisk.entitynum || - bs->enemy != blueobelisk.entitynum) { - return qfalse; - } - } - } - else if (gametype == GT_HARVESTER) { - //never chase if carrying cubes - if (BotHarvesterCarryingCubes(bs)) - return qfalse; - } -#endif - //if the bot is getting the flag - if (bs->ltgtype == LTG_GETFLAG) - return qfalse; - // - if (BotAggression(bs) > 50) - return qtrue; - return qfalse; -} - -/* -================== -BotWantsToHelp -================== -*/ -int BotWantsToHelp(bot_state_t *bs) { - return qtrue; -} - -/* -================== -BotCanAndWantsToRocketJump -================== -*/ -int BotCanAndWantsToRocketJump(bot_state_t *bs) { - float rocketjumper; - - //if rocket jumping is disabled - if (!bot_rocketjump.integer) return qfalse; - //if no rocket launcher - if (bs->inventory[INVENTORY_ROCKETLAUNCHER] <= 0) return qfalse; - //if low on rockets - if (bs->inventory[INVENTORY_ROCKETS] < 3) return qfalse; - //never rocket jump with the Quad - if (bs->inventory[INVENTORY_QUAD]) return qfalse; - //if low on health - if (bs->inventory[INVENTORY_HEALTH] < 60) return qfalse; - //if not full health - if (bs->inventory[INVENTORY_HEALTH] < 90) { - //if the bot has insufficient armor - if (bs->inventory[INVENTORY_ARMOR] < 40) return qfalse; - } - rocketjumper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_WEAPONJUMPING, 0, 1); - if (rocketjumper < 0.5) return qfalse; - return qtrue; -} - -/* -================== -BotHasPersistantPowerupAndWeapon -================== -*/ -int BotHasPersistantPowerupAndWeapon(bot_state_t *bs) { -#ifdef MISSIONPACK - // if the bot does not have a persistant powerup - if (!bs->inventory[INVENTORY_SCOUT] && - !bs->inventory[INVENTORY_GUARD] && - !bs->inventory[INVENTORY_DOUBLER] && - !bs->inventory[INVENTORY_AMMOREGEN] ) { - return qfalse; - } -#endif - //if the bot is very low on health - if (bs->inventory[INVENTORY_HEALTH] < 60) return qfalse; - //if the bot is low on health - if (bs->inventory[INVENTORY_HEALTH] < 80) { - //if the bot has insufficient armor - if (bs->inventory[INVENTORY_ARMOR] < 40) return qfalse; - } - //if the bot can use the bfg - if (bs->inventory[INVENTORY_BFG10K] > 0 && - bs->inventory[INVENTORY_BFGAMMO] > 7) return qtrue; - //if the bot can use the railgun - if (bs->inventory[INVENTORY_RAILGUN] > 0 && - bs->inventory[INVENTORY_SLUGS] > 5) return qtrue; - //if the bot can use the lightning gun - if (bs->inventory[INVENTORY_LIGHTNING] > 0 && - bs->inventory[INVENTORY_LIGHTNINGAMMO] > 50) return qtrue; - //if the bot can use the rocketlauncher - if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && - bs->inventory[INVENTORY_ROCKETS] > 5) return qtrue; - // - if (bs->inventory[INVENTORY_NAILGUN] > 0 && - bs->inventory[INVENTORY_NAILS] > 5) return qtrue; - // - if (bs->inventory[INVENTORY_PROXLAUNCHER] > 0 && - bs->inventory[INVENTORY_MINES] > 5) return qtrue; - // - if (bs->inventory[INVENTORY_CHAINGUN] > 0 && - bs->inventory[INVENTORY_BELT] > 40) return qtrue; - //if the bot can use the plasmagun - if (bs->inventory[INVENTORY_PLASMAGUN] > 0 && - bs->inventory[INVENTORY_CELLS] > 20) return qtrue; - return qfalse; -} - -/* -================== -BotGoCamp -================== -*/ -void BotGoCamp(bot_state_t *bs, bot_goal_t *goal) { - float camper; - - bs->decisionmaker = bs->client; - //set message time to zero so bot will NOT show any message - bs->teammessage_time = 0; - //set the ltg type - bs->ltgtype = LTG_CAMP; - //set the team goal - memcpy(&bs->teamgoal, goal, sizeof(bot_goal_t)); - //get the team goal time - camper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CAMPER, 0, 1); - if (camper > 0.99) bs->teamgoal_time = FloatTime() + 99999; - else bs->teamgoal_time = FloatTime() + 120 + 180 * camper + random() * 15; - //set the last time the bot started camping - bs->camp_time = FloatTime(); - //the teammate that requested the camping - bs->teammate = 0; - //do NOT type arrive message - bs->arrive_time = 1; -} - -/* -================== -BotWantsToCamp -================== -*/ -int BotWantsToCamp(bot_state_t *bs) { - float camper; - int cs, traveltime, besttraveltime; - bot_goal_t goal, bestgoal; - - camper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CAMPER, 0, 1); - if (camper < 0.1) return qfalse; - //if the bot has a team goal - if (bs->ltgtype == LTG_TEAMHELP || - bs->ltgtype == LTG_TEAMACCOMPANY || - bs->ltgtype == LTG_DEFENDKEYAREA || - bs->ltgtype == LTG_GETFLAG || - bs->ltgtype == LTG_RUSHBASE || - bs->ltgtype == LTG_CAMP || - bs->ltgtype == LTG_CAMPORDER || - bs->ltgtype == LTG_PATROL) { - return qfalse; - } - //if camped recently - if (bs->camp_time > FloatTime() - 60 + 300 * (1-camper)) return qfalse; - // - if (random() > camper) { - bs->camp_time = FloatTime(); - return qfalse; - } - //if the bot isn't healthy anough - if (BotAggression(bs) < 50) return qfalse; - //the bot should have at least have the rocket launcher, the railgun or the bfg10k with some ammo - if ((bs->inventory[INVENTORY_ROCKETLAUNCHER] <= 0 || bs->inventory[INVENTORY_ROCKETS < 10]) && - (bs->inventory[INVENTORY_RAILGUN] <= 0 || bs->inventory[INVENTORY_SLUGS] < 10) && - (bs->inventory[INVENTORY_BFG10K] <= 0 || bs->inventory[INVENTORY_BFGAMMO] < 10)) { - return qfalse; - } - //find the closest camp spot - besttraveltime = 99999; - for (cs = trap_BotGetNextCampSpotGoal(0, &goal); cs; cs = trap_BotGetNextCampSpotGoal(cs, &goal)) { - traveltime = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, goal.areanum, TFL_DEFAULT); - if (traveltime && traveltime < besttraveltime) { - besttraveltime = traveltime; - memcpy(&bestgoal, &goal, sizeof(bot_goal_t)); - } - } - if (besttraveltime > 150) return qfalse; - //ok found a camp spot, go camp there - BotGoCamp(bs, &bestgoal); - bs->ordered = qfalse; - // - return qtrue; -} - -/* -================== -BotDontAvoid -================== -*/ -void BotDontAvoid(bot_state_t *bs, char *itemname) { - bot_goal_t goal; - int num; - - num = trap_BotGetLevelItemGoal(-1, itemname, &goal); - while(num >= 0) { - trap_BotRemoveFromAvoidGoals(bs->gs, goal.number); - num = trap_BotGetLevelItemGoal(num, itemname, &goal); - } -} - -/* -================== -BotGoForPowerups -================== -*/ -void BotGoForPowerups(bot_state_t *bs) { - - //don't avoid any of the powerups anymore - BotDontAvoid(bs, "Quad Damage"); - BotDontAvoid(bs, "Regeneration"); - BotDontAvoid(bs, "Battle Suit"); - BotDontAvoid(bs, "Speed"); - BotDontAvoid(bs, "Invisibility"); - //BotDontAvoid(bs, "Flight"); - //reset the long term goal time so the bot will go for the powerup - //NOTE: the long term goal type doesn't change - bs->ltg_time = 0; -} - -/* -================== -BotRoamGoal -================== -*/ -void BotRoamGoal(bot_state_t *bs, vec3_t goal) { - int pc, i; - float len, rnd; - vec3_t dir, bestorg, belowbestorg; - bsp_trace_t trace; - - for (i = 0; i < 10; i++) { - //start at the bot origin - VectorCopy(bs->origin, bestorg); - rnd = random(); - if (rnd > 0.25) { - //add a random value to the x-coordinate - if (random() < 0.5) bestorg[0] -= 800 * random() + 100; - else bestorg[0] += 800 * random() + 100; - } - if (rnd < 0.75) { - //add a random value to the y-coordinate - if (random() < 0.5) bestorg[1] -= 800 * random() + 100; - else bestorg[1] += 800 * random() + 100; - } - //add a random value to the z-coordinate (NOTE: 48 = maxjump?) - bestorg[2] += 2 * 48 * crandom(); - //trace a line from the origin to the roam target - BotAI_Trace(&trace, bs->origin, NULL, NULL, bestorg, bs->entitynum, MASK_SOLID); - //direction and length towards the roam target - VectorSubtract(trace.endpos, bs->origin, dir); - len = VectorNormalize(dir); - //if the roam target is far away anough - if (len > 200) { - //the roam target is in the given direction before walls - VectorScale(dir, len * trace.fraction - 40, dir); - VectorAdd(bs->origin, dir, bestorg); - //get the coordinates of the floor below the roam target - belowbestorg[0] = bestorg[0]; - belowbestorg[1] = bestorg[1]; - belowbestorg[2] = bestorg[2] - 800; - BotAI_Trace(&trace, bestorg, NULL, NULL, belowbestorg, bs->entitynum, MASK_SOLID); - // - if (!trace.startsolid) { - trace.endpos[2]++; - pc = trap_PointContents(trace.endpos, bs->entitynum); - if (!(pc & (CONTENTS_LAVA | CONTENTS_SLIME))) { - VectorCopy(bestorg, goal); - return; - } - } - } - } - VectorCopy(bestorg, goal); -} - -/* -================== -BotAttackMove -================== -*/ -bot_moveresult_t BotAttackMove(bot_state_t *bs, int tfl) { - int movetype, i, attackentity; - float attack_skill, jumper, croucher, dist, strafechange_time; - float attack_dist, attack_range; - vec3_t forward, backward, sideward, hordir, up = {0, 0, 1}; - aas_entityinfo_t entinfo; - bot_moveresult_t moveresult; - bot_goal_t goal; - - attackentity = bs->enemy; - // - if (bs->attackchase_time > FloatTime()) { - //create the chase goal - goal.entitynum = attackentity; - goal.areanum = bs->lastenemyareanum; - VectorCopy(bs->lastenemyorigin, goal.origin); - VectorSet(goal.mins, -8, -8, -8); - VectorSet(goal.maxs, 8, 8, 8); - //initialize the movement state - BotSetupForMovement(bs); - //move towards the goal - trap_BotMoveToGoal(&moveresult, bs->ms, &goal, tfl); - return moveresult; - } - // - memset(&moveresult, 0, sizeof(bot_moveresult_t)); - // - attack_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ATTACK_SKILL, 0, 1); - jumper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_JUMPER, 0, 1); - croucher = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CROUCHER, 0, 1); - //if the bot is really stupid - if (attack_skill < 0.2) return moveresult; - //initialize the movement state - BotSetupForMovement(bs); - //get the enemy entity info - BotEntityInfo(attackentity, &entinfo); - //direction towards the enemy - VectorSubtract(entinfo.origin, bs->origin, forward); - //the distance towards the enemy - dist = VectorNormalize(forward); - VectorNegate(forward, backward); - //walk, crouch or jump - movetype = MOVE_WALK; - // - if (bs->attackcrouch_time < FloatTime() - 1) { - if (random() < jumper) { - movetype = MOVE_JUMP; - } - //wait at least one second before crouching again - else if (bs->attackcrouch_time < FloatTime() - 1 && random() < croucher) { - bs->attackcrouch_time = FloatTime() + croucher * 5; - } - } - if (bs->attackcrouch_time > FloatTime()) movetype = MOVE_CROUCH; - //if the bot should jump - if (movetype == MOVE_JUMP) { - //if jumped last frame - if (bs->attackjump_time > FloatTime()) { - movetype = MOVE_WALK; - } - else { - bs->attackjump_time = FloatTime() + 1; - } - } - if (bs->cur_ps.weapon == WP_GAUNTLET) { - attack_dist = 0; - attack_range = 0; - } - else { - attack_dist = IDEAL_ATTACKDIST; - attack_range = 40; - } - //if the bot is stupid - if (attack_skill <= 0.4) { - //just walk to or away from the enemy - if (dist > attack_dist + attack_range) { - if (trap_BotMoveInDirection(bs->ms, forward, 400, movetype)) return moveresult; - } - if (dist < attack_dist - attack_range) { - if (trap_BotMoveInDirection(bs->ms, backward, 400, movetype)) return moveresult; - } - return moveresult; - } - //increase the strafe time - bs->attackstrafe_time += bs->thinktime; - //get the strafe change time - strafechange_time = 0.4 + (1 - attack_skill) * 0.2; - if (attack_skill > 0.7) strafechange_time += crandom() * 0.2; - //if the strafe direction should be changed - if (bs->attackstrafe_time > strafechange_time) { - //some magic number :) - if (random() > 0.935) { - //flip the strafe direction - bs->flags ^= BFL_STRAFERIGHT; - bs->attackstrafe_time = 0; - } - } - // - for (i = 0; i < 2; i++) { - hordir[0] = forward[0]; - hordir[1] = forward[1]; - hordir[2] = 0; - VectorNormalize(hordir); - //get the sideward vector - CrossProduct(hordir, up, sideward); - //reverse the vector depending on the strafe direction - if (bs->flags & BFL_STRAFERIGHT) VectorNegate(sideward, sideward); - //randomly go back a little - if (random() > 0.9) { - VectorAdd(sideward, backward, sideward); - } - else { - //walk forward or backward to get at the ideal attack distance - if (dist > attack_dist + attack_range) { - VectorAdd(sideward, forward, sideward); - } - else if (dist < attack_dist - attack_range) { - VectorAdd(sideward, backward, sideward); - } - } - //perform the movement - if (trap_BotMoveInDirection(bs->ms, sideward, 400, movetype)) - return moveresult; - //movement failed, flip the strafe direction - bs->flags ^= BFL_STRAFERIGHT; - bs->attackstrafe_time = 0; - } - //bot couldn't do any usefull movement -// bs->attackchase_time = AAS_Time() + 6; - return moveresult; -} - -/* -================== -BotSameTeam -================== -*/ -int BotSameTeam(bot_state_t *bs, int entnum) { - char info1[1024], info2[1024]; - - if (bs->client < 0 || bs->client >= MAX_CLIENTS) { - //BotAI_Print(PRT_ERROR, "BotSameTeam: client out of range\n"); - return qfalse; - } - if (entnum < 0 || entnum >= MAX_CLIENTS) { - //BotAI_Print(PRT_ERROR, "BotSameTeam: client out of range\n"); - return qfalse; - } - if ( gametype >= GT_TEAM ) { - trap_GetConfigstring(CS_PLAYERS+bs->client, info1, sizeof(info1)); - trap_GetConfigstring(CS_PLAYERS+entnum, info2, sizeof(info2)); - // - if (atoi(Info_ValueForKey(info1, "t")) == atoi(Info_ValueForKey(info2, "t"))) return qtrue; - } - return qfalse; -} - -/* -================== -InFieldOfVision -================== -*/ -qboolean InFieldOfVision(vec3_t viewangles, float fov, vec3_t angles) -{ - int i; - float diff, angle; - - for (i = 0; i < 2; i++) { - angle = AngleMod(viewangles[i]); - angles[i] = AngleMod(angles[i]); - diff = angles[i] - angle; - if (angles[i] > angle) { - if (diff > 180.0) diff -= 360.0; - } - else { - if (diff < -180.0) diff += 360.0; - } - if (diff > 0) { - if (diff > fov * 0.5) return qfalse; - } - else { - if (diff < -fov * 0.5) return qfalse; - } - } - return qtrue; -} - -/* -================== -BotEntityVisible - -returns visibility in the range [0, 1] taking fog and water surfaces into account -================== -*/ -float BotEntityVisible(int viewer, vec3_t eye, vec3_t viewangles, float fov, int ent) { - int i, contents_mask, passent, hitent, infog, inwater, otherinfog, pc; - float squaredfogdist, waterfactor, vis, bestvis; - bsp_trace_t trace; - aas_entityinfo_t entinfo; - vec3_t dir, entangles, start, end, middle; - - //calculate middle of bounding box - BotEntityInfo(ent, &entinfo); - VectorAdd(entinfo.mins, entinfo.maxs, middle); - VectorScale(middle, 0.5, middle); - VectorAdd(entinfo.origin, middle, middle); - //check if entity is within field of vision - VectorSubtract(middle, eye, dir); - vectoangles(dir, entangles); - if (!InFieldOfVision(viewangles, fov, entangles)) return 0; - // - pc = trap_AAS_PointContents(eye); - infog = (pc & CONTENTS_FOG); - inwater = (pc & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)); - // - bestvis = 0; - for (i = 0; i < 3; i++) { - //if the point is not in potential visible sight - //if (!AAS_inPVS(eye, middle)) continue; - // - contents_mask = CONTENTS_SOLID|CONTENTS_PLAYERCLIP; - passent = viewer; - hitent = ent; - VectorCopy(eye, start); - VectorCopy(middle, end); - //if the entity is in water, lava or slime - if (trap_AAS_PointContents(middle) & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) { - contents_mask |= (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER); - } - //if eye is in water, lava or slime - if (inwater) { - if (!(contents_mask & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER))) { - passent = ent; - hitent = viewer; - VectorCopy(middle, start); - VectorCopy(eye, end); - } - contents_mask ^= (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER); - } - //trace from start to end - BotAI_Trace(&trace, start, NULL, NULL, end, passent, contents_mask); - //if water was hit - waterfactor = 1.0; - if (trace.contents & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) { - //if the water surface is translucent - if (1) { - //trace through the water - contents_mask &= ~(CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER); - BotAI_Trace(&trace, trace.endpos, NULL, NULL, end, passent, contents_mask); - waterfactor = 0.5; - } - } - //if a full trace or the hitent was hit - if (trace.fraction >= 1 || trace.ent == hitent) { - //check for fog, assuming there's only one fog brush where - //either the viewer or the entity is in or both are in - otherinfog = (trap_AAS_PointContents(middle) & CONTENTS_FOG); - if (infog && otherinfog) { - VectorSubtract(trace.endpos, eye, dir); - squaredfogdist = VectorLengthSquared(dir); - } - else if (infog) { - VectorCopy(trace.endpos, start); - BotAI_Trace(&trace, start, NULL, NULL, eye, viewer, CONTENTS_FOG); - VectorSubtract(eye, trace.endpos, dir); - squaredfogdist = VectorLengthSquared(dir); - } - else if (otherinfog) { - VectorCopy(trace.endpos, end); - BotAI_Trace(&trace, eye, NULL, NULL, end, viewer, CONTENTS_FOG); - VectorSubtract(end, trace.endpos, dir); - squaredfogdist = VectorLengthSquared(dir); - } - else { - //if the entity and the viewer are not in fog assume there's no fog in between - squaredfogdist = 0; - } - //decrease visibility with the view distance through fog - vis = 1 / ((squaredfogdist * 0.001) < 1 ? 1 : (squaredfogdist * 0.001)); - //if entering water visibility is reduced - vis *= waterfactor; - // - if (vis > bestvis) bestvis = vis; - //if pretty much no fog - if (bestvis >= 0.95) return bestvis; - } - //check bottom and top of bounding box as well - if (i == 0) middle[2] += entinfo.mins[2]; - else if (i == 1) middle[2] += entinfo.maxs[2] - entinfo.mins[2]; - } - return bestvis; -} - -/* -================== -BotFindEnemy -================== -*/ -int BotFindEnemy(bot_state_t *bs, int curenemy) { - int i, healthdecrease; - float f, alertness, easyfragger, vis; - float squaredist, cursquaredist; - aas_entityinfo_t entinfo, curenemyinfo; - vec3_t dir, angles; - - alertness = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ALERTNESS, 0, 1); - easyfragger = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_EASY_FRAGGER, 0, 1); - //check if the health decreased - healthdecrease = bs->lasthealth > bs->inventory[INVENTORY_HEALTH]; - //remember the current health value - bs->lasthealth = bs->inventory[INVENTORY_HEALTH]; - // - if (curenemy >= 0) { - BotEntityInfo(curenemy, &curenemyinfo); - if (EntityCarriesFlag(&curenemyinfo)) return qfalse; - VectorSubtract(curenemyinfo.origin, bs->origin, dir); - cursquaredist = VectorLengthSquared(dir); - } - else { - cursquaredist = 0; - } -#ifdef MISSIONPACK - if (gametype == GT_OBELISK) { - vec3_t target; - bot_goal_t *goal; - bsp_trace_t trace; - - if (BotTeam(bs) == TEAM_RED) - goal = &blueobelisk; - else - goal = &redobelisk; - //if the obelisk is visible - VectorCopy(goal->origin, target); - target[2] += 1; - BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); - if (trace.fraction >= 1 || trace.ent == goal->entitynum) { - if (goal->entitynum == bs->enemy) { - return qfalse; - } - bs->enemy = goal->entitynum; - bs->enemysight_time = FloatTime(); - bs->enemysuicide = qfalse; - bs->enemydeath_time = 0; - bs->enemyvisible_time = FloatTime(); - return qtrue; - } - } -#endif - // - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - - if (i == bs->client) continue; - //if it's the current enemy - if (i == curenemy) continue; - // - BotEntityInfo(i, &entinfo); - // - if (!entinfo.valid) continue; - //if the enemy isn't dead and the enemy isn't the bot self - if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue; - //if the enemy is invisible and not shooting - if (EntityIsInvisible(&entinfo) && !EntityIsShooting(&entinfo)) { - continue; - } - //if not an easy fragger don't shoot at chatting players - if (easyfragger < 0.5 && EntityIsChatting(&entinfo)) continue; - // - if (lastteleport_time > FloatTime() - 3) { - VectorSubtract(entinfo.origin, lastteleport_origin, dir); - if (VectorLengthSquared(dir) < Square(70)) continue; - } - //calculate the distance towards the enemy - VectorSubtract(entinfo.origin, bs->origin, dir); - squaredist = VectorLengthSquared(dir); - //if this entity is not carrying a flag - if (!EntityCarriesFlag(&entinfo)) - { - //if this enemy is further away than the current one - if (curenemy >= 0 && squaredist > cursquaredist) continue; - } //end if - //if the bot has no - if (squaredist > Square(900.0 + alertness * 4000.0)) continue; - //if on the same team - if (BotSameTeam(bs, i)) continue; - //if the bot's health decreased or the enemy is shooting - if (curenemy < 0 && (healthdecrease || EntityIsShooting(&entinfo))) - f = 360; - else - f = 90 + 90 - (90 - (squaredist > Square(810) ? Square(810) : squaredist) / (810 * 9)); - //check if the enemy is visible - vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, f, i); - if (vis <= 0) continue; - //if the enemy is quite far away, not shooting and the bot is not damaged - if (curenemy < 0 && squaredist > Square(100) && !healthdecrease && !EntityIsShooting(&entinfo)) - { - //check if we can avoid this enemy - VectorSubtract(bs->origin, entinfo.origin, dir); - vectoangles(dir, angles); - //if the bot isn't in the fov of the enemy - if (!InFieldOfVision(entinfo.angles, 90, angles)) { - //update some stuff for this enemy - BotUpdateBattleInventory(bs, i); - //if the bot doesn't really want to fight - if (BotWantsToRetreat(bs)) continue; - } - } - //found an enemy - bs->enemy = entinfo.number; - if (curenemy >= 0) bs->enemysight_time = FloatTime() - 2; - else bs->enemysight_time = FloatTime(); - bs->enemysuicide = qfalse; - bs->enemydeath_time = 0; - bs->enemyvisible_time = FloatTime(); - return qtrue; - } - return qfalse; -} - -/* -================== -BotTeamFlagCarrierVisible -================== -*/ -int BotTeamFlagCarrierVisible(bot_state_t *bs) { - int i; - float vis; - aas_entityinfo_t entinfo; - - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - if (i == bs->client) - continue; - // - BotEntityInfo(i, &entinfo); - //if this player is active - if (!entinfo.valid) - continue; - //if this player is carrying a flag - if (!EntityCarriesFlag(&entinfo)) - continue; - //if the flag carrier is not on the same team - if (!BotSameTeam(bs, i)) - continue; - //if the flag carrier is not visible - vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); - if (vis <= 0) - continue; - // - return i; - } - return -1; -} - -/* -================== -BotTeamFlagCarrier -================== -*/ -int BotTeamFlagCarrier(bot_state_t *bs) { - int i; - aas_entityinfo_t entinfo; - - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - if (i == bs->client) - continue; - // - BotEntityInfo(i, &entinfo); - //if this player is active - if (!entinfo.valid) - continue; - //if this player is carrying a flag - if (!EntityCarriesFlag(&entinfo)) - continue; - //if the flag carrier is not on the same team - if (!BotSameTeam(bs, i)) - continue; - // - return i; - } - return -1; -} - -/* -================== -BotEnemyFlagCarrierVisible -================== -*/ -int BotEnemyFlagCarrierVisible(bot_state_t *bs) { - int i; - float vis; - aas_entityinfo_t entinfo; - - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - if (i == bs->client) - continue; - // - BotEntityInfo(i, &entinfo); - //if this player is active - if (!entinfo.valid) - continue; - //if this player is carrying a flag - if (!EntityCarriesFlag(&entinfo)) - continue; - //if the flag carrier is on the same team - if (BotSameTeam(bs, i)) - continue; - //if the flag carrier is not visible - vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); - if (vis <= 0) - continue; - // - return i; - } - return -1; -} - -/* -================== -BotVisibleTeamMatesAndEnemies -================== -*/ -void BotVisibleTeamMatesAndEnemies(bot_state_t *bs, int *teammates, int *enemies, float range) { - int i; - float vis; - aas_entityinfo_t entinfo; - vec3_t dir; - - if (teammates) - *teammates = 0; - if (enemies) - *enemies = 0; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - if (i == bs->client) - continue; - // - BotEntityInfo(i, &entinfo); - //if this player is active - if (!entinfo.valid) - continue; - //if this player is carrying a flag - if (!EntityCarriesFlag(&entinfo)) - continue; - //if not within range - VectorSubtract(entinfo.origin, bs->origin, dir); - if (VectorLengthSquared(dir) > Square(range)) - continue; - //if the flag carrier is not visible - vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); - if (vis <= 0) - continue; - //if the flag carrier is on the same team - if (BotSameTeam(bs, i)) { - if (teammates) - (*teammates)++; - } - else { - if (enemies) - (*enemies)++; - } - } -} - -#ifdef MISSIONPACK -/* -================== -BotTeamCubeCarrierVisible -================== -*/ -int BotTeamCubeCarrierVisible(bot_state_t *bs) { - int i; - float vis; - aas_entityinfo_t entinfo; - - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - if (i == bs->client) continue; - // - BotEntityInfo(i, &entinfo); - //if this player is active - if (!entinfo.valid) continue; - //if this player is carrying a flag - if (!EntityCarriesCubes(&entinfo)) continue; - //if the flag carrier is not on the same team - if (!BotSameTeam(bs, i)) continue; - //if the flag carrier is not visible - vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); - if (vis <= 0) continue; - // - return i; - } - return -1; -} - -/* -================== -BotEnemyCubeCarrierVisible -================== -*/ -int BotEnemyCubeCarrierVisible(bot_state_t *bs) { - int i; - float vis; - aas_entityinfo_t entinfo; - - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - if (i == bs->client) - continue; - // - BotEntityInfo(i, &entinfo); - //if this player is active - if (!entinfo.valid) - continue; - //if this player is carrying a flag - if (!EntityCarriesCubes(&entinfo)) continue; - //if the flag carrier is on the same team - if (BotSameTeam(bs, i)) - continue; - //if the flag carrier is not visible - vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); - if (vis <= 0) - continue; - // - return i; - } - return -1; -} -#endif - -/* -================== -BotAimAtEnemy -================== -*/ -void BotAimAtEnemy(bot_state_t *bs) { - int i, enemyvisible; - float dist, f, aim_skill, aim_accuracy, speed, reactiontime; - vec3_t dir, bestorigin, end, start, groundtarget, cmdmove, enemyvelocity; - vec3_t mins = {-4,-4,-4}, maxs = {4, 4, 4}; - weaponinfo_t wi; - aas_entityinfo_t entinfo; - bot_goal_t goal; - bsp_trace_t trace; - vec3_t target; - - //if the bot has no enemy - if (bs->enemy < 0) { - return; - } - //get the enemy entity information - BotEntityInfo(bs->enemy, &entinfo); - //if this is not a player (should be an obelisk) - if (bs->enemy >= MAX_CLIENTS) { - //if the obelisk is visible - VectorCopy(entinfo.origin, target); -#ifdef MISSIONPACK - // if attacking an obelisk - if ( bs->enemy == redobelisk.entitynum || - bs->enemy == blueobelisk.entitynum ) { - target[2] += 32; - } -#endif - //aim at the obelisk - VectorSubtract(target, bs->eye, dir); - vectoangles(dir, bs->ideal_viewangles); - //set the aim target before trying to attack - VectorCopy(target, bs->aimtarget); - return; - } - // - //BotAI_Print(PRT_MESSAGE, "client %d: aiming at client %d\n", bs->entitynum, bs->enemy); - // - aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL, 0, 1); - aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY, 0, 1); - // - if (aim_skill > 0.95) { - //don't aim too early - reactiontime = 0.5 * trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_REACTIONTIME, 0, 1); - if (bs->enemysight_time > FloatTime() - reactiontime) return; - if (bs->teleport_time > FloatTime() - reactiontime) return; - } - - //get the weapon information - trap_BotGetWeaponInfo(bs->ws, bs->weaponnum, &wi); - //get the weapon specific aim accuracy and or aim skill - if (wi.number == WP_MACHINEGUN) { - aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_MACHINEGUN, 0, 1); - } - else if (wi.number == WP_SHOTGUN) { - aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_SHOTGUN, 0, 1); - } - else if (wi.number == WP_GRENADE_LAUNCHER) { - aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_GRENADELAUNCHER, 0, 1); - aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_GRENADELAUNCHER, 0, 1); - } - else if (wi.number == WP_ROCKET_LAUNCHER) { - aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_ROCKETLAUNCHER, 0, 1); - aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_ROCKETLAUNCHER, 0, 1); - } - else if (wi.number == WP_LIGHTNING) { - aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_LIGHTNING, 0, 1); - } - else if (wi.number == WP_RAILGUN) { - aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_RAILGUN, 0, 1); - } - else if (wi.number == WP_PLASMAGUN) { - aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_PLASMAGUN, 0, 1); - aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_PLASMAGUN, 0, 1); - } - else if (wi.number == WP_BFG) { - aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_BFG10K, 0, 1); - aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_BFG10K, 0, 1); - } - // - if (aim_accuracy <= 0) aim_accuracy = 0.0001f; - //get the enemy entity information - BotEntityInfo(bs->enemy, &entinfo); - //if the enemy is invisible then shoot crappy most of the time - if (EntityIsInvisible(&entinfo)) { - if (random() > 0.1) aim_accuracy *= 0.4f; - } - // - VectorSubtract(entinfo.origin, entinfo.lastvisorigin, enemyvelocity); - VectorScale(enemyvelocity, 1 / entinfo.update_time, enemyvelocity); - //enemy origin and velocity is remembered every 0.5 seconds - if (bs->enemyposition_time < FloatTime()) { - // - bs->enemyposition_time = FloatTime() + 0.5; - VectorCopy(enemyvelocity, bs->enemyvelocity); - VectorCopy(entinfo.origin, bs->enemyorigin); - } - //if not extremely skilled - if (aim_skill < 0.9) { - VectorSubtract(entinfo.origin, bs->enemyorigin, dir); - //if the enemy moved a bit - if (VectorLengthSquared(dir) > Square(48)) { - //if the enemy changed direction - if (DotProduct(bs->enemyvelocity, enemyvelocity) < 0) { - //aim accuracy should be worse now - aim_accuracy *= 0.7f; - } - } - } - //check visibility of enemy - enemyvisible = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy); - //if the enemy is visible - if (enemyvisible) { - // - VectorCopy(entinfo.origin, bestorigin); - bestorigin[2] += 8; - //get the start point shooting from - //NOTE: the x and y projectile start offsets are ignored - VectorCopy(bs->origin, start); - start[2] += bs->cur_ps.viewheight; - start[2] += wi.offset[2]; - // - BotAI_Trace(&trace, start, mins, maxs, bestorigin, bs->entitynum, MASK_SHOT); - //if the enemy is NOT hit - if (trace.fraction <= 1 && trace.ent != entinfo.number) { - bestorigin[2] += 16; - } - //if it is not an instant hit weapon the bot might want to predict the enemy - if (wi.speed) { - // - VectorSubtract(bestorigin, bs->origin, dir); - dist = VectorLength(dir); - VectorSubtract(entinfo.origin, bs->enemyorigin, dir); - //if the enemy is NOT pretty far away and strafing just small steps left and right - if (!(dist > 100 && VectorLengthSquared(dir) < Square(32))) { - //if skilled anough do exact prediction - if (aim_skill > 0.8 && - //if the weapon is ready to fire - bs->cur_ps.weaponstate == WEAPON_READY) { - aas_clientmove_t move; - vec3_t origin; - - VectorSubtract(entinfo.origin, bs->origin, dir); - //distance towards the enemy - dist = VectorLength(dir); - //direction the enemy is moving in - VectorSubtract(entinfo.origin, entinfo.lastvisorigin, dir); - // - VectorScale(dir, 1 / entinfo.update_time, dir); - // - VectorCopy(entinfo.origin, origin); - origin[2] += 1; - // - VectorClear(cmdmove); - //AAS_ClearShownDebugLines(); - trap_AAS_PredictClientMovement(&move, bs->enemy, origin, - PRESENCE_CROUCH, qfalse, - dir, cmdmove, 0, - dist * 10 / wi.speed, 0.1f, 0, 0, qfalse); - VectorCopy(move.endpos, bestorigin); - //BotAI_Print(PRT_MESSAGE, "%1.1f predicted speed = %f, frames = %f\n", FloatTime(), VectorLength(dir), dist * 10 / wi.speed); - } - //if not that skilled do linear prediction - else if (aim_skill > 0.4) { - VectorSubtract(entinfo.origin, bs->origin, dir); - //distance towards the enemy - dist = VectorLength(dir); - //direction the enemy is moving in - VectorSubtract(entinfo.origin, entinfo.lastvisorigin, dir); - dir[2] = 0; - // - speed = VectorNormalize(dir) / entinfo.update_time; - //botimport.Print(PRT_MESSAGE, "speed = %f, wi->speed = %f\n", speed, wi->speed); - //best spot to aim at - VectorMA(entinfo.origin, (dist / wi.speed) * speed, dir, bestorigin); - } - } - } - //if the projectile does radial damage - if (aim_skill > 0.6 && wi.proj.damagetype & DAMAGETYPE_RADIAL) { - //if the enemy isn't standing significantly higher than the bot - if (entinfo.origin[2] < bs->origin[2] + 16) { - //try to aim at the ground in front of the enemy - VectorCopy(entinfo.origin, end); - end[2] -= 64; - BotAI_Trace(&trace, entinfo.origin, NULL, NULL, end, entinfo.number, MASK_SHOT); - // - VectorCopy(bestorigin, groundtarget); - if (trace.startsolid) groundtarget[2] = entinfo.origin[2] - 16; - else groundtarget[2] = trace.endpos[2] - 8; - //trace a line from projectile start to ground target - BotAI_Trace(&trace, start, NULL, NULL, groundtarget, bs->entitynum, MASK_SHOT); - //if hitpoint is not vertically too far from the ground target - if (fabs(trace.endpos[2] - groundtarget[2]) < 50) { - VectorSubtract(trace.endpos, groundtarget, dir); - //if the hitpoint is near anough the ground target - if (VectorLengthSquared(dir) < Square(60)) { - VectorSubtract(trace.endpos, start, dir); - //if the hitpoint is far anough from the bot - if (VectorLengthSquared(dir) > Square(100)) { - //check if the bot is visible from the ground target - trace.endpos[2] += 1; - BotAI_Trace(&trace, trace.endpos, NULL, NULL, entinfo.origin, entinfo.number, MASK_SHOT); - if (trace.fraction >= 1) { - //botimport.Print(PRT_MESSAGE, "%1.1f aiming at ground\n", AAS_Time()); - VectorCopy(groundtarget, bestorigin); - } - } - } - } - } - } - bestorigin[0] += 20 * crandom() * (1 - aim_accuracy); - bestorigin[1] += 20 * crandom() * (1 - aim_accuracy); - bestorigin[2] += 10 * crandom() * (1 - aim_accuracy); - } - else { - // - VectorCopy(bs->lastenemyorigin, bestorigin); - bestorigin[2] += 8; - //if the bot is skilled anough - if (aim_skill > 0.5) { - //do prediction shots around corners - if (wi.number == WP_BFG || - wi.number == WP_ROCKET_LAUNCHER || - wi.number == WP_GRENADE_LAUNCHER) { - //create the chase goal - goal.entitynum = bs->client; - goal.areanum = bs->areanum; - VectorCopy(bs->eye, goal.origin); - VectorSet(goal.mins, -8, -8, -8); - VectorSet(goal.maxs, 8, 8, 8); - // - if (trap_BotPredictVisiblePosition(bs->lastenemyorigin, bs->lastenemyareanum, &goal, TFL_DEFAULT, target)) { - VectorSubtract(target, bs->eye, dir); - if (VectorLengthSquared(dir) > Square(80)) { - VectorCopy(target, bestorigin); - bestorigin[2] -= 20; - } - } - aim_accuracy = 1; - } - } - } - // - if (enemyvisible) { - BotAI_Trace(&trace, bs->eye, NULL, NULL, bestorigin, bs->entitynum, MASK_SHOT); - VectorCopy(trace.endpos, bs->aimtarget); - } - else { - VectorCopy(bestorigin, bs->aimtarget); - } - //get aim direction - VectorSubtract(bestorigin, bs->eye, dir); - // - if (wi.number == WP_MACHINEGUN || - wi.number == WP_SHOTGUN || - wi.number == WP_LIGHTNING || - wi.number == WP_RAILGUN) { - //distance towards the enemy - dist = VectorLength(dir); - if (dist > 150) dist = 150; - f = 0.6 + dist / 150 * 0.4; - aim_accuracy *= f; - } - //add some random stuff to the aim direction depending on the aim accuracy - if (aim_accuracy < 0.8) { - VectorNormalize(dir); - for (i = 0; i < 3; i++) dir[i] += 0.3 * crandom() * (1 - aim_accuracy); - } - //set the ideal view angles - vectoangles(dir, bs->ideal_viewangles); - //take the weapon spread into account for lower skilled bots - bs->ideal_viewangles[PITCH] += 6 * wi.vspread * crandom() * (1 - aim_accuracy); - bs->ideal_viewangles[PITCH] = AngleMod(bs->ideal_viewangles[PITCH]); - bs->ideal_viewangles[YAW] += 6 * wi.hspread * crandom() * (1 - aim_accuracy); - bs->ideal_viewangles[YAW] = AngleMod(bs->ideal_viewangles[YAW]); - //if the bots should be really challenging - if (bot_challenge.integer) { - //if the bot is really accurate and has the enemy in view for some time - if (aim_accuracy > 0.9 && bs->enemysight_time < FloatTime() - 1) { - //set the view angles directly - if (bs->ideal_viewangles[PITCH] > 180) bs->ideal_viewangles[PITCH] -= 360; - VectorCopy(bs->ideal_viewangles, bs->viewangles); - trap_EA_View(bs->client, bs->viewangles); - } - } -} - -/* -================== -BotCheckAttack -================== -*/ -void BotCheckAttack(bot_state_t *bs) { - float points, reactiontime, fov, firethrottle; - int attackentity; - bsp_trace_t bsptrace; - //float selfpreservation; - vec3_t forward, right, start, end, dir, angles; - weaponinfo_t wi; - bsp_trace_t trace; - aas_entityinfo_t entinfo; - vec3_t mins = {-8, -8, -8}, maxs = {8, 8, 8}; - - attackentity = bs->enemy; - // - BotEntityInfo(attackentity, &entinfo); - // if not attacking a player - if (attackentity >= MAX_CLIENTS) { -#ifdef MISSIONPACK - // if attacking an obelisk - if ( entinfo.number == redobelisk.entitynum || - entinfo.number == blueobelisk.entitynum ) { - // if obelisk is respawning return - if ( g_entities[entinfo.number].activator && - g_entities[entinfo.number].activator->s.frame == 2 ) { - return; - } - } -#endif - } - // - reactiontime = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_REACTIONTIME, 0, 1); - if (bs->enemysight_time > FloatTime() - reactiontime) return; - if (bs->teleport_time > FloatTime() - reactiontime) return; - //if changing weapons - if (bs->weaponchange_time > FloatTime() - 0.1) return; - //check fire throttle characteristic - if (bs->firethrottlewait_time > FloatTime()) return; - firethrottle = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_FIRETHROTTLE, 0, 1); - if (bs->firethrottleshoot_time < FloatTime()) { - if (random() > firethrottle) { - bs->firethrottlewait_time = FloatTime() + firethrottle; - bs->firethrottleshoot_time = 0; - } - else { - bs->firethrottleshoot_time = FloatTime() + 1 - firethrottle; - bs->firethrottlewait_time = 0; - } - } - // - // - VectorSubtract(bs->aimtarget, bs->eye, dir); - // - if (bs->weaponnum == WP_GAUNTLET) { - if (VectorLengthSquared(dir) > Square(60)) { - return; - } - } - if (VectorLengthSquared(dir) < Square(100)) - fov = 120; - else - fov = 50; - // - vectoangles(dir, angles); - if (!InFieldOfVision(bs->viewangles, fov, angles)) - return; - BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, bs->aimtarget, bs->client, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); - if (bsptrace.fraction < 1 && bsptrace.ent != attackentity) - return; - - //get the weapon info - trap_BotGetWeaponInfo(bs->ws, bs->weaponnum, &wi); - //get the start point shooting from - VectorCopy(bs->origin, start); - start[2] += bs->cur_ps.viewheight; - AngleVectors(bs->viewangles, forward, right, NULL); - start[0] += forward[0] * wi.offset[0] + right[0] * wi.offset[1]; - start[1] += forward[1] * wi.offset[0] + right[1] * wi.offset[1]; - start[2] += forward[2] * wi.offset[0] + right[2] * wi.offset[1] + wi.offset[2]; - //end point aiming at - VectorMA(start, 1000, forward, end); - //a little back to make sure not inside a very close enemy - VectorMA(start, -12, forward, start); - BotAI_Trace(&trace, start, mins, maxs, end, bs->entitynum, MASK_SHOT); - //if the entity is a client - if (trace.ent > 0 && trace.ent <= MAX_CLIENTS) { - if (trace.ent != attackentity) { - //if a teammate is hit - if (BotSameTeam(bs, trace.ent)) - return; - } - } - //if won't hit the enemy or not attacking a player (obelisk) - if (trace.ent != attackentity || attackentity >= MAX_CLIENTS) { - //if the projectile does radial damage - if (wi.proj.damagetype & DAMAGETYPE_RADIAL) { - if (trace.fraction * 1000 < wi.proj.radius) { - points = (wi.proj.damage - 0.5 * trace.fraction * 1000) * 0.5; - if (points > 0) { - return; - } - } - //FIXME: check if a teammate gets radial damage - } - } - //if fire has to be release to activate weapon - if (wi.flags & WFL_FIRERELEASED) { - if (bs->flags & BFL_ATTACKED) { - trap_EA_Attack(bs->client); - } - } - else { - trap_EA_Attack(bs->client); - } - bs->flags ^= BFL_ATTACKED; -} - -/* -================== -BotMapScripts -================== -*/ -void BotMapScripts(bot_state_t *bs) { - char info[1024]; - char mapname[128]; - int i, shootbutton; - float aim_accuracy; - aas_entityinfo_t entinfo; - vec3_t dir; - - trap_GetServerinfo(info, sizeof(info)); - - strncpy(mapname, Info_ValueForKey( info, "mapname" ), sizeof(mapname)-1); - mapname[sizeof(mapname)-1] = '\0'; - - if (!Q_stricmp(mapname, "q3tourney6")) { - vec3_t mins = {700, 204, 672}, maxs = {964, 468, 680}; - vec3_t buttonorg = {304, 352, 920}; - //NOTE: NEVER use the func_bobbing in q3tourney6 - bs->tfl &= ~TFL_FUNCBOB; - //if the bot is below the bounding box - if (bs->origin[0] > mins[0] && bs->origin[0] < maxs[0]) { - if (bs->origin[1] > mins[1] && bs->origin[1] < maxs[1]) { - if (bs->origin[2] < mins[2]) { - return; - } - } - } - shootbutton = qfalse; - //if an enemy is below this bounding box then shoot the button - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - - if (i == bs->client) continue; - // - BotEntityInfo(i, &entinfo); - // - if (!entinfo.valid) continue; - //if the enemy isn't dead and the enemy isn't the bot self - if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue; - // - if (entinfo.origin[0] > mins[0] && entinfo.origin[0] < maxs[0]) { - if (entinfo.origin[1] > mins[1] && entinfo.origin[1] < maxs[1]) { - if (entinfo.origin[2] < mins[2]) { - //if there's a team mate below the crusher - if (BotSameTeam(bs, i)) { - shootbutton = qfalse; - break; - } - else { - shootbutton = qtrue; - } - } - } - } - } - if (shootbutton) { - bs->flags |= BFL_IDEALVIEWSET; - VectorSubtract(buttonorg, bs->eye, dir); - vectoangles(dir, bs->ideal_viewangles); - aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY, 0, 1); - bs->ideal_viewangles[PITCH] += 8 * crandom() * (1 - aim_accuracy); - bs->ideal_viewangles[PITCH] = AngleMod(bs->ideal_viewangles[PITCH]); - bs->ideal_viewangles[YAW] += 8 * crandom() * (1 - aim_accuracy); - bs->ideal_viewangles[YAW] = AngleMod(bs->ideal_viewangles[YAW]); - // - if (InFieldOfVision(bs->viewangles, 20, bs->ideal_viewangles)) { - trap_EA_Attack(bs->client); - } - } - } - else if (!Q_stricmp(mapname, "mpq3tourney6")) { - //NOTE: NEVER use the func_bobbing in mpq3tourney6 - bs->tfl &= ~TFL_FUNCBOB; - } -} - -/* -================== -BotSetMovedir -================== -*/ -// bk001205 - made these static -static vec3_t VEC_UP = {0, -1, 0}; -static vec3_t MOVEDIR_UP = {0, 0, 1}; -static vec3_t VEC_DOWN = {0, -2, 0}; -static vec3_t MOVEDIR_DOWN = {0, 0, -1}; - -void BotSetMovedir(vec3_t angles, vec3_t movedir) { - if (VectorCompare(angles, VEC_UP)) { - VectorCopy(MOVEDIR_UP, movedir); - } - else if (VectorCompare(angles, VEC_DOWN)) { - VectorCopy(MOVEDIR_DOWN, movedir); - } - else { - AngleVectors(angles, movedir, NULL, NULL); - } -} - -/* -================== -BotModelMinsMaxs - -this is ugly -================== -*/ -int BotModelMinsMaxs(int modelindex, int eType, int contents, vec3_t mins, vec3_t maxs) { - gentity_t *ent; - int i; - - ent = &g_entities[0]; - for (i = 0; i < level.num_entities; i++, ent++) { - if ( !ent->inuse ) { - continue; - } - if ( eType && ent->s.eType != eType) { - continue; - } - if ( contents && ent->r.contents != contents) { - continue; - } - if (ent->s.modelindex == modelindex) { - if (mins) - VectorAdd(ent->r.currentOrigin, ent->r.mins, mins); - if (maxs) - VectorAdd(ent->r.currentOrigin, ent->r.maxs, maxs); - return i; - } - } - if (mins) - VectorClear(mins); - if (maxs) - VectorClear(maxs); - return 0; -} - -/* -================== -BotFuncButtonGoal -================== -*/ -int BotFuncButtonActivateGoal(bot_state_t *bs, int bspent, bot_activategoal_t *activategoal) { - int i, areas[10], numareas, modelindex, entitynum; - char model[128]; - float lip, dist, health, angle; - vec3_t size, start, end, mins, maxs, angles, points[10]; - vec3_t movedir, origin, goalorigin, bboxmins, bboxmaxs; - vec3_t extramins = {1, 1, 1}, extramaxs = {-1, -1, -1}; - bsp_trace_t bsptrace; - - activategoal->shoot = qfalse; - VectorClear(activategoal->target); - //create a bot goal towards the button - trap_AAS_ValueForBSPEpairKey(bspent, "model", model, sizeof(model)); - if (!*model) - return qfalse; - modelindex = atoi(model+1); - if (!modelindex) - return qfalse; - VectorClear(angles); - entitynum = BotModelMinsMaxs(modelindex, ET_MOVER, 0, mins, maxs); - //get the lip of the button - trap_AAS_FloatForBSPEpairKey(bspent, "lip", &lip); - if (!lip) lip = 4; - //get the move direction from the angle - trap_AAS_FloatForBSPEpairKey(bspent, "angle", &angle); - VectorSet(angles, 0, angle, 0); - BotSetMovedir(angles, movedir); - //button size - VectorSubtract(maxs, mins, size); - //button origin - VectorAdd(mins, maxs, origin); - VectorScale(origin, 0.5, origin); - //touch distance of the button - dist = fabs(movedir[0]) * size[0] + fabs(movedir[1]) * size[1] + fabs(movedir[2]) * size[2]; - dist *= 0.5; - // - trap_AAS_FloatForBSPEpairKey(bspent, "health", &health); - //if the button is shootable - if (health) { - //calculate the shoot target - VectorMA(origin, -dist, movedir, goalorigin); - // - VectorCopy(goalorigin, activategoal->target); - activategoal->shoot = qtrue; - // - BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, goalorigin, bs->entitynum, MASK_SHOT); - // if the button is visible from the current position - if (bsptrace.fraction >= 1.0 || bsptrace.ent == entitynum) { - // - activategoal->goal.entitynum = entitynum; //NOTE: this is the entity number of the shootable button - activategoal->goal.number = 0; - activategoal->goal.flags = 0; - VectorCopy(bs->origin, activategoal->goal.origin); - activategoal->goal.areanum = bs->areanum; - VectorSet(activategoal->goal.mins, -8, -8, -8); - VectorSet(activategoal->goal.maxs, 8, 8, 8); - // - return qtrue; - } - else { - //create a goal from where the button is visible and shoot at the button from there - //add bounding box size to the dist - trap_AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, bboxmins, bboxmaxs); - for (i = 0; i < 3; i++) { - if (movedir[i] < 0) dist += fabs(movedir[i]) * fabs(bboxmaxs[i]); - else dist += fabs(movedir[i]) * fabs(bboxmins[i]); - } - //calculate the goal origin - VectorMA(origin, -dist, movedir, goalorigin); - // - VectorCopy(goalorigin, start); - start[2] += 24; - VectorCopy(start, end); - end[2] -= 512; - numareas = trap_AAS_TraceAreas(start, end, areas, points, 10); - // - for (i = numareas-1; i >= 0; i--) { - if (trap_AAS_AreaReachability(areas[i])) { - break; - } - } - if (i < 0) { - // FIXME: trace forward and maybe in other directions to find a valid area - } - if (i >= 0) { - // - VectorCopy(points[i], activategoal->goal.origin); - activategoal->goal.areanum = areas[i]; - VectorSet(activategoal->goal.mins, 8, 8, 8); - VectorSet(activategoal->goal.maxs, -8, -8, -8); - // - for (i = 0; i < 3; i++) - { - if (movedir[i] < 0) activategoal->goal.maxs[i] += fabs(movedir[i]) * fabs(extramaxs[i]); - else activategoal->goal.mins[i] += fabs(movedir[i]) * fabs(extramins[i]); - } //end for - // - activategoal->goal.entitynum = entitynum; - activategoal->goal.number = 0; - activategoal->goal.flags = 0; - return qtrue; - } - } - return qfalse; - } - else { - //add bounding box size to the dist - trap_AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, bboxmins, bboxmaxs); - for (i = 0; i < 3; i++) { - if (movedir[i] < 0) dist += fabs(movedir[i]) * fabs(bboxmaxs[i]); - else dist += fabs(movedir[i]) * fabs(bboxmins[i]); - } - //calculate the goal origin - VectorMA(origin, -dist, movedir, goalorigin); - // - VectorCopy(goalorigin, start); - start[2] += 24; - VectorCopy(start, end); - end[2] -= 100; - numareas = trap_AAS_TraceAreas(start, end, areas, NULL, 10); - // - for (i = 0; i < numareas; i++) { - if (trap_AAS_AreaReachability(areas[i])) { - break; - } - } - if (i < numareas) { - // - VectorCopy(origin, activategoal->goal.origin); - activategoal->goal.areanum = areas[i]; - VectorSubtract(mins, origin, activategoal->goal.mins); - VectorSubtract(maxs, origin, activategoal->goal.maxs); - // - for (i = 0; i < 3; i++) - { - if (movedir[i] < 0) activategoal->goal.maxs[i] += fabs(movedir[i]) * fabs(extramaxs[i]); - else activategoal->goal.mins[i] += fabs(movedir[i]) * fabs(extramins[i]); - } //end for - // - activategoal->goal.entitynum = entitynum; - activategoal->goal.number = 0; - activategoal->goal.flags = 0; - return qtrue; - } - } - return qfalse; -} - -/* -================== -BotFuncDoorGoal -================== -*/ -int BotFuncDoorActivateGoal(bot_state_t *bs, int bspent, bot_activategoal_t *activategoal) { - int modelindex, entitynum; - char model[MAX_INFO_STRING]; - vec3_t mins, maxs, origin, angles; - - //shoot at the shootable door - trap_AAS_ValueForBSPEpairKey(bspent, "model", model, sizeof(model)); - if (!*model) - return qfalse; - modelindex = atoi(model+1); - if (!modelindex) - return qfalse; - VectorClear(angles); - entitynum = BotModelMinsMaxs(modelindex, ET_MOVER, 0, mins, maxs); - //door origin - VectorAdd(mins, maxs, origin); - VectorScale(origin, 0.5, origin); - VectorCopy(origin, activategoal->target); - activategoal->shoot = qtrue; - // - activategoal->goal.entitynum = entitynum; //NOTE: this is the entity number of the shootable door - activategoal->goal.number = 0; - activategoal->goal.flags = 0; - VectorCopy(bs->origin, activategoal->goal.origin); - activategoal->goal.areanum = bs->areanum; - VectorSet(activategoal->goal.mins, -8, -8, -8); - VectorSet(activategoal->goal.maxs, 8, 8, 8); - return qtrue; -} - -/* -================== -BotTriggerMultipleGoal -================== -*/ -int BotTriggerMultipleActivateGoal(bot_state_t *bs, int bspent, bot_activategoal_t *activategoal) { - int i, areas[10], numareas, modelindex, entitynum; - char model[128]; - vec3_t start, end, mins, maxs, angles; - vec3_t origin, goalorigin; - - activategoal->shoot = qfalse; - VectorClear(activategoal->target); - //create a bot goal towards the trigger - trap_AAS_ValueForBSPEpairKey(bspent, "model", model, sizeof(model)); - if (!*model) - return qfalse; - modelindex = atoi(model+1); - if (!modelindex) - return qfalse; - VectorClear(angles); - entitynum = BotModelMinsMaxs(modelindex, 0, CONTENTS_TRIGGER, mins, maxs); - //trigger origin - VectorAdd(mins, maxs, origin); - VectorScale(origin, 0.5, origin); - VectorCopy(origin, goalorigin); - // - VectorCopy(goalorigin, start); - start[2] += 24; - VectorCopy(start, end); - end[2] -= 100; - numareas = trap_AAS_TraceAreas(start, end, areas, NULL, 10); - // - for (i = 0; i < numareas; i++) { - if (trap_AAS_AreaReachability(areas[i])) { - break; - } - } - if (i < numareas) { - VectorCopy(origin, activategoal->goal.origin); - activategoal->goal.areanum = areas[i]; - VectorSubtract(mins, origin, activategoal->goal.mins); - VectorSubtract(maxs, origin, activategoal->goal.maxs); - // - activategoal->goal.entitynum = entitynum; - activategoal->goal.number = 0; - activategoal->goal.flags = 0; - return qtrue; - } - return qfalse; -} - -/* -================== -BotPopFromActivateGoalStack -================== -*/ -int BotPopFromActivateGoalStack(bot_state_t *bs) { - if (!bs->activatestack) - return qfalse; - BotEnableActivateGoalAreas(bs->activatestack, qtrue); - bs->activatestack->inuse = qfalse; - bs->activatestack->justused_time = FloatTime(); - bs->activatestack = bs->activatestack->next; - return qtrue; -} - -/* -================== -BotPushOntoActivateGoalStack -================== -*/ -int BotPushOntoActivateGoalStack(bot_state_t *bs, bot_activategoal_t *activategoal) { - int i, best; - float besttime; - - best = -1; - besttime = FloatTime() + 9999; - // - for (i = 0; i < MAX_ACTIVATESTACK; i++) { - if (!bs->activategoalheap[i].inuse) { - if (bs->activategoalheap[i].justused_time < besttime) { - besttime = bs->activategoalheap[i].justused_time; - best = i; - } - } - } - if (best != -1) { - memcpy(&bs->activategoalheap[best], activategoal, sizeof(bot_activategoal_t)); - bs->activategoalheap[best].inuse = qtrue; - bs->activategoalheap[best].next = bs->activatestack; - bs->activatestack = &bs->activategoalheap[best]; - return qtrue; - } - return qfalse; -} - -/* -================== -BotClearActivateGoalStack -================== -*/ -void BotClearActivateGoalStack(bot_state_t *bs) { - while(bs->activatestack) - BotPopFromActivateGoalStack(bs); -} - -/* -================== -BotEnableActivateGoalAreas -================== -*/ -void BotEnableActivateGoalAreas(bot_activategoal_t *activategoal, int enable) { - int i; - - if (activategoal->areasdisabled == !enable) - return; - for (i = 0; i < activategoal->numareas; i++) - trap_AAS_EnableRoutingArea( activategoal->areas[i], enable ); - activategoal->areasdisabled = !enable; -} - -/* -================== -BotIsGoingToActivateEntity -================== -*/ -int BotIsGoingToActivateEntity(bot_state_t *bs, int entitynum) { - bot_activategoal_t *a; - int i; - - for (a = bs->activatestack; a; a = a->next) { - if (a->time < FloatTime()) - continue; - if (a->goal.entitynum == entitynum) - return qtrue; - } - for (i = 0; i < MAX_ACTIVATESTACK; i++) { - if (bs->activategoalheap[i].inuse) - continue; - // - if (bs->activategoalheap[i].goal.entitynum == entitynum) { - // if the bot went for this goal less than 2 seconds ago - if (bs->activategoalheap[i].justused_time > FloatTime() - 2) - return qtrue; - } - } - return qfalse; -} - -/* -================== -BotGetActivateGoal - - returns the number of the bsp entity to activate - goal->entitynum will be set to the game entity to activate -================== -*/ -//#define OBSTACLEDEBUG - -int BotGetActivateGoal(bot_state_t *bs, int entitynum, bot_activategoal_t *activategoal) { - int i, ent, cur_entities[10], spawnflags, modelindex, areas[MAX_ACTIVATEAREAS*2], numareas, t; - char model[MAX_INFO_STRING], tmpmodel[128]; - char target[128], classname[128]; - float health; - char targetname[10][128]; - aas_entityinfo_t entinfo; - aas_areainfo_t areainfo; - vec3_t origin, angles, absmins, absmaxs; - - memset(activategoal, 0, sizeof(bot_activategoal_t)); - BotEntityInfo(entitynum, &entinfo); - Com_sprintf(model, sizeof( model ), "*%d", entinfo.modelindex); - for (ent = trap_AAS_NextBSPEntity(0); ent; ent = trap_AAS_NextBSPEntity(ent)) { - if (!trap_AAS_ValueForBSPEpairKey(ent, "model", tmpmodel, sizeof(tmpmodel))) continue; - if (!strcmp(model, tmpmodel)) break; - } - if (!ent) { - BotAI_Print(PRT_ERROR, "BotGetActivateGoal: no entity found with model %s\n", model); - return 0; - } - trap_AAS_ValueForBSPEpairKey(ent, "classname", classname, sizeof(classname)); - if (!classname) { - BotAI_Print(PRT_ERROR, "BotGetActivateGoal: entity with model %s has no classname\n", model); - return 0; - } - //if it is a door - if (!strcmp(classname, "func_door")) { - if (trap_AAS_FloatForBSPEpairKey(ent, "health", &health)) { - //if the door has health then the door must be shot to open - if (health) { - BotFuncDoorActivateGoal(bs, ent, activategoal); - return ent; - } - } - // - trap_AAS_IntForBSPEpairKey(ent, "spawnflags", &spawnflags); - // if the door starts open then just wait for the door to return - if ( spawnflags & 1 ) - return 0; - //get the door origin - if (!trap_AAS_VectorForBSPEpairKey(ent, "origin", origin)) { - VectorClear(origin); - } - //if the door is open or opening already - if (!VectorCompare(origin, entinfo.origin)) - return 0; - // store all the areas the door is in - trap_AAS_ValueForBSPEpairKey(ent, "model", model, sizeof(model)); - if (*model) { - modelindex = atoi(model+1); - if (modelindex) { - VectorClear(angles); - BotModelMinsMaxs(modelindex, ET_MOVER, 0, absmins, absmaxs); - // - numareas = trap_AAS_BBoxAreas(absmins, absmaxs, areas, MAX_ACTIVATEAREAS*2); - // store the areas with reachabilities first - for (i = 0; i < numareas; i++) { - if (activategoal->numareas >= MAX_ACTIVATEAREAS) - break; - if ( !trap_AAS_AreaReachability(areas[i]) ) { - continue; - } - trap_AAS_AreaInfo(areas[i], &areainfo); - if (areainfo.contents & AREACONTENTS_MOVER) { - activategoal->areas[activategoal->numareas++] = areas[i]; - } - } - // store any remaining areas - for (i = 0; i < numareas; i++) { - if (activategoal->numareas >= MAX_ACTIVATEAREAS) - break; - if ( trap_AAS_AreaReachability(areas[i]) ) { - continue; - } - trap_AAS_AreaInfo(areas[i], &areainfo); - if (areainfo.contents & AREACONTENTS_MOVER) { - activategoal->areas[activategoal->numareas++] = areas[i]; - } - } - } - } - } - // if the bot is blocked by or standing on top of a button - if (!strcmp(classname, "func_button")) { - return 0; - } - // get the targetname so we can find an entity with a matching target - if (!trap_AAS_ValueForBSPEpairKey(ent, "targetname", targetname[0], sizeof(targetname[0]))) { - if (bot_developer.integer) { - BotAI_Print(PRT_ERROR, "BotGetActivateGoal: entity with model \"%s\" has no targetname\n", model); - } - return 0; - } - // allow tree-like activation - cur_entities[0] = trap_AAS_NextBSPEntity(0); - for (i = 0; i >= 0 && i < 10;) { - for (ent = cur_entities[i]; ent; ent = trap_AAS_NextBSPEntity(ent)) { - if (!trap_AAS_ValueForBSPEpairKey(ent, "target", target, sizeof(target))) continue; - if (!strcmp(targetname[i], target)) { - cur_entities[i] = trap_AAS_NextBSPEntity(ent); - break; - } - } - if (!ent) { - if (bot_developer.integer) { - BotAI_Print(PRT_ERROR, "BotGetActivateGoal: no entity with target \"%s\"\n", targetname[i]); - } - i--; - continue; - } - if (!trap_AAS_ValueForBSPEpairKey(ent, "classname", classname, sizeof(classname))) { - if (bot_developer.integer) { - BotAI_Print(PRT_ERROR, "BotGetActivateGoal: entity with target \"%s\" has no classname\n", targetname[i]); - } - continue; - } - // BSP button model - if (!strcmp(classname, "func_button")) { - // - if (!BotFuncButtonActivateGoal(bs, ent, activategoal)) - continue; - // if the bot tries to activate this button already - if ( bs->activatestack && bs->activatestack->inuse && - bs->activatestack->goal.entitynum == activategoal->goal.entitynum && - bs->activatestack->time > FloatTime() && - bs->activatestack->start_time < FloatTime() - 2) - continue; - // if the bot is in a reachability area - if ( trap_AAS_AreaReachability(bs->areanum) ) { - // disable all areas the blocking entity is in - BotEnableActivateGoalAreas( activategoal, qfalse ); - // - t = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, activategoal->goal.areanum, bs->tfl); - // if the button is not reachable - if (!t) { - continue; - } - activategoal->time = FloatTime() + t * 0.01 + 5; - } - return ent; - } - // invisible trigger multiple box - else if (!strcmp(classname, "trigger_multiple")) { - // - if (!BotTriggerMultipleActivateGoal(bs, ent, activategoal)) - continue; - // if the bot tries to activate this trigger already - if ( bs->activatestack && bs->activatestack->inuse && - bs->activatestack->goal.entitynum == activategoal->goal.entitynum && - bs->activatestack->time > FloatTime() && - bs->activatestack->start_time < FloatTime() - 2) - continue; - // if the bot is in a reachability area - if ( trap_AAS_AreaReachability(bs->areanum) ) { - // disable all areas the blocking entity is in - BotEnableActivateGoalAreas( activategoal, qfalse ); - // - t = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, activategoal->goal.areanum, bs->tfl); - // if the trigger is not reachable - if (!t) { - continue; - } - activategoal->time = FloatTime() + t * 0.01 + 5; - } - return ent; - } - else if (!strcmp(classname, "func_timer")) { - // just skip the func_timer - continue; - } - // the actual button or trigger might be linked through a target_relay or target_delay - else if (!strcmp(classname, "target_relay") || !strcmp(classname, "target_delay")) { - if (trap_AAS_ValueForBSPEpairKey(ent, "targetname", targetname[i+1], sizeof(targetname[0]))) { - i++; - cur_entities[i] = trap_AAS_NextBSPEntity(0); - } - } - } -#ifdef OBSTACLEDEBUG - BotAI_Print(PRT_ERROR, "BotGetActivateGoal: no valid activator for entity with target \"%s\"\n", targetname[0]); -#endif - return 0; -} - -/* -================== -BotGoForActivateGoal -================== -*/ -int BotGoForActivateGoal(bot_state_t *bs, bot_activategoal_t *activategoal) { - aas_entityinfo_t activateinfo; - - activategoal->inuse = qtrue; - if (!activategoal->time) - activategoal->time = FloatTime() + 10; - activategoal->start_time = FloatTime(); - BotEntityInfo(activategoal->goal.entitynum, &activateinfo); - VectorCopy(activateinfo.origin, activategoal->origin); - // - if (BotPushOntoActivateGoalStack(bs, activategoal)) { - // enter the activate entity AI node - AIEnter_Seek_ActivateEntity(bs, "BotGoForActivateGoal"); - return qtrue; - } - else { - // enable any routing areas that were disabled - BotEnableActivateGoalAreas(activategoal, qtrue); - return qfalse; - } -} - -/* -================== -BotPrintActivateGoalInfo -================== -*/ -void BotPrintActivateGoalInfo(bot_state_t *bs, bot_activategoal_t *activategoal, int bspent) { - char netname[MAX_NETNAME]; - char classname[128]; - char buf[128]; - - ClientName(bs->client, netname, sizeof(netname)); - trap_AAS_ValueForBSPEpairKey(bspent, "classname", classname, sizeof(classname)); - if (activategoal->shoot) { - Com_sprintf(buf, sizeof(buf), "%s: I have to shoot at a %s from %1.1f %1.1f %1.1f in area %d\n", - netname, classname, - activategoal->goal.origin[0], - activategoal->goal.origin[1], - activategoal->goal.origin[2], - activategoal->goal.areanum); - } - else { - Com_sprintf(buf, sizeof(buf), "%s: I have to activate a %s at %1.1f %1.1f %1.1f in area %d\n", - netname, classname, - activategoal->goal.origin[0], - activategoal->goal.origin[1], - activategoal->goal.origin[2], - activategoal->goal.areanum); - } - trap_EA_Say(bs->client, buf); -} - -/* -================== -BotRandomMove -================== -*/ -void BotRandomMove(bot_state_t *bs, bot_moveresult_t *moveresult) { - vec3_t dir, angles; - - angles[0] = 0; - angles[1] = random() * 360; - angles[2] = 0; - AngleVectors(angles, dir, NULL, NULL); - - trap_BotMoveInDirection(bs->ms, dir, 400, MOVE_WALK); - - moveresult->failure = qfalse; - VectorCopy(dir, moveresult->movedir); -} - -/* -================== -BotAIBlocked - -Very basic handling of bots being blocked by other entities. -Check what kind of entity is blocking the bot and try to activate -it. If that's not an option then try to walk around or over the entity. -Before the bot ends in this part of the AI it should predict which doors to -open, which buttons to activate etc. -================== -*/ -void BotAIBlocked(bot_state_t *bs, bot_moveresult_t *moveresult, int activate) { - int movetype, bspent; - vec3_t hordir, start, end, mins, maxs, sideward, angles, up = {0, 0, 1}; - aas_entityinfo_t entinfo; - bot_activategoal_t activategoal; - - // if the bot is not blocked by anything - if (!moveresult->blocked) { - bs->notblocked_time = FloatTime(); - return; - } - // if stuck in a solid area - if ( moveresult->type == RESULTTYPE_INSOLIDAREA ) { - // move in a random direction in the hope to get out - BotRandomMove(bs, moveresult); - // - return; - } - // get info for the entity that is blocking the bot - BotEntityInfo(moveresult->blockentity, &entinfo); -#ifdef OBSTACLEDEBUG - ClientName(bs->client, netname, sizeof(netname)); - BotAI_Print(PRT_MESSAGE, "%s: I'm blocked by model %d\n", netname, entinfo.modelindex); -#endif // OBSTACLEDEBUG - // if blocked by a bsp model and the bot wants to activate it - if (activate && entinfo.modelindex > 0 && entinfo.modelindex <= max_bspmodelindex) { - // find the bsp entity which should be activated in order to get the blocking entity out of the way - bspent = BotGetActivateGoal(bs, entinfo.number, &activategoal); - if (bspent) { - // - if (bs->activatestack && !bs->activatestack->inuse) - bs->activatestack = NULL; - // if not already trying to activate this entity - if (!BotIsGoingToActivateEntity(bs, activategoal.goal.entitynum)) { - // - BotGoForActivateGoal(bs, &activategoal); - } - // if ontop of an obstacle or - // if the bot is not in a reachability area it'll still - // need some dynamic obstacle avoidance, otherwise return - if (!(moveresult->flags & MOVERESULT_ONTOPOFOBSTACLE) && - trap_AAS_AreaReachability(bs->areanum)) - return; - } - else { - // enable any routing areas that were disabled - BotEnableActivateGoalAreas(&activategoal, qtrue); - } - } - // just some basic dynamic obstacle avoidance code - hordir[0] = moveresult->movedir[0]; - hordir[1] = moveresult->movedir[1]; - hordir[2] = 0; - // if no direction just take a random direction - if (VectorNormalize(hordir) < 0.1) { - VectorSet(angles, 0, 360 * random(), 0); - AngleVectors(angles, hordir, NULL, NULL); - } - // - //if (moveresult->flags & MOVERESULT_ONTOPOFOBSTACLE) movetype = MOVE_JUMP; - //else - movetype = MOVE_WALK; - // if there's an obstacle at the bot's feet and head then - // the bot might be able to crouch through - VectorCopy(bs->origin, start); - start[2] += 18; - VectorMA(start, 5, hordir, end); - VectorSet(mins, -16, -16, -24); - VectorSet(maxs, 16, 16, 4); - // - //bsptrace = AAS_Trace(start, mins, maxs, end, bs->entitynum, MASK_PLAYERSOLID); - //if (bsptrace.fraction >= 1) movetype = MOVE_CROUCH; - // get the sideward vector - CrossProduct(hordir, up, sideward); - // - if (bs->flags & BFL_AVOIDRIGHT) VectorNegate(sideward, sideward); - // try to crouch straight forward? - if (movetype != MOVE_CROUCH || !trap_BotMoveInDirection(bs->ms, hordir, 400, movetype)) { - // perform the movement - if (!trap_BotMoveInDirection(bs->ms, sideward, 400, movetype)) { - // flip the avoid direction flag - bs->flags ^= BFL_AVOIDRIGHT; - // flip the direction - // VectorNegate(sideward, sideward); - VectorMA(sideward, -1, hordir, sideward); - // move in the other direction - trap_BotMoveInDirection(bs->ms, sideward, 400, movetype); - } - } - // - if (bs->notblocked_time < FloatTime() - 0.4) { - // just reset goals and hope the bot will go into another direction? - // is this still needed?? - if (bs->ainode == AINode_Seek_NBG) bs->nbg_time = 0; - else if (bs->ainode == AINode_Seek_LTG) bs->ltg_time = 0; - } -} - -/* -================== -BotAIPredictObstacles - -Predict the route towards the goal and check if the bot -will be blocked by certain obstacles. When the bot has obstacles -on it's path the bot should figure out if they can be removed -by activating certain entities. -================== -*/ -int BotAIPredictObstacles(bot_state_t *bs, bot_goal_t *goal) { - int modelnum, entitynum, bspent; - bot_activategoal_t activategoal; - aas_predictroute_t route; - - if (!bot_predictobstacles.integer) - return qfalse; - - // always predict when the goal change or at regular intervals - if (bs->predictobstacles_goalareanum == goal->areanum && - bs->predictobstacles_time > FloatTime() - 6) { - return qfalse; - } - bs->predictobstacles_goalareanum = goal->areanum; - bs->predictobstacles_time = FloatTime(); - - // predict at most 100 areas or 10 seconds ahead - trap_AAS_PredictRoute(&route, bs->areanum, bs->origin, - goal->areanum, bs->tfl, 100, 1000, - RSE_USETRAVELTYPE|RSE_ENTERCONTENTS, - AREACONTENTS_MOVER, TFL_BRIDGE, 0); - // if bot has to travel through an area with a mover - if (route.stopevent & RSE_ENTERCONTENTS) { - // if the bot will run into a mover - if (route.endcontents & AREACONTENTS_MOVER) { - //NOTE: this only works with bspc 2.1 or higher - modelnum = (route.endcontents & AREACONTENTS_MODELNUM) >> AREACONTENTS_MODELNUMSHIFT; - if (modelnum) { - // - entitynum = BotModelMinsMaxs(modelnum, ET_MOVER, 0, NULL, NULL); - if (entitynum) { - //NOTE: BotGetActivateGoal already checks if the door is open or not - bspent = BotGetActivateGoal(bs, entitynum, &activategoal); - if (bspent) { - // - if (bs->activatestack && !bs->activatestack->inuse) - bs->activatestack = NULL; - // if not already trying to activate this entity - if (!BotIsGoingToActivateEntity(bs, activategoal.goal.entitynum)) { - // - //BotAI_Print(PRT_MESSAGE, "blocked by mover model %d, entity %d ?\n", modelnum, entitynum); - // - BotGoForActivateGoal(bs, &activategoal); - return qtrue; - } - else { - // enable any routing areas that were disabled - BotEnableActivateGoalAreas(&activategoal, qtrue); - } - } - } - } - } - } - else if (route.stopevent & RSE_USETRAVELTYPE) { - if (route.endtravelflags & TFL_BRIDGE) { - //FIXME: check if the bridge is available to travel over - } - } - return qfalse; -} - -/* -================== -BotCheckConsoleMessages -================== -*/ -void BotCheckConsoleMessages(bot_state_t *bs) { - char botname[MAX_NETNAME], message[MAX_MESSAGE_SIZE], netname[MAX_NETNAME], *ptr; - float chat_reply; - int context, handle; - bot_consolemessage_t m; - bot_match_t match; - - //the name of this bot - ClientName(bs->client, botname, sizeof(botname)); - // - while((handle = trap_BotNextConsoleMessage(bs->cs, &m)) != 0) { - //if the chat state is flooded with messages the bot will read them quickly - if (trap_BotNumConsoleMessages(bs->cs) < 10) { - //if it is a chat message the bot needs some time to read it - if (m.type == CMS_CHAT && m.time > FloatTime() - (1 + random())) break; - } - // - ptr = m.message; - //if it is a chat message then don't unify white spaces and don't - //replace synonyms in the netname - if (m.type == CMS_CHAT) { - // - if (trap_BotFindMatch(m.message, &match, MTCONTEXT_REPLYCHAT)) { - ptr = m.message + match.variables[MESSAGE].offset; - } - } - //unify the white spaces in the message - trap_UnifyWhiteSpaces(ptr); - //replace synonyms in the right context - context = BotSynonymContext(bs); - trap_BotReplaceSynonyms(ptr, context); - //if there's no match - if (!BotMatchMessage(bs, m.message)) { - //if it is a chat message - if (m.type == CMS_CHAT && !bot_nochat.integer) { - // - if (!trap_BotFindMatch(m.message, &match, MTCONTEXT_REPLYCHAT)) { - trap_BotRemoveConsoleMessage(bs->cs, handle); - continue; - } - //don't use eliza chats with team messages - if (match.subtype & ST_TEAM) { - trap_BotRemoveConsoleMessage(bs->cs, handle); - continue; - } - // - trap_BotMatchVariable(&match, NETNAME, netname, sizeof(netname)); - trap_BotMatchVariable(&match, MESSAGE, message, sizeof(message)); - //if this is a message from the bot self - if (bs->client == ClientFromName(netname)) { - trap_BotRemoveConsoleMessage(bs->cs, handle); - continue; - } - //unify the message - trap_UnifyWhiteSpaces(message); - // - trap_Cvar_Update(&bot_testrchat); - if (bot_testrchat.integer) { - // - trap_BotLibVarSet("bot_testrchat", "1"); - //if bot replies with a chat message - if (trap_BotReplyChat(bs->cs, message, context, CONTEXT_REPLY, - NULL, NULL, - NULL, NULL, - NULL, NULL, - botname, netname)) { - BotAI_Print(PRT_MESSAGE, "------------------------\n"); - } - else { - BotAI_Print(PRT_MESSAGE, "**** no valid reply ****\n"); - } - } - //if at a valid chat position and not chatting already and not in teamplay - else if (bs->ainode != AINode_Stand && BotValidChatPosition(bs) && !TeamPlayIsOn()) { - chat_reply = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_REPLY, 0, 1); - if (random() < 1.5 / (NumBots()+1) && random() < chat_reply) { - //if bot replies with a chat message - if (trap_BotReplyChat(bs->cs, message, context, CONTEXT_REPLY, - NULL, NULL, - NULL, NULL, - NULL, NULL, - botname, netname)) { - //remove the console message - trap_BotRemoveConsoleMessage(bs->cs, handle); - bs->stand_time = FloatTime() + BotChatTime(bs); - AIEnter_Stand(bs, "BotCheckConsoleMessages: reply chat"); - //EA_Say(bs->client, bs->cs.chatmessage); - break; - } - } - } - } - } - //remove the console message - trap_BotRemoveConsoleMessage(bs->cs, handle); - } -} - -/* -================== -BotCheckEvents -================== -*/ -void BotCheckForGrenades(bot_state_t *bs, entityState_t *state) { - // if this is not a grenade - if (state->eType != ET_MISSILE || state->weapon != WP_GRENADE_LAUNCHER) - return; - // try to avoid the grenade - trap_BotAddAvoidSpot(bs->ms, state->pos.trBase, 160, AVOID_ALWAYS); -} - -#ifdef MISSIONPACK -/* -================== -BotCheckForProxMines -================== -*/ -void BotCheckForProxMines(bot_state_t *bs, entityState_t *state) { - // if this is not a prox mine - if (state->eType != ET_MISSILE || state->weapon != WP_PROX_LAUNCHER) - return; - // if this prox mine is from someone on our own team - if (state->generic1 == BotTeam(bs)) - return; - // if the bot doesn't have a weapon to deactivate the mine - if (!(bs->inventory[INVENTORY_PLASMAGUN] > 0 && bs->inventory[INVENTORY_CELLS] > 0) && - !(bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && bs->inventory[INVENTORY_ROCKETS] > 0) && - !(bs->inventory[INVENTORY_BFG10K] > 0 && bs->inventory[INVENTORY_BFGAMMO] > 0) ) { - return; - } - // try to avoid the prox mine - trap_BotAddAvoidSpot(bs->ms, state->pos.trBase, 160, AVOID_ALWAYS); - // - if (bs->numproxmines >= MAX_PROXMINES) - return; - bs->proxmines[bs->numproxmines] = state->number; - bs->numproxmines++; -} - -/* -================== -BotCheckForKamikazeBody -================== -*/ -void BotCheckForKamikazeBody(bot_state_t *bs, entityState_t *state) { - // if this entity is not wearing the kamikaze - if (!(state->eFlags & EF_KAMIKAZE)) - return; - // if this entity isn't dead - if (!(state->eFlags & EF_DEAD)) - return; - //remember this kamikaze body - bs->kamikazebody = state->number; -} -#endif - -/* -================== -BotCheckEvents -================== -*/ -void BotCheckEvents(bot_state_t *bs, entityState_t *state) { - int event; - char buf[128]; -#ifdef MISSIONPACK - aas_entityinfo_t entinfo; -#endif - - //NOTE: this sucks, we're accessing the gentity_t directly - //but there's no other fast way to do it right now - if (bs->entityeventTime[state->number] == g_entities[state->number].eventTime) { - return; - } - bs->entityeventTime[state->number] = g_entities[state->number].eventTime; - //if it's an event only entity - if (state->eType > ET_EVENTS) { - event = (state->eType - ET_EVENTS) & ~EV_EVENT_BITS; - } - else { - event = state->event & ~EV_EVENT_BITS; - } - // - switch(event) { - //client obituary event - case EV_OBITUARY: - { - int target, attacker, mod; - - target = state->otherEntityNum; - attacker = state->otherEntityNum2; - mod = state->eventParm; - // - if (target == bs->client) { - bs->botdeathtype = mod; - bs->lastkilledby = attacker; - // - if (target == attacker || - target == ENTITYNUM_NONE || - target == ENTITYNUM_WORLD) bs->botsuicide = qtrue; - else bs->botsuicide = qfalse; - // - bs->num_deaths++; - } - //else if this client was killed by the bot - else if (attacker == bs->client) { - bs->enemydeathtype = mod; - bs->lastkilledplayer = target; - bs->killedenemy_time = FloatTime(); - // - bs->num_kills++; - } - else if (attacker == bs->enemy && target == attacker) { - bs->enemysuicide = qtrue; - } - // -#ifdef MISSIONPACK - if (gametype == GT_1FCTF) { - // - BotEntityInfo(target, &entinfo); - if ( entinfo.powerups & ( 1 << PW_NEUTRALFLAG ) ) { - if (!BotSameTeam(bs, target)) { - bs->neutralflagstatus = 3; //enemy dropped the flag - bs->flagstatuschanged = qtrue; - } - } - } -#endif - break; - } - case EV_GLOBAL_SOUND: - { - if (state->eventParm < 0 || state->eventParm > MAX_SOUNDS) { - BotAI_Print(PRT_ERROR, "EV_GLOBAL_SOUND: eventParm (%d) out of range\n", state->eventParm); - break; - } - trap_GetConfigstring(CS_SOUNDS + state->eventParm, buf, sizeof(buf)); - /* - if (!strcmp(buf, "sound/teamplay/flagret_red.wav")) { - //red flag is returned - bs->redflagstatus = 0; - bs->flagstatuschanged = qtrue; - } - else if (!strcmp(buf, "sound/teamplay/flagret_blu.wav")) { - //blue flag is returned - bs->blueflagstatus = 0; - bs->flagstatuschanged = qtrue; - } - else*/ -#ifdef MISSIONPACK - if (!strcmp(buf, "sound/items/kamikazerespawn.wav" )) { - //the kamikaze respawned so dont avoid it - BotDontAvoid(bs, "Kamikaze"); - } - else -#endif - if (!strcmp(buf, "sound/items/poweruprespawn.wav")) { - //powerup respawned... go get it - BotGoForPowerups(bs); - } - break; - } - case EV_GLOBAL_TEAM_SOUND: - { - if (gametype == GT_CTF) { - switch(state->eventParm) { - case GTS_RED_CAPTURE: - bs->blueflagstatus = 0; - bs->redflagstatus = 0; - bs->flagstatuschanged = qtrue; - break; //see BotMatch_CTF - case GTS_BLUE_CAPTURE: - bs->blueflagstatus = 0; - bs->redflagstatus = 0; - bs->flagstatuschanged = qtrue; - break; //see BotMatch_CTF - case GTS_RED_RETURN: - //blue flag is returned - bs->blueflagstatus = 0; - bs->flagstatuschanged = qtrue; - break; - case GTS_BLUE_RETURN: - //red flag is returned - bs->redflagstatus = 0; - bs->flagstatuschanged = qtrue; - break; - case GTS_RED_TAKEN: - //blue flag is taken - bs->blueflagstatus = 1; - bs->flagstatuschanged = qtrue; - break; //see BotMatch_CTF - case GTS_BLUE_TAKEN: - //red flag is taken - bs->redflagstatus = 1; - bs->flagstatuschanged = qtrue; - break; //see BotMatch_CTF - } - } -#ifdef MISSIONPACK - else if (gametype == GT_1FCTF) { - switch(state->eventParm) { - case GTS_RED_CAPTURE: - bs->neutralflagstatus = 0; - bs->flagstatuschanged = qtrue; - break; - case GTS_BLUE_CAPTURE: - bs->neutralflagstatus = 0; - bs->flagstatuschanged = qtrue; - break; - case GTS_RED_RETURN: - //flag has returned - bs->neutralflagstatus = 0; - bs->flagstatuschanged = qtrue; - break; - case GTS_BLUE_RETURN: - //flag has returned - bs->neutralflagstatus = 0; - bs->flagstatuschanged = qtrue; - break; - case GTS_RED_TAKEN: - bs->neutralflagstatus = BotTeam(bs) == TEAM_RED ? 2 : 1; //FIXME: check Team_TakeFlagSound in g_team.c - bs->flagstatuschanged = qtrue; - break; - case GTS_BLUE_TAKEN: - bs->neutralflagstatus = BotTeam(bs) == TEAM_BLUE ? 2 : 1; //FIXME: check Team_TakeFlagSound in g_team.c - bs->flagstatuschanged = qtrue; - break; - } - } -#endif - break; - } - case EV_PLAYER_TELEPORT_IN: - { - VectorCopy(state->origin, lastteleport_origin); - lastteleport_time = FloatTime(); - break; - } - case EV_GENERAL_SOUND: - { - //if this sound is played on the bot - if (state->number == bs->client) { - if (state->eventParm < 0 || state->eventParm > MAX_SOUNDS) { - BotAI_Print(PRT_ERROR, "EV_GENERAL_SOUND: eventParm (%d) out of range\n", state->eventParm); - break; - } - //check out the sound - trap_GetConfigstring(CS_SOUNDS + state->eventParm, buf, sizeof(buf)); - //if falling into a death pit - if (!strcmp(buf, "*falling1.wav")) { - //if the bot has a personal teleporter - if (bs->inventory[INVENTORY_TELEPORTER] > 0) { - //use the holdable item - trap_EA_Use(bs->client); - } - } - } - break; - } - case EV_FOOTSTEP: - case EV_FOOTSTEP_METAL: - case EV_FOOTSPLASH: - case EV_FOOTWADE: - case EV_SWIM: - case EV_FALL_SHORT: - case EV_FALL_MEDIUM: - case EV_FALL_FAR: - case EV_STEP_4: - case EV_STEP_8: - case EV_STEP_12: - case EV_STEP_16: - case EV_JUMP_PAD: - case EV_JUMP: - case EV_TAUNT: - case EV_WATER_TOUCH: - case EV_WATER_LEAVE: - case EV_WATER_UNDER: - case EV_WATER_CLEAR: - case EV_ITEM_PICKUP: - case EV_GLOBAL_ITEM_PICKUP: - case EV_NOAMMO: - case EV_CHANGE_WEAPON: - case EV_FIRE_WEAPON: - //FIXME: either add to sound queue or mark player as someone making noise - break; - case EV_USE_ITEM0: - case EV_USE_ITEM1: - case EV_USE_ITEM2: - case EV_USE_ITEM3: - case EV_USE_ITEM4: - case EV_USE_ITEM5: - case EV_USE_ITEM6: - case EV_USE_ITEM7: - case EV_USE_ITEM8: - case EV_USE_ITEM9: - case EV_USE_ITEM10: - case EV_USE_ITEM11: - case EV_USE_ITEM12: - case EV_USE_ITEM13: - case EV_USE_ITEM14: - break; - } -} - -/* -================== -BotCheckSnapshot -================== -*/ -void BotCheckSnapshot(bot_state_t *bs) { - int ent; - entityState_t state; - - //remove all avoid spots - trap_BotAddAvoidSpot(bs->ms, vec3_origin, 0, AVOID_CLEAR); - //reset kamikaze body - bs->kamikazebody = 0; - //reset number of proxmines - bs->numproxmines = 0; - // - ent = 0; - while( ( ent = BotAI_GetSnapshotEntity( bs->client, ent, &state ) ) != -1 ) { - //check the entity state for events - BotCheckEvents(bs, &state); - //check for grenades the bot should avoid - BotCheckForGrenades(bs, &state); - // -#ifdef MISSIONPACK - //check for proximity mines which the bot should deactivate - BotCheckForProxMines(bs, &state); - //check for dead bodies with the kamikaze effect which should be gibbed - BotCheckForKamikazeBody(bs, &state); -#endif - } - //check the player state for events - BotAI_GetEntityState(bs->client, &state); - //copy the player state events to the entity state - state.event = bs->cur_ps.externalEvent; - state.eventParm = bs->cur_ps.externalEventParm; - // - BotCheckEvents(bs, &state); -} - -/* -================== -BotCheckAir -================== -*/ -void BotCheckAir(bot_state_t *bs) { - if (bs->inventory[INVENTORY_ENVIRONMENTSUIT] <= 0) { - if (trap_AAS_PointContents(bs->eye) & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA)) { - return; - } - } - bs->lastair_time = FloatTime(); -} - -/* -================== -BotAlternateRoute -================== -*/ -bot_goal_t *BotAlternateRoute(bot_state_t *bs, bot_goal_t *goal) { - int t; - - // if the bot has an alternative route goal - if (bs->altroutegoal.areanum) { - // - if (bs->reachedaltroutegoal_time) - return goal; - // travel time towards alternative route goal - t = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, bs->altroutegoal.areanum, bs->tfl); - if (t && t < 20) { - //BotAI_Print(PRT_MESSAGE, "reached alternate route goal\n"); - bs->reachedaltroutegoal_time = FloatTime(); - } - memcpy(goal, &bs->altroutegoal, sizeof(bot_goal_t)); - return &bs->altroutegoal; - } - return goal; -} - -/* -================== -BotGetAlternateRouteGoal -================== -*/ -int BotGetAlternateRouteGoal(bot_state_t *bs, int base) { - aas_altroutegoal_t *altroutegoals; - bot_goal_t *goal; - int numaltroutegoals, rnd; - - if (base == TEAM_RED) { - altroutegoals = red_altroutegoals; - numaltroutegoals = red_numaltroutegoals; - } - else { - altroutegoals = blue_altroutegoals; - numaltroutegoals = blue_numaltroutegoals; - } - if (!numaltroutegoals) - return qfalse; - rnd = (float) random() * numaltroutegoals; - if (rnd >= numaltroutegoals) - rnd = numaltroutegoals-1; - goal = &bs->altroutegoal; - goal->areanum = altroutegoals[rnd].areanum; - VectorCopy(altroutegoals[rnd].origin, goal->origin); - VectorSet(goal->mins, -8, -8, -8); - VectorSet(goal->maxs, 8, 8, 8); - goal->entitynum = 0; - goal->iteminfo = 0; - goal->number = 0; - goal->flags = 0; - // - bs->reachedaltroutegoal_time = 0; - return qtrue; -} - -/* -================== -BotSetupAlternateRouteGoals -================== -*/ -void BotSetupAlternativeRouteGoals(void) { - - if (altroutegoals_setup) - return; -#ifdef MISSIONPACK - if (gametype == GT_CTF) { - if (trap_BotGetLevelItemGoal(-1, "Neutral Flag", &ctf_neutralflag) < 0) - BotAI_Print(PRT_WARNING, "no alt routes without Neutral Flag\n"); - if (ctf_neutralflag.areanum) { - // - red_numaltroutegoals = trap_AAS_AlternativeRouteGoals( - ctf_neutralflag.origin, ctf_neutralflag.areanum, - ctf_redflag.origin, ctf_redflag.areanum, TFL_DEFAULT, - red_altroutegoals, MAX_ALTROUTEGOALS, - ALTROUTEGOAL_CLUSTERPORTALS| - ALTROUTEGOAL_VIEWPORTALS); - blue_numaltroutegoals = trap_AAS_AlternativeRouteGoals( - ctf_neutralflag.origin, ctf_neutralflag.areanum, - ctf_blueflag.origin, ctf_blueflag.areanum, TFL_DEFAULT, - blue_altroutegoals, MAX_ALTROUTEGOALS, - ALTROUTEGOAL_CLUSTERPORTALS| - ALTROUTEGOAL_VIEWPORTALS); - } - } - else if (gametype == GT_1FCTF) { - // - red_numaltroutegoals = trap_AAS_AlternativeRouteGoals( - ctf_neutralflag.origin, ctf_neutralflag.areanum, - ctf_redflag.origin, ctf_redflag.areanum, TFL_DEFAULT, - red_altroutegoals, MAX_ALTROUTEGOALS, - ALTROUTEGOAL_CLUSTERPORTALS| - ALTROUTEGOAL_VIEWPORTALS); - blue_numaltroutegoals = trap_AAS_AlternativeRouteGoals( - ctf_neutralflag.origin, ctf_neutralflag.areanum, - ctf_blueflag.origin, ctf_blueflag.areanum, TFL_DEFAULT, - blue_altroutegoals, MAX_ALTROUTEGOALS, - ALTROUTEGOAL_CLUSTERPORTALS| - ALTROUTEGOAL_VIEWPORTALS); - } - else if (gametype == GT_OBELISK) { - if (trap_BotGetLevelItemGoal(-1, "Neutral Obelisk", &neutralobelisk) < 0) - BotAI_Print(PRT_WARNING, "Harvester without neutral obelisk\n"); - // - red_numaltroutegoals = trap_AAS_AlternativeRouteGoals( - neutralobelisk.origin, neutralobelisk.areanum, - redobelisk.origin, redobelisk.areanum, TFL_DEFAULT, - red_altroutegoals, MAX_ALTROUTEGOALS, - ALTROUTEGOAL_CLUSTERPORTALS| - ALTROUTEGOAL_VIEWPORTALS); - blue_numaltroutegoals = trap_AAS_AlternativeRouteGoals( - neutralobelisk.origin, neutralobelisk.areanum, - blueobelisk.origin, blueobelisk.areanum, TFL_DEFAULT, - blue_altroutegoals, MAX_ALTROUTEGOALS, - ALTROUTEGOAL_CLUSTERPORTALS| - ALTROUTEGOAL_VIEWPORTALS); - } - else if (gametype == GT_HARVESTER) { - // - red_numaltroutegoals = trap_AAS_AlternativeRouteGoals( - neutralobelisk.origin, neutralobelisk.areanum, - redobelisk.origin, redobelisk.areanum, TFL_DEFAULT, - red_altroutegoals, MAX_ALTROUTEGOALS, - ALTROUTEGOAL_CLUSTERPORTALS| - ALTROUTEGOAL_VIEWPORTALS); - blue_numaltroutegoals = trap_AAS_AlternativeRouteGoals( - neutralobelisk.origin, neutralobelisk.areanum, - blueobelisk.origin, blueobelisk.areanum, TFL_DEFAULT, - blue_altroutegoals, MAX_ALTROUTEGOALS, - ALTROUTEGOAL_CLUSTERPORTALS| - ALTROUTEGOAL_VIEWPORTALS); - } -#endif - altroutegoals_setup = qtrue; -} - -/* -================== -BotDeathmatchAI -================== -*/ -void BotDeathmatchAI(bot_state_t *bs, float thinktime) { - char gender[144], name[144], buf[144]; - char userinfo[MAX_INFO_STRING]; - int i; - - //if the bot has just been setup - if (bs->setupcount > 0) { - bs->setupcount--; - if (bs->setupcount > 0) return; - //get the gender characteristic - trap_Characteristic_String(bs->character, CHARACTERISTIC_GENDER, gender, sizeof(gender)); - //set the bot gender - trap_GetUserinfo(bs->client, userinfo, sizeof(userinfo)); - Info_SetValueForKey(userinfo, "sex", gender); - trap_SetUserinfo(bs->client, userinfo); - //set the team - if ( !bs->map_restart && g_gametype.integer != GT_TOURNAMENT ) { - Com_sprintf(buf, sizeof(buf), "team %s", bs->settings.team); - trap_EA_Command(bs->client, buf); - } - //set the chat gender - if (gender[0] == 'm') trap_BotSetChatGender(bs->cs, CHAT_GENDERMALE); - else if (gender[0] == 'f') trap_BotSetChatGender(bs->cs, CHAT_GENDERFEMALE); - else trap_BotSetChatGender(bs->cs, CHAT_GENDERLESS); - //set the chat name - ClientName(bs->client, name, sizeof(name)); - trap_BotSetChatName(bs->cs, name, bs->client); - // - bs->lastframe_health = bs->inventory[INVENTORY_HEALTH]; - bs->lasthitcount = bs->cur_ps.persistant[PERS_HITS]; - // - bs->setupcount = 0; - // - BotSetupAlternativeRouteGoals(); - } - //no ideal view set - bs->flags &= ~BFL_IDEALVIEWSET; - // - if (!BotIntermission(bs)) { - //set the teleport time - BotSetTeleportTime(bs); - //update some inventory values - BotUpdateInventory(bs); - //check out the snapshot - BotCheckSnapshot(bs); - //check for air - BotCheckAir(bs); - } - //check the console messages - BotCheckConsoleMessages(bs); - //if not in the intermission and not in observer mode - if (!BotIntermission(bs) && !BotIsObserver(bs)) { - //do team AI - BotTeamAI(bs); - } - //if the bot has no ai node - if (!bs->ainode) { - AIEnter_Seek_LTG(bs, "BotDeathmatchAI: no ai node"); - } - //if the bot entered the game less than 8 seconds ago - if (!bs->entergamechat && bs->entergame_time > FloatTime() - 8) { - if (BotChat_EnterGame(bs)) { - bs->stand_time = FloatTime() + BotChatTime(bs); - AIEnter_Stand(bs, "BotDeathmatchAI: chat enter game"); - } - bs->entergamechat = qtrue; - } - //reset the node switches from the previous frame - BotResetNodeSwitches(); - //execute AI nodes - for (i = 0; i < MAX_NODESWITCHES; i++) { - if (bs->ainode(bs)) break; - } - //if the bot removed itself :) - if (!bs->inuse) return; - //if the bot executed too many AI nodes - if (i >= MAX_NODESWITCHES) { - trap_BotDumpGoalStack(bs->gs); - trap_BotDumpAvoidGoals(bs->gs); - BotDumpNodeSwitches(bs); - ClientName(bs->client, name, sizeof(name)); - BotAI_Print(PRT_ERROR, "%s at %1.1f switched more than %d AI nodes\n", name, FloatTime(), MAX_NODESWITCHES); - } - // - bs->lastframe_health = bs->inventory[INVENTORY_HEALTH]; - bs->lasthitcount = bs->cur_ps.persistant[PERS_HITS]; -} - -/* -================== -BotSetEntityNumForGoalWithModel -================== -*/ -void BotSetEntityNumForGoalWithModel(bot_goal_t *goal, int eType, char *modelname) { - gentity_t *ent; - int i, modelindex; - vec3_t dir; - - modelindex = G_ModelIndex( modelname ); - ent = &g_entities[0]; - for (i = 0; i < level.num_entities; i++, ent++) { - if ( !ent->inuse ) { - continue; - } - if ( eType && ent->s.eType != eType) { - continue; - } - if (ent->s.modelindex != modelindex) { - continue; - } - VectorSubtract(goal->origin, ent->s.origin, dir); - if (VectorLengthSquared(dir) < Square(10)) { - goal->entitynum = i; - return; - } - } -} - -/* -================== -BotSetEntityNumForGoal -================== -*/ -void BotSetEntityNumForGoal(bot_goal_t *goal, char *classname) { - gentity_t *ent; - int i; - vec3_t dir; - - ent = &g_entities[0]; - for (i = 0; i < level.num_entities; i++, ent++) { - if ( !ent->inuse ) { - continue; - } - if ( !Q_stricmp(ent->classname, classname) ) { - continue; - } - VectorSubtract(goal->origin, ent->s.origin, dir); - if (VectorLengthSquared(dir) < Square(10)) { - goal->entitynum = i; - return; - } - } -} - -/* -================== -BotGoalForBSPEntity -================== -*/ -int BotGoalForBSPEntity( char *classname, bot_goal_t *goal ) { - char value[MAX_INFO_STRING]; - vec3_t origin, start, end; - int ent, numareas, areas[10]; - - memset(goal, 0, sizeof(bot_goal_t)); - for (ent = trap_AAS_NextBSPEntity(0); ent; ent = trap_AAS_NextBSPEntity(ent)) { - if (!trap_AAS_ValueForBSPEpairKey(ent, "classname", value, sizeof(value))) - continue; - if (!strcmp(value, classname)) { - if (!trap_AAS_VectorForBSPEpairKey(ent, "origin", origin)) - return qfalse; - VectorCopy(origin, goal->origin); - VectorCopy(origin, start); - start[2] -= 32; - VectorCopy(origin, end); - end[2] += 32; - numareas = trap_AAS_TraceAreas(start, end, areas, NULL, 10); - if (!numareas) - return qfalse; - goal->areanum = areas[0]; - return qtrue; - } - } - return qfalse; -} - -/* -================== -BotSetupDeathmatchAI -================== -*/ -void BotSetupDeathmatchAI(void) { - int ent, modelnum; - char model[128]; - - gametype = trap_Cvar_VariableIntegerValue("g_gametype"); - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - - trap_Cvar_Register(&bot_rocketjump, "bot_rocketjump", "1", 0); - trap_Cvar_Register(&bot_grapple, "bot_grapple", "0", 0); - trap_Cvar_Register(&bot_fastchat, "bot_fastchat", "0", 0); - trap_Cvar_Register(&bot_nochat, "bot_nochat", "0", 0); - trap_Cvar_Register(&bot_testrchat, "bot_testrchat", "0", 0); - trap_Cvar_Register(&bot_challenge, "bot_challenge", "0", 0); - trap_Cvar_Register(&bot_predictobstacles, "bot_predictobstacles", "1", 0); - trap_Cvar_Register(&g_spSkill, "g_spSkill", "2", 0); - // - if (gametype == GT_CTF) { - if (trap_BotGetLevelItemGoal(-1, "Red Flag", &ctf_redflag) < 0) - BotAI_Print(PRT_WARNING, "CTF without Red Flag\n"); - if (trap_BotGetLevelItemGoal(-1, "Blue Flag", &ctf_blueflag) < 0) - BotAI_Print(PRT_WARNING, "CTF without Blue Flag\n"); - } -#ifdef MISSIONPACK - else if (gametype == GT_1FCTF) { - if (trap_BotGetLevelItemGoal(-1, "Neutral Flag", &ctf_neutralflag) < 0) - BotAI_Print(PRT_WARNING, "One Flag CTF without Neutral Flag\n"); - if (trap_BotGetLevelItemGoal(-1, "Red Flag", &ctf_redflag) < 0) - BotAI_Print(PRT_WARNING, "CTF without Red Flag\n"); - if (trap_BotGetLevelItemGoal(-1, "Blue Flag", &ctf_blueflag) < 0) - BotAI_Print(PRT_WARNING, "CTF without Blue Flag\n"); - } - else if (gametype == GT_OBELISK) { - if (trap_BotGetLevelItemGoal(-1, "Red Obelisk", &redobelisk) < 0) - BotAI_Print(PRT_WARNING, "Obelisk without red obelisk\n"); - BotSetEntityNumForGoal(&redobelisk, "team_redobelisk"); - if (trap_BotGetLevelItemGoal(-1, "Blue Obelisk", &blueobelisk) < 0) - BotAI_Print(PRT_WARNING, "Obelisk without blue obelisk\n"); - BotSetEntityNumForGoal(&blueobelisk, "team_blueobelisk"); - } - else if (gametype == GT_HARVESTER) { - if (trap_BotGetLevelItemGoal(-1, "Red Obelisk", &redobelisk) < 0) - BotAI_Print(PRT_WARNING, "Harvester without red obelisk\n"); - BotSetEntityNumForGoal(&redobelisk, "team_redobelisk"); - if (trap_BotGetLevelItemGoal(-1, "Blue Obelisk", &blueobelisk) < 0) - BotAI_Print(PRT_WARNING, "Harvester without blue obelisk\n"); - BotSetEntityNumForGoal(&blueobelisk, "team_blueobelisk"); - if (trap_BotGetLevelItemGoal(-1, "Neutral Obelisk", &neutralobelisk) < 0) - BotAI_Print(PRT_WARNING, "Harvester without neutral obelisk\n"); - BotSetEntityNumForGoal(&neutralobelisk, "team_neutralobelisk"); - } -#endif - - max_bspmodelindex = 0; - for (ent = trap_AAS_NextBSPEntity(0); ent; ent = trap_AAS_NextBSPEntity(ent)) { - if (!trap_AAS_ValueForBSPEpairKey(ent, "model", model, sizeof(model))) continue; - if (model[0] == '*') { - modelnum = atoi(model+1); - if (modelnum > max_bspmodelindex) - max_bspmodelindex = modelnum; - } - } - //initialize the waypoint heap - BotInitWaypoints(); -} - -/* -================== -BotShutdownDeathmatchAI -================== -*/ -void BotShutdownDeathmatchAI(void) { - altroutegoals_setup = qfalse; -} +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: ai_dmq3.c + * + * desc: Quake3 bot AI + * + * $Archive: /MissionPack/code/game/ai_dmq3.c $ + * + *****************************************************************************/ + + +#include "g_local.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_ea.h" +#include "be_ai_char.h" +#include "be_ai_chat.h" +#include "be_ai_gen.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" +#include "be_ai_weap.h" +// +#include "ai_main.h" +#include "ai_dmq3.h" +#include "ai_chat.h" +#include "ai_cmd.h" +#include "ai_dmnet.h" +#include "ai_team.h" +// +#include "chars.h" //characteristics +#include "inv.h" //indexes into the inventory +#include "syn.h" //synonyms +#include "match.h" //string matching types and vars + +// for the voice chats +#include "../../ui/menudef.h" // sos001205 - for q3_ui also + +// from aasfile.h +#define AREACONTENTS_MOVER 1024 +#define AREACONTENTS_MODELNUMSHIFT 24 +#define AREACONTENTS_MAXMODELNUM 0xFF +#define AREACONTENTS_MODELNUM (AREACONTENTS_MAXMODELNUM << AREACONTENTS_MODELNUMSHIFT) + +#define IDEAL_ATTACKDIST 140 + +#define MAX_WAYPOINTS 128 +// +bot_waypoint_t botai_waypoints[MAX_WAYPOINTS]; +bot_waypoint_t *botai_freewaypoints; + +//NOTE: not using a cvars which can be updated because the game should be reloaded anyway +int gametype; //game type +int maxclients; //maximum number of clients + +vmCvar_t bot_grapple; +vmCvar_t bot_rocketjump; +vmCvar_t bot_fastchat; +vmCvar_t bot_nochat; +vmCvar_t bot_testrchat; +vmCvar_t bot_challenge; +vmCvar_t bot_predictobstacles; +vmCvar_t g_spSkill; + +extern vmCvar_t bot_developer; + +vec3_t lastteleport_origin; //last teleport event origin +float lastteleport_time; //last teleport event time +int max_bspmodelindex; //maximum BSP model index + +//CTF flag goals +bot_goal_t ctf_redflag; +bot_goal_t ctf_blueflag; +#ifdef MISSIONPACK +bot_goal_t ctf_neutralflag; +bot_goal_t redobelisk; +bot_goal_t blueobelisk; +bot_goal_t neutralobelisk; +#endif + +#define MAX_ALTROUTEGOALS 32 + +int altroutegoals_setup; +aas_altroutegoal_t red_altroutegoals[MAX_ALTROUTEGOALS]; +int red_numaltroutegoals; +aas_altroutegoal_t blue_altroutegoals[MAX_ALTROUTEGOALS]; +int blue_numaltroutegoals; + + +/* +================== +BotSetUserInfo +================== +*/ +void BotSetUserInfo(bot_state_t *bs, char *key, char *value) { + char userinfo[MAX_INFO_STRING]; + + trap_GetUserinfo(bs->client, userinfo, sizeof(userinfo)); + Info_SetValueForKey(userinfo, key, value); + trap_SetUserinfo(bs->client, userinfo); + ClientUserinfoChanged( bs->client ); +} + +/* +================== +BotCTFCarryingFlag +================== +*/ +int BotCTFCarryingFlag(bot_state_t *bs) { + if (gametype != GT_CTF) return CTF_FLAG_NONE; + + if (bs->inventory[INVENTORY_REDFLAG] > 0) return CTF_FLAG_RED; + else if (bs->inventory[INVENTORY_BLUEFLAG] > 0) return CTF_FLAG_BLUE; + return CTF_FLAG_NONE; +} + +/* +================== +BotTeam +================== +*/ +int BotTeam(bot_state_t *bs) { + char info[1024]; + + if (bs->client < 0 || bs->client >= MAX_CLIENTS) { + //BotAI_Print(PRT_ERROR, "BotCTFTeam: client out of range\n"); + return qfalse; + } + trap_GetConfigstring(CS_PLAYERS+bs->client, info, sizeof(info)); + // + if (atoi(Info_ValueForKey(info, "t")) == TEAM_RED) return TEAM_RED; + else if (atoi(Info_ValueForKey(info, "t")) == TEAM_BLUE) return TEAM_BLUE; + return TEAM_FREE; +} + +/* +================== +BotOppositeTeam +================== +*/ +int BotOppositeTeam(bot_state_t *bs) { + switch(BotTeam(bs)) { + case TEAM_RED: return TEAM_BLUE; + case TEAM_BLUE: return TEAM_RED; + default: return TEAM_FREE; + } +} + +/* +================== +BotEnemyFlag +================== +*/ +bot_goal_t *BotEnemyFlag(bot_state_t *bs) { + if (BotTeam(bs) == TEAM_RED) { + return &ctf_blueflag; + } + else { + return &ctf_redflag; + } +} + +/* +================== +BotTeamFlag +================== +*/ +bot_goal_t *BotTeamFlag(bot_state_t *bs) { + if (BotTeam(bs) == TEAM_RED) { + return &ctf_redflag; + } + else { + return &ctf_blueflag; + } +} + + +/* +================== +EntityIsDead +================== +*/ +qboolean EntityIsDead(aas_entityinfo_t *entinfo) { + playerState_t ps; + + if (entinfo->number >= 0 && entinfo->number < MAX_CLIENTS) { + //retrieve the current client state + BotAI_GetClientState( entinfo->number, &ps ); + if (ps.pm_type != PM_NORMAL) return qtrue; + } + return qfalse; +} + +/* +================== +EntityCarriesFlag +================== +*/ +qboolean EntityCarriesFlag(aas_entityinfo_t *entinfo) { + if ( entinfo->powerups & ( 1 << PW_REDFLAG ) ) + return qtrue; + if ( entinfo->powerups & ( 1 << PW_BLUEFLAG ) ) + return qtrue; +#ifdef MISSIONPACK + if ( entinfo->powerups & ( 1 << PW_NEUTRALFLAG ) ) + return qtrue; +#endif + return qfalse; +} + +/* +================== +EntityIsInvisible +================== +*/ +qboolean EntityIsInvisible(aas_entityinfo_t *entinfo) { + // the flag is always visible + if (EntityCarriesFlag(entinfo)) { + return qfalse; + } + if (entinfo->powerups & (1 << PW_INVIS)) { + return qtrue; + } + return qfalse; +} + +/* +================== +EntityIsShooting +================== +*/ +qboolean EntityIsShooting(aas_entityinfo_t *entinfo) { + if (entinfo->flags & EF_FIRING) { + return qtrue; + } + return qfalse; +} + +/* +================== +EntityIsChatting +================== +*/ +qboolean EntityIsChatting(aas_entityinfo_t *entinfo) { + if (entinfo->flags & EF_TALK) { + return qtrue; + } + return qfalse; +} + +/* +================== +EntityHasQuad +================== +*/ +qboolean EntityHasQuad(aas_entityinfo_t *entinfo) { + if (entinfo->powerups & (1 << PW_QUAD)) { + return qtrue; + } + return qfalse; +} + +#ifdef MISSIONPACK +/* +================== +EntityHasKamikze +================== +*/ +qboolean EntityHasKamikaze(aas_entityinfo_t *entinfo) { + if (entinfo->flags & EF_KAMIKAZE) { + return qtrue; + } + return qfalse; +} + +/* +================== +EntityCarriesCubes +================== +*/ +qboolean EntityCarriesCubes(aas_entityinfo_t *entinfo) { + entityState_t state; + + if (gametype != GT_HARVESTER) + return qfalse; + //FIXME: get this info from the aas_entityinfo_t ? + BotAI_GetEntityState(entinfo->number, &state); + if (state.generic1 > 0) + return qtrue; + return qfalse; +} + +/* +================== +Bot1FCTFCarryingFlag +================== +*/ +int Bot1FCTFCarryingFlag(bot_state_t *bs) { + if (gametype != GT_1FCTF) return qfalse; + + if (bs->inventory[INVENTORY_NEUTRALFLAG] > 0) return qtrue; + return qfalse; +} + +/* +================== +BotHarvesterCarryingCubes +================== +*/ +int BotHarvesterCarryingCubes(bot_state_t *bs) { + if (gametype != GT_HARVESTER) return qfalse; + + if (bs->inventory[INVENTORY_REDCUBE] > 0) return qtrue; + if (bs->inventory[INVENTORY_BLUECUBE] > 0) return qtrue; + return qfalse; +} +#endif + +/* +================== +BotRememberLastOrderedTask +================== +*/ +void BotRememberLastOrderedTask(bot_state_t *bs) { + if (!bs->ordered) { + return; + } + bs->lastgoal_decisionmaker = bs->decisionmaker; + bs->lastgoal_ltgtype = bs->ltgtype; + memcpy(&bs->lastgoal_teamgoal, &bs->teamgoal, sizeof(bot_goal_t)); + bs->lastgoal_teammate = bs->teammate; +} + +/* +================== +BotSetTeamStatus +================== +*/ +void BotSetTeamStatus(bot_state_t *bs) { +#ifdef MISSIONPACK + int teamtask; + aas_entityinfo_t entinfo; + + teamtask = TEAMTASK_PATROL; + + switch(bs->ltgtype) { + case LTG_TEAMHELP: + break; + case LTG_TEAMACCOMPANY: + BotEntityInfo(bs->teammate, &entinfo); + if ( ( (gametype == GT_CTF || gametype == GT_1FCTF) && EntityCarriesFlag(&entinfo)) + || ( gametype == GT_HARVESTER && EntityCarriesCubes(&entinfo)) ) { + teamtask = TEAMTASK_ESCORT; + } + else { + teamtask = TEAMTASK_FOLLOW; + } + break; + case LTG_DEFENDKEYAREA: + teamtask = TEAMTASK_DEFENSE; + break; + case LTG_GETFLAG: + teamtask = TEAMTASK_OFFENSE; + break; + case LTG_RUSHBASE: + teamtask = TEAMTASK_DEFENSE; + break; + case LTG_RETURNFLAG: + teamtask = TEAMTASK_RETRIEVE; + break; + case LTG_CAMP: + case LTG_CAMPORDER: + teamtask = TEAMTASK_CAMP; + break; + case LTG_PATROL: + teamtask = TEAMTASK_PATROL; + break; + case LTG_GETITEM: + teamtask = TEAMTASK_PATROL; + break; + case LTG_KILL: + teamtask = TEAMTASK_PATROL; + break; + case LTG_HARVEST: + teamtask = TEAMTASK_OFFENSE; + break; + case LTG_ATTACKENEMYBASE: + teamtask = TEAMTASK_OFFENSE; + break; + default: + teamtask = TEAMTASK_PATROL; + break; + } + BotSetUserInfo(bs, "teamtask", va("%d", teamtask)); +#endif +} + +/* +================== +BotSetLastOrderedTask +================== +*/ +int BotSetLastOrderedTask(bot_state_t *bs) { + + if (gametype == GT_CTF) { + // don't go back to returning the flag if it's at the base + if ( bs->lastgoal_ltgtype == LTG_RETURNFLAG ) { + if ( BotTeam(bs) == TEAM_RED ) { + if ( bs->redflagstatus == 0 ) { + bs->lastgoal_ltgtype = 0; + } + } + else { + if ( bs->blueflagstatus == 0 ) { + bs->lastgoal_ltgtype = 0; + } + } + } + } + + if ( bs->lastgoal_ltgtype ) { + bs->decisionmaker = bs->lastgoal_decisionmaker; + bs->ordered = qtrue; + bs->ltgtype = bs->lastgoal_ltgtype; + memcpy(&bs->teamgoal, &bs->lastgoal_teamgoal, sizeof(bot_goal_t)); + bs->teammate = bs->lastgoal_teammate; + bs->teamgoal_time = FloatTime() + 300; + BotSetTeamStatus(bs); + // + if ( gametype == GT_CTF ) { + if ( bs->ltgtype == LTG_GETFLAG ) { + bot_goal_t *tb, *eb; + int tt, et; + + tb = BotTeamFlag(bs); + eb = BotEnemyFlag(bs); + tt = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, tb->areanum, TFL_DEFAULT); + et = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, eb->areanum, TFL_DEFAULT); + // if the travel time towards the enemy base is larger than towards our base + if (et > tt) { + //get an alternative route goal towards the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + } + } + } + return qtrue; + } + return qfalse; +} + +/* +================== +BotRefuseOrder +================== +*/ +void BotRefuseOrder(bot_state_t *bs) { + if (!bs->ordered) + return; + // if the bot was ordered to do something + if ( bs->order_time && bs->order_time > FloatTime() - 10 ) { + trap_EA_Action(bs->client, ACTION_NEGATIVE); + BotVoiceChat(bs, bs->decisionmaker, VOICECHAT_NO); + bs->order_time = 0; + } +} + +/* +================== +BotCTFSeekGoals +================== +*/ +void BotCTFSeekGoals(bot_state_t *bs) { + float rnd, l1, l2; + int flagstatus, c; + vec3_t dir; + aas_entityinfo_t entinfo; + + //when carrying a flag in ctf the bot should rush to the base + if (BotCTFCarryingFlag(bs)) { + //if not already rushing to the base + if (bs->ltgtype != LTG_RUSHBASE) { + BotRefuseOrder(bs); + bs->ltgtype = LTG_RUSHBASE; + bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; + bs->rushbaseaway_time = 0; + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + // + switch(BotTeam(bs)) { + case TEAM_RED: VectorSubtract(bs->origin, ctf_blueflag.origin, dir); break; + case TEAM_BLUE: VectorSubtract(bs->origin, ctf_redflag.origin, dir); break; + default: VectorSet(dir, 999, 999, 999); break; + } + // if the bot picked up the flag very close to the enemy base + if ( VectorLength(dir) < 128 ) { + // get an alternative route goal through the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + } else { + // don't use any alt route goal, just get the hell out of the base + bs->altroutegoal.areanum = 0; + } + BotSetUserInfo(bs, "teamtask", va("%d", TEAMTASK_OFFENSE)); + BotVoiceChat(bs, -1, VOICECHAT_IHAVEFLAG); + } + else if (bs->rushbaseaway_time > FloatTime()) { + if (BotTeam(bs) == TEAM_RED) flagstatus = bs->redflagstatus; + else flagstatus = bs->blueflagstatus; + //if the flag is back + if (flagstatus == 0) { + bs->rushbaseaway_time = 0; + } + } + return; + } + // if the bot decided to follow someone + if ( bs->ltgtype == LTG_TEAMACCOMPANY && !bs->ordered ) { + // if the team mate being accompanied no longer carries the flag + BotEntityInfo(bs->teammate, &entinfo); + if (!EntityCarriesFlag(&entinfo)) { + bs->ltgtype = 0; + } + } + // + if (BotTeam(bs) == TEAM_RED) flagstatus = bs->redflagstatus * 2 + bs->blueflagstatus; + else flagstatus = bs->blueflagstatus * 2 + bs->redflagstatus; + //if our team has the enemy flag and our flag is at the base + if (flagstatus == 1) { + // + if (bs->owndecision_time < FloatTime()) { + //if Not defending the base already + if (!(bs->ltgtype == LTG_DEFENDKEYAREA && + (bs->teamgoal.number == ctf_redflag.number || + bs->teamgoal.number == ctf_blueflag.number))) { + //if there is a visible team mate flag carrier + c = BotTeamFlagCarrierVisible(bs); + if (c >= 0 && + // and not already following the team mate flag carrier + (bs->ltgtype != LTG_TEAMACCOMPANY || bs->teammate != c)) { + // + BotRefuseOrder(bs); + //follow the flag carrier + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + //the team mate + bs->teammate = c; + //last time the team mate was visible + bs->teammatevisible_time = FloatTime(); + //no message + bs->teammessage_time = 0; + //no arrive message + bs->arrive_time = 1; + // + BotVoiceChat(bs, bs->teammate, VOICECHAT_ONFOLLOW); + //get the team goal time + bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; + bs->ltgtype = LTG_TEAMACCOMPANY; + bs->formation_dist = 3.5 * 32; //3.5 meter + BotSetTeamStatus(bs); + bs->owndecision_time = FloatTime() + 5; + } + } + } + return; + } + //if the enemy has our flag + else if (flagstatus == 2) { + // + if (bs->owndecision_time < FloatTime()) { + //if enemy flag carrier is visible + c = BotEnemyFlagCarrierVisible(bs); + if (c >= 0) { + //FIXME: fight enemy flag carrier + } + //if not already doing something important + if (bs->ltgtype != LTG_GETFLAG && + bs->ltgtype != LTG_RETURNFLAG && + bs->ltgtype != LTG_TEAMHELP && + bs->ltgtype != LTG_TEAMACCOMPANY && + bs->ltgtype != LTG_CAMPORDER && + bs->ltgtype != LTG_PATROL && + bs->ltgtype != LTG_GETITEM) { + + BotRefuseOrder(bs); + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + // + if (random() < 0.5) { + //go for the enemy flag + bs->ltgtype = LTG_GETFLAG; + } + else { + bs->ltgtype = LTG_RETURNFLAG; + } + //no team message + bs->teammessage_time = 0; + //set the time the bot will stop getting the flag + bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME; + //get an alternative route goal towards the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + // + BotSetTeamStatus(bs); + bs->owndecision_time = FloatTime() + 5; + } + } + return; + } + //if both flags Not at their bases + else if (flagstatus == 3) { + // + if (bs->owndecision_time < FloatTime()) { + // if not trying to return the flag and not following the team flag carrier + if ( bs->ltgtype != LTG_RETURNFLAG && bs->ltgtype != LTG_TEAMACCOMPANY ) { + // + c = BotTeamFlagCarrierVisible(bs); + // if there is a visible team mate flag carrier + if (c >= 0) { + BotRefuseOrder(bs); + //follow the flag carrier + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + //the team mate + bs->teammate = c; + //last time the team mate was visible + bs->teammatevisible_time = FloatTime(); + //no message + bs->teammessage_time = 0; + //no arrive message + bs->arrive_time = 1; + // + BotVoiceChat(bs, bs->teammate, VOICECHAT_ONFOLLOW); + //get the team goal time + bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; + bs->ltgtype = LTG_TEAMACCOMPANY; + bs->formation_dist = 3.5 * 32; //3.5 meter + // + BotSetTeamStatus(bs); + bs->owndecision_time = FloatTime() + 5; + } + else { + BotRefuseOrder(bs); + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + //get the enemy flag + bs->teammessage_time = FloatTime() + 2 * random(); + //get the flag + bs->ltgtype = LTG_RETURNFLAG; + //set the time the bot will stop getting the flag + bs->teamgoal_time = FloatTime() + CTF_RETURNFLAG_TIME; + //get an alternative route goal towards the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + // + BotSetTeamStatus(bs); + bs->owndecision_time = FloatTime() + 5; + } + } + } + return; + } + // don't just do something wait for the bot team leader to give orders + if (BotTeamLeader(bs)) { + return; + } + // if the bot is ordered to do something + if ( bs->lastgoal_ltgtype ) { + bs->teamgoal_time += 60; + } + // if the bot decided to do something on it's own and has a last ordered goal + if ( !bs->ordered && bs->lastgoal_ltgtype ) { + bs->ltgtype = 0; + } + //if already a CTF or team goal + if (bs->ltgtype == LTG_TEAMHELP || + bs->ltgtype == LTG_TEAMACCOMPANY || + bs->ltgtype == LTG_DEFENDKEYAREA || + bs->ltgtype == LTG_GETFLAG || + bs->ltgtype == LTG_RUSHBASE || + bs->ltgtype == LTG_RETURNFLAG || + bs->ltgtype == LTG_CAMPORDER || + bs->ltgtype == LTG_PATROL || + bs->ltgtype == LTG_GETITEM || + bs->ltgtype == LTG_MAKELOVE_UNDER || + bs->ltgtype == LTG_MAKELOVE_ONTOP) { + return; + } + // + if (BotSetLastOrderedTask(bs)) + return; + // + if (bs->owndecision_time > FloatTime()) + return;; + //if the bot is roaming + if (bs->ctfroam_time > FloatTime()) + return; + //if the bot has anough aggression to decide what to do + if (BotAggression(bs) < 50) + return; + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + // + if (bs->teamtaskpreference & (TEAMTP_ATTACKER|TEAMTP_DEFENDER)) { + if (bs->teamtaskpreference & TEAMTP_ATTACKER) { + l1 = 0.7f; + } + else { + l1 = 0.2f; + } + l2 = 0.9f; + } + else { + l1 = 0.4f; + l2 = 0.7f; + } + //get the flag or defend the base + rnd = random(); + if (rnd < l1 && ctf_redflag.areanum && ctf_blueflag.areanum) { + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + bs->ltgtype = LTG_GETFLAG; + //set the time the bot will stop getting the flag + bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME; + //get an alternative route goal towards the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + BotSetTeamStatus(bs); + } + else if (rnd < l2 && ctf_redflag.areanum && ctf_blueflag.areanum) { + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + // + if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); + else memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); + //set the ltg type + bs->ltgtype = LTG_DEFENDKEYAREA; + //set the time the bot stops defending the base + bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; + bs->defendaway_time = 0; + BotSetTeamStatus(bs); + } + else { + bs->ltgtype = 0; + //set the time the bot will stop roaming + bs->ctfroam_time = FloatTime() + CTF_ROAM_TIME; + BotSetTeamStatus(bs); + } + bs->owndecision_time = FloatTime() + 5; +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotCTFRetreatGoals +================== +*/ +void BotCTFRetreatGoals(bot_state_t *bs) { + //when carrying a flag in ctf the bot should rush to the base + if (BotCTFCarryingFlag(bs)) { + //if not already rushing to the base + if (bs->ltgtype != LTG_RUSHBASE) { + BotRefuseOrder(bs); + bs->ltgtype = LTG_RUSHBASE; + bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; + bs->rushbaseaway_time = 0; + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + BotSetTeamStatus(bs); + } + } +} + +#ifdef MISSIONPACK +/* +================== +Bot1FCTFSeekGoals +================== +*/ +void Bot1FCTFSeekGoals(bot_state_t *bs) { + aas_entityinfo_t entinfo; + float rnd, l1, l2; + int c; + + //when carrying a flag in ctf the bot should rush to the base + if (Bot1FCTFCarryingFlag(bs)) { + //if not already rushing to the base + if (bs->ltgtype != LTG_RUSHBASE) { + BotRefuseOrder(bs); + bs->ltgtype = LTG_RUSHBASE; + bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; + bs->rushbaseaway_time = 0; + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + //get an alternative route goal towards the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + // + BotSetTeamStatus(bs); + BotVoiceChat(bs, -1, VOICECHAT_IHAVEFLAG); + } + return; + } + // if the bot decided to follow someone + if ( bs->ltgtype == LTG_TEAMACCOMPANY && !bs->ordered ) { + // if the team mate being accompanied no longer carries the flag + BotEntityInfo(bs->teammate, &entinfo); + if (!EntityCarriesFlag(&entinfo)) { + bs->ltgtype = 0; + } + } + //our team has the flag + if (bs->neutralflagstatus == 1) { + if (bs->owndecision_time < FloatTime()) { + // if not already following someone + if (bs->ltgtype != LTG_TEAMACCOMPANY) { + //if there is a visible team mate flag carrier + c = BotTeamFlagCarrierVisible(bs); + if (c >= 0) { + BotRefuseOrder(bs); + //follow the flag carrier + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + //the team mate + bs->teammate = c; + //last time the team mate was visible + bs->teammatevisible_time = FloatTime(); + //no message + bs->teammessage_time = 0; + //no arrive message + bs->arrive_time = 1; + // + BotVoiceChat(bs, bs->teammate, VOICECHAT_ONFOLLOW); + //get the team goal time + bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; + bs->ltgtype = LTG_TEAMACCOMPANY; + bs->formation_dist = 3.5 * 32; //3.5 meter + BotSetTeamStatus(bs); + bs->owndecision_time = FloatTime() + 5; + return; + } + } + //if already a CTF or team goal + if (bs->ltgtype == LTG_TEAMHELP || + bs->ltgtype == LTG_TEAMACCOMPANY || + bs->ltgtype == LTG_DEFENDKEYAREA || + bs->ltgtype == LTG_GETFLAG || + bs->ltgtype == LTG_RUSHBASE || + bs->ltgtype == LTG_CAMPORDER || + bs->ltgtype == LTG_PATROL || + bs->ltgtype == LTG_ATTACKENEMYBASE || + bs->ltgtype == LTG_GETITEM || + bs->ltgtype == LTG_MAKELOVE_UNDER || + bs->ltgtype == LTG_MAKELOVE_ONTOP) { + return; + } + //if not already attacking the enemy base + if (bs->ltgtype != LTG_ATTACKENEMYBASE) { + BotRefuseOrder(bs); + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + // + if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); + else memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); + //set the ltg type + bs->ltgtype = LTG_ATTACKENEMYBASE; + //set the time the bot will stop getting the flag + bs->teamgoal_time = FloatTime() + TEAM_ATTACKENEMYBASE_TIME; + BotSetTeamStatus(bs); + bs->owndecision_time = FloatTime() + 5; + } + } + return; + } + //enemy team has the flag + else if (bs->neutralflagstatus == 2) { + if (bs->owndecision_time < FloatTime()) { + c = BotEnemyFlagCarrierVisible(bs); + if (c >= 0) { + //FIXME: attack enemy flag carrier + } + //if already a CTF or team goal + if (bs->ltgtype == LTG_TEAMHELP || + bs->ltgtype == LTG_TEAMACCOMPANY || + bs->ltgtype == LTG_CAMPORDER || + bs->ltgtype == LTG_PATROL || + bs->ltgtype == LTG_GETITEM) { + return; + } + // if not already defending the base + if (bs->ltgtype != LTG_DEFENDKEYAREA) { + BotRefuseOrder(bs); + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + // + if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); + else memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); + //set the ltg type + bs->ltgtype = LTG_DEFENDKEYAREA; + //set the time the bot stops defending the base + bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; + bs->defendaway_time = 0; + BotSetTeamStatus(bs); + bs->owndecision_time = FloatTime() + 5; + } + } + return; + } + // don't just do something wait for the bot team leader to give orders + if (BotTeamLeader(bs)) { + return; + } + // if the bot is ordered to do something + if ( bs->lastgoal_ltgtype ) { + bs->teamgoal_time += 60; + } + // if the bot decided to do something on it's own and has a last ordered goal + if ( !bs->ordered && bs->lastgoal_ltgtype ) { + bs->ltgtype = 0; + } + //if already a CTF or team goal + if (bs->ltgtype == LTG_TEAMHELP || + bs->ltgtype == LTG_TEAMACCOMPANY || + bs->ltgtype == LTG_DEFENDKEYAREA || + bs->ltgtype == LTG_GETFLAG || + bs->ltgtype == LTG_RUSHBASE || + bs->ltgtype == LTG_RETURNFLAG || + bs->ltgtype == LTG_CAMPORDER || + bs->ltgtype == LTG_PATROL || + bs->ltgtype == LTG_ATTACKENEMYBASE || + bs->ltgtype == LTG_GETITEM || + bs->ltgtype == LTG_MAKELOVE_UNDER || + bs->ltgtype == LTG_MAKELOVE_ONTOP) { + return; + } + // + if (BotSetLastOrderedTask(bs)) + return; + // + if (bs->owndecision_time > FloatTime()) + return;; + //if the bot is roaming + if (bs->ctfroam_time > FloatTime()) + return; + //if the bot has anough aggression to decide what to do + if (BotAggression(bs) < 50) + return; + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + // + if (bs->teamtaskpreference & (TEAMTP_ATTACKER|TEAMTP_DEFENDER)) { + if (bs->teamtaskpreference & TEAMTP_ATTACKER) { + l1 = 0.7f; + } + else { + l1 = 0.2f; + } + l2 = 0.9f; + } + else { + l1 = 0.4f; + l2 = 0.7f; + } + //get the flag or defend the base + rnd = random(); + if (rnd < l1 && ctf_neutralflag.areanum) { + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + bs->ltgtype = LTG_GETFLAG; + //set the time the bot will stop getting the flag + bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME; + BotSetTeamStatus(bs); + } + else if (rnd < l2 && ctf_redflag.areanum && ctf_blueflag.areanum) { + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + // + if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); + else memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); + //set the ltg type + bs->ltgtype = LTG_DEFENDKEYAREA; + //set the time the bot stops defending the base + bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; + bs->defendaway_time = 0; + BotSetTeamStatus(bs); + } + else { + bs->ltgtype = 0; + //set the time the bot will stop roaming + bs->ctfroam_time = FloatTime() + CTF_ROAM_TIME; + BotSetTeamStatus(bs); + } + bs->owndecision_time = FloatTime() + 5; +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +Bot1FCTFRetreatGoals +================== +*/ +void Bot1FCTFRetreatGoals(bot_state_t *bs) { + //when carrying a flag in ctf the bot should rush to the enemy base + if (Bot1FCTFCarryingFlag(bs)) { + //if not already rushing to the base + if (bs->ltgtype != LTG_RUSHBASE) { + BotRefuseOrder(bs); + bs->ltgtype = LTG_RUSHBASE; + bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; + bs->rushbaseaway_time = 0; + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + //get an alternative route goal towards the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + BotSetTeamStatus(bs); + } + } +} + +/* +================== +BotObeliskSeekGoals +================== +*/ +void BotObeliskSeekGoals(bot_state_t *bs) { + float rnd, l1, l2; + + // don't just do something wait for the bot team leader to give orders + if (BotTeamLeader(bs)) { + return; + } + // if the bot is ordered to do something + if ( bs->lastgoal_ltgtype ) { + bs->teamgoal_time += 60; + } + //if already a team goal + if (bs->ltgtype == LTG_TEAMHELP || + bs->ltgtype == LTG_TEAMACCOMPANY || + bs->ltgtype == LTG_DEFENDKEYAREA || + bs->ltgtype == LTG_GETFLAG || + bs->ltgtype == LTG_RUSHBASE || + bs->ltgtype == LTG_RETURNFLAG || + bs->ltgtype == LTG_CAMPORDER || + bs->ltgtype == LTG_PATROL || + bs->ltgtype == LTG_ATTACKENEMYBASE || + bs->ltgtype == LTG_GETITEM || + bs->ltgtype == LTG_MAKELOVE_UNDER || + bs->ltgtype == LTG_MAKELOVE_ONTOP) { + return; + } + // + if (BotSetLastOrderedTask(bs)) + return; + //if the bot is roaming + if (bs->ctfroam_time > FloatTime()) + return; + //if the bot has anough aggression to decide what to do + if (BotAggression(bs) < 50) + return; + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + // + if (bs->teamtaskpreference & (TEAMTP_ATTACKER|TEAMTP_DEFENDER)) { + if (bs->teamtaskpreference & TEAMTP_ATTACKER) { + l1 = 0.7f; + } + else { + l1 = 0.2f; + } + l2 = 0.9f; + } + else { + l1 = 0.4f; + l2 = 0.7f; + } + //get the flag or defend the base + rnd = random(); + if (rnd < l1 && redobelisk.areanum && blueobelisk.areanum) { + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + // + if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t)); + else memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t)); + //set the ltg type + bs->ltgtype = LTG_ATTACKENEMYBASE; + //set the time the bot will stop attacking the enemy base + bs->teamgoal_time = FloatTime() + TEAM_ATTACKENEMYBASE_TIME; + //get an alternate route goal towards the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + BotSetTeamStatus(bs); + } + else if (rnd < l2 && redobelisk.areanum && blueobelisk.areanum) { + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + // + if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t)); + else memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t)); + //set the ltg type + bs->ltgtype = LTG_DEFENDKEYAREA; + //set the time the bot stops defending the base + bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; + bs->defendaway_time = 0; + BotSetTeamStatus(bs); + } + else { + bs->ltgtype = 0; + //set the time the bot will stop roaming + bs->ctfroam_time = FloatTime() + CTF_ROAM_TIME; + BotSetTeamStatus(bs); + } +} + +/* +================== +BotGoHarvest +================== +*/ +void BotGoHarvest(bot_state_t *bs) { + // + if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t)); + else memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t)); + //set the ltg type + bs->ltgtype = LTG_HARVEST; + //set the time the bot will stop harvesting + bs->teamgoal_time = FloatTime() + TEAM_HARVEST_TIME; + bs->harvestaway_time = 0; + BotSetTeamStatus(bs); +} + +/* +================== +BotObeliskRetreatGoals +================== +*/ +void BotObeliskRetreatGoals(bot_state_t *bs) { + //nothing special +} + +/* +================== +BotHarvesterSeekGoals +================== +*/ +void BotHarvesterSeekGoals(bot_state_t *bs) { + aas_entityinfo_t entinfo; + float rnd, l1, l2; + int c; + + //when carrying cubes in harvester the bot should rush to the base + if (BotHarvesterCarryingCubes(bs)) { + //if not already rushing to the base + if (bs->ltgtype != LTG_RUSHBASE) { + BotRefuseOrder(bs); + bs->ltgtype = LTG_RUSHBASE; + bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; + bs->rushbaseaway_time = 0; + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + //get an alternative route goal towards the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + // + BotSetTeamStatus(bs); + } + return; + } + // don't just do something wait for the bot team leader to give orders + if (BotTeamLeader(bs)) { + return; + } + // if the bot decided to follow someone + if ( bs->ltgtype == LTG_TEAMACCOMPANY && !bs->ordered ) { + // if the team mate being accompanied no longer carries the flag + BotEntityInfo(bs->teammate, &entinfo); + if (!EntityCarriesCubes(&entinfo)) { + bs->ltgtype = 0; + } + } + // if the bot is ordered to do something + if ( bs->lastgoal_ltgtype ) { + bs->teamgoal_time += 60; + } + //if not yet doing something + if (bs->ltgtype == LTG_TEAMHELP || + bs->ltgtype == LTG_TEAMACCOMPANY || + bs->ltgtype == LTG_DEFENDKEYAREA || + bs->ltgtype == LTG_GETFLAG || + bs->ltgtype == LTG_CAMPORDER || + bs->ltgtype == LTG_PATROL || + bs->ltgtype == LTG_ATTACKENEMYBASE || + bs->ltgtype == LTG_HARVEST || + bs->ltgtype == LTG_GETITEM || + bs->ltgtype == LTG_MAKELOVE_UNDER || + bs->ltgtype == LTG_MAKELOVE_ONTOP) { + return; + } + // + if (BotSetLastOrderedTask(bs)) + return; + //if the bot is roaming + if (bs->ctfroam_time > FloatTime()) + return; + //if the bot has anough aggression to decide what to do + if (BotAggression(bs) < 50) + return; + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + // + c = BotEnemyCubeCarrierVisible(bs); + if (c >= 0) { + //FIXME: attack enemy cube carrier + } + if (bs->ltgtype != LTG_TEAMACCOMPANY) { + //if there is a visible team mate carrying cubes + c = BotTeamCubeCarrierVisible(bs); + if (c >= 0) { + //follow the team mate carrying cubes + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + //the team mate + bs->teammate = c; + //last time the team mate was visible + bs->teammatevisible_time = FloatTime(); + //no message + bs->teammessage_time = 0; + //no arrive message + bs->arrive_time = 1; + // + BotVoiceChat(bs, bs->teammate, VOICECHAT_ONFOLLOW); + //get the team goal time + bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; + bs->ltgtype = LTG_TEAMACCOMPANY; + bs->formation_dist = 3.5 * 32; //3.5 meter + BotSetTeamStatus(bs); + return; + } + } + // + if (bs->teamtaskpreference & (TEAMTP_ATTACKER|TEAMTP_DEFENDER)) { + if (bs->teamtaskpreference & TEAMTP_ATTACKER) { + l1 = 0.7f; + } + else { + l1 = 0.2f; + } + l2 = 0.9f; + } + else { + l1 = 0.4f; + l2 = 0.7f; + } + // + rnd = random(); + if (rnd < l1 && redobelisk.areanum && blueobelisk.areanum) { + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + BotGoHarvest(bs); + } + else if (rnd < l2 && redobelisk.areanum && blueobelisk.areanum) { + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + // + if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t)); + else memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t)); + //set the ltg type + bs->ltgtype = LTG_DEFENDKEYAREA; + //set the time the bot stops defending the base + bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; + bs->defendaway_time = 0; + BotSetTeamStatus(bs); + } + else { + bs->ltgtype = 0; + //set the time the bot will stop roaming + bs->ctfroam_time = FloatTime() + CTF_ROAM_TIME; + BotSetTeamStatus(bs); + } +} + +/* +================== +BotHarvesterRetreatGoals +================== +*/ +void BotHarvesterRetreatGoals(bot_state_t *bs) { + //when carrying cubes in harvester the bot should rush to the base + if (BotHarvesterCarryingCubes(bs)) { + //if not already rushing to the base + if (bs->ltgtype != LTG_RUSHBASE) { + BotRefuseOrder(bs); + bs->ltgtype = LTG_RUSHBASE; + bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; + bs->rushbaseaway_time = 0; + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + BotSetTeamStatus(bs); + } + return; + } +} +#endif + +/* +================== +BotTeamGoals +================== +*/ +void BotTeamGoals(bot_state_t *bs, int retreat) { + + if ( retreat ) { + if (gametype == GT_CTF) { + BotCTFRetreatGoals(bs); + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + Bot1FCTFRetreatGoals(bs); + } + else if (gametype == GT_OBELISK) { + BotObeliskRetreatGoals(bs); + } + else if (gametype == GT_HARVESTER) { + BotHarvesterRetreatGoals(bs); + } +#endif + } + else { + if (gametype == GT_CTF) { + //decide what to do in CTF mode + BotCTFSeekGoals(bs); + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + Bot1FCTFSeekGoals(bs); + } + else if (gametype == GT_OBELISK) { + BotObeliskSeekGoals(bs); + } + else if (gametype == GT_HARVESTER) { + BotHarvesterSeekGoals(bs); + } +#endif + } + // reset the order time which is used to see if + // we decided to refuse an order + bs->order_time = 0; +} + +/* +================== +BotPointAreaNum +================== +*/ +int BotPointAreaNum(vec3_t origin) { + int areanum, numareas, areas[10]; + vec3_t end; + + areanum = trap_AAS_PointAreaNum(origin); + if (areanum) return areanum; + VectorCopy(origin, end); + end[2] += 10; + numareas = trap_AAS_TraceAreas(origin, end, areas, NULL, 10); + if (numareas > 0) return areas[0]; + return 0; +} + +/* +================== +ClientName +================== +*/ +char *ClientName(int client, char *name, int size) { + char buf[MAX_INFO_STRING]; + + if (client < 0 || client >= MAX_CLIENTS) { + BotAI_Print(PRT_ERROR, "ClientName: client out of range\n"); + return "[client out of range]"; + } + trap_GetConfigstring(CS_PLAYERS+client, buf, sizeof(buf)); + strncpy(name, Info_ValueForKey(buf, "n"), size-1); + name[size-1] = '\0'; + Q_CleanStr( name ); + return name; +} + +/* +================== +ClientSkin +================== +*/ +char *ClientSkin(int client, char *skin, int size) { + char buf[MAX_INFO_STRING]; + + if (client < 0 || client >= MAX_CLIENTS) { + BotAI_Print(PRT_ERROR, "ClientSkin: client out of range\n"); + return "[client out of range]"; + } + trap_GetConfigstring(CS_PLAYERS+client, buf, sizeof(buf)); + strncpy(skin, Info_ValueForKey(buf, "model"), size-1); + skin[size-1] = '\0'; + return skin; +} + +/* +================== +ClientFromName +================== +*/ +int ClientFromName(char *name) { + int i; + char buf[MAX_INFO_STRING]; + static int maxclients; + + if (!maxclients) + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + Q_CleanStr( buf ); + if (!Q_stricmp(Info_ValueForKey(buf, "n"), name)) return i; + } + return -1; +} + +/* +================== +ClientOnSameTeamFromName +================== +*/ +int ClientOnSameTeamFromName(bot_state_t *bs, char *name) { + int i; + char buf[MAX_INFO_STRING]; + static int maxclients; + + if (!maxclients) + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + if (!BotSameTeam(bs, i)) + continue; + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + Q_CleanStr( buf ); + if (!Q_stricmp(Info_ValueForKey(buf, "n"), name)) return i; + } + return -1; +} + +/* +================== +stristr +================== +*/ +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; +} + +/* +================== +EasyClientName +================== +*/ +char *EasyClientName(int client, char *buf, int size) { + int i; + char *str1, *str2, *ptr, c; + char name[128]; + + strcpy(name, ClientName(client, name, sizeof(name))); + for (i = 0; name[i]; i++) name[i] &= 127; + //remove all spaces + for (ptr = strstr(name, " "); ptr; ptr = strstr(name, " ")) { + memmove(ptr, ptr+1, strlen(ptr+1)+1); + } + //check for [x] and ]x[ clan names + str1 = strstr(name, "["); + str2 = strstr(name, "]"); + if (str1 && str2) { + if (str2 > str1) memmove(str1, str2+1, strlen(str2+1)+1); + else memmove(str2, str1+1, strlen(str1+1)+1); + } + //remove Mr prefix + if ((name[0] == 'm' || name[0] == 'M') && + (name[1] == 'r' || name[1] == 'R')) { + memmove(name, name+2, strlen(name+2)+1); + } + //only allow lower case alphabet characters + ptr = name; + while(*ptr) { + c = *ptr; + if ((c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || c == '_') { + ptr++; + } + else if (c >= 'A' && c <= 'Z') { + *ptr += 'a' - 'A'; + ptr++; + } + else { + memmove(ptr, ptr+1, strlen(ptr + 1)+1); + } + } + strncpy(buf, name, size-1); + buf[size-1] = '\0'; + return buf; +} + +/* +================== +BotSynonymContext +================== +*/ +int BotSynonymContext(bot_state_t *bs) { + int context; + + context = CONTEXT_NORMAL|CONTEXT_NEARBYITEM|CONTEXT_NAMES; + // + if (gametype == GT_CTF +#ifdef MISSIONPACK + || gametype == GT_1FCTF +#endif + ) { + if (BotTeam(bs) == TEAM_RED) context |= CONTEXT_CTFREDTEAM; + else context |= CONTEXT_CTFBLUETEAM; + } +#ifdef MISSIONPACK + else if (gametype == GT_OBELISK) { + if (BotTeam(bs) == TEAM_RED) context |= CONTEXT_OBELISKREDTEAM; + else context |= CONTEXT_OBELISKBLUETEAM; + } + else if (gametype == GT_HARVESTER) { + if (BotTeam(bs) == TEAM_RED) context |= CONTEXT_HARVESTERREDTEAM; + else context |= CONTEXT_HARVESTERBLUETEAM; + } +#endif + return context; +} + +/* +================== +BotChooseWeapon +================== +*/ +void BotChooseWeapon(bot_state_t *bs) { + int newweaponnum; + + if (bs->cur_ps.weaponstate == WEAPON_RAISING || + bs->cur_ps.weaponstate == WEAPON_DROPPING) { + trap_EA_SelectWeapon(bs->client, bs->weaponnum); + } + else { + newweaponnum = trap_BotChooseBestFightWeapon(bs->ws, bs->inventory); + if (bs->weaponnum != newweaponnum) bs->weaponchange_time = FloatTime(); + bs->weaponnum = newweaponnum; + //BotAI_Print(PRT_MESSAGE, "bs->weaponnum = %d\n", bs->weaponnum); + trap_EA_SelectWeapon(bs->client, bs->weaponnum); + } +} + +/* +================== +BotSetupForMovement +================== +*/ +void BotSetupForMovement(bot_state_t *bs) { + bot_initmove_t initmove; + + memset(&initmove, 0, sizeof(bot_initmove_t)); + VectorCopy(bs->cur_ps.origin, initmove.origin); + VectorCopy(bs->cur_ps.velocity, initmove.velocity); + VectorClear(initmove.viewoffset); + initmove.viewoffset[2] += bs->cur_ps.viewheight; + initmove.entitynum = bs->entitynum; + initmove.client = bs->client; + initmove.thinktime = bs->thinktime; + //set the onground flag + if (bs->cur_ps.groundEntityNum != ENTITYNUM_NONE) initmove.or_moveflags |= MFL_ONGROUND; + //set the teleported flag + if ((bs->cur_ps.pm_flags & PMF_TIME_KNOCKBACK) && (bs->cur_ps.pm_time > 0)) { + initmove.or_moveflags |= MFL_TELEPORTED; + } + //set the waterjump flag + if ((bs->cur_ps.pm_flags & PMF_TIME_WATERJUMP) && (bs->cur_ps.pm_time > 0)) { + initmove.or_moveflags |= MFL_WATERJUMP; + } + //set presence type + if (bs->cur_ps.pm_flags & PMF_DUCKED) initmove.presencetype = PRESENCE_CROUCH; + else initmove.presencetype = PRESENCE_NORMAL; + // + if (bs->walker > 0.5) initmove.or_moveflags |= MFL_WALK; + // + VectorCopy(bs->viewangles, initmove.viewangles); + // + trap_BotInitMoveState(bs->ms, &initmove); +} + +/* +================== +BotCheckItemPickup +================== +*/ +void BotCheckItemPickup(bot_state_t *bs, int *oldinventory) { +#ifdef MISSIONPACK + int offence, leader; + + if (gametype <= GT_TEAM) + return; + + offence = -1; + // go into offence if picked up the kamikaze or invulnerability + if (!oldinventory[INVENTORY_KAMIKAZE] && bs->inventory[INVENTORY_KAMIKAZE] >= 1) { + offence = qtrue; + } + if (!oldinventory[INVENTORY_INVULNERABILITY] && bs->inventory[INVENTORY_INVULNERABILITY] >= 1) { + offence = qtrue; + } + // if not already wearing the kamikaze or invulnerability + if (!bs->inventory[INVENTORY_KAMIKAZE] && !bs->inventory[INVENTORY_INVULNERABILITY]) { + if (!oldinventory[INVENTORY_SCOUT] && bs->inventory[INVENTORY_SCOUT] >= 1) { + offence = qtrue; + } + if (!oldinventory[INVENTORY_GUARD] && bs->inventory[INVENTORY_GUARD] >= 1) { + offence = qtrue; + } + if (!oldinventory[INVENTORY_DOUBLER] && bs->inventory[INVENTORY_DOUBLER] >= 1) { + offence = qfalse; + } + if (!oldinventory[INVENTORY_AMMOREGEN] && bs->inventory[INVENTORY_AMMOREGEN] >= 1) { + offence = qfalse; + } + } + + if (offence >= 0) { + leader = ClientFromName(bs->teamleader); + if (offence) { + if (!(bs->teamtaskpreference & TEAMTP_ATTACKER)) { + // if we have a bot team leader + if (BotTeamLeader(bs)) { + // tell the leader we want to be on offence + BotVoiceChat(bs, leader, VOICECHAT_WANTONOFFENSE); + //BotAI_BotInitialChat(bs, "wantoffence", NULL); + //trap_BotEnterChat(bs->cs, leader, CHAT_TELL); + } + else if (g_spSkill.integer <= 3) { + if ( bs->ltgtype != LTG_GETFLAG && + bs->ltgtype != LTG_ATTACKENEMYBASE && + bs->ltgtype != LTG_HARVEST ) { + // + if ((gametype != GT_CTF || (bs->redflagstatus == 0 && bs->blueflagstatus == 0)) && + (gametype != GT_1FCTF || bs->neutralflagstatus == 0) ) { + // tell the leader we want to be on offence + BotVoiceChat(bs, leader, VOICECHAT_WANTONOFFENSE); + //BotAI_BotInitialChat(bs, "wantoffence", NULL); + //trap_BotEnterChat(bs->cs, leader, CHAT_TELL); + } + } + bs->teamtaskpreference |= TEAMTP_ATTACKER; + } + } + bs->teamtaskpreference &= ~TEAMTP_DEFENDER; + } + else { + if (!(bs->teamtaskpreference & TEAMTP_DEFENDER)) { + // if we have a bot team leader + if (BotTeamLeader(bs)) { + // tell the leader we want to be on defense + BotVoiceChat(bs, -1, VOICECHAT_WANTONDEFENSE); + //BotAI_BotInitialChat(bs, "wantdefence", NULL); + //trap_BotEnterChat(bs->cs, leader, CHAT_TELL); + } + else if (g_spSkill.integer <= 3) { + if ( bs->ltgtype != LTG_DEFENDKEYAREA ) { + // + if ((gametype != GT_CTF || (bs->redflagstatus == 0 && bs->blueflagstatus == 0)) && + (gametype != GT_1FCTF || bs->neutralflagstatus == 0) ) { + // tell the leader we want to be on defense + BotVoiceChat(bs, -1, VOICECHAT_WANTONDEFENSE); + //BotAI_BotInitialChat(bs, "wantdefence", NULL); + //trap_BotEnterChat(bs->cs, leader, CHAT_TELL); + } + } + } + bs->teamtaskpreference |= TEAMTP_DEFENDER; + } + bs->teamtaskpreference &= ~TEAMTP_ATTACKER; + } + } +#endif +} + +/* +================== +BotUpdateInventory +================== +*/ +void BotUpdateInventory(bot_state_t *bs) { + int oldinventory[MAX_ITEMS]; + + memcpy(oldinventory, bs->inventory, sizeof(oldinventory)); + //armor + bs->inventory[INVENTORY_ARMOR] = bs->cur_ps.stats[STAT_ARMOR]; + //weapons + bs->inventory[INVENTORY_GAUNTLET] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GAUNTLET)) != 0; + bs->inventory[INVENTORY_SHOTGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_SHOTGUN)) != 0; + bs->inventory[INVENTORY_MACHINEGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_MACHINEGUN)) != 0; + bs->inventory[INVENTORY_GRENADELAUNCHER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GRENADE_LAUNCHER)) != 0; + bs->inventory[INVENTORY_ROCKETLAUNCHER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_ROCKET_LAUNCHER)) != 0; + bs->inventory[INVENTORY_LIGHTNING] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_LIGHTNING)) != 0; + bs->inventory[INVENTORY_RAILGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_RAILGUN)) != 0; + bs->inventory[INVENTORY_PLASMAGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_PLASMAGUN)) != 0; + bs->inventory[INVENTORY_BFG10K] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_BFG)) != 0; + bs->inventory[INVENTORY_GRAPPLINGHOOK] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GRAPPLING_HOOK)) != 0; +#ifdef MISSIONPACK + bs->inventory[INVENTORY_NAILGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_NAILGUN)) != 0;; + bs->inventory[INVENTORY_PROXLAUNCHER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_PROX_LAUNCHER)) != 0;; + bs->inventory[INVENTORY_CHAINGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_CHAINGUN)) != 0;; +#endif + //ammo + bs->inventory[INVENTORY_SHELLS] = bs->cur_ps.ammo[WP_SHOTGUN]; + bs->inventory[INVENTORY_BULLETS] = bs->cur_ps.ammo[WP_MACHINEGUN]; + bs->inventory[INVENTORY_GRENADES] = bs->cur_ps.ammo[WP_GRENADE_LAUNCHER]; + bs->inventory[INVENTORY_CELLS] = bs->cur_ps.ammo[WP_PLASMAGUN]; + bs->inventory[INVENTORY_LIGHTNINGAMMO] = bs->cur_ps.ammo[WP_LIGHTNING]; + bs->inventory[INVENTORY_ROCKETS] = bs->cur_ps.ammo[WP_ROCKET_LAUNCHER]; + bs->inventory[INVENTORY_SLUGS] = bs->cur_ps.ammo[WP_RAILGUN]; + bs->inventory[INVENTORY_BFGAMMO] = bs->cur_ps.ammo[WP_BFG]; +#ifdef MISSIONPACK + bs->inventory[INVENTORY_NAILS] = bs->cur_ps.ammo[WP_NAILGUN]; + bs->inventory[INVENTORY_MINES] = bs->cur_ps.ammo[WP_PROX_LAUNCHER]; + bs->inventory[INVENTORY_BELT] = bs->cur_ps.ammo[WP_CHAINGUN]; +#endif + //powerups + bs->inventory[INVENTORY_HEALTH] = bs->cur_ps.stats[STAT_HEALTH]; + bs->inventory[INVENTORY_TELEPORTER] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_TELEPORTER; + bs->inventory[INVENTORY_MEDKIT] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_MEDKIT; +#ifdef MISSIONPACK + bs->inventory[INVENTORY_KAMIKAZE] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_KAMIKAZE; + bs->inventory[INVENTORY_PORTAL] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_PORTAL; + bs->inventory[INVENTORY_INVULNERABILITY] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_INVULNERABILITY; +#endif + bs->inventory[INVENTORY_QUAD] = bs->cur_ps.powerups[PW_QUAD] != 0; + bs->inventory[INVENTORY_ENVIRONMENTSUIT] = bs->cur_ps.powerups[PW_BATTLESUIT] != 0; + bs->inventory[INVENTORY_HASTE] = bs->cur_ps.powerups[PW_HASTE] != 0; + bs->inventory[INVENTORY_INVISIBILITY] = bs->cur_ps.powerups[PW_INVIS] != 0; + bs->inventory[INVENTORY_REGEN] = bs->cur_ps.powerups[PW_REGEN] != 0; + bs->inventory[INVENTORY_FLIGHT] = bs->cur_ps.powerups[PW_FLIGHT] != 0; +#ifdef MISSIONPACK + bs->inventory[INVENTORY_SCOUT] = bs->cur_ps.stats[STAT_PERSISTANT_POWERUP] == MODELINDEX_SCOUT; + bs->inventory[INVENTORY_GUARD] = bs->cur_ps.stats[STAT_PERSISTANT_POWERUP] == MODELINDEX_GUARD; + bs->inventory[INVENTORY_DOUBLER] = bs->cur_ps.stats[STAT_PERSISTANT_POWERUP] == MODELINDEX_DOUBLER; + bs->inventory[INVENTORY_AMMOREGEN] = bs->cur_ps.stats[STAT_PERSISTANT_POWERUP] == MODELINDEX_AMMOREGEN; +#endif + bs->inventory[INVENTORY_REDFLAG] = bs->cur_ps.powerups[PW_REDFLAG] != 0; + bs->inventory[INVENTORY_BLUEFLAG] = bs->cur_ps.powerups[PW_BLUEFLAG] != 0; +#ifdef MISSIONPACK + bs->inventory[INVENTORY_NEUTRALFLAG] = bs->cur_ps.powerups[PW_NEUTRALFLAG] != 0; + if (BotTeam(bs) == TEAM_RED) { + bs->inventory[INVENTORY_REDCUBE] = bs->cur_ps.generic1; + bs->inventory[INVENTORY_BLUECUBE] = 0; + } + else { + bs->inventory[INVENTORY_REDCUBE] = 0; + bs->inventory[INVENTORY_BLUECUBE] = bs->cur_ps.generic1; + } +#endif + BotCheckItemPickup(bs, oldinventory); +} + +/* +================== +BotUpdateBattleInventory +================== +*/ +void BotUpdateBattleInventory(bot_state_t *bs, int enemy) { + vec3_t dir; + aas_entityinfo_t entinfo; + + BotEntityInfo(enemy, &entinfo); + VectorSubtract(entinfo.origin, bs->origin, dir); + bs->inventory[ENEMY_HEIGHT] = (int) dir[2]; + dir[2] = 0; + bs->inventory[ENEMY_HORIZONTAL_DIST] = (int) VectorLength(dir); + //FIXME: add num visible enemies and num visible team mates to the inventory +} + +#ifdef MISSIONPACK +/* +================== +BotUseKamikaze +================== +*/ +#define KAMIKAZE_DIST 1024 + +void BotUseKamikaze(bot_state_t *bs) { + int c, teammates, enemies; + aas_entityinfo_t entinfo; + vec3_t dir, target; + bot_goal_t *goal; + bsp_trace_t trace; + + //if the bot has no kamikaze + if (bs->inventory[INVENTORY_KAMIKAZE] <= 0) + return; + if (bs->kamikaze_time > FloatTime()) + return; + bs->kamikaze_time = FloatTime() + 0.2; + if (gametype == GT_CTF) { + //never use kamikaze if the team flag carrier is visible + if (BotCTFCarryingFlag(bs)) + return; + c = BotTeamFlagCarrierVisible(bs); + if (c >= 0) { + BotEntityInfo(c, &entinfo); + VectorSubtract(entinfo.origin, bs->origin, dir); + if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) + return; + } + c = BotEnemyFlagCarrierVisible(bs); + if (c >= 0) { + BotEntityInfo(c, &entinfo); + VectorSubtract(entinfo.origin, bs->origin, dir); + if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) { + trap_EA_Use(bs->client); + return; + } + } + } + else if (gametype == GT_1FCTF) { + //never use kamikaze if the team flag carrier is visible + if (Bot1FCTFCarryingFlag(bs)) + return; + c = BotTeamFlagCarrierVisible(bs); + if (c >= 0) { + BotEntityInfo(c, &entinfo); + VectorSubtract(entinfo.origin, bs->origin, dir); + if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) + return; + } + c = BotEnemyFlagCarrierVisible(bs); + if (c >= 0) { + BotEntityInfo(c, &entinfo); + VectorSubtract(entinfo.origin, bs->origin, dir); + if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) { + trap_EA_Use(bs->client); + return; + } + } + } + else if (gametype == GT_OBELISK) { + switch(BotTeam(bs)) { + case TEAM_RED: goal = &blueobelisk; break; + default: goal = &redobelisk; break; + } + //if the obelisk is visible + VectorCopy(goal->origin, target); + target[2] += 1; + VectorSubtract(bs->origin, target, dir); + if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST * 0.9)) { + BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); + if (trace.fraction >= 1 || trace.ent == goal->entitynum) { + trap_EA_Use(bs->client); + return; + } + } + } + else if (gametype == GT_HARVESTER) { + // + if (BotHarvesterCarryingCubes(bs)) + return; + //never use kamikaze if a team mate carrying cubes is visible + c = BotTeamCubeCarrierVisible(bs); + if (c >= 0) { + BotEntityInfo(c, &entinfo); + VectorSubtract(entinfo.origin, bs->origin, dir); + if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) + return; + } + c = BotEnemyCubeCarrierVisible(bs); + if (c >= 0) { + BotEntityInfo(c, &entinfo); + VectorSubtract(entinfo.origin, bs->origin, dir); + if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) { + trap_EA_Use(bs->client); + return; + } + } + } + // + BotVisibleTeamMatesAndEnemies(bs, &teammates, &enemies, KAMIKAZE_DIST); + // + if (enemies > 2 && enemies > teammates+1) { + trap_EA_Use(bs->client); + return; + } +} + +/* +================== +BotUseInvulnerability +================== +*/ +void BotUseInvulnerability(bot_state_t *bs) { + int c; + vec3_t dir, target; + bot_goal_t *goal; + bsp_trace_t trace; + + //if the bot has no invulnerability + if (bs->inventory[INVENTORY_INVULNERABILITY] <= 0) + return; + if (bs->invulnerability_time > FloatTime()) + return; + bs->invulnerability_time = FloatTime() + 0.2; + if (gametype == GT_CTF) { + //never use kamikaze if the team flag carrier is visible + if (BotCTFCarryingFlag(bs)) + return; + c = BotEnemyFlagCarrierVisible(bs); + if (c >= 0) + return; + //if near enemy flag and the flag is visible + switch(BotTeam(bs)) { + case TEAM_RED: goal = &ctf_blueflag; break; + default: goal = &ctf_redflag; break; + } + //if the obelisk is visible + VectorCopy(goal->origin, target); + target[2] += 1; + VectorSubtract(bs->origin, target, dir); + if (VectorLengthSquared(dir) < Square(200)) { + BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); + if (trace.fraction >= 1 || trace.ent == goal->entitynum) { + trap_EA_Use(bs->client); + return; + } + } + } + else if (gametype == GT_1FCTF) { + //never use kamikaze if the team flag carrier is visible + if (Bot1FCTFCarryingFlag(bs)) + return; + c = BotEnemyFlagCarrierVisible(bs); + if (c >= 0) + return; + //if near enemy flag and the flag is visible + switch(BotTeam(bs)) { + case TEAM_RED: goal = &ctf_blueflag; break; + default: goal = &ctf_redflag; break; + } + //if the obelisk is visible + VectorCopy(goal->origin, target); + target[2] += 1; + VectorSubtract(bs->origin, target, dir); + if (VectorLengthSquared(dir) < Square(200)) { + BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); + if (trace.fraction >= 1 || trace.ent == goal->entitynum) { + trap_EA_Use(bs->client); + return; + } + } + } + else if (gametype == GT_OBELISK) { + switch(BotTeam(bs)) { + case TEAM_RED: goal = &blueobelisk; break; + default: goal = &redobelisk; break; + } + //if the obelisk is visible + VectorCopy(goal->origin, target); + target[2] += 1; + VectorSubtract(bs->origin, target, dir); + if (VectorLengthSquared(dir) < Square(300)) { + BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); + if (trace.fraction >= 1 || trace.ent == goal->entitynum) { + trap_EA_Use(bs->client); + return; + } + } + } + else if (gametype == GT_HARVESTER) { + // + if (BotHarvesterCarryingCubes(bs)) + return; + c = BotEnemyCubeCarrierVisible(bs); + if (c >= 0) + return; + //if near enemy base and enemy base is visible + switch(BotTeam(bs)) { + case TEAM_RED: goal = &blueobelisk; break; + default: goal = &redobelisk; break; + } + //if the obelisk is visible + VectorCopy(goal->origin, target); + target[2] += 1; + VectorSubtract(bs->origin, target, dir); + if (VectorLengthSquared(dir) < Square(200)) { + BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); + if (trace.fraction >= 1 || trace.ent == goal->entitynum) { + trap_EA_Use(bs->client); + return; + } + } + } +} +#endif + +/* +================== +BotBattleUseItems +================== +*/ +void BotBattleUseItems(bot_state_t *bs) { + if (bs->inventory[INVENTORY_HEALTH] < 40) { + if (bs->inventory[INVENTORY_TELEPORTER] > 0) { + if (!BotCTFCarryingFlag(bs) +#ifdef MISSIONPACK + && !Bot1FCTFCarryingFlag(bs) + && !BotHarvesterCarryingCubes(bs) +#endif + ) { + trap_EA_Use(bs->client); + } + } + } + if (bs->inventory[INVENTORY_HEALTH] < 60) { + if (bs->inventory[INVENTORY_MEDKIT] > 0) { + trap_EA_Use(bs->client); + } + } +#ifdef MISSIONPACK + BotUseKamikaze(bs); + BotUseInvulnerability(bs); +#endif +} + +/* +================== +BotSetTeleportTime +================== +*/ +void BotSetTeleportTime(bot_state_t *bs) { + if ((bs->cur_ps.eFlags ^ bs->last_eFlags) & EF_TELEPORT_BIT) { + bs->teleport_time = FloatTime(); + } + bs->last_eFlags = bs->cur_ps.eFlags; +} + +/* +================== +BotIsDead +================== +*/ +qboolean BotIsDead(bot_state_t *bs) { + return (bs->cur_ps.pm_type == PM_DEAD); +} + +/* +================== +BotIsObserver +================== +*/ +qboolean BotIsObserver(bot_state_t *bs) { + char buf[MAX_INFO_STRING]; + if (bs->cur_ps.pm_type == PM_SPECTATOR) return qtrue; + trap_GetConfigstring(CS_PLAYERS+bs->client, buf, sizeof(buf)); + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) return qtrue; + return qfalse; +} + +/* +================== +BotIntermission +================== +*/ +qboolean BotIntermission(bot_state_t *bs) { + //NOTE: we shouldn't be looking at the game code... + if (level.intermissiontime) return qtrue; + return (bs->cur_ps.pm_type == PM_FREEZE || bs->cur_ps.pm_type == PM_INTERMISSION); +} + +/* +================== +BotInLavaOrSlime +================== +*/ +qboolean BotInLavaOrSlime(bot_state_t *bs) { + vec3_t feet; + + VectorCopy(bs->origin, feet); + feet[2] -= 23; + return (trap_AAS_PointContents(feet) & (CONTENTS_LAVA|CONTENTS_SLIME)); +} + +/* +================== +BotCreateWayPoint +================== +*/ +bot_waypoint_t *BotCreateWayPoint(char *name, vec3_t origin, int areanum) { + bot_waypoint_t *wp; + vec3_t waypointmins = {-8, -8, -8}, waypointmaxs = {8, 8, 8}; + + wp = botai_freewaypoints; + if ( !wp ) { + BotAI_Print( PRT_WARNING, "BotCreateWayPoint: Out of waypoints\n" ); + return NULL; + } + botai_freewaypoints = botai_freewaypoints->next; + + Q_strncpyz( wp->name, name, sizeof(wp->name) ); + VectorCopy(origin, wp->goal.origin); + VectorCopy(waypointmins, wp->goal.mins); + VectorCopy(waypointmaxs, wp->goal.maxs); + wp->goal.areanum = areanum; + wp->next = NULL; + wp->prev = NULL; + return wp; +} + +/* +================== +BotFindWayPoint +================== +*/ +bot_waypoint_t *BotFindWayPoint(bot_waypoint_t *waypoints, char *name) { + bot_waypoint_t *wp; + + for (wp = waypoints; wp; wp = wp->next) { + if (!Q_stricmp(wp->name, name)) return wp; + } + return NULL; +} + +/* +================== +BotFreeWaypoints +================== +*/ +void BotFreeWaypoints(bot_waypoint_t *wp) { + bot_waypoint_t *nextwp; + + for (; wp; wp = nextwp) { + nextwp = wp->next; + wp->next = botai_freewaypoints; + botai_freewaypoints = wp; + } +} + +/* +================== +BotInitWaypoints +================== +*/ +void BotInitWaypoints(void) { + int i; + + botai_freewaypoints = NULL; + for (i = 0; i < MAX_WAYPOINTS; i++) { + botai_waypoints[i].next = botai_freewaypoints; + botai_freewaypoints = &botai_waypoints[i]; + } +} + +/* +================== +TeamPlayIsOn +================== +*/ +int TeamPlayIsOn(void) { + return ( gametype >= GT_TEAM ); +} + +/* +================== +BotAggression +================== +*/ +float BotAggression(bot_state_t *bs) { + //if the bot has quad + if (bs->inventory[INVENTORY_QUAD]) { + //if the bot is not holding the gauntlet or the enemy is really nearby + if (bs->weaponnum != WP_GAUNTLET || + bs->inventory[ENEMY_HORIZONTAL_DIST] < 80) { + return 70; + } + } + //if the enemy is located way higher than the bot + if (bs->inventory[ENEMY_HEIGHT] > 200) return 0; + //if the bot is very low on health + if (bs->inventory[INVENTORY_HEALTH] < 60) return 0; + //if the bot is low on health + if (bs->inventory[INVENTORY_HEALTH] < 80) { + //if the bot has insufficient armor + if (bs->inventory[INVENTORY_ARMOR] < 40) return 0; + } + //if the bot can use the bfg + if (bs->inventory[INVENTORY_BFG10K] > 0 && + bs->inventory[INVENTORY_BFGAMMO] > 7) return 100; + //if the bot can use the railgun + if (bs->inventory[INVENTORY_RAILGUN] > 0 && + bs->inventory[INVENTORY_SLUGS] > 5) return 95; + //if the bot can use the lightning gun + if (bs->inventory[INVENTORY_LIGHTNING] > 0 && + bs->inventory[INVENTORY_LIGHTNINGAMMO] > 50) return 90; + //if the bot can use the rocketlauncher + if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && + bs->inventory[INVENTORY_ROCKETS] > 5) return 90; + //if the bot can use the plasmagun + if (bs->inventory[INVENTORY_PLASMAGUN] > 0 && + bs->inventory[INVENTORY_CELLS] > 40) return 85; + //if the bot can use the grenade launcher + if (bs->inventory[INVENTORY_GRENADELAUNCHER] > 0 && + bs->inventory[INVENTORY_GRENADES] > 10) return 80; + //if the bot can use the shotgun + if (bs->inventory[INVENTORY_SHOTGUN] > 0 && + bs->inventory[INVENTORY_SHELLS] > 10) return 50; + //otherwise the bot is not feeling too good + return 0; +} + +/* +================== +BotFeelingBad +================== +*/ +float BotFeelingBad(bot_state_t *bs) { + if (bs->weaponnum == WP_GAUNTLET) { + return 100; + } + if (bs->inventory[INVENTORY_HEALTH] < 40) { + return 100; + } + if (bs->weaponnum == WP_MACHINEGUN) { + return 90; + } + if (bs->inventory[INVENTORY_HEALTH] < 60) { + return 80; + } + return 0; +} + +/* +================== +BotWantsToRetreat +================== +*/ +int BotWantsToRetreat(bot_state_t *bs) { + aas_entityinfo_t entinfo; + + if (gametype == GT_CTF) { + //always retreat when carrying a CTF flag + if (BotCTFCarryingFlag(bs)) + return qtrue; + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + //if carrying the flag then always retreat + if (Bot1FCTFCarryingFlag(bs)) + return qtrue; + } + else if (gametype == GT_OBELISK) { + //the bots should be dedicated to attacking the enemy obelisk + if (bs->ltgtype == LTG_ATTACKENEMYBASE) { + if (bs->enemy != redobelisk.entitynum || + bs->enemy != blueobelisk.entitynum) { + return qtrue; + } + } + if (BotFeelingBad(bs) > 50) { + return qtrue; + } + return qfalse; + } + else if (gametype == GT_HARVESTER) { + //if carrying cubes then always retreat + if (BotHarvesterCarryingCubes(bs)) return qtrue; + } +#endif + // + if (bs->enemy >= 0) { + //if the enemy is carrying a flag + BotEntityInfo(bs->enemy, &entinfo); + if (EntityCarriesFlag(&entinfo)) + return qfalse; + } + //if the bot is getting the flag + if (bs->ltgtype == LTG_GETFLAG) + return qtrue; + // + if (BotAggression(bs) < 50) + return qtrue; + return qfalse; +} + +/* +================== +BotWantsToChase +================== +*/ +int BotWantsToChase(bot_state_t *bs) { + aas_entityinfo_t entinfo; + + if (gametype == GT_CTF) { + //never chase when carrying a CTF flag + if (BotCTFCarryingFlag(bs)) + return qfalse; + //always chase if the enemy is carrying a flag + BotEntityInfo(bs->enemy, &entinfo); + if (EntityCarriesFlag(&entinfo)) + return qtrue; + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + //never chase if carrying the flag + if (Bot1FCTFCarryingFlag(bs)) + return qfalse; + //always chase if the enemy is carrying a flag + BotEntityInfo(bs->enemy, &entinfo); + if (EntityCarriesFlag(&entinfo)) + return qtrue; + } + else if (gametype == GT_OBELISK) { + //the bots should be dedicated to attacking the enemy obelisk + if (bs->ltgtype == LTG_ATTACKENEMYBASE) { + if (bs->enemy != redobelisk.entitynum || + bs->enemy != blueobelisk.entitynum) { + return qfalse; + } + } + } + else if (gametype == GT_HARVESTER) { + //never chase if carrying cubes + if (BotHarvesterCarryingCubes(bs)) + return qfalse; + } +#endif + //if the bot is getting the flag + if (bs->ltgtype == LTG_GETFLAG) + return qfalse; + // + if (BotAggression(bs) > 50) + return qtrue; + return qfalse; +} + +/* +================== +BotWantsToHelp +================== +*/ +int BotWantsToHelp(bot_state_t *bs) { + return qtrue; +} + +/* +================== +BotCanAndWantsToRocketJump +================== +*/ +int BotCanAndWantsToRocketJump(bot_state_t *bs) { + float rocketjumper; + + //if rocket jumping is disabled + if (!bot_rocketjump.integer) return qfalse; + //if no rocket launcher + if (bs->inventory[INVENTORY_ROCKETLAUNCHER] <= 0) return qfalse; + //if low on rockets + if (bs->inventory[INVENTORY_ROCKETS] < 3) return qfalse; + //never rocket jump with the Quad + if (bs->inventory[INVENTORY_QUAD]) return qfalse; + //if low on health + if (bs->inventory[INVENTORY_HEALTH] < 60) return qfalse; + //if not full health + if (bs->inventory[INVENTORY_HEALTH] < 90) { + //if the bot has insufficient armor + if (bs->inventory[INVENTORY_ARMOR] < 40) return qfalse; + } + rocketjumper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_WEAPONJUMPING, 0, 1); + if (rocketjumper < 0.5) return qfalse; + return qtrue; +} + +/* +================== +BotHasPersistantPowerupAndWeapon +================== +*/ +int BotHasPersistantPowerupAndWeapon(bot_state_t *bs) { +#ifdef MISSIONPACK + // if the bot does not have a persistant powerup + if (!bs->inventory[INVENTORY_SCOUT] && + !bs->inventory[INVENTORY_GUARD] && + !bs->inventory[INVENTORY_DOUBLER] && + !bs->inventory[INVENTORY_AMMOREGEN] ) { + return qfalse; + } +#endif + //if the bot is very low on health + if (bs->inventory[INVENTORY_HEALTH] < 60) return qfalse; + //if the bot is low on health + if (bs->inventory[INVENTORY_HEALTH] < 80) { + //if the bot has insufficient armor + if (bs->inventory[INVENTORY_ARMOR] < 40) return qfalse; + } + //if the bot can use the bfg + if (bs->inventory[INVENTORY_BFG10K] > 0 && + bs->inventory[INVENTORY_BFGAMMO] > 7) return qtrue; + //if the bot can use the railgun + if (bs->inventory[INVENTORY_RAILGUN] > 0 && + bs->inventory[INVENTORY_SLUGS] > 5) return qtrue; + //if the bot can use the lightning gun + if (bs->inventory[INVENTORY_LIGHTNING] > 0 && + bs->inventory[INVENTORY_LIGHTNINGAMMO] > 50) return qtrue; + //if the bot can use the rocketlauncher + if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && + bs->inventory[INVENTORY_ROCKETS] > 5) return qtrue; + // + if (bs->inventory[INVENTORY_NAILGUN] > 0 && + bs->inventory[INVENTORY_NAILS] > 5) return qtrue; + // + if (bs->inventory[INVENTORY_PROXLAUNCHER] > 0 && + bs->inventory[INVENTORY_MINES] > 5) return qtrue; + // + if (bs->inventory[INVENTORY_CHAINGUN] > 0 && + bs->inventory[INVENTORY_BELT] > 40) return qtrue; + //if the bot can use the plasmagun + if (bs->inventory[INVENTORY_PLASMAGUN] > 0 && + bs->inventory[INVENTORY_CELLS] > 20) return qtrue; + return qfalse; +} + +/* +================== +BotGoCamp +================== +*/ +void BotGoCamp(bot_state_t *bs, bot_goal_t *goal) { + float camper; + + bs->decisionmaker = bs->client; + //set message time to zero so bot will NOT show any message + bs->teammessage_time = 0; + //set the ltg type + bs->ltgtype = LTG_CAMP; + //set the team goal + memcpy(&bs->teamgoal, goal, sizeof(bot_goal_t)); + //get the team goal time + camper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CAMPER, 0, 1); + if (camper > 0.99) bs->teamgoal_time = FloatTime() + 99999; + else bs->teamgoal_time = FloatTime() + 120 + 180 * camper + random() * 15; + //set the last time the bot started camping + bs->camp_time = FloatTime(); + //the teammate that requested the camping + bs->teammate = 0; + //do NOT type arrive message + bs->arrive_time = 1; +} + +/* +================== +BotWantsToCamp +================== +*/ +int BotWantsToCamp(bot_state_t *bs) { + float camper; + int cs, traveltime, besttraveltime; + bot_goal_t goal, bestgoal; + + camper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CAMPER, 0, 1); + if (camper < 0.1) return qfalse; + //if the bot has a team goal + if (bs->ltgtype == LTG_TEAMHELP || + bs->ltgtype == LTG_TEAMACCOMPANY || + bs->ltgtype == LTG_DEFENDKEYAREA || + bs->ltgtype == LTG_GETFLAG || + bs->ltgtype == LTG_RUSHBASE || + bs->ltgtype == LTG_CAMP || + bs->ltgtype == LTG_CAMPORDER || + bs->ltgtype == LTG_PATROL) { + return qfalse; + } + //if camped recently + if (bs->camp_time > FloatTime() - 60 + 300 * (1-camper)) return qfalse; + // + if (random() > camper) { + bs->camp_time = FloatTime(); + return qfalse; + } + //if the bot isn't healthy anough + if (BotAggression(bs) < 50) return qfalse; + //the bot should have at least have the rocket launcher, the railgun or the bfg10k with some ammo + if ((bs->inventory[INVENTORY_ROCKETLAUNCHER] <= 0 || bs->inventory[INVENTORY_ROCKETS < 10]) && + (bs->inventory[INVENTORY_RAILGUN] <= 0 || bs->inventory[INVENTORY_SLUGS] < 10) && + (bs->inventory[INVENTORY_BFG10K] <= 0 || bs->inventory[INVENTORY_BFGAMMO] < 10)) { + return qfalse; + } + //find the closest camp spot + besttraveltime = 99999; + for (cs = trap_BotGetNextCampSpotGoal(0, &goal); cs; cs = trap_BotGetNextCampSpotGoal(cs, &goal)) { + traveltime = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, goal.areanum, TFL_DEFAULT); + if (traveltime && traveltime < besttraveltime) { + besttraveltime = traveltime; + memcpy(&bestgoal, &goal, sizeof(bot_goal_t)); + } + } + if (besttraveltime > 150) return qfalse; + //ok found a camp spot, go camp there + BotGoCamp(bs, &bestgoal); + bs->ordered = qfalse; + // + return qtrue; +} + +/* +================== +BotDontAvoid +================== +*/ +void BotDontAvoid(bot_state_t *bs, char *itemname) { + bot_goal_t goal; + int num; + + num = trap_BotGetLevelItemGoal(-1, itemname, &goal); + while(num >= 0) { + trap_BotRemoveFromAvoidGoals(bs->gs, goal.number); + num = trap_BotGetLevelItemGoal(num, itemname, &goal); + } +} + +/* +================== +BotGoForPowerups +================== +*/ +void BotGoForPowerups(bot_state_t *bs) { + + //don't avoid any of the powerups anymore + BotDontAvoid(bs, "Quad Damage"); + BotDontAvoid(bs, "Regeneration"); + BotDontAvoid(bs, "Battle Suit"); + BotDontAvoid(bs, "Speed"); + BotDontAvoid(bs, "Invisibility"); + //BotDontAvoid(bs, "Flight"); + //reset the long term goal time so the bot will go for the powerup + //NOTE: the long term goal type doesn't change + bs->ltg_time = 0; +} + +/* +================== +BotRoamGoal +================== +*/ +void BotRoamGoal(bot_state_t *bs, vec3_t goal) { + int pc, i; + float len, rnd; + vec3_t dir, bestorg, belowbestorg; + bsp_trace_t trace; + + for (i = 0; i < 10; i++) { + //start at the bot origin + VectorCopy(bs->origin, bestorg); + rnd = random(); + if (rnd > 0.25) { + //add a random value to the x-coordinate + if (random() < 0.5) bestorg[0] -= 800 * random() + 100; + else bestorg[0] += 800 * random() + 100; + } + if (rnd < 0.75) { + //add a random value to the y-coordinate + if (random() < 0.5) bestorg[1] -= 800 * random() + 100; + else bestorg[1] += 800 * random() + 100; + } + //add a random value to the z-coordinate (NOTE: 48 = maxjump?) + bestorg[2] += 2 * 48 * crandom(); + //trace a line from the origin to the roam target + BotAI_Trace(&trace, bs->origin, NULL, NULL, bestorg, bs->entitynum, MASK_SOLID); + //direction and length towards the roam target + VectorSubtract(trace.endpos, bs->origin, dir); + len = VectorNormalize(dir); + //if the roam target is far away anough + if (len > 200) { + //the roam target is in the given direction before walls + VectorScale(dir, len * trace.fraction - 40, dir); + VectorAdd(bs->origin, dir, bestorg); + //get the coordinates of the floor below the roam target + belowbestorg[0] = bestorg[0]; + belowbestorg[1] = bestorg[1]; + belowbestorg[2] = bestorg[2] - 800; + BotAI_Trace(&trace, bestorg, NULL, NULL, belowbestorg, bs->entitynum, MASK_SOLID); + // + if (!trace.startsolid) { + trace.endpos[2]++; + pc = trap_PointContents(trace.endpos, bs->entitynum); + if (!(pc & (CONTENTS_LAVA | CONTENTS_SLIME))) { + VectorCopy(bestorg, goal); + return; + } + } + } + } + VectorCopy(bestorg, goal); +} + +/* +================== +BotAttackMove +================== +*/ +bot_moveresult_t BotAttackMove(bot_state_t *bs, int tfl) { + int movetype, i, attackentity; + float attack_skill, jumper, croucher, dist, strafechange_time; + float attack_dist, attack_range; + vec3_t forward, backward, sideward, hordir, up = {0, 0, 1}; + aas_entityinfo_t entinfo; + bot_moveresult_t moveresult; + bot_goal_t goal; + + attackentity = bs->enemy; + // + if (bs->attackchase_time > FloatTime()) { + //create the chase goal + goal.entitynum = attackentity; + goal.areanum = bs->lastenemyareanum; + VectorCopy(bs->lastenemyorigin, goal.origin); + VectorSet(goal.mins, -8, -8, -8); + VectorSet(goal.maxs, 8, 8, 8); + //initialize the movement state + BotSetupForMovement(bs); + //move towards the goal + trap_BotMoveToGoal(&moveresult, bs->ms, &goal, tfl); + return moveresult; + } + // + memset(&moveresult, 0, sizeof(bot_moveresult_t)); + // + attack_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ATTACK_SKILL, 0, 1); + jumper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_JUMPER, 0, 1); + croucher = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CROUCHER, 0, 1); + //if the bot is really stupid + if (attack_skill < 0.2) return moveresult; + //initialize the movement state + BotSetupForMovement(bs); + //get the enemy entity info + BotEntityInfo(attackentity, &entinfo); + //direction towards the enemy + VectorSubtract(entinfo.origin, bs->origin, forward); + //the distance towards the enemy + dist = VectorNormalize(forward); + VectorNegate(forward, backward); + //walk, crouch or jump + movetype = MOVE_WALK; + // + if (bs->attackcrouch_time < FloatTime() - 1) { + if (random() < jumper) { + movetype = MOVE_JUMP; + } + //wait at least one second before crouching again + else if (bs->attackcrouch_time < FloatTime() - 1 && random() < croucher) { + bs->attackcrouch_time = FloatTime() + croucher * 5; + } + } + if (bs->attackcrouch_time > FloatTime()) movetype = MOVE_CROUCH; + //if the bot should jump + if (movetype == MOVE_JUMP) { + //if jumped last frame + if (bs->attackjump_time > FloatTime()) { + movetype = MOVE_WALK; + } + else { + bs->attackjump_time = FloatTime() + 1; + } + } + if (bs->cur_ps.weapon == WP_GAUNTLET) { + attack_dist = 0; + attack_range = 0; + } + else { + attack_dist = IDEAL_ATTACKDIST; + attack_range = 40; + } + //if the bot is stupid + if (attack_skill <= 0.4) { + //just walk to or away from the enemy + if (dist > attack_dist + attack_range) { + if (trap_BotMoveInDirection(bs->ms, forward, 400, movetype)) return moveresult; + } + if (dist < attack_dist - attack_range) { + if (trap_BotMoveInDirection(bs->ms, backward, 400, movetype)) return moveresult; + } + return moveresult; + } + //increase the strafe time + bs->attackstrafe_time += bs->thinktime; + //get the strafe change time + strafechange_time = 0.4 + (1 - attack_skill) * 0.2; + if (attack_skill > 0.7) strafechange_time += crandom() * 0.2; + //if the strafe direction should be changed + if (bs->attackstrafe_time > strafechange_time) { + //some magic number :) + if (random() > 0.935) { + //flip the strafe direction + bs->flags ^= BFL_STRAFERIGHT; + bs->attackstrafe_time = 0; + } + } + // + for (i = 0; i < 2; i++) { + hordir[0] = forward[0]; + hordir[1] = forward[1]; + hordir[2] = 0; + VectorNormalize(hordir); + //get the sideward vector + CrossProduct(hordir, up, sideward); + //reverse the vector depending on the strafe direction + if (bs->flags & BFL_STRAFERIGHT) VectorNegate(sideward, sideward); + //randomly go back a little + if (random() > 0.9) { + VectorAdd(sideward, backward, sideward); + } + else { + //walk forward or backward to get at the ideal attack distance + if (dist > attack_dist + attack_range) { + VectorAdd(sideward, forward, sideward); + } + else if (dist < attack_dist - attack_range) { + VectorAdd(sideward, backward, sideward); + } + } + //perform the movement + if (trap_BotMoveInDirection(bs->ms, sideward, 400, movetype)) + return moveresult; + //movement failed, flip the strafe direction + bs->flags ^= BFL_STRAFERIGHT; + bs->attackstrafe_time = 0; + } + //bot couldn't do any usefull movement +// bs->attackchase_time = AAS_Time() + 6; + return moveresult; +} + +/* +================== +BotSameTeam +================== +*/ +int BotSameTeam(bot_state_t *bs, int entnum) { + char info1[1024], info2[1024]; + + if (bs->client < 0 || bs->client >= MAX_CLIENTS) { + //BotAI_Print(PRT_ERROR, "BotSameTeam: client out of range\n"); + return qfalse; + } + if (entnum < 0 || entnum >= MAX_CLIENTS) { + //BotAI_Print(PRT_ERROR, "BotSameTeam: client out of range\n"); + return qfalse; + } + if ( gametype >= GT_TEAM ) { + trap_GetConfigstring(CS_PLAYERS+bs->client, info1, sizeof(info1)); + trap_GetConfigstring(CS_PLAYERS+entnum, info2, sizeof(info2)); + // + if (atoi(Info_ValueForKey(info1, "t")) == atoi(Info_ValueForKey(info2, "t"))) return qtrue; + } + return qfalse; +} + +/* +================== +InFieldOfVision +================== +*/ +qboolean InFieldOfVision(vec3_t viewangles, float fov, vec3_t angles) +{ + int i; + float diff, angle; + + for (i = 0; i < 2; i++) { + angle = AngleMod(viewangles[i]); + angles[i] = AngleMod(angles[i]); + diff = angles[i] - angle; + if (angles[i] > angle) { + if (diff > 180.0) diff -= 360.0; + } + else { + if (diff < -180.0) diff += 360.0; + } + if (diff > 0) { + if (diff > fov * 0.5) return qfalse; + } + else { + if (diff < -fov * 0.5) return qfalse; + } + } + return qtrue; +} + +/* +================== +BotEntityVisible + +returns visibility in the range [0, 1] taking fog and water surfaces into account +================== +*/ +float BotEntityVisible(int viewer, vec3_t eye, vec3_t viewangles, float fov, int ent) { + int i, contents_mask, passent, hitent, infog, inwater, otherinfog, pc; + float squaredfogdist, waterfactor, vis, bestvis; + bsp_trace_t trace; + aas_entityinfo_t entinfo; + vec3_t dir, entangles, start, end, middle; + + //calculate middle of bounding box + BotEntityInfo(ent, &entinfo); + VectorAdd(entinfo.mins, entinfo.maxs, middle); + VectorScale(middle, 0.5, middle); + VectorAdd(entinfo.origin, middle, middle); + //check if entity is within field of vision + VectorSubtract(middle, eye, dir); + vectoangles(dir, entangles); + if (!InFieldOfVision(viewangles, fov, entangles)) return 0; + // + pc = trap_AAS_PointContents(eye); + infog = (pc & CONTENTS_FOG); + inwater = (pc & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)); + // + bestvis = 0; + for (i = 0; i < 3; i++) { + //if the point is not in potential visible sight + //if (!AAS_inPVS(eye, middle)) continue; + // + contents_mask = CONTENTS_SOLID|CONTENTS_PLAYERCLIP; + passent = viewer; + hitent = ent; + VectorCopy(eye, start); + VectorCopy(middle, end); + //if the entity is in water, lava or slime + if (trap_AAS_PointContents(middle) & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) { + contents_mask |= (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER); + } + //if eye is in water, lava or slime + if (inwater) { + if (!(contents_mask & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER))) { + passent = ent; + hitent = viewer; + VectorCopy(middle, start); + VectorCopy(eye, end); + } + contents_mask ^= (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER); + } + //trace from start to end + BotAI_Trace(&trace, start, NULL, NULL, end, passent, contents_mask); + //if water was hit + waterfactor = 1.0; + if (trace.contents & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) { + //if the water surface is translucent + if (1) { + //trace through the water + contents_mask &= ~(CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER); + BotAI_Trace(&trace, trace.endpos, NULL, NULL, end, passent, contents_mask); + waterfactor = 0.5; + } + } + //if a full trace or the hitent was hit + if (trace.fraction >= 1 || trace.ent == hitent) { + //check for fog, assuming there's only one fog brush where + //either the viewer or the entity is in or both are in + otherinfog = (trap_AAS_PointContents(middle) & CONTENTS_FOG); + if (infog && otherinfog) { + VectorSubtract(trace.endpos, eye, dir); + squaredfogdist = VectorLengthSquared(dir); + } + else if (infog) { + VectorCopy(trace.endpos, start); + BotAI_Trace(&trace, start, NULL, NULL, eye, viewer, CONTENTS_FOG); + VectorSubtract(eye, trace.endpos, dir); + squaredfogdist = VectorLengthSquared(dir); + } + else if (otherinfog) { + VectorCopy(trace.endpos, end); + BotAI_Trace(&trace, eye, NULL, NULL, end, viewer, CONTENTS_FOG); + VectorSubtract(end, trace.endpos, dir); + squaredfogdist = VectorLengthSquared(dir); + } + else { + //if the entity and the viewer are not in fog assume there's no fog in between + squaredfogdist = 0; + } + //decrease visibility with the view distance through fog + vis = 1 / ((squaredfogdist * 0.001) < 1 ? 1 : (squaredfogdist * 0.001)); + //if entering water visibility is reduced + vis *= waterfactor; + // + if (vis > bestvis) bestvis = vis; + //if pretty much no fog + if (bestvis >= 0.95) return bestvis; + } + //check bottom and top of bounding box as well + if (i == 0) middle[2] += entinfo.mins[2]; + else if (i == 1) middle[2] += entinfo.maxs[2] - entinfo.mins[2]; + } + return bestvis; +} + +/* +================== +BotFindEnemy +================== +*/ +int BotFindEnemy(bot_state_t *bs, int curenemy) { + int i, healthdecrease; + float f, alertness, easyfragger, vis; + float squaredist, cursquaredist; + aas_entityinfo_t entinfo, curenemyinfo; + vec3_t dir, angles; + + alertness = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ALERTNESS, 0, 1); + easyfragger = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_EASY_FRAGGER, 0, 1); + //check if the health decreased + healthdecrease = bs->lasthealth > bs->inventory[INVENTORY_HEALTH]; + //remember the current health value + bs->lasthealth = bs->inventory[INVENTORY_HEALTH]; + // + if (curenemy >= 0) { + BotEntityInfo(curenemy, &curenemyinfo); + if (EntityCarriesFlag(&curenemyinfo)) return qfalse; + VectorSubtract(curenemyinfo.origin, bs->origin, dir); + cursquaredist = VectorLengthSquared(dir); + } + else { + cursquaredist = 0; + } +#ifdef MISSIONPACK + if (gametype == GT_OBELISK) { + vec3_t target; + bot_goal_t *goal; + bsp_trace_t trace; + + if (BotTeam(bs) == TEAM_RED) + goal = &blueobelisk; + else + goal = &redobelisk; + //if the obelisk is visible + VectorCopy(goal->origin, target); + target[2] += 1; + BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); + if (trace.fraction >= 1 || trace.ent == goal->entitynum) { + if (goal->entitynum == bs->enemy) { + return qfalse; + } + bs->enemy = goal->entitynum; + bs->enemysight_time = FloatTime(); + bs->enemysuicide = qfalse; + bs->enemydeath_time = 0; + bs->enemyvisible_time = FloatTime(); + return qtrue; + } + } +#endif + // + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + + if (i == bs->client) continue; + //if it's the current enemy + if (i == curenemy) continue; + // + BotEntityInfo(i, &entinfo); + // + if (!entinfo.valid) continue; + //if the enemy isn't dead and the enemy isn't the bot self + if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue; + //if the enemy is invisible and not shooting + if (EntityIsInvisible(&entinfo) && !EntityIsShooting(&entinfo)) { + continue; + } + //if not an easy fragger don't shoot at chatting players + if (easyfragger < 0.5 && EntityIsChatting(&entinfo)) continue; + // + if (lastteleport_time > FloatTime() - 3) { + VectorSubtract(entinfo.origin, lastteleport_origin, dir); + if (VectorLengthSquared(dir) < Square(70)) continue; + } + //calculate the distance towards the enemy + VectorSubtract(entinfo.origin, bs->origin, dir); + squaredist = VectorLengthSquared(dir); + //if this entity is not carrying a flag + if (!EntityCarriesFlag(&entinfo)) + { + //if this enemy is further away than the current one + if (curenemy >= 0 && squaredist > cursquaredist) continue; + } //end if + //if the bot has no + if (squaredist > Square(900.0 + alertness * 4000.0)) continue; + //if on the same team + if (BotSameTeam(bs, i)) continue; + //if the bot's health decreased or the enemy is shooting + if (curenemy < 0 && (healthdecrease || EntityIsShooting(&entinfo))) + f = 360; + else + f = 90 + 90 - (90 - (squaredist > Square(810) ? Square(810) : squaredist) / (810 * 9)); + //check if the enemy is visible + vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, f, i); + if (vis <= 0) continue; + //if the enemy is quite far away, not shooting and the bot is not damaged + if (curenemy < 0 && squaredist > Square(100) && !healthdecrease && !EntityIsShooting(&entinfo)) + { + //check if we can avoid this enemy + VectorSubtract(bs->origin, entinfo.origin, dir); + vectoangles(dir, angles); + //if the bot isn't in the fov of the enemy + if (!InFieldOfVision(entinfo.angles, 90, angles)) { + //update some stuff for this enemy + BotUpdateBattleInventory(bs, i); + //if the bot doesn't really want to fight + if (BotWantsToRetreat(bs)) continue; + } + } + //found an enemy + bs->enemy = entinfo.number; + if (curenemy >= 0) bs->enemysight_time = FloatTime() - 2; + else bs->enemysight_time = FloatTime(); + bs->enemysuicide = qfalse; + bs->enemydeath_time = 0; + bs->enemyvisible_time = FloatTime(); + return qtrue; + } + return qfalse; +} + +/* +================== +BotTeamFlagCarrierVisible +================== +*/ +int BotTeamFlagCarrierVisible(bot_state_t *bs) { + int i; + float vis; + aas_entityinfo_t entinfo; + + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + if (i == bs->client) + continue; + // + BotEntityInfo(i, &entinfo); + //if this player is active + if (!entinfo.valid) + continue; + //if this player is carrying a flag + if (!EntityCarriesFlag(&entinfo)) + continue; + //if the flag carrier is not on the same team + if (!BotSameTeam(bs, i)) + continue; + //if the flag carrier is not visible + vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); + if (vis <= 0) + continue; + // + return i; + } + return -1; +} + +/* +================== +BotTeamFlagCarrier +================== +*/ +int BotTeamFlagCarrier(bot_state_t *bs) { + int i; + aas_entityinfo_t entinfo; + + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + if (i == bs->client) + continue; + // + BotEntityInfo(i, &entinfo); + //if this player is active + if (!entinfo.valid) + continue; + //if this player is carrying a flag + if (!EntityCarriesFlag(&entinfo)) + continue; + //if the flag carrier is not on the same team + if (!BotSameTeam(bs, i)) + continue; + // + return i; + } + return -1; +} + +/* +================== +BotEnemyFlagCarrierVisible +================== +*/ +int BotEnemyFlagCarrierVisible(bot_state_t *bs) { + int i; + float vis; + aas_entityinfo_t entinfo; + + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + if (i == bs->client) + continue; + // + BotEntityInfo(i, &entinfo); + //if this player is active + if (!entinfo.valid) + continue; + //if this player is carrying a flag + if (!EntityCarriesFlag(&entinfo)) + continue; + //if the flag carrier is on the same team + if (BotSameTeam(bs, i)) + continue; + //if the flag carrier is not visible + vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); + if (vis <= 0) + continue; + // + return i; + } + return -1; +} + +/* +================== +BotVisibleTeamMatesAndEnemies +================== +*/ +void BotVisibleTeamMatesAndEnemies(bot_state_t *bs, int *teammates, int *enemies, float range) { + int i; + float vis; + aas_entityinfo_t entinfo; + vec3_t dir; + + if (teammates) + *teammates = 0; + if (enemies) + *enemies = 0; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + if (i == bs->client) + continue; + // + BotEntityInfo(i, &entinfo); + //if this player is active + if (!entinfo.valid) + continue; + //if this player is carrying a flag + if (!EntityCarriesFlag(&entinfo)) + continue; + //if not within range + VectorSubtract(entinfo.origin, bs->origin, dir); + if (VectorLengthSquared(dir) > Square(range)) + continue; + //if the flag carrier is not visible + vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); + if (vis <= 0) + continue; + //if the flag carrier is on the same team + if (BotSameTeam(bs, i)) { + if (teammates) + (*teammates)++; + } + else { + if (enemies) + (*enemies)++; + } + } +} + +#ifdef MISSIONPACK +/* +================== +BotTeamCubeCarrierVisible +================== +*/ +int BotTeamCubeCarrierVisible(bot_state_t *bs) { + int i; + float vis; + aas_entityinfo_t entinfo; + + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + if (i == bs->client) continue; + // + BotEntityInfo(i, &entinfo); + //if this player is active + if (!entinfo.valid) continue; + //if this player is carrying a flag + if (!EntityCarriesCubes(&entinfo)) continue; + //if the flag carrier is not on the same team + if (!BotSameTeam(bs, i)) continue; + //if the flag carrier is not visible + vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); + if (vis <= 0) continue; + // + return i; + } + return -1; +} + +/* +================== +BotEnemyCubeCarrierVisible +================== +*/ +int BotEnemyCubeCarrierVisible(bot_state_t *bs) { + int i; + float vis; + aas_entityinfo_t entinfo; + + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + if (i == bs->client) + continue; + // + BotEntityInfo(i, &entinfo); + //if this player is active + if (!entinfo.valid) + continue; + //if this player is carrying a flag + if (!EntityCarriesCubes(&entinfo)) continue; + //if the flag carrier is on the same team + if (BotSameTeam(bs, i)) + continue; + //if the flag carrier is not visible + vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); + if (vis <= 0) + continue; + // + return i; + } + return -1; +} +#endif + +/* +================== +BotAimAtEnemy +================== +*/ +void BotAimAtEnemy(bot_state_t *bs) { + int i, enemyvisible; + float dist, f, aim_skill, aim_accuracy, speed, reactiontime; + vec3_t dir, bestorigin, end, start, groundtarget, cmdmove, enemyvelocity; + vec3_t mins = {-4,-4,-4}, maxs = {4, 4, 4}; + weaponinfo_t wi; + aas_entityinfo_t entinfo; + bot_goal_t goal; + bsp_trace_t trace; + vec3_t target; + + //if the bot has no enemy + if (bs->enemy < 0) { + return; + } + //get the enemy entity information + BotEntityInfo(bs->enemy, &entinfo); + //if this is not a player (should be an obelisk) + if (bs->enemy >= MAX_CLIENTS) { + //if the obelisk is visible + VectorCopy(entinfo.origin, target); +#ifdef MISSIONPACK + // if attacking an obelisk + if ( bs->enemy == redobelisk.entitynum || + bs->enemy == blueobelisk.entitynum ) { + target[2] += 32; + } +#endif + //aim at the obelisk + VectorSubtract(target, bs->eye, dir); + vectoangles(dir, bs->ideal_viewangles); + //set the aim target before trying to attack + VectorCopy(target, bs->aimtarget); + return; + } + // + //BotAI_Print(PRT_MESSAGE, "client %d: aiming at client %d\n", bs->entitynum, bs->enemy); + // + aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL, 0, 1); + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY, 0, 1); + // + if (aim_skill > 0.95) { + //don't aim too early + reactiontime = 0.5 * trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_REACTIONTIME, 0, 1); + if (bs->enemysight_time > FloatTime() - reactiontime) return; + if (bs->teleport_time > FloatTime() - reactiontime) return; + } + + //get the weapon information + trap_BotGetWeaponInfo(bs->ws, bs->weaponnum, &wi); + //get the weapon specific aim accuracy and or aim skill + if (wi.number == WP_MACHINEGUN) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_MACHINEGUN, 0, 1); + } + else if (wi.number == WP_SHOTGUN) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_SHOTGUN, 0, 1); + } + else if (wi.number == WP_GRENADE_LAUNCHER) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_GRENADELAUNCHER, 0, 1); + aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_GRENADELAUNCHER, 0, 1); + } + else if (wi.number == WP_ROCKET_LAUNCHER) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_ROCKETLAUNCHER, 0, 1); + aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_ROCKETLAUNCHER, 0, 1); + } + else if (wi.number == WP_LIGHTNING) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_LIGHTNING, 0, 1); + } + else if (wi.number == WP_RAILGUN) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_RAILGUN, 0, 1); + } + else if (wi.number == WP_PLASMAGUN) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_PLASMAGUN, 0, 1); + aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_PLASMAGUN, 0, 1); + } + else if (wi.number == WP_BFG) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_BFG10K, 0, 1); + aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_BFG10K, 0, 1); + } + // + if (aim_accuracy <= 0) aim_accuracy = 0.0001f; + //get the enemy entity information + BotEntityInfo(bs->enemy, &entinfo); + //if the enemy is invisible then shoot crappy most of the time + if (EntityIsInvisible(&entinfo)) { + if (random() > 0.1) aim_accuracy *= 0.4f; + } + // + VectorSubtract(entinfo.origin, entinfo.lastvisorigin, enemyvelocity); + VectorScale(enemyvelocity, 1 / entinfo.update_time, enemyvelocity); + //enemy origin and velocity is remembered every 0.5 seconds + if (bs->enemyposition_time < FloatTime()) { + // + bs->enemyposition_time = FloatTime() + 0.5; + VectorCopy(enemyvelocity, bs->enemyvelocity); + VectorCopy(entinfo.origin, bs->enemyorigin); + } + //if not extremely skilled + if (aim_skill < 0.9) { + VectorSubtract(entinfo.origin, bs->enemyorigin, dir); + //if the enemy moved a bit + if (VectorLengthSquared(dir) > Square(48)) { + //if the enemy changed direction + if (DotProduct(bs->enemyvelocity, enemyvelocity) < 0) { + //aim accuracy should be worse now + aim_accuracy *= 0.7f; + } + } + } + //check visibility of enemy + enemyvisible = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy); + //if the enemy is visible + if (enemyvisible) { + // + VectorCopy(entinfo.origin, bestorigin); + bestorigin[2] += 8; + //get the start point shooting from + //NOTE: the x and y projectile start offsets are ignored + VectorCopy(bs->origin, start); + start[2] += bs->cur_ps.viewheight; + start[2] += wi.offset[2]; + // + BotAI_Trace(&trace, start, mins, maxs, bestorigin, bs->entitynum, MASK_SHOT); + //if the enemy is NOT hit + if (trace.fraction <= 1 && trace.ent != entinfo.number) { + bestorigin[2] += 16; + } + //if it is not an instant hit weapon the bot might want to predict the enemy + if (wi.speed) { + // + VectorSubtract(bestorigin, bs->origin, dir); + dist = VectorLength(dir); + VectorSubtract(entinfo.origin, bs->enemyorigin, dir); + //if the enemy is NOT pretty far away and strafing just small steps left and right + if (!(dist > 100 && VectorLengthSquared(dir) < Square(32))) { + //if skilled anough do exact prediction + if (aim_skill > 0.8 && + //if the weapon is ready to fire + bs->cur_ps.weaponstate == WEAPON_READY) { + aas_clientmove_t move; + vec3_t origin; + + VectorSubtract(entinfo.origin, bs->origin, dir); + //distance towards the enemy + dist = VectorLength(dir); + //direction the enemy is moving in + VectorSubtract(entinfo.origin, entinfo.lastvisorigin, dir); + // + VectorScale(dir, 1 / entinfo.update_time, dir); + // + VectorCopy(entinfo.origin, origin); + origin[2] += 1; + // + VectorClear(cmdmove); + //AAS_ClearShownDebugLines(); + trap_AAS_PredictClientMovement(&move, bs->enemy, origin, + PRESENCE_CROUCH, qfalse, + dir, cmdmove, 0, + dist * 10 / wi.speed, 0.1f, 0, 0, qfalse); + VectorCopy(move.endpos, bestorigin); + //BotAI_Print(PRT_MESSAGE, "%1.1f predicted speed = %f, frames = %f\n", FloatTime(), VectorLength(dir), dist * 10 / wi.speed); + } + //if not that skilled do linear prediction + else if (aim_skill > 0.4) { + VectorSubtract(entinfo.origin, bs->origin, dir); + //distance towards the enemy + dist = VectorLength(dir); + //direction the enemy is moving in + VectorSubtract(entinfo.origin, entinfo.lastvisorigin, dir); + dir[2] = 0; + // + speed = VectorNormalize(dir) / entinfo.update_time; + //botimport.Print(PRT_MESSAGE, "speed = %f, wi->speed = %f\n", speed, wi->speed); + //best spot to aim at + VectorMA(entinfo.origin, (dist / wi.speed) * speed, dir, bestorigin); + } + } + } + //if the projectile does radial damage + if (aim_skill > 0.6 && wi.proj.damagetype & DAMAGETYPE_RADIAL) { + //if the enemy isn't standing significantly higher than the bot + if (entinfo.origin[2] < bs->origin[2] + 16) { + //try to aim at the ground in front of the enemy + VectorCopy(entinfo.origin, end); + end[2] -= 64; + BotAI_Trace(&trace, entinfo.origin, NULL, NULL, end, entinfo.number, MASK_SHOT); + // + VectorCopy(bestorigin, groundtarget); + if (trace.startsolid) groundtarget[2] = entinfo.origin[2] - 16; + else groundtarget[2] = trace.endpos[2] - 8; + //trace a line from projectile start to ground target + BotAI_Trace(&trace, start, NULL, NULL, groundtarget, bs->entitynum, MASK_SHOT); + //if hitpoint is not vertically too far from the ground target + if (fabs(trace.endpos[2] - groundtarget[2]) < 50) { + VectorSubtract(trace.endpos, groundtarget, dir); + //if the hitpoint is near anough the ground target + if (VectorLengthSquared(dir) < Square(60)) { + VectorSubtract(trace.endpos, start, dir); + //if the hitpoint is far anough from the bot + if (VectorLengthSquared(dir) > Square(100)) { + //check if the bot is visible from the ground target + trace.endpos[2] += 1; + BotAI_Trace(&trace, trace.endpos, NULL, NULL, entinfo.origin, entinfo.number, MASK_SHOT); + if (trace.fraction >= 1) { + //botimport.Print(PRT_MESSAGE, "%1.1f aiming at ground\n", AAS_Time()); + VectorCopy(groundtarget, bestorigin); + } + } + } + } + } + } + bestorigin[0] += 20 * crandom() * (1 - aim_accuracy); + bestorigin[1] += 20 * crandom() * (1 - aim_accuracy); + bestorigin[2] += 10 * crandom() * (1 - aim_accuracy); + } + else { + // + VectorCopy(bs->lastenemyorigin, bestorigin); + bestorigin[2] += 8; + //if the bot is skilled anough + if (aim_skill > 0.5) { + //do prediction shots around corners + if (wi.number == WP_BFG || + wi.number == WP_ROCKET_LAUNCHER || + wi.number == WP_GRENADE_LAUNCHER) { + //create the chase goal + goal.entitynum = bs->client; + goal.areanum = bs->areanum; + VectorCopy(bs->eye, goal.origin); + VectorSet(goal.mins, -8, -8, -8); + VectorSet(goal.maxs, 8, 8, 8); + // + if (trap_BotPredictVisiblePosition(bs->lastenemyorigin, bs->lastenemyareanum, &goal, TFL_DEFAULT, target)) { + VectorSubtract(target, bs->eye, dir); + if (VectorLengthSquared(dir) > Square(80)) { + VectorCopy(target, bestorigin); + bestorigin[2] -= 20; + } + } + aim_accuracy = 1; + } + } + } + // + if (enemyvisible) { + BotAI_Trace(&trace, bs->eye, NULL, NULL, bestorigin, bs->entitynum, MASK_SHOT); + VectorCopy(trace.endpos, bs->aimtarget); + } + else { + VectorCopy(bestorigin, bs->aimtarget); + } + //get aim direction + VectorSubtract(bestorigin, bs->eye, dir); + // + if (wi.number == WP_MACHINEGUN || + wi.number == WP_SHOTGUN || + wi.number == WP_LIGHTNING || + wi.number == WP_RAILGUN) { + //distance towards the enemy + dist = VectorLength(dir); + if (dist > 150) dist = 150; + f = 0.6 + dist / 150 * 0.4; + aim_accuracy *= f; + } + //add some random stuff to the aim direction depending on the aim accuracy + if (aim_accuracy < 0.8) { + VectorNormalize(dir); + for (i = 0; i < 3; i++) dir[i] += 0.3 * crandom() * (1 - aim_accuracy); + } + //set the ideal view angles + vectoangles(dir, bs->ideal_viewangles); + //take the weapon spread into account for lower skilled bots + bs->ideal_viewangles[PITCH] += 6 * wi.vspread * crandom() * (1 - aim_accuracy); + bs->ideal_viewangles[PITCH] = AngleMod(bs->ideal_viewangles[PITCH]); + bs->ideal_viewangles[YAW] += 6 * wi.hspread * crandom() * (1 - aim_accuracy); + bs->ideal_viewangles[YAW] = AngleMod(bs->ideal_viewangles[YAW]); + //if the bots should be really challenging + if (bot_challenge.integer) { + //if the bot is really accurate and has the enemy in view for some time + if (aim_accuracy > 0.9 && bs->enemysight_time < FloatTime() - 1) { + //set the view angles directly + if (bs->ideal_viewangles[PITCH] > 180) bs->ideal_viewangles[PITCH] -= 360; + VectorCopy(bs->ideal_viewangles, bs->viewangles); + trap_EA_View(bs->client, bs->viewangles); + } + } +} + +/* +================== +BotCheckAttack +================== +*/ +void BotCheckAttack(bot_state_t *bs) { + float points, reactiontime, fov, firethrottle; + int attackentity; + bsp_trace_t bsptrace; + //float selfpreservation; + vec3_t forward, right, start, end, dir, angles; + weaponinfo_t wi; + bsp_trace_t trace; + aas_entityinfo_t entinfo; + vec3_t mins = {-8, -8, -8}, maxs = {8, 8, 8}; + + attackentity = bs->enemy; + // + BotEntityInfo(attackentity, &entinfo); + // if not attacking a player + if (attackentity >= MAX_CLIENTS) { +#ifdef MISSIONPACK + // if attacking an obelisk + if ( entinfo.number == redobelisk.entitynum || + entinfo.number == blueobelisk.entitynum ) { + // if obelisk is respawning return + if ( g_entities[entinfo.number].activator && + g_entities[entinfo.number].activator->s.frame == 2 ) { + return; + } + } +#endif + } + // + reactiontime = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_REACTIONTIME, 0, 1); + if (bs->enemysight_time > FloatTime() - reactiontime) return; + if (bs->teleport_time > FloatTime() - reactiontime) return; + //if changing weapons + if (bs->weaponchange_time > FloatTime() - 0.1) return; + //check fire throttle characteristic + if (bs->firethrottlewait_time > FloatTime()) return; + firethrottle = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_FIRETHROTTLE, 0, 1); + if (bs->firethrottleshoot_time < FloatTime()) { + if (random() > firethrottle) { + bs->firethrottlewait_time = FloatTime() + firethrottle; + bs->firethrottleshoot_time = 0; + } + else { + bs->firethrottleshoot_time = FloatTime() + 1 - firethrottle; + bs->firethrottlewait_time = 0; + } + } + // + // + VectorSubtract(bs->aimtarget, bs->eye, dir); + // + if (bs->weaponnum == WP_GAUNTLET) { + if (VectorLengthSquared(dir) > Square(60)) { + return; + } + } + if (VectorLengthSquared(dir) < Square(100)) + fov = 120; + else + fov = 50; + // + vectoangles(dir, angles); + if (!InFieldOfVision(bs->viewangles, fov, angles)) + return; + BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, bs->aimtarget, bs->client, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); + if (bsptrace.fraction < 1 && bsptrace.ent != attackentity) + return; + + //get the weapon info + trap_BotGetWeaponInfo(bs->ws, bs->weaponnum, &wi); + //get the start point shooting from + VectorCopy(bs->origin, start); + start[2] += bs->cur_ps.viewheight; + AngleVectors(bs->viewangles, forward, right, NULL); + start[0] += forward[0] * wi.offset[0] + right[0] * wi.offset[1]; + start[1] += forward[1] * wi.offset[0] + right[1] * wi.offset[1]; + start[2] += forward[2] * wi.offset[0] + right[2] * wi.offset[1] + wi.offset[2]; + //end point aiming at + VectorMA(start, 1000, forward, end); + //a little back to make sure not inside a very close enemy + VectorMA(start, -12, forward, start); + BotAI_Trace(&trace, start, mins, maxs, end, bs->entitynum, MASK_SHOT); + //if the entity is a client + if (trace.ent > 0 && trace.ent <= MAX_CLIENTS) { + if (trace.ent != attackentity) { + //if a teammate is hit + if (BotSameTeam(bs, trace.ent)) + return; + } + } + //if won't hit the enemy or not attacking a player (obelisk) + if (trace.ent != attackentity || attackentity >= MAX_CLIENTS) { + //if the projectile does radial damage + if (wi.proj.damagetype & DAMAGETYPE_RADIAL) { + if (trace.fraction * 1000 < wi.proj.radius) { + points = (wi.proj.damage - 0.5 * trace.fraction * 1000) * 0.5; + if (points > 0) { + return; + } + } + //FIXME: check if a teammate gets radial damage + } + } + //if fire has to be release to activate weapon + if (wi.flags & WFL_FIRERELEASED) { + if (bs->flags & BFL_ATTACKED) { + trap_EA_Attack(bs->client); + } + } + else { + trap_EA_Attack(bs->client); + } + bs->flags ^= BFL_ATTACKED; +} + +/* +================== +BotMapScripts +================== +*/ +void BotMapScripts(bot_state_t *bs) { + char info[1024]; + char mapname[128]; + int i, shootbutton; + float aim_accuracy; + aas_entityinfo_t entinfo; + vec3_t dir; + + trap_GetServerinfo(info, sizeof(info)); + + strncpy(mapname, Info_ValueForKey( info, "mapname" ), sizeof(mapname)-1); + mapname[sizeof(mapname)-1] = '\0'; + + if (!Q_stricmp(mapname, "q3tourney6")) { + vec3_t mins = {700, 204, 672}, maxs = {964, 468, 680}; + vec3_t buttonorg = {304, 352, 920}; + //NOTE: NEVER use the func_bobbing in q3tourney6 + bs->tfl &= ~TFL_FUNCBOB; + //if the bot is below the bounding box + if (bs->origin[0] > mins[0] && bs->origin[0] < maxs[0]) { + if (bs->origin[1] > mins[1] && bs->origin[1] < maxs[1]) { + if (bs->origin[2] < mins[2]) { + return; + } + } + } + shootbutton = qfalse; + //if an enemy is below this bounding box then shoot the button + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + + if (i == bs->client) continue; + // + BotEntityInfo(i, &entinfo); + // + if (!entinfo.valid) continue; + //if the enemy isn't dead and the enemy isn't the bot self + if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue; + // + if (entinfo.origin[0] > mins[0] && entinfo.origin[0] < maxs[0]) { + if (entinfo.origin[1] > mins[1] && entinfo.origin[1] < maxs[1]) { + if (entinfo.origin[2] < mins[2]) { + //if there's a team mate below the crusher + if (BotSameTeam(bs, i)) { + shootbutton = qfalse; + break; + } + else { + shootbutton = qtrue; + } + } + } + } + } + if (shootbutton) { + bs->flags |= BFL_IDEALVIEWSET; + VectorSubtract(buttonorg, bs->eye, dir); + vectoangles(dir, bs->ideal_viewangles); + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY, 0, 1); + bs->ideal_viewangles[PITCH] += 8 * crandom() * (1 - aim_accuracy); + bs->ideal_viewangles[PITCH] = AngleMod(bs->ideal_viewangles[PITCH]); + bs->ideal_viewangles[YAW] += 8 * crandom() * (1 - aim_accuracy); + bs->ideal_viewangles[YAW] = AngleMod(bs->ideal_viewangles[YAW]); + // + if (InFieldOfVision(bs->viewangles, 20, bs->ideal_viewangles)) { + trap_EA_Attack(bs->client); + } + } + } + else if (!Q_stricmp(mapname, "mpq3tourney6")) { + //NOTE: NEVER use the func_bobbing in mpq3tourney6 + bs->tfl &= ~TFL_FUNCBOB; + } +} + +/* +================== +BotSetMovedir +================== +*/ +// bk001205 - made these static +static vec3_t VEC_UP = {0, -1, 0}; +static vec3_t MOVEDIR_UP = {0, 0, 1}; +static vec3_t VEC_DOWN = {0, -2, 0}; +static vec3_t MOVEDIR_DOWN = {0, 0, -1}; + +void BotSetMovedir(vec3_t angles, vec3_t movedir) { + if (VectorCompare(angles, VEC_UP)) { + VectorCopy(MOVEDIR_UP, movedir); + } + else if (VectorCompare(angles, VEC_DOWN)) { + VectorCopy(MOVEDIR_DOWN, movedir); + } + else { + AngleVectors(angles, movedir, NULL, NULL); + } +} + +/* +================== +BotModelMinsMaxs + +this is ugly +================== +*/ +int BotModelMinsMaxs(int modelindex, int eType, int contents, vec3_t mins, vec3_t maxs) { + gentity_t *ent; + int i; + + ent = &g_entities[0]; + for (i = 0; i < level.num_entities; i++, ent++) { + if ( !ent->inuse ) { + continue; + } + if ( eType && ent->s.eType != eType) { + continue; + } + if ( contents && ent->r.contents != contents) { + continue; + } + if (ent->s.modelindex == modelindex) { + if (mins) + VectorAdd(ent->r.currentOrigin, ent->r.mins, mins); + if (maxs) + VectorAdd(ent->r.currentOrigin, ent->r.maxs, maxs); + return i; + } + } + if (mins) + VectorClear(mins); + if (maxs) + VectorClear(maxs); + return 0; +} + +/* +================== +BotFuncButtonGoal +================== +*/ +int BotFuncButtonActivateGoal(bot_state_t *bs, int bspent, bot_activategoal_t *activategoal) { + int i, areas[10], numareas, modelindex, entitynum; + char model[128]; + float lip, dist, health, angle; + vec3_t size, start, end, mins, maxs, angles, points[10]; + vec3_t movedir, origin, goalorigin, bboxmins, bboxmaxs; + vec3_t extramins = {1, 1, 1}, extramaxs = {-1, -1, -1}; + bsp_trace_t bsptrace; + + activategoal->shoot = qfalse; + VectorClear(activategoal->target); + //create a bot goal towards the button + trap_AAS_ValueForBSPEpairKey(bspent, "model", model, sizeof(model)); + if (!*model) + return qfalse; + modelindex = atoi(model+1); + if (!modelindex) + return qfalse; + VectorClear(angles); + entitynum = BotModelMinsMaxs(modelindex, ET_MOVER, 0, mins, maxs); + //get the lip of the button + trap_AAS_FloatForBSPEpairKey(bspent, "lip", &lip); + if (!lip) lip = 4; + //get the move direction from the angle + trap_AAS_FloatForBSPEpairKey(bspent, "angle", &angle); + VectorSet(angles, 0, angle, 0); + BotSetMovedir(angles, movedir); + //button size + VectorSubtract(maxs, mins, size); + //button origin + VectorAdd(mins, maxs, origin); + VectorScale(origin, 0.5, origin); + //touch distance of the button + dist = fabs(movedir[0]) * size[0] + fabs(movedir[1]) * size[1] + fabs(movedir[2]) * size[2]; + dist *= 0.5; + // + trap_AAS_FloatForBSPEpairKey(bspent, "health", &health); + //if the button is shootable + if (health) { + //calculate the shoot target + VectorMA(origin, -dist, movedir, goalorigin); + // + VectorCopy(goalorigin, activategoal->target); + activategoal->shoot = qtrue; + // + BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, goalorigin, bs->entitynum, MASK_SHOT); + // if the button is visible from the current position + if (bsptrace.fraction >= 1.0 || bsptrace.ent == entitynum) { + // + activategoal->goal.entitynum = entitynum; //NOTE: this is the entity number of the shootable button + activategoal->goal.number = 0; + activategoal->goal.flags = 0; + VectorCopy(bs->origin, activategoal->goal.origin); + activategoal->goal.areanum = bs->areanum; + VectorSet(activategoal->goal.mins, -8, -8, -8); + VectorSet(activategoal->goal.maxs, 8, 8, 8); + // + return qtrue; + } + else { + //create a goal from where the button is visible and shoot at the button from there + //add bounding box size to the dist + trap_AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, bboxmins, bboxmaxs); + for (i = 0; i < 3; i++) { + if (movedir[i] < 0) dist += fabs(movedir[i]) * fabs(bboxmaxs[i]); + else dist += fabs(movedir[i]) * fabs(bboxmins[i]); + } + //calculate the goal origin + VectorMA(origin, -dist, movedir, goalorigin); + // + VectorCopy(goalorigin, start); + start[2] += 24; + VectorCopy(start, end); + end[2] -= 512; + numareas = trap_AAS_TraceAreas(start, end, areas, points, 10); + // + for (i = numareas-1; i >= 0; i--) { + if (trap_AAS_AreaReachability(areas[i])) { + break; + } + } + if (i < 0) { + // FIXME: trace forward and maybe in other directions to find a valid area + } + if (i >= 0) { + // + VectorCopy(points[i], activategoal->goal.origin); + activategoal->goal.areanum = areas[i]; + VectorSet(activategoal->goal.mins, 8, 8, 8); + VectorSet(activategoal->goal.maxs, -8, -8, -8); + // + for (i = 0; i < 3; i++) + { + if (movedir[i] < 0) activategoal->goal.maxs[i] += fabs(movedir[i]) * fabs(extramaxs[i]); + else activategoal->goal.mins[i] += fabs(movedir[i]) * fabs(extramins[i]); + } //end for + // + activategoal->goal.entitynum = entitynum; + activategoal->goal.number = 0; + activategoal->goal.flags = 0; + return qtrue; + } + } + return qfalse; + } + else { + //add bounding box size to the dist + trap_AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, bboxmins, bboxmaxs); + for (i = 0; i < 3; i++) { + if (movedir[i] < 0) dist += fabs(movedir[i]) * fabs(bboxmaxs[i]); + else dist += fabs(movedir[i]) * fabs(bboxmins[i]); + } + //calculate the goal origin + VectorMA(origin, -dist, movedir, goalorigin); + // + VectorCopy(goalorigin, start); + start[2] += 24; + VectorCopy(start, end); + end[2] -= 100; + numareas = trap_AAS_TraceAreas(start, end, areas, NULL, 10); + // + for (i = 0; i < numareas; i++) { + if (trap_AAS_AreaReachability(areas[i])) { + break; + } + } + if (i < numareas) { + // + VectorCopy(origin, activategoal->goal.origin); + activategoal->goal.areanum = areas[i]; + VectorSubtract(mins, origin, activategoal->goal.mins); + VectorSubtract(maxs, origin, activategoal->goal.maxs); + // + for (i = 0; i < 3; i++) + { + if (movedir[i] < 0) activategoal->goal.maxs[i] += fabs(movedir[i]) * fabs(extramaxs[i]); + else activategoal->goal.mins[i] += fabs(movedir[i]) * fabs(extramins[i]); + } //end for + // + activategoal->goal.entitynum = entitynum; + activategoal->goal.number = 0; + activategoal->goal.flags = 0; + return qtrue; + } + } + return qfalse; +} + +/* +================== +BotFuncDoorGoal +================== +*/ +int BotFuncDoorActivateGoal(bot_state_t *bs, int bspent, bot_activategoal_t *activategoal) { + int modelindex, entitynum; + char model[MAX_INFO_STRING]; + vec3_t mins, maxs, origin, angles; + + //shoot at the shootable door + trap_AAS_ValueForBSPEpairKey(bspent, "model", model, sizeof(model)); + if (!*model) + return qfalse; + modelindex = atoi(model+1); + if (!modelindex) + return qfalse; + VectorClear(angles); + entitynum = BotModelMinsMaxs(modelindex, ET_MOVER, 0, mins, maxs); + //door origin + VectorAdd(mins, maxs, origin); + VectorScale(origin, 0.5, origin); + VectorCopy(origin, activategoal->target); + activategoal->shoot = qtrue; + // + activategoal->goal.entitynum = entitynum; //NOTE: this is the entity number of the shootable door + activategoal->goal.number = 0; + activategoal->goal.flags = 0; + VectorCopy(bs->origin, activategoal->goal.origin); + activategoal->goal.areanum = bs->areanum; + VectorSet(activategoal->goal.mins, -8, -8, -8); + VectorSet(activategoal->goal.maxs, 8, 8, 8); + return qtrue; +} + +/* +================== +BotTriggerMultipleGoal +================== +*/ +int BotTriggerMultipleActivateGoal(bot_state_t *bs, int bspent, bot_activategoal_t *activategoal) { + int i, areas[10], numareas, modelindex, entitynum; + char model[128]; + vec3_t start, end, mins, maxs, angles; + vec3_t origin, goalorigin; + + activategoal->shoot = qfalse; + VectorClear(activategoal->target); + //create a bot goal towards the trigger + trap_AAS_ValueForBSPEpairKey(bspent, "model", model, sizeof(model)); + if (!*model) + return qfalse; + modelindex = atoi(model+1); + if (!modelindex) + return qfalse; + VectorClear(angles); + entitynum = BotModelMinsMaxs(modelindex, 0, CONTENTS_TRIGGER, mins, maxs); + //trigger origin + VectorAdd(mins, maxs, origin); + VectorScale(origin, 0.5, origin); + VectorCopy(origin, goalorigin); + // + VectorCopy(goalorigin, start); + start[2] += 24; + VectorCopy(start, end); + end[2] -= 100; + numareas = trap_AAS_TraceAreas(start, end, areas, NULL, 10); + // + for (i = 0; i < numareas; i++) { + if (trap_AAS_AreaReachability(areas[i])) { + break; + } + } + if (i < numareas) { + VectorCopy(origin, activategoal->goal.origin); + activategoal->goal.areanum = areas[i]; + VectorSubtract(mins, origin, activategoal->goal.mins); + VectorSubtract(maxs, origin, activategoal->goal.maxs); + // + activategoal->goal.entitynum = entitynum; + activategoal->goal.number = 0; + activategoal->goal.flags = 0; + return qtrue; + } + return qfalse; +} + +/* +================== +BotPopFromActivateGoalStack +================== +*/ +int BotPopFromActivateGoalStack(bot_state_t *bs) { + if (!bs->activatestack) + return qfalse; + BotEnableActivateGoalAreas(bs->activatestack, qtrue); + bs->activatestack->inuse = qfalse; + bs->activatestack->justused_time = FloatTime(); + bs->activatestack = bs->activatestack->next; + return qtrue; +} + +/* +================== +BotPushOntoActivateGoalStack +================== +*/ +int BotPushOntoActivateGoalStack(bot_state_t *bs, bot_activategoal_t *activategoal) { + int i, best; + float besttime; + + best = -1; + besttime = FloatTime() + 9999; + // + for (i = 0; i < MAX_ACTIVATESTACK; i++) { + if (!bs->activategoalheap[i].inuse) { + if (bs->activategoalheap[i].justused_time < besttime) { + besttime = bs->activategoalheap[i].justused_time; + best = i; + } + } + } + if (best != -1) { + memcpy(&bs->activategoalheap[best], activategoal, sizeof(bot_activategoal_t)); + bs->activategoalheap[best].inuse = qtrue; + bs->activategoalheap[best].next = bs->activatestack; + bs->activatestack = &bs->activategoalheap[best]; + return qtrue; + } + return qfalse; +} + +/* +================== +BotClearActivateGoalStack +================== +*/ +void BotClearActivateGoalStack(bot_state_t *bs) { + while(bs->activatestack) + BotPopFromActivateGoalStack(bs); +} + +/* +================== +BotEnableActivateGoalAreas +================== +*/ +void BotEnableActivateGoalAreas(bot_activategoal_t *activategoal, int enable) { + int i; + + if (activategoal->areasdisabled == !enable) + return; + for (i = 0; i < activategoal->numareas; i++) + trap_AAS_EnableRoutingArea( activategoal->areas[i], enable ); + activategoal->areasdisabled = !enable; +} + +/* +================== +BotIsGoingToActivateEntity +================== +*/ +int BotIsGoingToActivateEntity(bot_state_t *bs, int entitynum) { + bot_activategoal_t *a; + int i; + + for (a = bs->activatestack; a; a = a->next) { + if (a->time < FloatTime()) + continue; + if (a->goal.entitynum == entitynum) + return qtrue; + } + for (i = 0; i < MAX_ACTIVATESTACK; i++) { + if (bs->activategoalheap[i].inuse) + continue; + // + if (bs->activategoalheap[i].goal.entitynum == entitynum) { + // if the bot went for this goal less than 2 seconds ago + if (bs->activategoalheap[i].justused_time > FloatTime() - 2) + return qtrue; + } + } + return qfalse; +} + +/* +================== +BotGetActivateGoal + + returns the number of the bsp entity to activate + goal->entitynum will be set to the game entity to activate +================== +*/ +//#define OBSTACLEDEBUG + +int BotGetActivateGoal(bot_state_t *bs, int entitynum, bot_activategoal_t *activategoal) { + int i, ent, cur_entities[10], spawnflags, modelindex, areas[MAX_ACTIVATEAREAS*2], numareas, t; + char model[MAX_INFO_STRING], tmpmodel[128]; + char target[128], classname[128]; + float health; + char targetname[10][128]; + aas_entityinfo_t entinfo; + aas_areainfo_t areainfo; + vec3_t origin, angles, absmins, absmaxs; + + memset(activategoal, 0, sizeof(bot_activategoal_t)); + BotEntityInfo(entitynum, &entinfo); + Com_sprintf(model, sizeof( model ), "*%d", entinfo.modelindex); + for (ent = trap_AAS_NextBSPEntity(0); ent; ent = trap_AAS_NextBSPEntity(ent)) { + if (!trap_AAS_ValueForBSPEpairKey(ent, "model", tmpmodel, sizeof(tmpmodel))) continue; + if (!strcmp(model, tmpmodel)) break; + } + if (!ent) { + BotAI_Print(PRT_ERROR, "BotGetActivateGoal: no entity found with model %s\n", model); + return 0; + } + trap_AAS_ValueForBSPEpairKey(ent, "classname", classname, sizeof(classname)); + if (!classname) { + BotAI_Print(PRT_ERROR, "BotGetActivateGoal: entity with model %s has no classname\n", model); + return 0; + } + //if it is a door + if (!strcmp(classname, "func_door")) { + if (trap_AAS_FloatForBSPEpairKey(ent, "health", &health)) { + //if the door has health then the door must be shot to open + if (health) { + BotFuncDoorActivateGoal(bs, ent, activategoal); + return ent; + } + } + // + trap_AAS_IntForBSPEpairKey(ent, "spawnflags", &spawnflags); + // if the door starts open then just wait for the door to return + if ( spawnflags & 1 ) + return 0; + //get the door origin + if (!trap_AAS_VectorForBSPEpairKey(ent, "origin", origin)) { + VectorClear(origin); + } + //if the door is open or opening already + if (!VectorCompare(origin, entinfo.origin)) + return 0; + // store all the areas the door is in + trap_AAS_ValueForBSPEpairKey(ent, "model", model, sizeof(model)); + if (*model) { + modelindex = atoi(model+1); + if (modelindex) { + VectorClear(angles); + BotModelMinsMaxs(modelindex, ET_MOVER, 0, absmins, absmaxs); + // + numareas = trap_AAS_BBoxAreas(absmins, absmaxs, areas, MAX_ACTIVATEAREAS*2); + // store the areas with reachabilities first + for (i = 0; i < numareas; i++) { + if (activategoal->numareas >= MAX_ACTIVATEAREAS) + break; + if ( !trap_AAS_AreaReachability(areas[i]) ) { + continue; + } + trap_AAS_AreaInfo(areas[i], &areainfo); + if (areainfo.contents & AREACONTENTS_MOVER) { + activategoal->areas[activategoal->numareas++] = areas[i]; + } + } + // store any remaining areas + for (i = 0; i < numareas; i++) { + if (activategoal->numareas >= MAX_ACTIVATEAREAS) + break; + if ( trap_AAS_AreaReachability(areas[i]) ) { + continue; + } + trap_AAS_AreaInfo(areas[i], &areainfo); + if (areainfo.contents & AREACONTENTS_MOVER) { + activategoal->areas[activategoal->numareas++] = areas[i]; + } + } + } + } + } + // if the bot is blocked by or standing on top of a button + if (!strcmp(classname, "func_button")) { + return 0; + } + // get the targetname so we can find an entity with a matching target + if (!trap_AAS_ValueForBSPEpairKey(ent, "targetname", targetname[0], sizeof(targetname[0]))) { + if (bot_developer.integer) { + BotAI_Print(PRT_ERROR, "BotGetActivateGoal: entity with model \"%s\" has no targetname\n", model); + } + return 0; + } + // allow tree-like activation + cur_entities[0] = trap_AAS_NextBSPEntity(0); + for (i = 0; i >= 0 && i < 10;) { + for (ent = cur_entities[i]; ent; ent = trap_AAS_NextBSPEntity(ent)) { + if (!trap_AAS_ValueForBSPEpairKey(ent, "target", target, sizeof(target))) continue; + if (!strcmp(targetname[i], target)) { + cur_entities[i] = trap_AAS_NextBSPEntity(ent); + break; + } + } + if (!ent) { + if (bot_developer.integer) { + BotAI_Print(PRT_ERROR, "BotGetActivateGoal: no entity with target \"%s\"\n", targetname[i]); + } + i--; + continue; + } + if (!trap_AAS_ValueForBSPEpairKey(ent, "classname", classname, sizeof(classname))) { + if (bot_developer.integer) { + BotAI_Print(PRT_ERROR, "BotGetActivateGoal: entity with target \"%s\" has no classname\n", targetname[i]); + } + continue; + } + // BSP button model + if (!strcmp(classname, "func_button")) { + // + if (!BotFuncButtonActivateGoal(bs, ent, activategoal)) + continue; + // if the bot tries to activate this button already + if ( bs->activatestack && bs->activatestack->inuse && + bs->activatestack->goal.entitynum == activategoal->goal.entitynum && + bs->activatestack->time > FloatTime() && + bs->activatestack->start_time < FloatTime() - 2) + continue; + // if the bot is in a reachability area + if ( trap_AAS_AreaReachability(bs->areanum) ) { + // disable all areas the blocking entity is in + BotEnableActivateGoalAreas( activategoal, qfalse ); + // + t = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, activategoal->goal.areanum, bs->tfl); + // if the button is not reachable + if (!t) { + continue; + } + activategoal->time = FloatTime() + t * 0.01 + 5; + } + return ent; + } + // invisible trigger multiple box + else if (!strcmp(classname, "trigger_multiple")) { + // + if (!BotTriggerMultipleActivateGoal(bs, ent, activategoal)) + continue; + // if the bot tries to activate this trigger already + if ( bs->activatestack && bs->activatestack->inuse && + bs->activatestack->goal.entitynum == activategoal->goal.entitynum && + bs->activatestack->time > FloatTime() && + bs->activatestack->start_time < FloatTime() - 2) + continue; + // if the bot is in a reachability area + if ( trap_AAS_AreaReachability(bs->areanum) ) { + // disable all areas the blocking entity is in + BotEnableActivateGoalAreas( activategoal, qfalse ); + // + t = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, activategoal->goal.areanum, bs->tfl); + // if the trigger is not reachable + if (!t) { + continue; + } + activategoal->time = FloatTime() + t * 0.01 + 5; + } + return ent; + } + else if (!strcmp(classname, "func_timer")) { + // just skip the func_timer + continue; + } + // the actual button or trigger might be linked through a target_relay or target_delay + else if (!strcmp(classname, "target_relay") || !strcmp(classname, "target_delay")) { + if (trap_AAS_ValueForBSPEpairKey(ent, "targetname", targetname[i+1], sizeof(targetname[0]))) { + i++; + cur_entities[i] = trap_AAS_NextBSPEntity(0); + } + } + } +#ifdef OBSTACLEDEBUG + BotAI_Print(PRT_ERROR, "BotGetActivateGoal: no valid activator for entity with target \"%s\"\n", targetname[0]); +#endif + return 0; +} + +/* +================== +BotGoForActivateGoal +================== +*/ +int BotGoForActivateGoal(bot_state_t *bs, bot_activategoal_t *activategoal) { + aas_entityinfo_t activateinfo; + + activategoal->inuse = qtrue; + if (!activategoal->time) + activategoal->time = FloatTime() + 10; + activategoal->start_time = FloatTime(); + BotEntityInfo(activategoal->goal.entitynum, &activateinfo); + VectorCopy(activateinfo.origin, activategoal->origin); + // + if (BotPushOntoActivateGoalStack(bs, activategoal)) { + // enter the activate entity AI node + AIEnter_Seek_ActivateEntity(bs, "BotGoForActivateGoal"); + return qtrue; + } + else { + // enable any routing areas that were disabled + BotEnableActivateGoalAreas(activategoal, qtrue); + return qfalse; + } +} + +/* +================== +BotPrintActivateGoalInfo +================== +*/ +void BotPrintActivateGoalInfo(bot_state_t *bs, bot_activategoal_t *activategoal, int bspent) { + char netname[MAX_NETNAME]; + char classname[128]; + char buf[128]; + + ClientName(bs->client, netname, sizeof(netname)); + trap_AAS_ValueForBSPEpairKey(bspent, "classname", classname, sizeof(classname)); + if (activategoal->shoot) { + Com_sprintf(buf, sizeof(buf), "%s: I have to shoot at a %s from %1.1f %1.1f %1.1f in area %d\n", + netname, classname, + activategoal->goal.origin[0], + activategoal->goal.origin[1], + activategoal->goal.origin[2], + activategoal->goal.areanum); + } + else { + Com_sprintf(buf, sizeof(buf), "%s: I have to activate a %s at %1.1f %1.1f %1.1f in area %d\n", + netname, classname, + activategoal->goal.origin[0], + activategoal->goal.origin[1], + activategoal->goal.origin[2], + activategoal->goal.areanum); + } + trap_EA_Say(bs->client, buf); +} + +/* +================== +BotRandomMove +================== +*/ +void BotRandomMove(bot_state_t *bs, bot_moveresult_t *moveresult) { + vec3_t dir, angles; + + angles[0] = 0; + angles[1] = random() * 360; + angles[2] = 0; + AngleVectors(angles, dir, NULL, NULL); + + trap_BotMoveInDirection(bs->ms, dir, 400, MOVE_WALK); + + moveresult->failure = qfalse; + VectorCopy(dir, moveresult->movedir); +} + +/* +================== +BotAIBlocked + +Very basic handling of bots being blocked by other entities. +Check what kind of entity is blocking the bot and try to activate +it. If that's not an option then try to walk around or over the entity. +Before the bot ends in this part of the AI it should predict which doors to +open, which buttons to activate etc. +================== +*/ +void BotAIBlocked(bot_state_t *bs, bot_moveresult_t *moveresult, int activate) { + int movetype, bspent; + vec3_t hordir, start, end, mins, maxs, sideward, angles, up = {0, 0, 1}; + aas_entityinfo_t entinfo; + bot_activategoal_t activategoal; + + // if the bot is not blocked by anything + if (!moveresult->blocked) { + bs->notblocked_time = FloatTime(); + return; + } + // if stuck in a solid area + if ( moveresult->type == RESULTTYPE_INSOLIDAREA ) { + // move in a random direction in the hope to get out + BotRandomMove(bs, moveresult); + // + return; + } + // get info for the entity that is blocking the bot + BotEntityInfo(moveresult->blockentity, &entinfo); +#ifdef OBSTACLEDEBUG + ClientName(bs->client, netname, sizeof(netname)); + BotAI_Print(PRT_MESSAGE, "%s: I'm blocked by model %d\n", netname, entinfo.modelindex); +#endif // OBSTACLEDEBUG + // if blocked by a bsp model and the bot wants to activate it + if (activate && entinfo.modelindex > 0 && entinfo.modelindex <= max_bspmodelindex) { + // find the bsp entity which should be activated in order to get the blocking entity out of the way + bspent = BotGetActivateGoal(bs, entinfo.number, &activategoal); + if (bspent) { + // + if (bs->activatestack && !bs->activatestack->inuse) + bs->activatestack = NULL; + // if not already trying to activate this entity + if (!BotIsGoingToActivateEntity(bs, activategoal.goal.entitynum)) { + // + BotGoForActivateGoal(bs, &activategoal); + } + // if ontop of an obstacle or + // if the bot is not in a reachability area it'll still + // need some dynamic obstacle avoidance, otherwise return + if (!(moveresult->flags & MOVERESULT_ONTOPOFOBSTACLE) && + trap_AAS_AreaReachability(bs->areanum)) + return; + } + else { + // enable any routing areas that were disabled + BotEnableActivateGoalAreas(&activategoal, qtrue); + } + } + // just some basic dynamic obstacle avoidance code + hordir[0] = moveresult->movedir[0]; + hordir[1] = moveresult->movedir[1]; + hordir[2] = 0; + // if no direction just take a random direction + if (VectorNormalize(hordir) < 0.1) { + VectorSet(angles, 0, 360 * random(), 0); + AngleVectors(angles, hordir, NULL, NULL); + } + // + //if (moveresult->flags & MOVERESULT_ONTOPOFOBSTACLE) movetype = MOVE_JUMP; + //else + movetype = MOVE_WALK; + // if there's an obstacle at the bot's feet and head then + // the bot might be able to crouch through + VectorCopy(bs->origin, start); + start[2] += 18; + VectorMA(start, 5, hordir, end); + VectorSet(mins, -16, -16, -24); + VectorSet(maxs, 16, 16, 4); + // + //bsptrace = AAS_Trace(start, mins, maxs, end, bs->entitynum, MASK_PLAYERSOLID); + //if (bsptrace.fraction >= 1) movetype = MOVE_CROUCH; + // get the sideward vector + CrossProduct(hordir, up, sideward); + // + if (bs->flags & BFL_AVOIDRIGHT) VectorNegate(sideward, sideward); + // try to crouch straight forward? + if (movetype != MOVE_CROUCH || !trap_BotMoveInDirection(bs->ms, hordir, 400, movetype)) { + // perform the movement + if (!trap_BotMoveInDirection(bs->ms, sideward, 400, movetype)) { + // flip the avoid direction flag + bs->flags ^= BFL_AVOIDRIGHT; + // flip the direction + // VectorNegate(sideward, sideward); + VectorMA(sideward, -1, hordir, sideward); + // move in the other direction + trap_BotMoveInDirection(bs->ms, sideward, 400, movetype); + } + } + // + if (bs->notblocked_time < FloatTime() - 0.4) { + // just reset goals and hope the bot will go into another direction? + // is this still needed?? + if (bs->ainode == AINode_Seek_NBG) bs->nbg_time = 0; + else if (bs->ainode == AINode_Seek_LTG) bs->ltg_time = 0; + } +} + +/* +================== +BotAIPredictObstacles + +Predict the route towards the goal and check if the bot +will be blocked by certain obstacles. When the bot has obstacles +on it's path the bot should figure out if they can be removed +by activating certain entities. +================== +*/ +int BotAIPredictObstacles(bot_state_t *bs, bot_goal_t *goal) { + int modelnum, entitynum, bspent; + bot_activategoal_t activategoal; + aas_predictroute_t route; + + if (!bot_predictobstacles.integer) + return qfalse; + + // always predict when the goal change or at regular intervals + if (bs->predictobstacles_goalareanum == goal->areanum && + bs->predictobstacles_time > FloatTime() - 6) { + return qfalse; + } + bs->predictobstacles_goalareanum = goal->areanum; + bs->predictobstacles_time = FloatTime(); + + // predict at most 100 areas or 10 seconds ahead + trap_AAS_PredictRoute(&route, bs->areanum, bs->origin, + goal->areanum, bs->tfl, 100, 1000, + RSE_USETRAVELTYPE|RSE_ENTERCONTENTS, + AREACONTENTS_MOVER, TFL_BRIDGE, 0); + // if bot has to travel through an area with a mover + if (route.stopevent & RSE_ENTERCONTENTS) { + // if the bot will run into a mover + if (route.endcontents & AREACONTENTS_MOVER) { + //NOTE: this only works with bspc 2.1 or higher + modelnum = (route.endcontents & AREACONTENTS_MODELNUM) >> AREACONTENTS_MODELNUMSHIFT; + if (modelnum) { + // + entitynum = BotModelMinsMaxs(modelnum, ET_MOVER, 0, NULL, NULL); + if (entitynum) { + //NOTE: BotGetActivateGoal already checks if the door is open or not + bspent = BotGetActivateGoal(bs, entitynum, &activategoal); + if (bspent) { + // + if (bs->activatestack && !bs->activatestack->inuse) + bs->activatestack = NULL; + // if not already trying to activate this entity + if (!BotIsGoingToActivateEntity(bs, activategoal.goal.entitynum)) { + // + //BotAI_Print(PRT_MESSAGE, "blocked by mover model %d, entity %d ?\n", modelnum, entitynum); + // + BotGoForActivateGoal(bs, &activategoal); + return qtrue; + } + else { + // enable any routing areas that were disabled + BotEnableActivateGoalAreas(&activategoal, qtrue); + } + } + } + } + } + } + else if (route.stopevent & RSE_USETRAVELTYPE) { + if (route.endtravelflags & TFL_BRIDGE) { + //FIXME: check if the bridge is available to travel over + } + } + return qfalse; +} + +/* +================== +BotCheckConsoleMessages +================== +*/ +void BotCheckConsoleMessages(bot_state_t *bs) { + char botname[MAX_NETNAME], message[MAX_MESSAGE_SIZE], netname[MAX_NETNAME], *ptr; + float chat_reply; + int context, handle; + bot_consolemessage_t m; + bot_match_t match; + + //the name of this bot + ClientName(bs->client, botname, sizeof(botname)); + // + while((handle = trap_BotNextConsoleMessage(bs->cs, &m)) != 0) { + //if the chat state is flooded with messages the bot will read them quickly + if (trap_BotNumConsoleMessages(bs->cs) < 10) { + //if it is a chat message the bot needs some time to read it + if (m.type == CMS_CHAT && m.time > FloatTime() - (1 + random())) break; + } + // + ptr = m.message; + //if it is a chat message then don't unify white spaces and don't + //replace synonyms in the netname + if (m.type == CMS_CHAT) { + // + if (trap_BotFindMatch(m.message, &match, MTCONTEXT_REPLYCHAT)) { + ptr = m.message + match.variables[MESSAGE].offset; + } + } + //unify the white spaces in the message + trap_UnifyWhiteSpaces(ptr); + //replace synonyms in the right context + context = BotSynonymContext(bs); + trap_BotReplaceSynonyms(ptr, context); + //if there's no match + if (!BotMatchMessage(bs, m.message)) { + //if it is a chat message + if (m.type == CMS_CHAT && !bot_nochat.integer) { + // + if (!trap_BotFindMatch(m.message, &match, MTCONTEXT_REPLYCHAT)) { + trap_BotRemoveConsoleMessage(bs->cs, handle); + continue; + } + //don't use eliza chats with team messages + if (match.subtype & ST_TEAM) { + trap_BotRemoveConsoleMessage(bs->cs, handle); + continue; + } + // + trap_BotMatchVariable(&match, NETNAME, netname, sizeof(netname)); + trap_BotMatchVariable(&match, MESSAGE, message, sizeof(message)); + //if this is a message from the bot self + if (bs->client == ClientFromName(netname)) { + trap_BotRemoveConsoleMessage(bs->cs, handle); + continue; + } + //unify the message + trap_UnifyWhiteSpaces(message); + // + trap_Cvar_Update(&bot_testrchat); + if (bot_testrchat.integer) { + // + trap_BotLibVarSet("bot_testrchat", "1"); + //if bot replies with a chat message + if (trap_BotReplyChat(bs->cs, message, context, CONTEXT_REPLY, + NULL, NULL, + NULL, NULL, + NULL, NULL, + botname, netname)) { + BotAI_Print(PRT_MESSAGE, "------------------------\n"); + } + else { + BotAI_Print(PRT_MESSAGE, "**** no valid reply ****\n"); + } + } + //if at a valid chat position and not chatting already and not in teamplay + else if (bs->ainode != AINode_Stand && BotValidChatPosition(bs) && !TeamPlayIsOn()) { + chat_reply = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_REPLY, 0, 1); + if (random() < 1.5 / (NumBots()+1) && random() < chat_reply) { + //if bot replies with a chat message + if (trap_BotReplyChat(bs->cs, message, context, CONTEXT_REPLY, + NULL, NULL, + NULL, NULL, + NULL, NULL, + botname, netname)) { + //remove the console message + trap_BotRemoveConsoleMessage(bs->cs, handle); + bs->stand_time = FloatTime() + BotChatTime(bs); + AIEnter_Stand(bs, "BotCheckConsoleMessages: reply chat"); + //EA_Say(bs->client, bs->cs.chatmessage); + break; + } + } + } + } + } + //remove the console message + trap_BotRemoveConsoleMessage(bs->cs, handle); + } +} + +/* +================== +BotCheckEvents +================== +*/ +void BotCheckForGrenades(bot_state_t *bs, entityState_t *state) { + // if this is not a grenade + if (state->eType != ET_MISSILE || state->weapon != WP_GRENADE_LAUNCHER) + return; + // try to avoid the grenade + trap_BotAddAvoidSpot(bs->ms, state->pos.trBase, 160, AVOID_ALWAYS); +} + +#ifdef MISSIONPACK +/* +================== +BotCheckForProxMines +================== +*/ +void BotCheckForProxMines(bot_state_t *bs, entityState_t *state) { + // if this is not a prox mine + if (state->eType != ET_MISSILE || state->weapon != WP_PROX_LAUNCHER) + return; + // if this prox mine is from someone on our own team + if (state->generic1 == BotTeam(bs)) + return; + // if the bot doesn't have a weapon to deactivate the mine + if (!(bs->inventory[INVENTORY_PLASMAGUN] > 0 && bs->inventory[INVENTORY_CELLS] > 0) && + !(bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && bs->inventory[INVENTORY_ROCKETS] > 0) && + !(bs->inventory[INVENTORY_BFG10K] > 0 && bs->inventory[INVENTORY_BFGAMMO] > 0) ) { + return; + } + // try to avoid the prox mine + trap_BotAddAvoidSpot(bs->ms, state->pos.trBase, 160, AVOID_ALWAYS); + // + if (bs->numproxmines >= MAX_PROXMINES) + return; + bs->proxmines[bs->numproxmines] = state->number; + bs->numproxmines++; +} + +/* +================== +BotCheckForKamikazeBody +================== +*/ +void BotCheckForKamikazeBody(bot_state_t *bs, entityState_t *state) { + // if this entity is not wearing the kamikaze + if (!(state->eFlags & EF_KAMIKAZE)) + return; + // if this entity isn't dead + if (!(state->eFlags & EF_DEAD)) + return; + //remember this kamikaze body + bs->kamikazebody = state->number; +} +#endif + +/* +================== +BotCheckEvents +================== +*/ +void BotCheckEvents(bot_state_t *bs, entityState_t *state) { + int event; + char buf[128]; +#ifdef MISSIONPACK + aas_entityinfo_t entinfo; +#endif + + //NOTE: this sucks, we're accessing the gentity_t directly + //but there's no other fast way to do it right now + if (bs->entityeventTime[state->number] == g_entities[state->number].eventTime) { + return; + } + bs->entityeventTime[state->number] = g_entities[state->number].eventTime; + //if it's an event only entity + if (state->eType > ET_EVENTS) { + event = (state->eType - ET_EVENTS) & ~EV_EVENT_BITS; + } + else { + event = state->event & ~EV_EVENT_BITS; + } + // + switch(event) { + //client obituary event + case EV_OBITUARY: + { + int target, attacker, mod; + + target = state->otherEntityNum; + attacker = state->otherEntityNum2; + mod = state->eventParm; + // + if (target == bs->client) { + bs->botdeathtype = mod; + bs->lastkilledby = attacker; + // + if (target == attacker || + target == ENTITYNUM_NONE || + target == ENTITYNUM_WORLD) bs->botsuicide = qtrue; + else bs->botsuicide = qfalse; + // + bs->num_deaths++; + } + //else if this client was killed by the bot + else if (attacker == bs->client) { + bs->enemydeathtype = mod; + bs->lastkilledplayer = target; + bs->killedenemy_time = FloatTime(); + // + bs->num_kills++; + } + else if (attacker == bs->enemy && target == attacker) { + bs->enemysuicide = qtrue; + } + // +#ifdef MISSIONPACK + if (gametype == GT_1FCTF) { + // + BotEntityInfo(target, &entinfo); + if ( entinfo.powerups & ( 1 << PW_NEUTRALFLAG ) ) { + if (!BotSameTeam(bs, target)) { + bs->neutralflagstatus = 3; //enemy dropped the flag + bs->flagstatuschanged = qtrue; + } + } + } +#endif + break; + } + case EV_GLOBAL_SOUND: + { + if (state->eventParm < 0 || state->eventParm > MAX_SOUNDS) { + BotAI_Print(PRT_ERROR, "EV_GLOBAL_SOUND: eventParm (%d) out of range\n", state->eventParm); + break; + } + trap_GetConfigstring(CS_SOUNDS + state->eventParm, buf, sizeof(buf)); + /* + if (!strcmp(buf, "sound/teamplay/flagret_red.wav")) { + //red flag is returned + bs->redflagstatus = 0; + bs->flagstatuschanged = qtrue; + } + else if (!strcmp(buf, "sound/teamplay/flagret_blu.wav")) { + //blue flag is returned + bs->blueflagstatus = 0; + bs->flagstatuschanged = qtrue; + } + else*/ +#ifdef MISSIONPACK + if (!strcmp(buf, "sound/items/kamikazerespawn.wav" )) { + //the kamikaze respawned so dont avoid it + BotDontAvoid(bs, "Kamikaze"); + } + else +#endif + if (!strcmp(buf, "sound/items/poweruprespawn.wav")) { + //powerup respawned... go get it + BotGoForPowerups(bs); + } + break; + } + case EV_GLOBAL_TEAM_SOUND: + { + if (gametype == GT_CTF) { + switch(state->eventParm) { + case GTS_RED_CAPTURE: + bs->blueflagstatus = 0; + bs->redflagstatus = 0; + bs->flagstatuschanged = qtrue; + break; //see BotMatch_CTF + case GTS_BLUE_CAPTURE: + bs->blueflagstatus = 0; + bs->redflagstatus = 0; + bs->flagstatuschanged = qtrue; + break; //see BotMatch_CTF + case GTS_RED_RETURN: + //blue flag is returned + bs->blueflagstatus = 0; + bs->flagstatuschanged = qtrue; + break; + case GTS_BLUE_RETURN: + //red flag is returned + bs->redflagstatus = 0; + bs->flagstatuschanged = qtrue; + break; + case GTS_RED_TAKEN: + //blue flag is taken + bs->blueflagstatus = 1; + bs->flagstatuschanged = qtrue; + break; //see BotMatch_CTF + case GTS_BLUE_TAKEN: + //red flag is taken + bs->redflagstatus = 1; + bs->flagstatuschanged = qtrue; + break; //see BotMatch_CTF + } + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + switch(state->eventParm) { + case GTS_RED_CAPTURE: + bs->neutralflagstatus = 0; + bs->flagstatuschanged = qtrue; + break; + case GTS_BLUE_CAPTURE: + bs->neutralflagstatus = 0; + bs->flagstatuschanged = qtrue; + break; + case GTS_RED_RETURN: + //flag has returned + bs->neutralflagstatus = 0; + bs->flagstatuschanged = qtrue; + break; + case GTS_BLUE_RETURN: + //flag has returned + bs->neutralflagstatus = 0; + bs->flagstatuschanged = qtrue; + break; + case GTS_RED_TAKEN: + bs->neutralflagstatus = BotTeam(bs) == TEAM_RED ? 2 : 1; //FIXME: check Team_TakeFlagSound in g_team.c + bs->flagstatuschanged = qtrue; + break; + case GTS_BLUE_TAKEN: + bs->neutralflagstatus = BotTeam(bs) == TEAM_BLUE ? 2 : 1; //FIXME: check Team_TakeFlagSound in g_team.c + bs->flagstatuschanged = qtrue; + break; + } + } +#endif + break; + } + case EV_PLAYER_TELEPORT_IN: + { + VectorCopy(state->origin, lastteleport_origin); + lastteleport_time = FloatTime(); + break; + } + case EV_GENERAL_SOUND: + { + //if this sound is played on the bot + if (state->number == bs->client) { + if (state->eventParm < 0 || state->eventParm > MAX_SOUNDS) { + BotAI_Print(PRT_ERROR, "EV_GENERAL_SOUND: eventParm (%d) out of range\n", state->eventParm); + break; + } + //check out the sound + trap_GetConfigstring(CS_SOUNDS + state->eventParm, buf, sizeof(buf)); + //if falling into a death pit + if (!strcmp(buf, "*falling1.wav")) { + //if the bot has a personal teleporter + if (bs->inventory[INVENTORY_TELEPORTER] > 0) { + //use the holdable item + trap_EA_Use(bs->client); + } + } + } + break; + } + case EV_FOOTSTEP: + case EV_FOOTSTEP_METAL: + case EV_FOOTSPLASH: + case EV_FOOTWADE: + case EV_SWIM: + case EV_FALL_SHORT: + case EV_FALL_MEDIUM: + case EV_FALL_FAR: + case EV_STEP_4: + case EV_STEP_8: + case EV_STEP_12: + case EV_STEP_16: + case EV_JUMP_PAD: + case EV_JUMP: + case EV_TAUNT: + case EV_WATER_TOUCH: + case EV_WATER_LEAVE: + case EV_WATER_UNDER: + case EV_WATER_CLEAR: + case EV_ITEM_PICKUP: + case EV_GLOBAL_ITEM_PICKUP: + case EV_NOAMMO: + case EV_CHANGE_WEAPON: + case EV_FIRE_WEAPON: + //FIXME: either add to sound queue or mark player as someone making noise + break; + case EV_USE_ITEM0: + case EV_USE_ITEM1: + case EV_USE_ITEM2: + case EV_USE_ITEM3: + case EV_USE_ITEM4: + case EV_USE_ITEM5: + case EV_USE_ITEM6: + case EV_USE_ITEM7: + case EV_USE_ITEM8: + case EV_USE_ITEM9: + case EV_USE_ITEM10: + case EV_USE_ITEM11: + case EV_USE_ITEM12: + case EV_USE_ITEM13: + case EV_USE_ITEM14: + break; + } +} + +/* +================== +BotCheckSnapshot +================== +*/ +void BotCheckSnapshot(bot_state_t *bs) { + int ent; + entityState_t state; + + //remove all avoid spots + trap_BotAddAvoidSpot(bs->ms, vec3_origin, 0, AVOID_CLEAR); + //reset kamikaze body + bs->kamikazebody = 0; + //reset number of proxmines + bs->numproxmines = 0; + // + ent = 0; + while( ( ent = BotAI_GetSnapshotEntity( bs->client, ent, &state ) ) != -1 ) { + //check the entity state for events + BotCheckEvents(bs, &state); + //check for grenades the bot should avoid + BotCheckForGrenades(bs, &state); + // +#ifdef MISSIONPACK + //check for proximity mines which the bot should deactivate + BotCheckForProxMines(bs, &state); + //check for dead bodies with the kamikaze effect which should be gibbed + BotCheckForKamikazeBody(bs, &state); +#endif + } + //check the player state for events + BotAI_GetEntityState(bs->client, &state); + //copy the player state events to the entity state + state.event = bs->cur_ps.externalEvent; + state.eventParm = bs->cur_ps.externalEventParm; + // + BotCheckEvents(bs, &state); +} + +/* +================== +BotCheckAir +================== +*/ +void BotCheckAir(bot_state_t *bs) { + if (bs->inventory[INVENTORY_ENVIRONMENTSUIT] <= 0) { + if (trap_AAS_PointContents(bs->eye) & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA)) { + return; + } + } + bs->lastair_time = FloatTime(); +} + +/* +================== +BotAlternateRoute +================== +*/ +bot_goal_t *BotAlternateRoute(bot_state_t *bs, bot_goal_t *goal) { + int t; + + // if the bot has an alternative route goal + if (bs->altroutegoal.areanum) { + // + if (bs->reachedaltroutegoal_time) + return goal; + // travel time towards alternative route goal + t = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, bs->altroutegoal.areanum, bs->tfl); + if (t && t < 20) { + //BotAI_Print(PRT_MESSAGE, "reached alternate route goal\n"); + bs->reachedaltroutegoal_time = FloatTime(); + } + memcpy(goal, &bs->altroutegoal, sizeof(bot_goal_t)); + return &bs->altroutegoal; + } + return goal; +} + +/* +================== +BotGetAlternateRouteGoal +================== +*/ +int BotGetAlternateRouteGoal(bot_state_t *bs, int base) { + aas_altroutegoal_t *altroutegoals; + bot_goal_t *goal; + int numaltroutegoals, rnd; + + if (base == TEAM_RED) { + altroutegoals = red_altroutegoals; + numaltroutegoals = red_numaltroutegoals; + } + else { + altroutegoals = blue_altroutegoals; + numaltroutegoals = blue_numaltroutegoals; + } + if (!numaltroutegoals) + return qfalse; + rnd = (float) random() * numaltroutegoals; + if (rnd >= numaltroutegoals) + rnd = numaltroutegoals-1; + goal = &bs->altroutegoal; + goal->areanum = altroutegoals[rnd].areanum; + VectorCopy(altroutegoals[rnd].origin, goal->origin); + VectorSet(goal->mins, -8, -8, -8); + VectorSet(goal->maxs, 8, 8, 8); + goal->entitynum = 0; + goal->iteminfo = 0; + goal->number = 0; + goal->flags = 0; + // + bs->reachedaltroutegoal_time = 0; + return qtrue; +} + +/* +================== +BotSetupAlternateRouteGoals +================== +*/ +void BotSetupAlternativeRouteGoals(void) { + + if (altroutegoals_setup) + return; +#ifdef MISSIONPACK + if (gametype == GT_CTF) { + if (trap_BotGetLevelItemGoal(-1, "Neutral Flag", &ctf_neutralflag) < 0) + BotAI_Print(PRT_WARNING, "no alt routes without Neutral Flag\n"); + if (ctf_neutralflag.areanum) { + // + red_numaltroutegoals = trap_AAS_AlternativeRouteGoals( + ctf_neutralflag.origin, ctf_neutralflag.areanum, + ctf_redflag.origin, ctf_redflag.areanum, TFL_DEFAULT, + red_altroutegoals, MAX_ALTROUTEGOALS, + ALTROUTEGOAL_CLUSTERPORTALS| + ALTROUTEGOAL_VIEWPORTALS); + blue_numaltroutegoals = trap_AAS_AlternativeRouteGoals( + ctf_neutralflag.origin, ctf_neutralflag.areanum, + ctf_blueflag.origin, ctf_blueflag.areanum, TFL_DEFAULT, + blue_altroutegoals, MAX_ALTROUTEGOALS, + ALTROUTEGOAL_CLUSTERPORTALS| + ALTROUTEGOAL_VIEWPORTALS); + } + } + else if (gametype == GT_1FCTF) { + // + red_numaltroutegoals = trap_AAS_AlternativeRouteGoals( + ctf_neutralflag.origin, ctf_neutralflag.areanum, + ctf_redflag.origin, ctf_redflag.areanum, TFL_DEFAULT, + red_altroutegoals, MAX_ALTROUTEGOALS, + ALTROUTEGOAL_CLUSTERPORTALS| + ALTROUTEGOAL_VIEWPORTALS); + blue_numaltroutegoals = trap_AAS_AlternativeRouteGoals( + ctf_neutralflag.origin, ctf_neutralflag.areanum, + ctf_blueflag.origin, ctf_blueflag.areanum, TFL_DEFAULT, + blue_altroutegoals, MAX_ALTROUTEGOALS, + ALTROUTEGOAL_CLUSTERPORTALS| + ALTROUTEGOAL_VIEWPORTALS); + } + else if (gametype == GT_OBELISK) { + if (trap_BotGetLevelItemGoal(-1, "Neutral Obelisk", &neutralobelisk) < 0) + BotAI_Print(PRT_WARNING, "Harvester without neutral obelisk\n"); + // + red_numaltroutegoals = trap_AAS_AlternativeRouteGoals( + neutralobelisk.origin, neutralobelisk.areanum, + redobelisk.origin, redobelisk.areanum, TFL_DEFAULT, + red_altroutegoals, MAX_ALTROUTEGOALS, + ALTROUTEGOAL_CLUSTERPORTALS| + ALTROUTEGOAL_VIEWPORTALS); + blue_numaltroutegoals = trap_AAS_AlternativeRouteGoals( + neutralobelisk.origin, neutralobelisk.areanum, + blueobelisk.origin, blueobelisk.areanum, TFL_DEFAULT, + blue_altroutegoals, MAX_ALTROUTEGOALS, + ALTROUTEGOAL_CLUSTERPORTALS| + ALTROUTEGOAL_VIEWPORTALS); + } + else if (gametype == GT_HARVESTER) { + // + red_numaltroutegoals = trap_AAS_AlternativeRouteGoals( + neutralobelisk.origin, neutralobelisk.areanum, + redobelisk.origin, redobelisk.areanum, TFL_DEFAULT, + red_altroutegoals, MAX_ALTROUTEGOALS, + ALTROUTEGOAL_CLUSTERPORTALS| + ALTROUTEGOAL_VIEWPORTALS); + blue_numaltroutegoals = trap_AAS_AlternativeRouteGoals( + neutralobelisk.origin, neutralobelisk.areanum, + blueobelisk.origin, blueobelisk.areanum, TFL_DEFAULT, + blue_altroutegoals, MAX_ALTROUTEGOALS, + ALTROUTEGOAL_CLUSTERPORTALS| + ALTROUTEGOAL_VIEWPORTALS); + } +#endif + altroutegoals_setup = qtrue; +} + +/* +================== +BotDeathmatchAI +================== +*/ +void BotDeathmatchAI(bot_state_t *bs, float thinktime) { + char gender[144], name[144], buf[144]; + char userinfo[MAX_INFO_STRING]; + int i; + + //if the bot has just been setup + if (bs->setupcount > 0) { + bs->setupcount--; + if (bs->setupcount > 0) return; + //get the gender characteristic + trap_Characteristic_String(bs->character, CHARACTERISTIC_GENDER, gender, sizeof(gender)); + //set the bot gender + trap_GetUserinfo(bs->client, userinfo, sizeof(userinfo)); + Info_SetValueForKey(userinfo, "sex", gender); + trap_SetUserinfo(bs->client, userinfo); + //set the team + if ( !bs->map_restart && g_gametype.integer != GT_TOURNAMENT ) { + Com_sprintf(buf, sizeof(buf), "team %s", bs->settings.team); + trap_EA_Command(bs->client, buf); + } + //set the chat gender + if (gender[0] == 'm') trap_BotSetChatGender(bs->cs, CHAT_GENDERMALE); + else if (gender[0] == 'f') trap_BotSetChatGender(bs->cs, CHAT_GENDERFEMALE); + else trap_BotSetChatGender(bs->cs, CHAT_GENDERLESS); + //set the chat name + ClientName(bs->client, name, sizeof(name)); + trap_BotSetChatName(bs->cs, name, bs->client); + // + bs->lastframe_health = bs->inventory[INVENTORY_HEALTH]; + bs->lasthitcount = bs->cur_ps.persistant[PERS_HITS]; + // + bs->setupcount = 0; + // + BotSetupAlternativeRouteGoals(); + } + //no ideal view set + bs->flags &= ~BFL_IDEALVIEWSET; + // + if (!BotIntermission(bs)) { + //set the teleport time + BotSetTeleportTime(bs); + //update some inventory values + BotUpdateInventory(bs); + //check out the snapshot + BotCheckSnapshot(bs); + //check for air + BotCheckAir(bs); + } + //check the console messages + BotCheckConsoleMessages(bs); + //if not in the intermission and not in observer mode + if (!BotIntermission(bs) && !BotIsObserver(bs)) { + //do team AI + BotTeamAI(bs); + } + //if the bot has no ai node + if (!bs->ainode) { + AIEnter_Seek_LTG(bs, "BotDeathmatchAI: no ai node"); + } + //if the bot entered the game less than 8 seconds ago + if (!bs->entergamechat && bs->entergame_time > FloatTime() - 8) { + if (BotChat_EnterGame(bs)) { + bs->stand_time = FloatTime() + BotChatTime(bs); + AIEnter_Stand(bs, "BotDeathmatchAI: chat enter game"); + } + bs->entergamechat = qtrue; + } + //reset the node switches from the previous frame + BotResetNodeSwitches(); + //execute AI nodes + for (i = 0; i < MAX_NODESWITCHES; i++) { + if (bs->ainode(bs)) break; + } + //if the bot removed itself :) + if (!bs->inuse) return; + //if the bot executed too many AI nodes + if (i >= MAX_NODESWITCHES) { + trap_BotDumpGoalStack(bs->gs); + trap_BotDumpAvoidGoals(bs->gs); + BotDumpNodeSwitches(bs); + ClientName(bs->client, name, sizeof(name)); + BotAI_Print(PRT_ERROR, "%s at %1.1f switched more than %d AI nodes\n", name, FloatTime(), MAX_NODESWITCHES); + } + // + bs->lastframe_health = bs->inventory[INVENTORY_HEALTH]; + bs->lasthitcount = bs->cur_ps.persistant[PERS_HITS]; +} + +/* +================== +BotSetEntityNumForGoalWithModel +================== +*/ +void BotSetEntityNumForGoalWithModel(bot_goal_t *goal, int eType, char *modelname) { + gentity_t *ent; + int i, modelindex; + vec3_t dir; + + modelindex = G_ModelIndex( modelname ); + ent = &g_entities[0]; + for (i = 0; i < level.num_entities; i++, ent++) { + if ( !ent->inuse ) { + continue; + } + if ( eType && ent->s.eType != eType) { + continue; + } + if (ent->s.modelindex != modelindex) { + continue; + } + VectorSubtract(goal->origin, ent->s.origin, dir); + if (VectorLengthSquared(dir) < Square(10)) { + goal->entitynum = i; + return; + } + } +} + +/* +================== +BotSetEntityNumForGoal +================== +*/ +void BotSetEntityNumForGoal(bot_goal_t *goal, char *classname) { + gentity_t *ent; + int i; + vec3_t dir; + + ent = &g_entities[0]; + for (i = 0; i < level.num_entities; i++, ent++) { + if ( !ent->inuse ) { + continue; + } + if ( !Q_stricmp(ent->classname, classname) ) { + continue; + } + VectorSubtract(goal->origin, ent->s.origin, dir); + if (VectorLengthSquared(dir) < Square(10)) { + goal->entitynum = i; + return; + } + } +} + +/* +================== +BotGoalForBSPEntity +================== +*/ +int BotGoalForBSPEntity( char *classname, bot_goal_t *goal ) { + char value[MAX_INFO_STRING]; + vec3_t origin, start, end; + int ent, numareas, areas[10]; + + memset(goal, 0, sizeof(bot_goal_t)); + for (ent = trap_AAS_NextBSPEntity(0); ent; ent = trap_AAS_NextBSPEntity(ent)) { + if (!trap_AAS_ValueForBSPEpairKey(ent, "classname", value, sizeof(value))) + continue; + if (!strcmp(value, classname)) { + if (!trap_AAS_VectorForBSPEpairKey(ent, "origin", origin)) + return qfalse; + VectorCopy(origin, goal->origin); + VectorCopy(origin, start); + start[2] -= 32; + VectorCopy(origin, end); + end[2] += 32; + numareas = trap_AAS_TraceAreas(start, end, areas, NULL, 10); + if (!numareas) + return qfalse; + goal->areanum = areas[0]; + return qtrue; + } + } + return qfalse; +} + +/* +================== +BotSetupDeathmatchAI +================== +*/ +void BotSetupDeathmatchAI(void) { + int ent, modelnum; + char model[128]; + + gametype = trap_Cvar_VariableIntegerValue("g_gametype"); + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); + + trap_Cvar_Register(&bot_rocketjump, "bot_rocketjump", "1", 0); + trap_Cvar_Register(&bot_grapple, "bot_grapple", "0", 0); + trap_Cvar_Register(&bot_fastchat, "bot_fastchat", "0", 0); + trap_Cvar_Register(&bot_nochat, "bot_nochat", "0", 0); + trap_Cvar_Register(&bot_testrchat, "bot_testrchat", "0", 0); + trap_Cvar_Register(&bot_challenge, "bot_challenge", "0", 0); + trap_Cvar_Register(&bot_predictobstacles, "bot_predictobstacles", "1", 0); + trap_Cvar_Register(&g_spSkill, "g_spSkill", "2", 0); + // + if (gametype == GT_CTF) { + if (trap_BotGetLevelItemGoal(-1, "Red Flag", &ctf_redflag) < 0) + BotAI_Print(PRT_WARNING, "CTF without Red Flag\n"); + if (trap_BotGetLevelItemGoal(-1, "Blue Flag", &ctf_blueflag) < 0) + BotAI_Print(PRT_WARNING, "CTF without Blue Flag\n"); + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + if (trap_BotGetLevelItemGoal(-1, "Neutral Flag", &ctf_neutralflag) < 0) + BotAI_Print(PRT_WARNING, "One Flag CTF without Neutral Flag\n"); + if (trap_BotGetLevelItemGoal(-1, "Red Flag", &ctf_redflag) < 0) + BotAI_Print(PRT_WARNING, "CTF without Red Flag\n"); + if (trap_BotGetLevelItemGoal(-1, "Blue Flag", &ctf_blueflag) < 0) + BotAI_Print(PRT_WARNING, "CTF without Blue Flag\n"); + } + else if (gametype == GT_OBELISK) { + if (trap_BotGetLevelItemGoal(-1, "Red Obelisk", &redobelisk) < 0) + BotAI_Print(PRT_WARNING, "Obelisk without red obelisk\n"); + BotSetEntityNumForGoal(&redobelisk, "team_redobelisk"); + if (trap_BotGetLevelItemGoal(-1, "Blue Obelisk", &blueobelisk) < 0) + BotAI_Print(PRT_WARNING, "Obelisk without blue obelisk\n"); + BotSetEntityNumForGoal(&blueobelisk, "team_blueobelisk"); + } + else if (gametype == GT_HARVESTER) { + if (trap_BotGetLevelItemGoal(-1, "Red Obelisk", &redobelisk) < 0) + BotAI_Print(PRT_WARNING, "Harvester without red obelisk\n"); + BotSetEntityNumForGoal(&redobelisk, "team_redobelisk"); + if (trap_BotGetLevelItemGoal(-1, "Blue Obelisk", &blueobelisk) < 0) + BotAI_Print(PRT_WARNING, "Harvester without blue obelisk\n"); + BotSetEntityNumForGoal(&blueobelisk, "team_blueobelisk"); + if (trap_BotGetLevelItemGoal(-1, "Neutral Obelisk", &neutralobelisk) < 0) + BotAI_Print(PRT_WARNING, "Harvester without neutral obelisk\n"); + BotSetEntityNumForGoal(&neutralobelisk, "team_neutralobelisk"); + } +#endif + + max_bspmodelindex = 0; + for (ent = trap_AAS_NextBSPEntity(0); ent; ent = trap_AAS_NextBSPEntity(ent)) { + if (!trap_AAS_ValueForBSPEpairKey(ent, "model", model, sizeof(model))) continue; + if (model[0] == '*') { + modelnum = atoi(model+1); + if (modelnum > max_bspmodelindex) + max_bspmodelindex = modelnum; + } + } + //initialize the waypoint heap + BotInitWaypoints(); +} + +/* +================== +BotShutdownDeathmatchAI +================== +*/ +void BotShutdownDeathmatchAI(void) { + altroutegoals_setup = qfalse; +} diff --git a/code/game/ai_dmq3.h b/code/game/ai_dmq3.h index b7ab19d..a39a350 100755 --- a/code/game/ai_dmq3.h +++ b/code/game/ai_dmq3.h @@ -1,206 +1,206 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -/***************************************************************************** - * name: ai_dmq3.h - * - * desc: Quake3 bot AI - * - * $Archive: /source/code/botai/ai_chat.c $ - * - *****************************************************************************/ - -//setup the deathmatch AI -void BotSetupDeathmatchAI(void); -//shutdown the deathmatch AI -void BotShutdownDeathmatchAI(void); -//let the bot live within it's deathmatch AI net -void BotDeathmatchAI(bot_state_t *bs, float thinktime); -//free waypoints -void BotFreeWaypoints(bot_waypoint_t *wp); -//choose a weapon -void BotChooseWeapon(bot_state_t *bs); -//setup movement stuff -void BotSetupForMovement(bot_state_t *bs); -//update the inventory -void BotUpdateInventory(bot_state_t *bs); -//update the inventory during battle -void BotUpdateBattleInventory(bot_state_t *bs, int enemy); -//use holdable items during battle -void BotBattleUseItems(bot_state_t *bs); -//return true if the bot is dead -qboolean BotIsDead(bot_state_t *bs); -//returns true if the bot is in observer mode -qboolean BotIsObserver(bot_state_t *bs); -//returns true if the bot is in the intermission -qboolean BotIntermission(bot_state_t *bs); -//returns true if the bot is in lava or slime -qboolean BotInLavaOrSlime(bot_state_t *bs); -//returns true if the entity is dead -qboolean EntityIsDead(aas_entityinfo_t *entinfo); -//returns true if the entity is invisible -qboolean EntityIsInvisible(aas_entityinfo_t *entinfo); -//returns true if the entity is shooting -qboolean EntityIsShooting(aas_entityinfo_t *entinfo); -#ifdef MISSIONPACK -//returns true if this entity has the kamikaze -qboolean EntityHasKamikaze(aas_entityinfo_t *entinfo); -#endif -// set a user info key/value pair -void BotSetUserInfo(bot_state_t *bs, char *key, char *value); -// set the team status (offense, defense etc.) -void BotSetTeamStatus(bot_state_t *bs); -//returns the name of the client -char *ClientName(int client, char *name, int size); -//returns an simplyfied client name -char *EasyClientName(int client, char *name, int size); -//returns the skin used by the client -char *ClientSkin(int client, char *skin, int size); -// returns the appropriate synonym context for the current game type and situation -int BotSynonymContext(bot_state_t *bs); -// set last ordered task -int BotSetLastOrderedTask(bot_state_t *bs); -// selection of goals for teamplay -void BotTeamGoals(bot_state_t *bs, int retreat); -//returns the aggression of the bot in the range [0, 100] -float BotAggression(bot_state_t *bs); -//returns how bad the bot feels -float BotFeelingBad(bot_state_t *bs); -//returns true if the bot wants to retreat -int BotWantsToRetreat(bot_state_t *bs); -//returns true if the bot wants to chase -int BotWantsToChase(bot_state_t *bs); -//returns true if the bot wants to help -int BotWantsToHelp(bot_state_t *bs); -//returns true if the bot can and wants to rocketjump -int BotCanAndWantsToRocketJump(bot_state_t *bs); -// returns true if the bot has a persistant powerup and a weapon -int BotHasPersistantPowerupAndWeapon(bot_state_t *bs); -//returns true if the bot wants to and goes camping -int BotWantsToCamp(bot_state_t *bs); -//the bot will perform attack movements -bot_moveresult_t BotAttackMove(bot_state_t *bs, int tfl); -//returns true if the bot and the entity are in the same team -int BotSameTeam(bot_state_t *bs, int entnum); -//returns true if teamplay is on -int TeamPlayIsOn(void); -// returns the client number of the team mate flag carrier (-1 if none) -int BotTeamFlagCarrier(bot_state_t *bs); -//returns visible team mate flag carrier if available -int BotTeamFlagCarrierVisible(bot_state_t *bs); -//returns visible enemy flag carrier if available -int BotEnemyFlagCarrierVisible(bot_state_t *bs); -//get the number of visible teammates and enemies -void BotVisibleTeamMatesAndEnemies(bot_state_t *bs, int *teammates, int *enemies, float range); -//returns true if within the field of vision for the given angles -qboolean InFieldOfVision(vec3_t viewangles, float fov, vec3_t angles); -//returns true and sets the .enemy field when an enemy is found -int BotFindEnemy(bot_state_t *bs, int curenemy); -//returns a roam goal -void BotRoamGoal(bot_state_t *bs, vec3_t goal); -//returns entity visibility in the range [0, 1] -float BotEntityVisible(int viewer, vec3_t eye, vec3_t viewangles, float fov, int ent); -//the bot will aim at the current enemy -void BotAimAtEnemy(bot_state_t *bs); -//check if the bot should attack -void BotCheckAttack(bot_state_t *bs); -//AI when the bot is blocked -void BotAIBlocked(bot_state_t *bs, bot_moveresult_t *moveresult, int activate); -//AI to predict obstacles -int BotAIPredictObstacles(bot_state_t *bs, bot_goal_t *goal); -//enable or disable the areas the blocking entity is in -void BotEnableActivateGoalAreas(bot_activategoal_t *activategoal, int enable); -//pop an activate goal from the stack -int BotPopFromActivateGoalStack(bot_state_t *bs); -//clear the activate goal stack -void BotClearActivateGoalStack(bot_state_t *bs); -//returns the team the bot is in -int BotTeam(bot_state_t *bs); -//retuns the opposite team of the bot -int BotOppositeTeam(bot_state_t *bs); -//returns the flag the bot is carrying (CTFFLAG_?) -int BotCTFCarryingFlag(bot_state_t *bs); -//remember the last ordered task -void BotRememberLastOrderedTask(bot_state_t *bs); -//set ctf goals (defend base, get enemy flag) during seek -void BotCTFSeekGoals(bot_state_t *bs); -//set ctf goals (defend base, get enemy flag) during retreat -void BotCTFRetreatGoals(bot_state_t *bs); -// -#ifdef MISSIONPACK -int Bot1FCTFCarryingFlag(bot_state_t *bs); -int BotHarvesterCarryingCubes(bot_state_t *bs); -void Bot1FCTFSeekGoals(bot_state_t *bs); -void Bot1FCTFRetreatGoals(bot_state_t *bs); -void BotObeliskSeekGoals(bot_state_t *bs); -void BotObeliskRetreatGoals(bot_state_t *bs); -void BotGoHarvest(bot_state_t *bs); -void BotHarvesterSeekGoals(bot_state_t *bs); -void BotHarvesterRetreatGoals(bot_state_t *bs); -int BotTeamCubeCarrierVisible(bot_state_t *bs); -int BotEnemyCubeCarrierVisible(bot_state_t *bs); -#endif -//get a random alternate route goal towards the given base -int BotGetAlternateRouteGoal(bot_state_t *bs, int base); -//returns either the alternate route goal or the given goal -bot_goal_t *BotAlternateRoute(bot_state_t *bs, bot_goal_t *goal); -//create a new waypoint -bot_waypoint_t *BotCreateWayPoint(char *name, vec3_t origin, int areanum); -//find a waypoint with the given name -bot_waypoint_t *BotFindWayPoint(bot_waypoint_t *waypoints, char *name); -//strstr but case insensitive -char *stristr(char *str, char *charset); -//returns the number of the client with the given name -int ClientFromName(char *name); -int ClientOnSameTeamFromName(bot_state_t *bs, char *name); -// -int BotPointAreaNum(vec3_t origin); -// -void BotMapScripts(bot_state_t *bs); - -//ctf flags -#define CTF_FLAG_NONE 0 -#define CTF_FLAG_RED 1 -#define CTF_FLAG_BLUE 2 -//CTF skins -#define CTF_SKIN_REDTEAM "red" -#define CTF_SKIN_BLUETEAM "blue" - -extern int gametype; //game type -extern int maxclients; //maximum number of clients - -extern vmCvar_t bot_grapple; -extern vmCvar_t bot_rocketjump; -extern vmCvar_t bot_fastchat; -extern vmCvar_t bot_nochat; -extern vmCvar_t bot_testrchat; -extern vmCvar_t bot_challenge; - -extern bot_goal_t ctf_redflag; -extern bot_goal_t ctf_blueflag; -#ifdef MISSIONPACK -extern bot_goal_t ctf_neutralflag; -extern bot_goal_t redobelisk; -extern bot_goal_t blueobelisk; -extern bot_goal_t neutralobelisk; -#endif +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: ai_dmq3.h + * + * desc: Quake3 bot AI + * + * $Archive: /source/code/botai/ai_chat.c $ + * + *****************************************************************************/ + +//setup the deathmatch AI +void BotSetupDeathmatchAI(void); +//shutdown the deathmatch AI +void BotShutdownDeathmatchAI(void); +//let the bot live within it's deathmatch AI net +void BotDeathmatchAI(bot_state_t *bs, float thinktime); +//free waypoints +void BotFreeWaypoints(bot_waypoint_t *wp); +//choose a weapon +void BotChooseWeapon(bot_state_t *bs); +//setup movement stuff +void BotSetupForMovement(bot_state_t *bs); +//update the inventory +void BotUpdateInventory(bot_state_t *bs); +//update the inventory during battle +void BotUpdateBattleInventory(bot_state_t *bs, int enemy); +//use holdable items during battle +void BotBattleUseItems(bot_state_t *bs); +//return true if the bot is dead +qboolean BotIsDead(bot_state_t *bs); +//returns true if the bot is in observer mode +qboolean BotIsObserver(bot_state_t *bs); +//returns true if the bot is in the intermission +qboolean BotIntermission(bot_state_t *bs); +//returns true if the bot is in lava or slime +qboolean BotInLavaOrSlime(bot_state_t *bs); +//returns true if the entity is dead +qboolean EntityIsDead(aas_entityinfo_t *entinfo); +//returns true if the entity is invisible +qboolean EntityIsInvisible(aas_entityinfo_t *entinfo); +//returns true if the entity is shooting +qboolean EntityIsShooting(aas_entityinfo_t *entinfo); +#ifdef MISSIONPACK +//returns true if this entity has the kamikaze +qboolean EntityHasKamikaze(aas_entityinfo_t *entinfo); +#endif +// set a user info key/value pair +void BotSetUserInfo(bot_state_t *bs, char *key, char *value); +// set the team status (offense, defense etc.) +void BotSetTeamStatus(bot_state_t *bs); +//returns the name of the client +char *ClientName(int client, char *name, int size); +//returns an simplyfied client name +char *EasyClientName(int client, char *name, int size); +//returns the skin used by the client +char *ClientSkin(int client, char *skin, int size); +// returns the appropriate synonym context for the current game type and situation +int BotSynonymContext(bot_state_t *bs); +// set last ordered task +int BotSetLastOrderedTask(bot_state_t *bs); +// selection of goals for teamplay +void BotTeamGoals(bot_state_t *bs, int retreat); +//returns the aggression of the bot in the range [0, 100] +float BotAggression(bot_state_t *bs); +//returns how bad the bot feels +float BotFeelingBad(bot_state_t *bs); +//returns true if the bot wants to retreat +int BotWantsToRetreat(bot_state_t *bs); +//returns true if the bot wants to chase +int BotWantsToChase(bot_state_t *bs); +//returns true if the bot wants to help +int BotWantsToHelp(bot_state_t *bs); +//returns true if the bot can and wants to rocketjump +int BotCanAndWantsToRocketJump(bot_state_t *bs); +// returns true if the bot has a persistant powerup and a weapon +int BotHasPersistantPowerupAndWeapon(bot_state_t *bs); +//returns true if the bot wants to and goes camping +int BotWantsToCamp(bot_state_t *bs); +//the bot will perform attack movements +bot_moveresult_t BotAttackMove(bot_state_t *bs, int tfl); +//returns true if the bot and the entity are in the same team +int BotSameTeam(bot_state_t *bs, int entnum); +//returns true if teamplay is on +int TeamPlayIsOn(void); +// returns the client number of the team mate flag carrier (-1 if none) +int BotTeamFlagCarrier(bot_state_t *bs); +//returns visible team mate flag carrier if available +int BotTeamFlagCarrierVisible(bot_state_t *bs); +//returns visible enemy flag carrier if available +int BotEnemyFlagCarrierVisible(bot_state_t *bs); +//get the number of visible teammates and enemies +void BotVisibleTeamMatesAndEnemies(bot_state_t *bs, int *teammates, int *enemies, float range); +//returns true if within the field of vision for the given angles +qboolean InFieldOfVision(vec3_t viewangles, float fov, vec3_t angles); +//returns true and sets the .enemy field when an enemy is found +int BotFindEnemy(bot_state_t *bs, int curenemy); +//returns a roam goal +void BotRoamGoal(bot_state_t *bs, vec3_t goal); +//returns entity visibility in the range [0, 1] +float BotEntityVisible(int viewer, vec3_t eye, vec3_t viewangles, float fov, int ent); +//the bot will aim at the current enemy +void BotAimAtEnemy(bot_state_t *bs); +//check if the bot should attack +void BotCheckAttack(bot_state_t *bs); +//AI when the bot is blocked +void BotAIBlocked(bot_state_t *bs, bot_moveresult_t *moveresult, int activate); +//AI to predict obstacles +int BotAIPredictObstacles(bot_state_t *bs, bot_goal_t *goal); +//enable or disable the areas the blocking entity is in +void BotEnableActivateGoalAreas(bot_activategoal_t *activategoal, int enable); +//pop an activate goal from the stack +int BotPopFromActivateGoalStack(bot_state_t *bs); +//clear the activate goal stack +void BotClearActivateGoalStack(bot_state_t *bs); +//returns the team the bot is in +int BotTeam(bot_state_t *bs); +//retuns the opposite team of the bot +int BotOppositeTeam(bot_state_t *bs); +//returns the flag the bot is carrying (CTFFLAG_?) +int BotCTFCarryingFlag(bot_state_t *bs); +//remember the last ordered task +void BotRememberLastOrderedTask(bot_state_t *bs); +//set ctf goals (defend base, get enemy flag) during seek +void BotCTFSeekGoals(bot_state_t *bs); +//set ctf goals (defend base, get enemy flag) during retreat +void BotCTFRetreatGoals(bot_state_t *bs); +// +#ifdef MISSIONPACK +int Bot1FCTFCarryingFlag(bot_state_t *bs); +int BotHarvesterCarryingCubes(bot_state_t *bs); +void Bot1FCTFSeekGoals(bot_state_t *bs); +void Bot1FCTFRetreatGoals(bot_state_t *bs); +void BotObeliskSeekGoals(bot_state_t *bs); +void BotObeliskRetreatGoals(bot_state_t *bs); +void BotGoHarvest(bot_state_t *bs); +void BotHarvesterSeekGoals(bot_state_t *bs); +void BotHarvesterRetreatGoals(bot_state_t *bs); +int BotTeamCubeCarrierVisible(bot_state_t *bs); +int BotEnemyCubeCarrierVisible(bot_state_t *bs); +#endif +//get a random alternate route goal towards the given base +int BotGetAlternateRouteGoal(bot_state_t *bs, int base); +//returns either the alternate route goal or the given goal +bot_goal_t *BotAlternateRoute(bot_state_t *bs, bot_goal_t *goal); +//create a new waypoint +bot_waypoint_t *BotCreateWayPoint(char *name, vec3_t origin, int areanum); +//find a waypoint with the given name +bot_waypoint_t *BotFindWayPoint(bot_waypoint_t *waypoints, char *name); +//strstr but case insensitive +char *stristr(char *str, char *charset); +//returns the number of the client with the given name +int ClientFromName(char *name); +int ClientOnSameTeamFromName(bot_state_t *bs, char *name); +// +int BotPointAreaNum(vec3_t origin); +// +void BotMapScripts(bot_state_t *bs); + +//ctf flags +#define CTF_FLAG_NONE 0 +#define CTF_FLAG_RED 1 +#define CTF_FLAG_BLUE 2 +//CTF skins +#define CTF_SKIN_REDTEAM "red" +#define CTF_SKIN_BLUETEAM "blue" + +extern int gametype; //game type +extern int maxclients; //maximum number of clients + +extern vmCvar_t bot_grapple; +extern vmCvar_t bot_rocketjump; +extern vmCvar_t bot_fastchat; +extern vmCvar_t bot_nochat; +extern vmCvar_t bot_testrchat; +extern vmCvar_t bot_challenge; + +extern bot_goal_t ctf_redflag; +extern bot_goal_t ctf_blueflag; +#ifdef MISSIONPACK +extern bot_goal_t ctf_neutralflag; +extern bot_goal_t redobelisk; +extern bot_goal_t blueobelisk; +extern bot_goal_t neutralobelisk; +#endif diff --git a/code/game/ai_main.c b/code/game/ai_main.c index d13696a..cd6dbc9 100755 --- a/code/game/ai_main.c +++ b/code/game/ai_main.c @@ -1,1695 +1,1695 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -/***************************************************************************** - * name: ai_main.c - * - * desc: Quake3 bot AI - * - * $Archive: /MissionPack/code/game/ai_main.c $ - * - *****************************************************************************/ - - -#include "g_local.h" -#include "q_shared.h" -#include "botlib.h" //bot lib interface -#include "be_aas.h" -#include "be_ea.h" -#include "be_ai_char.h" -#include "be_ai_chat.h" -#include "be_ai_gen.h" -#include "be_ai_goal.h" -#include "be_ai_move.h" -#include "be_ai_weap.h" -// -#include "ai_main.h" -#include "ai_dmq3.h" -#include "ai_chat.h" -#include "ai_cmd.h" -#include "ai_dmnet.h" -#include "ai_vcmd.h" - -// -#include "chars.h" -#include "inv.h" -#include "syn.h" - -#define MAX_PATH 144 - - -//bot states -bot_state_t *botstates[MAX_CLIENTS]; -//number of bots -int numbots; -//floating point time -float floattime; -//time to do a regular update -float regularupdate_time; -// -int bot_interbreed; -int bot_interbreedmatchcount; -// -vmCvar_t bot_thinktime; -vmCvar_t bot_memorydump; -vmCvar_t bot_saveroutingcache; -vmCvar_t bot_pause; -vmCvar_t bot_report; -vmCvar_t bot_testsolid; -vmCvar_t bot_testclusters; -vmCvar_t bot_developer; -vmCvar_t bot_interbreedchar; -vmCvar_t bot_interbreedbots; -vmCvar_t bot_interbreedcycle; -vmCvar_t bot_interbreedwrite; - - -void ExitLevel( void ); - - -/* -================== -BotAI_Print -================== -*/ -void QDECL BotAI_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: { - G_Printf("%s", str); - break; - } - case PRT_WARNING: { - G_Printf( S_COLOR_YELLOW "Warning: %s", str ); - break; - } - case PRT_ERROR: { - G_Printf( S_COLOR_RED "Error: %s", str ); - break; - } - case PRT_FATAL: { - G_Printf( S_COLOR_RED "Fatal: %s", str ); - break; - } - case PRT_EXIT: { - G_Error( S_COLOR_RED "Exit: %s", str ); - break; - } - default: { - G_Printf( "unknown print type\n" ); - break; - } - } -} - - -/* -================== -BotAI_Trace -================== -*/ -void BotAI_Trace(bsp_trace_t *bsptrace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask) { - trace_t trace; - - trap_Trace(&trace, start, mins, maxs, end, passent, contentmask); - //copy the trace information - bsptrace->allsolid = trace.allsolid; - bsptrace->startsolid = 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; -} - -/* -================== -BotAI_GetClientState -================== -*/ -int BotAI_GetClientState( int clientNum, playerState_t *state ) { - gentity_t *ent; - - ent = &g_entities[clientNum]; - if ( !ent->inuse ) { - return qfalse; - } - if ( !ent->client ) { - return qfalse; - } - - memcpy( state, &ent->client->ps, sizeof(playerState_t) ); - return qtrue; -} - -/* -================== -BotAI_GetEntityState -================== -*/ -int BotAI_GetEntityState( int entityNum, entityState_t *state ) { - gentity_t *ent; - - ent = &g_entities[entityNum]; - memset( state, 0, sizeof(entityState_t) ); - if (!ent->inuse) return qfalse; - if (!ent->r.linked) return qfalse; - if (ent->r.svFlags & SVF_NOCLIENT) return qfalse; - memcpy( state, &ent->s, sizeof(entityState_t) ); - return qtrue; -} - -/* -================== -BotAI_GetSnapshotEntity -================== -*/ -int BotAI_GetSnapshotEntity( int clientNum, int sequence, entityState_t *state ) { - int entNum; - - entNum = trap_BotGetSnapshotEntity( clientNum, sequence ); - if ( entNum == -1 ) { - memset(state, 0, sizeof(entityState_t)); - return -1; - } - - BotAI_GetEntityState( entNum, state ); - - return sequence + 1; -} - -/* -================== -BotAI_BotInitialChat -================== -*/ -void QDECL BotAI_BotInitialChat( bot_state_t *bs, char *type, ... ) { - int i, mcontext; - va_list ap; - char *p; - char *vars[MAX_MATCHVARIABLES]; - - memset(vars, 0, sizeof(vars)); - va_start(ap, type); - p = va_arg(ap, char *); - for (i = 0; i < MAX_MATCHVARIABLES; i++) { - if( !p ) { - break; - } - vars[i] = p; - p = va_arg(ap, char *); - } - va_end(ap); - - mcontext = BotSynonymContext(bs); - - trap_BotInitialChat( bs->cs, type, mcontext, vars[0], vars[1], vars[2], vars[3], vars[4], vars[5], vars[6], vars[7] ); -} - - -/* -================== -BotTestAAS -================== -*/ -void BotTestAAS(vec3_t origin) { - int areanum; - aas_areainfo_t info; - - trap_Cvar_Update(&bot_testsolid); - trap_Cvar_Update(&bot_testclusters); - if (bot_testsolid.integer) { - if (!trap_AAS_Initialized()) return; - areanum = BotPointAreaNum(origin); - if (areanum) BotAI_Print(PRT_MESSAGE, "\remtpy area"); - else BotAI_Print(PRT_MESSAGE, "\r^1SOLID area"); - } - else if (bot_testclusters.integer) { - if (!trap_AAS_Initialized()) return; - areanum = BotPointAreaNum(origin); - if (!areanum) - BotAI_Print(PRT_MESSAGE, "\r^1Solid! "); - else { - trap_AAS_AreaInfo(areanum, &info); - BotAI_Print(PRT_MESSAGE, "\rarea %d, cluster %d ", areanum, info.cluster); - } - } -} - -/* -================== -BotReportStatus -================== -*/ -void BotReportStatus(bot_state_t *bs) { - char goalname[MAX_MESSAGE_SIZE]; - char netname[MAX_MESSAGE_SIZE]; - char *leader, flagstatus[32]; - // - ClientName(bs->client, netname, sizeof(netname)); - if (Q_stricmp(netname, bs->teamleader) == 0) leader = "L"; - else leader = " "; - - strcpy(flagstatus, " "); - if (gametype == GT_CTF) { - if (BotCTFCarryingFlag(bs)) { - if (BotTeam(bs) == TEAM_RED) strcpy(flagstatus, S_COLOR_RED"F "); - else strcpy(flagstatus, S_COLOR_BLUE"F "); - } - } -#ifdef MISSIONPACK - else if (gametype == GT_1FCTF) { - if (Bot1FCTFCarryingFlag(bs)) { - if (BotTeam(bs) == TEAM_RED) strcpy(flagstatus, S_COLOR_RED"F "); - else strcpy(flagstatus, S_COLOR_BLUE"F "); - } - } - else if (gametype == GT_HARVESTER) { - if (BotHarvesterCarryingCubes(bs)) { - if (BotTeam(bs) == TEAM_RED) Com_sprintf(flagstatus, sizeof(flagstatus), S_COLOR_RED"%2d", bs->inventory[INVENTORY_REDCUBE]); - else Com_sprintf(flagstatus, sizeof(flagstatus), S_COLOR_BLUE"%2d", bs->inventory[INVENTORY_BLUECUBE]); - } - } -#endif - - switch(bs->ltgtype) { - case LTG_TEAMHELP: - { - EasyClientName(bs->teammate, goalname, sizeof(goalname)); - BotAI_Print(PRT_MESSAGE, "%-20s%s%s: helping %s\n", netname, leader, flagstatus, goalname); - break; - } - case LTG_TEAMACCOMPANY: - { - EasyClientName(bs->teammate, goalname, sizeof(goalname)); - BotAI_Print(PRT_MESSAGE, "%-20s%s%s: accompanying %s\n", netname, leader, flagstatus, goalname); - break; - } - case LTG_DEFENDKEYAREA: - { - trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); - BotAI_Print(PRT_MESSAGE, "%-20s%s%s: defending %s\n", netname, leader, flagstatus, goalname); - break; - } - case LTG_GETITEM: - { - trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); - BotAI_Print(PRT_MESSAGE, "%-20s%s%s: getting item %s\n", netname, leader, flagstatus, goalname); - break; - } - case LTG_KILL: - { - ClientName(bs->teamgoal.entitynum, goalname, sizeof(goalname)); - BotAI_Print(PRT_MESSAGE, "%-20s%s%s: killing %s\n", netname, leader, flagstatus, goalname); - break; - } - case LTG_CAMP: - case LTG_CAMPORDER: - { - BotAI_Print(PRT_MESSAGE, "%-20s%s%s: camping\n", netname, leader, flagstatus); - break; - } - case LTG_PATROL: - { - BotAI_Print(PRT_MESSAGE, "%-20s%s%s: patrolling\n", netname, leader, flagstatus); - break; - } - case LTG_GETFLAG: - { - BotAI_Print(PRT_MESSAGE, "%-20s%s%s: capturing flag\n", netname, leader, flagstatus); - break; - } - case LTG_RUSHBASE: - { - BotAI_Print(PRT_MESSAGE, "%-20s%s%s: rushing base\n", netname, leader, flagstatus); - break; - } - case LTG_RETURNFLAG: - { - BotAI_Print(PRT_MESSAGE, "%-20s%s%s: returning flag\n", netname, leader, flagstatus); - break; - } - case LTG_ATTACKENEMYBASE: - { - BotAI_Print(PRT_MESSAGE, "%-20s%s%s: attacking the enemy base\n", netname, leader, flagstatus); - break; - } - case LTG_HARVEST: - { - BotAI_Print(PRT_MESSAGE, "%-20s%s%s: harvesting\n", netname, leader, flagstatus); - break; - } - default: - { - BotAI_Print(PRT_MESSAGE, "%-20s%s%s: roaming\n", netname, leader, flagstatus); - break; - } - } -} - -/* -================== -BotTeamplayReport -================== -*/ -void BotTeamplayReport(void) { - int i; - char buf[MAX_INFO_STRING]; - - BotAI_Print(PRT_MESSAGE, S_COLOR_RED"RED\n"); - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - // - if ( !botstates[i] || !botstates[i]->inuse ) continue; - // - trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); - //if no config string or no name - if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; - //skip spectators - if (atoi(Info_ValueForKey(buf, "t")) == TEAM_RED) { - BotReportStatus(botstates[i]); - } - } - BotAI_Print(PRT_MESSAGE, S_COLOR_BLUE"BLUE\n"); - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - // - if ( !botstates[i] || !botstates[i]->inuse ) continue; - // - trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); - //if no config string or no name - if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; - //skip spectators - if (atoi(Info_ValueForKey(buf, "t")) == TEAM_BLUE) { - BotReportStatus(botstates[i]); - } - } -} - -/* -================== -BotSetInfoConfigString -================== -*/ -void BotSetInfoConfigString(bot_state_t *bs) { - char goalname[MAX_MESSAGE_SIZE]; - char netname[MAX_MESSAGE_SIZE]; - char action[MAX_MESSAGE_SIZE]; - char *leader, carrying[32], *cs; - bot_goal_t goal; - // - ClientName(bs->client, netname, sizeof(netname)); - if (Q_stricmp(netname, bs->teamleader) == 0) leader = "L"; - else leader = " "; - - strcpy(carrying, " "); - if (gametype == GT_CTF) { - if (BotCTFCarryingFlag(bs)) { - strcpy(carrying, "F "); - } - } -#ifdef MISSIONPACK - else if (gametype == GT_1FCTF) { - if (Bot1FCTFCarryingFlag(bs)) { - strcpy(carrying, "F "); - } - } - else if (gametype == GT_HARVESTER) { - if (BotHarvesterCarryingCubes(bs)) { - if (BotTeam(bs) == TEAM_RED) Com_sprintf(carrying, sizeof(carrying), "%2d", bs->inventory[INVENTORY_REDCUBE]); - else Com_sprintf(carrying, sizeof(carrying), "%2d", bs->inventory[INVENTORY_BLUECUBE]); - } - } -#endif - - switch(bs->ltgtype) { - case LTG_TEAMHELP: - { - EasyClientName(bs->teammate, goalname, sizeof(goalname)); - Com_sprintf(action, sizeof(action), "helping %s", goalname); - break; - } - case LTG_TEAMACCOMPANY: - { - EasyClientName(bs->teammate, goalname, sizeof(goalname)); - Com_sprintf(action, sizeof(action), "accompanying %s", goalname); - break; - } - case LTG_DEFENDKEYAREA: - { - trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); - Com_sprintf(action, sizeof(action), "defending %s", goalname); - break; - } - case LTG_GETITEM: - { - trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); - Com_sprintf(action, sizeof(action), "getting item %s", goalname); - break; - } - case LTG_KILL: - { - ClientName(bs->teamgoal.entitynum, goalname, sizeof(goalname)); - Com_sprintf(action, sizeof(action), "killing %s", goalname); - break; - } - case LTG_CAMP: - case LTG_CAMPORDER: - { - Com_sprintf(action, sizeof(action), "camping"); - break; - } - case LTG_PATROL: - { - Com_sprintf(action, sizeof(action), "patrolling"); - break; - } - case LTG_GETFLAG: - { - Com_sprintf(action, sizeof(action), "capturing flag"); - break; - } - case LTG_RUSHBASE: - { - Com_sprintf(action, sizeof(action), "rushing base"); - break; - } - case LTG_RETURNFLAG: - { - Com_sprintf(action, sizeof(action), "returning flag"); - break; - } - case LTG_ATTACKENEMYBASE: - { - Com_sprintf(action, sizeof(action), "attacking the enemy base"); - break; - } - case LTG_HARVEST: - { - Com_sprintf(action, sizeof(action), "harvesting"); - break; - } - default: - { - trap_BotGetTopGoal(bs->gs, &goal); - trap_BotGoalName(goal.number, goalname, sizeof(goalname)); - Com_sprintf(action, sizeof(action), "roaming %s", goalname); - break; - } - } - cs = va("l\\%s\\c\\%s\\a\\%s", - leader, - carrying, - action); - trap_SetConfigstring (CS_BOTINFO + bs->client, cs); -} - -/* -============== -BotUpdateInfoConfigStrings -============== -*/ -void BotUpdateInfoConfigStrings(void) { - int i; - char buf[MAX_INFO_STRING]; - - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - // - if ( !botstates[i] || !botstates[i]->inuse ) - continue; - // - trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); - //if no config string or no name - if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) - continue; - BotSetInfoConfigString(botstates[i]); - } -} - -/* -============== -BotInterbreedBots -============== -*/ -void BotInterbreedBots(void) { - float ranks[MAX_CLIENTS]; - int parent1, parent2, child; - int i; - - // get rankings for all the bots - for (i = 0; i < MAX_CLIENTS; i++) { - if ( botstates[i] && botstates[i]->inuse ) { - ranks[i] = botstates[i]->num_kills * 2 - botstates[i]->num_deaths; - } - else { - ranks[i] = -1; - } - } - - if (trap_GeneticParentsAndChildSelection(MAX_CLIENTS, ranks, &parent1, &parent2, &child)) { - trap_BotInterbreedGoalFuzzyLogic(botstates[parent1]->gs, botstates[parent2]->gs, botstates[child]->gs); - trap_BotMutateGoalFuzzyLogic(botstates[child]->gs, 1); - } - // reset the kills and deaths - for (i = 0; i < MAX_CLIENTS; i++) { - if (botstates[i] && botstates[i]->inuse) { - botstates[i]->num_kills = 0; - botstates[i]->num_deaths = 0; - } - } -} - -/* -============== -BotWriteInterbreeded -============== -*/ -void BotWriteInterbreeded(char *filename) { - float rank, bestrank; - int i, bestbot; - - bestrank = 0; - bestbot = -1; - // get the best bot - for (i = 0; i < MAX_CLIENTS; i++) { - if ( botstates[i] && botstates[i]->inuse ) { - rank = botstates[i]->num_kills * 2 - botstates[i]->num_deaths; - } - else { - rank = -1; - } - if (rank > bestrank) { - bestrank = rank; - bestbot = i; - } - } - if (bestbot >= 0) { - //write out the new goal fuzzy logic - trap_BotSaveGoalFuzzyLogic(botstates[bestbot]->gs, filename); - } -} - -/* -============== -BotInterbreedEndMatch - -add link back into ExitLevel? -============== -*/ -void BotInterbreedEndMatch(void) { - - if (!bot_interbreed) return; - bot_interbreedmatchcount++; - if (bot_interbreedmatchcount >= bot_interbreedcycle.integer) { - bot_interbreedmatchcount = 0; - // - trap_Cvar_Update(&bot_interbreedwrite); - if (strlen(bot_interbreedwrite.string)) { - BotWriteInterbreeded(bot_interbreedwrite.string); - trap_Cvar_Set("bot_interbreedwrite", ""); - } - BotInterbreedBots(); - } -} - -/* -============== -BotInterbreeding -============== -*/ -void BotInterbreeding(void) { - int i; - - trap_Cvar_Update(&bot_interbreedchar); - if (!strlen(bot_interbreedchar.string)) return; - //make sure we are in tournament mode - if (gametype != GT_TOURNAMENT) { - trap_Cvar_Set("g_gametype", va("%d", GT_TOURNAMENT)); - ExitLevel(); - return; - } - //shutdown all the bots - for (i = 0; i < MAX_CLIENTS; i++) { - if (botstates[i] && botstates[i]->inuse) { - BotAIShutdownClient(botstates[i]->client, qfalse); - } - } - //make sure all item weight configs are reloaded and Not shared - trap_BotLibVarSet("bot_reloadcharacters", "1"); - //add a number of bots using the desired bot character - for (i = 0; i < bot_interbreedbots.integer; i++) { - trap_SendConsoleCommand( EXEC_INSERT, va("addbot %s 4 free %i %s%d\n", - bot_interbreedchar.string, i * 50, bot_interbreedchar.string, i) ); - } - // - trap_Cvar_Set("bot_interbreedchar", ""); - bot_interbreed = qtrue; -} - -/* -============== -BotEntityInfo -============== -*/ -void BotEntityInfo(int entnum, aas_entityinfo_t *info) { - trap_AAS_EntityInfo(entnum, info); -} - -/* -============== -NumBots -============== -*/ -int NumBots(void) { - return numbots; -} - -/* -============== -BotTeamLeader -============== -*/ -int BotTeamLeader(bot_state_t *bs) { - int leader; - - leader = ClientFromName(bs->teamleader); - if (leader < 0) return qfalse; - if (!botstates[leader] || !botstates[leader]->inuse) return qfalse; - return qtrue; -} - -/* -============== -AngleDifference -============== -*/ -float AngleDifference(float ang1, float ang2) { - float diff; - - diff = ang1 - ang2; - if (ang1 > ang2) { - if (diff > 180.0) diff -= 360.0; - } - else { - if (diff < -180.0) diff += 360.0; - } - return diff; -} - -/* -============== -BotChangeViewAngle -============== -*/ -float BotChangeViewAngle(float angle, float ideal_angle, float speed) { - float move; - - angle = AngleMod(angle); - ideal_angle = AngleMod(ideal_angle); - if (angle == ideal_angle) return angle; - move = ideal_angle - angle; - if (ideal_angle > angle) { - if (move > 180.0) move -= 360.0; - } - else { - if (move < -180.0) move += 360.0; - } - if (move > 0) { - if (move > speed) move = speed; - } - else { - if (move < -speed) move = -speed; - } - return AngleMod(angle + move); -} - -/* -============== -BotChangeViewAngles -============== -*/ -void BotChangeViewAngles(bot_state_t *bs, float thinktime) { - float diff, factor, maxchange, anglespeed, disired_speed; - int i; - - if (bs->ideal_viewangles[PITCH] > 180) bs->ideal_viewangles[PITCH] -= 360; - // - if (bs->enemy >= 0) { - factor = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_VIEW_FACTOR, 0.01f, 1); - maxchange = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_VIEW_MAXCHANGE, 1, 1800); - } - else { - factor = 0.05f; - maxchange = 360; - } - if (maxchange < 240) maxchange = 240; - maxchange *= thinktime; - for (i = 0; i < 2; i++) { - // - if (bot_challenge.integer) { - //smooth slowdown view model - diff = abs(AngleDifference(bs->viewangles[i], bs->ideal_viewangles[i])); - anglespeed = diff * factor; - if (anglespeed > maxchange) anglespeed = maxchange; - bs->viewangles[i] = BotChangeViewAngle(bs->viewangles[i], - bs->ideal_viewangles[i], anglespeed); - } - else { - //over reaction view model - bs->viewangles[i] = AngleMod(bs->viewangles[i]); - bs->ideal_viewangles[i] = AngleMod(bs->ideal_viewangles[i]); - diff = AngleDifference(bs->viewangles[i], bs->ideal_viewangles[i]); - disired_speed = diff * factor; - bs->viewanglespeed[i] += (bs->viewanglespeed[i] - disired_speed); - if (bs->viewanglespeed[i] > 180) bs->viewanglespeed[i] = maxchange; - if (bs->viewanglespeed[i] < -180) bs->viewanglespeed[i] = -maxchange; - anglespeed = bs->viewanglespeed[i]; - if (anglespeed > maxchange) anglespeed = maxchange; - if (anglespeed < -maxchange) anglespeed = -maxchange; - bs->viewangles[i] += anglespeed; - bs->viewangles[i] = AngleMod(bs->viewangles[i]); - //demping - bs->viewanglespeed[i] *= 0.45 * (1 - factor); - } - //BotAI_Print(PRT_MESSAGE, "ideal_angles %f %f\n", bs->ideal_viewangles[0], bs->ideal_viewangles[1], bs->ideal_viewangles[2]);` - //bs->viewangles[i] = bs->ideal_viewangles[i]; - } - //bs->viewangles[PITCH] = 0; - if (bs->viewangles[PITCH] > 180) bs->viewangles[PITCH] -= 360; - //elementary action: view - trap_EA_View(bs->client, bs->viewangles); -} - -/* -============== -BotInputToUserCommand -============== -*/ -void BotInputToUserCommand(bot_input_t *bi, usercmd_t *ucmd, int delta_angles[3], int time) { - vec3_t angles, forward, right; - short temp; - int j; - - //clear the whole structure - memset(ucmd, 0, sizeof(usercmd_t)); - // - //Com_Printf("dir = %f %f %f speed = %f\n", bi->dir[0], bi->dir[1], bi->dir[2], bi->speed); - //the duration for the user command in milli seconds - ucmd->serverTime = time; - // - if (bi->actionflags & ACTION_DELAYEDJUMP) { - bi->actionflags |= ACTION_JUMP; - bi->actionflags &= ~ACTION_DELAYEDJUMP; - } - //set the buttons - if (bi->actionflags & ACTION_RESPAWN) ucmd->buttons = BUTTON_ATTACK; - if (bi->actionflags & ACTION_ATTACK) ucmd->buttons |= BUTTON_ATTACK; - if (bi->actionflags & ACTION_TALK) ucmd->buttons |= BUTTON_TALK; - if (bi->actionflags & ACTION_GESTURE) ucmd->buttons |= BUTTON_GESTURE; - if (bi->actionflags & ACTION_USE) ucmd->buttons |= BUTTON_USE_HOLDABLE; - if (bi->actionflags & ACTION_WALK) ucmd->buttons |= BUTTON_WALKING; - if (bi->actionflags & ACTION_AFFIRMATIVE) ucmd->buttons |= BUTTON_AFFIRMATIVE; - if (bi->actionflags & ACTION_NEGATIVE) ucmd->buttons |= BUTTON_NEGATIVE; - if (bi->actionflags & ACTION_GETFLAG) ucmd->buttons |= BUTTON_GETFLAG; - if (bi->actionflags & ACTION_GUARDBASE) ucmd->buttons |= BUTTON_GUARDBASE; - if (bi->actionflags & ACTION_PATROL) ucmd->buttons |= BUTTON_PATROL; - if (bi->actionflags & ACTION_FOLLOWME) ucmd->buttons |= BUTTON_FOLLOWME; - // - ucmd->weapon = bi->weapon; - //set the view angles - //NOTE: the ucmd->angles are the angles WITHOUT the delta angles - ucmd->angles[PITCH] = ANGLE2SHORT(bi->viewangles[PITCH]); - ucmd->angles[YAW] = ANGLE2SHORT(bi->viewangles[YAW]); - ucmd->angles[ROLL] = ANGLE2SHORT(bi->viewangles[ROLL]); - //subtract the delta angles - for (j = 0; j < 3; j++) { - temp = ucmd->angles[j] - delta_angles[j]; - /*NOTE: disabled because temp should be mod first - if ( j == PITCH ) { - // don't let the player look up or down more than 90 degrees - if ( temp > 16000 ) temp = 16000; - else if ( temp < -16000 ) temp = -16000; - } - */ - ucmd->angles[j] = temp; - } - //NOTE: movement is relative to the REAL view angles - //get the horizontal forward and right vector - //get the pitch in the range [-180, 180] - if (bi->dir[2]) angles[PITCH] = bi->viewangles[PITCH]; - else angles[PITCH] = 0; - angles[YAW] = bi->viewangles[YAW]; - angles[ROLL] = 0; - AngleVectors(angles, forward, right, NULL); - //bot input speed is in the range [0, 400] - bi->speed = bi->speed * 127 / 400; - //set the view independent movement - ucmd->forwardmove = DotProduct(forward, bi->dir) * bi->speed; - ucmd->rightmove = DotProduct(right, bi->dir) * bi->speed; - ucmd->upmove = abs(forward[2]) * bi->dir[2] * bi->speed; - //normal keyboard movement - if (bi->actionflags & ACTION_MOVEFORWARD) ucmd->forwardmove += 127; - if (bi->actionflags & ACTION_MOVEBACK) ucmd->forwardmove -= 127; - if (bi->actionflags & ACTION_MOVELEFT) ucmd->rightmove -= 127; - if (bi->actionflags & ACTION_MOVERIGHT) ucmd->rightmove += 127; - //jump/moveup - if (bi->actionflags & ACTION_JUMP) ucmd->upmove += 127; - //crouch/movedown - if (bi->actionflags & ACTION_CROUCH) ucmd->upmove -= 127; - // - //Com_Printf("forward = %d right = %d up = %d\n", ucmd.forwardmove, ucmd.rightmove, ucmd.upmove); - //Com_Printf("ucmd->serverTime = %d\n", ucmd->serverTime); -} - -/* -============== -BotUpdateInput -============== -*/ -void BotUpdateInput(bot_state_t *bs, int time, int elapsed_time) { - bot_input_t bi; - int j; - - //add the delta angles to the bot's current view angles - for (j = 0; j < 3; j++) { - bs->viewangles[j] = AngleMod(bs->viewangles[j] + SHORT2ANGLE(bs->cur_ps.delta_angles[j])); - } - //change the bot view angles - BotChangeViewAngles(bs, (float) elapsed_time / 1000); - //retrieve the bot input - trap_EA_GetInput(bs->client, (float) time / 1000, &bi); - //respawn hack - if (bi.actionflags & ACTION_RESPAWN) { - if (bs->lastucmd.buttons & BUTTON_ATTACK) bi.actionflags &= ~(ACTION_RESPAWN|ACTION_ATTACK); - } - //convert the bot input to a usercmd - BotInputToUserCommand(&bi, &bs->lastucmd, bs->cur_ps.delta_angles, time); - //subtract the delta angles - for (j = 0; j < 3; j++) { - bs->viewangles[j] = AngleMod(bs->viewangles[j] - SHORT2ANGLE(bs->cur_ps.delta_angles[j])); - } -} - -/* -============== -BotAIRegularUpdate -============== -*/ -void BotAIRegularUpdate(void) { - if (regularupdate_time < FloatTime()) { - trap_BotUpdateEntityItems(); - regularupdate_time = FloatTime() + 0.3; - } -} - -/* -============== -RemoveColorEscapeSequences -============== -*/ -void RemoveColorEscapeSequences( char *text ) { - int i, l; - - l = 0; - for ( i = 0; text[i]; i++ ) { - if (Q_IsColorString(&text[i])) { - i++; - continue; - } - if (text[i] > 0x7E) - continue; - text[l++] = text[i]; - } - text[l] = '\0'; -} - -/* -============== -BotAI -============== -*/ -int BotAI(int client, float thinktime) { - bot_state_t *bs; - char buf[1024], *args; - int j; - - trap_EA_ResetInput(client); - // - bs = botstates[client]; - if (!bs || !bs->inuse) { - BotAI_Print(PRT_FATAL, "BotAI: client %d is not setup\n", client); - return qfalse; - } - - //retrieve the current client state - BotAI_GetClientState( client, &bs->cur_ps ); - - //retrieve any waiting server commands - while( trap_BotGetServerCommand(client, buf, sizeof(buf)) ) { - //have buf point to the command and args to the command arguments - args = strchr( buf, ' '); - if (!args) continue; - *args++ = '\0'; - - //remove color espace sequences from the arguments - RemoveColorEscapeSequences( args ); - - if (!Q_stricmp(buf, "cp ")) - { /*CenterPrintf*/ } - else if (!Q_stricmp(buf, "cs")) - { /*ConfigStringModified*/ } - else if (!Q_stricmp(buf, "print")) { - //remove first and last quote from the chat message - memmove(args, args+1, strlen(args)); - args[strlen(args)-1] = '\0'; - trap_BotQueueConsoleMessage(bs->cs, CMS_NORMAL, args); - } - else if (!Q_stricmp(buf, "chat")) { - //remove first and last quote from the chat message - memmove(args, args+1, strlen(args)); - args[strlen(args)-1] = '\0'; - trap_BotQueueConsoleMessage(bs->cs, CMS_CHAT, args); - } - else if (!Q_stricmp(buf, "tchat")) { - //remove first and last quote from the chat message - memmove(args, args+1, strlen(args)); - args[strlen(args)-1] = '\0'; - trap_BotQueueConsoleMessage(bs->cs, CMS_CHAT, args); - } -#ifdef MISSIONPACK - else if (!Q_stricmp(buf, "vchat")) { - BotVoiceChatCommand(bs, SAY_ALL, args); - } - else if (!Q_stricmp(buf, "vtchat")) { - BotVoiceChatCommand(bs, SAY_TEAM, args); - } - else if (!Q_stricmp(buf, "vtell")) { - BotVoiceChatCommand(bs, SAY_TELL, args); - } -#endif - else if (!Q_stricmp(buf, "scores")) - { /*FIXME: parse scores?*/ } - else if (!Q_stricmp(buf, "clientLevelShot")) - { /*ignore*/ } - } - //add the delta angles to the bot's current view angles - for (j = 0; j < 3; j++) { - bs->viewangles[j] = AngleMod(bs->viewangles[j] + SHORT2ANGLE(bs->cur_ps.delta_angles[j])); - } - //increase the local time of the bot - bs->ltime += thinktime; - // - bs->thinktime = thinktime; - //origin of the bot - VectorCopy(bs->cur_ps.origin, bs->origin); - //eye coordinates of the bot - VectorCopy(bs->cur_ps.origin, bs->eye); - bs->eye[2] += bs->cur_ps.viewheight; - //get the area the bot is in - bs->areanum = BotPointAreaNum(bs->origin); - //the real AI - BotDeathmatchAI(bs, thinktime); - //set the weapon selection every AI frame - trap_EA_SelectWeapon(bs->client, bs->weaponnum); - //subtract the delta angles - for (j = 0; j < 3; j++) { - bs->viewangles[j] = AngleMod(bs->viewangles[j] - SHORT2ANGLE(bs->cur_ps.delta_angles[j])); - } - //everything was ok - return qtrue; -} - -/* -================== -BotScheduleBotThink -================== -*/ -void BotScheduleBotThink(void) { - int i, botnum; - - botnum = 0; - - for( i = 0; i < MAX_CLIENTS; i++ ) { - if( !botstates[i] || !botstates[i]->inuse ) { - continue; - } - //initialize the bot think residual time - botstates[i]->botthink_residual = bot_thinktime.integer * botnum / numbots; - botnum++; - } -} - -/* -============== -BotWriteSessionData -============== -*/ -void BotWriteSessionData(bot_state_t *bs) { - const char *s; - const char *var; - - s = va( - "%i %i %i %i %i %i %i %i" - " %f %f %f" - " %f %f %f" - " %f %f %f", - bs->lastgoal_decisionmaker, - bs->lastgoal_ltgtype, - bs->lastgoal_teammate, - bs->lastgoal_teamgoal.areanum, - bs->lastgoal_teamgoal.entitynum, - bs->lastgoal_teamgoal.flags, - bs->lastgoal_teamgoal.iteminfo, - bs->lastgoal_teamgoal.number, - bs->lastgoal_teamgoal.origin[0], - bs->lastgoal_teamgoal.origin[1], - bs->lastgoal_teamgoal.origin[2], - bs->lastgoal_teamgoal.mins[0], - bs->lastgoal_teamgoal.mins[1], - bs->lastgoal_teamgoal.mins[2], - bs->lastgoal_teamgoal.maxs[0], - bs->lastgoal_teamgoal.maxs[1], - bs->lastgoal_teamgoal.maxs[2] - ); - - var = va( "botsession%i", bs->client ); - - trap_Cvar_Set( var, s ); -} - -/* -============== -BotReadSessionData -============== -*/ -void BotReadSessionData(bot_state_t *bs) { - char s[MAX_STRING_CHARS]; - const char *var; - - var = va( "botsession%i", bs->client ); - trap_Cvar_VariableStringBuffer( var, s, sizeof(s) ); - - sscanf(s, - "%i %i %i %i %i %i %i %i" - " %f %f %f" - " %f %f %f" - " %f %f %f", - &bs->lastgoal_decisionmaker, - &bs->lastgoal_ltgtype, - &bs->lastgoal_teammate, - &bs->lastgoal_teamgoal.areanum, - &bs->lastgoal_teamgoal.entitynum, - &bs->lastgoal_teamgoal.flags, - &bs->lastgoal_teamgoal.iteminfo, - &bs->lastgoal_teamgoal.number, - &bs->lastgoal_teamgoal.origin[0], - &bs->lastgoal_teamgoal.origin[1], - &bs->lastgoal_teamgoal.origin[2], - &bs->lastgoal_teamgoal.mins[0], - &bs->lastgoal_teamgoal.mins[1], - &bs->lastgoal_teamgoal.mins[2], - &bs->lastgoal_teamgoal.maxs[0], - &bs->lastgoal_teamgoal.maxs[1], - &bs->lastgoal_teamgoal.maxs[2] - ); -} - -/* -============== -BotAISetupClient -============== -*/ -int BotAISetupClient(int client, struct bot_settings_s *settings, qboolean restart) { - char filename[MAX_PATH], name[MAX_PATH], gender[MAX_PATH]; - bot_state_t *bs; - int errnum; - - if (!botstates[client]) botstates[client] = G_Alloc(sizeof(bot_state_t)); - bs = botstates[client]; - - if (bs && bs->inuse) { - BotAI_Print(PRT_FATAL, "BotAISetupClient: client %d already setup\n", client); - return qfalse; - } - - if (!trap_AAS_Initialized()) { - BotAI_Print(PRT_FATAL, "AAS not initialized\n"); - return qfalse; - } - - //load the bot character - bs->character = trap_BotLoadCharacter(settings->characterfile, settings->skill); - if (!bs->character) { - BotAI_Print(PRT_FATAL, "couldn't load skill %f from %s\n", settings->skill, settings->characterfile); - return qfalse; - } - //copy the settings - memcpy(&bs->settings, settings, sizeof(bot_settings_t)); - //allocate a goal state - bs->gs = trap_BotAllocGoalState(client); - //load the item weights - trap_Characteristic_String(bs->character, CHARACTERISTIC_ITEMWEIGHTS, filename, MAX_PATH); - errnum = trap_BotLoadItemWeights(bs->gs, filename); - if (errnum != BLERR_NOERROR) { - trap_BotFreeGoalState(bs->gs); - return qfalse; - } - //allocate a weapon state - bs->ws = trap_BotAllocWeaponState(); - //load the weapon weights - trap_Characteristic_String(bs->character, CHARACTERISTIC_WEAPONWEIGHTS, filename, MAX_PATH); - errnum = trap_BotLoadWeaponWeights(bs->ws, filename); - if (errnum != BLERR_NOERROR) { - trap_BotFreeGoalState(bs->gs); - trap_BotFreeWeaponState(bs->ws); - return qfalse; - } - //allocate a chat state - bs->cs = trap_BotAllocChatState(); - //load the chat file - trap_Characteristic_String(bs->character, CHARACTERISTIC_CHAT_FILE, filename, MAX_PATH); - trap_Characteristic_String(bs->character, CHARACTERISTIC_CHAT_NAME, name, MAX_PATH); - errnum = trap_BotLoadChatFile(bs->cs, filename, name); - if (errnum != BLERR_NOERROR) { - trap_BotFreeChatState(bs->cs); - trap_BotFreeGoalState(bs->gs); - trap_BotFreeWeaponState(bs->ws); - return qfalse; - } - //get the gender characteristic - trap_Characteristic_String(bs->character, CHARACTERISTIC_GENDER, gender, MAX_PATH); - //set the chat gender - if (*gender == 'f' || *gender == 'F') trap_BotSetChatGender(bs->cs, CHAT_GENDERFEMALE); - else if (*gender == 'm' || *gender == 'M') trap_BotSetChatGender(bs->cs, CHAT_GENDERMALE); - else trap_BotSetChatGender(bs->cs, CHAT_GENDERLESS); - - bs->inuse = qtrue; - bs->client = client; - bs->entitynum = client; - bs->setupcount = 4; - bs->entergame_time = FloatTime(); - bs->ms = trap_BotAllocMoveState(); - bs->walker = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_WALKER, 0, 1); - numbots++; - - if (trap_Cvar_VariableIntegerValue("bot_testichat")) { - trap_BotLibVarSet("bot_testichat", "1"); - BotChatTest(bs); - } - //NOTE: reschedule the bot thinking - BotScheduleBotThink(); - //if interbreeding start with a mutation - if (bot_interbreed) { - trap_BotMutateGoalFuzzyLogic(bs->gs, 1); - } - // if we kept the bot client - if (restart) { - BotReadSessionData(bs); - } - //bot has been setup succesfully - return qtrue; -} - -/* -============== -BotAIShutdownClient -============== -*/ -int BotAIShutdownClient(int client, qboolean restart) { - bot_state_t *bs; - - bs = botstates[client]; - if (!bs || !bs->inuse) { - //BotAI_Print(PRT_ERROR, "BotAIShutdownClient: client %d already shutdown\n", client); - return qfalse; - } - - if (restart) { - BotWriteSessionData(bs); - } - - if (BotChat_ExitGame(bs)) { - trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL); - } - - trap_BotFreeMoveState(bs->ms); - //free the goal state` - trap_BotFreeGoalState(bs->gs); - //free the chat file - trap_BotFreeChatState(bs->cs); - //free the weapon weights - trap_BotFreeWeaponState(bs->ws); - //free the bot character - trap_BotFreeCharacter(bs->character); - // - BotFreeWaypoints(bs->checkpoints); - BotFreeWaypoints(bs->patrolpoints); - //clear activate goal stack - BotClearActivateGoalStack(bs); - //clear the bot state - memset(bs, 0, sizeof(bot_state_t)); - //set the inuse flag to qfalse - bs->inuse = qfalse; - //there's one bot less - numbots--; - //everything went ok - return qtrue; -} - -/* -============== -BotResetState - -called when a bot enters the intermission or observer mode and -when the level is changed -============== -*/ -void BotResetState(bot_state_t *bs) { - int client, entitynum, inuse; - int movestate, goalstate, chatstate, weaponstate; - bot_settings_t settings; - int character; - playerState_t ps; //current player state - float entergame_time; - - //save some things that should not be reset here - memcpy(&settings, &bs->settings, sizeof(bot_settings_t)); - memcpy(&ps, &bs->cur_ps, sizeof(playerState_t)); - inuse = bs->inuse; - client = bs->client; - entitynum = bs->entitynum; - character = bs->character; - movestate = bs->ms; - goalstate = bs->gs; - chatstate = bs->cs; - weaponstate = bs->ws; - entergame_time = bs->entergame_time; - //free checkpoints and patrol points - BotFreeWaypoints(bs->checkpoints); - BotFreeWaypoints(bs->patrolpoints); - //reset the whole state - memset(bs, 0, sizeof(bot_state_t)); - //copy back some state stuff that should not be reset - bs->ms = movestate; - bs->gs = goalstate; - bs->cs = chatstate; - bs->ws = weaponstate; - memcpy(&bs->cur_ps, &ps, sizeof(playerState_t)); - memcpy(&bs->settings, &settings, sizeof(bot_settings_t)); - bs->inuse = inuse; - bs->client = client; - bs->entitynum = entitynum; - bs->character = character; - bs->entergame_time = entergame_time; - //reset several states - if (bs->ms) trap_BotResetMoveState(bs->ms); - if (bs->gs) trap_BotResetGoalState(bs->gs); - if (bs->ws) trap_BotResetWeaponState(bs->ws); - if (bs->gs) trap_BotResetAvoidGoals(bs->gs); - if (bs->ms) trap_BotResetAvoidReach(bs->ms); -} - -/* -============== -BotAILoadMap -============== -*/ -int BotAILoadMap( int restart ) { - int i; - vmCvar_t mapname; - - if (!restart) { - trap_Cvar_Register( &mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM ); - trap_BotLibLoadMap( mapname.string ); - } - - for (i = 0; i < MAX_CLIENTS; i++) { - if (botstates[i] && botstates[i]->inuse) { - BotResetState( botstates[i] ); - botstates[i]->setupcount = 4; - } - } - - BotSetupDeathmatchAI(); - - return qtrue; -} - -#ifdef MISSIONPACK -void ProximityMine_Trigger( gentity_t *trigger, gentity_t *other, trace_t *trace ); -#endif - -/* -================== -BotAIStartFrame -================== -*/ -int BotAIStartFrame(int time) { - int i; - gentity_t *ent; - bot_entitystate_t state; - int elapsed_time, thinktime; - static int local_time; - static int botlib_residual; - static int lastbotthink_time; - - G_CheckBotSpawn(); - - trap_Cvar_Update(&bot_rocketjump); - trap_Cvar_Update(&bot_grapple); - trap_Cvar_Update(&bot_fastchat); - trap_Cvar_Update(&bot_nochat); - trap_Cvar_Update(&bot_testrchat); - trap_Cvar_Update(&bot_thinktime); - trap_Cvar_Update(&bot_memorydump); - trap_Cvar_Update(&bot_saveroutingcache); - trap_Cvar_Update(&bot_pause); - trap_Cvar_Update(&bot_report); - - if (bot_report.integer) { -// BotTeamplayReport(); -// trap_Cvar_Set("bot_report", "0"); - BotUpdateInfoConfigStrings(); - } - - if (bot_pause.integer) { - // execute bot user commands every frame - for( i = 0; i < MAX_CLIENTS; i++ ) { - if( !botstates[i] || !botstates[i]->inuse ) { - continue; - } - if( g_entities[i].client->pers.connected != CON_CONNECTED ) { - continue; - } - botstates[i]->lastucmd.forwardmove = 0; - botstates[i]->lastucmd.rightmove = 0; - botstates[i]->lastucmd.upmove = 0; - botstates[i]->lastucmd.buttons = 0; - botstates[i]->lastucmd.serverTime = time; - trap_BotUserCommand(botstates[i]->client, &botstates[i]->lastucmd); - } - return qtrue; - } - - if (bot_memorydump.integer) { - trap_BotLibVarSet("memorydump", "1"); - trap_Cvar_Set("bot_memorydump", "0"); - } - if (bot_saveroutingcache.integer) { - trap_BotLibVarSet("saveroutingcache", "1"); - trap_Cvar_Set("bot_saveroutingcache", "0"); - } - //check if bot interbreeding is activated - BotInterbreeding(); - //cap the bot think time - if (bot_thinktime.integer > 200) { - trap_Cvar_Set("bot_thinktime", "200"); - } - //if the bot think time changed we should reschedule the bots - if (bot_thinktime.integer != lastbotthink_time) { - lastbotthink_time = bot_thinktime.integer; - BotScheduleBotThink(); - } - - elapsed_time = time - local_time; - local_time = time; - - botlib_residual += elapsed_time; - - if (elapsed_time > bot_thinktime.integer) thinktime = elapsed_time; - else thinktime = bot_thinktime.integer; - - // update the bot library - if ( botlib_residual >= thinktime ) { - botlib_residual -= thinktime; - - trap_BotLibStartFrame((float) time / 1000); - - if (!trap_AAS_Initialized()) return qfalse; - - //update entities in the botlib - for (i = 0; i < MAX_GENTITIES; i++) { - ent = &g_entities[i]; - if (!ent->inuse) { - trap_BotLibUpdateEntity(i, NULL); - continue; - } - if (!ent->r.linked) { - trap_BotLibUpdateEntity(i, NULL); - continue; - } - if (ent->r.svFlags & SVF_NOCLIENT) { - trap_BotLibUpdateEntity(i, NULL); - continue; - } - // do not update missiles - if (ent->s.eType == ET_MISSILE && ent->s.weapon != WP_GRAPPLING_HOOK) { - trap_BotLibUpdateEntity(i, NULL); - continue; - } - // do not update event only entities - if (ent->s.eType > ET_EVENTS) { - trap_BotLibUpdateEntity(i, NULL); - continue; - } -#ifdef MISSIONPACK - // never link prox mine triggers - if (ent->r.contents == CONTENTS_TRIGGER) { - if (ent->touch == ProximityMine_Trigger) { - trap_BotLibUpdateEntity(i, NULL); - continue; - } - } -#endif - // - memset(&state, 0, sizeof(bot_entitystate_t)); - // - VectorCopy(ent->r.currentOrigin, state.origin); - if (i < MAX_CLIENTS) { - VectorCopy(ent->s.apos.trBase, state.angles); - } else { - VectorCopy(ent->r.currentAngles, state.angles); - } - VectorCopy(ent->s.origin2, state.old_origin); - VectorCopy(ent->r.mins, state.mins); - VectorCopy(ent->r.maxs, state.maxs); - state.type = ent->s.eType; - state.flags = ent->s.eFlags; - if (ent->r.bmodel) state.solid = SOLID_BSP; - else state.solid = SOLID_BBOX; - state.groundent = ent->s.groundEntityNum; - state.modelindex = ent->s.modelindex; - state.modelindex2 = ent->s.modelindex2; - state.frame = ent->s.frame; - state.event = ent->s.event; - state.eventParm = ent->s.eventParm; - state.powerups = ent->s.powerups; - state.legsAnim = ent->s.legsAnim; - state.torsoAnim = ent->s.torsoAnim; - state.weapon = ent->s.weapon; - // - trap_BotLibUpdateEntity(i, &state); - } - - BotAIRegularUpdate(); - } - - floattime = trap_AAS_Time(); - - // execute scheduled bot AI - for( i = 0; i < MAX_CLIENTS; i++ ) { - if( !botstates[i] || !botstates[i]->inuse ) { - continue; - } - // - botstates[i]->botthink_residual += elapsed_time; - // - if ( botstates[i]->botthink_residual >= thinktime ) { - botstates[i]->botthink_residual -= thinktime; - - if (!trap_AAS_Initialized()) return qfalse; - - if (g_entities[i].client->pers.connected == CON_CONNECTED) { - BotAI(i, (float) thinktime / 1000); - } - } - } - - - // execute bot user commands every frame - for( i = 0; i < MAX_CLIENTS; i++ ) { - if( !botstates[i] || !botstates[i]->inuse ) { - continue; - } - if( g_entities[i].client->pers.connected != CON_CONNECTED ) { - continue; - } - - BotUpdateInput(botstates[i], time, elapsed_time); - trap_BotUserCommand(botstates[i]->client, &botstates[i]->lastucmd); - } - - return qtrue; -} - -/* -============== -BotInitLibrary -============== -*/ -int BotInitLibrary(void) { - char buf[144]; - - //set the maxclients and maxentities library variables before calling BotSetupLibrary - trap_Cvar_VariableStringBuffer("sv_maxclients", buf, sizeof(buf)); - if (!strlen(buf)) strcpy(buf, "8"); - trap_BotLibVarSet("maxclients", buf); - Com_sprintf(buf, sizeof(buf), "%d", MAX_GENTITIES); - trap_BotLibVarSet("maxentities", buf); - //bsp checksum - trap_Cvar_VariableStringBuffer("sv_mapChecksum", buf, sizeof(buf)); - if (strlen(buf)) trap_BotLibVarSet("sv_mapChecksum", buf); - //maximum number of aas links - trap_Cvar_VariableStringBuffer("max_aaslinks", buf, sizeof(buf)); - if (strlen(buf)) trap_BotLibVarSet("max_aaslinks", buf); - //maximum number of items in a level - trap_Cvar_VariableStringBuffer("max_levelitems", buf, sizeof(buf)); - if (strlen(buf)) trap_BotLibVarSet("max_levelitems", buf); - //game type - trap_Cvar_VariableStringBuffer("g_gametype", buf, sizeof(buf)); - if (!strlen(buf)) strcpy(buf, "0"); - trap_BotLibVarSet("g_gametype", buf); - //bot developer mode and log file - trap_BotLibVarSet("bot_developer", bot_developer.string); - trap_BotLibVarSet("log", buf); - //no chatting - trap_Cvar_VariableStringBuffer("bot_nochat", buf, sizeof(buf)); - if (strlen(buf)) trap_BotLibVarSet("nochat", "0"); - //visualize jump pads - trap_Cvar_VariableStringBuffer("bot_visualizejumppads", buf, sizeof(buf)); - if (strlen(buf)) trap_BotLibVarSet("bot_visualizejumppads", buf); - //forced clustering calculations - trap_Cvar_VariableStringBuffer("bot_forceclustering", buf, sizeof(buf)); - if (strlen(buf)) trap_BotLibVarSet("forceclustering", buf); - //forced reachability calculations - trap_Cvar_VariableStringBuffer("bot_forcereachability", buf, sizeof(buf)); - if (strlen(buf)) trap_BotLibVarSet("forcereachability", buf); - //force writing of AAS to file - trap_Cvar_VariableStringBuffer("bot_forcewrite", buf, sizeof(buf)); - if (strlen(buf)) trap_BotLibVarSet("forcewrite", buf); - //no AAS optimization - trap_Cvar_VariableStringBuffer("bot_aasoptimize", buf, sizeof(buf)); - if (strlen(buf)) trap_BotLibVarSet("aasoptimize", buf); - // - trap_Cvar_VariableStringBuffer("bot_saveroutingcache", buf, sizeof(buf)); - if (strlen(buf)) trap_BotLibVarSet("saveroutingcache", buf); - //reload instead of cache bot character files - trap_Cvar_VariableStringBuffer("bot_reloadcharacters", buf, sizeof(buf)); - if (!strlen(buf)) strcpy(buf, "0"); - trap_BotLibVarSet("bot_reloadcharacters", buf); - //base directory - trap_Cvar_VariableStringBuffer("fs_basepath", buf, sizeof(buf)); - if (strlen(buf)) trap_BotLibVarSet("basedir", buf); - //game directory - trap_Cvar_VariableStringBuffer("fs_game", buf, sizeof(buf)); - if (strlen(buf)) trap_BotLibVarSet("gamedir", buf); - //cd directory - trap_Cvar_VariableStringBuffer("fs_cdpath", buf, sizeof(buf)); - if (strlen(buf)) trap_BotLibVarSet("cddir", buf); - // -#ifdef MISSIONPACK - trap_BotLibDefine("MISSIONPACK"); -#endif - //setup the bot library - return trap_BotLibSetup(); -} - -/* -============== -BotAISetup -============== -*/ -int BotAISetup( int restart ) { - int errnum; - - trap_Cvar_Register(&bot_thinktime, "bot_thinktime", "100", CVAR_CHEAT); - trap_Cvar_Register(&bot_memorydump, "bot_memorydump", "0", CVAR_CHEAT); - trap_Cvar_Register(&bot_saveroutingcache, "bot_saveroutingcache", "0", CVAR_CHEAT); - trap_Cvar_Register(&bot_pause, "bot_pause", "0", CVAR_CHEAT); - trap_Cvar_Register(&bot_report, "bot_report", "0", CVAR_CHEAT); - trap_Cvar_Register(&bot_testsolid, "bot_testsolid", "0", CVAR_CHEAT); - trap_Cvar_Register(&bot_testclusters, "bot_testclusters", "0", CVAR_CHEAT); - trap_Cvar_Register(&bot_developer, "bot_developer", "0", CVAR_CHEAT); - trap_Cvar_Register(&bot_interbreedchar, "bot_interbreedchar", "", 0); - trap_Cvar_Register(&bot_interbreedbots, "bot_interbreedbots", "10", 0); - trap_Cvar_Register(&bot_interbreedcycle, "bot_interbreedcycle", "20", 0); - trap_Cvar_Register(&bot_interbreedwrite, "bot_interbreedwrite", "", 0); - - //if the game is restarted for a tournament - if (restart) { - return qtrue; - } - - //initialize the bot states - memset( botstates, 0, sizeof(botstates) ); - - errnum = BotInitLibrary(); - if (errnum != BLERR_NOERROR) return qfalse; - return qtrue; -} - -/* -============== -BotAIShutdown -============== -*/ -int BotAIShutdown( int restart ) { - - int i; - - //if the game is restarted for a tournament - if ( restart ) { - //shutdown all the bots in the botlib - for (i = 0; i < MAX_CLIENTS; i++) { - if (botstates[i] && botstates[i]->inuse) { - BotAIShutdownClient(botstates[i]->client, restart); - } - } - //don't shutdown the bot library - } - else { - trap_BotLibShutdown(); - } - return qtrue; -} - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: ai_main.c + * + * desc: Quake3 bot AI + * + * $Archive: /MissionPack/code/game/ai_main.c $ + * + *****************************************************************************/ + + +#include "g_local.h" +#include "q_shared.h" +#include "botlib.h" //bot lib interface +#include "be_aas.h" +#include "be_ea.h" +#include "be_ai_char.h" +#include "be_ai_chat.h" +#include "be_ai_gen.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" +#include "be_ai_weap.h" +// +#include "ai_main.h" +#include "ai_dmq3.h" +#include "ai_chat.h" +#include "ai_cmd.h" +#include "ai_dmnet.h" +#include "ai_vcmd.h" + +// +#include "chars.h" +#include "inv.h" +#include "syn.h" + +#define MAX_PATH 144 + + +//bot states +bot_state_t *botstates[MAX_CLIENTS]; +//number of bots +int numbots; +//floating point time +float floattime; +//time to do a regular update +float regularupdate_time; +// +int bot_interbreed; +int bot_interbreedmatchcount; +// +vmCvar_t bot_thinktime; +vmCvar_t bot_memorydump; +vmCvar_t bot_saveroutingcache; +vmCvar_t bot_pause; +vmCvar_t bot_report; +vmCvar_t bot_testsolid; +vmCvar_t bot_testclusters; +vmCvar_t bot_developer; +vmCvar_t bot_interbreedchar; +vmCvar_t bot_interbreedbots; +vmCvar_t bot_interbreedcycle; +vmCvar_t bot_interbreedwrite; + + +void ExitLevel( void ); + + +/* +================== +BotAI_Print +================== +*/ +void QDECL BotAI_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: { + G_Printf("%s", str); + break; + } + case PRT_WARNING: { + G_Printf( S_COLOR_YELLOW "Warning: %s", str ); + break; + } + case PRT_ERROR: { + G_Printf( S_COLOR_RED "Error: %s", str ); + break; + } + case PRT_FATAL: { + G_Printf( S_COLOR_RED "Fatal: %s", str ); + break; + } + case PRT_EXIT: { + G_Error( S_COLOR_RED "Exit: %s", str ); + break; + } + default: { + G_Printf( "unknown print type\n" ); + break; + } + } +} + + +/* +================== +BotAI_Trace +================== +*/ +void BotAI_Trace(bsp_trace_t *bsptrace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask) { + trace_t trace; + + trap_Trace(&trace, start, mins, maxs, end, passent, contentmask); + //copy the trace information + bsptrace->allsolid = trace.allsolid; + bsptrace->startsolid = 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; +} + +/* +================== +BotAI_GetClientState +================== +*/ +int BotAI_GetClientState( int clientNum, playerState_t *state ) { + gentity_t *ent; + + ent = &g_entities[clientNum]; + if ( !ent->inuse ) { + return qfalse; + } + if ( !ent->client ) { + return qfalse; + } + + memcpy( state, &ent->client->ps, sizeof(playerState_t) ); + return qtrue; +} + +/* +================== +BotAI_GetEntityState +================== +*/ +int BotAI_GetEntityState( int entityNum, entityState_t *state ) { + gentity_t *ent; + + ent = &g_entities[entityNum]; + memset( state, 0, sizeof(entityState_t) ); + if (!ent->inuse) return qfalse; + if (!ent->r.linked) return qfalse; + if (ent->r.svFlags & SVF_NOCLIENT) return qfalse; + memcpy( state, &ent->s, sizeof(entityState_t) ); + return qtrue; +} + +/* +================== +BotAI_GetSnapshotEntity +================== +*/ +int BotAI_GetSnapshotEntity( int clientNum, int sequence, entityState_t *state ) { + int entNum; + + entNum = trap_BotGetSnapshotEntity( clientNum, sequence ); + if ( entNum == -1 ) { + memset(state, 0, sizeof(entityState_t)); + return -1; + } + + BotAI_GetEntityState( entNum, state ); + + return sequence + 1; +} + +/* +================== +BotAI_BotInitialChat +================== +*/ +void QDECL BotAI_BotInitialChat( bot_state_t *bs, char *type, ... ) { + int i, mcontext; + va_list ap; + char *p; + char *vars[MAX_MATCHVARIABLES]; + + memset(vars, 0, sizeof(vars)); + va_start(ap, type); + p = va_arg(ap, char *); + for (i = 0; i < MAX_MATCHVARIABLES; i++) { + if( !p ) { + break; + } + vars[i] = p; + p = va_arg(ap, char *); + } + va_end(ap); + + mcontext = BotSynonymContext(bs); + + trap_BotInitialChat( bs->cs, type, mcontext, vars[0], vars[1], vars[2], vars[3], vars[4], vars[5], vars[6], vars[7] ); +} + + +/* +================== +BotTestAAS +================== +*/ +void BotTestAAS(vec3_t origin) { + int areanum; + aas_areainfo_t info; + + trap_Cvar_Update(&bot_testsolid); + trap_Cvar_Update(&bot_testclusters); + if (bot_testsolid.integer) { + if (!trap_AAS_Initialized()) return; + areanum = BotPointAreaNum(origin); + if (areanum) BotAI_Print(PRT_MESSAGE, "\remtpy area"); + else BotAI_Print(PRT_MESSAGE, "\r^1SOLID area"); + } + else if (bot_testclusters.integer) { + if (!trap_AAS_Initialized()) return; + areanum = BotPointAreaNum(origin); + if (!areanum) + BotAI_Print(PRT_MESSAGE, "\r^1Solid! "); + else { + trap_AAS_AreaInfo(areanum, &info); + BotAI_Print(PRT_MESSAGE, "\rarea %d, cluster %d ", areanum, info.cluster); + } + } +} + +/* +================== +BotReportStatus +================== +*/ +void BotReportStatus(bot_state_t *bs) { + char goalname[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + char *leader, flagstatus[32]; + // + ClientName(bs->client, netname, sizeof(netname)); + if (Q_stricmp(netname, bs->teamleader) == 0) leader = "L"; + else leader = " "; + + strcpy(flagstatus, " "); + if (gametype == GT_CTF) { + if (BotCTFCarryingFlag(bs)) { + if (BotTeam(bs) == TEAM_RED) strcpy(flagstatus, S_COLOR_RED"F "); + else strcpy(flagstatus, S_COLOR_BLUE"F "); + } + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + if (Bot1FCTFCarryingFlag(bs)) { + if (BotTeam(bs) == TEAM_RED) strcpy(flagstatus, S_COLOR_RED"F "); + else strcpy(flagstatus, S_COLOR_BLUE"F "); + } + } + else if (gametype == GT_HARVESTER) { + if (BotHarvesterCarryingCubes(bs)) { + if (BotTeam(bs) == TEAM_RED) Com_sprintf(flagstatus, sizeof(flagstatus), S_COLOR_RED"%2d", bs->inventory[INVENTORY_REDCUBE]); + else Com_sprintf(flagstatus, sizeof(flagstatus), S_COLOR_BLUE"%2d", bs->inventory[INVENTORY_BLUECUBE]); + } + } +#endif + + switch(bs->ltgtype) { + case LTG_TEAMHELP: + { + EasyClientName(bs->teammate, goalname, sizeof(goalname)); + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: helping %s\n", netname, leader, flagstatus, goalname); + break; + } + case LTG_TEAMACCOMPANY: + { + EasyClientName(bs->teammate, goalname, sizeof(goalname)); + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: accompanying %s\n", netname, leader, flagstatus, goalname); + break; + } + case LTG_DEFENDKEYAREA: + { + trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: defending %s\n", netname, leader, flagstatus, goalname); + break; + } + case LTG_GETITEM: + { + trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: getting item %s\n", netname, leader, flagstatus, goalname); + break; + } + case LTG_KILL: + { + ClientName(bs->teamgoal.entitynum, goalname, sizeof(goalname)); + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: killing %s\n", netname, leader, flagstatus, goalname); + break; + } + case LTG_CAMP: + case LTG_CAMPORDER: + { + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: camping\n", netname, leader, flagstatus); + break; + } + case LTG_PATROL: + { + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: patrolling\n", netname, leader, flagstatus); + break; + } + case LTG_GETFLAG: + { + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: capturing flag\n", netname, leader, flagstatus); + break; + } + case LTG_RUSHBASE: + { + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: rushing base\n", netname, leader, flagstatus); + break; + } + case LTG_RETURNFLAG: + { + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: returning flag\n", netname, leader, flagstatus); + break; + } + case LTG_ATTACKENEMYBASE: + { + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: attacking the enemy base\n", netname, leader, flagstatus); + break; + } + case LTG_HARVEST: + { + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: harvesting\n", netname, leader, flagstatus); + break; + } + default: + { + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: roaming\n", netname, leader, flagstatus); + break; + } + } +} + +/* +================== +BotTeamplayReport +================== +*/ +void BotTeamplayReport(void) { + int i; + char buf[MAX_INFO_STRING]; + + BotAI_Print(PRT_MESSAGE, S_COLOR_RED"RED\n"); + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + // + if ( !botstates[i] || !botstates[i]->inuse ) continue; + // + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_RED) { + BotReportStatus(botstates[i]); + } + } + BotAI_Print(PRT_MESSAGE, S_COLOR_BLUE"BLUE\n"); + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + // + if ( !botstates[i] || !botstates[i]->inuse ) continue; + // + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_BLUE) { + BotReportStatus(botstates[i]); + } + } +} + +/* +================== +BotSetInfoConfigString +================== +*/ +void BotSetInfoConfigString(bot_state_t *bs) { + char goalname[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + char action[MAX_MESSAGE_SIZE]; + char *leader, carrying[32], *cs; + bot_goal_t goal; + // + ClientName(bs->client, netname, sizeof(netname)); + if (Q_stricmp(netname, bs->teamleader) == 0) leader = "L"; + else leader = " "; + + strcpy(carrying, " "); + if (gametype == GT_CTF) { + if (BotCTFCarryingFlag(bs)) { + strcpy(carrying, "F "); + } + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + if (Bot1FCTFCarryingFlag(bs)) { + strcpy(carrying, "F "); + } + } + else if (gametype == GT_HARVESTER) { + if (BotHarvesterCarryingCubes(bs)) { + if (BotTeam(bs) == TEAM_RED) Com_sprintf(carrying, sizeof(carrying), "%2d", bs->inventory[INVENTORY_REDCUBE]); + else Com_sprintf(carrying, sizeof(carrying), "%2d", bs->inventory[INVENTORY_BLUECUBE]); + } + } +#endif + + switch(bs->ltgtype) { + case LTG_TEAMHELP: + { + EasyClientName(bs->teammate, goalname, sizeof(goalname)); + Com_sprintf(action, sizeof(action), "helping %s", goalname); + break; + } + case LTG_TEAMACCOMPANY: + { + EasyClientName(bs->teammate, goalname, sizeof(goalname)); + Com_sprintf(action, sizeof(action), "accompanying %s", goalname); + break; + } + case LTG_DEFENDKEYAREA: + { + trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); + Com_sprintf(action, sizeof(action), "defending %s", goalname); + break; + } + case LTG_GETITEM: + { + trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); + Com_sprintf(action, sizeof(action), "getting item %s", goalname); + break; + } + case LTG_KILL: + { + ClientName(bs->teamgoal.entitynum, goalname, sizeof(goalname)); + Com_sprintf(action, sizeof(action), "killing %s", goalname); + break; + } + case LTG_CAMP: + case LTG_CAMPORDER: + { + Com_sprintf(action, sizeof(action), "camping"); + break; + } + case LTG_PATROL: + { + Com_sprintf(action, sizeof(action), "patrolling"); + break; + } + case LTG_GETFLAG: + { + Com_sprintf(action, sizeof(action), "capturing flag"); + break; + } + case LTG_RUSHBASE: + { + Com_sprintf(action, sizeof(action), "rushing base"); + break; + } + case LTG_RETURNFLAG: + { + Com_sprintf(action, sizeof(action), "returning flag"); + break; + } + case LTG_ATTACKENEMYBASE: + { + Com_sprintf(action, sizeof(action), "attacking the enemy base"); + break; + } + case LTG_HARVEST: + { + Com_sprintf(action, sizeof(action), "harvesting"); + break; + } + default: + { + trap_BotGetTopGoal(bs->gs, &goal); + trap_BotGoalName(goal.number, goalname, sizeof(goalname)); + Com_sprintf(action, sizeof(action), "roaming %s", goalname); + break; + } + } + cs = va("l\\%s\\c\\%s\\a\\%s", + leader, + carrying, + action); + trap_SetConfigstring (CS_BOTINFO + bs->client, cs); +} + +/* +============== +BotUpdateInfoConfigStrings +============== +*/ +void BotUpdateInfoConfigStrings(void) { + int i; + char buf[MAX_INFO_STRING]; + + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + // + if ( !botstates[i] || !botstates[i]->inuse ) + continue; + // + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) + continue; + BotSetInfoConfigString(botstates[i]); + } +} + +/* +============== +BotInterbreedBots +============== +*/ +void BotInterbreedBots(void) { + float ranks[MAX_CLIENTS]; + int parent1, parent2, child; + int i; + + // get rankings for all the bots + for (i = 0; i < MAX_CLIENTS; i++) { + if ( botstates[i] && botstates[i]->inuse ) { + ranks[i] = botstates[i]->num_kills * 2 - botstates[i]->num_deaths; + } + else { + ranks[i] = -1; + } + } + + if (trap_GeneticParentsAndChildSelection(MAX_CLIENTS, ranks, &parent1, &parent2, &child)) { + trap_BotInterbreedGoalFuzzyLogic(botstates[parent1]->gs, botstates[parent2]->gs, botstates[child]->gs); + trap_BotMutateGoalFuzzyLogic(botstates[child]->gs, 1); + } + // reset the kills and deaths + for (i = 0; i < MAX_CLIENTS; i++) { + if (botstates[i] && botstates[i]->inuse) { + botstates[i]->num_kills = 0; + botstates[i]->num_deaths = 0; + } + } +} + +/* +============== +BotWriteInterbreeded +============== +*/ +void BotWriteInterbreeded(char *filename) { + float rank, bestrank; + int i, bestbot; + + bestrank = 0; + bestbot = -1; + // get the best bot + for (i = 0; i < MAX_CLIENTS; i++) { + if ( botstates[i] && botstates[i]->inuse ) { + rank = botstates[i]->num_kills * 2 - botstates[i]->num_deaths; + } + else { + rank = -1; + } + if (rank > bestrank) { + bestrank = rank; + bestbot = i; + } + } + if (bestbot >= 0) { + //write out the new goal fuzzy logic + trap_BotSaveGoalFuzzyLogic(botstates[bestbot]->gs, filename); + } +} + +/* +============== +BotInterbreedEndMatch + +add link back into ExitLevel? +============== +*/ +void BotInterbreedEndMatch(void) { + + if (!bot_interbreed) return; + bot_interbreedmatchcount++; + if (bot_interbreedmatchcount >= bot_interbreedcycle.integer) { + bot_interbreedmatchcount = 0; + // + trap_Cvar_Update(&bot_interbreedwrite); + if (strlen(bot_interbreedwrite.string)) { + BotWriteInterbreeded(bot_interbreedwrite.string); + trap_Cvar_Set("bot_interbreedwrite", ""); + } + BotInterbreedBots(); + } +} + +/* +============== +BotInterbreeding +============== +*/ +void BotInterbreeding(void) { + int i; + + trap_Cvar_Update(&bot_interbreedchar); + if (!strlen(bot_interbreedchar.string)) return; + //make sure we are in tournament mode + if (gametype != GT_TOURNAMENT) { + trap_Cvar_Set("g_gametype", va("%d", GT_TOURNAMENT)); + ExitLevel(); + return; + } + //shutdown all the bots + for (i = 0; i < MAX_CLIENTS; i++) { + if (botstates[i] && botstates[i]->inuse) { + BotAIShutdownClient(botstates[i]->client, qfalse); + } + } + //make sure all item weight configs are reloaded and Not shared + trap_BotLibVarSet("bot_reloadcharacters", "1"); + //add a number of bots using the desired bot character + for (i = 0; i < bot_interbreedbots.integer; i++) { + trap_SendConsoleCommand( EXEC_INSERT, va("addbot %s 4 free %i %s%d\n", + bot_interbreedchar.string, i * 50, bot_interbreedchar.string, i) ); + } + // + trap_Cvar_Set("bot_interbreedchar", ""); + bot_interbreed = qtrue; +} + +/* +============== +BotEntityInfo +============== +*/ +void BotEntityInfo(int entnum, aas_entityinfo_t *info) { + trap_AAS_EntityInfo(entnum, info); +} + +/* +============== +NumBots +============== +*/ +int NumBots(void) { + return numbots; +} + +/* +============== +BotTeamLeader +============== +*/ +int BotTeamLeader(bot_state_t *bs) { + int leader; + + leader = ClientFromName(bs->teamleader); + if (leader < 0) return qfalse; + if (!botstates[leader] || !botstates[leader]->inuse) return qfalse; + return qtrue; +} + +/* +============== +AngleDifference +============== +*/ +float AngleDifference(float ang1, float ang2) { + float diff; + + diff = ang1 - ang2; + if (ang1 > ang2) { + if (diff > 180.0) diff -= 360.0; + } + else { + if (diff < -180.0) diff += 360.0; + } + return diff; +} + +/* +============== +BotChangeViewAngle +============== +*/ +float BotChangeViewAngle(float angle, float ideal_angle, float speed) { + float move; + + angle = AngleMod(angle); + ideal_angle = AngleMod(ideal_angle); + if (angle == ideal_angle) return angle; + move = ideal_angle - angle; + if (ideal_angle > angle) { + if (move > 180.0) move -= 360.0; + } + else { + if (move < -180.0) move += 360.0; + } + if (move > 0) { + if (move > speed) move = speed; + } + else { + if (move < -speed) move = -speed; + } + return AngleMod(angle + move); +} + +/* +============== +BotChangeViewAngles +============== +*/ +void BotChangeViewAngles(bot_state_t *bs, float thinktime) { + float diff, factor, maxchange, anglespeed, disired_speed; + int i; + + if (bs->ideal_viewangles[PITCH] > 180) bs->ideal_viewangles[PITCH] -= 360; + // + if (bs->enemy >= 0) { + factor = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_VIEW_FACTOR, 0.01f, 1); + maxchange = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_VIEW_MAXCHANGE, 1, 1800); + } + else { + factor = 0.05f; + maxchange = 360; + } + if (maxchange < 240) maxchange = 240; + maxchange *= thinktime; + for (i = 0; i < 2; i++) { + // + if (bot_challenge.integer) { + //smooth slowdown view model + diff = abs(AngleDifference(bs->viewangles[i], bs->ideal_viewangles[i])); + anglespeed = diff * factor; + if (anglespeed > maxchange) anglespeed = maxchange; + bs->viewangles[i] = BotChangeViewAngle(bs->viewangles[i], + bs->ideal_viewangles[i], anglespeed); + } + else { + //over reaction view model + bs->viewangles[i] = AngleMod(bs->viewangles[i]); + bs->ideal_viewangles[i] = AngleMod(bs->ideal_viewangles[i]); + diff = AngleDifference(bs->viewangles[i], bs->ideal_viewangles[i]); + disired_speed = diff * factor; + bs->viewanglespeed[i] += (bs->viewanglespeed[i] - disired_speed); + if (bs->viewanglespeed[i] > 180) bs->viewanglespeed[i] = maxchange; + if (bs->viewanglespeed[i] < -180) bs->viewanglespeed[i] = -maxchange; + anglespeed = bs->viewanglespeed[i]; + if (anglespeed > maxchange) anglespeed = maxchange; + if (anglespeed < -maxchange) anglespeed = -maxchange; + bs->viewangles[i] += anglespeed; + bs->viewangles[i] = AngleMod(bs->viewangles[i]); + //demping + bs->viewanglespeed[i] *= 0.45 * (1 - factor); + } + //BotAI_Print(PRT_MESSAGE, "ideal_angles %f %f\n", bs->ideal_viewangles[0], bs->ideal_viewangles[1], bs->ideal_viewangles[2]);` + //bs->viewangles[i] = bs->ideal_viewangles[i]; + } + //bs->viewangles[PITCH] = 0; + if (bs->viewangles[PITCH] > 180) bs->viewangles[PITCH] -= 360; + //elementary action: view + trap_EA_View(bs->client, bs->viewangles); +} + +/* +============== +BotInputToUserCommand +============== +*/ +void BotInputToUserCommand(bot_input_t *bi, usercmd_t *ucmd, int delta_angles[3], int time) { + vec3_t angles, forward, right; + short temp; + int j; + + //clear the whole structure + memset(ucmd, 0, sizeof(usercmd_t)); + // + //Com_Printf("dir = %f %f %f speed = %f\n", bi->dir[0], bi->dir[1], bi->dir[2], bi->speed); + //the duration for the user command in milli seconds + ucmd->serverTime = time; + // + if (bi->actionflags & ACTION_DELAYEDJUMP) { + bi->actionflags |= ACTION_JUMP; + bi->actionflags &= ~ACTION_DELAYEDJUMP; + } + //set the buttons + if (bi->actionflags & ACTION_RESPAWN) ucmd->buttons = BUTTON_ATTACK; + if (bi->actionflags & ACTION_ATTACK) ucmd->buttons |= BUTTON_ATTACK; + if (bi->actionflags & ACTION_TALK) ucmd->buttons |= BUTTON_TALK; + if (bi->actionflags & ACTION_GESTURE) ucmd->buttons |= BUTTON_GESTURE; + if (bi->actionflags & ACTION_USE) ucmd->buttons |= BUTTON_USE_HOLDABLE; + if (bi->actionflags & ACTION_WALK) ucmd->buttons |= BUTTON_WALKING; + if (bi->actionflags & ACTION_AFFIRMATIVE) ucmd->buttons |= BUTTON_AFFIRMATIVE; + if (bi->actionflags & ACTION_NEGATIVE) ucmd->buttons |= BUTTON_NEGATIVE; + if (bi->actionflags & ACTION_GETFLAG) ucmd->buttons |= BUTTON_GETFLAG; + if (bi->actionflags & ACTION_GUARDBASE) ucmd->buttons |= BUTTON_GUARDBASE; + if (bi->actionflags & ACTION_PATROL) ucmd->buttons |= BUTTON_PATROL; + if (bi->actionflags & ACTION_FOLLOWME) ucmd->buttons |= BUTTON_FOLLOWME; + // + ucmd->weapon = bi->weapon; + //set the view angles + //NOTE: the ucmd->angles are the angles WITHOUT the delta angles + ucmd->angles[PITCH] = ANGLE2SHORT(bi->viewangles[PITCH]); + ucmd->angles[YAW] = ANGLE2SHORT(bi->viewangles[YAW]); + ucmd->angles[ROLL] = ANGLE2SHORT(bi->viewangles[ROLL]); + //subtract the delta angles + for (j = 0; j < 3; j++) { + temp = ucmd->angles[j] - delta_angles[j]; + /*NOTE: disabled because temp should be mod first + if ( j == PITCH ) { + // don't let the player look up or down more than 90 degrees + if ( temp > 16000 ) temp = 16000; + else if ( temp < -16000 ) temp = -16000; + } + */ + ucmd->angles[j] = temp; + } + //NOTE: movement is relative to the REAL view angles + //get the horizontal forward and right vector + //get the pitch in the range [-180, 180] + if (bi->dir[2]) angles[PITCH] = bi->viewangles[PITCH]; + else angles[PITCH] = 0; + angles[YAW] = bi->viewangles[YAW]; + angles[ROLL] = 0; + AngleVectors(angles, forward, right, NULL); + //bot input speed is in the range [0, 400] + bi->speed = bi->speed * 127 / 400; + //set the view independent movement + ucmd->forwardmove = DotProduct(forward, bi->dir) * bi->speed; + ucmd->rightmove = DotProduct(right, bi->dir) * bi->speed; + ucmd->upmove = abs(forward[2]) * bi->dir[2] * bi->speed; + //normal keyboard movement + if (bi->actionflags & ACTION_MOVEFORWARD) ucmd->forwardmove += 127; + if (bi->actionflags & ACTION_MOVEBACK) ucmd->forwardmove -= 127; + if (bi->actionflags & ACTION_MOVELEFT) ucmd->rightmove -= 127; + if (bi->actionflags & ACTION_MOVERIGHT) ucmd->rightmove += 127; + //jump/moveup + if (bi->actionflags & ACTION_JUMP) ucmd->upmove += 127; + //crouch/movedown + if (bi->actionflags & ACTION_CROUCH) ucmd->upmove -= 127; + // + //Com_Printf("forward = %d right = %d up = %d\n", ucmd.forwardmove, ucmd.rightmove, ucmd.upmove); + //Com_Printf("ucmd->serverTime = %d\n", ucmd->serverTime); +} + +/* +============== +BotUpdateInput +============== +*/ +void BotUpdateInput(bot_state_t *bs, int time, int elapsed_time) { + bot_input_t bi; + int j; + + //add the delta angles to the bot's current view angles + for (j = 0; j < 3; j++) { + bs->viewangles[j] = AngleMod(bs->viewangles[j] + SHORT2ANGLE(bs->cur_ps.delta_angles[j])); + } + //change the bot view angles + BotChangeViewAngles(bs, (float) elapsed_time / 1000); + //retrieve the bot input + trap_EA_GetInput(bs->client, (float) time / 1000, &bi); + //respawn hack + if (bi.actionflags & ACTION_RESPAWN) { + if (bs->lastucmd.buttons & BUTTON_ATTACK) bi.actionflags &= ~(ACTION_RESPAWN|ACTION_ATTACK); + } + //convert the bot input to a usercmd + BotInputToUserCommand(&bi, &bs->lastucmd, bs->cur_ps.delta_angles, time); + //subtract the delta angles + for (j = 0; j < 3; j++) { + bs->viewangles[j] = AngleMod(bs->viewangles[j] - SHORT2ANGLE(bs->cur_ps.delta_angles[j])); + } +} + +/* +============== +BotAIRegularUpdate +============== +*/ +void BotAIRegularUpdate(void) { + if (regularupdate_time < FloatTime()) { + trap_BotUpdateEntityItems(); + regularupdate_time = FloatTime() + 0.3; + } +} + +/* +============== +RemoveColorEscapeSequences +============== +*/ +void RemoveColorEscapeSequences( char *text ) { + int i, l; + + l = 0; + for ( i = 0; text[i]; i++ ) { + if (Q_IsColorString(&text[i])) { + i++; + continue; + } + if (text[i] > 0x7E) + continue; + text[l++] = text[i]; + } + text[l] = '\0'; +} + +/* +============== +BotAI +============== +*/ +int BotAI(int client, float thinktime) { + bot_state_t *bs; + char buf[1024], *args; + int j; + + trap_EA_ResetInput(client); + // + bs = botstates[client]; + if (!bs || !bs->inuse) { + BotAI_Print(PRT_FATAL, "BotAI: client %d is not setup\n", client); + return qfalse; + } + + //retrieve the current client state + BotAI_GetClientState( client, &bs->cur_ps ); + + //retrieve any waiting server commands + while( trap_BotGetServerCommand(client, buf, sizeof(buf)) ) { + //have buf point to the command and args to the command arguments + args = strchr( buf, ' '); + if (!args) continue; + *args++ = '\0'; + + //remove color espace sequences from the arguments + RemoveColorEscapeSequences( args ); + + if (!Q_stricmp(buf, "cp ")) + { /*CenterPrintf*/ } + else if (!Q_stricmp(buf, "cs")) + { /*ConfigStringModified*/ } + else if (!Q_stricmp(buf, "print")) { + //remove first and last quote from the chat message + memmove(args, args+1, strlen(args)); + args[strlen(args)-1] = '\0'; + trap_BotQueueConsoleMessage(bs->cs, CMS_NORMAL, args); + } + else if (!Q_stricmp(buf, "chat")) { + //remove first and last quote from the chat message + memmove(args, args+1, strlen(args)); + args[strlen(args)-1] = '\0'; + trap_BotQueueConsoleMessage(bs->cs, CMS_CHAT, args); + } + else if (!Q_stricmp(buf, "tchat")) { + //remove first and last quote from the chat message + memmove(args, args+1, strlen(args)); + args[strlen(args)-1] = '\0'; + trap_BotQueueConsoleMessage(bs->cs, CMS_CHAT, args); + } +#ifdef MISSIONPACK + else if (!Q_stricmp(buf, "vchat")) { + BotVoiceChatCommand(bs, SAY_ALL, args); + } + else if (!Q_stricmp(buf, "vtchat")) { + BotVoiceChatCommand(bs, SAY_TEAM, args); + } + else if (!Q_stricmp(buf, "vtell")) { + BotVoiceChatCommand(bs, SAY_TELL, args); + } +#endif + else if (!Q_stricmp(buf, "scores")) + { /*FIXME: parse scores?*/ } + else if (!Q_stricmp(buf, "clientLevelShot")) + { /*ignore*/ } + } + //add the delta angles to the bot's current view angles + for (j = 0; j < 3; j++) { + bs->viewangles[j] = AngleMod(bs->viewangles[j] + SHORT2ANGLE(bs->cur_ps.delta_angles[j])); + } + //increase the local time of the bot + bs->ltime += thinktime; + // + bs->thinktime = thinktime; + //origin of the bot + VectorCopy(bs->cur_ps.origin, bs->origin); + //eye coordinates of the bot + VectorCopy(bs->cur_ps.origin, bs->eye); + bs->eye[2] += bs->cur_ps.viewheight; + //get the area the bot is in + bs->areanum = BotPointAreaNum(bs->origin); + //the real AI + BotDeathmatchAI(bs, thinktime); + //set the weapon selection every AI frame + trap_EA_SelectWeapon(bs->client, bs->weaponnum); + //subtract the delta angles + for (j = 0; j < 3; j++) { + bs->viewangles[j] = AngleMod(bs->viewangles[j] - SHORT2ANGLE(bs->cur_ps.delta_angles[j])); + } + //everything was ok + return qtrue; +} + +/* +================== +BotScheduleBotThink +================== +*/ +void BotScheduleBotThink(void) { + int i, botnum; + + botnum = 0; + + for( i = 0; i < MAX_CLIENTS; i++ ) { + if( !botstates[i] || !botstates[i]->inuse ) { + continue; + } + //initialize the bot think residual time + botstates[i]->botthink_residual = bot_thinktime.integer * botnum / numbots; + botnum++; + } +} + +/* +============== +BotWriteSessionData +============== +*/ +void BotWriteSessionData(bot_state_t *bs) { + const char *s; + const char *var; + + s = va( + "%i %i %i %i %i %i %i %i" + " %f %f %f" + " %f %f %f" + " %f %f %f", + bs->lastgoal_decisionmaker, + bs->lastgoal_ltgtype, + bs->lastgoal_teammate, + bs->lastgoal_teamgoal.areanum, + bs->lastgoal_teamgoal.entitynum, + bs->lastgoal_teamgoal.flags, + bs->lastgoal_teamgoal.iteminfo, + bs->lastgoal_teamgoal.number, + bs->lastgoal_teamgoal.origin[0], + bs->lastgoal_teamgoal.origin[1], + bs->lastgoal_teamgoal.origin[2], + bs->lastgoal_teamgoal.mins[0], + bs->lastgoal_teamgoal.mins[1], + bs->lastgoal_teamgoal.mins[2], + bs->lastgoal_teamgoal.maxs[0], + bs->lastgoal_teamgoal.maxs[1], + bs->lastgoal_teamgoal.maxs[2] + ); + + var = va( "botsession%i", bs->client ); + + trap_Cvar_Set( var, s ); +} + +/* +============== +BotReadSessionData +============== +*/ +void BotReadSessionData(bot_state_t *bs) { + char s[MAX_STRING_CHARS]; + const char *var; + + var = va( "botsession%i", bs->client ); + trap_Cvar_VariableStringBuffer( var, s, sizeof(s) ); + + sscanf(s, + "%i %i %i %i %i %i %i %i" + " %f %f %f" + " %f %f %f" + " %f %f %f", + &bs->lastgoal_decisionmaker, + &bs->lastgoal_ltgtype, + &bs->lastgoal_teammate, + &bs->lastgoal_teamgoal.areanum, + &bs->lastgoal_teamgoal.entitynum, + &bs->lastgoal_teamgoal.flags, + &bs->lastgoal_teamgoal.iteminfo, + &bs->lastgoal_teamgoal.number, + &bs->lastgoal_teamgoal.origin[0], + &bs->lastgoal_teamgoal.origin[1], + &bs->lastgoal_teamgoal.origin[2], + &bs->lastgoal_teamgoal.mins[0], + &bs->lastgoal_teamgoal.mins[1], + &bs->lastgoal_teamgoal.mins[2], + &bs->lastgoal_teamgoal.maxs[0], + &bs->lastgoal_teamgoal.maxs[1], + &bs->lastgoal_teamgoal.maxs[2] + ); +} + +/* +============== +BotAISetupClient +============== +*/ +int BotAISetupClient(int client, struct bot_settings_s *settings, qboolean restart) { + char filename[MAX_PATH], name[MAX_PATH], gender[MAX_PATH]; + bot_state_t *bs; + int errnum; + + if (!botstates[client]) botstates[client] = G_Alloc(sizeof(bot_state_t)); + bs = botstates[client]; + + if (bs && bs->inuse) { + BotAI_Print(PRT_FATAL, "BotAISetupClient: client %d already setup\n", client); + return qfalse; + } + + if (!trap_AAS_Initialized()) { + BotAI_Print(PRT_FATAL, "AAS not initialized\n"); + return qfalse; + } + + //load the bot character + bs->character = trap_BotLoadCharacter(settings->characterfile, settings->skill); + if (!bs->character) { + BotAI_Print(PRT_FATAL, "couldn't load skill %f from %s\n", settings->skill, settings->characterfile); + return qfalse; + } + //copy the settings + memcpy(&bs->settings, settings, sizeof(bot_settings_t)); + //allocate a goal state + bs->gs = trap_BotAllocGoalState(client); + //load the item weights + trap_Characteristic_String(bs->character, CHARACTERISTIC_ITEMWEIGHTS, filename, MAX_PATH); + errnum = trap_BotLoadItemWeights(bs->gs, filename); + if (errnum != BLERR_NOERROR) { + trap_BotFreeGoalState(bs->gs); + return qfalse; + } + //allocate a weapon state + bs->ws = trap_BotAllocWeaponState(); + //load the weapon weights + trap_Characteristic_String(bs->character, CHARACTERISTIC_WEAPONWEIGHTS, filename, MAX_PATH); + errnum = trap_BotLoadWeaponWeights(bs->ws, filename); + if (errnum != BLERR_NOERROR) { + trap_BotFreeGoalState(bs->gs); + trap_BotFreeWeaponState(bs->ws); + return qfalse; + } + //allocate a chat state + bs->cs = trap_BotAllocChatState(); + //load the chat file + trap_Characteristic_String(bs->character, CHARACTERISTIC_CHAT_FILE, filename, MAX_PATH); + trap_Characteristic_String(bs->character, CHARACTERISTIC_CHAT_NAME, name, MAX_PATH); + errnum = trap_BotLoadChatFile(bs->cs, filename, name); + if (errnum != BLERR_NOERROR) { + trap_BotFreeChatState(bs->cs); + trap_BotFreeGoalState(bs->gs); + trap_BotFreeWeaponState(bs->ws); + return qfalse; + } + //get the gender characteristic + trap_Characteristic_String(bs->character, CHARACTERISTIC_GENDER, gender, MAX_PATH); + //set the chat gender + if (*gender == 'f' || *gender == 'F') trap_BotSetChatGender(bs->cs, CHAT_GENDERFEMALE); + else if (*gender == 'm' || *gender == 'M') trap_BotSetChatGender(bs->cs, CHAT_GENDERMALE); + else trap_BotSetChatGender(bs->cs, CHAT_GENDERLESS); + + bs->inuse = qtrue; + bs->client = client; + bs->entitynum = client; + bs->setupcount = 4; + bs->entergame_time = FloatTime(); + bs->ms = trap_BotAllocMoveState(); + bs->walker = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_WALKER, 0, 1); + numbots++; + + if (trap_Cvar_VariableIntegerValue("bot_testichat")) { + trap_BotLibVarSet("bot_testichat", "1"); + BotChatTest(bs); + } + //NOTE: reschedule the bot thinking + BotScheduleBotThink(); + //if interbreeding start with a mutation + if (bot_interbreed) { + trap_BotMutateGoalFuzzyLogic(bs->gs, 1); + } + // if we kept the bot client + if (restart) { + BotReadSessionData(bs); + } + //bot has been setup succesfully + return qtrue; +} + +/* +============== +BotAIShutdownClient +============== +*/ +int BotAIShutdownClient(int client, qboolean restart) { + bot_state_t *bs; + + bs = botstates[client]; + if (!bs || !bs->inuse) { + //BotAI_Print(PRT_ERROR, "BotAIShutdownClient: client %d already shutdown\n", client); + return qfalse; + } + + if (restart) { + BotWriteSessionData(bs); + } + + if (BotChat_ExitGame(bs)) { + trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL); + } + + trap_BotFreeMoveState(bs->ms); + //free the goal state` + trap_BotFreeGoalState(bs->gs); + //free the chat file + trap_BotFreeChatState(bs->cs); + //free the weapon weights + trap_BotFreeWeaponState(bs->ws); + //free the bot character + trap_BotFreeCharacter(bs->character); + // + BotFreeWaypoints(bs->checkpoints); + BotFreeWaypoints(bs->patrolpoints); + //clear activate goal stack + BotClearActivateGoalStack(bs); + //clear the bot state + memset(bs, 0, sizeof(bot_state_t)); + //set the inuse flag to qfalse + bs->inuse = qfalse; + //there's one bot less + numbots--; + //everything went ok + return qtrue; +} + +/* +============== +BotResetState + +called when a bot enters the intermission or observer mode and +when the level is changed +============== +*/ +void BotResetState(bot_state_t *bs) { + int client, entitynum, inuse; + int movestate, goalstate, chatstate, weaponstate; + bot_settings_t settings; + int character; + playerState_t ps; //current player state + float entergame_time; + + //save some things that should not be reset here + memcpy(&settings, &bs->settings, sizeof(bot_settings_t)); + memcpy(&ps, &bs->cur_ps, sizeof(playerState_t)); + inuse = bs->inuse; + client = bs->client; + entitynum = bs->entitynum; + character = bs->character; + movestate = bs->ms; + goalstate = bs->gs; + chatstate = bs->cs; + weaponstate = bs->ws; + entergame_time = bs->entergame_time; + //free checkpoints and patrol points + BotFreeWaypoints(bs->checkpoints); + BotFreeWaypoints(bs->patrolpoints); + //reset the whole state + memset(bs, 0, sizeof(bot_state_t)); + //copy back some state stuff that should not be reset + bs->ms = movestate; + bs->gs = goalstate; + bs->cs = chatstate; + bs->ws = weaponstate; + memcpy(&bs->cur_ps, &ps, sizeof(playerState_t)); + memcpy(&bs->settings, &settings, sizeof(bot_settings_t)); + bs->inuse = inuse; + bs->client = client; + bs->entitynum = entitynum; + bs->character = character; + bs->entergame_time = entergame_time; + //reset several states + if (bs->ms) trap_BotResetMoveState(bs->ms); + if (bs->gs) trap_BotResetGoalState(bs->gs); + if (bs->ws) trap_BotResetWeaponState(bs->ws); + if (bs->gs) trap_BotResetAvoidGoals(bs->gs); + if (bs->ms) trap_BotResetAvoidReach(bs->ms); +} + +/* +============== +BotAILoadMap +============== +*/ +int BotAILoadMap( int restart ) { + int i; + vmCvar_t mapname; + + if (!restart) { + trap_Cvar_Register( &mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM ); + trap_BotLibLoadMap( mapname.string ); + } + + for (i = 0; i < MAX_CLIENTS; i++) { + if (botstates[i] && botstates[i]->inuse) { + BotResetState( botstates[i] ); + botstates[i]->setupcount = 4; + } + } + + BotSetupDeathmatchAI(); + + return qtrue; +} + +#ifdef MISSIONPACK +void ProximityMine_Trigger( gentity_t *trigger, gentity_t *other, trace_t *trace ); +#endif + +/* +================== +BotAIStartFrame +================== +*/ +int BotAIStartFrame(int time) { + int i; + gentity_t *ent; + bot_entitystate_t state; + int elapsed_time, thinktime; + static int local_time; + static int botlib_residual; + static int lastbotthink_time; + + G_CheckBotSpawn(); + + trap_Cvar_Update(&bot_rocketjump); + trap_Cvar_Update(&bot_grapple); + trap_Cvar_Update(&bot_fastchat); + trap_Cvar_Update(&bot_nochat); + trap_Cvar_Update(&bot_testrchat); + trap_Cvar_Update(&bot_thinktime); + trap_Cvar_Update(&bot_memorydump); + trap_Cvar_Update(&bot_saveroutingcache); + trap_Cvar_Update(&bot_pause); + trap_Cvar_Update(&bot_report); + + if (bot_report.integer) { +// BotTeamplayReport(); +// trap_Cvar_Set("bot_report", "0"); + BotUpdateInfoConfigStrings(); + } + + if (bot_pause.integer) { + // execute bot user commands every frame + for( i = 0; i < MAX_CLIENTS; i++ ) { + if( !botstates[i] || !botstates[i]->inuse ) { + continue; + } + if( g_entities[i].client->pers.connected != CON_CONNECTED ) { + continue; + } + botstates[i]->lastucmd.forwardmove = 0; + botstates[i]->lastucmd.rightmove = 0; + botstates[i]->lastucmd.upmove = 0; + botstates[i]->lastucmd.buttons = 0; + botstates[i]->lastucmd.serverTime = time; + trap_BotUserCommand(botstates[i]->client, &botstates[i]->lastucmd); + } + return qtrue; + } + + if (bot_memorydump.integer) { + trap_BotLibVarSet("memorydump", "1"); + trap_Cvar_Set("bot_memorydump", "0"); + } + if (bot_saveroutingcache.integer) { + trap_BotLibVarSet("saveroutingcache", "1"); + trap_Cvar_Set("bot_saveroutingcache", "0"); + } + //check if bot interbreeding is activated + BotInterbreeding(); + //cap the bot think time + if (bot_thinktime.integer > 200) { + trap_Cvar_Set("bot_thinktime", "200"); + } + //if the bot think time changed we should reschedule the bots + if (bot_thinktime.integer != lastbotthink_time) { + lastbotthink_time = bot_thinktime.integer; + BotScheduleBotThink(); + } + + elapsed_time = time - local_time; + local_time = time; + + botlib_residual += elapsed_time; + + if (elapsed_time > bot_thinktime.integer) thinktime = elapsed_time; + else thinktime = bot_thinktime.integer; + + // update the bot library + if ( botlib_residual >= thinktime ) { + botlib_residual -= thinktime; + + trap_BotLibStartFrame((float) time / 1000); + + if (!trap_AAS_Initialized()) return qfalse; + + //update entities in the botlib + for (i = 0; i < MAX_GENTITIES; i++) { + ent = &g_entities[i]; + if (!ent->inuse) { + trap_BotLibUpdateEntity(i, NULL); + continue; + } + if (!ent->r.linked) { + trap_BotLibUpdateEntity(i, NULL); + continue; + } + if (ent->r.svFlags & SVF_NOCLIENT) { + trap_BotLibUpdateEntity(i, NULL); + continue; + } + // do not update missiles + if (ent->s.eType == ET_MISSILE && ent->s.weapon != WP_GRAPPLING_HOOK) { + trap_BotLibUpdateEntity(i, NULL); + continue; + } + // do not update event only entities + if (ent->s.eType > ET_EVENTS) { + trap_BotLibUpdateEntity(i, NULL); + continue; + } +#ifdef MISSIONPACK + // never link prox mine triggers + if (ent->r.contents == CONTENTS_TRIGGER) { + if (ent->touch == ProximityMine_Trigger) { + trap_BotLibUpdateEntity(i, NULL); + continue; + } + } +#endif + // + memset(&state, 0, sizeof(bot_entitystate_t)); + // + VectorCopy(ent->r.currentOrigin, state.origin); + if (i < MAX_CLIENTS) { + VectorCopy(ent->s.apos.trBase, state.angles); + } else { + VectorCopy(ent->r.currentAngles, state.angles); + } + VectorCopy(ent->s.origin2, state.old_origin); + VectorCopy(ent->r.mins, state.mins); + VectorCopy(ent->r.maxs, state.maxs); + state.type = ent->s.eType; + state.flags = ent->s.eFlags; + if (ent->r.bmodel) state.solid = SOLID_BSP; + else state.solid = SOLID_BBOX; + state.groundent = ent->s.groundEntityNum; + state.modelindex = ent->s.modelindex; + state.modelindex2 = ent->s.modelindex2; + state.frame = ent->s.frame; + state.event = ent->s.event; + state.eventParm = ent->s.eventParm; + state.powerups = ent->s.powerups; + state.legsAnim = ent->s.legsAnim; + state.torsoAnim = ent->s.torsoAnim; + state.weapon = ent->s.weapon; + // + trap_BotLibUpdateEntity(i, &state); + } + + BotAIRegularUpdate(); + } + + floattime = trap_AAS_Time(); + + // execute scheduled bot AI + for( i = 0; i < MAX_CLIENTS; i++ ) { + if( !botstates[i] || !botstates[i]->inuse ) { + continue; + } + // + botstates[i]->botthink_residual += elapsed_time; + // + if ( botstates[i]->botthink_residual >= thinktime ) { + botstates[i]->botthink_residual -= thinktime; + + if (!trap_AAS_Initialized()) return qfalse; + + if (g_entities[i].client->pers.connected == CON_CONNECTED) { + BotAI(i, (float) thinktime / 1000); + } + } + } + + + // execute bot user commands every frame + for( i = 0; i < MAX_CLIENTS; i++ ) { + if( !botstates[i] || !botstates[i]->inuse ) { + continue; + } + if( g_entities[i].client->pers.connected != CON_CONNECTED ) { + continue; + } + + BotUpdateInput(botstates[i], time, elapsed_time); + trap_BotUserCommand(botstates[i]->client, &botstates[i]->lastucmd); + } + + return qtrue; +} + +/* +============== +BotInitLibrary +============== +*/ +int BotInitLibrary(void) { + char buf[144]; + + //set the maxclients and maxentities library variables before calling BotSetupLibrary + trap_Cvar_VariableStringBuffer("sv_maxclients", buf, sizeof(buf)); + if (!strlen(buf)) strcpy(buf, "8"); + trap_BotLibVarSet("maxclients", buf); + Com_sprintf(buf, sizeof(buf), "%d", MAX_GENTITIES); + trap_BotLibVarSet("maxentities", buf); + //bsp checksum + trap_Cvar_VariableStringBuffer("sv_mapChecksum", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("sv_mapChecksum", buf); + //maximum number of aas links + trap_Cvar_VariableStringBuffer("max_aaslinks", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("max_aaslinks", buf); + //maximum number of items in a level + trap_Cvar_VariableStringBuffer("max_levelitems", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("max_levelitems", buf); + //game type + trap_Cvar_VariableStringBuffer("g_gametype", buf, sizeof(buf)); + if (!strlen(buf)) strcpy(buf, "0"); + trap_BotLibVarSet("g_gametype", buf); + //bot developer mode and log file + trap_BotLibVarSet("bot_developer", bot_developer.string); + trap_BotLibVarSet("log", buf); + //no chatting + trap_Cvar_VariableStringBuffer("bot_nochat", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("nochat", "0"); + //visualize jump pads + trap_Cvar_VariableStringBuffer("bot_visualizejumppads", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("bot_visualizejumppads", buf); + //forced clustering calculations + trap_Cvar_VariableStringBuffer("bot_forceclustering", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("forceclustering", buf); + //forced reachability calculations + trap_Cvar_VariableStringBuffer("bot_forcereachability", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("forcereachability", buf); + //force writing of AAS to file + trap_Cvar_VariableStringBuffer("bot_forcewrite", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("forcewrite", buf); + //no AAS optimization + trap_Cvar_VariableStringBuffer("bot_aasoptimize", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("aasoptimize", buf); + // + trap_Cvar_VariableStringBuffer("bot_saveroutingcache", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("saveroutingcache", buf); + //reload instead of cache bot character files + trap_Cvar_VariableStringBuffer("bot_reloadcharacters", buf, sizeof(buf)); + if (!strlen(buf)) strcpy(buf, "0"); + trap_BotLibVarSet("bot_reloadcharacters", buf); + //base directory + trap_Cvar_VariableStringBuffer("fs_basepath", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("basedir", buf); + //game directory + trap_Cvar_VariableStringBuffer("fs_game", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("gamedir", buf); + //cd directory + trap_Cvar_VariableStringBuffer("fs_cdpath", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("cddir", buf); + // +#ifdef MISSIONPACK + trap_BotLibDefine("MISSIONPACK"); +#endif + //setup the bot library + return trap_BotLibSetup(); +} + +/* +============== +BotAISetup +============== +*/ +int BotAISetup( int restart ) { + int errnum; + + trap_Cvar_Register(&bot_thinktime, "bot_thinktime", "100", CVAR_CHEAT); + trap_Cvar_Register(&bot_memorydump, "bot_memorydump", "0", CVAR_CHEAT); + trap_Cvar_Register(&bot_saveroutingcache, "bot_saveroutingcache", "0", CVAR_CHEAT); + trap_Cvar_Register(&bot_pause, "bot_pause", "0", CVAR_CHEAT); + trap_Cvar_Register(&bot_report, "bot_report", "0", CVAR_CHEAT); + trap_Cvar_Register(&bot_testsolid, "bot_testsolid", "0", CVAR_CHEAT); + trap_Cvar_Register(&bot_testclusters, "bot_testclusters", "0", CVAR_CHEAT); + trap_Cvar_Register(&bot_developer, "bot_developer", "0", CVAR_CHEAT); + trap_Cvar_Register(&bot_interbreedchar, "bot_interbreedchar", "", 0); + trap_Cvar_Register(&bot_interbreedbots, "bot_interbreedbots", "10", 0); + trap_Cvar_Register(&bot_interbreedcycle, "bot_interbreedcycle", "20", 0); + trap_Cvar_Register(&bot_interbreedwrite, "bot_interbreedwrite", "", 0); + + //if the game is restarted for a tournament + if (restart) { + return qtrue; + } + + //initialize the bot states + memset( botstates, 0, sizeof(botstates) ); + + errnum = BotInitLibrary(); + if (errnum != BLERR_NOERROR) return qfalse; + return qtrue; +} + +/* +============== +BotAIShutdown +============== +*/ +int BotAIShutdown( int restart ) { + + int i; + + //if the game is restarted for a tournament + if ( restart ) { + //shutdown all the bots in the botlib + for (i = 0; i < MAX_CLIENTS; i++) { + if (botstates[i] && botstates[i]->inuse) { + BotAIShutdownClient(botstates[i]->client, restart); + } + } + //don't shutdown the bot library + } + else { + trap_BotLibShutdown(); + } + return qtrue; +} + diff --git a/code/game/ai_main.h b/code/game/ai_main.h index 91e8895..5d0e9ec 100755 --- a/code/game/ai_main.h +++ b/code/game/ai_main.h @@ -1,299 +1,299 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -/***************************************************************************** - * name: ai_main.h - * - * desc: Quake3 bot AI - * - * $Archive: /source/code/botai/ai_chat.c $ - * - *****************************************************************************/ - -//#define DEBUG -#define CTF - -#define MAX_ITEMS 256 -//bot flags -#define BFL_STRAFERIGHT 1 //strafe to the right -#define BFL_ATTACKED 2 //bot has attacked last ai frame -#define BFL_ATTACKJUMPED 4 //bot jumped during attack last frame -#define BFL_AIMATENEMY 8 //bot aimed at the enemy this frame -#define BFL_AVOIDRIGHT 16 //avoid obstacles by going to the right -#define BFL_IDEALVIEWSET 32 //bot has ideal view angles set -#define BFL_FIGHTSUICIDAL 64 //bot is in a suicidal fight -//long term goal types -#define LTG_TEAMHELP 1 //help a team mate -#define LTG_TEAMACCOMPANY 2 //accompany a team mate -#define LTG_DEFENDKEYAREA 3 //defend a key area -#define LTG_GETFLAG 4 //get the enemy flag -#define LTG_RUSHBASE 5 //rush to the base -#define LTG_RETURNFLAG 6 //return the flag -#define LTG_CAMP 7 //camp somewhere -#define LTG_CAMPORDER 8 //ordered to camp somewhere -#define LTG_PATROL 9 //patrol -#define LTG_GETITEM 10 //get an item -#define LTG_KILL 11 //kill someone -#define LTG_HARVEST 12 //harvest skulls -#define LTG_ATTACKENEMYBASE 13 //attack the enemy base -#define LTG_MAKELOVE_UNDER 14 -#define LTG_MAKELOVE_ONTOP 15 -//some goal dedication times -#define TEAM_HELP_TIME 60 //1 minute teamplay help time -#define TEAM_ACCOMPANY_TIME 600 //10 minutes teamplay accompany time -#define TEAM_DEFENDKEYAREA_TIME 600 //10 minutes ctf defend base time -#define TEAM_CAMP_TIME 600 //10 minutes camping time -#define TEAM_PATROL_TIME 600 //10 minutes patrolling time -#define TEAM_LEAD_TIME 600 //10 minutes taking the lead -#define TEAM_GETITEM_TIME 60 //1 minute -#define TEAM_KILL_SOMEONE 180 //3 minute to kill someone -#define TEAM_ATTACKENEMYBASE_TIME 600 //10 minutes -#define TEAM_HARVEST_TIME 120 //2 minutes -#define CTF_GETFLAG_TIME 600 //10 minutes ctf get flag time -#define CTF_RUSHBASE_TIME 120 //2 minutes ctf rush base time -#define CTF_RETURNFLAG_TIME 180 //3 minutes to return the flag -#define CTF_ROAM_TIME 60 //1 minute ctf roam time -//patrol flags -#define PATROL_LOOP 1 -#define PATROL_REVERSE 2 -#define PATROL_BACK 4 -//teamplay task preference -#define TEAMTP_DEFENDER 1 -#define TEAMTP_ATTACKER 2 -//CTF strategy -#define CTFS_AGRESSIVE 1 -//copied from the aas file header -#define PRESENCE_NONE 1 -#define PRESENCE_NORMAL 2 -#define PRESENCE_CROUCH 4 -// -#define MAX_PROXMINES 64 - -//check points -typedef struct bot_waypoint_s -{ - int inuse; - char name[32]; - bot_goal_t goal; - struct bot_waypoint_s *next, *prev; -} bot_waypoint_t; - -#define MAX_ACTIVATESTACK 8 -#define MAX_ACTIVATEAREAS 32 - -typedef struct bot_activategoal_s -{ - int inuse; - bot_goal_t goal; //goal to activate (buttons etc.) - float time; //time to activate something - float start_time; //time starting to activate something - float justused_time; //time the goal was used - int shoot; //true if bot has to shoot to activate - int weapon; //weapon to be used for activation - vec3_t target; //target to shoot at to activate something - vec3_t origin; //origin of the blocking entity to activate - int areas[MAX_ACTIVATEAREAS]; //routing areas disabled by blocking entity - int numareas; //number of disabled routing areas - int areasdisabled; //true if the areas are disabled for the routing - struct bot_activategoal_s *next; //next activate goal on stack -} bot_activategoal_t; - -//bot state -typedef struct bot_state_s -{ - int inuse; //true if this state is used by a bot client - int botthink_residual; //residual for the bot thinks - int client; //client number of the bot - int entitynum; //entity number of the bot - playerState_t cur_ps; //current player state - int last_eFlags; //last ps flags - usercmd_t lastucmd; //usercmd from last frame - int entityeventTime[1024]; //last entity event time - // - bot_settings_t settings; //several bot settings - int (*ainode)(struct bot_state_s *bs); //current AI node - float thinktime; //time the bot thinks this frame - vec3_t origin; //origin of the bot - vec3_t velocity; //velocity of the bot - int presencetype; //presence type of the bot - vec3_t eye; //eye coordinates of the bot - int areanum; //the number of the area the bot is in - int inventory[MAX_ITEMS]; //string with items amounts the bot has - int tfl; //the travel flags the bot uses - int flags; //several flags - int respawn_wait; //wait until respawned - int lasthealth; //health value previous frame - int lastkilledplayer; //last killed player - int lastkilledby; //player that last killed this bot - int botdeathtype; //the death type of the bot - int enemydeathtype; //the death type of the enemy - int botsuicide; //true when the bot suicides - int enemysuicide; //true when the enemy of the bot suicides - int setupcount; //true when the bot has just been setup - int map_restart; //true when the map is being restarted - int entergamechat; //true when the bot used an enter game chat - int num_deaths; //number of time this bot died - int num_kills; //number of kills of this bot - int revenge_enemy; //the revenge enemy - int revenge_kills; //number of kills the enemy made - int lastframe_health; //health value the last frame - int lasthitcount; //number of hits last frame - int chatto; //chat to all or team - float walker; //walker charactertic - float ltime; //local bot time - float entergame_time; //time the bot entered the game - float ltg_time; //long term goal time - float nbg_time; //nearby goal time - float respawn_time; //time the bot takes to respawn - float respawnchat_time; //time the bot started a chat during respawn - float chase_time; //time the bot will chase the enemy - float enemyvisible_time; //time the enemy was last visible - float check_time; //time to check for nearby items - float stand_time; //time the bot is standing still - float lastchat_time; //time the bot last selected a chat - float kamikaze_time; //time to check for kamikaze usage - float invulnerability_time; //time to check for invulnerability usage - float standfindenemy_time; //time to find enemy while standing - float attackstrafe_time; //time the bot is strafing in one dir - float attackcrouch_time; //time the bot will stop crouching - float attackchase_time; //time the bot chases during actual attack - float attackjump_time; //time the bot jumped during attack - float enemysight_time; //time before reacting to enemy - float enemydeath_time; //time the enemy died - float enemyposition_time; //time the position and velocity of the enemy were stored - float defendaway_time; //time away while defending - float defendaway_range; //max travel time away from defend area - float rushbaseaway_time; //time away from rushing to the base - float attackaway_time; //time away from attacking the enemy base - float harvestaway_time; //time away from harvesting - float ctfroam_time; //time the bot is roaming in ctf - float killedenemy_time; //time the bot killed the enemy - float arrive_time; //time arrived (at companion) - float lastair_time; //last time the bot had air - float teleport_time; //last time the bot teleported - float camp_time; //last time camped - float camp_range; //camp range - float weaponchange_time; //time the bot started changing weapons - float firethrottlewait_time; //amount of time to wait - float firethrottleshoot_time; //amount of time to shoot - float notblocked_time; //last time the bot was not blocked - float blockedbyavoidspot_time; //time blocked by an avoid spot - float predictobstacles_time; //last time the bot predicted obstacles - int predictobstacles_goalareanum; //last goal areanum the bot predicted obstacles for - vec3_t aimtarget; - vec3_t enemyvelocity; //enemy velocity 0.5 secs ago during battle - vec3_t enemyorigin; //enemy origin 0.5 secs ago during battle - // - int kamikazebody; //kamikaze body - int proxmines[MAX_PROXMINES]; - int numproxmines; - // - int character; //the bot character - int ms; //move state of the bot - int gs; //goal state of the bot - int cs; //chat state of the bot - int ws; //weapon state of the bot - // - int enemy; //enemy entity number - int lastenemyareanum; //last reachability area the enemy was in - vec3_t lastenemyorigin; //last origin of the enemy in the reachability area - int weaponnum; //current weapon number - vec3_t viewangles; //current view angles - vec3_t ideal_viewangles; //ideal view angles - vec3_t viewanglespeed; - // - int ltgtype; //long term goal type - // team goals - int teammate; //team mate involved in this team goal - int decisionmaker; //player who decided to go for this goal - int ordered; //true if ordered to do something - float order_time; //time ordered to do something - int owndecision_time; //time the bot made it's own decision - bot_goal_t teamgoal; //the team goal - bot_goal_t altroutegoal; //alternative route goal - float reachedaltroutegoal_time; //time the bot reached the alt route goal - float teammessage_time; //time to message team mates what the bot is doing - float teamgoal_time; //time to stop helping team mate - float teammatevisible_time; //last time the team mate was NOT visible - int teamtaskpreference; //team task preference - // last ordered team goal - int lastgoal_decisionmaker; - int lastgoal_ltgtype; - int lastgoal_teammate; - bot_goal_t lastgoal_teamgoal; - // for leading team mates - int lead_teammate; //team mate the bot is leading - bot_goal_t lead_teamgoal; //team goal while leading - float lead_time; //time leading someone - float leadvisible_time; //last time the team mate was visible - float leadmessage_time; //last time a messaged was sent to the team mate - float leadbackup_time; //time backing up towards team mate - // - char teamleader[32]; //netname of the team leader - float askteamleader_time; //time asked for team leader - float becometeamleader_time; //time the bot will become the team leader - float teamgiveorders_time; //time to give team orders - float lastflagcapture_time; //last time a flag was captured - int numteammates; //number of team mates - int redflagstatus; //0 = at base, 1 = not at base - int blueflagstatus; //0 = at base, 1 = not at base - int neutralflagstatus; //0 = at base, 1 = our team has flag, 2 = enemy team has flag, 3 = enemy team dropped the flag - int flagstatuschanged; //flag status changed - int forceorders; //true if forced to give orders - int flagcarrier; //team mate carrying the enemy flag - int ctfstrategy; //ctf strategy - char subteam[32]; //sub team name - float formation_dist; //formation team mate intervening space - char formation_teammate[16]; //netname of the team mate the bot uses for relative positioning - float formation_angle; //angle relative to the formation team mate - vec3_t formation_dir; //the direction the formation is moving in - vec3_t formation_origin; //origin the bot uses for relative positioning - bot_goal_t formation_goal; //formation goal - - bot_activategoal_t *activatestack; //first activate goal on the stack - bot_activategoal_t activategoalheap[MAX_ACTIVATESTACK]; //activate goal heap - - bot_waypoint_t *checkpoints; //check points - bot_waypoint_t *patrolpoints; //patrol points - bot_waypoint_t *curpatrolpoint; //current patrol point the bot is going for - int patrolflags; //patrol flags -} bot_state_t; - -//resets the whole bot state -void BotResetState(bot_state_t *bs); -//returns the number of bots in the game -int NumBots(void); -//returns info about the entity -void BotEntityInfo(int entnum, aas_entityinfo_t *info); - -extern float floattime; -#define FloatTime() floattime - -// from the game source -void QDECL BotAI_Print(int type, char *fmt, ...); -void QDECL QDECL BotAI_BotInitialChat( bot_state_t *bs, char *type, ... ); -void BotAI_Trace(bsp_trace_t *bsptrace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask); -int BotAI_GetClientState( int clientNum, playerState_t *state ); -int BotAI_GetEntityState( int entityNum, entityState_t *state ); -int BotAI_GetSnapshotEntity( int clientNum, int sequence, entityState_t *state ); -int BotTeamLeader(bot_state_t *bs); +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: ai_main.h + * + * desc: Quake3 bot AI + * + * $Archive: /source/code/botai/ai_chat.c $ + * + *****************************************************************************/ + +//#define DEBUG +#define CTF + +#define MAX_ITEMS 256 +//bot flags +#define BFL_STRAFERIGHT 1 //strafe to the right +#define BFL_ATTACKED 2 //bot has attacked last ai frame +#define BFL_ATTACKJUMPED 4 //bot jumped during attack last frame +#define BFL_AIMATENEMY 8 //bot aimed at the enemy this frame +#define BFL_AVOIDRIGHT 16 //avoid obstacles by going to the right +#define BFL_IDEALVIEWSET 32 //bot has ideal view angles set +#define BFL_FIGHTSUICIDAL 64 //bot is in a suicidal fight +//long term goal types +#define LTG_TEAMHELP 1 //help a team mate +#define LTG_TEAMACCOMPANY 2 //accompany a team mate +#define LTG_DEFENDKEYAREA 3 //defend a key area +#define LTG_GETFLAG 4 //get the enemy flag +#define LTG_RUSHBASE 5 //rush to the base +#define LTG_RETURNFLAG 6 //return the flag +#define LTG_CAMP 7 //camp somewhere +#define LTG_CAMPORDER 8 //ordered to camp somewhere +#define LTG_PATROL 9 //patrol +#define LTG_GETITEM 10 //get an item +#define LTG_KILL 11 //kill someone +#define LTG_HARVEST 12 //harvest skulls +#define LTG_ATTACKENEMYBASE 13 //attack the enemy base +#define LTG_MAKELOVE_UNDER 14 +#define LTG_MAKELOVE_ONTOP 15 +//some goal dedication times +#define TEAM_HELP_TIME 60 //1 minute teamplay help time +#define TEAM_ACCOMPANY_TIME 600 //10 minutes teamplay accompany time +#define TEAM_DEFENDKEYAREA_TIME 600 //10 minutes ctf defend base time +#define TEAM_CAMP_TIME 600 //10 minutes camping time +#define TEAM_PATROL_TIME 600 //10 minutes patrolling time +#define TEAM_LEAD_TIME 600 //10 minutes taking the lead +#define TEAM_GETITEM_TIME 60 //1 minute +#define TEAM_KILL_SOMEONE 180 //3 minute to kill someone +#define TEAM_ATTACKENEMYBASE_TIME 600 //10 minutes +#define TEAM_HARVEST_TIME 120 //2 minutes +#define CTF_GETFLAG_TIME 600 //10 minutes ctf get flag time +#define CTF_RUSHBASE_TIME 120 //2 minutes ctf rush base time +#define CTF_RETURNFLAG_TIME 180 //3 minutes to return the flag +#define CTF_ROAM_TIME 60 //1 minute ctf roam time +//patrol flags +#define PATROL_LOOP 1 +#define PATROL_REVERSE 2 +#define PATROL_BACK 4 +//teamplay task preference +#define TEAMTP_DEFENDER 1 +#define TEAMTP_ATTACKER 2 +//CTF strategy +#define CTFS_AGRESSIVE 1 +//copied from the aas file header +#define PRESENCE_NONE 1 +#define PRESENCE_NORMAL 2 +#define PRESENCE_CROUCH 4 +// +#define MAX_PROXMINES 64 + +//check points +typedef struct bot_waypoint_s +{ + int inuse; + char name[32]; + bot_goal_t goal; + struct bot_waypoint_s *next, *prev; +} bot_waypoint_t; + +#define MAX_ACTIVATESTACK 8 +#define MAX_ACTIVATEAREAS 32 + +typedef struct bot_activategoal_s +{ + int inuse; + bot_goal_t goal; //goal to activate (buttons etc.) + float time; //time to activate something + float start_time; //time starting to activate something + float justused_time; //time the goal was used + int shoot; //true if bot has to shoot to activate + int weapon; //weapon to be used for activation + vec3_t target; //target to shoot at to activate something + vec3_t origin; //origin of the blocking entity to activate + int areas[MAX_ACTIVATEAREAS]; //routing areas disabled by blocking entity + int numareas; //number of disabled routing areas + int areasdisabled; //true if the areas are disabled for the routing + struct bot_activategoal_s *next; //next activate goal on stack +} bot_activategoal_t; + +//bot state +typedef struct bot_state_s +{ + int inuse; //true if this state is used by a bot client + int botthink_residual; //residual for the bot thinks + int client; //client number of the bot + int entitynum; //entity number of the bot + playerState_t cur_ps; //current player state + int last_eFlags; //last ps flags + usercmd_t lastucmd; //usercmd from last frame + int entityeventTime[1024]; //last entity event time + // + bot_settings_t settings; //several bot settings + int (*ainode)(struct bot_state_s *bs); //current AI node + float thinktime; //time the bot thinks this frame + vec3_t origin; //origin of the bot + vec3_t velocity; //velocity of the bot + int presencetype; //presence type of the bot + vec3_t eye; //eye coordinates of the bot + int areanum; //the number of the area the bot is in + int inventory[MAX_ITEMS]; //string with items amounts the bot has + int tfl; //the travel flags the bot uses + int flags; //several flags + int respawn_wait; //wait until respawned + int lasthealth; //health value previous frame + int lastkilledplayer; //last killed player + int lastkilledby; //player that last killed this bot + int botdeathtype; //the death type of the bot + int enemydeathtype; //the death type of the enemy + int botsuicide; //true when the bot suicides + int enemysuicide; //true when the enemy of the bot suicides + int setupcount; //true when the bot has just been setup + int map_restart; //true when the map is being restarted + int entergamechat; //true when the bot used an enter game chat + int num_deaths; //number of time this bot died + int num_kills; //number of kills of this bot + int revenge_enemy; //the revenge enemy + int revenge_kills; //number of kills the enemy made + int lastframe_health; //health value the last frame + int lasthitcount; //number of hits last frame + int chatto; //chat to all or team + float walker; //walker charactertic + float ltime; //local bot time + float entergame_time; //time the bot entered the game + float ltg_time; //long term goal time + float nbg_time; //nearby goal time + float respawn_time; //time the bot takes to respawn + float respawnchat_time; //time the bot started a chat during respawn + float chase_time; //time the bot will chase the enemy + float enemyvisible_time; //time the enemy was last visible + float check_time; //time to check for nearby items + float stand_time; //time the bot is standing still + float lastchat_time; //time the bot last selected a chat + float kamikaze_time; //time to check for kamikaze usage + float invulnerability_time; //time to check for invulnerability usage + float standfindenemy_time; //time to find enemy while standing + float attackstrafe_time; //time the bot is strafing in one dir + float attackcrouch_time; //time the bot will stop crouching + float attackchase_time; //time the bot chases during actual attack + float attackjump_time; //time the bot jumped during attack + float enemysight_time; //time before reacting to enemy + float enemydeath_time; //time the enemy died + float enemyposition_time; //time the position and velocity of the enemy were stored + float defendaway_time; //time away while defending + float defendaway_range; //max travel time away from defend area + float rushbaseaway_time; //time away from rushing to the base + float attackaway_time; //time away from attacking the enemy base + float harvestaway_time; //time away from harvesting + float ctfroam_time; //time the bot is roaming in ctf + float killedenemy_time; //time the bot killed the enemy + float arrive_time; //time arrived (at companion) + float lastair_time; //last time the bot had air + float teleport_time; //last time the bot teleported + float camp_time; //last time camped + float camp_range; //camp range + float weaponchange_time; //time the bot started changing weapons + float firethrottlewait_time; //amount of time to wait + float firethrottleshoot_time; //amount of time to shoot + float notblocked_time; //last time the bot was not blocked + float blockedbyavoidspot_time; //time blocked by an avoid spot + float predictobstacles_time; //last time the bot predicted obstacles + int predictobstacles_goalareanum; //last goal areanum the bot predicted obstacles for + vec3_t aimtarget; + vec3_t enemyvelocity; //enemy velocity 0.5 secs ago during battle + vec3_t enemyorigin; //enemy origin 0.5 secs ago during battle + // + int kamikazebody; //kamikaze body + int proxmines[MAX_PROXMINES]; + int numproxmines; + // + int character; //the bot character + int ms; //move state of the bot + int gs; //goal state of the bot + int cs; //chat state of the bot + int ws; //weapon state of the bot + // + int enemy; //enemy entity number + int lastenemyareanum; //last reachability area the enemy was in + vec3_t lastenemyorigin; //last origin of the enemy in the reachability area + int weaponnum; //current weapon number + vec3_t viewangles; //current view angles + vec3_t ideal_viewangles; //ideal view angles + vec3_t viewanglespeed; + // + int ltgtype; //long term goal type + // team goals + int teammate; //team mate involved in this team goal + int decisionmaker; //player who decided to go for this goal + int ordered; //true if ordered to do something + float order_time; //time ordered to do something + int owndecision_time; //time the bot made it's own decision + bot_goal_t teamgoal; //the team goal + bot_goal_t altroutegoal; //alternative route goal + float reachedaltroutegoal_time; //time the bot reached the alt route goal + float teammessage_time; //time to message team mates what the bot is doing + float teamgoal_time; //time to stop helping team mate + float teammatevisible_time; //last time the team mate was NOT visible + int teamtaskpreference; //team task preference + // last ordered team goal + int lastgoal_decisionmaker; + int lastgoal_ltgtype; + int lastgoal_teammate; + bot_goal_t lastgoal_teamgoal; + // for leading team mates + int lead_teammate; //team mate the bot is leading + bot_goal_t lead_teamgoal; //team goal while leading + float lead_time; //time leading someone + float leadvisible_time; //last time the team mate was visible + float leadmessage_time; //last time a messaged was sent to the team mate + float leadbackup_time; //time backing up towards team mate + // + char teamleader[32]; //netname of the team leader + float askteamleader_time; //time asked for team leader + float becometeamleader_time; //time the bot will become the team leader + float teamgiveorders_time; //time to give team orders + float lastflagcapture_time; //last time a flag was captured + int numteammates; //number of team mates + int redflagstatus; //0 = at base, 1 = not at base + int blueflagstatus; //0 = at base, 1 = not at base + int neutralflagstatus; //0 = at base, 1 = our team has flag, 2 = enemy team has flag, 3 = enemy team dropped the flag + int flagstatuschanged; //flag status changed + int forceorders; //true if forced to give orders + int flagcarrier; //team mate carrying the enemy flag + int ctfstrategy; //ctf strategy + char subteam[32]; //sub team name + float formation_dist; //formation team mate intervening space + char formation_teammate[16]; //netname of the team mate the bot uses for relative positioning + float formation_angle; //angle relative to the formation team mate + vec3_t formation_dir; //the direction the formation is moving in + vec3_t formation_origin; //origin the bot uses for relative positioning + bot_goal_t formation_goal; //formation goal + + bot_activategoal_t *activatestack; //first activate goal on the stack + bot_activategoal_t activategoalheap[MAX_ACTIVATESTACK]; //activate goal heap + + bot_waypoint_t *checkpoints; //check points + bot_waypoint_t *patrolpoints; //patrol points + bot_waypoint_t *curpatrolpoint; //current patrol point the bot is going for + int patrolflags; //patrol flags +} bot_state_t; + +//resets the whole bot state +void BotResetState(bot_state_t *bs); +//returns the number of bots in the game +int NumBots(void); +//returns info about the entity +void BotEntityInfo(int entnum, aas_entityinfo_t *info); + +extern float floattime; +#define FloatTime() floattime + +// from the game source +void QDECL BotAI_Print(int type, char *fmt, ...); +void QDECL QDECL BotAI_BotInitialChat( bot_state_t *bs, char *type, ... ); +void BotAI_Trace(bsp_trace_t *bsptrace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask); +int BotAI_GetClientState( int clientNum, playerState_t *state ); +int BotAI_GetEntityState( int entityNum, entityState_t *state ); +int BotAI_GetSnapshotEntity( int clientNum, int sequence, entityState_t *state ); +int BotTeamLeader(bot_state_t *bs); diff --git a/code/game/ai_team.c b/code/game/ai_team.c index 9215702..44250d8 100755 --- a/code/game/ai_team.c +++ b/code/game/ai_team.c @@ -1,2080 +1,2080 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -/***************************************************************************** - * name: ai_team.c - * - * desc: Quake3 bot AI - * - * $Archive: /MissionPack/code/game/ai_team.c $ - * - *****************************************************************************/ - -#include "g_local.h" -#include "botlib.h" -#include "be_aas.h" -#include "be_ea.h" -#include "be_ai_char.h" -#include "be_ai_chat.h" -#include "be_ai_gen.h" -#include "be_ai_goal.h" -#include "be_ai_move.h" -#include "be_ai_weap.h" -// -#include "ai_main.h" -#include "ai_dmq3.h" -#include "ai_chat.h" -#include "ai_cmd.h" -#include "ai_dmnet.h" -#include "ai_team.h" -#include "ai_vcmd.h" - -#include "match.h" - -// for the voice chats -#include "../../ui/menudef.h" - -//ctf task preferences for a client -typedef struct bot_ctftaskpreference_s -{ - char name[36]; - int preference; -} bot_ctftaskpreference_t; - -bot_ctftaskpreference_t ctftaskpreferences[MAX_CLIENTS]; - - -/* -================== -BotValidTeamLeader -================== -*/ -int BotValidTeamLeader(bot_state_t *bs) { - if (!strlen(bs->teamleader)) return qfalse; - if (ClientFromName(bs->teamleader) == -1) return qfalse; - return qtrue; -} - -/* -================== -BotNumTeamMates -================== -*/ -int BotNumTeamMates(bot_state_t *bs) { - int i, numplayers; - char buf[MAX_INFO_STRING]; - static int maxclients; - - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - - numplayers = 0; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); - //if no config string or no name - if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; - //skip spectators - if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; - // - if (BotSameTeam(bs, i)) { - numplayers++; - } - } - return numplayers; -} - -/* -================== -BotClientTravelTimeToGoal -================== -*/ -int BotClientTravelTimeToGoal(int client, bot_goal_t *goal) { - playerState_t ps; - int areanum; - - BotAI_GetClientState(client, &ps); - areanum = BotPointAreaNum(ps.origin); - if (!areanum) return 1; - return trap_AAS_AreaTravelTimeToGoalArea(areanum, ps.origin, goal->areanum, TFL_DEFAULT); -} - -/* -================== -BotSortTeamMatesByBaseTravelTime -================== -*/ -int BotSortTeamMatesByBaseTravelTime(bot_state_t *bs, int *teammates, int maxteammates) { - - int i, j, k, numteammates, traveltime; - char buf[MAX_INFO_STRING]; - static int maxclients; - int traveltimes[MAX_CLIENTS]; - bot_goal_t *goal = NULL; - - if (gametype == GT_CTF || gametype == GT_1FCTF) { - if (BotTeam(bs) == TEAM_RED) - goal = &ctf_redflag; - else - goal = &ctf_blueflag; - } -#ifdef MISSIONPACK - else { - if (BotTeam(bs) == TEAM_RED) - goal = &redobelisk; - else - goal = &blueobelisk; - } -#endif - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - - numteammates = 0; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); - //if no config string or no name - if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; - //skip spectators - if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; - // - if (BotSameTeam(bs, i)) { - // - traveltime = BotClientTravelTimeToGoal(i, goal); - // - for (j = 0; j < numteammates; j++) { - if (traveltime < traveltimes[j]) { - for (k = numteammates; k > j; k--) { - traveltimes[k] = traveltimes[k-1]; - teammates[k] = teammates[k-1]; - } - break; - } - } - traveltimes[j] = traveltime; - teammates[j] = i; - numteammates++; - if (numteammates >= maxteammates) break; - } - } - return numteammates; -} - -/* -================== -BotSetTeamMateTaskPreference -================== -*/ -void BotSetTeamMateTaskPreference(bot_state_t *bs, int teammate, int preference) { - char teammatename[MAX_NETNAME]; - - ctftaskpreferences[teammate].preference = preference; - ClientName(teammate, teammatename, sizeof(teammatename)); - strcpy(ctftaskpreferences[teammate].name, teammatename); -} - -/* -================== -BotGetTeamMateTaskPreference -================== -*/ -int BotGetTeamMateTaskPreference(bot_state_t *bs, int teammate) { - char teammatename[MAX_NETNAME]; - - if (!ctftaskpreferences[teammate].preference) return 0; - ClientName(teammate, teammatename, sizeof(teammatename)); - if (Q_stricmp(teammatename, ctftaskpreferences[teammate].name)) return 0; - return ctftaskpreferences[teammate].preference; -} - -/* -================== -BotSortTeamMatesByTaskPreference -================== -*/ -int BotSortTeamMatesByTaskPreference(bot_state_t *bs, int *teammates, int numteammates) { - int defenders[MAX_CLIENTS], numdefenders; - int attackers[MAX_CLIENTS], numattackers; - int roamers[MAX_CLIENTS], numroamers; - int i, preference; - - numdefenders = numattackers = numroamers = 0; - for (i = 0; i < numteammates; i++) { - preference = BotGetTeamMateTaskPreference(bs, teammates[i]); - if (preference & TEAMTP_DEFENDER) { - defenders[numdefenders++] = teammates[i]; - } - else if (preference & TEAMTP_ATTACKER) { - attackers[numattackers++] = teammates[i]; - } - else { - roamers[numroamers++] = teammates[i]; - } - } - numteammates = 0; - //defenders at the front of the list - memcpy(&teammates[numteammates], defenders, numdefenders * sizeof(int)); - numteammates += numdefenders; - //roamers in the middle - memcpy(&teammates[numteammates], roamers, numroamers * sizeof(int)); - numteammates += numroamers; - //attacker in the back of the list - memcpy(&teammates[numteammates], attackers, numattackers * sizeof(int)); - numteammates += numattackers; - - return numteammates; -} - -/* -================== -BotSayTeamOrders -================== -*/ -void BotSayTeamOrderAlways(bot_state_t *bs, int toclient) { - char teamchat[MAX_MESSAGE_SIZE]; - char buf[MAX_MESSAGE_SIZE]; - char name[MAX_NETNAME]; - - //if the bot is talking to itself - if (bs->client == toclient) { - //don't show the message just put it in the console message queue - trap_BotGetChatMessage(bs->cs, buf, sizeof(buf)); - ClientName(bs->client, name, sizeof(name)); - Com_sprintf(teamchat, sizeof(teamchat), EC"(%s"EC")"EC": %s", name, buf); - trap_BotQueueConsoleMessage(bs->cs, CMS_CHAT, teamchat); - } - else { - trap_BotEnterChat(bs->cs, toclient, CHAT_TELL); - } -} - -/* -================== -BotSayTeamOrders -================== -*/ -void BotSayTeamOrder(bot_state_t *bs, int toclient) { -#ifdef MISSIONPACK - // voice chats only - char buf[MAX_MESSAGE_SIZE]; - - trap_BotGetChatMessage(bs->cs, buf, sizeof(buf)); -#else - BotSayTeamOrderAlways(bs, toclient); -#endif -} - -/* -================== -BotVoiceChat -================== -*/ -void BotVoiceChat(bot_state_t *bs, int toclient, char *voicechat) { -#ifdef MISSIONPACK - if (toclient == -1) - // voice only say team - trap_EA_Command(bs->client, va("vsay_team %s", voicechat)); - else - // voice only tell single player - trap_EA_Command(bs->client, va("vtell %d %s", toclient, voicechat)); -#endif -} - -/* -================== -BotVoiceChatOnly -================== -*/ -void BotVoiceChatOnly(bot_state_t *bs, int toclient, char *voicechat) { -#ifdef MISSIONPACK - if (toclient == -1) - // voice only say team - trap_EA_Command(bs->client, va("vosay_team %s", voicechat)); - else - // voice only tell single player - trap_EA_Command(bs->client, va("votell %d %s", toclient, voicechat)); -#endif -} - -/* -================== -BotSayVoiceTeamOrder -================== -*/ -void BotSayVoiceTeamOrder(bot_state_t *bs, int toclient, char *voicechat) { -#ifdef MISSIONPACK - BotVoiceChat(bs, toclient, voicechat); -#endif -} - -/* -================== -BotCTFOrders -================== -*/ -void BotCTFOrders_BothFlagsNotAtBase(bot_state_t *bs) { - int numteammates, defenders, attackers, i, other; - int teammates[MAX_CLIENTS]; - char name[MAX_NETNAME], carriername[MAX_NETNAME]; - - numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); - BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); - //different orders based on the number of team mates - switch(bs->numteammates) { - case 1: break; - case 2: - { - //tell the one not carrying the flag to attack the enemy base - if (teammates[0] != bs->flagcarrier) other = teammates[0]; - else other = teammates[1]; - ClientName(other, name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, other); - BotSayVoiceTeamOrder(bs, other, VOICECHAT_GETFLAG); - break; - } - case 3: - { - //tell the one closest to the base not carrying the flag to accompany the flag carrier - if (teammates[0] != bs->flagcarrier) other = teammates[0]; - else other = teammates[1]; - ClientName(other, name, sizeof(name)); - if ( bs->flagcarrier != -1 ) { - ClientName(bs->flagcarrier, carriername, sizeof(carriername)); - if (bs->flagcarrier == bs->client) { - BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); - BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWME); - } - else { - BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); - BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWFLAGCARRIER); - } - } - else { - // - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayVoiceTeamOrder(bs, other, VOICECHAT_GETFLAG); - } - BotSayTeamOrder(bs, other); - //tell the one furthest from the the base not carrying the flag to get the enemy flag - if (teammates[2] != bs->flagcarrier) other = teammates[2]; - else other = teammates[1]; - ClientName(other, name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, other); - BotSayVoiceTeamOrder(bs, other, VOICECHAT_RETURNFLAG); - break; - } - default: - { - defenders = (int) (float) numteammates * 0.4 + 0.5; - if (defenders > 4) defenders = 4; - attackers = (int) (float) numteammates * 0.5 + 0.5; - if (attackers > 5) attackers = 5; - if (bs->flagcarrier != -1) { - ClientName(bs->flagcarrier, carriername, sizeof(carriername)); - for (i = 0; i < defenders; i++) { - // - if (teammates[i] == bs->flagcarrier) { - continue; - } - // - ClientName(teammates[i], name, sizeof(name)); - if (bs->flagcarrier == bs->client) { - BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); - BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_FOLLOWME); - } - else { - BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); - BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_FOLLOWFLAGCARRIER); - } - BotSayTeamOrder(bs, teammates[i]); - } - } - else { - for (i = 0; i < defenders; i++) { - // - if (teammates[i] == bs->flagcarrier) { - continue; - } - // - ClientName(teammates[i], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_GETFLAG); - BotSayTeamOrder(bs, teammates[i]); - } - } - for (i = 0; i < attackers; i++) { - // - if (teammates[numteammates - i - 1] == bs->flagcarrier) { - continue; - } - // - ClientName(teammates[numteammates - i - 1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[numteammates - i - 1]); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_RETURNFLAG); - } - // - break; - } - } -} - -/* -================== -BotCTFOrders -================== -*/ -void BotCTFOrders_FlagNotAtBase(bot_state_t *bs) { - int numteammates, defenders, attackers, i; - int teammates[MAX_CLIENTS]; - char name[MAX_NETNAME]; - - numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); - BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); - //passive strategy - if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { - //different orders based on the number of team mates - switch(bs->numteammates) { - case 1: break; - case 2: - { - //both will go for the enemy flag - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_GETFLAG); - // - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); - break; - } - case 3: - { - //keep one near the base for when the flag is returned - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the other two get the flag - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); - // - ClientName(teammates[2], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[2]); - BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); - break; - } - default: - { - //keep some people near the base for when the flag is returned - defenders = (int) (float) numteammates * 0.3 + 0.5; - if (defenders > 3) defenders = 3; - attackers = (int) (float) numteammates * 0.7 + 0.5; - if (attackers > 6) attackers = 6; - for (i = 0; i < defenders; i++) { - // - ClientName(teammates[i], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[i]); - BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); - } - for (i = 0; i < attackers; i++) { - // - ClientName(teammates[numteammates - i - 1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[numteammates - i - 1]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_GETFLAG); - } - // - break; - } - } - } - else { - //different orders based on the number of team mates - switch(bs->numteammates) { - case 1: break; - case 2: - { - //both will go for the enemy flag - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_GETFLAG); - // - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); - break; - } - case 3: - { - //everyone go for the flag - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_GETFLAG); - // - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); - // - ClientName(teammates[2], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[2]); - BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); - break; - } - default: - { - //keep some people near the base for when the flag is returned - defenders = (int) (float) numteammates * 0.2 + 0.5; - if (defenders > 2) defenders = 2; - attackers = (int) (float) numteammates * 0.7 + 0.5; - if (attackers > 7) attackers = 7; - for (i = 0; i < defenders; i++) { - // - ClientName(teammates[i], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[i]); - BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); - } - for (i = 0; i < attackers; i++) { - // - ClientName(teammates[numteammates - i - 1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[numteammates - i - 1]); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); - } - // - break; - } - } - } -} - -/* -================== -BotCTFOrders -================== -*/ -void BotCTFOrders_EnemyFlagNotAtBase(bot_state_t *bs) { - int numteammates, defenders, attackers, i, other; - int teammates[MAX_CLIENTS]; - char name[MAX_NETNAME], carriername[MAX_NETNAME]; - - numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); - BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); - //different orders based on the number of team mates - switch(numteammates) { - case 1: break; - case 2: - { - //tell the one not carrying the flag to defend the base - if (teammates[0] == bs->flagcarrier) other = teammates[1]; - else other = teammates[0]; - ClientName(other, name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, other); - BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); - break; - } - case 3: - { - //tell the one closest to the base not carrying the flag to defend the base - if (teammates[0] != bs->flagcarrier) other = teammates[0]; - else other = teammates[1]; - ClientName(other, name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, other); - BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); - //tell the other also to defend the base - if (teammates[2] != bs->flagcarrier) other = teammates[2]; - else other = teammates[1]; - ClientName(other, name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, other); - BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); - break; - } - default: - { - //60% will defend the base - defenders = (int) (float) numteammates * 0.6 + 0.5; - if (defenders > 6) defenders = 6; - //30% accompanies the flag carrier - attackers = (int) (float) numteammates * 0.3 + 0.5; - if (attackers > 3) attackers = 3; - for (i = 0; i < defenders; i++) { - // - if (teammates[i] == bs->flagcarrier) { - continue; - } - ClientName(teammates[i], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[i]); - BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); - } - // if we have a flag carrier - if ( bs->flagcarrier != -1 ) { - ClientName(bs->flagcarrier, carriername, sizeof(carriername)); - for (i = 0; i < attackers; i++) { - // - if (teammates[numteammates - i - 1] == bs->flagcarrier) { - continue; - } - // - ClientName(teammates[numteammates - i - 1], name, sizeof(name)); - if (bs->flagcarrier == bs->client) { - BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWME); - } - else { - BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWFLAGCARRIER); - } - BotSayTeamOrder(bs, teammates[numteammates - i - 1]); - } - } - else { - for (i = 0; i < attackers; i++) { - // - if (teammates[numteammates - i - 1] == bs->flagcarrier) { - continue; - } - // - ClientName(teammates[numteammates - i - 1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); - BotSayTeamOrder(bs, teammates[numteammates - i - 1]); - } - } - // - break; - } - } -} - - -/* -================== -BotCTFOrders -================== -*/ -void BotCTFOrders_BothFlagsAtBase(bot_state_t *bs) { - int numteammates, defenders, attackers, i; - int teammates[MAX_CLIENTS]; - char name[MAX_NETNAME]; - - //sort team mates by travel time to base - numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); - //sort team mates by CTF preference - BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); - //passive strategy - if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { - //different orders based on the number of team mates - switch(numteammates) { - case 1: break; - case 2: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the other will get the flag - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); - break; - } - case 3: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the second one closest to the base will defend the base - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); - //the other will get the flag - ClientName(teammates[2], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[2]); - BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); - break; - } - default: - { - defenders = (int) (float) numteammates * 0.5 + 0.5; - if (defenders > 5) defenders = 5; - attackers = (int) (float) numteammates * 0.4 + 0.5; - if (attackers > 4) attackers = 4; - for (i = 0; i < defenders; i++) { - // - ClientName(teammates[i], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[i]); - BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); - } - for (i = 0; i < attackers; i++) { - // - ClientName(teammates[numteammates - i - 1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[numteammates - i - 1]); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); - } - // - break; - } - } - } - else { - //different orders based on the number of team mates - switch(numteammates) { - case 1: break; - case 2: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the other will get the flag - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); - break; - } - case 3: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the others should go for the enemy flag - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); - // - ClientName(teammates[2], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[2]); - BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); - break; - } - default: - { - defenders = (int) (float) numteammates * 0.4 + 0.5; - if (defenders > 4) defenders = 4; - attackers = (int) (float) numteammates * 0.5 + 0.5; - if (attackers > 5) attackers = 5; - for (i = 0; i < defenders; i++) { - // - ClientName(teammates[i], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[i]); - BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); - } - for (i = 0; i < attackers; i++) { - // - ClientName(teammates[numteammates - i - 1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[numteammates - i - 1]); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); - } - // - break; - } - } - } -} - -/* -================== -BotCTFOrders -================== -*/ -void BotCTFOrders(bot_state_t *bs) { - int flagstatus; - - // - if (BotTeam(bs) == TEAM_RED) flagstatus = bs->redflagstatus * 2 + bs->blueflagstatus; - else flagstatus = bs->blueflagstatus * 2 + bs->redflagstatus; - // - switch(flagstatus) { - case 0: BotCTFOrders_BothFlagsAtBase(bs); break; - case 1: BotCTFOrders_EnemyFlagNotAtBase(bs); break; - case 2: BotCTFOrders_FlagNotAtBase(bs); break; - case 3: BotCTFOrders_BothFlagsNotAtBase(bs); break; - } -} - - -/* -================== -BotCreateGroup -================== -*/ -void BotCreateGroup(bot_state_t *bs, int *teammates, int groupsize) { - char name[MAX_NETNAME], leadername[MAX_NETNAME]; - int i; - - // the others in the group will follow the teammates[0] - ClientName(teammates[0], leadername, sizeof(leadername)); - for (i = 1; i < groupsize; i++) - { - ClientName(teammates[i], name, sizeof(name)); - if (teammates[0] == bs->client) { - BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); - } - else { - BotAI_BotInitialChat(bs, "cmd_accompany", name, leadername, NULL); - } - BotSayTeamOrderAlways(bs, teammates[i]); - } -} - -/* -================== -BotTeamOrders - - FIXME: defend key areas? -================== -*/ -void BotTeamOrders(bot_state_t *bs) { - int teammates[MAX_CLIENTS]; - int numteammates, i; - char buf[MAX_INFO_STRING]; - static int maxclients; - - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - - numteammates = 0; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); - //if no config string or no name - if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; - //skip spectators - if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; - // - if (BotSameTeam(bs, i)) { - teammates[numteammates] = i; - numteammates++; - } - } - // - switch(numteammates) { - case 1: break; - case 2: - { - //nothing special - break; - } - case 3: - { - //have one follow another and one free roaming - BotCreateGroup(bs, teammates, 2); - break; - } - case 4: - { - BotCreateGroup(bs, teammates, 2); //a group of 2 - BotCreateGroup(bs, &teammates[2], 2); //a group of 2 - break; - } - case 5: - { - BotCreateGroup(bs, teammates, 2); //a group of 2 - BotCreateGroup(bs, &teammates[2], 3); //a group of 3 - break; - } - default: - { - if (numteammates <= 10) { - for (i = 0; i < numteammates / 2; i++) { - BotCreateGroup(bs, &teammates[i*2], 2); //groups of 2 - } - } - break; - } - } -} - -#ifdef MISSIONPACK - -/* -================== -Bot1FCTFOrders_FlagAtCenter - - X% defend the base, Y% get the flag -================== -*/ -void Bot1FCTFOrders_FlagAtCenter(bot_state_t *bs) { - int numteammates, defenders, attackers, i; - int teammates[MAX_CLIENTS]; - char name[MAX_NETNAME]; - - //sort team mates by travel time to base - numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); - //sort team mates by CTF preference - BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); - //passive strategy - if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { - //different orders based on the number of team mates - switch(numteammates) { - case 1: break; - case 2: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the other will get the flag - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); - break; - } - case 3: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the second one closest to the base will defend the base - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the other will get the flag - ClientName(teammates[2], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[2]); - BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); - break; - } - default: - { - //50% defend the base - defenders = (int) (float) numteammates * 0.5 + 0.5; - if (defenders > 5) defenders = 5; - //40% get the flag - attackers = (int) (float) numteammates * 0.4 + 0.5; - if (attackers > 4) attackers = 4; - for (i = 0; i < defenders; i++) { - // - ClientName(teammates[i], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[i]); - BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); - } - for (i = 0; i < attackers; i++) { - // - ClientName(teammates[numteammates - i - 1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[numteammates - i - 1]); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); - } - // - break; - } - } - } - else { //agressive - //different orders based on the number of team mates - switch(numteammates) { - case 1: break; - case 2: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the other will get the flag - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); - break; - } - case 3: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the others should go for the enemy flag - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); - // - ClientName(teammates[2], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[2]); - BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); - break; - } - default: - { - //30% defend the base - defenders = (int) (float) numteammates * 0.3 + 0.5; - if (defenders > 3) defenders = 3; - //60% get the flag - attackers = (int) (float) numteammates * 0.6 + 0.5; - if (attackers > 6) attackers = 6; - for (i = 0; i < defenders; i++) { - // - ClientName(teammates[i], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[i]); - BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); - } - for (i = 0; i < attackers; i++) { - // - ClientName(teammates[numteammates - i - 1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[numteammates - i - 1]); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); - } - // - break; - } - } - } -} - -/* -================== -Bot1FCTFOrders_TeamHasFlag - - X% towards neutral flag, Y% go towards enemy base and accompany flag carrier if visible -================== -*/ -void Bot1FCTFOrders_TeamHasFlag(bot_state_t *bs) { - int numteammates, defenders, attackers, i, other; - int teammates[MAX_CLIENTS]; - char name[MAX_NETNAME], carriername[MAX_NETNAME]; - - //sort team mates by travel time to base - numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); - //sort team mates by CTF preference - BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); - //passive strategy - if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { - //different orders based on the number of team mates - switch(numteammates) { - case 1: break; - case 2: - { - //tell the one not carrying the flag to attack the enemy base - if (teammates[0] == bs->flagcarrier) other = teammates[1]; - else other = teammates[0]; - ClientName(other, name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); - BotSayTeamOrder(bs, other); - BotSayVoiceTeamOrder(bs, other, VOICECHAT_OFFENSE); - break; - } - case 3: - { - //tell the one closest to the base not carrying the flag to defend the base - if (teammates[0] != bs->flagcarrier) other = teammates[0]; - else other = teammates[1]; - ClientName(other, name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, other); - BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); - //tell the one furthest from the base not carrying the flag to accompany the flag carrier - if (teammates[2] != bs->flagcarrier) other = teammates[2]; - else other = teammates[1]; - ClientName(other, name, sizeof(name)); - if ( bs->flagcarrier != -1 ) { - ClientName(bs->flagcarrier, carriername, sizeof(carriername)); - if (bs->flagcarrier == bs->client) { - BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); - BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWME); - } - else { - BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); - BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWFLAGCARRIER); - } - } - else { - // - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayVoiceTeamOrder(bs, other, VOICECHAT_GETFLAG); - } - BotSayTeamOrder(bs, other); - break; - } - default: - { - //30% will defend the base - defenders = (int) (float) numteammates * 0.3 + 0.5; - if (defenders > 3) defenders = 3; - //70% accompanies the flag carrier - attackers = (int) (float) numteammates * 0.7 + 0.5; - if (attackers > 7) attackers = 7; - for (i = 0; i < defenders; i++) { - // - if (teammates[i] == bs->flagcarrier) { - continue; - } - ClientName(teammates[i], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[i]); - BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); - } - if (bs->flagcarrier != -1) { - ClientName(bs->flagcarrier, carriername, sizeof(carriername)); - for (i = 0; i < attackers; i++) { - // - if (teammates[numteammates - i - 1] == bs->flagcarrier) { - continue; - } - // - ClientName(teammates[numteammates - i - 1], name, sizeof(name)); - if (bs->flagcarrier == bs->client) { - BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWME); - } - else { - BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWFLAGCARRIER); - } - BotSayTeamOrder(bs, teammates[numteammates - i - 1]); - } - } - else { - for (i = 0; i < attackers; i++) { - // - if (teammates[numteammates - i - 1] == bs->flagcarrier) { - continue; - } - // - ClientName(teammates[numteammates - i - 1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[numteammates - i - 1]); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); - } - } - // - break; - } - } - } - else { //agressive - //different orders based on the number of team mates - switch(numteammates) { - case 1: break; - case 2: - { - //tell the one not carrying the flag to defend the base - if (teammates[0] == bs->flagcarrier) other = teammates[1]; - else other = teammates[0]; - ClientName(other, name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, other); - BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); - break; - } - case 3: - { - //tell the one closest to the base not carrying the flag to defend the base - if (teammates[0] != bs->flagcarrier) other = teammates[0]; - else other = teammates[1]; - ClientName(other, name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, other); - BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); - //tell the one furthest from the base not carrying the flag to accompany the flag carrier - if (teammates[2] != bs->flagcarrier) other = teammates[2]; - else other = teammates[1]; - ClientName(other, name, sizeof(name)); - ClientName(bs->flagcarrier, carriername, sizeof(carriername)); - if (bs->flagcarrier == bs->client) { - BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); - BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWME); - } - else { - BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); - BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWFLAGCARRIER); - } - BotSayTeamOrder(bs, other); - break; - } - default: - { - //20% will defend the base - defenders = (int) (float) numteammates * 0.2 + 0.5; - if (defenders > 2) defenders = 2; - //80% accompanies the flag carrier - attackers = (int) (float) numteammates * 0.8 + 0.5; - if (attackers > 8) attackers = 8; - for (i = 0; i < defenders; i++) { - // - if (teammates[i] == bs->flagcarrier) { - continue; - } - ClientName(teammates[i], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[i]); - BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); - } - ClientName(bs->flagcarrier, carriername, sizeof(carriername)); - for (i = 0; i < attackers; i++) { - // - if (teammates[numteammates - i - 1] == bs->flagcarrier) { - continue; - } - // - ClientName(teammates[numteammates - i - 1], name, sizeof(name)); - if (bs->flagcarrier == bs->client) { - BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWME); - } - else { - BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWFLAGCARRIER); - } - BotSayTeamOrder(bs, teammates[numteammates - i - 1]); - } - // - break; - } - } - } -} - -/* -================== -Bot1FCTFOrders_EnemyHasFlag - - X% defend the base, Y% towards neutral flag -================== -*/ -void Bot1FCTFOrders_EnemyHasFlag(bot_state_t *bs) { - int numteammates, defenders, attackers, i; - int teammates[MAX_CLIENTS]; - char name[MAX_NETNAME]; - - //sort team mates by travel time to base - numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); - //sort team mates by CTF preference - BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); - //passive strategy - if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { - //different orders based on the number of team mates - switch(numteammates) { - case 1: break; - case 2: - { - //both defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - // - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); - break; - } - case 3: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the second one closest to the base will defend the base - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); - //the other will also defend the base - ClientName(teammates[2], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[2]); - BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_DEFEND); - break; - } - default: - { - //80% will defend the base - defenders = (int) (float) numteammates * 0.8 + 0.5; - if (defenders > 8) defenders = 8; - //10% will try to return the flag - attackers = (int) (float) numteammates * 0.1 + 0.5; - if (attackers > 2) attackers = 2; - for (i = 0; i < defenders; i++) { - // - ClientName(teammates[i], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[i]); - BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); - } - for (i = 0; i < attackers; i++) { - // - ClientName(teammates[numteammates - i - 1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_returnflag", name, NULL); - BotSayTeamOrder(bs, teammates[numteammates - i - 1]); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); - } - // - break; - } - } - } - else { //agressive - //different orders based on the number of team mates - switch(numteammates) { - case 1: break; - case 2: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the other will get the flag - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); - break; - } - case 3: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the others should go for the enemy flag - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); - // - ClientName(teammates[2], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_returnflag", name, NULL); - BotSayTeamOrder(bs, teammates[2]); - BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); - break; - } - default: - { - //70% defend the base - defenders = (int) (float) numteammates * 0.7 + 0.5; - if (defenders > 8) defenders = 8; - //20% try to return the flag - attackers = (int) (float) numteammates * 0.2 + 0.5; - if (attackers > 2) attackers = 2; - for (i = 0; i < defenders; i++) { - // - ClientName(teammates[i], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[i]); - BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); - } - for (i = 0; i < attackers; i++) { - // - ClientName(teammates[numteammates - i - 1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_returnflag", name, NULL); - BotSayTeamOrder(bs, teammates[numteammates - i - 1]); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); - } - // - break; - } - } - } -} - -/* -================== -Bot1FCTFOrders_EnemyDroppedFlag - - X% defend the base, Y% get the flag -================== -*/ -void Bot1FCTFOrders_EnemyDroppedFlag(bot_state_t *bs) { - int numteammates, defenders, attackers, i; - int teammates[MAX_CLIENTS]; - char name[MAX_NETNAME]; - - //sort team mates by travel time to base - numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); - //sort team mates by CTF preference - BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); - //passive strategy - if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { - //different orders based on the number of team mates - switch(numteammates) { - case 1: break; - case 2: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the other will get the flag - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); - break; - } - case 3: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the second one closest to the base will defend the base - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); - //the other will get the flag - ClientName(teammates[2], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[2]); - BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); - break; - } - default: - { - //50% defend the base - defenders = (int) (float) numteammates * 0.5 + 0.5; - if (defenders > 5) defenders = 5; - //40% get the flag - attackers = (int) (float) numteammates * 0.4 + 0.5; - if (attackers > 4) attackers = 4; - for (i = 0; i < defenders; i++) { - // - ClientName(teammates[i], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[i]); - BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); - } - for (i = 0; i < attackers; i++) { - // - ClientName(teammates[numteammates - i - 1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[numteammates - i - 1]); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); - } - // - break; - } - } - } - else { //agressive - //different orders based on the number of team mates - switch(numteammates) { - case 1: break; - case 2: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the other will get the flag - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); - break; - } - case 3: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the others should go for the enemy flag - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); - // - ClientName(teammates[2], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[2]); - BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); - break; - } - default: - { - //30% defend the base - defenders = (int) (float) numteammates * 0.3 + 0.5; - if (defenders > 3) defenders = 3; - //60% get the flag - attackers = (int) (float) numteammates * 0.6 + 0.5; - if (attackers > 6) attackers = 6; - for (i = 0; i < defenders; i++) { - // - ClientName(teammates[i], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[i]); - BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); - } - for (i = 0; i < attackers; i++) { - // - ClientName(teammates[numteammates - i - 1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[numteammates - i - 1]); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_DEFEND); - } - // - break; - } - } - } -} - -/* -================== -Bot1FCTFOrders -================== -*/ -void Bot1FCTFOrders(bot_state_t *bs) { - switch(bs->neutralflagstatus) { - case 0: Bot1FCTFOrders_FlagAtCenter(bs); break; - case 1: Bot1FCTFOrders_TeamHasFlag(bs); break; - case 2: Bot1FCTFOrders_EnemyHasFlag(bs); break; - case 3: Bot1FCTFOrders_EnemyDroppedFlag(bs); break; - } -} - -/* -================== -BotObeliskOrders - - X% in defence Y% in offence -================== -*/ -void BotObeliskOrders(bot_state_t *bs) { - int numteammates, defenders, attackers, i; - int teammates[MAX_CLIENTS]; - char name[MAX_NETNAME]; - - //sort team mates by travel time to base - numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); - //sort team mates by CTF preference - BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); - //passive strategy - if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { - //different orders based on the number of team mates - switch(numteammates) { - case 1: break; - case 2: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the other will attack the enemy base - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); - break; - } - case 3: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the one second closest to the base also defends the base - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); - //the other one attacks the enemy base - ClientName(teammates[2], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); - BotSayTeamOrder(bs, teammates[2]); - BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_OFFENSE); - break; - } - default: - { - //50% defend the base - defenders = (int) (float) numteammates * 0.5 + 0.5; - if (defenders > 5) defenders = 5; - //40% attack the enemy base - attackers = (int) (float) numteammates * 0.4 + 0.5; - if (attackers > 4) attackers = 4; - for (i = 0; i < defenders; i++) { - // - ClientName(teammates[i], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[i]); - BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); - } - for (i = 0; i < attackers; i++) { - // - ClientName(teammates[numteammates - i - 1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); - BotSayTeamOrder(bs, teammates[numteammates - i - 1]); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_OFFENSE); - } - // - break; - } - } - } - else { - //different orders based on the number of team mates - switch(numteammates) { - case 1: break; - case 2: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the other will attack the enemy base - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); - break; - } - case 3: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the others attack the enemy base - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); - // - ClientName(teammates[2], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); - BotSayTeamOrder(bs, teammates[2]); - BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_OFFENSE); - break; - } - default: - { - //30% defend the base - defenders = (int) (float) numteammates * 0.3 + 0.5; - if (defenders > 3) defenders = 3; - //70% attack the enemy base - attackers = (int) (float) numteammates * 0.7 + 0.5; - if (attackers > 7) attackers = 7; - for (i = 0; i < defenders; i++) { - // - ClientName(teammates[i], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[i]); - BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); - } - for (i = 0; i < attackers; i++) { - // - ClientName(teammates[numteammates - i - 1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); - BotSayTeamOrder(bs, teammates[numteammates - i - 1]); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_OFFENSE); - } - // - break; - } - } - } -} - -/* -================== -BotHarvesterOrders - - X% defend the base, Y% harvest -================== -*/ -void BotHarvesterOrders(bot_state_t *bs) { - int numteammates, defenders, attackers, i; - int teammates[MAX_CLIENTS]; - char name[MAX_NETNAME]; - - //sort team mates by travel time to base - numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); - //sort team mates by CTF preference - BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); - //passive strategy - if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { - //different orders based on the number of team mates - switch(numteammates) { - case 1: break; - case 2: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the other will harvest - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); - break; - } - case 3: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the one second closest to the base also defends the base - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); - //the other one goes harvesting - ClientName(teammates[2], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); - BotSayTeamOrder(bs, teammates[2]); - BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_OFFENSE); - break; - } - default: - { - //50% defend the base - defenders = (int) (float) numteammates * 0.5 + 0.5; - if (defenders > 5) defenders = 5; - //40% goes harvesting - attackers = (int) (float) numteammates * 0.4 + 0.5; - if (attackers > 4) attackers = 4; - for (i = 0; i < defenders; i++) { - // - ClientName(teammates[i], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[i]); - BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); - } - for (i = 0; i < attackers; i++) { - // - ClientName(teammates[numteammates - i - 1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); - BotSayTeamOrder(bs, teammates[numteammates - i - 1]); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_OFFENSE); - } - // - break; - } - } - } - else { - //different orders based on the number of team mates - switch(numteammates) { - case 1: break; - case 2: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the other will harvest - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); - break; - } - case 3: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the others go harvesting - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); - // - ClientName(teammates[2], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); - BotSayTeamOrder(bs, teammates[2]); - BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_OFFENSE); - break; - } - default: - { - //30% defend the base - defenders = (int) (float) numteammates * 0.3 + 0.5; - if (defenders > 3) defenders = 3; - //70% go harvesting - attackers = (int) (float) numteammates * 0.7 + 0.5; - if (attackers > 7) attackers = 7; - for (i = 0; i < defenders; i++) { - // - ClientName(teammates[i], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[i]); - BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); - } - for (i = 0; i < attackers; i++) { - // - ClientName(teammates[numteammates - i - 1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); - BotSayTeamOrder(bs, teammates[numteammates - i - 1]); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_OFFENSE); - } - // - break; - } - } - } -} -#endif - -/* -================== -FindHumanTeamLeader -================== -*/ -int FindHumanTeamLeader(bot_state_t *bs) { - int i; - - for (i = 0; i < MAX_CLIENTS; i++) { - if ( g_entities[i].inuse ) { - // if this player is not a bot - if ( !(g_entities[i].r.svFlags & SVF_BOT) ) { - // if this player is ok with being the leader - if (!notleader[i]) { - // if this player is on the same team - if ( BotSameTeam(bs, i) ) { - ClientName(i, bs->teamleader, sizeof(bs->teamleader)); - // if not yet ordered to do anything - if ( !BotSetLastOrderedTask(bs) ) { - // go on defense by default - BotVoiceChat_Defend(bs, i, SAY_TELL); - } - return qtrue; - } - } - } - } - } - return qfalse; -} - -/* -================== -BotTeamAI -================== -*/ -void BotTeamAI(bot_state_t *bs) { - int numteammates; - char netname[MAX_NETNAME]; - - // - if ( gametype < GT_TEAM ) - return; - // make sure we've got a valid team leader - if (!BotValidTeamLeader(bs)) { - // - if (!FindHumanTeamLeader(bs)) { - // - if (!bs->askteamleader_time && !bs->becometeamleader_time) { - if (bs->entergame_time + 10 > FloatTime()) { - bs->askteamleader_time = FloatTime() + 5 + random() * 10; - } - else { - bs->becometeamleader_time = FloatTime() + 5 + random() * 10; - } - } - if (bs->askteamleader_time && bs->askteamleader_time < FloatTime()) { - // if asked for a team leader and no response - BotAI_BotInitialChat(bs, "whoisteamleader", NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); - bs->askteamleader_time = 0; - bs->becometeamleader_time = FloatTime() + 8 + random() * 10; - } - if (bs->becometeamleader_time && bs->becometeamleader_time < FloatTime()) { - BotAI_BotInitialChat(bs, "iamteamleader", NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); - BotSayVoiceTeamOrder(bs, -1, VOICECHAT_STARTLEADER); - ClientName(bs->client, netname, sizeof(netname)); - strncpy(bs->teamleader, netname, sizeof(bs->teamleader)); - bs->teamleader[sizeof(bs->teamleader)] = '\0'; - bs->becometeamleader_time = 0; - } - return; - } - } - bs->askteamleader_time = 0; - bs->becometeamleader_time = 0; - - //return if this bot is NOT the team leader - ClientName(bs->client, netname, sizeof(netname)); - if (Q_stricmp(netname, bs->teamleader) != 0) return; - // - numteammates = BotNumTeamMates(bs); - //give orders - switch(gametype) { - case GT_TEAM: - { - if (bs->numteammates != numteammates || bs->forceorders) { - bs->teamgiveorders_time = FloatTime(); - bs->numteammates = numteammates; - bs->forceorders = qfalse; - } - //if it's time to give orders - if (bs->teamgiveorders_time && bs->teamgiveorders_time < FloatTime() - 5) { - BotTeamOrders(bs); - //give orders again after 120 seconds - bs->teamgiveorders_time = FloatTime() + 120; - } - break; - } - case GT_CTF: - { - //if the number of team mates changed or the flag status changed - //or someone wants to know what to do - if (bs->numteammates != numteammates || bs->flagstatuschanged || bs->forceorders) { - bs->teamgiveorders_time = FloatTime(); - bs->numteammates = numteammates; - bs->flagstatuschanged = qfalse; - bs->forceorders = qfalse; - } - //if there were no flag captures the last 3 minutes - if (bs->lastflagcapture_time < FloatTime() - 240) { - bs->lastflagcapture_time = FloatTime(); - //randomly change the CTF strategy - if (random() < 0.4) { - bs->ctfstrategy ^= CTFS_AGRESSIVE; - bs->teamgiveorders_time = FloatTime(); - } - } - //if it's time to give orders - if (bs->teamgiveorders_time && bs->teamgiveorders_time < FloatTime() - 3) { - BotCTFOrders(bs); - // - bs->teamgiveorders_time = 0; - } - break; - } -#ifdef MISSIONPACK - case GT_1FCTF: - { - if (bs->numteammates != numteammates || bs->flagstatuschanged || bs->forceorders) { - bs->teamgiveorders_time = FloatTime(); - bs->numteammates = numteammates; - bs->flagstatuschanged = qfalse; - bs->forceorders = qfalse; - } - //if there were no flag captures the last 4 minutes - if (bs->lastflagcapture_time < FloatTime() - 240) { - bs->lastflagcapture_time = FloatTime(); - //randomly change the CTF strategy - if (random() < 0.4) { - bs->ctfstrategy ^= CTFS_AGRESSIVE; - bs->teamgiveorders_time = FloatTime(); - } - } - //if it's time to give orders - if (bs->teamgiveorders_time && bs->teamgiveorders_time < FloatTime() - 2) { - Bot1FCTFOrders(bs); - // - bs->teamgiveorders_time = 0; - } - break; - } - case GT_OBELISK: - { - if (bs->numteammates != numteammates || bs->forceorders) { - bs->teamgiveorders_time = FloatTime(); - bs->numteammates = numteammates; - bs->forceorders = qfalse; - } - //if it's time to give orders - if (bs->teamgiveorders_time && bs->teamgiveorders_time < FloatTime() - 5) { - BotObeliskOrders(bs); - //give orders again after 30 seconds - bs->teamgiveorders_time = FloatTime() + 30; - } - break; - } - case GT_HARVESTER: - { - if (bs->numteammates != numteammates || bs->forceorders) { - bs->teamgiveorders_time = FloatTime(); - bs->numteammates = numteammates; - bs->forceorders = qfalse; - } - //if it's time to give orders - if (bs->teamgiveorders_time && bs->teamgiveorders_time < FloatTime() - 5) { - BotHarvesterOrders(bs); - //give orders again after 30 seconds - bs->teamgiveorders_time = FloatTime() + 30; - } - break; - } -#endif - } -} - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: ai_team.c + * + * desc: Quake3 bot AI + * + * $Archive: /MissionPack/code/game/ai_team.c $ + * + *****************************************************************************/ + +#include "g_local.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_ea.h" +#include "be_ai_char.h" +#include "be_ai_chat.h" +#include "be_ai_gen.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" +#include "be_ai_weap.h" +// +#include "ai_main.h" +#include "ai_dmq3.h" +#include "ai_chat.h" +#include "ai_cmd.h" +#include "ai_dmnet.h" +#include "ai_team.h" +#include "ai_vcmd.h" + +#include "match.h" + +// for the voice chats +#include "../../ui/menudef.h" + +//ctf task preferences for a client +typedef struct bot_ctftaskpreference_s +{ + char name[36]; + int preference; +} bot_ctftaskpreference_t; + +bot_ctftaskpreference_t ctftaskpreferences[MAX_CLIENTS]; + + +/* +================== +BotValidTeamLeader +================== +*/ +int BotValidTeamLeader(bot_state_t *bs) { + if (!strlen(bs->teamleader)) return qfalse; + if (ClientFromName(bs->teamleader) == -1) return qfalse; + return qtrue; +} + +/* +================== +BotNumTeamMates +================== +*/ +int BotNumTeamMates(bot_state_t *bs) { + int i, numplayers; + char buf[MAX_INFO_STRING]; + static int maxclients; + + if (!maxclients) + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); + + numplayers = 0; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + // + if (BotSameTeam(bs, i)) { + numplayers++; + } + } + return numplayers; +} + +/* +================== +BotClientTravelTimeToGoal +================== +*/ +int BotClientTravelTimeToGoal(int client, bot_goal_t *goal) { + playerState_t ps; + int areanum; + + BotAI_GetClientState(client, &ps); + areanum = BotPointAreaNum(ps.origin); + if (!areanum) return 1; + return trap_AAS_AreaTravelTimeToGoalArea(areanum, ps.origin, goal->areanum, TFL_DEFAULT); +} + +/* +================== +BotSortTeamMatesByBaseTravelTime +================== +*/ +int BotSortTeamMatesByBaseTravelTime(bot_state_t *bs, int *teammates, int maxteammates) { + + int i, j, k, numteammates, traveltime; + char buf[MAX_INFO_STRING]; + static int maxclients; + int traveltimes[MAX_CLIENTS]; + bot_goal_t *goal = NULL; + + if (gametype == GT_CTF || gametype == GT_1FCTF) { + if (BotTeam(bs) == TEAM_RED) + goal = &ctf_redflag; + else + goal = &ctf_blueflag; + } +#ifdef MISSIONPACK + else { + if (BotTeam(bs) == TEAM_RED) + goal = &redobelisk; + else + goal = &blueobelisk; + } +#endif + if (!maxclients) + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); + + numteammates = 0; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + // + if (BotSameTeam(bs, i)) { + // + traveltime = BotClientTravelTimeToGoal(i, goal); + // + for (j = 0; j < numteammates; j++) { + if (traveltime < traveltimes[j]) { + for (k = numteammates; k > j; k--) { + traveltimes[k] = traveltimes[k-1]; + teammates[k] = teammates[k-1]; + } + break; + } + } + traveltimes[j] = traveltime; + teammates[j] = i; + numteammates++; + if (numteammates >= maxteammates) break; + } + } + return numteammates; +} + +/* +================== +BotSetTeamMateTaskPreference +================== +*/ +void BotSetTeamMateTaskPreference(bot_state_t *bs, int teammate, int preference) { + char teammatename[MAX_NETNAME]; + + ctftaskpreferences[teammate].preference = preference; + ClientName(teammate, teammatename, sizeof(teammatename)); + strcpy(ctftaskpreferences[teammate].name, teammatename); +} + +/* +================== +BotGetTeamMateTaskPreference +================== +*/ +int BotGetTeamMateTaskPreference(bot_state_t *bs, int teammate) { + char teammatename[MAX_NETNAME]; + + if (!ctftaskpreferences[teammate].preference) return 0; + ClientName(teammate, teammatename, sizeof(teammatename)); + if (Q_stricmp(teammatename, ctftaskpreferences[teammate].name)) return 0; + return ctftaskpreferences[teammate].preference; +} + +/* +================== +BotSortTeamMatesByTaskPreference +================== +*/ +int BotSortTeamMatesByTaskPreference(bot_state_t *bs, int *teammates, int numteammates) { + int defenders[MAX_CLIENTS], numdefenders; + int attackers[MAX_CLIENTS], numattackers; + int roamers[MAX_CLIENTS], numroamers; + int i, preference; + + numdefenders = numattackers = numroamers = 0; + for (i = 0; i < numteammates; i++) { + preference = BotGetTeamMateTaskPreference(bs, teammates[i]); + if (preference & TEAMTP_DEFENDER) { + defenders[numdefenders++] = teammates[i]; + } + else if (preference & TEAMTP_ATTACKER) { + attackers[numattackers++] = teammates[i]; + } + else { + roamers[numroamers++] = teammates[i]; + } + } + numteammates = 0; + //defenders at the front of the list + memcpy(&teammates[numteammates], defenders, numdefenders * sizeof(int)); + numteammates += numdefenders; + //roamers in the middle + memcpy(&teammates[numteammates], roamers, numroamers * sizeof(int)); + numteammates += numroamers; + //attacker in the back of the list + memcpy(&teammates[numteammates], attackers, numattackers * sizeof(int)); + numteammates += numattackers; + + return numteammates; +} + +/* +================== +BotSayTeamOrders +================== +*/ +void BotSayTeamOrderAlways(bot_state_t *bs, int toclient) { + char teamchat[MAX_MESSAGE_SIZE]; + char buf[MAX_MESSAGE_SIZE]; + char name[MAX_NETNAME]; + + //if the bot is talking to itself + if (bs->client == toclient) { + //don't show the message just put it in the console message queue + trap_BotGetChatMessage(bs->cs, buf, sizeof(buf)); + ClientName(bs->client, name, sizeof(name)); + Com_sprintf(teamchat, sizeof(teamchat), EC"(%s"EC")"EC": %s", name, buf); + trap_BotQueueConsoleMessage(bs->cs, CMS_CHAT, teamchat); + } + else { + trap_BotEnterChat(bs->cs, toclient, CHAT_TELL); + } +} + +/* +================== +BotSayTeamOrders +================== +*/ +void BotSayTeamOrder(bot_state_t *bs, int toclient) { +#ifdef MISSIONPACK + // voice chats only + char buf[MAX_MESSAGE_SIZE]; + + trap_BotGetChatMessage(bs->cs, buf, sizeof(buf)); +#else + BotSayTeamOrderAlways(bs, toclient); +#endif +} + +/* +================== +BotVoiceChat +================== +*/ +void BotVoiceChat(bot_state_t *bs, int toclient, char *voicechat) { +#ifdef MISSIONPACK + if (toclient == -1) + // voice only say team + trap_EA_Command(bs->client, va("vsay_team %s", voicechat)); + else + // voice only tell single player + trap_EA_Command(bs->client, va("vtell %d %s", toclient, voicechat)); +#endif +} + +/* +================== +BotVoiceChatOnly +================== +*/ +void BotVoiceChatOnly(bot_state_t *bs, int toclient, char *voicechat) { +#ifdef MISSIONPACK + if (toclient == -1) + // voice only say team + trap_EA_Command(bs->client, va("vosay_team %s", voicechat)); + else + // voice only tell single player + trap_EA_Command(bs->client, va("votell %d %s", toclient, voicechat)); +#endif +} + +/* +================== +BotSayVoiceTeamOrder +================== +*/ +void BotSayVoiceTeamOrder(bot_state_t *bs, int toclient, char *voicechat) { +#ifdef MISSIONPACK + BotVoiceChat(bs, toclient, voicechat); +#endif +} + +/* +================== +BotCTFOrders +================== +*/ +void BotCTFOrders_BothFlagsNotAtBase(bot_state_t *bs) { + int numteammates, defenders, attackers, i, other; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME], carriername[MAX_NETNAME]; + + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); + //different orders based on the number of team mates + switch(bs->numteammates) { + case 1: break; + case 2: + { + //tell the one not carrying the flag to attack the enemy base + if (teammates[0] != bs->flagcarrier) other = teammates[0]; + else other = teammates[1]; + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, other); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_GETFLAG); + break; + } + case 3: + { + //tell the one closest to the base not carrying the flag to accompany the flag carrier + if (teammates[0] != bs->flagcarrier) other = teammates[0]; + else other = teammates[1]; + ClientName(other, name, sizeof(name)); + if ( bs->flagcarrier != -1 ) { + ClientName(bs->flagcarrier, carriername, sizeof(carriername)); + if (bs->flagcarrier == bs->client) { + BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWME); + } + else { + BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWFLAGCARRIER); + } + } + else { + // + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_GETFLAG); + } + BotSayTeamOrder(bs, other); + //tell the one furthest from the the base not carrying the flag to get the enemy flag + if (teammates[2] != bs->flagcarrier) other = teammates[2]; + else other = teammates[1]; + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, other); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_RETURNFLAG); + break; + } + default: + { + defenders = (int) (float) numteammates * 0.4 + 0.5; + if (defenders > 4) defenders = 4; + attackers = (int) (float) numteammates * 0.5 + 0.5; + if (attackers > 5) attackers = 5; + if (bs->flagcarrier != -1) { + ClientName(bs->flagcarrier, carriername, sizeof(carriername)); + for (i = 0; i < defenders; i++) { + // + if (teammates[i] == bs->flagcarrier) { + continue; + } + // + ClientName(teammates[i], name, sizeof(name)); + if (bs->flagcarrier == bs->client) { + BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_FOLLOWME); + } + else { + BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_FOLLOWFLAGCARRIER); + } + BotSayTeamOrder(bs, teammates[i]); + } + } + else { + for (i = 0; i < defenders; i++) { + // + if (teammates[i] == bs->flagcarrier) { + continue; + } + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_GETFLAG); + BotSayTeamOrder(bs, teammates[i]); + } + } + for (i = 0; i < attackers; i++) { + // + if (teammates[numteammates - i - 1] == bs->flagcarrier) { + continue; + } + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_RETURNFLAG); + } + // + break; + } + } +} + +/* +================== +BotCTFOrders +================== +*/ +void BotCTFOrders_FlagNotAtBase(bot_state_t *bs) { + int numteammates, defenders, attackers, i; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME]; + + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); + //passive strategy + if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { + //different orders based on the number of team mates + switch(bs->numteammates) { + case 1: break; + case 2: + { + //both will go for the enemy flag + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_GETFLAG); + // + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + break; + } + case 3: + { + //keep one near the base for when the flag is returned + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other two get the flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + // + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); + break; + } + default: + { + //keep some people near the base for when the flag is returned + defenders = (int) (float) numteammates * 0.3 + 0.5; + if (defenders > 3) defenders = 3; + attackers = (int) (float) numteammates * 0.7 + 0.5; + if (attackers > 6) attackers = 6; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_GETFLAG); + } + // + break; + } + } + } + else { + //different orders based on the number of team mates + switch(bs->numteammates) { + case 1: break; + case 2: + { + //both will go for the enemy flag + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_GETFLAG); + // + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + break; + } + case 3: + { + //everyone go for the flag + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_GETFLAG); + // + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + // + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); + break; + } + default: + { + //keep some people near the base for when the flag is returned + defenders = (int) (float) numteammates * 0.2 + 0.5; + if (defenders > 2) defenders = 2; + attackers = (int) (float) numteammates * 0.7 + 0.5; + if (attackers > 7) attackers = 7; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); + } + // + break; + } + } + } +} + +/* +================== +BotCTFOrders +================== +*/ +void BotCTFOrders_EnemyFlagNotAtBase(bot_state_t *bs) { + int numteammates, defenders, attackers, i, other; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME], carriername[MAX_NETNAME]; + + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //tell the one not carrying the flag to defend the base + if (teammates[0] == bs->flagcarrier) other = teammates[1]; + else other = teammates[0]; + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, other); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); + break; + } + case 3: + { + //tell the one closest to the base not carrying the flag to defend the base + if (teammates[0] != bs->flagcarrier) other = teammates[0]; + else other = teammates[1]; + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, other); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); + //tell the other also to defend the base + if (teammates[2] != bs->flagcarrier) other = teammates[2]; + else other = teammates[1]; + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, other); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); + break; + } + default: + { + //60% will defend the base + defenders = (int) (float) numteammates * 0.6 + 0.5; + if (defenders > 6) defenders = 6; + //30% accompanies the flag carrier + attackers = (int) (float) numteammates * 0.3 + 0.5; + if (attackers > 3) attackers = 3; + for (i = 0; i < defenders; i++) { + // + if (teammates[i] == bs->flagcarrier) { + continue; + } + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + // if we have a flag carrier + if ( bs->flagcarrier != -1 ) { + ClientName(bs->flagcarrier, carriername, sizeof(carriername)); + for (i = 0; i < attackers; i++) { + // + if (teammates[numteammates - i - 1] == bs->flagcarrier) { + continue; + } + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + if (bs->flagcarrier == bs->client) { + BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWME); + } + else { + BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWFLAGCARRIER); + } + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + } + } + else { + for (i = 0; i < attackers; i++) { + // + if (teammates[numteammates - i - 1] == bs->flagcarrier) { + continue; + } + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + } + } + // + break; + } + } +} + + +/* +================== +BotCTFOrders +================== +*/ +void BotCTFOrders_BothFlagsAtBase(bot_state_t *bs) { + int numteammates, defenders, attackers, i; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME]; + + //sort team mates by travel time to base + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + //sort team mates by CTF preference + BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); + //passive strategy + if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will get the flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the second one closest to the base will defend the base + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); + //the other will get the flag + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); + break; + } + default: + { + defenders = (int) (float) numteammates * 0.5 + 0.5; + if (defenders > 5) defenders = 5; + attackers = (int) (float) numteammates * 0.4 + 0.5; + if (attackers > 4) attackers = 4; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); + } + // + break; + } + } + } + else { + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will get the flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the others should go for the enemy flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + // + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); + break; + } + default: + { + defenders = (int) (float) numteammates * 0.4 + 0.5; + if (defenders > 4) defenders = 4; + attackers = (int) (float) numteammates * 0.5 + 0.5; + if (attackers > 5) attackers = 5; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); + } + // + break; + } + } + } +} + +/* +================== +BotCTFOrders +================== +*/ +void BotCTFOrders(bot_state_t *bs) { + int flagstatus; + + // + if (BotTeam(bs) == TEAM_RED) flagstatus = bs->redflagstatus * 2 + bs->blueflagstatus; + else flagstatus = bs->blueflagstatus * 2 + bs->redflagstatus; + // + switch(flagstatus) { + case 0: BotCTFOrders_BothFlagsAtBase(bs); break; + case 1: BotCTFOrders_EnemyFlagNotAtBase(bs); break; + case 2: BotCTFOrders_FlagNotAtBase(bs); break; + case 3: BotCTFOrders_BothFlagsNotAtBase(bs); break; + } +} + + +/* +================== +BotCreateGroup +================== +*/ +void BotCreateGroup(bot_state_t *bs, int *teammates, int groupsize) { + char name[MAX_NETNAME], leadername[MAX_NETNAME]; + int i; + + // the others in the group will follow the teammates[0] + ClientName(teammates[0], leadername, sizeof(leadername)); + for (i = 1; i < groupsize; i++) + { + ClientName(teammates[i], name, sizeof(name)); + if (teammates[0] == bs->client) { + BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); + } + else { + BotAI_BotInitialChat(bs, "cmd_accompany", name, leadername, NULL); + } + BotSayTeamOrderAlways(bs, teammates[i]); + } +} + +/* +================== +BotTeamOrders + + FIXME: defend key areas? +================== +*/ +void BotTeamOrders(bot_state_t *bs) { + int teammates[MAX_CLIENTS]; + int numteammates, i; + char buf[MAX_INFO_STRING]; + static int maxclients; + + if (!maxclients) + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); + + numteammates = 0; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + // + if (BotSameTeam(bs, i)) { + teammates[numteammates] = i; + numteammates++; + } + } + // + switch(numteammates) { + case 1: break; + case 2: + { + //nothing special + break; + } + case 3: + { + //have one follow another and one free roaming + BotCreateGroup(bs, teammates, 2); + break; + } + case 4: + { + BotCreateGroup(bs, teammates, 2); //a group of 2 + BotCreateGroup(bs, &teammates[2], 2); //a group of 2 + break; + } + case 5: + { + BotCreateGroup(bs, teammates, 2); //a group of 2 + BotCreateGroup(bs, &teammates[2], 3); //a group of 3 + break; + } + default: + { + if (numteammates <= 10) { + for (i = 0; i < numteammates / 2; i++) { + BotCreateGroup(bs, &teammates[i*2], 2); //groups of 2 + } + } + break; + } + } +} + +#ifdef MISSIONPACK + +/* +================== +Bot1FCTFOrders_FlagAtCenter + + X% defend the base, Y% get the flag +================== +*/ +void Bot1FCTFOrders_FlagAtCenter(bot_state_t *bs) { + int numteammates, defenders, attackers, i; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME]; + + //sort team mates by travel time to base + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + //sort team mates by CTF preference + BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); + //passive strategy + if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will get the flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the second one closest to the base will defend the base + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will get the flag + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); + break; + } + default: + { + //50% defend the base + defenders = (int) (float) numteammates * 0.5 + 0.5; + if (defenders > 5) defenders = 5; + //40% get the flag + attackers = (int) (float) numteammates * 0.4 + 0.5; + if (attackers > 4) attackers = 4; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); + } + // + break; + } + } + } + else { //agressive + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will get the flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the others should go for the enemy flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + // + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); + break; + } + default: + { + //30% defend the base + defenders = (int) (float) numteammates * 0.3 + 0.5; + if (defenders > 3) defenders = 3; + //60% get the flag + attackers = (int) (float) numteammates * 0.6 + 0.5; + if (attackers > 6) attackers = 6; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); + } + // + break; + } + } + } +} + +/* +================== +Bot1FCTFOrders_TeamHasFlag + + X% towards neutral flag, Y% go towards enemy base and accompany flag carrier if visible +================== +*/ +void Bot1FCTFOrders_TeamHasFlag(bot_state_t *bs) { + int numteammates, defenders, attackers, i, other; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME], carriername[MAX_NETNAME]; + + //sort team mates by travel time to base + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + //sort team mates by CTF preference + BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); + //passive strategy + if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //tell the one not carrying the flag to attack the enemy base + if (teammates[0] == bs->flagcarrier) other = teammates[1]; + else other = teammates[0]; + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); + BotSayTeamOrder(bs, other); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_OFFENSE); + break; + } + case 3: + { + //tell the one closest to the base not carrying the flag to defend the base + if (teammates[0] != bs->flagcarrier) other = teammates[0]; + else other = teammates[1]; + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, other); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); + //tell the one furthest from the base not carrying the flag to accompany the flag carrier + if (teammates[2] != bs->flagcarrier) other = teammates[2]; + else other = teammates[1]; + ClientName(other, name, sizeof(name)); + if ( bs->flagcarrier != -1 ) { + ClientName(bs->flagcarrier, carriername, sizeof(carriername)); + if (bs->flagcarrier == bs->client) { + BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWME); + } + else { + BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWFLAGCARRIER); + } + } + else { + // + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_GETFLAG); + } + BotSayTeamOrder(bs, other); + break; + } + default: + { + //30% will defend the base + defenders = (int) (float) numteammates * 0.3 + 0.5; + if (defenders > 3) defenders = 3; + //70% accompanies the flag carrier + attackers = (int) (float) numteammates * 0.7 + 0.5; + if (attackers > 7) attackers = 7; + for (i = 0; i < defenders; i++) { + // + if (teammates[i] == bs->flagcarrier) { + continue; + } + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + if (bs->flagcarrier != -1) { + ClientName(bs->flagcarrier, carriername, sizeof(carriername)); + for (i = 0; i < attackers; i++) { + // + if (teammates[numteammates - i - 1] == bs->flagcarrier) { + continue; + } + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + if (bs->flagcarrier == bs->client) { + BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWME); + } + else { + BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWFLAGCARRIER); + } + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + } + } + else { + for (i = 0; i < attackers; i++) { + // + if (teammates[numteammates - i - 1] == bs->flagcarrier) { + continue; + } + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); + } + } + // + break; + } + } + } + else { //agressive + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //tell the one not carrying the flag to defend the base + if (teammates[0] == bs->flagcarrier) other = teammates[1]; + else other = teammates[0]; + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, other); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); + break; + } + case 3: + { + //tell the one closest to the base not carrying the flag to defend the base + if (teammates[0] != bs->flagcarrier) other = teammates[0]; + else other = teammates[1]; + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, other); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); + //tell the one furthest from the base not carrying the flag to accompany the flag carrier + if (teammates[2] != bs->flagcarrier) other = teammates[2]; + else other = teammates[1]; + ClientName(other, name, sizeof(name)); + ClientName(bs->flagcarrier, carriername, sizeof(carriername)); + if (bs->flagcarrier == bs->client) { + BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWME); + } + else { + BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWFLAGCARRIER); + } + BotSayTeamOrder(bs, other); + break; + } + default: + { + //20% will defend the base + defenders = (int) (float) numteammates * 0.2 + 0.5; + if (defenders > 2) defenders = 2; + //80% accompanies the flag carrier + attackers = (int) (float) numteammates * 0.8 + 0.5; + if (attackers > 8) attackers = 8; + for (i = 0; i < defenders; i++) { + // + if (teammates[i] == bs->flagcarrier) { + continue; + } + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + ClientName(bs->flagcarrier, carriername, sizeof(carriername)); + for (i = 0; i < attackers; i++) { + // + if (teammates[numteammates - i - 1] == bs->flagcarrier) { + continue; + } + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + if (bs->flagcarrier == bs->client) { + BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWME); + } + else { + BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWFLAGCARRIER); + } + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + } + // + break; + } + } + } +} + +/* +================== +Bot1FCTFOrders_EnemyHasFlag + + X% defend the base, Y% towards neutral flag +================== +*/ +void Bot1FCTFOrders_EnemyHasFlag(bot_state_t *bs) { + int numteammates, defenders, attackers, i; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME]; + + //sort team mates by travel time to base + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + //sort team mates by CTF preference + BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); + //passive strategy + if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //both defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + // + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the second one closest to the base will defend the base + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); + //the other will also defend the base + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_DEFEND); + break; + } + default: + { + //80% will defend the base + defenders = (int) (float) numteammates * 0.8 + 0.5; + if (defenders > 8) defenders = 8; + //10% will try to return the flag + attackers = (int) (float) numteammates * 0.1 + 0.5; + if (attackers > 2) attackers = 2; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_returnflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); + } + // + break; + } + } + } + else { //agressive + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will get the flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the others should go for the enemy flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); + // + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_returnflag", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); + break; + } + default: + { + //70% defend the base + defenders = (int) (float) numteammates * 0.7 + 0.5; + if (defenders > 8) defenders = 8; + //20% try to return the flag + attackers = (int) (float) numteammates * 0.2 + 0.5; + if (attackers > 2) attackers = 2; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_returnflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); + } + // + break; + } + } + } +} + +/* +================== +Bot1FCTFOrders_EnemyDroppedFlag + + X% defend the base, Y% get the flag +================== +*/ +void Bot1FCTFOrders_EnemyDroppedFlag(bot_state_t *bs) { + int numteammates, defenders, attackers, i; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME]; + + //sort team mates by travel time to base + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + //sort team mates by CTF preference + BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); + //passive strategy + if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will get the flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the second one closest to the base will defend the base + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); + //the other will get the flag + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); + break; + } + default: + { + //50% defend the base + defenders = (int) (float) numteammates * 0.5 + 0.5; + if (defenders > 5) defenders = 5; + //40% get the flag + attackers = (int) (float) numteammates * 0.4 + 0.5; + if (attackers > 4) attackers = 4; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); + } + // + break; + } + } + } + else { //agressive + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will get the flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the others should go for the enemy flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + // + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); + break; + } + default: + { + //30% defend the base + defenders = (int) (float) numteammates * 0.3 + 0.5; + if (defenders > 3) defenders = 3; + //60% get the flag + attackers = (int) (float) numteammates * 0.6 + 0.5; + if (attackers > 6) attackers = 6; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_DEFEND); + } + // + break; + } + } + } +} + +/* +================== +Bot1FCTFOrders +================== +*/ +void Bot1FCTFOrders(bot_state_t *bs) { + switch(bs->neutralflagstatus) { + case 0: Bot1FCTFOrders_FlagAtCenter(bs); break; + case 1: Bot1FCTFOrders_TeamHasFlag(bs); break; + case 2: Bot1FCTFOrders_EnemyHasFlag(bs); break; + case 3: Bot1FCTFOrders_EnemyDroppedFlag(bs); break; + } +} + +/* +================== +BotObeliskOrders + + X% in defence Y% in offence +================== +*/ +void BotObeliskOrders(bot_state_t *bs) { + int numteammates, defenders, attackers, i; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME]; + + //sort team mates by travel time to base + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + //sort team mates by CTF preference + BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); + //passive strategy + if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will attack the enemy base + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the one second closest to the base also defends the base + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); + //the other one attacks the enemy base + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_OFFENSE); + break; + } + default: + { + //50% defend the base + defenders = (int) (float) numteammates * 0.5 + 0.5; + if (defenders > 5) defenders = 5; + //40% attack the enemy base + attackers = (int) (float) numteammates * 0.4 + 0.5; + if (attackers > 4) attackers = 4; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_OFFENSE); + } + // + break; + } + } + } + else { + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will attack the enemy base + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the others attack the enemy base + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); + // + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_OFFENSE); + break; + } + default: + { + //30% defend the base + defenders = (int) (float) numteammates * 0.3 + 0.5; + if (defenders > 3) defenders = 3; + //70% attack the enemy base + attackers = (int) (float) numteammates * 0.7 + 0.5; + if (attackers > 7) attackers = 7; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_OFFENSE); + } + // + break; + } + } + } +} + +/* +================== +BotHarvesterOrders + + X% defend the base, Y% harvest +================== +*/ +void BotHarvesterOrders(bot_state_t *bs) { + int numteammates, defenders, attackers, i; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME]; + + //sort team mates by travel time to base + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + //sort team mates by CTF preference + BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); + //passive strategy + if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will harvest + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the one second closest to the base also defends the base + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); + //the other one goes harvesting + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_OFFENSE); + break; + } + default: + { + //50% defend the base + defenders = (int) (float) numteammates * 0.5 + 0.5; + if (defenders > 5) defenders = 5; + //40% goes harvesting + attackers = (int) (float) numteammates * 0.4 + 0.5; + if (attackers > 4) attackers = 4; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_OFFENSE); + } + // + break; + } + } + } + else { + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will harvest + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the others go harvesting + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); + // + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_OFFENSE); + break; + } + default: + { + //30% defend the base + defenders = (int) (float) numteammates * 0.3 + 0.5; + if (defenders > 3) defenders = 3; + //70% go harvesting + attackers = (int) (float) numteammates * 0.7 + 0.5; + if (attackers > 7) attackers = 7; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_OFFENSE); + } + // + break; + } + } + } +} +#endif + +/* +================== +FindHumanTeamLeader +================== +*/ +int FindHumanTeamLeader(bot_state_t *bs) { + int i; + + for (i = 0; i < MAX_CLIENTS; i++) { + if ( g_entities[i].inuse ) { + // if this player is not a bot + if ( !(g_entities[i].r.svFlags & SVF_BOT) ) { + // if this player is ok with being the leader + if (!notleader[i]) { + // if this player is on the same team + if ( BotSameTeam(bs, i) ) { + ClientName(i, bs->teamleader, sizeof(bs->teamleader)); + // if not yet ordered to do anything + if ( !BotSetLastOrderedTask(bs) ) { + // go on defense by default + BotVoiceChat_Defend(bs, i, SAY_TELL); + } + return qtrue; + } + } + } + } + } + return qfalse; +} + +/* +================== +BotTeamAI +================== +*/ +void BotTeamAI(bot_state_t *bs) { + int numteammates; + char netname[MAX_NETNAME]; + + // + if ( gametype < GT_TEAM ) + return; + // make sure we've got a valid team leader + if (!BotValidTeamLeader(bs)) { + // + if (!FindHumanTeamLeader(bs)) { + // + if (!bs->askteamleader_time && !bs->becometeamleader_time) { + if (bs->entergame_time + 10 > FloatTime()) { + bs->askteamleader_time = FloatTime() + 5 + random() * 10; + } + else { + bs->becometeamleader_time = FloatTime() + 5 + random() * 10; + } + } + if (bs->askteamleader_time && bs->askteamleader_time < FloatTime()) { + // if asked for a team leader and no response + BotAI_BotInitialChat(bs, "whoisteamleader", NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + bs->askteamleader_time = 0; + bs->becometeamleader_time = FloatTime() + 8 + random() * 10; + } + if (bs->becometeamleader_time && bs->becometeamleader_time < FloatTime()) { + BotAI_BotInitialChat(bs, "iamteamleader", NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotSayVoiceTeamOrder(bs, -1, VOICECHAT_STARTLEADER); + ClientName(bs->client, netname, sizeof(netname)); + strncpy(bs->teamleader, netname, sizeof(bs->teamleader)); + bs->teamleader[sizeof(bs->teamleader)] = '\0'; + bs->becometeamleader_time = 0; + } + return; + } + } + bs->askteamleader_time = 0; + bs->becometeamleader_time = 0; + + //return if this bot is NOT the team leader + ClientName(bs->client, netname, sizeof(netname)); + if (Q_stricmp(netname, bs->teamleader) != 0) return; + // + numteammates = BotNumTeamMates(bs); + //give orders + switch(gametype) { + case GT_TEAM: + { + if (bs->numteammates != numteammates || bs->forceorders) { + bs->teamgiveorders_time = FloatTime(); + bs->numteammates = numteammates; + bs->forceorders = qfalse; + } + //if it's time to give orders + if (bs->teamgiveorders_time && bs->teamgiveorders_time < FloatTime() - 5) { + BotTeamOrders(bs); + //give orders again after 120 seconds + bs->teamgiveorders_time = FloatTime() + 120; + } + break; + } + case GT_CTF: + { + //if the number of team mates changed or the flag status changed + //or someone wants to know what to do + if (bs->numteammates != numteammates || bs->flagstatuschanged || bs->forceorders) { + bs->teamgiveorders_time = FloatTime(); + bs->numteammates = numteammates; + bs->flagstatuschanged = qfalse; + bs->forceorders = qfalse; + } + //if there were no flag captures the last 3 minutes + if (bs->lastflagcapture_time < FloatTime() - 240) { + bs->lastflagcapture_time = FloatTime(); + //randomly change the CTF strategy + if (random() < 0.4) { + bs->ctfstrategy ^= CTFS_AGRESSIVE; + bs->teamgiveorders_time = FloatTime(); + } + } + //if it's time to give orders + if (bs->teamgiveorders_time && bs->teamgiveorders_time < FloatTime() - 3) { + BotCTFOrders(bs); + // + bs->teamgiveorders_time = 0; + } + break; + } +#ifdef MISSIONPACK + case GT_1FCTF: + { + if (bs->numteammates != numteammates || bs->flagstatuschanged || bs->forceorders) { + bs->teamgiveorders_time = FloatTime(); + bs->numteammates = numteammates; + bs->flagstatuschanged = qfalse; + bs->forceorders = qfalse; + } + //if there were no flag captures the last 4 minutes + if (bs->lastflagcapture_time < FloatTime() - 240) { + bs->lastflagcapture_time = FloatTime(); + //randomly change the CTF strategy + if (random() < 0.4) { + bs->ctfstrategy ^= CTFS_AGRESSIVE; + bs->teamgiveorders_time = FloatTime(); + } + } + //if it's time to give orders + if (bs->teamgiveorders_time && bs->teamgiveorders_time < FloatTime() - 2) { + Bot1FCTFOrders(bs); + // + bs->teamgiveorders_time = 0; + } + break; + } + case GT_OBELISK: + { + if (bs->numteammates != numteammates || bs->forceorders) { + bs->teamgiveorders_time = FloatTime(); + bs->numteammates = numteammates; + bs->forceorders = qfalse; + } + //if it's time to give orders + if (bs->teamgiveorders_time && bs->teamgiveorders_time < FloatTime() - 5) { + BotObeliskOrders(bs); + //give orders again after 30 seconds + bs->teamgiveorders_time = FloatTime() + 30; + } + break; + } + case GT_HARVESTER: + { + if (bs->numteammates != numteammates || bs->forceorders) { + bs->teamgiveorders_time = FloatTime(); + bs->numteammates = numteammates; + bs->forceorders = qfalse; + } + //if it's time to give orders + if (bs->teamgiveorders_time && bs->teamgiveorders_time < FloatTime() - 5) { + BotHarvesterOrders(bs); + //give orders again after 30 seconds + bs->teamgiveorders_time = FloatTime() + 30; + } + break; + } +#endif + } +} + diff --git a/code/game/ai_team.h b/code/game/ai_team.h index fbe4a9e..bb29414 100755 --- a/code/game/ai_team.h +++ b/code/game/ai_team.h @@ -1,39 +1,39 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -/***************************************************************************** - * name: ai_team.h - * - * desc: Quake3 bot AI - * - * $Archive: /source/code/botai/ai_chat.c $ - * - *****************************************************************************/ - -void BotTeamAI(bot_state_t *bs); -int BotGetTeamMateTaskPreference(bot_state_t *bs, int teammate); -void BotSetTeamMateTaskPreference(bot_state_t *bs, int teammate, int preference); -void BotVoiceChat(bot_state_t *bs, int toclient, char *voicechat); -void BotVoiceChatOnly(bot_state_t *bs, int toclient, char *voicechat); - - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: ai_team.h + * + * desc: Quake3 bot AI + * + * $Archive: /source/code/botai/ai_chat.c $ + * + *****************************************************************************/ + +void BotTeamAI(bot_state_t *bs); +int BotGetTeamMateTaskPreference(bot_state_t *bs, int teammate); +void BotSetTeamMateTaskPreference(bot_state_t *bs, int teammate, int preference); +void BotVoiceChat(bot_state_t *bs, int toclient, char *voicechat); +void BotVoiceChatOnly(bot_state_t *bs, int toclient, char *voicechat); + + diff --git a/code/game/ai_vcmd.c b/code/game/ai_vcmd.c index 812fe8f..026bbf0 100755 --- a/code/game/ai_vcmd.c +++ b/code/game/ai_vcmd.c @@ -1,550 +1,550 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -/***************************************************************************** - * name: ai_vcmd.c - * - * desc: Quake3 bot AI - * - * $Archive: /MissionPack/code/game/ai_vcmd.c $ - * - *****************************************************************************/ - -#include "g_local.h" -#include "botlib.h" -#include "be_aas.h" -#include "be_ea.h" -#include "be_ai_char.h" -#include "be_ai_chat.h" -#include "be_ai_gen.h" -#include "be_ai_goal.h" -#include "be_ai_move.h" -#include "be_ai_weap.h" -// -#include "ai_main.h" -#include "ai_dmq3.h" -#include "ai_chat.h" -#include "ai_cmd.h" -#include "ai_dmnet.h" -#include "ai_team.h" -#include "ai_vcmd.h" -// -#include "chars.h" //characteristics -#include "inv.h" //indexes into the inventory -#include "syn.h" //synonyms -#include "match.h" //string matching types and vars - -// for the voice chats -#include "../../ui/menudef.h" - - -typedef struct voiceCommand_s -{ - char *cmd; - void (*func)(bot_state_t *bs, int client, int mode); -} voiceCommand_t; - -/* -================== -BotVoiceChat_GetFlag -================== -*/ -void BotVoiceChat_GetFlag(bot_state_t *bs, int client, int mode) { - // - if (gametype == GT_CTF) { - if (!ctf_redflag.areanum || !ctf_blueflag.areanum) - return; - } -#ifdef MISSIONPACK - else if (gametype == GT_1FCTF) { - if (!ctf_neutralflag.areanum || !ctf_redflag.areanum || !ctf_blueflag.areanum) - return; - } -#endif - else { - return; - } - // - bs->decisionmaker = client; - bs->ordered = qtrue; - bs->order_time = FloatTime(); - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - //set the ltg type - bs->ltgtype = LTG_GETFLAG; - //set the team goal time - bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME; - // get an alternate route in ctf - if (gametype == GT_CTF) { - //get an alternative route goal towards the enemy base - BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); - } - // - BotSetTeamStatus(bs); - // remember last ordered task - BotRememberLastOrderedTask(bs); -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} - -/* -================== -BotVoiceChat_Offense -================== -*/ -void BotVoiceChat_Offense(bot_state_t *bs, int client, int mode) { - if ( gametype == GT_CTF -#ifdef MISSIONPACK - || gametype == GT_1FCTF -#endif - ) { - BotVoiceChat_GetFlag(bs, client, mode); - return; - } -#ifdef MISSIONPACK - if (gametype == GT_HARVESTER) { - // - bs->decisionmaker = client; - bs->ordered = qtrue; - bs->order_time = FloatTime(); - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - //set the ltg type - bs->ltgtype = LTG_HARVEST; - //set the team goal time - bs->teamgoal_time = FloatTime() + TEAM_HARVEST_TIME; - bs->harvestaway_time = 0; - // - BotSetTeamStatus(bs); - // remember last ordered task - BotRememberLastOrderedTask(bs); - } - else -#endif - { - // - bs->decisionmaker = client; - bs->ordered = qtrue; - bs->order_time = FloatTime(); - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - //set the ltg type - bs->ltgtype = LTG_ATTACKENEMYBASE; - //set the team goal time - bs->teamgoal_time = FloatTime() + TEAM_ATTACKENEMYBASE_TIME; - bs->attackaway_time = 0; - // - BotSetTeamStatus(bs); - // remember last ordered task - BotRememberLastOrderedTask(bs); - } -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} - -/* -================== -BotVoiceChat_Defend -================== -*/ -void BotVoiceChat_Defend(bot_state_t *bs, int client, int mode) { -#ifdef MISSIONPACK - if ( gametype == GT_OBELISK || gametype == GT_HARVESTER) { - // - switch(BotTeam(bs)) { - case TEAM_RED: memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t)); break; - case TEAM_BLUE: memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t)); break; - default: return; - } - } - else -#endif - if (gametype == GT_CTF -#ifdef MISSIONPACK - || gametype == GT_1FCTF -#endif - ) { - // - switch(BotTeam(bs)) { - case TEAM_RED: memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); break; - case TEAM_BLUE: memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); break; - default: return; - } - } - else { - return; - } - // - bs->decisionmaker = client; - bs->ordered = qtrue; - bs->order_time = FloatTime(); - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - //set the ltg type - bs->ltgtype = LTG_DEFENDKEYAREA; - //get the team goal time - bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; - //away from defending - bs->defendaway_time = 0; - // - BotSetTeamStatus(bs); - // remember last ordered task - BotRememberLastOrderedTask(bs); -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} - -/* -================== -BotVoiceChat_DefendFlag -================== -*/ -void BotVoiceChat_DefendFlag(bot_state_t *bs, int client, int mode) { - BotVoiceChat_Defend(bs, client, mode); -} - -/* -================== -BotVoiceChat_Patrol -================== -*/ -void BotVoiceChat_Patrol(bot_state_t *bs, int client, int mode) { - // - bs->decisionmaker = client; - // - bs->ltgtype = 0; - bs->lead_time = 0; - bs->lastgoal_ltgtype = 0; - // - BotAI_BotInitialChat(bs, "dismissed", NULL); - trap_BotEnterChat(bs->cs, client, CHAT_TELL); - BotVoiceChatOnly(bs, -1, VOICECHAT_ONPATROL); - // - BotSetTeamStatus(bs); -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} - -/* -================== -BotVoiceChat_Camp -================== -*/ -void BotVoiceChat_Camp(bot_state_t *bs, int client, int mode) { - int areanum; - aas_entityinfo_t entinfo; - char netname[MAX_NETNAME]; - - // - bs->teamgoal.entitynum = -1; - BotEntityInfo(client, &entinfo); - //if info is valid (in PVS) - if (entinfo.valid) { - areanum = BotPointAreaNum(entinfo.origin); - if (areanum) { // && trap_AAS_AreaReachability(areanum)) { - //NOTE: just assume the bot knows where the person is - //if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, client)) { - bs->teamgoal.entitynum = client; - bs->teamgoal.areanum = areanum; - VectorCopy(entinfo.origin, bs->teamgoal.origin); - VectorSet(bs->teamgoal.mins, -8, -8, -8); - VectorSet(bs->teamgoal.maxs, 8, 8, 8); - //} - } - } - //if the other is not visible - if (bs->teamgoal.entitynum < 0) { - BotAI_BotInitialChat(bs, "whereareyou", EasyClientName(client, netname, sizeof(netname)), NULL); - trap_BotEnterChat(bs->cs, client, CHAT_TELL); - return; - } - // - bs->decisionmaker = client; - bs->ordered = qtrue; - bs->order_time = FloatTime(); - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - //set the ltg type - bs->ltgtype = LTG_CAMPORDER; - //get the team goal time - bs->teamgoal_time = FloatTime() + TEAM_CAMP_TIME; - //the teammate that requested the camping - bs->teammate = client; - //not arrived yet - bs->arrive_time = 0; - // - BotSetTeamStatus(bs); - // remember last ordered task - BotRememberLastOrderedTask(bs); -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} - -/* -================== -BotVoiceChat_FollowMe -================== -*/ -void BotVoiceChat_FollowMe(bot_state_t *bs, int client, int mode) { - int areanum; - aas_entityinfo_t entinfo; - char netname[MAX_NETNAME]; - - bs->teamgoal.entitynum = -1; - BotEntityInfo(client, &entinfo); - //if info is valid (in PVS) - if (entinfo.valid) { - areanum = BotPointAreaNum(entinfo.origin); - if (areanum) { // && trap_AAS_AreaReachability(areanum)) { - bs->teamgoal.entitynum = client; - bs->teamgoal.areanum = areanum; - VectorCopy(entinfo.origin, bs->teamgoal.origin); - VectorSet(bs->teamgoal.mins, -8, -8, -8); - VectorSet(bs->teamgoal.maxs, 8, 8, 8); - } - } - //if the other is not visible - if (bs->teamgoal.entitynum < 0) { - BotAI_BotInitialChat(bs, "whereareyou", EasyClientName(client, netname, sizeof(netname)), NULL); - trap_BotEnterChat(bs->cs, client, CHAT_TELL); - return; - } - // - bs->decisionmaker = client; - bs->ordered = qtrue; - bs->order_time = FloatTime(); - //the team mate - bs->teammate = client; - //last time the team mate was assumed visible - bs->teammatevisible_time = FloatTime(); - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - //get the team goal time - bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; - //set the ltg type - bs->ltgtype = LTG_TEAMACCOMPANY; - bs->formation_dist = 3.5 * 32; //3.5 meter - bs->arrive_time = 0; - // - BotSetTeamStatus(bs); - // remember last ordered task - BotRememberLastOrderedTask(bs); -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} - -/* -================== -BotVoiceChat_FollowFlagCarrier -================== -*/ -void BotVoiceChat_FollowFlagCarrier(bot_state_t *bs, int client, int mode) { - int carrier; - - carrier = BotTeamFlagCarrier(bs); - if (carrier >= 0) - BotVoiceChat_FollowMe(bs, carrier, mode); -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} - -/* -================== -BotVoiceChat_ReturnFlag -================== -*/ -void BotVoiceChat_ReturnFlag(bot_state_t *bs, int client, int mode) { - //if not in CTF mode - if ( - gametype != GT_CTF -#ifdef MISSIONPACK - && gametype != GT_1FCTF -#endif - ) { - return; - } - // - bs->decisionmaker = client; - bs->ordered = qtrue; - bs->order_time = FloatTime(); - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - //set the ltg type - bs->ltgtype = LTG_RETURNFLAG; - //set the team goal time - bs->teamgoal_time = FloatTime() + CTF_RETURNFLAG_TIME; - bs->rushbaseaway_time = 0; - BotSetTeamStatus(bs); -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} - -/* -================== -BotVoiceChat_StartLeader -================== -*/ -void BotVoiceChat_StartLeader(bot_state_t *bs, int client, int mode) { - ClientName(client, bs->teamleader, sizeof(bs->teamleader)); -} - -/* -================== -BotVoiceChat_StopLeader -================== -*/ -void BotVoiceChat_StopLeader(bot_state_t *bs, int client, int mode) { - char netname[MAX_MESSAGE_SIZE]; - - if (!Q_stricmp(bs->teamleader, ClientName(client, netname, sizeof(netname)))) { - bs->teamleader[0] = '\0'; - notleader[client] = qtrue; - } -} - -/* -================== -BotVoiceChat_WhoIsLeader -================== -*/ -void BotVoiceChat_WhoIsLeader(bot_state_t *bs, int client, int mode) { - char netname[MAX_MESSAGE_SIZE]; - - if (!TeamPlayIsOn()) return; - - ClientName(bs->client, netname, sizeof(netname)); - //if this bot IS the team leader - if (!Q_stricmp(netname, bs->teamleader)) { - BotAI_BotInitialChat(bs, "iamteamleader", NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); - BotVoiceChatOnly(bs, -1, VOICECHAT_STARTLEADER); - } -} - -/* -================== -BotVoiceChat_WantOnDefense -================== -*/ -void BotVoiceChat_WantOnDefense(bot_state_t *bs, int client, int mode) { - char netname[MAX_NETNAME]; - int preference; - - preference = BotGetTeamMateTaskPreference(bs, client); - preference &= ~TEAMTP_ATTACKER; - preference |= TEAMTP_DEFENDER; - BotSetTeamMateTaskPreference(bs, client, preference); - // - EasyClientName(client, netname, sizeof(netname)); - BotAI_BotInitialChat(bs, "keepinmind", netname, NULL); - trap_BotEnterChat(bs->cs, client, CHAT_TELL); - BotVoiceChatOnly(bs, client, VOICECHAT_YES); - trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); -} - -/* -================== -BotVoiceChat_WantOnOffense -================== -*/ -void BotVoiceChat_WantOnOffense(bot_state_t *bs, int client, int mode) { - char netname[MAX_NETNAME]; - int preference; - - preference = BotGetTeamMateTaskPreference(bs, client); - preference &= ~TEAMTP_DEFENDER; - preference |= TEAMTP_ATTACKER; - BotSetTeamMateTaskPreference(bs, client, preference); - // - EasyClientName(client, netname, sizeof(netname)); - BotAI_BotInitialChat(bs, "keepinmind", netname, NULL); - trap_BotEnterChat(bs->cs, client, CHAT_TELL); - BotVoiceChatOnly(bs, client, VOICECHAT_YES); - trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); -} - -void BotVoiceChat_Dummy(bot_state_t *bs, int client, int mode) { -} - -voiceCommand_t voiceCommands[] = { - {VOICECHAT_GETFLAG, BotVoiceChat_GetFlag}, - {VOICECHAT_OFFENSE, BotVoiceChat_Offense }, - {VOICECHAT_DEFEND, BotVoiceChat_Defend }, - {VOICECHAT_DEFENDFLAG, BotVoiceChat_DefendFlag }, - {VOICECHAT_PATROL, BotVoiceChat_Patrol }, - {VOICECHAT_CAMP, BotVoiceChat_Camp }, - {VOICECHAT_FOLLOWME, BotVoiceChat_FollowMe }, - {VOICECHAT_FOLLOWFLAGCARRIER, BotVoiceChat_FollowFlagCarrier }, - {VOICECHAT_RETURNFLAG, BotVoiceChat_ReturnFlag }, - {VOICECHAT_STARTLEADER, BotVoiceChat_StartLeader }, - {VOICECHAT_STOPLEADER, BotVoiceChat_StopLeader }, - {VOICECHAT_WHOISLEADER, BotVoiceChat_WhoIsLeader }, - {VOICECHAT_WANTONDEFENSE, BotVoiceChat_WantOnDefense }, - {VOICECHAT_WANTONOFFENSE, BotVoiceChat_WantOnOffense }, - {NULL, BotVoiceChat_Dummy} -}; - -int BotVoiceChatCommand(bot_state_t *bs, int mode, char *voiceChat) { - int i, voiceOnly, clientNum, color; - char *ptr, buf[MAX_MESSAGE_SIZE], *cmd; - - if (!TeamPlayIsOn()) { - return qfalse; - } - - if ( mode == SAY_ALL ) { - return qfalse; // don't do anything with voice chats to everyone - } - - Q_strncpyz(buf, voiceChat, sizeof(buf)); - cmd = buf; - for (ptr = cmd; *cmd && *cmd > ' '; cmd++); - while (*cmd && *cmd <= ' ') *cmd++ = '\0'; - voiceOnly = atoi(ptr); - for (ptr = cmd; *cmd && *cmd > ' '; cmd++); - while (*cmd && *cmd <= ' ') *cmd++ = '\0'; - clientNum = atoi(ptr); - for (ptr = cmd; *cmd && *cmd > ' '; cmd++); - while (*cmd && *cmd <= ' ') *cmd++ = '\0'; - color = atoi(ptr); - - if (!BotSameTeam(bs, clientNum)) { - return qfalse; - } - - for (i = 0; voiceCommands[i].cmd; i++) { - if (!Q_stricmp(cmd, voiceCommands[i].cmd)) { - voiceCommands[i].func(bs, clientNum, mode); - return qtrue; - } - } - return qfalse; -} +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: ai_vcmd.c + * + * desc: Quake3 bot AI + * + * $Archive: /MissionPack/code/game/ai_vcmd.c $ + * + *****************************************************************************/ + +#include "g_local.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_ea.h" +#include "be_ai_char.h" +#include "be_ai_chat.h" +#include "be_ai_gen.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" +#include "be_ai_weap.h" +// +#include "ai_main.h" +#include "ai_dmq3.h" +#include "ai_chat.h" +#include "ai_cmd.h" +#include "ai_dmnet.h" +#include "ai_team.h" +#include "ai_vcmd.h" +// +#include "chars.h" //characteristics +#include "inv.h" //indexes into the inventory +#include "syn.h" //synonyms +#include "match.h" //string matching types and vars + +// for the voice chats +#include "../../ui/menudef.h" + + +typedef struct voiceCommand_s +{ + char *cmd; + void (*func)(bot_state_t *bs, int client, int mode); +} voiceCommand_t; + +/* +================== +BotVoiceChat_GetFlag +================== +*/ +void BotVoiceChat_GetFlag(bot_state_t *bs, int client, int mode) { + // + if (gametype == GT_CTF) { + if (!ctf_redflag.areanum || !ctf_blueflag.areanum) + return; + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + if (!ctf_neutralflag.areanum || !ctf_redflag.areanum || !ctf_blueflag.areanum) + return; + } +#endif + else { + return; + } + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_GETFLAG; + //set the team goal time + bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME; + // get an alternate route in ctf + if (gametype == GT_CTF) { + //get an alternative route goal towards the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + } + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotVoiceChat_Offense +================== +*/ +void BotVoiceChat_Offense(bot_state_t *bs, int client, int mode) { + if ( gametype == GT_CTF +#ifdef MISSIONPACK + || gametype == GT_1FCTF +#endif + ) { + BotVoiceChat_GetFlag(bs, client, mode); + return; + } +#ifdef MISSIONPACK + if (gametype == GT_HARVESTER) { + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_HARVEST; + //set the team goal time + bs->teamgoal_time = FloatTime() + TEAM_HARVEST_TIME; + bs->harvestaway_time = 0; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); + } + else +#endif + { + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_ATTACKENEMYBASE; + //set the team goal time + bs->teamgoal_time = FloatTime() + TEAM_ATTACKENEMYBASE_TIME; + bs->attackaway_time = 0; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); + } +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotVoiceChat_Defend +================== +*/ +void BotVoiceChat_Defend(bot_state_t *bs, int client, int mode) { +#ifdef MISSIONPACK + if ( gametype == GT_OBELISK || gametype == GT_HARVESTER) { + // + switch(BotTeam(bs)) { + case TEAM_RED: memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t)); break; + case TEAM_BLUE: memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t)); break; + default: return; + } + } + else +#endif + if (gametype == GT_CTF +#ifdef MISSIONPACK + || gametype == GT_1FCTF +#endif + ) { + // + switch(BotTeam(bs)) { + case TEAM_RED: memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); break; + case TEAM_BLUE: memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); break; + default: return; + } + } + else { + return; + } + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_DEFENDKEYAREA; + //get the team goal time + bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; + //away from defending + bs->defendaway_time = 0; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotVoiceChat_DefendFlag +================== +*/ +void BotVoiceChat_DefendFlag(bot_state_t *bs, int client, int mode) { + BotVoiceChat_Defend(bs, client, mode); +} + +/* +================== +BotVoiceChat_Patrol +================== +*/ +void BotVoiceChat_Patrol(bot_state_t *bs, int client, int mode) { + // + bs->decisionmaker = client; + // + bs->ltgtype = 0; + bs->lead_time = 0; + bs->lastgoal_ltgtype = 0; + // + BotAI_BotInitialChat(bs, "dismissed", NULL); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); + BotVoiceChatOnly(bs, -1, VOICECHAT_ONPATROL); + // + BotSetTeamStatus(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotVoiceChat_Camp +================== +*/ +void BotVoiceChat_Camp(bot_state_t *bs, int client, int mode) { + int areanum; + aas_entityinfo_t entinfo; + char netname[MAX_NETNAME]; + + // + bs->teamgoal.entitynum = -1; + BotEntityInfo(client, &entinfo); + //if info is valid (in PVS) + if (entinfo.valid) { + areanum = BotPointAreaNum(entinfo.origin); + if (areanum) { // && trap_AAS_AreaReachability(areanum)) { + //NOTE: just assume the bot knows where the person is + //if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, client)) { + bs->teamgoal.entitynum = client; + bs->teamgoal.areanum = areanum; + VectorCopy(entinfo.origin, bs->teamgoal.origin); + VectorSet(bs->teamgoal.mins, -8, -8, -8); + VectorSet(bs->teamgoal.maxs, 8, 8, 8); + //} + } + } + //if the other is not visible + if (bs->teamgoal.entitynum < 0) { + BotAI_BotInitialChat(bs, "whereareyou", EasyClientName(client, netname, sizeof(netname)), NULL); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); + return; + } + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_CAMPORDER; + //get the team goal time + bs->teamgoal_time = FloatTime() + TEAM_CAMP_TIME; + //the teammate that requested the camping + bs->teammate = client; + //not arrived yet + bs->arrive_time = 0; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotVoiceChat_FollowMe +================== +*/ +void BotVoiceChat_FollowMe(bot_state_t *bs, int client, int mode) { + int areanum; + aas_entityinfo_t entinfo; + char netname[MAX_NETNAME]; + + bs->teamgoal.entitynum = -1; + BotEntityInfo(client, &entinfo); + //if info is valid (in PVS) + if (entinfo.valid) { + areanum = BotPointAreaNum(entinfo.origin); + if (areanum) { // && trap_AAS_AreaReachability(areanum)) { + bs->teamgoal.entitynum = client; + bs->teamgoal.areanum = areanum; + VectorCopy(entinfo.origin, bs->teamgoal.origin); + VectorSet(bs->teamgoal.mins, -8, -8, -8); + VectorSet(bs->teamgoal.maxs, 8, 8, 8); + } + } + //if the other is not visible + if (bs->teamgoal.entitynum < 0) { + BotAI_BotInitialChat(bs, "whereareyou", EasyClientName(client, netname, sizeof(netname)), NULL); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); + return; + } + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //the team mate + bs->teammate = client; + //last time the team mate was assumed visible + bs->teammatevisible_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //get the team goal time + bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; + //set the ltg type + bs->ltgtype = LTG_TEAMACCOMPANY; + bs->formation_dist = 3.5 * 32; //3.5 meter + bs->arrive_time = 0; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotVoiceChat_FollowFlagCarrier +================== +*/ +void BotVoiceChat_FollowFlagCarrier(bot_state_t *bs, int client, int mode) { + int carrier; + + carrier = BotTeamFlagCarrier(bs); + if (carrier >= 0) + BotVoiceChat_FollowMe(bs, carrier, mode); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotVoiceChat_ReturnFlag +================== +*/ +void BotVoiceChat_ReturnFlag(bot_state_t *bs, int client, int mode) { + //if not in CTF mode + if ( + gametype != GT_CTF +#ifdef MISSIONPACK + && gametype != GT_1FCTF +#endif + ) { + return; + } + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_RETURNFLAG; + //set the team goal time + bs->teamgoal_time = FloatTime() + CTF_RETURNFLAG_TIME; + bs->rushbaseaway_time = 0; + BotSetTeamStatus(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotVoiceChat_StartLeader +================== +*/ +void BotVoiceChat_StartLeader(bot_state_t *bs, int client, int mode) { + ClientName(client, bs->teamleader, sizeof(bs->teamleader)); +} + +/* +================== +BotVoiceChat_StopLeader +================== +*/ +void BotVoiceChat_StopLeader(bot_state_t *bs, int client, int mode) { + char netname[MAX_MESSAGE_SIZE]; + + if (!Q_stricmp(bs->teamleader, ClientName(client, netname, sizeof(netname)))) { + bs->teamleader[0] = '\0'; + notleader[client] = qtrue; + } +} + +/* +================== +BotVoiceChat_WhoIsLeader +================== +*/ +void BotVoiceChat_WhoIsLeader(bot_state_t *bs, int client, int mode) { + char netname[MAX_MESSAGE_SIZE]; + + if (!TeamPlayIsOn()) return; + + ClientName(bs->client, netname, sizeof(netname)); + //if this bot IS the team leader + if (!Q_stricmp(netname, bs->teamleader)) { + BotAI_BotInitialChat(bs, "iamteamleader", NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotVoiceChatOnly(bs, -1, VOICECHAT_STARTLEADER); + } +} + +/* +================== +BotVoiceChat_WantOnDefense +================== +*/ +void BotVoiceChat_WantOnDefense(bot_state_t *bs, int client, int mode) { + char netname[MAX_NETNAME]; + int preference; + + preference = BotGetTeamMateTaskPreference(bs, client); + preference &= ~TEAMTP_ATTACKER; + preference |= TEAMTP_DEFENDER; + BotSetTeamMateTaskPreference(bs, client, preference); + // + EasyClientName(client, netname, sizeof(netname)); + BotAI_BotInitialChat(bs, "keepinmind", netname, NULL); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); + BotVoiceChatOnly(bs, client, VOICECHAT_YES); + trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); +} + +/* +================== +BotVoiceChat_WantOnOffense +================== +*/ +void BotVoiceChat_WantOnOffense(bot_state_t *bs, int client, int mode) { + char netname[MAX_NETNAME]; + int preference; + + preference = BotGetTeamMateTaskPreference(bs, client); + preference &= ~TEAMTP_DEFENDER; + preference |= TEAMTP_ATTACKER; + BotSetTeamMateTaskPreference(bs, client, preference); + // + EasyClientName(client, netname, sizeof(netname)); + BotAI_BotInitialChat(bs, "keepinmind", netname, NULL); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); + BotVoiceChatOnly(bs, client, VOICECHAT_YES); + trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); +} + +void BotVoiceChat_Dummy(bot_state_t *bs, int client, int mode) { +} + +voiceCommand_t voiceCommands[] = { + {VOICECHAT_GETFLAG, BotVoiceChat_GetFlag}, + {VOICECHAT_OFFENSE, BotVoiceChat_Offense }, + {VOICECHAT_DEFEND, BotVoiceChat_Defend }, + {VOICECHAT_DEFENDFLAG, BotVoiceChat_DefendFlag }, + {VOICECHAT_PATROL, BotVoiceChat_Patrol }, + {VOICECHAT_CAMP, BotVoiceChat_Camp }, + {VOICECHAT_FOLLOWME, BotVoiceChat_FollowMe }, + {VOICECHAT_FOLLOWFLAGCARRIER, BotVoiceChat_FollowFlagCarrier }, + {VOICECHAT_RETURNFLAG, BotVoiceChat_ReturnFlag }, + {VOICECHAT_STARTLEADER, BotVoiceChat_StartLeader }, + {VOICECHAT_STOPLEADER, BotVoiceChat_StopLeader }, + {VOICECHAT_WHOISLEADER, BotVoiceChat_WhoIsLeader }, + {VOICECHAT_WANTONDEFENSE, BotVoiceChat_WantOnDefense }, + {VOICECHAT_WANTONOFFENSE, BotVoiceChat_WantOnOffense }, + {NULL, BotVoiceChat_Dummy} +}; + +int BotVoiceChatCommand(bot_state_t *bs, int mode, char *voiceChat) { + int i, voiceOnly, clientNum, color; + char *ptr, buf[MAX_MESSAGE_SIZE], *cmd; + + if (!TeamPlayIsOn()) { + return qfalse; + } + + if ( mode == SAY_ALL ) { + return qfalse; // don't do anything with voice chats to everyone + } + + Q_strncpyz(buf, voiceChat, sizeof(buf)); + cmd = buf; + for (ptr = cmd; *cmd && *cmd > ' '; cmd++); + while (*cmd && *cmd <= ' ') *cmd++ = '\0'; + voiceOnly = atoi(ptr); + for (ptr = cmd; *cmd && *cmd > ' '; cmd++); + while (*cmd && *cmd <= ' ') *cmd++ = '\0'; + clientNum = atoi(ptr); + for (ptr = cmd; *cmd && *cmd > ' '; cmd++); + while (*cmd && *cmd <= ' ') *cmd++ = '\0'; + color = atoi(ptr); + + if (!BotSameTeam(bs, clientNum)) { + return qfalse; + } + + for (i = 0; voiceCommands[i].cmd; i++) { + if (!Q_stricmp(cmd, voiceCommands[i].cmd)) { + voiceCommands[i].func(bs, clientNum, mode); + return qtrue; + } + } + return qfalse; +} diff --git a/code/game/ai_vcmd.h b/code/game/ai_vcmd.h index 0eef6ac..f965f11 100755 --- a/code/game/ai_vcmd.h +++ b/code/game/ai_vcmd.h @@ -1,36 +1,36 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -/***************************************************************************** - * name: ai_vcmd.h - * - * desc: Quake3 bot AI - * - * $Archive: /source/code/botai/ai_vcmd.c $ - * - *****************************************************************************/ - -int BotVoiceChatCommand(bot_state_t *bs, int mode, char *voicechat); -void BotVoiceChat_Defend(bot_state_t *bs, int client, int mode); - - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: ai_vcmd.h + * + * desc: Quake3 bot AI + * + * $Archive: /source/code/botai/ai_vcmd.c $ + * + *****************************************************************************/ + +int BotVoiceChatCommand(bot_state_t *bs, int mode, char *voicechat); +void BotVoiceChat_Defend(bot_state_t *bs, int client, int mode); + + diff --git a/code/game/be_aas.h b/code/game/be_aas.h index 24f982c..4a98a8e 100755 --- a/code/game/be_aas.h +++ b/code/game/be_aas.h @@ -1,221 +1,221 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -/***************************************************************************** - * name: be_aas.h - * - * desc: Area Awareness System, stuff exported to the AI - * - * $Archive: /source/code/botlib/be_aas.h $ - * - *****************************************************************************/ - -#ifndef MAX_STRINGFIELD -#define MAX_STRINGFIELD 80 -#endif - -//travel flags -#define TFL_INVALID 0x00000001 //traveling temporary not possible -#define TFL_WALK 0x00000002 //walking -#define TFL_CROUCH 0x00000004 //crouching -#define TFL_BARRIERJUMP 0x00000008 //jumping onto a barrier -#define TFL_JUMP 0x00000010 //jumping -#define TFL_LADDER 0x00000020 //climbing a ladder -#define TFL_WALKOFFLEDGE 0x00000080 //walking of a ledge -#define TFL_SWIM 0x00000100 //swimming -#define TFL_WATERJUMP 0x00000200 //jumping out of the water -#define TFL_TELEPORT 0x00000400 //teleporting -#define TFL_ELEVATOR 0x00000800 //elevator -#define TFL_ROCKETJUMP 0x00001000 //rocket jumping -#define TFL_BFGJUMP 0x00002000 //bfg jumping -#define TFL_GRAPPLEHOOK 0x00004000 //grappling hook -#define TFL_DOUBLEJUMP 0x00008000 //double jump -#define TFL_RAMPJUMP 0x00010000 //ramp jump -#define TFL_STRAFEJUMP 0x00020000 //strafe jump -#define TFL_JUMPPAD 0x00040000 //jump pad -#define TFL_AIR 0x00080000 //travel through air -#define TFL_WATER 0x00100000 //travel through water -#define TFL_SLIME 0x00200000 //travel through slime -#define TFL_LAVA 0x00400000 //travel through lava -#define TFL_DONOTENTER 0x00800000 //travel through donotenter area -#define TFL_FUNCBOB 0x01000000 //func bobbing -#define TFL_FLIGHT 0x02000000 //flight -#define TFL_BRIDGE 0x04000000 //move over a bridge -// -#define TFL_NOTTEAM1 0x08000000 //not team 1 -#define TFL_NOTTEAM2 0x10000000 //not team 2 - -//default travel flags -#define TFL_DEFAULT TFL_WALK|TFL_CROUCH|TFL_BARRIERJUMP|\ - TFL_JUMP|TFL_LADDER|\ - TFL_WALKOFFLEDGE|TFL_SWIM|TFL_WATERJUMP|\ - TFL_TELEPORT|TFL_ELEVATOR|\ - TFL_AIR|TFL_WATER|TFL_JUMPPAD|TFL_FUNCBOB - -typedef enum -{ - SOLID_NOT, // no interaction with other objects - SOLID_TRIGGER, // only touch when inside, after moving - SOLID_BBOX, // touch on edge - SOLID_BSP // bsp clip, touch on edge -} solid_t; - -//a trace is returned when a box is swept through the AAS world -typedef struct aas_trace_s -{ - qboolean startsolid; // if true, the initial point was in a solid area - float fraction; // time completed, 1.0 = didn't hit anything - vec3_t endpos; // final position - int ent; // entity blocking the trace - int lastarea; // last area the trace was in (zero if none) - int area; // area blocking the trace (zero if none) - int planenum; // number of the plane that was hit -} aas_trace_t; - -/* Defined in botlib.h - -//bsp_trace_t hit surface -typedef struct bsp_surface_s -{ - char name[16]; - int flags; - int value; -} bsp_surface_t; - -//a trace is returned when a box is swept through the BSP world -typedef struct bsp_trace_s -{ - qboolean allsolid; // if true, plane is not valid - qboolean startsolid; // if true, the initial point was in a solid area - float fraction; // time completed, 1.0 = didn't hit anything - vec3_t endpos; // final position - cplane_t plane; // surface normal at impact - float exp_dist; // expanded plane distance - int sidenum; // number of the brush side hit - bsp_surface_t surface; // hit surface - int contents; // contents on other side of surface hit - int ent; // number of entity hit -} bsp_trace_t; -// -*/ - -//entity info -typedef struct aas_entityinfo_s -{ - int valid; // true if updated this frame - int type; // entity type - int flags; // entity flags - float ltime; // local time - float update_time; // time between last and current update - int number; // number of the entity - vec3_t origin; // origin of the entity - vec3_t angles; // angles of the model - vec3_t old_origin; // for lerping - vec3_t lastvisorigin; // last visible origin - vec3_t mins; // bounding box minimums - vec3_t maxs; // bounding box maximums - int groundent; // ground entity - int solid; // solid type - int modelindex; // model used - int modelindex2; // weapons, CTF flags, etc - int frame; // model frame number - int event; // impulse events -- muzzle flashes, footsteps, etc - int eventParm; // even parameter - int powerups; // bit flags - int weapon; // determines weapon and flash model, etc - int legsAnim; // mask off ANIM_TOGGLEBIT - int torsoAnim; // mask off ANIM_TOGGLEBIT -} aas_entityinfo_t; - -// area info -typedef struct aas_areainfo_s -{ - int contents; - int flags; - int presencetype; - int cluster; - vec3_t mins; - vec3_t maxs; - vec3_t center; -} aas_areainfo_t; - -// client movement prediction stop events, stop as soon as: -#define SE_NONE 0 -#define SE_HITGROUND 1 // the ground is hit -#define SE_LEAVEGROUND 2 // there's no ground -#define SE_ENTERWATER 4 // water is entered -#define SE_ENTERSLIME 8 // slime is entered -#define SE_ENTERLAVA 16 // lava is entered -#define SE_HITGROUNDDAMAGE 32 // the ground is hit with damage -#define SE_GAP 64 // there's a gap -#define SE_TOUCHJUMPPAD 128 // touching a jump pad area -#define SE_TOUCHTELEPORTER 256 // touching teleporter -#define SE_ENTERAREA 512 // the given stoparea is entered -#define SE_HITGROUNDAREA 1024 // a ground face in the area is hit -#define SE_HITBOUNDINGBOX 2048 // hit the specified bounding box -#define SE_TOUCHCLUSTERPORTAL 4096 // touching a cluster portal - -typedef struct aas_clientmove_s -{ - vec3_t endpos; //position at the end of movement prediction - int endarea; //area at end of movement prediction - vec3_t velocity; //velocity at the end of movement prediction - aas_trace_t trace; //last trace - int presencetype; //presence type at end of movement prediction - int stopevent; //event that made the prediction stop - int endcontents; //contents at the end of movement prediction - float time; //time predicted ahead - int frames; //number of frames predicted ahead -} aas_clientmove_t; - -// alternate route goals -#define ALTROUTEGOAL_ALL 1 -#define ALTROUTEGOAL_CLUSTERPORTALS 2 -#define ALTROUTEGOAL_VIEWPORTALS 4 - -typedef struct aas_altroutegoal_s -{ - vec3_t origin; - int areanum; - unsigned short starttraveltime; - unsigned short goaltraveltime; - unsigned short extratraveltime; -} aas_altroutegoal_t; - -// route prediction stop events -#define RSE_NONE 0 -#define RSE_NOROUTE 1 //no route to goal -#define RSE_USETRAVELTYPE 2 //stop as soon as on of the given travel types is used -#define RSE_ENTERCONTENTS 4 //stop when entering the given contents -#define RSE_ENTERAREA 8 //stop when entering the given area - -typedef struct aas_predictroute_s -{ - vec3_t endpos; //position at the end of movement prediction - int endarea; //area at end of movement prediction - int stopevent; //event that made the prediction stop - int endcontents; //contents at the end of movement prediction - int endtravelflags; //end travel flags - int numareas; //number of areas predicted ahead - int time; //time predicted ahead (in hundreth of a sec) -} aas_predictroute_t; +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: be_aas.h + * + * desc: Area Awareness System, stuff exported to the AI + * + * $Archive: /source/code/botlib/be_aas.h $ + * + *****************************************************************************/ + +#ifndef MAX_STRINGFIELD +#define MAX_STRINGFIELD 80 +#endif + +//travel flags +#define TFL_INVALID 0x00000001 //traveling temporary not possible +#define TFL_WALK 0x00000002 //walking +#define TFL_CROUCH 0x00000004 //crouching +#define TFL_BARRIERJUMP 0x00000008 //jumping onto a barrier +#define TFL_JUMP 0x00000010 //jumping +#define TFL_LADDER 0x00000020 //climbing a ladder +#define TFL_WALKOFFLEDGE 0x00000080 //walking of a ledge +#define TFL_SWIM 0x00000100 //swimming +#define TFL_WATERJUMP 0x00000200 //jumping out of the water +#define TFL_TELEPORT 0x00000400 //teleporting +#define TFL_ELEVATOR 0x00000800 //elevator +#define TFL_ROCKETJUMP 0x00001000 //rocket jumping +#define TFL_BFGJUMP 0x00002000 //bfg jumping +#define TFL_GRAPPLEHOOK 0x00004000 //grappling hook +#define TFL_DOUBLEJUMP 0x00008000 //double jump +#define TFL_RAMPJUMP 0x00010000 //ramp jump +#define TFL_STRAFEJUMP 0x00020000 //strafe jump +#define TFL_JUMPPAD 0x00040000 //jump pad +#define TFL_AIR 0x00080000 //travel through air +#define TFL_WATER 0x00100000 //travel through water +#define TFL_SLIME 0x00200000 //travel through slime +#define TFL_LAVA 0x00400000 //travel through lava +#define TFL_DONOTENTER 0x00800000 //travel through donotenter area +#define TFL_FUNCBOB 0x01000000 //func bobbing +#define TFL_FLIGHT 0x02000000 //flight +#define TFL_BRIDGE 0x04000000 //move over a bridge +// +#define TFL_NOTTEAM1 0x08000000 //not team 1 +#define TFL_NOTTEAM2 0x10000000 //not team 2 + +//default travel flags +#define TFL_DEFAULT TFL_WALK|TFL_CROUCH|TFL_BARRIERJUMP|\ + TFL_JUMP|TFL_LADDER|\ + TFL_WALKOFFLEDGE|TFL_SWIM|TFL_WATERJUMP|\ + TFL_TELEPORT|TFL_ELEVATOR|\ + TFL_AIR|TFL_WATER|TFL_JUMPPAD|TFL_FUNCBOB + +typedef enum +{ + SOLID_NOT, // no interaction with other objects + SOLID_TRIGGER, // only touch when inside, after moving + SOLID_BBOX, // touch on edge + SOLID_BSP // bsp clip, touch on edge +} solid_t; + +//a trace is returned when a box is swept through the AAS world +typedef struct aas_trace_s +{ + qboolean startsolid; // if true, the initial point was in a solid area + float fraction; // time completed, 1.0 = didn't hit anything + vec3_t endpos; // final position + int ent; // entity blocking the trace + int lastarea; // last area the trace was in (zero if none) + int area; // area blocking the trace (zero if none) + int planenum; // number of the plane that was hit +} aas_trace_t; + +/* Defined in botlib.h + +//bsp_trace_t hit surface +typedef struct bsp_surface_s +{ + char name[16]; + int flags; + int value; +} bsp_surface_t; + +//a trace is returned when a box is swept through the BSP world +typedef struct bsp_trace_s +{ + qboolean allsolid; // if true, plane is not valid + qboolean startsolid; // if true, the initial point was in a solid area + float fraction; // time completed, 1.0 = didn't hit anything + vec3_t endpos; // final position + cplane_t plane; // surface normal at impact + float exp_dist; // expanded plane distance + int sidenum; // number of the brush side hit + bsp_surface_t surface; // hit surface + int contents; // contents on other side of surface hit + int ent; // number of entity hit +} bsp_trace_t; +// +*/ + +//entity info +typedef struct aas_entityinfo_s +{ + int valid; // true if updated this frame + int type; // entity type + int flags; // entity flags + float ltime; // local time + float update_time; // time between last and current update + int number; // number of the entity + vec3_t origin; // origin of the entity + vec3_t angles; // angles of the model + vec3_t old_origin; // for lerping + vec3_t lastvisorigin; // last visible origin + vec3_t mins; // bounding box minimums + vec3_t maxs; // bounding box maximums + int groundent; // ground entity + int solid; // solid type + int modelindex; // model used + int modelindex2; // weapons, CTF flags, etc + int frame; // model frame number + int event; // impulse events -- muzzle flashes, footsteps, etc + int eventParm; // even parameter + int powerups; // bit flags + int weapon; // determines weapon and flash model, etc + int legsAnim; // mask off ANIM_TOGGLEBIT + int torsoAnim; // mask off ANIM_TOGGLEBIT +} aas_entityinfo_t; + +// area info +typedef struct aas_areainfo_s +{ + int contents; + int flags; + int presencetype; + int cluster; + vec3_t mins; + vec3_t maxs; + vec3_t center; +} aas_areainfo_t; + +// client movement prediction stop events, stop as soon as: +#define SE_NONE 0 +#define SE_HITGROUND 1 // the ground is hit +#define SE_LEAVEGROUND 2 // there's no ground +#define SE_ENTERWATER 4 // water is entered +#define SE_ENTERSLIME 8 // slime is entered +#define SE_ENTERLAVA 16 // lava is entered +#define SE_HITGROUNDDAMAGE 32 // the ground is hit with damage +#define SE_GAP 64 // there's a gap +#define SE_TOUCHJUMPPAD 128 // touching a jump pad area +#define SE_TOUCHTELEPORTER 256 // touching teleporter +#define SE_ENTERAREA 512 // the given stoparea is entered +#define SE_HITGROUNDAREA 1024 // a ground face in the area is hit +#define SE_HITBOUNDINGBOX 2048 // hit the specified bounding box +#define SE_TOUCHCLUSTERPORTAL 4096 // touching a cluster portal + +typedef struct aas_clientmove_s +{ + vec3_t endpos; //position at the end of movement prediction + int endarea; //area at end of movement prediction + vec3_t velocity; //velocity at the end of movement prediction + aas_trace_t trace; //last trace + int presencetype; //presence type at end of movement prediction + int stopevent; //event that made the prediction stop + int endcontents; //contents at the end of movement prediction + float time; //time predicted ahead + int frames; //number of frames predicted ahead +} aas_clientmove_t; + +// alternate route goals +#define ALTROUTEGOAL_ALL 1 +#define ALTROUTEGOAL_CLUSTERPORTALS 2 +#define ALTROUTEGOAL_VIEWPORTALS 4 + +typedef struct aas_altroutegoal_s +{ + vec3_t origin; + int areanum; + unsigned short starttraveltime; + unsigned short goaltraveltime; + unsigned short extratraveltime; +} aas_altroutegoal_t; + +// route prediction stop events +#define RSE_NONE 0 +#define RSE_NOROUTE 1 //no route to goal +#define RSE_USETRAVELTYPE 2 //stop as soon as on of the given travel types is used +#define RSE_ENTERCONTENTS 4 //stop when entering the given contents +#define RSE_ENTERAREA 8 //stop when entering the given area + +typedef struct aas_predictroute_s +{ + vec3_t endpos; //position at the end of movement prediction + int endarea; //area at end of movement prediction + int stopevent; //event that made the prediction stop + int endcontents; //contents at the end of movement prediction + int endtravelflags; //end travel flags + int numareas; //number of areas predicted ahead + int time; //time predicted ahead (in hundreth of a sec) +} aas_predictroute_t; diff --git a/code/game/be_ai_char.h b/code/game/be_ai_char.h index 89f1e52..573fa1d 100755 --- a/code/game/be_ai_char.h +++ b/code/game/be_ai_char.h @@ -1,48 +1,48 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -/***************************************************************************** - * name: be_ai_char.h - * - * desc: bot characters - * - * $Archive: /source/code/botlib/be_ai_char.h $ - * - *****************************************************************************/ - -//loads a bot character from a file -int BotLoadCharacter(char *charfile, float skill); -//frees a bot character -void BotFreeCharacter(int character); -//returns a float characteristic -float Characteristic_Float(int character, int index); -//returns a bounded float characteristic -float Characteristic_BFloat(int character, int index, float min, float max); -//returns an integer characteristic -int Characteristic_Integer(int character, int index); -//returns a bounded integer characteristic -int Characteristic_BInteger(int character, int index, int min, int max); -//returns a string characteristic -void Characteristic_String(int character, int index, char *buf, int size); -//free cached bot characters -void BotShutdownCharacters(void); +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: be_ai_char.h + * + * desc: bot characters + * + * $Archive: /source/code/botlib/be_ai_char.h $ + * + *****************************************************************************/ + +//loads a bot character from a file +int BotLoadCharacter(char *charfile, float skill); +//frees a bot character +void BotFreeCharacter(int character); +//returns a float characteristic +float Characteristic_Float(int character, int index); +//returns a bounded float characteristic +float Characteristic_BFloat(int character, int index, float min, float max); +//returns an integer characteristic +int Characteristic_Integer(int character, int index); +//returns a bounded integer characteristic +int Characteristic_BInteger(int character, int index, int min, int max); +//returns a string characteristic +void Characteristic_String(int character, int index, char *buf, int size); +//free cached bot characters +void BotShutdownCharacters(void); diff --git a/code/game/be_ai_chat.h b/code/game/be_ai_chat.h index 8d3d7a4..67a38ed 100755 --- a/code/game/be_ai_chat.h +++ b/code/game/be_ai_chat.h @@ -1,113 +1,113 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -/***************************************************************************** - * name: be_ai_chat.h - * - * desc: char AI - * - * $Archive: /source/code/botlib/be_ai_chat.h $ - * - *****************************************************************************/ - -#define MAX_MESSAGE_SIZE 256 -#define MAX_CHATTYPE_NAME 32 -#define MAX_MATCHVARIABLES 8 - -#define CHAT_GENDERLESS 0 -#define CHAT_GENDERFEMALE 1 -#define CHAT_GENDERMALE 2 - -#define CHAT_ALL 0 -#define CHAT_TEAM 1 -#define CHAT_TELL 2 - -//a console message -typedef struct bot_consolemessage_s -{ - int handle; - float time; //message time - int type; //message type - char message[MAX_MESSAGE_SIZE]; //message - struct bot_consolemessage_s *prev, *next; //prev and next in list -} bot_consolemessage_t; - -//match variable -typedef struct bot_matchvariable_s -{ - char offset; - int length; -} bot_matchvariable_t; -//returned to AI when a match is found -typedef struct bot_match_s -{ - char string[MAX_MESSAGE_SIZE]; - int type; - int subtype; - bot_matchvariable_t variables[MAX_MATCHVARIABLES]; -} bot_match_t; - -//setup the chat AI -int BotSetupChatAI(void); -//shutdown the chat AI -void BotShutdownChatAI(void); -//returns the handle to a newly allocated chat state -int BotAllocChatState(void); -//frees the chatstate -void BotFreeChatState(int handle); -//adds a console message to the chat state -void BotQueueConsoleMessage(int chatstate, int type, char *message); -//removes the console message from the chat state -void BotRemoveConsoleMessage(int chatstate, int handle); -//returns the next console message from the state -int BotNextConsoleMessage(int chatstate, bot_consolemessage_t *cm); -//returns the number of console messages currently stored in the state -int BotNumConsoleMessages(int chatstate); -//selects a chat message of the given type -void BotInitialChat(int chatstate, char *type, int mcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7); -//returns the number of initial chat messages of the given type -int BotNumInitialChats(int chatstate, char *type); -//find and select a reply for the given message -int BotReplyChat(int chatstate, char *message, int mcontext, int vcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7); -//returns the length of the currently selected chat message -int BotChatLength(int chatstate); -//enters the selected chat message -void BotEnterChat(int chatstate, int clientto, int sendto); -//get the chat message ready to be output -void BotGetChatMessage(int chatstate, char *buf, int size); -//checks if the first string contains the second one, returns index into first string or -1 if not found -int StringContains(char *str1, char *str2, int casesensitive); -//finds a match for the given string using the match templates -int BotFindMatch(char *str, bot_match_t *match, unsigned long int context); -//returns a variable from a match -void BotMatchVariable(bot_match_t *match, int variable, char *buf, int size); -//unify all the white spaces in the string -void UnifyWhiteSpaces(char *string); -//replace all the context related synonyms in the string -void BotReplaceSynonyms(char *string, unsigned long int context); -//loads a chat file for the chat state -int BotLoadChatFile(int chatstate, char *chatfile, char *chatname); -//store the gender of the bot in the chat state -void BotSetChatGender(int chatstate, int gender); -//store the bot name in the chat state -void BotSetChatName(int chatstate, char *name, int client); - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +/***************************************************************************** + * name: be_ai_chat.h + * + * desc: char AI + * + * $Archive: /source/code/botlib/be_ai_chat.h $ + * + *****************************************************************************/ + +#define MAX_MESSAGE_SIZE 256 +#define MAX_CHATTYPE_NAME 32 +#define MAX_MATCHVARIABLES 8 + +#define CHAT_GENDERLESS 0 +#define CHAT_GENDERFEMALE 1 +#define CHAT_GENDERMALE 2 + +#define CHAT_ALL 0 +#define CHAT_TEAM 1 +#define CHAT_TELL 2 + +//a console message +typedef struct bot_consolemessage_s +{ + int handle; + float time; //message time + int type; //message type + char message[MAX_MESSAGE_SIZE]; //message + struct bot_consolemessage_s *prev, *next; //prev and next in list +} bot_consolemessage_t; + +//match variable +typedef struct bot_matchvariable_s +{ + char offset; + int length; +} bot_matchvariable_t; +//returned to AI when a match is found +typedef struct bot_match_s +{ + char string[MAX_MESSAGE_SIZE]; + int type; + int subtype; + bot_matchvariable_t variables[MAX_MATCHVARIABLES]; +} bot_match_t; + +//setup the chat AI +int BotSetupChatAI(void); +//shutdown the chat AI +void BotShutdownChatAI(void); +//returns the handle to a newly allocated chat state +int BotAllocChatState(void); +//frees the chatstate +void BotFreeChatState(int handle); +//adds a console message to the chat state +void BotQueueConsoleMessage(int chatstate, int type, char *message); +//removes the console message from the chat state +void BotRemoveConsoleMessage(int chatstate, int handle); +//returns the next console message from the state +int BotNextConsoleMessage(int chatstate, bot_consolemessage_t *cm); +//returns the number of console messages currently stored in the state +int BotNumConsoleMessages(int chatstate); +//selects a chat message of the given type +void BotInitialChat(int chatstate, char *type, int mcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7); +//returns the number of initial chat messages of the given type +int BotNumInitialChats(int chatstate, char *type); +//find and select a reply for the given message +int BotReplyChat(int chatstate, char *message, int mcontext, int vcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7); +//returns the length of the currently selected chat message +int BotChatLength(int chatstate); +//enters the selected chat message +void BotEnterChat(int chatstate, int clientto, int sendto); +//get the chat message ready to be output +void BotGetChatMessage(int chatstate, char *buf, int size); +//checks if the first string contains the second one, returns index into first string or -1 if not found +int StringContains(char *str1, char *str2, int casesensitive); +//finds a match for the given string using the match templates +int BotFindMatch(char *str, bot_match_t *match, unsigned long int context); +//returns a variable from a match +void BotMatchVariable(bot_match_t *match, int variable, char *buf, int size); +//unify all the white spaces in the string +void UnifyWhiteSpaces(char *string); +//replace all the context related synonyms in the string +void BotReplaceSynonyms(char *string, unsigned long int context); +//loads a chat file for the chat state +int BotLoadChatFile(int chatstate, char *chatfile, char *chatname); +//store the gender of the bot in the chat state +void BotSetChatGender(int chatstate, int gender); +//store the bot name in the chat state +void BotSetChatName(int chatstate, char *name, int client); + diff --git a/code/game/be_ai_gen.h b/code/game/be_ai_gen.h index 94528ed..2a4b53d 100755 --- a/code/game/be_ai_gen.h +++ b/code/game/be_ai_gen.h @@ -1,33 +1,33 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -/***************************************************************************** - * name: be_ai_gen.h - * - * desc: genetic selection - * - * $Archive: /source/code/botlib/be_ai_gen.h $ - * - *****************************************************************************/ - -int GeneticParentsAndChildSelection(int numranks, float *ranks, int *parent1, int *parent2, int *child); +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: be_ai_gen.h + * + * desc: genetic selection + * + * $Archive: /source/code/botlib/be_ai_gen.h $ + * + *****************************************************************************/ + +int GeneticParentsAndChildSelection(int numranks, float *ranks, int *parent1, int *parent2, int *child); diff --git a/code/game/be_ai_goal.h b/code/game/be_ai_goal.h index 1fdddf3..354609d 100755 --- a/code/game/be_ai_goal.h +++ b/code/game/be_ai_goal.h @@ -1,118 +1,118 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -/***************************************************************************** - * name: be_ai_goal.h - * - * desc: goal AI - * - * $Archive: /source/code/botlib/be_ai_goal.h $ - * - *****************************************************************************/ - -#define MAX_AVOIDGOALS 256 -#define MAX_GOALSTACK 8 - -#define GFL_NONE 0 -#define GFL_ITEM 1 -#define GFL_ROAM 2 -#define GFL_DROPPED 4 - -//a bot goal -typedef struct bot_goal_s -{ - vec3_t origin; //origin of the goal - int areanum; //area number of the goal - vec3_t mins, maxs; //mins and maxs of the goal - int entitynum; //number of the goal entity - int number; //goal number - int flags; //goal flags - int iteminfo; //item information -} bot_goal_t; - -//reset the whole goal state, but keep the item weights -void BotResetGoalState(int goalstate); -//reset avoid goals -void BotResetAvoidGoals(int goalstate); -//remove the goal with the given number from the avoid goals -void BotRemoveFromAvoidGoals(int goalstate, int number); -//push a goal onto the goal stack -void BotPushGoal(int goalstate, bot_goal_t *goal); -//pop a goal from the goal stack -void BotPopGoal(int goalstate); -//empty the bot's goal stack -void BotEmptyGoalStack(int goalstate); -//dump the avoid goals -void BotDumpAvoidGoals(int goalstate); -//dump the goal stack -void BotDumpGoalStack(int goalstate); -//get the name name of the goal with the given number -void BotGoalName(int number, char *name, int size); -//get the top goal from the stack -int BotGetTopGoal(int goalstate, bot_goal_t *goal); -//get the second goal on the stack -int BotGetSecondGoal(int goalstate, bot_goal_t *goal); -//choose the best long term goal item for the bot -int BotChooseLTGItem(int goalstate, vec3_t origin, int *inventory, int travelflags); -//choose the best nearby goal item for the bot -//the item may not be further away from the current bot position than maxtime -//also the travel time from the nearby goal towards the long term goal may not -//be larger than the travel time towards the long term goal from the current bot position -int BotChooseNBGItem(int goalstate, vec3_t origin, int *inventory, int travelflags, - bot_goal_t *ltg, float maxtime); -//returns true if the bot touches the goal -int BotTouchingGoal(vec3_t origin, bot_goal_t *goal); -//returns true if the goal should be visible but isn't -int BotItemGoalInVisButNotVisible(int viewer, vec3_t eye, vec3_t viewangles, bot_goal_t *goal); -//search for a goal for the given classname, the index can be used -//as a start point for the search when multiple goals are available with that same classname -int BotGetLevelItemGoal(int index, char *classname, bot_goal_t *goal); -//get the next camp spot in the map -int BotGetNextCampSpotGoal(int num, bot_goal_t *goal); -//get the map location with the given name -int BotGetMapLocationGoal(char *name, bot_goal_t *goal); -//returns the avoid goal time -float BotAvoidGoalTime(int goalstate, int number); -//set the avoid goal time -void BotSetAvoidGoalTime(int goalstate, int number, float avoidtime); -//initializes the items in the level -void BotInitLevelItems(void); -//regularly update dynamic entity items (dropped weapons, flags etc.) -void BotUpdateEntityItems(void); -//interbreed the goal fuzzy logic -void BotInterbreedGoalFuzzyLogic(int parent1, int parent2, int child); -//save the goal fuzzy logic to disk -void BotSaveGoalFuzzyLogic(int goalstate, char *filename); -//mutate the goal fuzzy logic -void BotMutateGoalFuzzyLogic(int goalstate, float range); -//loads item weights for the bot -int BotLoadItemWeights(int goalstate, char *filename); -//frees the item weights of the bot -void BotFreeItemWeights(int goalstate); -//returns the handle of a newly allocated goal state -int BotAllocGoalState(int client); -//free the given goal state -void BotFreeGoalState(int handle); -//setup the goal AI -int BotSetupGoalAI(void); -//shut down the goal AI -void BotShutdownGoalAI(void); +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +/***************************************************************************** + * name: be_ai_goal.h + * + * desc: goal AI + * + * $Archive: /source/code/botlib/be_ai_goal.h $ + * + *****************************************************************************/ + +#define MAX_AVOIDGOALS 256 +#define MAX_GOALSTACK 8 + +#define GFL_NONE 0 +#define GFL_ITEM 1 +#define GFL_ROAM 2 +#define GFL_DROPPED 4 + +//a bot goal +typedef struct bot_goal_s +{ + vec3_t origin; //origin of the goal + int areanum; //area number of the goal + vec3_t mins, maxs; //mins and maxs of the goal + int entitynum; //number of the goal entity + int number; //goal number + int flags; //goal flags + int iteminfo; //item information +} bot_goal_t; + +//reset the whole goal state, but keep the item weights +void BotResetGoalState(int goalstate); +//reset avoid goals +void BotResetAvoidGoals(int goalstate); +//remove the goal with the given number from the avoid goals +void BotRemoveFromAvoidGoals(int goalstate, int number); +//push a goal onto the goal stack +void BotPushGoal(int goalstate, bot_goal_t *goal); +//pop a goal from the goal stack +void BotPopGoal(int goalstate); +//empty the bot's goal stack +void BotEmptyGoalStack(int goalstate); +//dump the avoid goals +void BotDumpAvoidGoals(int goalstate); +//dump the goal stack +void BotDumpGoalStack(int goalstate); +//get the name name of the goal with the given number +void BotGoalName(int number, char *name, int size); +//get the top goal from the stack +int BotGetTopGoal(int goalstate, bot_goal_t *goal); +//get the second goal on the stack +int BotGetSecondGoal(int goalstate, bot_goal_t *goal); +//choose the best long term goal item for the bot +int BotChooseLTGItem(int goalstate, vec3_t origin, int *inventory, int travelflags); +//choose the best nearby goal item for the bot +//the item may not be further away from the current bot position than maxtime +//also the travel time from the nearby goal towards the long term goal may not +//be larger than the travel time towards the long term goal from the current bot position +int BotChooseNBGItem(int goalstate, vec3_t origin, int *inventory, int travelflags, + bot_goal_t *ltg, float maxtime); +//returns true if the bot touches the goal +int BotTouchingGoal(vec3_t origin, bot_goal_t *goal); +//returns true if the goal should be visible but isn't +int BotItemGoalInVisButNotVisible(int viewer, vec3_t eye, vec3_t viewangles, bot_goal_t *goal); +//search for a goal for the given classname, the index can be used +//as a start point for the search when multiple goals are available with that same classname +int BotGetLevelItemGoal(int index, char *classname, bot_goal_t *goal); +//get the next camp spot in the map +int BotGetNextCampSpotGoal(int num, bot_goal_t *goal); +//get the map location with the given name +int BotGetMapLocationGoal(char *name, bot_goal_t *goal); +//returns the avoid goal time +float BotAvoidGoalTime(int goalstate, int number); +//set the avoid goal time +void BotSetAvoidGoalTime(int goalstate, int number, float avoidtime); +//initializes the items in the level +void BotInitLevelItems(void); +//regularly update dynamic entity items (dropped weapons, flags etc.) +void BotUpdateEntityItems(void); +//interbreed the goal fuzzy logic +void BotInterbreedGoalFuzzyLogic(int parent1, int parent2, int child); +//save the goal fuzzy logic to disk +void BotSaveGoalFuzzyLogic(int goalstate, char *filename); +//mutate the goal fuzzy logic +void BotMutateGoalFuzzyLogic(int goalstate, float range); +//loads item weights for the bot +int BotLoadItemWeights(int goalstate, char *filename); +//frees the item weights of the bot +void BotFreeItemWeights(int goalstate); +//returns the handle of a newly allocated goal state +int BotAllocGoalState(int client); +//free the given goal state +void BotFreeGoalState(int handle); +//setup the goal AI +int BotSetupGoalAI(void); +//shut down the goal AI +void BotShutdownGoalAI(void); diff --git a/code/game/be_ai_move.h b/code/game/be_ai_move.h index 1f0c756..fb80819 100755 --- a/code/game/be_ai_move.h +++ b/code/game/be_ai_move.h @@ -1,142 +1,142 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -/***************************************************************************** - * name: be_ai_move.h - * - * desc: movement AI - * - * $Archive: /source/code/botlib/be_ai_move.h $ - * - *****************************************************************************/ - -//movement types -#define MOVE_WALK 1 -#define MOVE_CROUCH 2 -#define MOVE_JUMP 4 -#define MOVE_GRAPPLE 8 -#define MOVE_ROCKETJUMP 16 -#define MOVE_BFGJUMP 32 -//move flags -#define MFL_BARRIERJUMP 1 //bot is performing a barrier jump -#define MFL_ONGROUND 2 //bot is in the ground -#define MFL_SWIMMING 4 //bot is swimming -#define MFL_AGAINSTLADDER 8 //bot is against a ladder -#define MFL_WATERJUMP 16 //bot is waterjumping -#define MFL_TELEPORTED 32 //bot is being teleported -#define MFL_GRAPPLEPULL 64 //bot is being pulled by the grapple -#define MFL_ACTIVEGRAPPLE 128 //bot is using the grapple hook -#define MFL_GRAPPLERESET 256 //bot has reset the grapple -#define MFL_WALK 512 //bot should walk slowly -// move result flags -#define MOVERESULT_MOVEMENTVIEW 1 //bot uses view for movement -#define MOVERESULT_SWIMVIEW 2 //bot uses view for swimming -#define MOVERESULT_WAITING 4 //bot is waiting for something -#define MOVERESULT_MOVEMENTVIEWSET 8 //bot has set the view in movement code -#define MOVERESULT_MOVEMENTWEAPON 16 //bot uses weapon for movement -#define MOVERESULT_ONTOPOFOBSTACLE 32 //bot is ontop of obstacle -#define MOVERESULT_ONTOPOF_FUNCBOB 64 //bot is ontop of a func_bobbing -#define MOVERESULT_ONTOPOF_ELEVATOR 128 //bot is ontop of an elevator (func_plat) -#define MOVERESULT_BLOCKEDBYAVOIDSPOT 256 //bot is blocked by an avoid spot -// -#define MAX_AVOIDREACH 1 -#define MAX_AVOIDSPOTS 32 -// avoid spot types -#define AVOID_CLEAR 0 //clear all avoid spots -#define AVOID_ALWAYS 1 //avoid always -#define AVOID_DONTBLOCK 2 //never totally block -// restult types -#define RESULTTYPE_ELEVATORUP 1 //elevator is up -#define RESULTTYPE_WAITFORFUNCBOBBING 2 //waiting for func bobbing to arrive -#define RESULTTYPE_BADGRAPPLEPATH 4 //grapple path is obstructed -#define RESULTTYPE_INSOLIDAREA 8 //stuck in solid area, this is bad - -//structure used to initialize the movement state -//the or_moveflags MFL_ONGROUND, MFL_TELEPORTED and MFL_WATERJUMP come from the playerstate -typedef struct bot_initmove_s -{ - vec3_t origin; //origin of the bot - vec3_t velocity; //velocity of the bot - vec3_t viewoffset; //view offset - int entitynum; //entity number of the bot - int client; //client number of the bot - float thinktime; //time the bot thinks - int presencetype; //presencetype of the bot - vec3_t viewangles; //view angles of the bot - int or_moveflags; //values ored to the movement flags -} bot_initmove_t; - -//NOTE: the ideal_viewangles are only valid if MFL_MOVEMENTVIEW is set -typedef struct bot_moveresult_s -{ - int failure; //true if movement failed all together - int type; //failure or blocked type - int blocked; //true if blocked by an entity - int blockentity; //entity blocking the bot - int traveltype; //last executed travel type - int flags; //result flags - int weapon; //weapon used for movement - vec3_t movedir; //movement direction - vec3_t ideal_viewangles; //ideal viewangles for the movement -} bot_moveresult_t; - -// bk001204: from code/botlib/be_ai_move.c -// TTimo 04/12/2001 was moved here to avoid dup defines -typedef struct bot_avoidspot_s -{ - vec3_t origin; - float radius; - int type; -} bot_avoidspot_t; - -//resets the whole move state -void BotResetMoveState(int movestate); -//moves the bot to the given goal -void BotMoveToGoal(bot_moveresult_t *result, int movestate, bot_goal_t *goal, int travelflags); -//moves the bot in the specified direction using the specified type of movement -int BotMoveInDirection(int movestate, vec3_t dir, float speed, int type); -//reset avoid reachability -void BotResetAvoidReach(int movestate); -//resets the last avoid reachability -void BotResetLastAvoidReach(int movestate); -//returns a reachability area if the origin is in one -int BotReachabilityArea(vec3_t origin, int client); -//view target based on movement -int BotMovementViewTarget(int movestate, bot_goal_t *goal, int travelflags, float lookahead, vec3_t target); -//predict the position of a player based on movement towards a goal -int BotPredictVisiblePosition(vec3_t origin, int areanum, bot_goal_t *goal, int travelflags, vec3_t target); -//returns the handle of a newly allocated movestate -int BotAllocMoveState(void); -//frees the movestate with the given handle -void BotFreeMoveState(int handle); -//initialize movement state before performing any movement -void BotInitMoveState(int handle, bot_initmove_t *initmove); -//add a spot to avoid (if type == AVOID_CLEAR all spots are removed) -void BotAddAvoidSpot(int movestate, vec3_t origin, float radius, int type); -//must be called every map change -void BotSetBrushModelTypes(void); -//setup movement AI -int BotSetupMoveAI(void); -//shutdown movement AI -void BotShutdownMoveAI(void); - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: be_ai_move.h + * + * desc: movement AI + * + * $Archive: /source/code/botlib/be_ai_move.h $ + * + *****************************************************************************/ + +//movement types +#define MOVE_WALK 1 +#define MOVE_CROUCH 2 +#define MOVE_JUMP 4 +#define MOVE_GRAPPLE 8 +#define MOVE_ROCKETJUMP 16 +#define MOVE_BFGJUMP 32 +//move flags +#define MFL_BARRIERJUMP 1 //bot is performing a barrier jump +#define MFL_ONGROUND 2 //bot is in the ground +#define MFL_SWIMMING 4 //bot is swimming +#define MFL_AGAINSTLADDER 8 //bot is against a ladder +#define MFL_WATERJUMP 16 //bot is waterjumping +#define MFL_TELEPORTED 32 //bot is being teleported +#define MFL_GRAPPLEPULL 64 //bot is being pulled by the grapple +#define MFL_ACTIVEGRAPPLE 128 //bot is using the grapple hook +#define MFL_GRAPPLERESET 256 //bot has reset the grapple +#define MFL_WALK 512 //bot should walk slowly +// move result flags +#define MOVERESULT_MOVEMENTVIEW 1 //bot uses view for movement +#define MOVERESULT_SWIMVIEW 2 //bot uses view for swimming +#define MOVERESULT_WAITING 4 //bot is waiting for something +#define MOVERESULT_MOVEMENTVIEWSET 8 //bot has set the view in movement code +#define MOVERESULT_MOVEMENTWEAPON 16 //bot uses weapon for movement +#define MOVERESULT_ONTOPOFOBSTACLE 32 //bot is ontop of obstacle +#define MOVERESULT_ONTOPOF_FUNCBOB 64 //bot is ontop of a func_bobbing +#define MOVERESULT_ONTOPOF_ELEVATOR 128 //bot is ontop of an elevator (func_plat) +#define MOVERESULT_BLOCKEDBYAVOIDSPOT 256 //bot is blocked by an avoid spot +// +#define MAX_AVOIDREACH 1 +#define MAX_AVOIDSPOTS 32 +// avoid spot types +#define AVOID_CLEAR 0 //clear all avoid spots +#define AVOID_ALWAYS 1 //avoid always +#define AVOID_DONTBLOCK 2 //never totally block +// restult types +#define RESULTTYPE_ELEVATORUP 1 //elevator is up +#define RESULTTYPE_WAITFORFUNCBOBBING 2 //waiting for func bobbing to arrive +#define RESULTTYPE_BADGRAPPLEPATH 4 //grapple path is obstructed +#define RESULTTYPE_INSOLIDAREA 8 //stuck in solid area, this is bad + +//structure used to initialize the movement state +//the or_moveflags MFL_ONGROUND, MFL_TELEPORTED and MFL_WATERJUMP come from the playerstate +typedef struct bot_initmove_s +{ + vec3_t origin; //origin of the bot + vec3_t velocity; //velocity of the bot + vec3_t viewoffset; //view offset + int entitynum; //entity number of the bot + int client; //client number of the bot + float thinktime; //time the bot thinks + int presencetype; //presencetype of the bot + vec3_t viewangles; //view angles of the bot + int or_moveflags; //values ored to the movement flags +} bot_initmove_t; + +//NOTE: the ideal_viewangles are only valid if MFL_MOVEMENTVIEW is set +typedef struct bot_moveresult_s +{ + int failure; //true if movement failed all together + int type; //failure or blocked type + int blocked; //true if blocked by an entity + int blockentity; //entity blocking the bot + int traveltype; //last executed travel type + int flags; //result flags + int weapon; //weapon used for movement + vec3_t movedir; //movement direction + vec3_t ideal_viewangles; //ideal viewangles for the movement +} bot_moveresult_t; + +// bk001204: from code/botlib/be_ai_move.c +// TTimo 04/12/2001 was moved here to avoid dup defines +typedef struct bot_avoidspot_s +{ + vec3_t origin; + float radius; + int type; +} bot_avoidspot_t; + +//resets the whole move state +void BotResetMoveState(int movestate); +//moves the bot to the given goal +void BotMoveToGoal(bot_moveresult_t *result, int movestate, bot_goal_t *goal, int travelflags); +//moves the bot in the specified direction using the specified type of movement +int BotMoveInDirection(int movestate, vec3_t dir, float speed, int type); +//reset avoid reachability +void BotResetAvoidReach(int movestate); +//resets the last avoid reachability +void BotResetLastAvoidReach(int movestate); +//returns a reachability area if the origin is in one +int BotReachabilityArea(vec3_t origin, int client); +//view target based on movement +int BotMovementViewTarget(int movestate, bot_goal_t *goal, int travelflags, float lookahead, vec3_t target); +//predict the position of a player based on movement towards a goal +int BotPredictVisiblePosition(vec3_t origin, int areanum, bot_goal_t *goal, int travelflags, vec3_t target); +//returns the handle of a newly allocated movestate +int BotAllocMoveState(void); +//frees the movestate with the given handle +void BotFreeMoveState(int handle); +//initialize movement state before performing any movement +void BotInitMoveState(int handle, bot_initmove_t *initmove); +//add a spot to avoid (if type == AVOID_CLEAR all spots are removed) +void BotAddAvoidSpot(int movestate, vec3_t origin, float radius, int type); +//must be called every map change +void BotSetBrushModelTypes(void); +//setup movement AI +int BotSetupMoveAI(void); +//shutdown movement AI +void BotShutdownMoveAI(void); + diff --git a/code/game/be_ai_weap.h b/code/game/be_ai_weap.h index 6dbb5a2..b78ad68 100755 --- a/code/game/be_ai_weap.h +++ b/code/game/be_ai_weap.h @@ -1,104 +1,104 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -/***************************************************************************** - * name: be_ai_weap.h - * - * desc: weapon AI - * - * $Archive: /source/code/botlib/be_ai_weap.h $ - * - *****************************************************************************/ - -//projectile flags -#define PFL_WINDOWDAMAGE 1 //projectile damages through window -#define PFL_RETURN 2 //set when projectile returns to owner -//weapon flags -#define WFL_FIRERELEASED 1 //set when projectile is fired with key-up event -//damage types -#define DAMAGETYPE_IMPACT 1 //damage on impact -#define DAMAGETYPE_RADIAL 2 //radial damage -#define DAMAGETYPE_VISIBLE 4 //damage to all entities visible to the projectile - -typedef struct projectileinfo_s -{ - char name[MAX_STRINGFIELD]; - char model[MAX_STRINGFIELD]; - int flags; - float gravity; - int damage; - float radius; - int visdamage; - int damagetype; - int healthinc; - float push; - float detonation; - float bounce; - float bouncefric; - float bouncestop; -} projectileinfo_t; - -typedef struct weaponinfo_s -{ - int valid; //true if the weapon info is valid - int number; //number of the weapon - char name[MAX_STRINGFIELD]; - char model[MAX_STRINGFIELD]; - int level; - int weaponindex; - int flags; - char projectile[MAX_STRINGFIELD]; - int numprojectiles; - float hspread; - float vspread; - float speed; - float acceleration; - vec3_t recoil; - vec3_t offset; - vec3_t angleoffset; - float extrazvelocity; - int ammoamount; - int ammoindex; - float activate; - float reload; - float spinup; - float spindown; - projectileinfo_t proj; //pointer to the used projectile -} weaponinfo_t; - -//setup the weapon AI -int BotSetupWeaponAI(void); -//shut down the weapon AI -void BotShutdownWeaponAI(void); -//returns the best weapon to fight with -int BotChooseBestFightWeapon(int weaponstate, int *inventory); -//returns the information of the current weapon -void BotGetWeaponInfo(int weaponstate, int weapon, weaponinfo_t *weaponinfo); -//loads the weapon weights -int BotLoadWeaponWeights(int weaponstate, char *filename); -//returns a handle to a newly allocated weapon state -int BotAllocWeaponState(void); -//frees the weapon state -void BotFreeWeaponState(int weaponstate); -//resets the whole weapon state -void BotResetWeaponState(int weaponstate); +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: be_ai_weap.h + * + * desc: weapon AI + * + * $Archive: /source/code/botlib/be_ai_weap.h $ + * + *****************************************************************************/ + +//projectile flags +#define PFL_WINDOWDAMAGE 1 //projectile damages through window +#define PFL_RETURN 2 //set when projectile returns to owner +//weapon flags +#define WFL_FIRERELEASED 1 //set when projectile is fired with key-up event +//damage types +#define DAMAGETYPE_IMPACT 1 //damage on impact +#define DAMAGETYPE_RADIAL 2 //radial damage +#define DAMAGETYPE_VISIBLE 4 //damage to all entities visible to the projectile + +typedef struct projectileinfo_s +{ + char name[MAX_STRINGFIELD]; + char model[MAX_STRINGFIELD]; + int flags; + float gravity; + int damage; + float radius; + int visdamage; + int damagetype; + int healthinc; + float push; + float detonation; + float bounce; + float bouncefric; + float bouncestop; +} projectileinfo_t; + +typedef struct weaponinfo_s +{ + int valid; //true if the weapon info is valid + int number; //number of the weapon + char name[MAX_STRINGFIELD]; + char model[MAX_STRINGFIELD]; + int level; + int weaponindex; + int flags; + char projectile[MAX_STRINGFIELD]; + int numprojectiles; + float hspread; + float vspread; + float speed; + float acceleration; + vec3_t recoil; + vec3_t offset; + vec3_t angleoffset; + float extrazvelocity; + int ammoamount; + int ammoindex; + float activate; + float reload; + float spinup; + float spindown; + projectileinfo_t proj; //pointer to the used projectile +} weaponinfo_t; + +//setup the weapon AI +int BotSetupWeaponAI(void); +//shut down the weapon AI +void BotShutdownWeaponAI(void); +//returns the best weapon to fight with +int BotChooseBestFightWeapon(int weaponstate, int *inventory); +//returns the information of the current weapon +void BotGetWeaponInfo(int weaponstate, int weapon, weaponinfo_t *weaponinfo); +//loads the weapon weights +int BotLoadWeaponWeights(int weaponstate, char *filename); +//returns a handle to a newly allocated weapon state +int BotAllocWeaponState(void); +//frees the weapon state +void BotFreeWeaponState(int weaponstate); +//resets the whole weapon state +void BotResetWeaponState(int weaponstate); diff --git a/code/game/be_ea.h b/code/game/be_ea.h index 01cc7e5..1bc436b 100755 --- a/code/game/be_ea.h +++ b/code/game/be_ea.h @@ -1,66 +1,66 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -/***************************************************************************** - * name: be_ea.h - * - * desc: elementary actions - * - * $Archive: /source/code/botlib/be_ea.h $ - * - *****************************************************************************/ - -//ClientCommand elementary actions -void EA_Say(int client, char *str); -void EA_SayTeam(int client, char *str); -void EA_Command(int client, char *command ); - -void EA_Action(int client, int action); -void EA_Crouch(int client); -void EA_Walk(int client); -void EA_MoveUp(int client); -void EA_MoveDown(int client); -void EA_MoveForward(int client); -void EA_MoveBack(int client); -void EA_MoveLeft(int client); -void EA_MoveRight(int client); -void EA_Attack(int client); -void EA_Respawn(int client); -void EA_Talk(int client); -void EA_Gesture(int client); -void EA_Use(int client); - -//regular elementary actions -void EA_SelectWeapon(int client, int weapon); -void EA_Jump(int client); -void EA_DelayedJump(int client); -void EA_Move(int client, vec3_t dir, float speed); -void EA_View(int client, vec3_t viewangles); - -//send regular input to the server -void EA_EndRegular(int client, float thinktime); -void EA_GetInput(int client, float thinktime, bot_input_t *input); -void EA_ResetInput(int client); -//setup and shutdown routines -int EA_Setup(void); -void EA_Shutdown(void); +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: be_ea.h + * + * desc: elementary actions + * + * $Archive: /source/code/botlib/be_ea.h $ + * + *****************************************************************************/ + +//ClientCommand elementary actions +void EA_Say(int client, char *str); +void EA_SayTeam(int client, char *str); +void EA_Command(int client, char *command ); + +void EA_Action(int client, int action); +void EA_Crouch(int client); +void EA_Walk(int client); +void EA_MoveUp(int client); +void EA_MoveDown(int client); +void EA_MoveForward(int client); +void EA_MoveBack(int client); +void EA_MoveLeft(int client); +void EA_MoveRight(int client); +void EA_Attack(int client); +void EA_Respawn(int client); +void EA_Talk(int client); +void EA_Gesture(int client); +void EA_Use(int client); + +//regular elementary actions +void EA_SelectWeapon(int client, int weapon); +void EA_Jump(int client); +void EA_DelayedJump(int client); +void EA_Move(int client, vec3_t dir, float speed); +void EA_View(int client, vec3_t viewangles); + +//send regular input to the server +void EA_EndRegular(int client, float thinktime); +void EA_GetInput(int client, float thinktime, bot_input_t *input); +void EA_ResetInput(int client); +//setup and shutdown routines +int EA_Setup(void); +void EA_Shutdown(void); diff --git a/code/game/bg_lib.c b/code/game/bg_lib.c index a5ee53e..bf1ac1e 100755 --- a/code/game/bg_lib.c +++ b/code/game/bg_lib.c @@ -1,1324 +1,1324 @@ -// -// -// bg_lib,c -- standard C library replacement routines used by code -// compiled for the virtual machine - -#include "q_shared.h" - -/*- - * Copyright (c) 1992, 1993 - * The Regents of the University of California. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * This product includes software developed by the University of - * California, Berkeley and its contributors. - * 4. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#if defined(LIBC_SCCS) && !defined(lint) -#if 0 -static char sccsid[] = "@(#)qsort.c 8.1 (Berkeley) 6/4/93"; -#endif -static const char rcsid[] = -#endif /* LIBC_SCCS and not lint */ - -// bk001127 - needed for DLL's -#if !defined( Q3_VM ) -typedef int cmp_t(const void *, const void *); -#endif - -static char* med3(char *, char *, char *, cmp_t *); -static void swapfunc(char *, char *, int, int); - -#ifndef min -#define min(a, b) (a) < (b) ? a : b -#endif - -/* - * Qsort routine from Bentley & McIlroy's "Engineering a Sort Function". - */ -#define swapcode(TYPE, parmi, parmj, n) { \ - long i = (n) / sizeof (TYPE); \ - register TYPE *pi = (TYPE *) (parmi); \ - register TYPE *pj = (TYPE *) (parmj); \ - do { \ - register TYPE t = *pi; \ - *pi++ = *pj; \ - *pj++ = t; \ - } while (--i > 0); \ -} - -#define SWAPINIT(a, es) swaptype = ((char *)a - (char *)0) % sizeof(long) || \ - es % sizeof(long) ? 2 : es == sizeof(long)? 0 : 1; - -static void -swapfunc(a, b, n, swaptype) - char *a, *b; - int n, swaptype; -{ - if(swaptype <= 1) - swapcode(long, a, b, n) - else - swapcode(char, a, b, n) -} - -#define swap(a, b) \ - if (swaptype == 0) { \ - long t = *(long *)(a); \ - *(long *)(a) = *(long *)(b); \ - *(long *)(b) = t; \ - } else \ - swapfunc(a, b, es, swaptype) - -#define vecswap(a, b, n) if ((n) > 0) swapfunc(a, b, n, swaptype) - -static char * -med3(a, b, c, cmp) - char *a, *b, *c; - cmp_t *cmp; -{ - return cmp(a, b) < 0 ? - (cmp(b, c) < 0 ? b : (cmp(a, c) < 0 ? c : a )) - :(cmp(b, c) > 0 ? b : (cmp(a, c) < 0 ? a : c )); -} - -void -qsort(a, n, es, cmp) - void *a; - size_t n, es; - cmp_t *cmp; -{ - char *pa, *pb, *pc, *pd, *pl, *pm, *pn; - int d, r, swaptype, swap_cnt; - -loop: SWAPINIT(a, es); - swap_cnt = 0; - if (n < 7) { - for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es) - for (pl = pm; pl > (char *)a && cmp(pl - es, pl) > 0; - pl -= es) - swap(pl, pl - es); - return; - } - pm = (char *)a + (n / 2) * es; - if (n > 7) { - pl = a; - pn = (char *)a + (n - 1) * es; - if (n > 40) { - d = (n / 8) * es; - pl = med3(pl, pl + d, pl + 2 * d, cmp); - pm = med3(pm - d, pm, pm + d, cmp); - pn = med3(pn - 2 * d, pn - d, pn, cmp); - } - pm = med3(pl, pm, pn, cmp); - } - swap(a, pm); - pa = pb = (char *)a + es; - - pc = pd = (char *)a + (n - 1) * es; - for (;;) { - while (pb <= pc && (r = cmp(pb, a)) <= 0) { - if (r == 0) { - swap_cnt = 1; - swap(pa, pb); - pa += es; - } - pb += es; - } - while (pb <= pc && (r = cmp(pc, a)) >= 0) { - if (r == 0) { - swap_cnt = 1; - swap(pc, pd); - pd -= es; - } - pc -= es; - } - if (pb > pc) - break; - swap(pb, pc); - swap_cnt = 1; - pb += es; - pc -= es; - } - if (swap_cnt == 0) { /* Switch to insertion sort */ - for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es) - for (pl = pm; pl > (char *)a && cmp(pl - es, pl) > 0; - pl -= es) - swap(pl, pl - es); - return; - } - - pn = (char *)a + n * es; - r = min(pa - (char *)a, pb - pa); - vecswap(a, pb - r, r); - r = min(pd - pc, pn - pd - es); - vecswap(pb, pn - r, r); - if ((r = pb - pa) > es) - qsort(a, r / es, es, cmp); - if ((r = pd - pc) > es) { - /* Iterate rather than recurse to save stack space */ - a = pn - r; - n = r / es; - goto loop; - } -/* qsort(pn - r, r / es, es, cmp);*/ -} - -//================================================================================== - - -// this file is excluded from release builds because of intrinsics - -// bk001211 - gcc errors on compiling strcpy: parse error before `__extension__' -#if defined ( Q3_VM ) - -size_t strlen( const char *string ) { - const char *s; - - s = string; - while ( *s ) { - s++; - } - return s - string; -} - - -char *strcat( char *strDestination, const char *strSource ) { - char *s; - - s = strDestination; - while ( *s ) { - s++; - } - while ( *strSource ) { - *s++ = *strSource++; - } - *s = 0; - return strDestination; -} - -char *strcpy( char *strDestination, const char *strSource ) { - char *s; - - s = strDestination; - while ( *strSource ) { - *s++ = *strSource++; - } - *s = 0; - return strDestination; -} - - -int strcmp( const char *string1, const char *string2 ) { - while ( *string1 == *string2 && *string1 && *string2 ) { - string1++; - string2++; - } - return *string1 - *string2; -} - - -char *strchr( const char *string, int c ) { - while ( *string ) { - if ( *string == c ) { - return ( char * )string; - } - string++; - } - return (char *)0; -} - -char *strstr( const char *string, const char *strCharSet ) { - while ( *string ) { - int i; - - for ( i = 0 ; strCharSet[i] ; i++ ) { - if ( string[i] != strCharSet[i] ) { - break; - } - } - if ( !strCharSet[i] ) { - return (char *)string; - } - string++; - } - return (char *)0; -} -#endif // bk001211 - -// bk001120 - presumably needed for Mac -//#if !defined(_MSC_VER) && !defined(__linux__) -// bk001127 - undid undo -#if defined ( Q3_VM ) -int tolower( int c ) { - if ( c >= 'A' && c <= 'Z' ) { - c += 'a' - 'A'; - } - return c; -} - - -int toupper( int c ) { - if ( c >= 'a' && c <= 'z' ) { - c += 'A' - 'a'; - } - return c; -} - -#endif -//#ifndef _MSC_VER - -void *memmove( void *dest, const void *src, size_t count ) { - int i; - - if ( dest > src ) { - for ( i = count-1 ; i >= 0 ; i-- ) { - ((char *)dest)[i] = ((char *)src)[i]; - } - } else { - for ( i = 0 ; i < count ; i++ ) { - ((char *)dest)[i] = ((char *)src)[i]; - } - } - return dest; -} - - -#if 0 - -double floor( double x ) { - return (int)(x + 0x40000000) - 0x40000000; -} - -void *memset( void *dest, int c, size_t count ) { - while ( count-- ) { - ((char *)dest)[count] = c; - } - return dest; -} - -void *memcpy( void *dest, const void *src, size_t count ) { - while ( count-- ) { - ((char *)dest)[count] = ((char *)src)[count]; - } - return dest; -} - -char *strncpy( char *strDest, const char *strSource, size_t count ) { - char *s; - - s = strDest; - while ( *strSource && count ) { - *s++ = *strSource++; - count--; - } - while ( count-- ) { - *s++ = 0; - } - return strDest; -} - -double sqrt( double x ) { - float y; - float delta; - float maxError; - - if ( x <= 0 ) { - return 0; - } - - // initial guess - y = x / 2; - - // refine - maxError = x * 0.001; - - do { - delta = ( y * y ) - x; - y -= delta / ( 2 * y ); - } while ( delta > maxError || delta < -maxError ); - - return y; -} - - -float sintable[1024] = { -0.000000,0.001534,0.003068,0.004602,0.006136,0.007670,0.009204,0.010738, -0.012272,0.013805,0.015339,0.016873,0.018407,0.019940,0.021474,0.023008, -0.024541,0.026075,0.027608,0.029142,0.030675,0.032208,0.033741,0.035274, -0.036807,0.038340,0.039873,0.041406,0.042938,0.044471,0.046003,0.047535, -0.049068,0.050600,0.052132,0.053664,0.055195,0.056727,0.058258,0.059790, -0.061321,0.062852,0.064383,0.065913,0.067444,0.068974,0.070505,0.072035, -0.073565,0.075094,0.076624,0.078153,0.079682,0.081211,0.082740,0.084269, -0.085797,0.087326,0.088854,0.090381,0.091909,0.093436,0.094963,0.096490, -0.098017,0.099544,0.101070,0.102596,0.104122,0.105647,0.107172,0.108697, -0.110222,0.111747,0.113271,0.114795,0.116319,0.117842,0.119365,0.120888, -0.122411,0.123933,0.125455,0.126977,0.128498,0.130019,0.131540,0.133061, -0.134581,0.136101,0.137620,0.139139,0.140658,0.142177,0.143695,0.145213, -0.146730,0.148248,0.149765,0.151281,0.152797,0.154313,0.155828,0.157343, -0.158858,0.160372,0.161886,0.163400,0.164913,0.166426,0.167938,0.169450, -0.170962,0.172473,0.173984,0.175494,0.177004,0.178514,0.180023,0.181532, -0.183040,0.184548,0.186055,0.187562,0.189069,0.190575,0.192080,0.193586, -0.195090,0.196595,0.198098,0.199602,0.201105,0.202607,0.204109,0.205610, -0.207111,0.208612,0.210112,0.211611,0.213110,0.214609,0.216107,0.217604, -0.219101,0.220598,0.222094,0.223589,0.225084,0.226578,0.228072,0.229565, -0.231058,0.232550,0.234042,0.235533,0.237024,0.238514,0.240003,0.241492, -0.242980,0.244468,0.245955,0.247442,0.248928,0.250413,0.251898,0.253382, -0.254866,0.256349,0.257831,0.259313,0.260794,0.262275,0.263755,0.265234, -0.266713,0.268191,0.269668,0.271145,0.272621,0.274097,0.275572,0.277046, -0.278520,0.279993,0.281465,0.282937,0.284408,0.285878,0.287347,0.288816, -0.290285,0.291752,0.293219,0.294685,0.296151,0.297616,0.299080,0.300543, -0.302006,0.303468,0.304929,0.306390,0.307850,0.309309,0.310767,0.312225, -0.313682,0.315138,0.316593,0.318048,0.319502,0.320955,0.322408,0.323859, -0.325310,0.326760,0.328210,0.329658,0.331106,0.332553,0.334000,0.335445, -0.336890,0.338334,0.339777,0.341219,0.342661,0.344101,0.345541,0.346980, -0.348419,0.349856,0.351293,0.352729,0.354164,0.355598,0.357031,0.358463, -0.359895,0.361326,0.362756,0.364185,0.365613,0.367040,0.368467,0.369892, -0.371317,0.372741,0.374164,0.375586,0.377007,0.378428,0.379847,0.381266, -0.382683,0.384100,0.385516,0.386931,0.388345,0.389758,0.391170,0.392582, -0.393992,0.395401,0.396810,0.398218,0.399624,0.401030,0.402435,0.403838, -0.405241,0.406643,0.408044,0.409444,0.410843,0.412241,0.413638,0.415034, -0.416430,0.417824,0.419217,0.420609,0.422000,0.423390,0.424780,0.426168, -0.427555,0.428941,0.430326,0.431711,0.433094,0.434476,0.435857,0.437237, -0.438616,0.439994,0.441371,0.442747,0.444122,0.445496,0.446869,0.448241, -0.449611,0.450981,0.452350,0.453717,0.455084,0.456449,0.457813,0.459177, -0.460539,0.461900,0.463260,0.464619,0.465976,0.467333,0.468689,0.470043, -0.471397,0.472749,0.474100,0.475450,0.476799,0.478147,0.479494,0.480839, -0.482184,0.483527,0.484869,0.486210,0.487550,0.488889,0.490226,0.491563, -0.492898,0.494232,0.495565,0.496897,0.498228,0.499557,0.500885,0.502212, -0.503538,0.504863,0.506187,0.507509,0.508830,0.510150,0.511469,0.512786, -0.514103,0.515418,0.516732,0.518045,0.519356,0.520666,0.521975,0.523283, -0.524590,0.525895,0.527199,0.528502,0.529804,0.531104,0.532403,0.533701, -0.534998,0.536293,0.537587,0.538880,0.540171,0.541462,0.542751,0.544039, -0.545325,0.546610,0.547894,0.549177,0.550458,0.551738,0.553017,0.554294, -0.555570,0.556845,0.558119,0.559391,0.560662,0.561931,0.563199,0.564466, -0.565732,0.566996,0.568259,0.569521,0.570781,0.572040,0.573297,0.574553, -0.575808,0.577062,0.578314,0.579565,0.580814,0.582062,0.583309,0.584554, -0.585798,0.587040,0.588282,0.589521,0.590760,0.591997,0.593232,0.594466, -0.595699,0.596931,0.598161,0.599389,0.600616,0.601842,0.603067,0.604290, -0.605511,0.606731,0.607950,0.609167,0.610383,0.611597,0.612810,0.614022, -0.615232,0.616440,0.617647,0.618853,0.620057,0.621260,0.622461,0.623661, -0.624859,0.626056,0.627252,0.628446,0.629638,0.630829,0.632019,0.633207, -0.634393,0.635578,0.636762,0.637944,0.639124,0.640303,0.641481,0.642657, -0.643832,0.645005,0.646176,0.647346,0.648514,0.649681,0.650847,0.652011, -0.653173,0.654334,0.655493,0.656651,0.657807,0.658961,0.660114,0.661266, -0.662416,0.663564,0.664711,0.665856,0.667000,0.668142,0.669283,0.670422, -0.671559,0.672695,0.673829,0.674962,0.676093,0.677222,0.678350,0.679476, -0.680601,0.681724,0.682846,0.683965,0.685084,0.686200,0.687315,0.688429, -0.689541,0.690651,0.691759,0.692866,0.693971,0.695075,0.696177,0.697278, -0.698376,0.699473,0.700569,0.701663,0.702755,0.703845,0.704934,0.706021, -0.707107,0.708191,0.709273,0.710353,0.711432,0.712509,0.713585,0.714659, -0.715731,0.716801,0.717870,0.718937,0.720003,0.721066,0.722128,0.723188, -0.724247,0.725304,0.726359,0.727413,0.728464,0.729514,0.730563,0.731609, -0.732654,0.733697,0.734739,0.735779,0.736817,0.737853,0.738887,0.739920, -0.740951,0.741980,0.743008,0.744034,0.745058,0.746080,0.747101,0.748119, -0.749136,0.750152,0.751165,0.752177,0.753187,0.754195,0.755201,0.756206, -0.757209,0.758210,0.759209,0.760207,0.761202,0.762196,0.763188,0.764179, -0.765167,0.766154,0.767139,0.768122,0.769103,0.770083,0.771061,0.772036, -0.773010,0.773983,0.774953,0.775922,0.776888,0.777853,0.778817,0.779778, -0.780737,0.781695,0.782651,0.783605,0.784557,0.785507,0.786455,0.787402, -0.788346,0.789289,0.790230,0.791169,0.792107,0.793042,0.793975,0.794907, -0.795837,0.796765,0.797691,0.798615,0.799537,0.800458,0.801376,0.802293, -0.803208,0.804120,0.805031,0.805940,0.806848,0.807753,0.808656,0.809558, -0.810457,0.811355,0.812251,0.813144,0.814036,0.814926,0.815814,0.816701, -0.817585,0.818467,0.819348,0.820226,0.821103,0.821977,0.822850,0.823721, -0.824589,0.825456,0.826321,0.827184,0.828045,0.828904,0.829761,0.830616, -0.831470,0.832321,0.833170,0.834018,0.834863,0.835706,0.836548,0.837387, -0.838225,0.839060,0.839894,0.840725,0.841555,0.842383,0.843208,0.844032, -0.844854,0.845673,0.846491,0.847307,0.848120,0.848932,0.849742,0.850549, -0.851355,0.852159,0.852961,0.853760,0.854558,0.855354,0.856147,0.856939, -0.857729,0.858516,0.859302,0.860085,0.860867,0.861646,0.862424,0.863199, -0.863973,0.864744,0.865514,0.866281,0.867046,0.867809,0.868571,0.869330, -0.870087,0.870842,0.871595,0.872346,0.873095,0.873842,0.874587,0.875329, -0.876070,0.876809,0.877545,0.878280,0.879012,0.879743,0.880471,0.881197, -0.881921,0.882643,0.883363,0.884081,0.884797,0.885511,0.886223,0.886932, -0.887640,0.888345,0.889048,0.889750,0.890449,0.891146,0.891841,0.892534, -0.893224,0.893913,0.894599,0.895284,0.895966,0.896646,0.897325,0.898001, -0.898674,0.899346,0.900016,0.900683,0.901349,0.902012,0.902673,0.903332, -0.903989,0.904644,0.905297,0.905947,0.906596,0.907242,0.907886,0.908528, -0.909168,0.909806,0.910441,0.911075,0.911706,0.912335,0.912962,0.913587, -0.914210,0.914830,0.915449,0.916065,0.916679,0.917291,0.917901,0.918508, -0.919114,0.919717,0.920318,0.920917,0.921514,0.922109,0.922701,0.923291, -0.923880,0.924465,0.925049,0.925631,0.926210,0.926787,0.927363,0.927935, -0.928506,0.929075,0.929641,0.930205,0.930767,0.931327,0.931884,0.932440, -0.932993,0.933544,0.934093,0.934639,0.935184,0.935726,0.936266,0.936803, -0.937339,0.937872,0.938404,0.938932,0.939459,0.939984,0.940506,0.941026, -0.941544,0.942060,0.942573,0.943084,0.943593,0.944100,0.944605,0.945107, -0.945607,0.946105,0.946601,0.947094,0.947586,0.948075,0.948561,0.949046, -0.949528,0.950008,0.950486,0.950962,0.951435,0.951906,0.952375,0.952842, -0.953306,0.953768,0.954228,0.954686,0.955141,0.955594,0.956045,0.956494, -0.956940,0.957385,0.957826,0.958266,0.958703,0.959139,0.959572,0.960002, -0.960431,0.960857,0.961280,0.961702,0.962121,0.962538,0.962953,0.963366, -0.963776,0.964184,0.964590,0.964993,0.965394,0.965793,0.966190,0.966584, -0.966976,0.967366,0.967754,0.968139,0.968522,0.968903,0.969281,0.969657, -0.970031,0.970403,0.970772,0.971139,0.971504,0.971866,0.972226,0.972584, -0.972940,0.973293,0.973644,0.973993,0.974339,0.974684,0.975025,0.975365, -0.975702,0.976037,0.976370,0.976700,0.977028,0.977354,0.977677,0.977999, -0.978317,0.978634,0.978948,0.979260,0.979570,0.979877,0.980182,0.980485, -0.980785,0.981083,0.981379,0.981673,0.981964,0.982253,0.982539,0.982824, -0.983105,0.983385,0.983662,0.983937,0.984210,0.984480,0.984749,0.985014, -0.985278,0.985539,0.985798,0.986054,0.986308,0.986560,0.986809,0.987057, -0.987301,0.987544,0.987784,0.988022,0.988258,0.988491,0.988722,0.988950, -0.989177,0.989400,0.989622,0.989841,0.990058,0.990273,0.990485,0.990695, -0.990903,0.991108,0.991311,0.991511,0.991710,0.991906,0.992099,0.992291, -0.992480,0.992666,0.992850,0.993032,0.993212,0.993389,0.993564,0.993737, -0.993907,0.994075,0.994240,0.994404,0.994565,0.994723,0.994879,0.995033, -0.995185,0.995334,0.995481,0.995625,0.995767,0.995907,0.996045,0.996180, -0.996313,0.996443,0.996571,0.996697,0.996820,0.996941,0.997060,0.997176, -0.997290,0.997402,0.997511,0.997618,0.997723,0.997825,0.997925,0.998023, -0.998118,0.998211,0.998302,0.998390,0.998476,0.998559,0.998640,0.998719, -0.998795,0.998870,0.998941,0.999011,0.999078,0.999142,0.999205,0.999265, -0.999322,0.999378,0.999431,0.999481,0.999529,0.999575,0.999619,0.999660, -0.999699,0.999735,0.999769,0.999801,0.999831,0.999858,0.999882,0.999905, -0.999925,0.999942,0.999958,0.999971,0.999981,0.999989,0.999995,0.999999 -}; - -double sin( double x ) { - int index; - int quad; - - index = 1024 * x / (M_PI * 0.5); - quad = ( index >> 10 ) & 3; - index &= 1023; - switch ( quad ) { - case 0: - return sintable[index]; - case 1: - return sintable[1023-index]; - case 2: - return -sintable[index]; - case 3: - return -sintable[1023-index]; - } - return 0; -} - - -double cos( double x ) { - int index; - int quad; - - index = 1024 * x / (M_PI * 0.5); - quad = ( index >> 10 ) & 3; - index &= 1023; - switch ( quad ) { - case 3: - return sintable[index]; - case 0: - return sintable[1023-index]; - case 1: - return -sintable[index]; - case 2: - return -sintable[1023-index]; - } - return 0; -} - - -/* -void create_acostable( void ) { - int i; - FILE *fp; - float a; - - fp = fopen("c:\\acostable.txt", "w"); - fprintf(fp, "float acostable[] = {"); - for (i = 0; i < 1024; i++) { - if (!(i & 7)) - fprintf(fp, "\n"); - a = acos( (float) -1 + i / 512 ); - fprintf(fp, "%1.8f,", a); - } - fprintf(fp, "\n}\n"); - fclose(fp); -} -*/ - -float acostable[] = { -3.14159265,3.07908248,3.05317551,3.03328655,3.01651113,3.00172442,2.98834964,2.97604422, -2.96458497,2.95381690,2.94362719,2.93393068,2.92466119,2.91576615,2.90720289,2.89893629, -2.89093699,2.88318015,2.87564455,2.86831188,2.86116621,2.85419358,2.84738169,2.84071962, -2.83419760,2.82780691,2.82153967,2.81538876,2.80934770,2.80341062,2.79757211,2.79182724, -2.78617145,2.78060056,2.77511069,2.76969824,2.76435988,2.75909250,2.75389319,2.74875926, -2.74368816,2.73867752,2.73372510,2.72882880,2.72398665,2.71919677,2.71445741,2.70976688, -2.70512362,2.70052613,2.69597298,2.69146283,2.68699438,2.68256642,2.67817778,2.67382735, -2.66951407,2.66523692,2.66099493,2.65678719,2.65261279,2.64847088,2.64436066,2.64028133, -2.63623214,2.63221238,2.62822133,2.62425835,2.62032277,2.61641398,2.61253138,2.60867440, -2.60484248,2.60103507,2.59725167,2.59349176,2.58975488,2.58604053,2.58234828,2.57867769, -2.57502832,2.57139977,2.56779164,2.56420354,2.56063509,2.55708594,2.55355572,2.55004409, -2.54655073,2.54307530,2.53961750,2.53617701,2.53275354,2.52934680,2.52595650,2.52258238, -2.51922417,2.51588159,2.51255441,2.50924238,2.50594525,2.50266278,2.49939476,2.49614096, -2.49290115,2.48967513,2.48646269,2.48326362,2.48007773,2.47690482,2.47374472,2.47059722, -2.46746215,2.46433933,2.46122860,2.45812977,2.45504269,2.45196720,2.44890314,2.44585034, -2.44280867,2.43977797,2.43675809,2.43374890,2.43075025,2.42776201,2.42478404,2.42181622, -2.41885841,2.41591048,2.41297232,2.41004380,2.40712480,2.40421521,2.40131491,2.39842379, -2.39554173,2.39266863,2.38980439,2.38694889,2.38410204,2.38126374,2.37843388,2.37561237, -2.37279910,2.36999400,2.36719697,2.36440790,2.36162673,2.35885335,2.35608768,2.35332964, -2.35057914,2.34783610,2.34510044,2.34237208,2.33965094,2.33693695,2.33423003,2.33153010, -2.32883709,2.32615093,2.32347155,2.32079888,2.31813284,2.31547337,2.31282041,2.31017388, -2.30753373,2.30489988,2.30227228,2.29965086,2.29703556,2.29442632,2.29182309,2.28922580, -2.28663439,2.28404881,2.28146900,2.27889490,2.27632647,2.27376364,2.27120637,2.26865460, -2.26610827,2.26356735,2.26103177,2.25850149,2.25597646,2.25345663,2.25094195,2.24843238, -2.24592786,2.24342836,2.24093382,2.23844420,2.23595946,2.23347956,2.23100444,2.22853408, -2.22606842,2.22360742,2.22115104,2.21869925,2.21625199,2.21380924,2.21137096,2.20893709, -2.20650761,2.20408248,2.20166166,2.19924511,2.19683280,2.19442469,2.19202074,2.18962092, -2.18722520,2.18483354,2.18244590,2.18006225,2.17768257,2.17530680,2.17293493,2.17056692, -2.16820274,2.16584236,2.16348574,2.16113285,2.15878367,2.15643816,2.15409630,2.15175805, -2.14942338,2.14709226,2.14476468,2.14244059,2.14011997,2.13780279,2.13548903,2.13317865, -2.13087163,2.12856795,2.12626757,2.12397047,2.12167662,2.11938600,2.11709859,2.11481435, -2.11253326,2.11025530,2.10798044,2.10570867,2.10343994,2.10117424,2.09891156,2.09665185, -2.09439510,2.09214129,2.08989040,2.08764239,2.08539725,2.08315496,2.08091550,2.07867884, -2.07644495,2.07421383,2.07198545,2.06975978,2.06753681,2.06531651,2.06309887,2.06088387, -2.05867147,2.05646168,2.05425445,2.05204979,2.04984765,2.04764804,2.04545092,2.04325628, -2.04106409,2.03887435,2.03668703,2.03450211,2.03231957,2.03013941,2.02796159,2.02578610, -2.02361292,2.02144204,2.01927344,2.01710710,2.01494300,2.01278113,2.01062146,2.00846399, -2.00630870,2.00415556,2.00200457,1.99985570,1.99770895,1.99556429,1.99342171,1.99128119, -1.98914271,1.98700627,1.98487185,1.98273942,1.98060898,1.97848051,1.97635399,1.97422942, -1.97210676,1.96998602,1.96786718,1.96575021,1.96363511,1.96152187,1.95941046,1.95730088, -1.95519310,1.95308712,1.95098292,1.94888050,1.94677982,1.94468089,1.94258368,1.94048818, -1.93839439,1.93630228,1.93421185,1.93212308,1.93003595,1.92795046,1.92586659,1.92378433, -1.92170367,1.91962459,1.91754708,1.91547113,1.91339673,1.91132385,1.90925250,1.90718266, -1.90511432,1.90304746,1.90098208,1.89891815,1.89685568,1.89479464,1.89273503,1.89067683, -1.88862003,1.88656463,1.88451060,1.88245794,1.88040664,1.87835668,1.87630806,1.87426076, -1.87221477,1.87017008,1.86812668,1.86608457,1.86404371,1.86200412,1.85996577,1.85792866, -1.85589277,1.85385809,1.85182462,1.84979234,1.84776125,1.84573132,1.84370256,1.84167495, -1.83964848,1.83762314,1.83559892,1.83357582,1.83155381,1.82953289,1.82751305,1.82549429, -1.82347658,1.82145993,1.81944431,1.81742973,1.81541617,1.81340362,1.81139207,1.80938151, -1.80737194,1.80536334,1.80335570,1.80134902,1.79934328,1.79733848,1.79533460,1.79333164, -1.79132959,1.78932843,1.78732817,1.78532878,1.78333027,1.78133261,1.77933581,1.77733985, -1.77534473,1.77335043,1.77135695,1.76936428,1.76737240,1.76538132,1.76339101,1.76140148, -1.75941271,1.75742470,1.75543743,1.75345090,1.75146510,1.74948002,1.74749565,1.74551198, -1.74352900,1.74154672,1.73956511,1.73758417,1.73560389,1.73362426,1.73164527,1.72966692, -1.72768920,1.72571209,1.72373560,1.72175971,1.71978441,1.71780969,1.71583556,1.71386199, -1.71188899,1.70991653,1.70794462,1.70597325,1.70400241,1.70203209,1.70006228,1.69809297, -1.69612416,1.69415584,1.69218799,1.69022062,1.68825372,1.68628727,1.68432127,1.68235571, -1.68039058,1.67842588,1.67646160,1.67449772,1.67253424,1.67057116,1.66860847,1.66664615, -1.66468420,1.66272262,1.66076139,1.65880050,1.65683996,1.65487975,1.65291986,1.65096028, -1.64900102,1.64704205,1.64508338,1.64312500,1.64116689,1.63920905,1.63725148,1.63529416, -1.63333709,1.63138026,1.62942366,1.62746728,1.62551112,1.62355517,1.62159943,1.61964388, -1.61768851,1.61573332,1.61377831,1.61182346,1.60986877,1.60791422,1.60595982,1.60400556, -1.60205142,1.60009739,1.59814349,1.59618968,1.59423597,1.59228235,1.59032882,1.58837536, -1.58642196,1.58446863,1.58251535,1.58056211,1.57860891,1.57665574,1.57470259,1.57274945, -1.57079633,1.56884320,1.56689007,1.56493692,1.56298375,1.56103055,1.55907731,1.55712403, -1.55517069,1.55321730,1.55126383,1.54931030,1.54735668,1.54540297,1.54344917,1.54149526, -1.53954124,1.53758710,1.53563283,1.53367843,1.53172389,1.52976919,1.52781434,1.52585933, -1.52390414,1.52194878,1.51999323,1.51803748,1.51608153,1.51412537,1.51216900,1.51021240, -1.50825556,1.50629849,1.50434117,1.50238360,1.50042576,1.49846765,1.49650927,1.49455060, -1.49259163,1.49063237,1.48867280,1.48671291,1.48475270,1.48279215,1.48083127,1.47887004, -1.47690845,1.47494650,1.47298419,1.47102149,1.46905841,1.46709493,1.46513106,1.46316677, -1.46120207,1.45923694,1.45727138,1.45530538,1.45333893,1.45137203,1.44940466,1.44743682, -1.44546850,1.44349969,1.44153038,1.43956057,1.43759024,1.43561940,1.43364803,1.43167612, -1.42970367,1.42773066,1.42575709,1.42378296,1.42180825,1.41983295,1.41785705,1.41588056, -1.41390346,1.41192573,1.40994738,1.40796840,1.40598877,1.40400849,1.40202755,1.40004594, -1.39806365,1.39608068,1.39409701,1.39211264,1.39012756,1.38814175,1.38615522,1.38416795, -1.38217994,1.38019117,1.37820164,1.37621134,1.37422025,1.37222837,1.37023570,1.36824222, -1.36624792,1.36425280,1.36225684,1.36026004,1.35826239,1.35626387,1.35426449,1.35226422, -1.35026307,1.34826101,1.34625805,1.34425418,1.34224937,1.34024364,1.33823695,1.33622932, -1.33422072,1.33221114,1.33020059,1.32818904,1.32617649,1.32416292,1.32214834,1.32013273, -1.31811607,1.31609837,1.31407960,1.31205976,1.31003885,1.30801684,1.30599373,1.30396951, -1.30194417,1.29991770,1.29789009,1.29586133,1.29383141,1.29180031,1.28976803,1.28773456, -1.28569989,1.28366400,1.28162688,1.27958854,1.27754894,1.27550809,1.27346597,1.27142257, -1.26937788,1.26733189,1.26528459,1.26323597,1.26118602,1.25913471,1.25708205,1.25502803, -1.25297262,1.25091583,1.24885763,1.24679802,1.24473698,1.24267450,1.24061058,1.23854519, -1.23647833,1.23440999,1.23234015,1.23026880,1.22819593,1.22612152,1.22404557,1.22196806, -1.21988898,1.21780832,1.21572606,1.21364219,1.21155670,1.20946958,1.20738080,1.20529037, -1.20319826,1.20110447,1.19900898,1.19691177,1.19481283,1.19271216,1.19060973,1.18850553, -1.18639955,1.18429178,1.18218219,1.18007079,1.17795754,1.17584244,1.17372548,1.17160663, -1.16948589,1.16736324,1.16523866,1.16311215,1.16098368,1.15885323,1.15672081,1.15458638, -1.15244994,1.15031147,1.14817095,1.14602836,1.14388370,1.14173695,1.13958808,1.13743709, -1.13528396,1.13312866,1.13097119,1.12881153,1.12664966,1.12448556,1.12231921,1.12015061, -1.11797973,1.11580656,1.11363107,1.11145325,1.10927308,1.10709055,1.10490563,1.10271831, -1.10052856,1.09833638,1.09614174,1.09394462,1.09174500,1.08954287,1.08733820,1.08513098, -1.08292118,1.08070879,1.07849378,1.07627614,1.07405585,1.07183287,1.06960721,1.06737882, -1.06514770,1.06291382,1.06067715,1.05843769,1.05619540,1.05395026,1.05170226,1.04945136, -1.04719755,1.04494080,1.04268110,1.04041841,1.03815271,1.03588399,1.03361221,1.03133735, -1.02905939,1.02677830,1.02449407,1.02220665,1.01991603,1.01762219,1.01532509,1.01302471, -1.01072102,1.00841400,1.00610363,1.00378986,1.00147268,0.99915206,0.99682798,0.99450039, -0.99216928,0.98983461,0.98749636,0.98515449,0.98280898,0.98045980,0.97810691,0.97575030, -0.97338991,0.97102573,0.96865772,0.96628585,0.96391009,0.96153040,0.95914675,0.95675912, -0.95436745,0.95197173,0.94957191,0.94716796,0.94475985,0.94234754,0.93993099,0.93751017, -0.93508504,0.93265556,0.93022170,0.92778341,0.92534066,0.92289341,0.92044161,0.91798524, -0.91552424,0.91305858,0.91058821,0.90811309,0.90563319,0.90314845,0.90065884,0.89816430, -0.89566479,0.89316028,0.89065070,0.88813602,0.88561619,0.88309116,0.88056088,0.87802531, -0.87548438,0.87293806,0.87038629,0.86782901,0.86526619,0.86269775,0.86012366,0.85754385, -0.85495827,0.85236686,0.84976956,0.84716633,0.84455709,0.84194179,0.83932037,0.83669277, -0.83405893,0.83141877,0.82877225,0.82611928,0.82345981,0.82079378,0.81812110,0.81544172, -0.81275556,0.81006255,0.80736262,0.80465570,0.80194171,0.79922057,0.79649221,0.79375655, -0.79101352,0.78826302,0.78550497,0.78273931,0.77996593,0.77718475,0.77439569,0.77159865, -0.76879355,0.76598029,0.76315878,0.76032891,0.75749061,0.75464376,0.75178826,0.74892402, -0.74605092,0.74316887,0.74027775,0.73737744,0.73446785,0.73154885,0.72862033,0.72568217, -0.72273425,0.71977644,0.71680861,0.71383064,0.71084240,0.70784376,0.70483456,0.70181469, -0.69878398,0.69574231,0.69268952,0.68962545,0.68654996,0.68346288,0.68036406,0.67725332, -0.67413051,0.67099544,0.66784794,0.66468783,0.66151492,0.65832903,0.65512997,0.65191753, -0.64869151,0.64545170,0.64219789,0.63892987,0.63564741,0.63235028,0.62903824,0.62571106, -0.62236849,0.61901027,0.61563615,0.61224585,0.60883911,0.60541564,0.60197515,0.59851735, -0.59504192,0.59154856,0.58803694,0.58450672,0.58095756,0.57738911,0.57380101,0.57019288, -0.56656433,0.56291496,0.55924437,0.55555212,0.55183778,0.54810089,0.54434099,0.54055758, -0.53675018,0.53291825,0.52906127,0.52517867,0.52126988,0.51733431,0.51337132,0.50938028, -0.50536051,0.50131132,0.49723200,0.49312177,0.48897987,0.48480547,0.48059772,0.47635573, -0.47207859,0.46776530,0.46341487,0.45902623,0.45459827,0.45012983,0.44561967,0.44106652, -0.43646903,0.43182577,0.42713525,0.42239588,0.41760600,0.41276385,0.40786755,0.40291513, -0.39790449,0.39283339,0.38769946,0.38250016,0.37723277,0.37189441,0.36648196,0.36099209, -0.35542120,0.34976542,0.34402054,0.33818204,0.33224495,0.32620390,0.32005298,0.31378574, -0.30739505,0.30087304,0.29421096,0.28739907,0.28042645,0.27328078,0.26594810,0.25841250, -0.25065566,0.24265636,0.23438976,0.22582651,0.21693146,0.20766198,0.19796546,0.18777575, -0.17700769,0.16554844,0.15324301,0.13986823,0.12508152,0.10830610,0.08841715,0.06251018, -} - -double acos( double x ) { - int index; - - if (x < -1) - x = -1; - if (x > 1) - x = 1; - index = (float) (1.0 + x) * 511.9; - return acostable[index]; -} - -double atan2( double y, double x ) { - float base; - float temp; - float dir; - float test; - int i; - - if ( x < 0 ) { - if ( y >= 0 ) { - // quad 1 - base = M_PI / 2; - temp = x; - x = y; - y = -temp; - } else { - // quad 2 - base = M_PI; - x = -x; - y = -y; - } - } else { - if ( y < 0 ) { - // quad 3 - base = 3 * M_PI / 2; - temp = x; - x = -y; - y = temp; - } - } - - if ( y > x ) { - base += M_PI/2; - temp = x; - x = y; - y = temp; - dir = -1; - } else { - dir = 1; - } - - // calcualte angle in octant 0 - if ( x == 0 ) { - return base; - } - y /= x; - - for ( i = 0 ; i < 512 ; i++ ) { - test = sintable[i] / sintable[1023-i]; - if ( test > y ) { - break; - } - } - - return base + dir * i * ( M_PI/2048); -} - - -#endif - -#ifdef Q3_VM -// bk001127 - guarded this tan replacement -// ld: undefined versioned symbol name tan@@GLIBC_2.0 -double tan( double x ) { - return sin(x) / cos(x); -} -#endif - - -static int randSeed = 0; - -void srand( unsigned seed ) { - randSeed = seed; -} - -int rand( void ) { - randSeed = (69069 * randSeed + 1); - return randSeed & 0x7fff; -} - -double atof( const char *string ) { - float sign; - float value; - int c; - - - // skip whitespace - while ( *string <= ' ' ) { - if ( !*string ) { - return 0; - } - string++; - } - - // check sign - switch ( *string ) { - case '+': - string++; - sign = 1; - break; - case '-': - string++; - sign = -1; - break; - default: - sign = 1; - break; - } - - // read digits - value = 0; - c = string[0]; - if ( c != '.' ) { - do { - c = *string++; - if ( c < '0' || c > '9' ) { - break; - } - c -= '0'; - value = value * 10 + c; - } while ( 1 ); - } else { - string++; - } - - // check for decimal point - if ( c == '.' ) { - double fraction; - - fraction = 0.1; - do { - c = *string++; - if ( c < '0' || c > '9' ) { - break; - } - c -= '0'; - value += c * fraction; - fraction *= 0.1; - } while ( 1 ); - - } - - // not handling 10e10 notation... - - return value * sign; -} - -double _atof( const char **stringPtr ) { - const char *string; - float sign; - float value; - int c = '0'; // bk001211 - uninitialized use possible - - string = *stringPtr; - - // skip whitespace - while ( *string <= ' ' ) { - if ( !*string ) { - *stringPtr = string; - return 0; - } - string++; - } - - // check sign - switch ( *string ) { - case '+': - string++; - sign = 1; - break; - case '-': - string++; - sign = -1; - break; - default: - sign = 1; - break; - } - - // read digits - value = 0; - if ( string[0] != '.' ) { - do { - c = *string++; - if ( c < '0' || c > '9' ) { - break; - } - c -= '0'; - value = value * 10 + c; - } while ( 1 ); - } - - // check for decimal point - if ( c == '.' ) { - double fraction; - - fraction = 0.1; - do { - c = *string++; - if ( c < '0' || c > '9' ) { - break; - } - c -= '0'; - value += c * fraction; - fraction *= 0.1; - } while ( 1 ); - - } - - // not handling 10e10 notation... - *stringPtr = string; - - return value * sign; -} - - -// bk001120 - presumably needed for Mac -//#if !defined ( _MSC_VER ) && ! defined ( __linux__ ) - -// bk001127 - undid undo -#if defined ( Q3_VM ) -int atoi( const char *string ) { - int sign; - int value; - int c; - - - // skip whitespace - while ( *string <= ' ' ) { - if ( !*string ) { - return 0; - } - string++; - } - - // check sign - switch ( *string ) { - case '+': - string++; - sign = 1; - break; - case '-': - string++; - sign = -1; - break; - default: - sign = 1; - break; - } - - // read digits - value = 0; - do { - c = *string++; - if ( c < '0' || c > '9' ) { - break; - } - c -= '0'; - value = value * 10 + c; - } while ( 1 ); - - // not handling 10e10 notation... - - return value * sign; -} - - -int _atoi( const char **stringPtr ) { - int sign; - int value; - int c; - const char *string; - - string = *stringPtr; - - // skip whitespace - while ( *string <= ' ' ) { - if ( !*string ) { - return 0; - } - string++; - } - - // check sign - switch ( *string ) { - case '+': - string++; - sign = 1; - break; - case '-': - string++; - sign = -1; - break; - default: - sign = 1; - break; - } - - // read digits - value = 0; - do { - c = *string++; - if ( c < '0' || c > '9' ) { - break; - } - c -= '0'; - value = value * 10 + c; - } while ( 1 ); - - // not handling 10e10 notation... - - *stringPtr = string; - - return value * sign; -} - -int abs( int n ) { - return n < 0 ? -n : n; -} - -double fabs( double x ) { - return x < 0 ? -x : x; -} - - - -//========================================================= - - -#define ALT 0x00000001 /* alternate form */ -#define HEXPREFIX 0x00000002 /* add 0x or 0X prefix */ -#define LADJUST 0x00000004 /* left adjustment */ -#define LONGDBL 0x00000008 /* long double */ -#define LONGINT 0x00000010 /* long integer */ -#define QUADINT 0x00000020 /* quad integer */ -#define SHORTINT 0x00000040 /* short integer */ -#define ZEROPAD 0x00000080 /* zero (as opposed to blank) pad */ -#define FPT 0x00000100 /* floating point number */ - -#define to_digit(c) ((c) - '0') -#define is_digit(c) ((unsigned)to_digit(c) <= 9) -#define to_char(n) ((n) + '0') - -void AddInt( char **buf_p, int val, int width, int flags ) { - char text[32]; - int digits; - int signedVal; - char *buf; - - digits = 0; - signedVal = val; - if ( val < 0 ) { - val = -val; - } - do { - text[digits++] = '0' + val % 10; - val /= 10; - } while ( val ); - - if ( signedVal < 0 ) { - text[digits++] = '-'; - } - - buf = *buf_p; - - if( !( flags & LADJUST ) ) { - while ( digits < width ) { - *buf++ = ( flags & ZEROPAD ) ? '0' : ' '; - width--; - } - } - - while ( digits-- ) { - *buf++ = text[digits]; - width--; - } - - if( flags & LADJUST ) { - while ( width-- ) { - *buf++ = ( flags & ZEROPAD ) ? '0' : ' '; - } - } - - *buf_p = buf; -} - -void AddFloat( char **buf_p, float fval, int width, int prec ) { - char text[32]; - int digits; - float signedVal; - char *buf; - int val; - - // get the sign - signedVal = fval; - if ( fval < 0 ) { - fval = -fval; - } - - // write the float number - digits = 0; - val = (int)fval; - do { - text[digits++] = '0' + val % 10; - val /= 10; - } while ( val ); - - if ( signedVal < 0 ) { - text[digits++] = '-'; - } - - buf = *buf_p; - - while ( digits < width ) { - *buf++ = ' '; - width--; - } - - while ( digits-- ) { - *buf++ = text[digits]; - } - - *buf_p = buf; - - if (prec < 0) - prec = 6; - // write the fraction - digits = 0; - while (digits < prec) { - fval -= (int) fval; - fval *= 10.0; - val = (int) fval; - text[digits++] = '0' + val % 10; - } - - if (digits > 0) { - buf = *buf_p; - *buf++ = '.'; - for (prec = 0; prec < digits; prec++) { - *buf++ = text[prec]; - } - *buf_p = buf; - } -} - - -void AddString( char **buf_p, char *string, int width, int prec ) { - int size; - char *buf; - - buf = *buf_p; - - if ( string == NULL ) { - string = "(null)"; - prec = -1; - } - - if ( prec >= 0 ) { - for( size = 0; size < prec; size++ ) { - if( string[size] == '\0' ) { - break; - } - } - } - else { - size = strlen( string ); - } - - width -= size; - - while( size-- ) { - *buf++ = *string++; - } - - while( width-- > 0 ) { - *buf++ = ' '; - } - - *buf_p = buf; -} - -/* -vsprintf - -I'm not going to support a bunch of the more arcane stuff in here -just to keep it simpler. For example, the '*' and '$' are not -currently supported. I've tried to make it so that it will just -parse and ignore formats we don't support. -*/ -int vsprintf( char *buffer, const char *fmt, va_list argptr ) { - int *arg; - char *buf_p; - char ch; - int flags; - int width; - int prec; - int n; - char sign; - - buf_p = buffer; - arg = (int *)argptr; - - while( qtrue ) { - // run through the format string until we hit a '%' or '\0' - for ( ch = *fmt; (ch = *fmt) != '\0' && ch != '%'; fmt++ ) { - *buf_p++ = ch; - } - if ( ch == '\0' ) { - goto done; - } - - // skip over the '%' - fmt++; - - // reset formatting state - flags = 0; - width = 0; - prec = -1; - sign = '\0'; - -rflag: - ch = *fmt++; -reswitch: - switch( ch ) { - case '-': - flags |= LADJUST; - goto rflag; - case '.': - n = 0; - while( is_digit( ( ch = *fmt++ ) ) ) { - n = 10 * n + ( ch - '0' ); - } - prec = n < 0 ? -1 : n; - goto reswitch; - case '0': - flags |= ZEROPAD; - goto rflag; - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - n = 0; - do { - n = 10 * n + ( ch - '0' ); - ch = *fmt++; - } while( is_digit( ch ) ); - width = n; - goto reswitch; - case 'c': - *buf_p++ = (char)*arg; - arg++; - break; - case 'd': - case 'i': - AddInt( &buf_p, *arg, width, flags ); - arg++; - break; - case 'f': - AddFloat( &buf_p, *(double *)arg, width, prec ); -#ifdef __LCC__ - arg += 1; // everything is 32 bit in my compiler -#else - arg += 2; -#endif - break; - case 's': - AddString( &buf_p, (char *)*arg, width, prec ); - arg++; - break; - case '%': - *buf_p++ = ch; - break; - default: - *buf_p++ = (char)*arg; - arg++; - break; - } - } - -done: - *buf_p = 0; - return buf_p - buffer; -} - -/* this is really crappy */ -int sscanf( const char *buffer, const char *fmt, ... ) { - int cmd; - int **arg; - int count; - - arg = (int **)&fmt + 1; - count = 0; - - while ( *fmt ) { - if ( fmt[0] != '%' ) { - fmt++; - continue; - } - - cmd = fmt[1]; - fmt += 2; - - switch ( cmd ) { - case 'i': - case 'd': - case 'u': - **arg = _atoi( &buffer ); - break; - case 'f': - *(float *)*arg = _atof( &buffer ); - break; - } - arg++; - } - - return count; -} - -#endif +// +// +// bg_lib,c -- standard C library replacement routines used by code +// compiled for the virtual machine + +#include "q_shared.h" + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if defined(LIBC_SCCS) && !defined(lint) +#if 0 +static char sccsid[] = "@(#)qsort.c 8.1 (Berkeley) 6/4/93"; +#endif +static const char rcsid[] = +#endif /* LIBC_SCCS and not lint */ + +// bk001127 - needed for DLL's +#if !defined( Q3_VM ) +typedef int cmp_t(const void *, const void *); +#endif + +static char* med3(char *, char *, char *, cmp_t *); +static void swapfunc(char *, char *, int, int); + +#ifndef min +#define min(a, b) (a) < (b) ? a : b +#endif + +/* + * Qsort routine from Bentley & McIlroy's "Engineering a Sort Function". + */ +#define swapcode(TYPE, parmi, parmj, n) { \ + long i = (n) / sizeof (TYPE); \ + register TYPE *pi = (TYPE *) (parmi); \ + register TYPE *pj = (TYPE *) (parmj); \ + do { \ + register TYPE t = *pi; \ + *pi++ = *pj; \ + *pj++ = t; \ + } while (--i > 0); \ +} + +#define SWAPINIT(a, es) swaptype = ((char *)a - (char *)0) % sizeof(long) || \ + es % sizeof(long) ? 2 : es == sizeof(long)? 0 : 1; + +static void +swapfunc(a, b, n, swaptype) + char *a, *b; + int n, swaptype; +{ + if(swaptype <= 1) + swapcode(long, a, b, n) + else + swapcode(char, a, b, n) +} + +#define swap(a, b) \ + if (swaptype == 0) { \ + long t = *(long *)(a); \ + *(long *)(a) = *(long *)(b); \ + *(long *)(b) = t; \ + } else \ + swapfunc(a, b, es, swaptype) + +#define vecswap(a, b, n) if ((n) > 0) swapfunc(a, b, n, swaptype) + +static char * +med3(a, b, c, cmp) + char *a, *b, *c; + cmp_t *cmp; +{ + return cmp(a, b) < 0 ? + (cmp(b, c) < 0 ? b : (cmp(a, c) < 0 ? c : a )) + :(cmp(b, c) > 0 ? b : (cmp(a, c) < 0 ? a : c )); +} + +void +qsort(a, n, es, cmp) + void *a; + size_t n, es; + cmp_t *cmp; +{ + char *pa, *pb, *pc, *pd, *pl, *pm, *pn; + int d, r, swaptype, swap_cnt; + +loop: SWAPINIT(a, es); + swap_cnt = 0; + if (n < 7) { + for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es) + for (pl = pm; pl > (char *)a && cmp(pl - es, pl) > 0; + pl -= es) + swap(pl, pl - es); + return; + } + pm = (char *)a + (n / 2) * es; + if (n > 7) { + pl = a; + pn = (char *)a + (n - 1) * es; + if (n > 40) { + d = (n / 8) * es; + pl = med3(pl, pl + d, pl + 2 * d, cmp); + pm = med3(pm - d, pm, pm + d, cmp); + pn = med3(pn - 2 * d, pn - d, pn, cmp); + } + pm = med3(pl, pm, pn, cmp); + } + swap(a, pm); + pa = pb = (char *)a + es; + + pc = pd = (char *)a + (n - 1) * es; + for (;;) { + while (pb <= pc && (r = cmp(pb, a)) <= 0) { + if (r == 0) { + swap_cnt = 1; + swap(pa, pb); + pa += es; + } + pb += es; + } + while (pb <= pc && (r = cmp(pc, a)) >= 0) { + if (r == 0) { + swap_cnt = 1; + swap(pc, pd); + pd -= es; + } + pc -= es; + } + if (pb > pc) + break; + swap(pb, pc); + swap_cnt = 1; + pb += es; + pc -= es; + } + if (swap_cnt == 0) { /* Switch to insertion sort */ + for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es) + for (pl = pm; pl > (char *)a && cmp(pl - es, pl) > 0; + pl -= es) + swap(pl, pl - es); + return; + } + + pn = (char *)a + n * es; + r = min(pa - (char *)a, pb - pa); + vecswap(a, pb - r, r); + r = min(pd - pc, pn - pd - es); + vecswap(pb, pn - r, r); + if ((r = pb - pa) > es) + qsort(a, r / es, es, cmp); + if ((r = pd - pc) > es) { + /* Iterate rather than recurse to save stack space */ + a = pn - r; + n = r / es; + goto loop; + } +/* qsort(pn - r, r / es, es, cmp);*/ +} + +//================================================================================== + + +// this file is excluded from release builds because of intrinsics + +// bk001211 - gcc errors on compiling strcpy: parse error before `__extension__' +#if defined ( Q3_VM ) + +size_t strlen( const char *string ) { + const char *s; + + s = string; + while ( *s ) { + s++; + } + return s - string; +} + + +char *strcat( char *strDestination, const char *strSource ) { + char *s; + + s = strDestination; + while ( *s ) { + s++; + } + while ( *strSource ) { + *s++ = *strSource++; + } + *s = 0; + return strDestination; +} + +char *strcpy( char *strDestination, const char *strSource ) { + char *s; + + s = strDestination; + while ( *strSource ) { + *s++ = *strSource++; + } + *s = 0; + return strDestination; +} + + +int strcmp( const char *string1, const char *string2 ) { + while ( *string1 == *string2 && *string1 && *string2 ) { + string1++; + string2++; + } + return *string1 - *string2; +} + + +char *strchr( const char *string, int c ) { + while ( *string ) { + if ( *string == c ) { + return ( char * )string; + } + string++; + } + return (char *)0; +} + +char *strstr( const char *string, const char *strCharSet ) { + while ( *string ) { + int i; + + for ( i = 0 ; strCharSet[i] ; i++ ) { + if ( string[i] != strCharSet[i] ) { + break; + } + } + if ( !strCharSet[i] ) { + return (char *)string; + } + string++; + } + return (char *)0; +} +#endif // bk001211 + +// bk001120 - presumably needed for Mac +//#if !defined(_MSC_VER) && !defined(__linux__) +// bk001127 - undid undo +#if defined ( Q3_VM ) +int tolower( int c ) { + if ( c >= 'A' && c <= 'Z' ) { + c += 'a' - 'A'; + } + return c; +} + + +int toupper( int c ) { + if ( c >= 'a' && c <= 'z' ) { + c += 'A' - 'a'; + } + return c; +} + +#endif +//#ifndef _MSC_VER + +void *memmove( void *dest, const void *src, size_t count ) { + int i; + + if ( dest > src ) { + for ( i = count-1 ; i >= 0 ; i-- ) { + ((char *)dest)[i] = ((char *)src)[i]; + } + } else { + for ( i = 0 ; i < count ; i++ ) { + ((char *)dest)[i] = ((char *)src)[i]; + } + } + return dest; +} + + +#if 0 + +double floor( double x ) { + return (int)(x + 0x40000000) - 0x40000000; +} + +void *memset( void *dest, int c, size_t count ) { + while ( count-- ) { + ((char *)dest)[count] = c; + } + return dest; +} + +void *memcpy( void *dest, const void *src, size_t count ) { + while ( count-- ) { + ((char *)dest)[count] = ((char *)src)[count]; + } + return dest; +} + +char *strncpy( char *strDest, const char *strSource, size_t count ) { + char *s; + + s = strDest; + while ( *strSource && count ) { + *s++ = *strSource++; + count--; + } + while ( count-- ) { + *s++ = 0; + } + return strDest; +} + +double sqrt( double x ) { + float y; + float delta; + float maxError; + + if ( x <= 0 ) { + return 0; + } + + // initial guess + y = x / 2; + + // refine + maxError = x * 0.001; + + do { + delta = ( y * y ) - x; + y -= delta / ( 2 * y ); + } while ( delta > maxError || delta < -maxError ); + + return y; +} + + +float sintable[1024] = { +0.000000,0.001534,0.003068,0.004602,0.006136,0.007670,0.009204,0.010738, +0.012272,0.013805,0.015339,0.016873,0.018407,0.019940,0.021474,0.023008, +0.024541,0.026075,0.027608,0.029142,0.030675,0.032208,0.033741,0.035274, +0.036807,0.038340,0.039873,0.041406,0.042938,0.044471,0.046003,0.047535, +0.049068,0.050600,0.052132,0.053664,0.055195,0.056727,0.058258,0.059790, +0.061321,0.062852,0.064383,0.065913,0.067444,0.068974,0.070505,0.072035, +0.073565,0.075094,0.076624,0.078153,0.079682,0.081211,0.082740,0.084269, +0.085797,0.087326,0.088854,0.090381,0.091909,0.093436,0.094963,0.096490, +0.098017,0.099544,0.101070,0.102596,0.104122,0.105647,0.107172,0.108697, +0.110222,0.111747,0.113271,0.114795,0.116319,0.117842,0.119365,0.120888, +0.122411,0.123933,0.125455,0.126977,0.128498,0.130019,0.131540,0.133061, +0.134581,0.136101,0.137620,0.139139,0.140658,0.142177,0.143695,0.145213, +0.146730,0.148248,0.149765,0.151281,0.152797,0.154313,0.155828,0.157343, +0.158858,0.160372,0.161886,0.163400,0.164913,0.166426,0.167938,0.169450, +0.170962,0.172473,0.173984,0.175494,0.177004,0.178514,0.180023,0.181532, +0.183040,0.184548,0.186055,0.187562,0.189069,0.190575,0.192080,0.193586, +0.195090,0.196595,0.198098,0.199602,0.201105,0.202607,0.204109,0.205610, +0.207111,0.208612,0.210112,0.211611,0.213110,0.214609,0.216107,0.217604, +0.219101,0.220598,0.222094,0.223589,0.225084,0.226578,0.228072,0.229565, +0.231058,0.232550,0.234042,0.235533,0.237024,0.238514,0.240003,0.241492, +0.242980,0.244468,0.245955,0.247442,0.248928,0.250413,0.251898,0.253382, +0.254866,0.256349,0.257831,0.259313,0.260794,0.262275,0.263755,0.265234, +0.266713,0.268191,0.269668,0.271145,0.272621,0.274097,0.275572,0.277046, +0.278520,0.279993,0.281465,0.282937,0.284408,0.285878,0.287347,0.288816, +0.290285,0.291752,0.293219,0.294685,0.296151,0.297616,0.299080,0.300543, +0.302006,0.303468,0.304929,0.306390,0.307850,0.309309,0.310767,0.312225, +0.313682,0.315138,0.316593,0.318048,0.319502,0.320955,0.322408,0.323859, +0.325310,0.326760,0.328210,0.329658,0.331106,0.332553,0.334000,0.335445, +0.336890,0.338334,0.339777,0.341219,0.342661,0.344101,0.345541,0.346980, +0.348419,0.349856,0.351293,0.352729,0.354164,0.355598,0.357031,0.358463, +0.359895,0.361326,0.362756,0.364185,0.365613,0.367040,0.368467,0.369892, +0.371317,0.372741,0.374164,0.375586,0.377007,0.378428,0.379847,0.381266, +0.382683,0.384100,0.385516,0.386931,0.388345,0.389758,0.391170,0.392582, +0.393992,0.395401,0.396810,0.398218,0.399624,0.401030,0.402435,0.403838, +0.405241,0.406643,0.408044,0.409444,0.410843,0.412241,0.413638,0.415034, +0.416430,0.417824,0.419217,0.420609,0.422000,0.423390,0.424780,0.426168, +0.427555,0.428941,0.430326,0.431711,0.433094,0.434476,0.435857,0.437237, +0.438616,0.439994,0.441371,0.442747,0.444122,0.445496,0.446869,0.448241, +0.449611,0.450981,0.452350,0.453717,0.455084,0.456449,0.457813,0.459177, +0.460539,0.461900,0.463260,0.464619,0.465976,0.467333,0.468689,0.470043, +0.471397,0.472749,0.474100,0.475450,0.476799,0.478147,0.479494,0.480839, +0.482184,0.483527,0.484869,0.486210,0.487550,0.488889,0.490226,0.491563, +0.492898,0.494232,0.495565,0.496897,0.498228,0.499557,0.500885,0.502212, +0.503538,0.504863,0.506187,0.507509,0.508830,0.510150,0.511469,0.512786, +0.514103,0.515418,0.516732,0.518045,0.519356,0.520666,0.521975,0.523283, +0.524590,0.525895,0.527199,0.528502,0.529804,0.531104,0.532403,0.533701, +0.534998,0.536293,0.537587,0.538880,0.540171,0.541462,0.542751,0.544039, +0.545325,0.546610,0.547894,0.549177,0.550458,0.551738,0.553017,0.554294, +0.555570,0.556845,0.558119,0.559391,0.560662,0.561931,0.563199,0.564466, +0.565732,0.566996,0.568259,0.569521,0.570781,0.572040,0.573297,0.574553, +0.575808,0.577062,0.578314,0.579565,0.580814,0.582062,0.583309,0.584554, +0.585798,0.587040,0.588282,0.589521,0.590760,0.591997,0.593232,0.594466, +0.595699,0.596931,0.598161,0.599389,0.600616,0.601842,0.603067,0.604290, +0.605511,0.606731,0.607950,0.609167,0.610383,0.611597,0.612810,0.614022, +0.615232,0.616440,0.617647,0.618853,0.620057,0.621260,0.622461,0.623661, +0.624859,0.626056,0.627252,0.628446,0.629638,0.630829,0.632019,0.633207, +0.634393,0.635578,0.636762,0.637944,0.639124,0.640303,0.641481,0.642657, +0.643832,0.645005,0.646176,0.647346,0.648514,0.649681,0.650847,0.652011, +0.653173,0.654334,0.655493,0.656651,0.657807,0.658961,0.660114,0.661266, +0.662416,0.663564,0.664711,0.665856,0.667000,0.668142,0.669283,0.670422, +0.671559,0.672695,0.673829,0.674962,0.676093,0.677222,0.678350,0.679476, +0.680601,0.681724,0.682846,0.683965,0.685084,0.686200,0.687315,0.688429, +0.689541,0.690651,0.691759,0.692866,0.693971,0.695075,0.696177,0.697278, +0.698376,0.699473,0.700569,0.701663,0.702755,0.703845,0.704934,0.706021, +0.707107,0.708191,0.709273,0.710353,0.711432,0.712509,0.713585,0.714659, +0.715731,0.716801,0.717870,0.718937,0.720003,0.721066,0.722128,0.723188, +0.724247,0.725304,0.726359,0.727413,0.728464,0.729514,0.730563,0.731609, +0.732654,0.733697,0.734739,0.735779,0.736817,0.737853,0.738887,0.739920, +0.740951,0.741980,0.743008,0.744034,0.745058,0.746080,0.747101,0.748119, +0.749136,0.750152,0.751165,0.752177,0.753187,0.754195,0.755201,0.756206, +0.757209,0.758210,0.759209,0.760207,0.761202,0.762196,0.763188,0.764179, +0.765167,0.766154,0.767139,0.768122,0.769103,0.770083,0.771061,0.772036, +0.773010,0.773983,0.774953,0.775922,0.776888,0.777853,0.778817,0.779778, +0.780737,0.781695,0.782651,0.783605,0.784557,0.785507,0.786455,0.787402, +0.788346,0.789289,0.790230,0.791169,0.792107,0.793042,0.793975,0.794907, +0.795837,0.796765,0.797691,0.798615,0.799537,0.800458,0.801376,0.802293, +0.803208,0.804120,0.805031,0.805940,0.806848,0.807753,0.808656,0.809558, +0.810457,0.811355,0.812251,0.813144,0.814036,0.814926,0.815814,0.816701, +0.817585,0.818467,0.819348,0.820226,0.821103,0.821977,0.822850,0.823721, +0.824589,0.825456,0.826321,0.827184,0.828045,0.828904,0.829761,0.830616, +0.831470,0.832321,0.833170,0.834018,0.834863,0.835706,0.836548,0.837387, +0.838225,0.839060,0.839894,0.840725,0.841555,0.842383,0.843208,0.844032, +0.844854,0.845673,0.846491,0.847307,0.848120,0.848932,0.849742,0.850549, +0.851355,0.852159,0.852961,0.853760,0.854558,0.855354,0.856147,0.856939, +0.857729,0.858516,0.859302,0.860085,0.860867,0.861646,0.862424,0.863199, +0.863973,0.864744,0.865514,0.866281,0.867046,0.867809,0.868571,0.869330, +0.870087,0.870842,0.871595,0.872346,0.873095,0.873842,0.874587,0.875329, +0.876070,0.876809,0.877545,0.878280,0.879012,0.879743,0.880471,0.881197, +0.881921,0.882643,0.883363,0.884081,0.884797,0.885511,0.886223,0.886932, +0.887640,0.888345,0.889048,0.889750,0.890449,0.891146,0.891841,0.892534, +0.893224,0.893913,0.894599,0.895284,0.895966,0.896646,0.897325,0.898001, +0.898674,0.899346,0.900016,0.900683,0.901349,0.902012,0.902673,0.903332, +0.903989,0.904644,0.905297,0.905947,0.906596,0.907242,0.907886,0.908528, +0.909168,0.909806,0.910441,0.911075,0.911706,0.912335,0.912962,0.913587, +0.914210,0.914830,0.915449,0.916065,0.916679,0.917291,0.917901,0.918508, +0.919114,0.919717,0.920318,0.920917,0.921514,0.922109,0.922701,0.923291, +0.923880,0.924465,0.925049,0.925631,0.926210,0.926787,0.927363,0.927935, +0.928506,0.929075,0.929641,0.930205,0.930767,0.931327,0.931884,0.932440, +0.932993,0.933544,0.934093,0.934639,0.935184,0.935726,0.936266,0.936803, +0.937339,0.937872,0.938404,0.938932,0.939459,0.939984,0.940506,0.941026, +0.941544,0.942060,0.942573,0.943084,0.943593,0.944100,0.944605,0.945107, +0.945607,0.946105,0.946601,0.947094,0.947586,0.948075,0.948561,0.949046, +0.949528,0.950008,0.950486,0.950962,0.951435,0.951906,0.952375,0.952842, +0.953306,0.953768,0.954228,0.954686,0.955141,0.955594,0.956045,0.956494, +0.956940,0.957385,0.957826,0.958266,0.958703,0.959139,0.959572,0.960002, +0.960431,0.960857,0.961280,0.961702,0.962121,0.962538,0.962953,0.963366, +0.963776,0.964184,0.964590,0.964993,0.965394,0.965793,0.966190,0.966584, +0.966976,0.967366,0.967754,0.968139,0.968522,0.968903,0.969281,0.969657, +0.970031,0.970403,0.970772,0.971139,0.971504,0.971866,0.972226,0.972584, +0.972940,0.973293,0.973644,0.973993,0.974339,0.974684,0.975025,0.975365, +0.975702,0.976037,0.976370,0.976700,0.977028,0.977354,0.977677,0.977999, +0.978317,0.978634,0.978948,0.979260,0.979570,0.979877,0.980182,0.980485, +0.980785,0.981083,0.981379,0.981673,0.981964,0.982253,0.982539,0.982824, +0.983105,0.983385,0.983662,0.983937,0.984210,0.984480,0.984749,0.985014, +0.985278,0.985539,0.985798,0.986054,0.986308,0.986560,0.986809,0.987057, +0.987301,0.987544,0.987784,0.988022,0.988258,0.988491,0.988722,0.988950, +0.989177,0.989400,0.989622,0.989841,0.990058,0.990273,0.990485,0.990695, +0.990903,0.991108,0.991311,0.991511,0.991710,0.991906,0.992099,0.992291, +0.992480,0.992666,0.992850,0.993032,0.993212,0.993389,0.993564,0.993737, +0.993907,0.994075,0.994240,0.994404,0.994565,0.994723,0.994879,0.995033, +0.995185,0.995334,0.995481,0.995625,0.995767,0.995907,0.996045,0.996180, +0.996313,0.996443,0.996571,0.996697,0.996820,0.996941,0.997060,0.997176, +0.997290,0.997402,0.997511,0.997618,0.997723,0.997825,0.997925,0.998023, +0.998118,0.998211,0.998302,0.998390,0.998476,0.998559,0.998640,0.998719, +0.998795,0.998870,0.998941,0.999011,0.999078,0.999142,0.999205,0.999265, +0.999322,0.999378,0.999431,0.999481,0.999529,0.999575,0.999619,0.999660, +0.999699,0.999735,0.999769,0.999801,0.999831,0.999858,0.999882,0.999905, +0.999925,0.999942,0.999958,0.999971,0.999981,0.999989,0.999995,0.999999 +}; + +double sin( double x ) { + int index; + int quad; + + index = 1024 * x / (M_PI * 0.5); + quad = ( index >> 10 ) & 3; + index &= 1023; + switch ( quad ) { + case 0: + return sintable[index]; + case 1: + return sintable[1023-index]; + case 2: + return -sintable[index]; + case 3: + return -sintable[1023-index]; + } + return 0; +} + + +double cos( double x ) { + int index; + int quad; + + index = 1024 * x / (M_PI * 0.5); + quad = ( index >> 10 ) & 3; + index &= 1023; + switch ( quad ) { + case 3: + return sintable[index]; + case 0: + return sintable[1023-index]; + case 1: + return -sintable[index]; + case 2: + return -sintable[1023-index]; + } + return 0; +} + + +/* +void create_acostable( void ) { + int i; + FILE *fp; + float a; + + fp = fopen("c:\\acostable.txt", "w"); + fprintf(fp, "float acostable[] = {"); + for (i = 0; i < 1024; i++) { + if (!(i & 7)) + fprintf(fp, "\n"); + a = acos( (float) -1 + i / 512 ); + fprintf(fp, "%1.8f,", a); + } + fprintf(fp, "\n}\n"); + fclose(fp); +} +*/ + +float acostable[] = { +3.14159265,3.07908248,3.05317551,3.03328655,3.01651113,3.00172442,2.98834964,2.97604422, +2.96458497,2.95381690,2.94362719,2.93393068,2.92466119,2.91576615,2.90720289,2.89893629, +2.89093699,2.88318015,2.87564455,2.86831188,2.86116621,2.85419358,2.84738169,2.84071962, +2.83419760,2.82780691,2.82153967,2.81538876,2.80934770,2.80341062,2.79757211,2.79182724, +2.78617145,2.78060056,2.77511069,2.76969824,2.76435988,2.75909250,2.75389319,2.74875926, +2.74368816,2.73867752,2.73372510,2.72882880,2.72398665,2.71919677,2.71445741,2.70976688, +2.70512362,2.70052613,2.69597298,2.69146283,2.68699438,2.68256642,2.67817778,2.67382735, +2.66951407,2.66523692,2.66099493,2.65678719,2.65261279,2.64847088,2.64436066,2.64028133, +2.63623214,2.63221238,2.62822133,2.62425835,2.62032277,2.61641398,2.61253138,2.60867440, +2.60484248,2.60103507,2.59725167,2.59349176,2.58975488,2.58604053,2.58234828,2.57867769, +2.57502832,2.57139977,2.56779164,2.56420354,2.56063509,2.55708594,2.55355572,2.55004409, +2.54655073,2.54307530,2.53961750,2.53617701,2.53275354,2.52934680,2.52595650,2.52258238, +2.51922417,2.51588159,2.51255441,2.50924238,2.50594525,2.50266278,2.49939476,2.49614096, +2.49290115,2.48967513,2.48646269,2.48326362,2.48007773,2.47690482,2.47374472,2.47059722, +2.46746215,2.46433933,2.46122860,2.45812977,2.45504269,2.45196720,2.44890314,2.44585034, +2.44280867,2.43977797,2.43675809,2.43374890,2.43075025,2.42776201,2.42478404,2.42181622, +2.41885841,2.41591048,2.41297232,2.41004380,2.40712480,2.40421521,2.40131491,2.39842379, +2.39554173,2.39266863,2.38980439,2.38694889,2.38410204,2.38126374,2.37843388,2.37561237, +2.37279910,2.36999400,2.36719697,2.36440790,2.36162673,2.35885335,2.35608768,2.35332964, +2.35057914,2.34783610,2.34510044,2.34237208,2.33965094,2.33693695,2.33423003,2.33153010, +2.32883709,2.32615093,2.32347155,2.32079888,2.31813284,2.31547337,2.31282041,2.31017388, +2.30753373,2.30489988,2.30227228,2.29965086,2.29703556,2.29442632,2.29182309,2.28922580, +2.28663439,2.28404881,2.28146900,2.27889490,2.27632647,2.27376364,2.27120637,2.26865460, +2.26610827,2.26356735,2.26103177,2.25850149,2.25597646,2.25345663,2.25094195,2.24843238, +2.24592786,2.24342836,2.24093382,2.23844420,2.23595946,2.23347956,2.23100444,2.22853408, +2.22606842,2.22360742,2.22115104,2.21869925,2.21625199,2.21380924,2.21137096,2.20893709, +2.20650761,2.20408248,2.20166166,2.19924511,2.19683280,2.19442469,2.19202074,2.18962092, +2.18722520,2.18483354,2.18244590,2.18006225,2.17768257,2.17530680,2.17293493,2.17056692, +2.16820274,2.16584236,2.16348574,2.16113285,2.15878367,2.15643816,2.15409630,2.15175805, +2.14942338,2.14709226,2.14476468,2.14244059,2.14011997,2.13780279,2.13548903,2.13317865, +2.13087163,2.12856795,2.12626757,2.12397047,2.12167662,2.11938600,2.11709859,2.11481435, +2.11253326,2.11025530,2.10798044,2.10570867,2.10343994,2.10117424,2.09891156,2.09665185, +2.09439510,2.09214129,2.08989040,2.08764239,2.08539725,2.08315496,2.08091550,2.07867884, +2.07644495,2.07421383,2.07198545,2.06975978,2.06753681,2.06531651,2.06309887,2.06088387, +2.05867147,2.05646168,2.05425445,2.05204979,2.04984765,2.04764804,2.04545092,2.04325628, +2.04106409,2.03887435,2.03668703,2.03450211,2.03231957,2.03013941,2.02796159,2.02578610, +2.02361292,2.02144204,2.01927344,2.01710710,2.01494300,2.01278113,2.01062146,2.00846399, +2.00630870,2.00415556,2.00200457,1.99985570,1.99770895,1.99556429,1.99342171,1.99128119, +1.98914271,1.98700627,1.98487185,1.98273942,1.98060898,1.97848051,1.97635399,1.97422942, +1.97210676,1.96998602,1.96786718,1.96575021,1.96363511,1.96152187,1.95941046,1.95730088, +1.95519310,1.95308712,1.95098292,1.94888050,1.94677982,1.94468089,1.94258368,1.94048818, +1.93839439,1.93630228,1.93421185,1.93212308,1.93003595,1.92795046,1.92586659,1.92378433, +1.92170367,1.91962459,1.91754708,1.91547113,1.91339673,1.91132385,1.90925250,1.90718266, +1.90511432,1.90304746,1.90098208,1.89891815,1.89685568,1.89479464,1.89273503,1.89067683, +1.88862003,1.88656463,1.88451060,1.88245794,1.88040664,1.87835668,1.87630806,1.87426076, +1.87221477,1.87017008,1.86812668,1.86608457,1.86404371,1.86200412,1.85996577,1.85792866, +1.85589277,1.85385809,1.85182462,1.84979234,1.84776125,1.84573132,1.84370256,1.84167495, +1.83964848,1.83762314,1.83559892,1.83357582,1.83155381,1.82953289,1.82751305,1.82549429, +1.82347658,1.82145993,1.81944431,1.81742973,1.81541617,1.81340362,1.81139207,1.80938151, +1.80737194,1.80536334,1.80335570,1.80134902,1.79934328,1.79733848,1.79533460,1.79333164, +1.79132959,1.78932843,1.78732817,1.78532878,1.78333027,1.78133261,1.77933581,1.77733985, +1.77534473,1.77335043,1.77135695,1.76936428,1.76737240,1.76538132,1.76339101,1.76140148, +1.75941271,1.75742470,1.75543743,1.75345090,1.75146510,1.74948002,1.74749565,1.74551198, +1.74352900,1.74154672,1.73956511,1.73758417,1.73560389,1.73362426,1.73164527,1.72966692, +1.72768920,1.72571209,1.72373560,1.72175971,1.71978441,1.71780969,1.71583556,1.71386199, +1.71188899,1.70991653,1.70794462,1.70597325,1.70400241,1.70203209,1.70006228,1.69809297, +1.69612416,1.69415584,1.69218799,1.69022062,1.68825372,1.68628727,1.68432127,1.68235571, +1.68039058,1.67842588,1.67646160,1.67449772,1.67253424,1.67057116,1.66860847,1.66664615, +1.66468420,1.66272262,1.66076139,1.65880050,1.65683996,1.65487975,1.65291986,1.65096028, +1.64900102,1.64704205,1.64508338,1.64312500,1.64116689,1.63920905,1.63725148,1.63529416, +1.63333709,1.63138026,1.62942366,1.62746728,1.62551112,1.62355517,1.62159943,1.61964388, +1.61768851,1.61573332,1.61377831,1.61182346,1.60986877,1.60791422,1.60595982,1.60400556, +1.60205142,1.60009739,1.59814349,1.59618968,1.59423597,1.59228235,1.59032882,1.58837536, +1.58642196,1.58446863,1.58251535,1.58056211,1.57860891,1.57665574,1.57470259,1.57274945, +1.57079633,1.56884320,1.56689007,1.56493692,1.56298375,1.56103055,1.55907731,1.55712403, +1.55517069,1.55321730,1.55126383,1.54931030,1.54735668,1.54540297,1.54344917,1.54149526, +1.53954124,1.53758710,1.53563283,1.53367843,1.53172389,1.52976919,1.52781434,1.52585933, +1.52390414,1.52194878,1.51999323,1.51803748,1.51608153,1.51412537,1.51216900,1.51021240, +1.50825556,1.50629849,1.50434117,1.50238360,1.50042576,1.49846765,1.49650927,1.49455060, +1.49259163,1.49063237,1.48867280,1.48671291,1.48475270,1.48279215,1.48083127,1.47887004, +1.47690845,1.47494650,1.47298419,1.47102149,1.46905841,1.46709493,1.46513106,1.46316677, +1.46120207,1.45923694,1.45727138,1.45530538,1.45333893,1.45137203,1.44940466,1.44743682, +1.44546850,1.44349969,1.44153038,1.43956057,1.43759024,1.43561940,1.43364803,1.43167612, +1.42970367,1.42773066,1.42575709,1.42378296,1.42180825,1.41983295,1.41785705,1.41588056, +1.41390346,1.41192573,1.40994738,1.40796840,1.40598877,1.40400849,1.40202755,1.40004594, +1.39806365,1.39608068,1.39409701,1.39211264,1.39012756,1.38814175,1.38615522,1.38416795, +1.38217994,1.38019117,1.37820164,1.37621134,1.37422025,1.37222837,1.37023570,1.36824222, +1.36624792,1.36425280,1.36225684,1.36026004,1.35826239,1.35626387,1.35426449,1.35226422, +1.35026307,1.34826101,1.34625805,1.34425418,1.34224937,1.34024364,1.33823695,1.33622932, +1.33422072,1.33221114,1.33020059,1.32818904,1.32617649,1.32416292,1.32214834,1.32013273, +1.31811607,1.31609837,1.31407960,1.31205976,1.31003885,1.30801684,1.30599373,1.30396951, +1.30194417,1.29991770,1.29789009,1.29586133,1.29383141,1.29180031,1.28976803,1.28773456, +1.28569989,1.28366400,1.28162688,1.27958854,1.27754894,1.27550809,1.27346597,1.27142257, +1.26937788,1.26733189,1.26528459,1.26323597,1.26118602,1.25913471,1.25708205,1.25502803, +1.25297262,1.25091583,1.24885763,1.24679802,1.24473698,1.24267450,1.24061058,1.23854519, +1.23647833,1.23440999,1.23234015,1.23026880,1.22819593,1.22612152,1.22404557,1.22196806, +1.21988898,1.21780832,1.21572606,1.21364219,1.21155670,1.20946958,1.20738080,1.20529037, +1.20319826,1.20110447,1.19900898,1.19691177,1.19481283,1.19271216,1.19060973,1.18850553, +1.18639955,1.18429178,1.18218219,1.18007079,1.17795754,1.17584244,1.17372548,1.17160663, +1.16948589,1.16736324,1.16523866,1.16311215,1.16098368,1.15885323,1.15672081,1.15458638, +1.15244994,1.15031147,1.14817095,1.14602836,1.14388370,1.14173695,1.13958808,1.13743709, +1.13528396,1.13312866,1.13097119,1.12881153,1.12664966,1.12448556,1.12231921,1.12015061, +1.11797973,1.11580656,1.11363107,1.11145325,1.10927308,1.10709055,1.10490563,1.10271831, +1.10052856,1.09833638,1.09614174,1.09394462,1.09174500,1.08954287,1.08733820,1.08513098, +1.08292118,1.08070879,1.07849378,1.07627614,1.07405585,1.07183287,1.06960721,1.06737882, +1.06514770,1.06291382,1.06067715,1.05843769,1.05619540,1.05395026,1.05170226,1.04945136, +1.04719755,1.04494080,1.04268110,1.04041841,1.03815271,1.03588399,1.03361221,1.03133735, +1.02905939,1.02677830,1.02449407,1.02220665,1.01991603,1.01762219,1.01532509,1.01302471, +1.01072102,1.00841400,1.00610363,1.00378986,1.00147268,0.99915206,0.99682798,0.99450039, +0.99216928,0.98983461,0.98749636,0.98515449,0.98280898,0.98045980,0.97810691,0.97575030, +0.97338991,0.97102573,0.96865772,0.96628585,0.96391009,0.96153040,0.95914675,0.95675912, +0.95436745,0.95197173,0.94957191,0.94716796,0.94475985,0.94234754,0.93993099,0.93751017, +0.93508504,0.93265556,0.93022170,0.92778341,0.92534066,0.92289341,0.92044161,0.91798524, +0.91552424,0.91305858,0.91058821,0.90811309,0.90563319,0.90314845,0.90065884,0.89816430, +0.89566479,0.89316028,0.89065070,0.88813602,0.88561619,0.88309116,0.88056088,0.87802531, +0.87548438,0.87293806,0.87038629,0.86782901,0.86526619,0.86269775,0.86012366,0.85754385, +0.85495827,0.85236686,0.84976956,0.84716633,0.84455709,0.84194179,0.83932037,0.83669277, +0.83405893,0.83141877,0.82877225,0.82611928,0.82345981,0.82079378,0.81812110,0.81544172, +0.81275556,0.81006255,0.80736262,0.80465570,0.80194171,0.79922057,0.79649221,0.79375655, +0.79101352,0.78826302,0.78550497,0.78273931,0.77996593,0.77718475,0.77439569,0.77159865, +0.76879355,0.76598029,0.76315878,0.76032891,0.75749061,0.75464376,0.75178826,0.74892402, +0.74605092,0.74316887,0.74027775,0.73737744,0.73446785,0.73154885,0.72862033,0.72568217, +0.72273425,0.71977644,0.71680861,0.71383064,0.71084240,0.70784376,0.70483456,0.70181469, +0.69878398,0.69574231,0.69268952,0.68962545,0.68654996,0.68346288,0.68036406,0.67725332, +0.67413051,0.67099544,0.66784794,0.66468783,0.66151492,0.65832903,0.65512997,0.65191753, +0.64869151,0.64545170,0.64219789,0.63892987,0.63564741,0.63235028,0.62903824,0.62571106, +0.62236849,0.61901027,0.61563615,0.61224585,0.60883911,0.60541564,0.60197515,0.59851735, +0.59504192,0.59154856,0.58803694,0.58450672,0.58095756,0.57738911,0.57380101,0.57019288, +0.56656433,0.56291496,0.55924437,0.55555212,0.55183778,0.54810089,0.54434099,0.54055758, +0.53675018,0.53291825,0.52906127,0.52517867,0.52126988,0.51733431,0.51337132,0.50938028, +0.50536051,0.50131132,0.49723200,0.49312177,0.48897987,0.48480547,0.48059772,0.47635573, +0.47207859,0.46776530,0.46341487,0.45902623,0.45459827,0.45012983,0.44561967,0.44106652, +0.43646903,0.43182577,0.42713525,0.42239588,0.41760600,0.41276385,0.40786755,0.40291513, +0.39790449,0.39283339,0.38769946,0.38250016,0.37723277,0.37189441,0.36648196,0.36099209, +0.35542120,0.34976542,0.34402054,0.33818204,0.33224495,0.32620390,0.32005298,0.31378574, +0.30739505,0.30087304,0.29421096,0.28739907,0.28042645,0.27328078,0.26594810,0.25841250, +0.25065566,0.24265636,0.23438976,0.22582651,0.21693146,0.20766198,0.19796546,0.18777575, +0.17700769,0.16554844,0.15324301,0.13986823,0.12508152,0.10830610,0.08841715,0.06251018, +} + +double acos( double x ) { + int index; + + if (x < -1) + x = -1; + if (x > 1) + x = 1; + index = (float) (1.0 + x) * 511.9; + return acostable[index]; +} + +double atan2( double y, double x ) { + float base; + float temp; + float dir; + float test; + int i; + + if ( x < 0 ) { + if ( y >= 0 ) { + // quad 1 + base = M_PI / 2; + temp = x; + x = y; + y = -temp; + } else { + // quad 2 + base = M_PI; + x = -x; + y = -y; + } + } else { + if ( y < 0 ) { + // quad 3 + base = 3 * M_PI / 2; + temp = x; + x = -y; + y = temp; + } + } + + if ( y > x ) { + base += M_PI/2; + temp = x; + x = y; + y = temp; + dir = -1; + } else { + dir = 1; + } + + // calcualte angle in octant 0 + if ( x == 0 ) { + return base; + } + y /= x; + + for ( i = 0 ; i < 512 ; i++ ) { + test = sintable[i] / sintable[1023-i]; + if ( test > y ) { + break; + } + } + + return base + dir * i * ( M_PI/2048); +} + + +#endif + +#ifdef Q3_VM +// bk001127 - guarded this tan replacement +// ld: undefined versioned symbol name tan@@GLIBC_2.0 +double tan( double x ) { + return sin(x) / cos(x); +} +#endif + + +static int randSeed = 0; + +void srand( unsigned seed ) { + randSeed = seed; +} + +int rand( void ) { + randSeed = (69069 * randSeed + 1); + return randSeed & 0x7fff; +} + +double atof( const char *string ) { + float sign; + float value; + int c; + + + // skip whitespace + while ( *string <= ' ' ) { + if ( !*string ) { + return 0; + } + string++; + } + + // check sign + switch ( *string ) { + case '+': + string++; + sign = 1; + break; + case '-': + string++; + sign = -1; + break; + default: + sign = 1; + break; + } + + // read digits + value = 0; + c = string[0]; + if ( c != '.' ) { + do { + c = *string++; + if ( c < '0' || c > '9' ) { + break; + } + c -= '0'; + value = value * 10 + c; + } while ( 1 ); + } else { + string++; + } + + // check for decimal point + if ( c == '.' ) { + double fraction; + + fraction = 0.1; + do { + c = *string++; + if ( c < '0' || c > '9' ) { + break; + } + c -= '0'; + value += c * fraction; + fraction *= 0.1; + } while ( 1 ); + + } + + // not handling 10e10 notation... + + return value * sign; +} + +double _atof( const char **stringPtr ) { + const char *string; + float sign; + float value; + int c = '0'; // bk001211 - uninitialized use possible + + string = *stringPtr; + + // skip whitespace + while ( *string <= ' ' ) { + if ( !*string ) { + *stringPtr = string; + return 0; + } + string++; + } + + // check sign + switch ( *string ) { + case '+': + string++; + sign = 1; + break; + case '-': + string++; + sign = -1; + break; + default: + sign = 1; + break; + } + + // read digits + value = 0; + if ( string[0] != '.' ) { + do { + c = *string++; + if ( c < '0' || c > '9' ) { + break; + } + c -= '0'; + value = value * 10 + c; + } while ( 1 ); + } + + // check for decimal point + if ( c == '.' ) { + double fraction; + + fraction = 0.1; + do { + c = *string++; + if ( c < '0' || c > '9' ) { + break; + } + c -= '0'; + value += c * fraction; + fraction *= 0.1; + } while ( 1 ); + + } + + // not handling 10e10 notation... + *stringPtr = string; + + return value * sign; +} + + +// bk001120 - presumably needed for Mac +//#if !defined ( _MSC_VER ) && ! defined ( __linux__ ) + +// bk001127 - undid undo +#if defined ( Q3_VM ) +int atoi( const char *string ) { + int sign; + int value; + int c; + + + // skip whitespace + while ( *string <= ' ' ) { + if ( !*string ) { + return 0; + } + string++; + } + + // check sign + switch ( *string ) { + case '+': + string++; + sign = 1; + break; + case '-': + string++; + sign = -1; + break; + default: + sign = 1; + break; + } + + // read digits + value = 0; + do { + c = *string++; + if ( c < '0' || c > '9' ) { + break; + } + c -= '0'; + value = value * 10 + c; + } while ( 1 ); + + // not handling 10e10 notation... + + return value * sign; +} + + +int _atoi( const char **stringPtr ) { + int sign; + int value; + int c; + const char *string; + + string = *stringPtr; + + // skip whitespace + while ( *string <= ' ' ) { + if ( !*string ) { + return 0; + } + string++; + } + + // check sign + switch ( *string ) { + case '+': + string++; + sign = 1; + break; + case '-': + string++; + sign = -1; + break; + default: + sign = 1; + break; + } + + // read digits + value = 0; + do { + c = *string++; + if ( c < '0' || c > '9' ) { + break; + } + c -= '0'; + value = value * 10 + c; + } while ( 1 ); + + // not handling 10e10 notation... + + *stringPtr = string; + + return value * sign; +} + +int abs( int n ) { + return n < 0 ? -n : n; +} + +double fabs( double x ) { + return x < 0 ? -x : x; +} + + + +//========================================================= + + +#define ALT 0x00000001 /* alternate form */ +#define HEXPREFIX 0x00000002 /* add 0x or 0X prefix */ +#define LADJUST 0x00000004 /* left adjustment */ +#define LONGDBL 0x00000008 /* long double */ +#define LONGINT 0x00000010 /* long integer */ +#define QUADINT 0x00000020 /* quad integer */ +#define SHORTINT 0x00000040 /* short integer */ +#define ZEROPAD 0x00000080 /* zero (as opposed to blank) pad */ +#define FPT 0x00000100 /* floating point number */ + +#define to_digit(c) ((c) - '0') +#define is_digit(c) ((unsigned)to_digit(c) <= 9) +#define to_char(n) ((n) + '0') + +void AddInt( char **buf_p, int val, int width, int flags ) { + char text[32]; + int digits; + int signedVal; + char *buf; + + digits = 0; + signedVal = val; + if ( val < 0 ) { + val = -val; + } + do { + text[digits++] = '0' + val % 10; + val /= 10; + } while ( val ); + + if ( signedVal < 0 ) { + text[digits++] = '-'; + } + + buf = *buf_p; + + if( !( flags & LADJUST ) ) { + while ( digits < width ) { + *buf++ = ( flags & ZEROPAD ) ? '0' : ' '; + width--; + } + } + + while ( digits-- ) { + *buf++ = text[digits]; + width--; + } + + if( flags & LADJUST ) { + while ( width-- ) { + *buf++ = ( flags & ZEROPAD ) ? '0' : ' '; + } + } + + *buf_p = buf; +} + +void AddFloat( char **buf_p, float fval, int width, int prec ) { + char text[32]; + int digits; + float signedVal; + char *buf; + int val; + + // get the sign + signedVal = fval; + if ( fval < 0 ) { + fval = -fval; + } + + // write the float number + digits = 0; + val = (int)fval; + do { + text[digits++] = '0' + val % 10; + val /= 10; + } while ( val ); + + if ( signedVal < 0 ) { + text[digits++] = '-'; + } + + buf = *buf_p; + + while ( digits < width ) { + *buf++ = ' '; + width--; + } + + while ( digits-- ) { + *buf++ = text[digits]; + } + + *buf_p = buf; + + if (prec < 0) + prec = 6; + // write the fraction + digits = 0; + while (digits < prec) { + fval -= (int) fval; + fval *= 10.0; + val = (int) fval; + text[digits++] = '0' + val % 10; + } + + if (digits > 0) { + buf = *buf_p; + *buf++ = '.'; + for (prec = 0; prec < digits; prec++) { + *buf++ = text[prec]; + } + *buf_p = buf; + } +} + + +void AddString( char **buf_p, char *string, int width, int prec ) { + int size; + char *buf; + + buf = *buf_p; + + if ( string == NULL ) { + string = "(null)"; + prec = -1; + } + + if ( prec >= 0 ) { + for( size = 0; size < prec; size++ ) { + if( string[size] == '\0' ) { + break; + } + } + } + else { + size = strlen( string ); + } + + width -= size; + + while( size-- ) { + *buf++ = *string++; + } + + while( width-- > 0 ) { + *buf++ = ' '; + } + + *buf_p = buf; +} + +/* +vsprintf + +I'm not going to support a bunch of the more arcane stuff in here +just to keep it simpler. For example, the '*' and '$' are not +currently supported. I've tried to make it so that it will just +parse and ignore formats we don't support. +*/ +int vsprintf( char *buffer, const char *fmt, va_list argptr ) { + int *arg; + char *buf_p; + char ch; + int flags; + int width; + int prec; + int n; + char sign; + + buf_p = buffer; + arg = (int *)argptr; + + while( qtrue ) { + // run through the format string until we hit a '%' or '\0' + for ( ch = *fmt; (ch = *fmt) != '\0' && ch != '%'; fmt++ ) { + *buf_p++ = ch; + } + if ( ch == '\0' ) { + goto done; + } + + // skip over the '%' + fmt++; + + // reset formatting state + flags = 0; + width = 0; + prec = -1; + sign = '\0'; + +rflag: + ch = *fmt++; +reswitch: + switch( ch ) { + case '-': + flags |= LADJUST; + goto rflag; + case '.': + n = 0; + while( is_digit( ( ch = *fmt++ ) ) ) { + n = 10 * n + ( ch - '0' ); + } + prec = n < 0 ? -1 : n; + goto reswitch; + case '0': + flags |= ZEROPAD; + goto rflag; + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + n = 0; + do { + n = 10 * n + ( ch - '0' ); + ch = *fmt++; + } while( is_digit( ch ) ); + width = n; + goto reswitch; + case 'c': + *buf_p++ = (char)*arg; + arg++; + break; + case 'd': + case 'i': + AddInt( &buf_p, *arg, width, flags ); + arg++; + break; + case 'f': + AddFloat( &buf_p, *(double *)arg, width, prec ); +#ifdef __LCC__ + arg += 1; // everything is 32 bit in my compiler +#else + arg += 2; +#endif + break; + case 's': + AddString( &buf_p, (char *)*arg, width, prec ); + arg++; + break; + case '%': + *buf_p++ = ch; + break; + default: + *buf_p++ = (char)*arg; + arg++; + break; + } + } + +done: + *buf_p = 0; + return buf_p - buffer; +} + +/* this is really crappy */ +int sscanf( const char *buffer, const char *fmt, ... ) { + int cmd; + int **arg; + int count; + + arg = (int **)&fmt + 1; + count = 0; + + while ( *fmt ) { + if ( fmt[0] != '%' ) { + fmt++; + continue; + } + + cmd = fmt[1]; + fmt += 2; + + switch ( cmd ) { + case 'i': + case 'd': + case 'u': + **arg = _atoi( &buffer ); + break; + case 'f': + *(float *)*arg = _atof( &buffer ); + break; + } + arg++; + } + + return count; +} + +#endif diff --git a/code/game/bg_lib.h b/code/game/bg_lib.h index 57333c0..d6deb60 100755 --- a/code/game/bg_lib.h +++ b/code/game/bg_lib.h @@ -1,91 +1,91 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// bg_lib.h -- standard C library replacement routines used by code -// compiled for the virtual machine - -// This file is NOT included on native builds - -typedef int size_t; - -typedef char * va_list; -#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) -#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ) -#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) -#define va_end(ap) ( ap = (va_list)0 ) - -#define CHAR_BIT 8 /* number of bits in a char */ -#define SCHAR_MIN (-128) /* minimum signed char value */ -#define SCHAR_MAX 127 /* maximum signed char value */ -#define UCHAR_MAX 0xff /* maximum unsigned char value */ - -#define SHRT_MIN (-32768) /* minimum (signed) short value */ -#define SHRT_MAX 32767 /* maximum (signed) short value */ -#define USHRT_MAX 0xffff /* maximum unsigned short value */ -#define INT_MIN (-2147483647 - 1) /* minimum (signed) int value */ -#define INT_MAX 2147483647 /* maximum (signed) int value */ -#define UINT_MAX 0xffffffff /* maximum unsigned int value */ -#define LONG_MIN (-2147483647L - 1) /* minimum (signed) long value */ -#define LONG_MAX 2147483647L /* maximum (signed) long value */ -#define ULONG_MAX 0xffffffffUL /* maximum unsigned long value */ - -// Misc functions -typedef int cmp_t(const void *, const void *); -void qsort(void *a, size_t n, size_t es, cmp_t *cmp); -void srand( unsigned seed ); -int rand( void ); - -// String functions -size_t strlen( const char *string ); -char *strcat( char *strDestination, const char *strSource ); -char *strcpy( char *strDestination, const char *strSource ); -int strcmp( const char *string1, const char *string2 ); -char *strchr( const char *string, int c ); -char *strstr( const char *string, const char *strCharSet ); -char *strncpy( char *strDest, const char *strSource, size_t count ); -int tolower( int c ); -int toupper( int c ); - -double atof( const char *string ); -double _atof( const char **stringPtr ); -int atoi( const char *string ); -int _atoi( const char **stringPtr ); - -int vsprintf( char *buffer, const char *fmt, va_list argptr ); -int sscanf( const char *buffer, const char *fmt, ... ); - -// Memory functions -void *memmove( void *dest, const void *src, size_t count ); -void *memset( void *dest, int c, size_t count ); -void *memcpy( void *dest, const void *src, size_t count ); - -// Math functions -double ceil( double x ); -double floor( double x ); -double sqrt( double x ); -double sin( double x ); -double cos( double x ); -double atan2( double y, double x ); -double tan( double x ); -int abs( int n ); -double fabs( double x ); -double acos( double x ); - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// bg_lib.h -- standard C library replacement routines used by code +// compiled for the virtual machine + +// This file is NOT included on native builds + +typedef int size_t; + +typedef char * va_list; +#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) +#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ) +#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) +#define va_end(ap) ( ap = (va_list)0 ) + +#define CHAR_BIT 8 /* number of bits in a char */ +#define SCHAR_MIN (-128) /* minimum signed char value */ +#define SCHAR_MAX 127 /* maximum signed char value */ +#define UCHAR_MAX 0xff /* maximum unsigned char value */ + +#define SHRT_MIN (-32768) /* minimum (signed) short value */ +#define SHRT_MAX 32767 /* maximum (signed) short value */ +#define USHRT_MAX 0xffff /* maximum unsigned short value */ +#define INT_MIN (-2147483647 - 1) /* minimum (signed) int value */ +#define INT_MAX 2147483647 /* maximum (signed) int value */ +#define UINT_MAX 0xffffffff /* maximum unsigned int value */ +#define LONG_MIN (-2147483647L - 1) /* minimum (signed) long value */ +#define LONG_MAX 2147483647L /* maximum (signed) long value */ +#define ULONG_MAX 0xffffffffUL /* maximum unsigned long value */ + +// Misc functions +typedef int cmp_t(const void *, const void *); +void qsort(void *a, size_t n, size_t es, cmp_t *cmp); +void srand( unsigned seed ); +int rand( void ); + +// String functions +size_t strlen( const char *string ); +char *strcat( char *strDestination, const char *strSource ); +char *strcpy( char *strDestination, const char *strSource ); +int strcmp( const char *string1, const char *string2 ); +char *strchr( const char *string, int c ); +char *strstr( const char *string, const char *strCharSet ); +char *strncpy( char *strDest, const char *strSource, size_t count ); +int tolower( int c ); +int toupper( int c ); + +double atof( const char *string ); +double _atof( const char **stringPtr ); +int atoi( const char *string ); +int _atoi( const char **stringPtr ); + +int vsprintf( char *buffer, const char *fmt, va_list argptr ); +int sscanf( const char *buffer, const char *fmt, ... ); + +// Memory functions +void *memmove( void *dest, const void *src, size_t count ); +void *memset( void *dest, int c, size_t count ); +void *memcpy( void *dest, const void *src, size_t count ); + +// Math functions +double ceil( double x ); +double floor( double x ); +double sqrt( double x ); +double sin( double x ); +double cos( double x ); +double atan2( double y, double x ); +double tan( double x ); +int abs( int n ); +double fabs( double x ); +double acos( double x ); + diff --git a/code/game/bg_local.h b/code/game/bg_local.h index 223c688..389683d 100755 --- a/code/game/bg_local.h +++ b/code/game/bg_local.h @@ -1,83 +1,83 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// bg_local.h -- local definitions for the bg (both games) files - -#define MIN_WALK_NORMAL 0.7f // can't walk on very steep slopes - -#define STEPSIZE 18 - -#define JUMP_VELOCITY 270 - -#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 float pm_stopspeed; -extern float pm_duckScale; -extern float pm_swimScale; -extern float pm_wadeScale; - -extern float pm_accelerate; -extern float pm_airaccelerate; -extern float pm_wateraccelerate; -extern float pm_flyaccelerate; - -extern float pm_friction; -extern float pm_waterfriction; -extern float pm_flightfriction; - -extern int c_pmove; - -void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce ); -void PM_AddTouchEnt( int entityNum ); -void PM_AddEvent( int newEvent ); - -qboolean PM_SlideMove( qboolean gravity ); -void PM_StepSlideMove( qboolean gravity ); - - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// bg_local.h -- local definitions for the bg (both games) files + +#define MIN_WALK_NORMAL 0.7f // can't walk on very steep slopes + +#define STEPSIZE 18 + +#define JUMP_VELOCITY 270 + +#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 float pm_stopspeed; +extern float pm_duckScale; +extern float pm_swimScale; +extern float pm_wadeScale; + +extern float pm_accelerate; +extern float pm_airaccelerate; +extern float pm_wateraccelerate; +extern float pm_flyaccelerate; + +extern float pm_friction; +extern float pm_waterfriction; +extern float pm_flightfriction; + +extern int c_pmove; + +void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce ); +void PM_AddTouchEnt( int entityNum ); +void PM_AddEvent( int newEvent ); + +qboolean PM_SlideMove( qboolean gravity ); +void PM_StepSlideMove( qboolean gravity ); + + diff --git a/code/game/bg_misc.c b/code/game/bg_misc.c index f729f06..1e5a27e 100755 --- a/code/game/bg_misc.c +++ b/code/game/bg_misc.c @@ -1,1604 +1,1604 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// bg_misc.c -- both games misc functions, all completely stateless - -#include "q_shared.h" -#include "bg_public.h" - -/*QUAKED item_***** ( 0 0 0 ) (-16 -16 -16) (16 16 16) suspended -DO NOT USE THIS CLASS, IT JUST HOLDS GENERAL INFORMATION. -The suspended flag will allow items to hang in the air, otherwise they are dropped to the next surface. - -If an item is the target of another entity, it will not spawn in until fired. - -An item fires all of its targets when it is picked up. If the toucher can't carry it, the targets won't be fired. - -"notfree" if set to 1, don't spawn in free for all games -"notteam" if set to 1, don't spawn in team games -"notsingle" if set to 1, don't spawn in single player games -"wait" override the default wait before respawning. -1 = never respawn automatically, which can be used with targeted spawning. -"random" random number of plus or minus seconds varied from the respawn time -"count" override quantity or duration on most items. -*/ - -gitem_t bg_itemlist[] = -{ - { - NULL, - NULL, - { NULL, - NULL, - 0, 0} , -/* icon */ NULL, -/* pickup */ NULL, - 0, - 0, - 0, -/* precache */ "", -/* sounds */ "" - }, // leave index 0 alone - - // - // ARMOR - // - -/*QUAKED item_armor_shard (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "item_armor_shard", - "sound/misc/ar1_pkup.wav", - { "models/powerups/armor/shard.md3", - "models/powerups/armor/shard_sphere.md3", - 0, 0} , -/* icon */ "icons/iconr_shard", -/* pickup */ "Armor Shard", - 5, - IT_ARMOR, - 0, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED item_armor_combat (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "item_armor_combat", - "sound/misc/ar2_pkup.wav", - { "models/powerups/armor/armor_yel.md3", - 0, 0, 0}, -/* icon */ "icons/iconr_yellow", -/* pickup */ "Armor", - 50, - IT_ARMOR, - 0, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED item_armor_body (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "item_armor_body", - "sound/misc/ar2_pkup.wav", - { "models/powerups/armor/armor_red.md3", - 0, 0, 0}, -/* icon */ "icons/iconr_red", -/* pickup */ "Heavy Armor", - 100, - IT_ARMOR, - 0, -/* precache */ "", -/* sounds */ "" - }, - - // - // health - // -/*QUAKED item_health_small (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "item_health_small", - "sound/items/s_health.wav", - { "models/powerups/health/small_cross.md3", - "models/powerups/health/small_sphere.md3", - 0, 0 }, -/* icon */ "icons/iconh_green", -/* pickup */ "5 Health", - 5, - IT_HEALTH, - 0, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED item_health (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "item_health", - "sound/items/n_health.wav", - { "models/powerups/health/medium_cross.md3", - "models/powerups/health/medium_sphere.md3", - 0, 0 }, -/* icon */ "icons/iconh_yellow", -/* pickup */ "25 Health", - 25, - IT_HEALTH, - 0, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED item_health_large (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "item_health_large", - "sound/items/l_health.wav", - { "models/powerups/health/large_cross.md3", - "models/powerups/health/large_sphere.md3", - 0, 0 }, -/* icon */ "icons/iconh_red", -/* pickup */ "50 Health", - 50, - IT_HEALTH, - 0, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED item_health_mega (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "item_health_mega", - "sound/items/m_health.wav", - { "models/powerups/health/mega_cross.md3", - "models/powerups/health/mega_sphere.md3", - 0, 0 }, -/* icon */ "icons/iconh_mega", -/* pickup */ "Mega Health", - 100, - IT_HEALTH, - 0, -/* precache */ "", -/* sounds */ "" - }, - - - // - // WEAPONS - // - -/*QUAKED weapon_gauntlet (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "weapon_gauntlet", - "sound/misc/w_pkup.wav", - { "models/weapons2/gauntlet/gauntlet.md3", - 0, 0, 0}, -/* icon */ "icons/iconw_gauntlet", -/* pickup */ "Gauntlet", - 0, - IT_WEAPON, - WP_GAUNTLET, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED weapon_shotgun (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "weapon_shotgun", - "sound/misc/w_pkup.wav", - { "models/weapons2/shotgun/shotgun.md3", - 0, 0, 0}, -/* icon */ "icons/iconw_shotgun", -/* pickup */ "Shotgun", - 10, - IT_WEAPON, - WP_SHOTGUN, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED weapon_machinegun (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "weapon_machinegun", - "sound/misc/w_pkup.wav", - { "models/weapons2/machinegun/machinegun.md3", - 0, 0, 0}, -/* icon */ "icons/iconw_machinegun", -/* pickup */ "Machinegun", - 40, - IT_WEAPON, - WP_MACHINEGUN, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED weapon_grenadelauncher (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "weapon_grenadelauncher", - "sound/misc/w_pkup.wav", - { "models/weapons2/grenadel/grenadel.md3", - 0, 0, 0}, -/* icon */ "icons/iconw_grenade", -/* pickup */ "Grenade Launcher", - 10, - IT_WEAPON, - WP_GRENADE_LAUNCHER, -/* precache */ "", -/* sounds */ "sound/weapons/grenade/hgrenb1a.wav sound/weapons/grenade/hgrenb2a.wav" - }, - -/*QUAKED weapon_rocketlauncher (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "weapon_rocketlauncher", - "sound/misc/w_pkup.wav", - { "models/weapons2/rocketl/rocketl.md3", - 0, 0, 0}, -/* icon */ "icons/iconw_rocket", -/* pickup */ "Rocket Launcher", - 10, - IT_WEAPON, - WP_ROCKET_LAUNCHER, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED weapon_lightning (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "weapon_lightning", - "sound/misc/w_pkup.wav", - { "models/weapons2/lightning/lightning.md3", - 0, 0, 0}, -/* icon */ "icons/iconw_lightning", -/* pickup */ "Lightning Gun", - 100, - IT_WEAPON, - WP_LIGHTNING, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED weapon_railgun (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "weapon_railgun", - "sound/misc/w_pkup.wav", - { "models/weapons2/railgun/railgun.md3", - 0, 0, 0}, -/* icon */ "icons/iconw_railgun", -/* pickup */ "Railgun", - 10, - IT_WEAPON, - WP_RAILGUN, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED weapon_plasmagun (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "weapon_plasmagun", - "sound/misc/w_pkup.wav", - { "models/weapons2/plasma/plasma.md3", - 0, 0, 0}, -/* icon */ "icons/iconw_plasma", -/* pickup */ "Plasma Gun", - 50, - IT_WEAPON, - WP_PLASMAGUN, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED weapon_bfg (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "weapon_bfg", - "sound/misc/w_pkup.wav", - { "models/weapons2/bfg/bfg.md3", - 0, 0, 0}, -/* icon */ "icons/iconw_bfg", -/* pickup */ "BFG10K", - 20, - IT_WEAPON, - WP_BFG, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED weapon_grapplinghook (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "weapon_grapplinghook", - "sound/misc/w_pkup.wav", - { "models/weapons2/grapple/grapple.md3", - 0, 0, 0}, -/* icon */ "icons/iconw_grapple", -/* pickup */ "Grappling Hook", - 0, - IT_WEAPON, - WP_GRAPPLING_HOOK, -/* precache */ "", -/* sounds */ "" - }, - - // - // AMMO ITEMS - // - -/*QUAKED ammo_shells (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "ammo_shells", - "sound/misc/am_pkup.wav", - { "models/powerups/ammo/shotgunam.md3", - 0, 0, 0}, -/* icon */ "icons/icona_shotgun", -/* pickup */ "Shells", - 10, - IT_AMMO, - WP_SHOTGUN, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED ammo_bullets (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "ammo_bullets", - "sound/misc/am_pkup.wav", - { "models/powerups/ammo/machinegunam.md3", - 0, 0, 0}, -/* icon */ "icons/icona_machinegun", -/* pickup */ "Bullets", - 50, - IT_AMMO, - WP_MACHINEGUN, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED ammo_grenades (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "ammo_grenades", - "sound/misc/am_pkup.wav", - { "models/powerups/ammo/grenadeam.md3", - 0, 0, 0}, -/* icon */ "icons/icona_grenade", -/* pickup */ "Grenades", - 5, - IT_AMMO, - WP_GRENADE_LAUNCHER, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED ammo_cells (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "ammo_cells", - "sound/misc/am_pkup.wav", - { "models/powerups/ammo/plasmaam.md3", - 0, 0, 0}, -/* icon */ "icons/icona_plasma", -/* pickup */ "Cells", - 30, - IT_AMMO, - WP_PLASMAGUN, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED ammo_lightning (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "ammo_lightning", - "sound/misc/am_pkup.wav", - { "models/powerups/ammo/lightningam.md3", - 0, 0, 0}, -/* icon */ "icons/icona_lightning", -/* pickup */ "Lightning", - 60, - IT_AMMO, - WP_LIGHTNING, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED ammo_rockets (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "ammo_rockets", - "sound/misc/am_pkup.wav", - { "models/powerups/ammo/rocketam.md3", - 0, 0, 0}, -/* icon */ "icons/icona_rocket", -/* pickup */ "Rockets", - 5, - IT_AMMO, - WP_ROCKET_LAUNCHER, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED ammo_slugs (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "ammo_slugs", - "sound/misc/am_pkup.wav", - { "models/powerups/ammo/railgunam.md3", - 0, 0, 0}, -/* icon */ "icons/icona_railgun", -/* pickup */ "Slugs", - 10, - IT_AMMO, - WP_RAILGUN, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED ammo_bfg (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "ammo_bfg", - "sound/misc/am_pkup.wav", - { "models/powerups/ammo/bfgam.md3", - 0, 0, 0}, -/* icon */ "icons/icona_bfg", -/* pickup */ "Bfg Ammo", - 15, - IT_AMMO, - WP_BFG, -/* precache */ "", -/* sounds */ "" - }, - - // - // HOLDABLE ITEMS - // -/*QUAKED holdable_teleporter (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "holdable_teleporter", - "sound/items/holdable.wav", - { "models/powerups/holdable/teleporter.md3", - 0, 0, 0}, -/* icon */ "icons/teleporter", -/* pickup */ "Personal Teleporter", - 60, - IT_HOLDABLE, - HI_TELEPORTER, -/* precache */ "", -/* sounds */ "" - }, -/*QUAKED holdable_medkit (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "holdable_medkit", - "sound/items/holdable.wav", - { - "models/powerups/holdable/medkit.md3", - "models/powerups/holdable/medkit_sphere.md3", - 0, 0}, -/* icon */ "icons/medkit", -/* pickup */ "Medkit", - 60, - IT_HOLDABLE, - HI_MEDKIT, -/* precache */ "", -/* sounds */ "sound/items/use_medkit.wav" - }, - - // - // POWERUP ITEMS - // -/*QUAKED item_quad (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "item_quad", - "sound/items/quaddamage.wav", - { "models/powerups/instant/quad.md3", - "models/powerups/instant/quad_ring.md3", - 0, 0 }, -/* icon */ "icons/quad", -/* pickup */ "Quad Damage", - 30, - IT_POWERUP, - PW_QUAD, -/* precache */ "", -/* sounds */ "sound/items/damage2.wav sound/items/damage3.wav" - }, - -/*QUAKED item_enviro (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "item_enviro", - "sound/items/protect.wav", - { "models/powerups/instant/enviro.md3", - "models/powerups/instant/enviro_ring.md3", - 0, 0 }, -/* icon */ "icons/envirosuit", -/* pickup */ "Battle Suit", - 30, - IT_POWERUP, - PW_BATTLESUIT, -/* precache */ "", -/* sounds */ "sound/items/airout.wav sound/items/protect3.wav" - }, - -/*QUAKED item_haste (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "item_haste", - "sound/items/haste.wav", - { "models/powerups/instant/haste.md3", - "models/powerups/instant/haste_ring.md3", - 0, 0 }, -/* icon */ "icons/haste", -/* pickup */ "Speed", - 30, - IT_POWERUP, - PW_HASTE, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED item_invis (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "item_invis", - "sound/items/invisibility.wav", - { "models/powerups/instant/invis.md3", - "models/powerups/instant/invis_ring.md3", - 0, 0 }, -/* icon */ "icons/invis", -/* pickup */ "Invisibility", - 30, - IT_POWERUP, - PW_INVIS, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED item_regen (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "item_regen", - "sound/items/regeneration.wav", - { "models/powerups/instant/regen.md3", - "models/powerups/instant/regen_ring.md3", - 0, 0 }, -/* icon */ "icons/regen", -/* pickup */ "Regeneration", - 30, - IT_POWERUP, - PW_REGEN, -/* precache */ "", -/* sounds */ "sound/items/regen.wav" - }, - -/*QUAKED item_flight (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "item_flight", - "sound/items/flight.wav", - { "models/powerups/instant/flight.md3", - "models/powerups/instant/flight_ring.md3", - 0, 0 }, -/* icon */ "icons/flight", -/* pickup */ "Flight", - 60, - IT_POWERUP, - PW_FLIGHT, -/* precache */ "", -/* sounds */ "sound/items/flight.wav" - }, - -/*QUAKED team_CTF_redflag (1 0 0) (-16 -16 -16) (16 16 16) -Only in CTF games -*/ - { - "team_CTF_redflag", - NULL, - { "models/flags/r_flag.md3", - 0, 0, 0 }, -/* icon */ "icons/iconf_red1", -/* pickup */ "Red Flag", - 0, - IT_TEAM, - PW_REDFLAG, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED team_CTF_blueflag (0 0 1) (-16 -16 -16) (16 16 16) -Only in CTF games -*/ - { - "team_CTF_blueflag", - NULL, - { "models/flags/b_flag.md3", - 0, 0, 0 }, -/* icon */ "icons/iconf_blu1", -/* pickup */ "Blue Flag", - 0, - IT_TEAM, - PW_BLUEFLAG, -/* precache */ "", -/* sounds */ "" - }, - -#ifdef MISSIONPACK -/*QUAKED holdable_kamikaze (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "holdable_kamikaze", - "sound/items/holdable.wav", - { "models/powerups/kamikazi.md3", - 0, 0, 0}, -/* icon */ "icons/kamikaze", -/* pickup */ "Kamikaze", - 60, - IT_HOLDABLE, - HI_KAMIKAZE, -/* precache */ "", -/* sounds */ "sound/items/kamikazerespawn.wav" - }, - -/*QUAKED holdable_portal (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "holdable_portal", - "sound/items/holdable.wav", - { "models/powerups/holdable/porter.md3", - 0, 0, 0}, -/* icon */ "icons/portal", -/* pickup */ "Portal", - 60, - IT_HOLDABLE, - HI_PORTAL, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED holdable_invulnerability (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "holdable_invulnerability", - "sound/items/holdable.wav", - { "models/powerups/holdable/invulnerability.md3", - 0, 0, 0}, -/* icon */ "icons/invulnerability", -/* pickup */ "Invulnerability", - 60, - IT_HOLDABLE, - HI_INVULNERABILITY, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED ammo_nails (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "ammo_nails", - "sound/misc/am_pkup.wav", - { "models/powerups/ammo/nailgunam.md3", - 0, 0, 0}, -/* icon */ "icons/icona_nailgun", -/* pickup */ "Nails", - 20, - IT_AMMO, - WP_NAILGUN, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED ammo_mines (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "ammo_mines", - "sound/misc/am_pkup.wav", - { "models/powerups/ammo/proxmineam.md3", - 0, 0, 0}, -/* icon */ "icons/icona_proxlauncher", -/* pickup */ "Proximity Mines", - 10, - IT_AMMO, - WP_PROX_LAUNCHER, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED ammo_belt (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "ammo_belt", - "sound/misc/am_pkup.wav", - { "models/powerups/ammo/chaingunam.md3", - 0, 0, 0}, -/* icon */ "icons/icona_chaingun", -/* pickup */ "Chaingun Belt", - 100, - IT_AMMO, - WP_CHAINGUN, -/* precache */ "", -/* sounds */ "" - }, - - // - // PERSISTANT POWERUP ITEMS - // -/*QUAKED item_scout (.3 .3 1) (-16 -16 -16) (16 16 16) suspended redTeam blueTeam -*/ - { - "item_scout", - "sound/items/scout.wav", - { "models/powerups/scout.md3", - 0, 0, 0 }, -/* icon */ "icons/scout", -/* pickup */ "Scout", - 30, - IT_PERSISTANT_POWERUP, - PW_SCOUT, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED item_guard (.3 .3 1) (-16 -16 -16) (16 16 16) suspended redTeam blueTeam -*/ - { - "item_guard", - "sound/items/guard.wav", - { "models/powerups/guard.md3", - 0, 0, 0 }, -/* icon */ "icons/guard", -/* pickup */ "Guard", - 30, - IT_PERSISTANT_POWERUP, - PW_GUARD, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED item_doubler (.3 .3 1) (-16 -16 -16) (16 16 16) suspended redTeam blueTeam -*/ - { - "item_doubler", - "sound/items/doubler.wav", - { "models/powerups/doubler.md3", - 0, 0, 0 }, -/* icon */ "icons/doubler", -/* pickup */ "Doubler", - 30, - IT_PERSISTANT_POWERUP, - PW_DOUBLER, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED item_doubler (.3 .3 1) (-16 -16 -16) (16 16 16) suspended redTeam blueTeam -*/ - { - "item_ammoregen", - "sound/items/ammoregen.wav", - { "models/powerups/ammo.md3", - 0, 0, 0 }, -/* icon */ "icons/ammo_regen", -/* pickup */ "Ammo Regen", - 30, - IT_PERSISTANT_POWERUP, - PW_AMMOREGEN, -/* precache */ "", -/* sounds */ "" - }, - - /*QUAKED team_CTF_neutralflag (0 0 1) (-16 -16 -16) (16 16 16) -Only in One Flag CTF games -*/ - { - "team_CTF_neutralflag", - NULL, - { "models/flags/n_flag.md3", - 0, 0, 0 }, -/* icon */ "icons/iconf_neutral1", -/* pickup */ "Neutral Flag", - 0, - IT_TEAM, - PW_NEUTRALFLAG, -/* precache */ "", -/* sounds */ "" - }, - - { - "item_redcube", - "sound/misc/am_pkup.wav", - { "models/powerups/orb/r_orb.md3", - 0, 0, 0 }, -/* icon */ "icons/iconh_rorb", -/* pickup */ "Red Cube", - 0, - IT_TEAM, - 0, -/* precache */ "", -/* sounds */ "" - }, - - { - "item_bluecube", - "sound/misc/am_pkup.wav", - { "models/powerups/orb/b_orb.md3", - 0, 0, 0 }, -/* icon */ "icons/iconh_borb", -/* pickup */ "Blue Cube", - 0, - IT_TEAM, - 0, -/* precache */ "", -/* sounds */ "" - }, -/*QUAKED weapon_nailgun (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "weapon_nailgun", - "sound/misc/w_pkup.wav", - { "models/weapons/nailgun/nailgun.md3", - 0, 0, 0}, -/* icon */ "icons/iconw_nailgun", -/* pickup */ "Nailgun", - 10, - IT_WEAPON, - WP_NAILGUN, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED weapon_prox_launcher (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "weapon_prox_launcher", - "sound/misc/w_pkup.wav", - { "models/weapons/proxmine/proxmine.md3", - 0, 0, 0}, -/* icon */ "icons/iconw_proxlauncher", -/* pickup */ "Prox Launcher", - 5, - IT_WEAPON, - WP_PROX_LAUNCHER, -/* precache */ "", -/* sounds */ "sound/weapons/proxmine/wstbtick.wav " - "sound/weapons/proxmine/wstbactv.wav " - "sound/weapons/proxmine/wstbimpl.wav " - "sound/weapons/proxmine/wstbimpm.wav " - "sound/weapons/proxmine/wstbimpd.wav " - "sound/weapons/proxmine/wstbactv.wav" - }, - -/*QUAKED weapon_chaingun (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "weapon_chaingun", - "sound/misc/w_pkup.wav", - { "models/weapons/vulcan/vulcan.md3", - 0, 0, 0}, -/* icon */ "icons/iconw_chaingun", -/* pickup */ "Chaingun", - 80, - IT_WEAPON, - WP_CHAINGUN, -/* precache */ "", -/* sounds */ "sound/weapons/vulcan/wvulwind.wav" - }, -#endif - - // end of list marker - {NULL} -}; - -int bg_numItems = sizeof(bg_itemlist) / sizeof(bg_itemlist[0]) - 1; - - -/* -============== -BG_FindItemForPowerup -============== -*/ -gitem_t *BG_FindItemForPowerup( powerup_t pw ) { - int i; - - for ( i = 0 ; i < bg_numItems ; i++ ) { - if ( (bg_itemlist[i].giType == IT_POWERUP || - bg_itemlist[i].giType == IT_TEAM || - bg_itemlist[i].giType == IT_PERSISTANT_POWERUP) && - bg_itemlist[i].giTag == pw ) { - return &bg_itemlist[i]; - } - } - - return NULL; -} - - -/* -============== -BG_FindItemForHoldable -============== -*/ -gitem_t *BG_FindItemForHoldable( holdable_t pw ) { - int i; - - for ( i = 0 ; i < bg_numItems ; i++ ) { - if ( bg_itemlist[i].giType == IT_HOLDABLE && bg_itemlist[i].giTag == pw ) { - return &bg_itemlist[i]; - } - } - - Com_Error( ERR_DROP, "HoldableItem not found" ); - - return NULL; -} - - -/* -=============== -BG_FindItemForWeapon - -=============== -*/ -gitem_t *BG_FindItemForWeapon( weapon_t weapon ) { - gitem_t *it; - - for ( it = bg_itemlist + 1 ; it->classname ; it++) { - if ( it->giType == IT_WEAPON && it->giTag == weapon ) { - return it; - } - } - - Com_Error( ERR_DROP, "Couldn't find item for weapon %i", weapon); - return NULL; -} - -/* -=============== -BG_FindItem - -=============== -*/ -gitem_t *BG_FindItem( const char *pickupName ) { - gitem_t *it; - - for ( it = bg_itemlist + 1 ; it->classname ; it++ ) { - if ( !Q_stricmp( it->pickup_name, pickupName ) ) - return it; - } - - return NULL; -} - -/* -============ -BG_PlayerTouchesItem - -Items can be picked up without actually touching their physical bounds to make -grabbing them easier -============ -*/ -qboolean BG_PlayerTouchesItem( playerState_t *ps, entityState_t *item, int atTime ) { - vec3_t origin; - - BG_EvaluateTrajectory( &item->pos, atTime, origin ); - - // we are ignoring ducked differences here - if ( ps->origin[0] - origin[0] > 44 - || ps->origin[0] - origin[0] < -50 - || ps->origin[1] - origin[1] > 36 - || ps->origin[1] - origin[1] < -36 - || ps->origin[2] - origin[2] > 36 - || ps->origin[2] - origin[2] < -36 ) { - return qfalse; - } - - return qtrue; -} - - - -/* -================ -BG_CanItemBeGrabbed - -Returns false if the item should not be picked up. -This needs to be the same for client side prediction and server use. -================ -*/ -qboolean BG_CanItemBeGrabbed( int gametype, const entityState_t *ent, const playerState_t *ps ) { - gitem_t *item; -#ifdef MISSIONPACK - int upperBound; -#endif - - 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: - return qtrue; // weapons are always picked up - - case IT_AMMO: - if ( ps->ammo[ item->giTag ] >= 200 ) { - return qfalse; // can't hold any more - } - return qtrue; - - case IT_ARMOR: -#ifdef MISSIONPACK - if( bg_itemlist[ps->stats[STAT_PERSISTANT_POWERUP]].giTag == PW_SCOUT ) { - return qfalse; - } - - // we also clamp armor to the maxhealth for handicapping - if( bg_itemlist[ps->stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) { - upperBound = ps->stats[STAT_MAX_HEALTH]; - } - else { - upperBound = ps->stats[STAT_MAX_HEALTH] * 2; - } - - if ( ps->stats[STAT_ARMOR] >= upperBound ) { - return qfalse; - } -#else - if ( ps->stats[STAT_ARMOR] >= ps->stats[STAT_MAX_HEALTH] * 2 ) { - return qfalse; - } -#endif - return qtrue; - - case IT_HEALTH: - // small and mega healths will go over the max, otherwise - // don't pick up if already at max -#ifdef MISSIONPACK - if( bg_itemlist[ps->stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) { - upperBound = ps->stats[STAT_MAX_HEALTH]; - } - else -#endif - if ( item->quantity == 5 || item->quantity == 100 ) { - if ( ps->stats[STAT_HEALTH] >= ps->stats[STAT_MAX_HEALTH] * 2 ) { - return qfalse; - } - return qtrue; - } - - if ( ps->stats[STAT_HEALTH] >= ps->stats[STAT_MAX_HEALTH] ) { - return qfalse; - } - return qtrue; - - case IT_POWERUP: - return qtrue; // powerups are always picked up - -#ifdef MISSIONPACK - case IT_PERSISTANT_POWERUP: - // can only hold one item at a time - if ( ps->stats[STAT_PERSISTANT_POWERUP] ) { - return qfalse; - } - - // check team only - if( ( ent->generic1 & 2 ) && ( ps->persistant[PERS_TEAM] != TEAM_RED ) ) { - return qfalse; - } - if( ( ent->generic1 & 4 ) && ( ps->persistant[PERS_TEAM] != TEAM_BLUE ) ) { - return qfalse; - } - - return qtrue; -#endif - - case IT_TEAM: // team items, such as flags -#ifdef MISSIONPACK - if( gametype == GT_1FCTF ) { - // neutral flag can always be picked up - if( item->giTag == PW_NEUTRALFLAG ) { - return qtrue; - } - if (ps->persistant[PERS_TEAM] == TEAM_RED) { - if (item->giTag == PW_BLUEFLAG && ps->powerups[PW_NEUTRALFLAG] ) { - return qtrue; - } - } else if (ps->persistant[PERS_TEAM] == TEAM_BLUE) { - if (item->giTag == PW_REDFLAG && ps->powerups[PW_NEUTRALFLAG] ) { - return qtrue; - } - } - } -#endif - if( gametype == GT_CTF ) { - // ent->modelindex2 is non-zero on items if they are dropped - // we need to know this because we can pick up our dropped flag (and return it) - // but we can't pick up our flag at base - if (ps->persistant[PERS_TEAM] == TEAM_RED) { - if (item->giTag == PW_BLUEFLAG || - (item->giTag == PW_REDFLAG && ent->modelindex2) || - (item->giTag == PW_REDFLAG && ps->powerups[PW_BLUEFLAG]) ) - return qtrue; - } else if (ps->persistant[PERS_TEAM] == TEAM_BLUE) { - if (item->giTag == PW_REDFLAG || - (item->giTag == PW_BLUEFLAG && ent->modelindex2) || - (item->giTag == PW_BLUEFLAG && ps->powerups[PW_REDFLAG]) ) - return qtrue; - } - } - -#ifdef MISSIONPACK - if( gametype == GT_HARVESTER ) { - return qtrue; - } -#endif - return qfalse; - - case IT_HOLDABLE: - // can only hold one item at a time - if ( ps->stats[STAT_HOLDABLE_ITEM] ) { - return qfalse; - } - return qtrue; - - case IT_BAD: - Com_Error( ERR_DROP, "BG_CanItemBeGrabbed: IT_BAD" ); - default: -#ifndef Q3_VM -#ifndef NDEBUG // bk0001204 - Com_Printf("BG_CanItemBeGrabbed: unknown enum %d\n", item->giType ); -#endif -#endif - break; - } - - return qfalse; -} - -//====================================================================== - -/* -================ -BG_EvaluateTrajectory - -================ -*/ -void BG_EvaluateTrajectory( const trajectory_t *tr, int atTime, vec3_t result ) { - float deltaTime; - float phase; - - switch( tr->trType ) { - case TR_STATIONARY: - case TR_INTERPOLATE: - VectorCopy( tr->trBase, result ); - break; - case TR_LINEAR: - deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds - VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); - break; - case TR_SINE: - deltaTime = ( atTime - tr->trTime ) / (float) tr->trDuration; - phase = sin( deltaTime * M_PI * 2 ); - VectorMA( tr->trBase, phase, tr->trDelta, result ); - break; - case TR_LINEAR_STOP: - if ( atTime > tr->trTime + tr->trDuration ) { - atTime = tr->trTime + tr->trDuration; - } - deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds - if ( deltaTime < 0 ) { - deltaTime = 0; - } - VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); - break; - case TR_GRAVITY: - deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds - VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); - result[2] -= 0.5 * DEFAULT_GRAVITY * deltaTime * deltaTime; // FIXME: local gravity... - break; - default: - Com_Error( ERR_DROP, "BG_EvaluateTrajectory: unknown trType: %i", tr->trTime ); - break; - } -} - -/* -================ -BG_EvaluateTrajectoryDelta - -For determining velocity at a given time -================ -*/ -void BG_EvaluateTrajectoryDelta( const trajectory_t *tr, int atTime, vec3_t result ) { - float deltaTime; - float phase; - - switch( tr->trType ) { - case TR_STATIONARY: - case TR_INTERPOLATE: - VectorClear( result ); - break; - case TR_LINEAR: - VectorCopy( tr->trDelta, result ); - break; - case TR_SINE: - deltaTime = ( atTime - tr->trTime ) / (float) tr->trDuration; - phase = cos( deltaTime * M_PI * 2 ); // derivative of sin = cos - phase *= 0.5; - VectorScale( tr->trDelta, phase, result ); - break; - case TR_LINEAR_STOP: - if ( atTime > tr->trTime + tr->trDuration ) { - VectorClear( result ); - return; - } - VectorCopy( tr->trDelta, result ); - break; - case TR_GRAVITY: - deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds - VectorCopy( tr->trDelta, result ); - result[2] -= DEFAULT_GRAVITY * deltaTime; // FIXME: local gravity... - break; - default: - Com_Error( ERR_DROP, "BG_EvaluateTrajectoryDelta: unknown trType: %i", tr->trTime ); - break; - } -} - -char *eventnames[] = { - "EV_NONE", - - "EV_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_PAD", // boing sound at origin", jump sound on player - - "EV_JUMP", - "EV_WATER_TOUCH", // foot touches - "EV_WATER_LEAVE", // foot leaves - "EV_WATER_UNDER", // head touches - "EV_WATER_CLEAR", // head leaves - - "EV_ITEM_PICKUP", // normal item pickups are predictable - "EV_GLOBAL_ITEM_PICKUP", // powerup / team sounds are broadcast to everyone - - "EV_NOAMMO", - "EV_CHANGE_WEAPON", - "EV_FIRE_WEAPON", - - "EV_USE_ITEM0", - "EV_USE_ITEM1", - "EV_USE_ITEM2", - "EV_USE_ITEM3", - "EV_USE_ITEM4", - "EV_USE_ITEM5", - "EV_USE_ITEM6", - "EV_USE_ITEM7", - "EV_USE_ITEM8", - "EV_USE_ITEM9", - "EV_USE_ITEM10", - "EV_USE_ITEM11", - "EV_USE_ITEM12", - "EV_USE_ITEM13", - "EV_USE_ITEM14", - "EV_USE_ITEM15", - - "EV_ITEM_RESPAWN", - "EV_ITEM_POP", - "EV_PLAYER_TELEPORT_IN", - "EV_PLAYER_TELEPORT_OUT", - - "EV_GRENADE_BOUNCE", // eventParm will be the soundindex - - "EV_GENERAL_SOUND", - "EV_GLOBAL_SOUND", // no attenuation - "EV_GLOBAL_TEAM_SOUND", - - "EV_BULLET_HIT_FLESH", - "EV_BULLET_HIT_WALL", - - "EV_MISSILE_HIT", - "EV_MISSILE_MISS", - "EV_MISSILE_MISS_METAL", - "EV_RAILTRAIL", - "EV_SHOTGUN", - "EV_BULLET", // otherEntity is the shooter - - "EV_PAIN", - "EV_DEATH1", - "EV_DEATH2", - "EV_DEATH3", - "EV_OBITUARY", - - "EV_POWERUP_QUAD", - "EV_POWERUP_BATTLESUIT", - "EV_POWERUP_REGEN", - - "EV_GIB_PLAYER", // gib a previously living player - "EV_SCOREPLUM", // score plum - -//#ifdef MISSIONPACK - "EV_PROXIMITY_MINE_STICK", - "EV_PROXIMITY_MINE_TRIGGER", - "EV_KAMIKAZE", // kamikaze explodes - "EV_OBELISKEXPLODE", // obelisk explodes - "EV_INVUL_IMPACT", // invulnerability sphere impact - "EV_JUICED", // invulnerability juiced effect - "EV_LIGHTNINGBOLT", // lightning bolt bounced of invulnerability sphere -//#endif - - "EV_DEBUG_LINE", - "EV_STOPLOOPINGSOUND", - "EV_TAUNT" - -}; - -/* -=============== -BG_AddPredictableEventToPlayerstate - -Handles the sequence numbers -=============== -*/ - -void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ); - -void BG_AddPredictableEventToPlayerstate( int newEvent, int eventParm, playerState_t *ps ) { - -#ifdef _DEBUG - { - char buf[256]; - trap_Cvar_VariableStringBuffer("showevents", buf, sizeof(buf)); - if ( atof(buf) != 0 ) { -#ifdef QAGAME - Com_Printf(" game event svt %5d -> %5d: num = %20s parm %d\n", ps->pmove_framecount/*ps->commandTime*/, ps->eventSequence, eventnames[newEvent], eventParm); -#else - Com_Printf("Cgame event svt %5d -> %5d: num = %20s parm %d\n", ps->pmove_framecount/*ps->commandTime*/, ps->eventSequence, eventnames[newEvent], eventParm); -#endif - } - } -#endif - ps->events[ps->eventSequence & (MAX_PS_EVENTS-1)] = newEvent; - ps->eventParms[ps->eventSequence & (MAX_PS_EVENTS-1)] = eventParm; - ps->eventSequence++; -} - -/* -======================== -BG_TouchJumpPad -======================== -*/ -void BG_TouchJumpPad( playerState_t *ps, entityState_t *jumppad ) { - vec3_t angles; - float p; - int effectNum; - - // spectators don't use jump pads - if ( ps->pm_type != PM_NORMAL ) { - return; - } - - // flying characters don't hit bounce pads - if ( ps->powerups[PW_FLIGHT] ) { - return; - } - - // if we didn't hit this same jumppad the previous frame - // then don't play the event sound again if we are in a fat trigger - if ( ps->jumppad_ent != jumppad->number ) { - - vectoangles( jumppad->origin2, angles); - p = fabs( AngleNormalize180( angles[PITCH] ) ); - if( p < 45 ) { - effectNum = 0; - } else { - effectNum = 1; - } - BG_AddPredictableEventToPlayerstate( EV_JUMP_PAD, effectNum, ps ); - } - // remember hitting this jumppad this frame - ps->jumppad_ent = jumppad->number; - ps->jumppad_frame = ps->pmove_framecount; - // give the player the velocity from the jumppad - VectorCopy( jumppad->origin2, ps->velocity ); -} - -/* -======================== -BG_PlayerStateToEntityState - -This is done after each set of usercmd_t on the server, -and after local prediction on the client -======================== -*/ -void BG_PlayerStateToEntityState( playerState_t *ps, entityState_t *s, qboolean snap ) { - int i; - - if ( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPECTATOR ) { - s->eType = ET_INVISIBLE; - } else if ( ps->stats[STAT_HEALTH] <= GIB_HEALTH ) { - s->eType = ET_INVISIBLE; - } else { - s->eType = ET_PLAYER; - } - - s->number = ps->clientNum; - - s->pos.trType = TR_INTERPOLATE; - VectorCopy( ps->origin, s->pos.trBase ); - if ( snap ) { - SnapVector( s->pos.trBase ); - } - // set the trDelta for flag direction - VectorCopy( ps->velocity, s->pos.trDelta ); - - s->apos.trType = TR_INTERPOLATE; - VectorCopy( ps->viewangles, s->apos.trBase ); - if ( snap ) { - SnapVector( s->apos.trBase ); - } - - s->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; - if ( ps->stats[STAT_HEALTH] <= 0 ) { - s->eFlags |= EF_DEAD; - } else { - s->eFlags &= ~EF_DEAD; - } - - if ( ps->externalEvent ) { - s->event = ps->externalEvent; - s->eventParm = ps->externalEventParm; - } else if ( ps->entityEventSequence < ps->eventSequence ) { - int seq; - - if ( ps->entityEventSequence < ps->eventSequence - MAX_PS_EVENTS) { - ps->entityEventSequence = ps->eventSequence - MAX_PS_EVENTS; - } - seq = ps->entityEventSequence & (MAX_PS_EVENTS-1); - s->event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 ); - s->eventParm = ps->eventParms[ seq ]; - ps->entityEventSequence++; - } - - s->weapon = ps->weapon; - s->groundEntityNum = ps->groundEntityNum; - - s->powerups = 0; - for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { - if ( ps->powerups[ i ] ) { - s->powerups |= 1 << i; - } - } - - s->loopSound = ps->loopSound; - s->generic1 = ps->generic1; -} - -/* -======================== -BG_PlayerStateToEntityStateExtraPolate - -This is done after each set of usercmd_t on the server, -and after local prediction on the client -======================== -*/ -void BG_PlayerStateToEntityStateExtraPolate( playerState_t *ps, entityState_t *s, int time, qboolean snap ) { - int i; - - if ( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPECTATOR ) { - s->eType = ET_INVISIBLE; - } else if ( ps->stats[STAT_HEALTH] <= GIB_HEALTH ) { - s->eType = ET_INVISIBLE; - } else { - s->eType = ET_PLAYER; - } - - s->number = ps->clientNum; - - s->pos.trType = TR_LINEAR_STOP; - VectorCopy( ps->origin, s->pos.trBase ); - if ( snap ) { - SnapVector( s->pos.trBase ); - } - // set the trDelta for flag direction and linear prediction - VectorCopy( ps->velocity, s->pos.trDelta ); - // set the time for linear prediction - s->pos.trTime = time; - // set maximum extra polation time - s->pos.trDuration = 50; // 1000 / sv_fps (default = 20) - - s->apos.trType = TR_INTERPOLATE; - VectorCopy( ps->viewangles, s->apos.trBase ); - if ( snap ) { - SnapVector( s->apos.trBase ); - } - - s->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; - if ( ps->stats[STAT_HEALTH] <= 0 ) { - s->eFlags |= EF_DEAD; - } else { - s->eFlags &= ~EF_DEAD; - } - - if ( ps->externalEvent ) { - s->event = ps->externalEvent; - s->eventParm = ps->externalEventParm; - } else if ( ps->entityEventSequence < ps->eventSequence ) { - int seq; - - if ( ps->entityEventSequence < ps->eventSequence - MAX_PS_EVENTS) { - ps->entityEventSequence = ps->eventSequence - MAX_PS_EVENTS; - } - seq = ps->entityEventSequence & (MAX_PS_EVENTS-1); - s->event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 ); - s->eventParm = ps->eventParms[ seq ]; - ps->entityEventSequence++; - } - - s->weapon = ps->weapon; - s->groundEntityNum = ps->groundEntityNum; - - s->powerups = 0; - for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { - if ( ps->powerups[ i ] ) { - s->powerups |= 1 << i; - } - } - - s->loopSound = ps->loopSound; - s->generic1 = ps->generic1; -} +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// bg_misc.c -- both games misc functions, all completely stateless + +#include "q_shared.h" +#include "bg_public.h" + +/*QUAKED item_***** ( 0 0 0 ) (-16 -16 -16) (16 16 16) suspended +DO NOT USE THIS CLASS, IT JUST HOLDS GENERAL INFORMATION. +The suspended flag will allow items to hang in the air, otherwise they are dropped to the next surface. + +If an item is the target of another entity, it will not spawn in until fired. + +An item fires all of its targets when it is picked up. If the toucher can't carry it, the targets won't be fired. + +"notfree" if set to 1, don't spawn in free for all games +"notteam" if set to 1, don't spawn in team games +"notsingle" if set to 1, don't spawn in single player games +"wait" override the default wait before respawning. -1 = never respawn automatically, which can be used with targeted spawning. +"random" random number of plus or minus seconds varied from the respawn time +"count" override quantity or duration on most items. +*/ + +gitem_t bg_itemlist[] = +{ + { + NULL, + NULL, + { NULL, + NULL, + 0, 0} , +/* icon */ NULL, +/* pickup */ NULL, + 0, + 0, + 0, +/* precache */ "", +/* sounds */ "" + }, // leave index 0 alone + + // + // ARMOR + // + +/*QUAKED item_armor_shard (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "item_armor_shard", + "sound/misc/ar1_pkup.wav", + { "models/powerups/armor/shard.md3", + "models/powerups/armor/shard_sphere.md3", + 0, 0} , +/* icon */ "icons/iconr_shard", +/* pickup */ "Armor Shard", + 5, + IT_ARMOR, + 0, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED item_armor_combat (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "item_armor_combat", + "sound/misc/ar2_pkup.wav", + { "models/powerups/armor/armor_yel.md3", + 0, 0, 0}, +/* icon */ "icons/iconr_yellow", +/* pickup */ "Armor", + 50, + IT_ARMOR, + 0, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED item_armor_body (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "item_armor_body", + "sound/misc/ar2_pkup.wav", + { "models/powerups/armor/armor_red.md3", + 0, 0, 0}, +/* icon */ "icons/iconr_red", +/* pickup */ "Heavy Armor", + 100, + IT_ARMOR, + 0, +/* precache */ "", +/* sounds */ "" + }, + + // + // health + // +/*QUAKED item_health_small (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "item_health_small", + "sound/items/s_health.wav", + { "models/powerups/health/small_cross.md3", + "models/powerups/health/small_sphere.md3", + 0, 0 }, +/* icon */ "icons/iconh_green", +/* pickup */ "5 Health", + 5, + IT_HEALTH, + 0, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED item_health (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "item_health", + "sound/items/n_health.wav", + { "models/powerups/health/medium_cross.md3", + "models/powerups/health/medium_sphere.md3", + 0, 0 }, +/* icon */ "icons/iconh_yellow", +/* pickup */ "25 Health", + 25, + IT_HEALTH, + 0, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED item_health_large (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "item_health_large", + "sound/items/l_health.wav", + { "models/powerups/health/large_cross.md3", + "models/powerups/health/large_sphere.md3", + 0, 0 }, +/* icon */ "icons/iconh_red", +/* pickup */ "50 Health", + 50, + IT_HEALTH, + 0, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED item_health_mega (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "item_health_mega", + "sound/items/m_health.wav", + { "models/powerups/health/mega_cross.md3", + "models/powerups/health/mega_sphere.md3", + 0, 0 }, +/* icon */ "icons/iconh_mega", +/* pickup */ "Mega Health", + 100, + IT_HEALTH, + 0, +/* precache */ "", +/* sounds */ "" + }, + + + // + // WEAPONS + // + +/*QUAKED weapon_gauntlet (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_gauntlet", + "sound/misc/w_pkup.wav", + { "models/weapons2/gauntlet/gauntlet.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_gauntlet", +/* pickup */ "Gauntlet", + 0, + IT_WEAPON, + WP_GAUNTLET, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_shotgun (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_shotgun", + "sound/misc/w_pkup.wav", + { "models/weapons2/shotgun/shotgun.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_shotgun", +/* pickup */ "Shotgun", + 10, + IT_WEAPON, + WP_SHOTGUN, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_machinegun (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_machinegun", + "sound/misc/w_pkup.wav", + { "models/weapons2/machinegun/machinegun.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_machinegun", +/* pickup */ "Machinegun", + 40, + IT_WEAPON, + WP_MACHINEGUN, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_grenadelauncher (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_grenadelauncher", + "sound/misc/w_pkup.wav", + { "models/weapons2/grenadel/grenadel.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_grenade", +/* pickup */ "Grenade Launcher", + 10, + IT_WEAPON, + WP_GRENADE_LAUNCHER, +/* precache */ "", +/* sounds */ "sound/weapons/grenade/hgrenb1a.wav sound/weapons/grenade/hgrenb2a.wav" + }, + +/*QUAKED weapon_rocketlauncher (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_rocketlauncher", + "sound/misc/w_pkup.wav", + { "models/weapons2/rocketl/rocketl.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_rocket", +/* pickup */ "Rocket Launcher", + 10, + IT_WEAPON, + WP_ROCKET_LAUNCHER, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_lightning (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_lightning", + "sound/misc/w_pkup.wav", + { "models/weapons2/lightning/lightning.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_lightning", +/* pickup */ "Lightning Gun", + 100, + IT_WEAPON, + WP_LIGHTNING, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_railgun (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_railgun", + "sound/misc/w_pkup.wav", + { "models/weapons2/railgun/railgun.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_railgun", +/* pickup */ "Railgun", + 10, + IT_WEAPON, + WP_RAILGUN, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_plasmagun (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_plasmagun", + "sound/misc/w_pkup.wav", + { "models/weapons2/plasma/plasma.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_plasma", +/* pickup */ "Plasma Gun", + 50, + IT_WEAPON, + WP_PLASMAGUN, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_bfg (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_bfg", + "sound/misc/w_pkup.wav", + { "models/weapons2/bfg/bfg.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_bfg", +/* pickup */ "BFG10K", + 20, + IT_WEAPON, + WP_BFG, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_grapplinghook (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_grapplinghook", + "sound/misc/w_pkup.wav", + { "models/weapons2/grapple/grapple.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_grapple", +/* pickup */ "Grappling Hook", + 0, + IT_WEAPON, + WP_GRAPPLING_HOOK, +/* precache */ "", +/* sounds */ "" + }, + + // + // AMMO ITEMS + // + +/*QUAKED ammo_shells (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "ammo_shells", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/shotgunam.md3", + 0, 0, 0}, +/* icon */ "icons/icona_shotgun", +/* pickup */ "Shells", + 10, + IT_AMMO, + WP_SHOTGUN, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED ammo_bullets (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "ammo_bullets", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/machinegunam.md3", + 0, 0, 0}, +/* icon */ "icons/icona_machinegun", +/* pickup */ "Bullets", + 50, + IT_AMMO, + WP_MACHINEGUN, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED ammo_grenades (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "ammo_grenades", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/grenadeam.md3", + 0, 0, 0}, +/* icon */ "icons/icona_grenade", +/* pickup */ "Grenades", + 5, + IT_AMMO, + WP_GRENADE_LAUNCHER, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED ammo_cells (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "ammo_cells", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/plasmaam.md3", + 0, 0, 0}, +/* icon */ "icons/icona_plasma", +/* pickup */ "Cells", + 30, + IT_AMMO, + WP_PLASMAGUN, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED ammo_lightning (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "ammo_lightning", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/lightningam.md3", + 0, 0, 0}, +/* icon */ "icons/icona_lightning", +/* pickup */ "Lightning", + 60, + IT_AMMO, + WP_LIGHTNING, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED ammo_rockets (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "ammo_rockets", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/rocketam.md3", + 0, 0, 0}, +/* icon */ "icons/icona_rocket", +/* pickup */ "Rockets", + 5, + IT_AMMO, + WP_ROCKET_LAUNCHER, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED ammo_slugs (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "ammo_slugs", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/railgunam.md3", + 0, 0, 0}, +/* icon */ "icons/icona_railgun", +/* pickup */ "Slugs", + 10, + IT_AMMO, + WP_RAILGUN, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED ammo_bfg (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "ammo_bfg", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/bfgam.md3", + 0, 0, 0}, +/* icon */ "icons/icona_bfg", +/* pickup */ "Bfg Ammo", + 15, + IT_AMMO, + WP_BFG, +/* precache */ "", +/* sounds */ "" + }, + + // + // HOLDABLE ITEMS + // +/*QUAKED holdable_teleporter (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "holdable_teleporter", + "sound/items/holdable.wav", + { "models/powerups/holdable/teleporter.md3", + 0, 0, 0}, +/* icon */ "icons/teleporter", +/* pickup */ "Personal Teleporter", + 60, + IT_HOLDABLE, + HI_TELEPORTER, +/* precache */ "", +/* sounds */ "" + }, +/*QUAKED holdable_medkit (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "holdable_medkit", + "sound/items/holdable.wav", + { + "models/powerups/holdable/medkit.md3", + "models/powerups/holdable/medkit_sphere.md3", + 0, 0}, +/* icon */ "icons/medkit", +/* pickup */ "Medkit", + 60, + IT_HOLDABLE, + HI_MEDKIT, +/* precache */ "", +/* sounds */ "sound/items/use_medkit.wav" + }, + + // + // POWERUP ITEMS + // +/*QUAKED item_quad (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "item_quad", + "sound/items/quaddamage.wav", + { "models/powerups/instant/quad.md3", + "models/powerups/instant/quad_ring.md3", + 0, 0 }, +/* icon */ "icons/quad", +/* pickup */ "Quad Damage", + 30, + IT_POWERUP, + PW_QUAD, +/* precache */ "", +/* sounds */ "sound/items/damage2.wav sound/items/damage3.wav" + }, + +/*QUAKED item_enviro (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "item_enviro", + "sound/items/protect.wav", + { "models/powerups/instant/enviro.md3", + "models/powerups/instant/enviro_ring.md3", + 0, 0 }, +/* icon */ "icons/envirosuit", +/* pickup */ "Battle Suit", + 30, + IT_POWERUP, + PW_BATTLESUIT, +/* precache */ "", +/* sounds */ "sound/items/airout.wav sound/items/protect3.wav" + }, + +/*QUAKED item_haste (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "item_haste", + "sound/items/haste.wav", + { "models/powerups/instant/haste.md3", + "models/powerups/instant/haste_ring.md3", + 0, 0 }, +/* icon */ "icons/haste", +/* pickup */ "Speed", + 30, + IT_POWERUP, + PW_HASTE, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED item_invis (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "item_invis", + "sound/items/invisibility.wav", + { "models/powerups/instant/invis.md3", + "models/powerups/instant/invis_ring.md3", + 0, 0 }, +/* icon */ "icons/invis", +/* pickup */ "Invisibility", + 30, + IT_POWERUP, + PW_INVIS, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED item_regen (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "item_regen", + "sound/items/regeneration.wav", + { "models/powerups/instant/regen.md3", + "models/powerups/instant/regen_ring.md3", + 0, 0 }, +/* icon */ "icons/regen", +/* pickup */ "Regeneration", + 30, + IT_POWERUP, + PW_REGEN, +/* precache */ "", +/* sounds */ "sound/items/regen.wav" + }, + +/*QUAKED item_flight (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "item_flight", + "sound/items/flight.wav", + { "models/powerups/instant/flight.md3", + "models/powerups/instant/flight_ring.md3", + 0, 0 }, +/* icon */ "icons/flight", +/* pickup */ "Flight", + 60, + IT_POWERUP, + PW_FLIGHT, +/* precache */ "", +/* sounds */ "sound/items/flight.wav" + }, + +/*QUAKED team_CTF_redflag (1 0 0) (-16 -16 -16) (16 16 16) +Only in CTF games +*/ + { + "team_CTF_redflag", + NULL, + { "models/flags/r_flag.md3", + 0, 0, 0 }, +/* icon */ "icons/iconf_red1", +/* pickup */ "Red Flag", + 0, + IT_TEAM, + PW_REDFLAG, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED team_CTF_blueflag (0 0 1) (-16 -16 -16) (16 16 16) +Only in CTF games +*/ + { + "team_CTF_blueflag", + NULL, + { "models/flags/b_flag.md3", + 0, 0, 0 }, +/* icon */ "icons/iconf_blu1", +/* pickup */ "Blue Flag", + 0, + IT_TEAM, + PW_BLUEFLAG, +/* precache */ "", +/* sounds */ "" + }, + +#ifdef MISSIONPACK +/*QUAKED holdable_kamikaze (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "holdable_kamikaze", + "sound/items/holdable.wav", + { "models/powerups/kamikazi.md3", + 0, 0, 0}, +/* icon */ "icons/kamikaze", +/* pickup */ "Kamikaze", + 60, + IT_HOLDABLE, + HI_KAMIKAZE, +/* precache */ "", +/* sounds */ "sound/items/kamikazerespawn.wav" + }, + +/*QUAKED holdable_portal (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "holdable_portal", + "sound/items/holdable.wav", + { "models/powerups/holdable/porter.md3", + 0, 0, 0}, +/* icon */ "icons/portal", +/* pickup */ "Portal", + 60, + IT_HOLDABLE, + HI_PORTAL, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED holdable_invulnerability (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "holdable_invulnerability", + "sound/items/holdable.wav", + { "models/powerups/holdable/invulnerability.md3", + 0, 0, 0}, +/* icon */ "icons/invulnerability", +/* pickup */ "Invulnerability", + 60, + IT_HOLDABLE, + HI_INVULNERABILITY, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED ammo_nails (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "ammo_nails", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/nailgunam.md3", + 0, 0, 0}, +/* icon */ "icons/icona_nailgun", +/* pickup */ "Nails", + 20, + IT_AMMO, + WP_NAILGUN, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED ammo_mines (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "ammo_mines", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/proxmineam.md3", + 0, 0, 0}, +/* icon */ "icons/icona_proxlauncher", +/* pickup */ "Proximity Mines", + 10, + IT_AMMO, + WP_PROX_LAUNCHER, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED ammo_belt (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "ammo_belt", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/chaingunam.md3", + 0, 0, 0}, +/* icon */ "icons/icona_chaingun", +/* pickup */ "Chaingun Belt", + 100, + IT_AMMO, + WP_CHAINGUN, +/* precache */ "", +/* sounds */ "" + }, + + // + // PERSISTANT POWERUP ITEMS + // +/*QUAKED item_scout (.3 .3 1) (-16 -16 -16) (16 16 16) suspended redTeam blueTeam +*/ + { + "item_scout", + "sound/items/scout.wav", + { "models/powerups/scout.md3", + 0, 0, 0 }, +/* icon */ "icons/scout", +/* pickup */ "Scout", + 30, + IT_PERSISTANT_POWERUP, + PW_SCOUT, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED item_guard (.3 .3 1) (-16 -16 -16) (16 16 16) suspended redTeam blueTeam +*/ + { + "item_guard", + "sound/items/guard.wav", + { "models/powerups/guard.md3", + 0, 0, 0 }, +/* icon */ "icons/guard", +/* pickup */ "Guard", + 30, + IT_PERSISTANT_POWERUP, + PW_GUARD, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED item_doubler (.3 .3 1) (-16 -16 -16) (16 16 16) suspended redTeam blueTeam +*/ + { + "item_doubler", + "sound/items/doubler.wav", + { "models/powerups/doubler.md3", + 0, 0, 0 }, +/* icon */ "icons/doubler", +/* pickup */ "Doubler", + 30, + IT_PERSISTANT_POWERUP, + PW_DOUBLER, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED item_doubler (.3 .3 1) (-16 -16 -16) (16 16 16) suspended redTeam blueTeam +*/ + { + "item_ammoregen", + "sound/items/ammoregen.wav", + { "models/powerups/ammo.md3", + 0, 0, 0 }, +/* icon */ "icons/ammo_regen", +/* pickup */ "Ammo Regen", + 30, + IT_PERSISTANT_POWERUP, + PW_AMMOREGEN, +/* precache */ "", +/* sounds */ "" + }, + + /*QUAKED team_CTF_neutralflag (0 0 1) (-16 -16 -16) (16 16 16) +Only in One Flag CTF games +*/ + { + "team_CTF_neutralflag", + NULL, + { "models/flags/n_flag.md3", + 0, 0, 0 }, +/* icon */ "icons/iconf_neutral1", +/* pickup */ "Neutral Flag", + 0, + IT_TEAM, + PW_NEUTRALFLAG, +/* precache */ "", +/* sounds */ "" + }, + + { + "item_redcube", + "sound/misc/am_pkup.wav", + { "models/powerups/orb/r_orb.md3", + 0, 0, 0 }, +/* icon */ "icons/iconh_rorb", +/* pickup */ "Red Cube", + 0, + IT_TEAM, + 0, +/* precache */ "", +/* sounds */ "" + }, + + { + "item_bluecube", + "sound/misc/am_pkup.wav", + { "models/powerups/orb/b_orb.md3", + 0, 0, 0 }, +/* icon */ "icons/iconh_borb", +/* pickup */ "Blue Cube", + 0, + IT_TEAM, + 0, +/* precache */ "", +/* sounds */ "" + }, +/*QUAKED weapon_nailgun (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_nailgun", + "sound/misc/w_pkup.wav", + { "models/weapons/nailgun/nailgun.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_nailgun", +/* pickup */ "Nailgun", + 10, + IT_WEAPON, + WP_NAILGUN, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_prox_launcher (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_prox_launcher", + "sound/misc/w_pkup.wav", + { "models/weapons/proxmine/proxmine.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_proxlauncher", +/* pickup */ "Prox Launcher", + 5, + IT_WEAPON, + WP_PROX_LAUNCHER, +/* precache */ "", +/* sounds */ "sound/weapons/proxmine/wstbtick.wav " + "sound/weapons/proxmine/wstbactv.wav " + "sound/weapons/proxmine/wstbimpl.wav " + "sound/weapons/proxmine/wstbimpm.wav " + "sound/weapons/proxmine/wstbimpd.wav " + "sound/weapons/proxmine/wstbactv.wav" + }, + +/*QUAKED weapon_chaingun (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_chaingun", + "sound/misc/w_pkup.wav", + { "models/weapons/vulcan/vulcan.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_chaingun", +/* pickup */ "Chaingun", + 80, + IT_WEAPON, + WP_CHAINGUN, +/* precache */ "", +/* sounds */ "sound/weapons/vulcan/wvulwind.wav" + }, +#endif + + // end of list marker + {NULL} +}; + +int bg_numItems = sizeof(bg_itemlist) / sizeof(bg_itemlist[0]) - 1; + + +/* +============== +BG_FindItemForPowerup +============== +*/ +gitem_t *BG_FindItemForPowerup( powerup_t pw ) { + int i; + + for ( i = 0 ; i < bg_numItems ; i++ ) { + if ( (bg_itemlist[i].giType == IT_POWERUP || + bg_itemlist[i].giType == IT_TEAM || + bg_itemlist[i].giType == IT_PERSISTANT_POWERUP) && + bg_itemlist[i].giTag == pw ) { + return &bg_itemlist[i]; + } + } + + return NULL; +} + + +/* +============== +BG_FindItemForHoldable +============== +*/ +gitem_t *BG_FindItemForHoldable( holdable_t pw ) { + int i; + + for ( i = 0 ; i < bg_numItems ; i++ ) { + if ( bg_itemlist[i].giType == IT_HOLDABLE && bg_itemlist[i].giTag == pw ) { + return &bg_itemlist[i]; + } + } + + Com_Error( ERR_DROP, "HoldableItem not found" ); + + return NULL; +} + + +/* +=============== +BG_FindItemForWeapon + +=============== +*/ +gitem_t *BG_FindItemForWeapon( weapon_t weapon ) { + gitem_t *it; + + for ( it = bg_itemlist + 1 ; it->classname ; it++) { + if ( it->giType == IT_WEAPON && it->giTag == weapon ) { + return it; + } + } + + Com_Error( ERR_DROP, "Couldn't find item for weapon %i", weapon); + return NULL; +} + +/* +=============== +BG_FindItem + +=============== +*/ +gitem_t *BG_FindItem( const char *pickupName ) { + gitem_t *it; + + for ( it = bg_itemlist + 1 ; it->classname ; it++ ) { + if ( !Q_stricmp( it->pickup_name, pickupName ) ) + return it; + } + + return NULL; +} + +/* +============ +BG_PlayerTouchesItem + +Items can be picked up without actually touching their physical bounds to make +grabbing them easier +============ +*/ +qboolean BG_PlayerTouchesItem( playerState_t *ps, entityState_t *item, int atTime ) { + vec3_t origin; + + BG_EvaluateTrajectory( &item->pos, atTime, origin ); + + // we are ignoring ducked differences here + if ( ps->origin[0] - origin[0] > 44 + || ps->origin[0] - origin[0] < -50 + || ps->origin[1] - origin[1] > 36 + || ps->origin[1] - origin[1] < -36 + || ps->origin[2] - origin[2] > 36 + || ps->origin[2] - origin[2] < -36 ) { + return qfalse; + } + + return qtrue; +} + + + +/* +================ +BG_CanItemBeGrabbed + +Returns false if the item should not be picked up. +This needs to be the same for client side prediction and server use. +================ +*/ +qboolean BG_CanItemBeGrabbed( int gametype, const entityState_t *ent, const playerState_t *ps ) { + gitem_t *item; +#ifdef MISSIONPACK + int upperBound; +#endif + + 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: + return qtrue; // weapons are always picked up + + case IT_AMMO: + if ( ps->ammo[ item->giTag ] >= 200 ) { + return qfalse; // can't hold any more + } + return qtrue; + + case IT_ARMOR: +#ifdef MISSIONPACK + if( bg_itemlist[ps->stats[STAT_PERSISTANT_POWERUP]].giTag == PW_SCOUT ) { + return qfalse; + } + + // we also clamp armor to the maxhealth for handicapping + if( bg_itemlist[ps->stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) { + upperBound = ps->stats[STAT_MAX_HEALTH]; + } + else { + upperBound = ps->stats[STAT_MAX_HEALTH] * 2; + } + + if ( ps->stats[STAT_ARMOR] >= upperBound ) { + return qfalse; + } +#else + if ( ps->stats[STAT_ARMOR] >= ps->stats[STAT_MAX_HEALTH] * 2 ) { + return qfalse; + } +#endif + return qtrue; + + case IT_HEALTH: + // small and mega healths will go over the max, otherwise + // don't pick up if already at max +#ifdef MISSIONPACK + if( bg_itemlist[ps->stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) { + upperBound = ps->stats[STAT_MAX_HEALTH]; + } + else +#endif + if ( item->quantity == 5 || item->quantity == 100 ) { + if ( ps->stats[STAT_HEALTH] >= ps->stats[STAT_MAX_HEALTH] * 2 ) { + return qfalse; + } + return qtrue; + } + + if ( ps->stats[STAT_HEALTH] >= ps->stats[STAT_MAX_HEALTH] ) { + return qfalse; + } + return qtrue; + + case IT_POWERUP: + return qtrue; // powerups are always picked up + +#ifdef MISSIONPACK + case IT_PERSISTANT_POWERUP: + // can only hold one item at a time + if ( ps->stats[STAT_PERSISTANT_POWERUP] ) { + return qfalse; + } + + // check team only + if( ( ent->generic1 & 2 ) && ( ps->persistant[PERS_TEAM] != TEAM_RED ) ) { + return qfalse; + } + if( ( ent->generic1 & 4 ) && ( ps->persistant[PERS_TEAM] != TEAM_BLUE ) ) { + return qfalse; + } + + return qtrue; +#endif + + case IT_TEAM: // team items, such as flags +#ifdef MISSIONPACK + if( gametype == GT_1FCTF ) { + // neutral flag can always be picked up + if( item->giTag == PW_NEUTRALFLAG ) { + return qtrue; + } + if (ps->persistant[PERS_TEAM] == TEAM_RED) { + if (item->giTag == PW_BLUEFLAG && ps->powerups[PW_NEUTRALFLAG] ) { + return qtrue; + } + } else if (ps->persistant[PERS_TEAM] == TEAM_BLUE) { + if (item->giTag == PW_REDFLAG && ps->powerups[PW_NEUTRALFLAG] ) { + return qtrue; + } + } + } +#endif + if( gametype == GT_CTF ) { + // ent->modelindex2 is non-zero on items if they are dropped + // we need to know this because we can pick up our dropped flag (and return it) + // but we can't pick up our flag at base + if (ps->persistant[PERS_TEAM] == TEAM_RED) { + if (item->giTag == PW_BLUEFLAG || + (item->giTag == PW_REDFLAG && ent->modelindex2) || + (item->giTag == PW_REDFLAG && ps->powerups[PW_BLUEFLAG]) ) + return qtrue; + } else if (ps->persistant[PERS_TEAM] == TEAM_BLUE) { + if (item->giTag == PW_REDFLAG || + (item->giTag == PW_BLUEFLAG && ent->modelindex2) || + (item->giTag == PW_BLUEFLAG && ps->powerups[PW_REDFLAG]) ) + return qtrue; + } + } + +#ifdef MISSIONPACK + if( gametype == GT_HARVESTER ) { + return qtrue; + } +#endif + return qfalse; + + case IT_HOLDABLE: + // can only hold one item at a time + if ( ps->stats[STAT_HOLDABLE_ITEM] ) { + return qfalse; + } + return qtrue; + + case IT_BAD: + Com_Error( ERR_DROP, "BG_CanItemBeGrabbed: IT_BAD" ); + default: +#ifndef Q3_VM +#ifndef NDEBUG // bk0001204 + Com_Printf("BG_CanItemBeGrabbed: unknown enum %d\n", item->giType ); +#endif +#endif + break; + } + + return qfalse; +} + +//====================================================================== + +/* +================ +BG_EvaluateTrajectory + +================ +*/ +void BG_EvaluateTrajectory( const trajectory_t *tr, int atTime, vec3_t result ) { + float deltaTime; + float phase; + + switch( tr->trType ) { + case TR_STATIONARY: + case TR_INTERPOLATE: + VectorCopy( tr->trBase, result ); + break; + case TR_LINEAR: + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + break; + case TR_SINE: + deltaTime = ( atTime - tr->trTime ) / (float) tr->trDuration; + phase = sin( deltaTime * M_PI * 2 ); + VectorMA( tr->trBase, phase, tr->trDelta, result ); + break; + case TR_LINEAR_STOP: + if ( atTime > tr->trTime + tr->trDuration ) { + atTime = tr->trTime + tr->trDuration; + } + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + if ( deltaTime < 0 ) { + deltaTime = 0; + } + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + break; + case TR_GRAVITY: + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + result[2] -= 0.5 * DEFAULT_GRAVITY * deltaTime * deltaTime; // FIXME: local gravity... + break; + default: + Com_Error( ERR_DROP, "BG_EvaluateTrajectory: unknown trType: %i", tr->trTime ); + break; + } +} + +/* +================ +BG_EvaluateTrajectoryDelta + +For determining velocity at a given time +================ +*/ +void BG_EvaluateTrajectoryDelta( const trajectory_t *tr, int atTime, vec3_t result ) { + float deltaTime; + float phase; + + switch( tr->trType ) { + case TR_STATIONARY: + case TR_INTERPOLATE: + VectorClear( result ); + break; + case TR_LINEAR: + VectorCopy( tr->trDelta, result ); + break; + case TR_SINE: + deltaTime = ( atTime - tr->trTime ) / (float) tr->trDuration; + phase = cos( deltaTime * M_PI * 2 ); // derivative of sin = cos + phase *= 0.5; + VectorScale( tr->trDelta, phase, result ); + break; + case TR_LINEAR_STOP: + if ( atTime > tr->trTime + tr->trDuration ) { + VectorClear( result ); + return; + } + VectorCopy( tr->trDelta, result ); + break; + case TR_GRAVITY: + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorCopy( tr->trDelta, result ); + result[2] -= DEFAULT_GRAVITY * deltaTime; // FIXME: local gravity... + break; + default: + Com_Error( ERR_DROP, "BG_EvaluateTrajectoryDelta: unknown trType: %i", tr->trTime ); + break; + } +} + +char *eventnames[] = { + "EV_NONE", + + "EV_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_PAD", // boing sound at origin", jump sound on player + + "EV_JUMP", + "EV_WATER_TOUCH", // foot touches + "EV_WATER_LEAVE", // foot leaves + "EV_WATER_UNDER", // head touches + "EV_WATER_CLEAR", // head leaves + + "EV_ITEM_PICKUP", // normal item pickups are predictable + "EV_GLOBAL_ITEM_PICKUP", // powerup / team sounds are broadcast to everyone + + "EV_NOAMMO", + "EV_CHANGE_WEAPON", + "EV_FIRE_WEAPON", + + "EV_USE_ITEM0", + "EV_USE_ITEM1", + "EV_USE_ITEM2", + "EV_USE_ITEM3", + "EV_USE_ITEM4", + "EV_USE_ITEM5", + "EV_USE_ITEM6", + "EV_USE_ITEM7", + "EV_USE_ITEM8", + "EV_USE_ITEM9", + "EV_USE_ITEM10", + "EV_USE_ITEM11", + "EV_USE_ITEM12", + "EV_USE_ITEM13", + "EV_USE_ITEM14", + "EV_USE_ITEM15", + + "EV_ITEM_RESPAWN", + "EV_ITEM_POP", + "EV_PLAYER_TELEPORT_IN", + "EV_PLAYER_TELEPORT_OUT", + + "EV_GRENADE_BOUNCE", // eventParm will be the soundindex + + "EV_GENERAL_SOUND", + "EV_GLOBAL_SOUND", // no attenuation + "EV_GLOBAL_TEAM_SOUND", + + "EV_BULLET_HIT_FLESH", + "EV_BULLET_HIT_WALL", + + "EV_MISSILE_HIT", + "EV_MISSILE_MISS", + "EV_MISSILE_MISS_METAL", + "EV_RAILTRAIL", + "EV_SHOTGUN", + "EV_BULLET", // otherEntity is the shooter + + "EV_PAIN", + "EV_DEATH1", + "EV_DEATH2", + "EV_DEATH3", + "EV_OBITUARY", + + "EV_POWERUP_QUAD", + "EV_POWERUP_BATTLESUIT", + "EV_POWERUP_REGEN", + + "EV_GIB_PLAYER", // gib a previously living player + "EV_SCOREPLUM", // score plum + +//#ifdef MISSIONPACK + "EV_PROXIMITY_MINE_STICK", + "EV_PROXIMITY_MINE_TRIGGER", + "EV_KAMIKAZE", // kamikaze explodes + "EV_OBELISKEXPLODE", // obelisk explodes + "EV_INVUL_IMPACT", // invulnerability sphere impact + "EV_JUICED", // invulnerability juiced effect + "EV_LIGHTNINGBOLT", // lightning bolt bounced of invulnerability sphere +//#endif + + "EV_DEBUG_LINE", + "EV_STOPLOOPINGSOUND", + "EV_TAUNT" + +}; + +/* +=============== +BG_AddPredictableEventToPlayerstate + +Handles the sequence numbers +=============== +*/ + +void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ); + +void BG_AddPredictableEventToPlayerstate( int newEvent, int eventParm, playerState_t *ps ) { + +#ifdef _DEBUG + { + char buf[256]; + trap_Cvar_VariableStringBuffer("showevents", buf, sizeof(buf)); + if ( atof(buf) != 0 ) { +#ifdef QAGAME + Com_Printf(" game event svt %5d -> %5d: num = %20s parm %d\n", ps->pmove_framecount/*ps->commandTime*/, ps->eventSequence, eventnames[newEvent], eventParm); +#else + Com_Printf("Cgame event svt %5d -> %5d: num = %20s parm %d\n", ps->pmove_framecount/*ps->commandTime*/, ps->eventSequence, eventnames[newEvent], eventParm); +#endif + } + } +#endif + ps->events[ps->eventSequence & (MAX_PS_EVENTS-1)] = newEvent; + ps->eventParms[ps->eventSequence & (MAX_PS_EVENTS-1)] = eventParm; + ps->eventSequence++; +} + +/* +======================== +BG_TouchJumpPad +======================== +*/ +void BG_TouchJumpPad( playerState_t *ps, entityState_t *jumppad ) { + vec3_t angles; + float p; + int effectNum; + + // spectators don't use jump pads + if ( ps->pm_type != PM_NORMAL ) { + return; + } + + // flying characters don't hit bounce pads + if ( ps->powerups[PW_FLIGHT] ) { + return; + } + + // if we didn't hit this same jumppad the previous frame + // then don't play the event sound again if we are in a fat trigger + if ( ps->jumppad_ent != jumppad->number ) { + + vectoangles( jumppad->origin2, angles); + p = fabs( AngleNormalize180( angles[PITCH] ) ); + if( p < 45 ) { + effectNum = 0; + } else { + effectNum = 1; + } + BG_AddPredictableEventToPlayerstate( EV_JUMP_PAD, effectNum, ps ); + } + // remember hitting this jumppad this frame + ps->jumppad_ent = jumppad->number; + ps->jumppad_frame = ps->pmove_framecount; + // give the player the velocity from the jumppad + VectorCopy( jumppad->origin2, ps->velocity ); +} + +/* +======================== +BG_PlayerStateToEntityState + +This is done after each set of usercmd_t on the server, +and after local prediction on the client +======================== +*/ +void BG_PlayerStateToEntityState( playerState_t *ps, entityState_t *s, qboolean snap ) { + int i; + + if ( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPECTATOR ) { + s->eType = ET_INVISIBLE; + } else if ( ps->stats[STAT_HEALTH] <= GIB_HEALTH ) { + s->eType = ET_INVISIBLE; + } else { + s->eType = ET_PLAYER; + } + + s->number = ps->clientNum; + + s->pos.trType = TR_INTERPOLATE; + VectorCopy( ps->origin, s->pos.trBase ); + if ( snap ) { + SnapVector( s->pos.trBase ); + } + // set the trDelta for flag direction + VectorCopy( ps->velocity, s->pos.trDelta ); + + s->apos.trType = TR_INTERPOLATE; + VectorCopy( ps->viewangles, s->apos.trBase ); + if ( snap ) { + SnapVector( s->apos.trBase ); + } + + s->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; + if ( ps->stats[STAT_HEALTH] <= 0 ) { + s->eFlags |= EF_DEAD; + } else { + s->eFlags &= ~EF_DEAD; + } + + if ( ps->externalEvent ) { + s->event = ps->externalEvent; + s->eventParm = ps->externalEventParm; + } else if ( ps->entityEventSequence < ps->eventSequence ) { + int seq; + + if ( ps->entityEventSequence < ps->eventSequence - MAX_PS_EVENTS) { + ps->entityEventSequence = ps->eventSequence - MAX_PS_EVENTS; + } + seq = ps->entityEventSequence & (MAX_PS_EVENTS-1); + s->event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 ); + s->eventParm = ps->eventParms[ seq ]; + ps->entityEventSequence++; + } + + s->weapon = ps->weapon; + s->groundEntityNum = ps->groundEntityNum; + + s->powerups = 0; + for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { + if ( ps->powerups[ i ] ) { + s->powerups |= 1 << i; + } + } + + s->loopSound = ps->loopSound; + s->generic1 = ps->generic1; +} + +/* +======================== +BG_PlayerStateToEntityStateExtraPolate + +This is done after each set of usercmd_t on the server, +and after local prediction on the client +======================== +*/ +void BG_PlayerStateToEntityStateExtraPolate( playerState_t *ps, entityState_t *s, int time, qboolean snap ) { + int i; + + if ( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPECTATOR ) { + s->eType = ET_INVISIBLE; + } else if ( ps->stats[STAT_HEALTH] <= GIB_HEALTH ) { + s->eType = ET_INVISIBLE; + } else { + s->eType = ET_PLAYER; + } + + s->number = ps->clientNum; + + s->pos.trType = TR_LINEAR_STOP; + VectorCopy( ps->origin, s->pos.trBase ); + if ( snap ) { + SnapVector( s->pos.trBase ); + } + // set the trDelta for flag direction and linear prediction + VectorCopy( ps->velocity, s->pos.trDelta ); + // set the time for linear prediction + s->pos.trTime = time; + // set maximum extra polation time + s->pos.trDuration = 50; // 1000 / sv_fps (default = 20) + + s->apos.trType = TR_INTERPOLATE; + VectorCopy( ps->viewangles, s->apos.trBase ); + if ( snap ) { + SnapVector( s->apos.trBase ); + } + + s->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; + if ( ps->stats[STAT_HEALTH] <= 0 ) { + s->eFlags |= EF_DEAD; + } else { + s->eFlags &= ~EF_DEAD; + } + + if ( ps->externalEvent ) { + s->event = ps->externalEvent; + s->eventParm = ps->externalEventParm; + } else if ( ps->entityEventSequence < ps->eventSequence ) { + int seq; + + if ( ps->entityEventSequence < ps->eventSequence - MAX_PS_EVENTS) { + ps->entityEventSequence = ps->eventSequence - MAX_PS_EVENTS; + } + seq = ps->entityEventSequence & (MAX_PS_EVENTS-1); + s->event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 ); + s->eventParm = ps->eventParms[ seq ]; + ps->entityEventSequence++; + } + + s->weapon = ps->weapon; + s->groundEntityNum = ps->groundEntityNum; + + s->powerups = 0; + for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { + if ( ps->powerups[ i ] ) { + s->powerups |= 1 << i; + } + } + + s->loopSound = ps->loopSound; + s->generic1 = ps->generic1; +} diff --git a/code/game/bg_pmove.c b/code/game/bg_pmove.c index 3711869..f5f5c68 100755 --- a/code/game/bg_pmove.c +++ b/code/game/bg_pmove.c @@ -1,2069 +1,2069 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// bg_pmove.c -- both games player movement code -// takes a playerstate and a usercmd as input and returns a modifed playerstate - -#include "q_shared.h" -#include "bg_public.h" -#include "bg_local.h" - -pmove_t *pm; -pml_t pml; - -// movement parameters -float pm_stopspeed = 100.0f; -float pm_duckScale = 0.25f; -float pm_swimScale = 0.50f; -float pm_wadeScale = 0.70f; - -float pm_accelerate = 10.0f; -float pm_airaccelerate = 1.0f; -float pm_wateraccelerate = 4.0f; -float pm_flyaccelerate = 8.0f; - -float pm_friction = 6.0f; -float pm_waterfriction = 1.0f; -float pm_flightfriction = 3.0f; -float pm_spectatorfriction = 5.0f; - -int c_pmove = 0; - - -/* -=============== -PM_AddEvent - -=============== -*/ -void PM_AddEvent( int newEvent ) { - BG_AddPredictableEventToPlayerstate( newEvent, 0, pm->ps ); -} - -/* -=============== -PM_AddTouchEnt -=============== -*/ -void PM_AddTouchEnt( int entityNum ) { - int i; - - if ( entityNum == ENTITYNUM_WORLD ) { - return; - } - if ( pm->numtouch == MAXTOUCH ) { - return; - } - - // see if it is already added - for ( i = 0 ; i < pm->numtouch ; i++ ) { - if ( pm->touchents[ i ] == entityNum ) { - return; - } - } - - // add it - pm->touchents[pm->numtouch] = entityNum; - pm->numtouch++; -} - -/* -=================== -PM_StartTorsoAnim -=================== -*/ -static void PM_StartTorsoAnim( int anim ) { - if ( pm->ps->pm_type >= PM_DEAD ) { - return; - } - pm->ps->torsoAnim = ( ( pm->ps->torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) - | anim; -} -static void PM_StartLegsAnim( int anim ) { - if ( pm->ps->pm_type >= PM_DEAD ) { - return; - } - if ( pm->ps->legsTimer > 0 ) { - return; // a high priority animation is running - } - pm->ps->legsAnim = ( ( pm->ps->legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) - | anim; -} - -static void PM_ContinueLegsAnim( int anim ) { - if ( ( pm->ps->legsAnim & ~ANIM_TOGGLEBIT ) == anim ) { - return; - } - if ( pm->ps->legsTimer > 0 ) { - return; // a high priority animation is running - } - PM_StartLegsAnim( anim ); -} - -static void PM_ContinueTorsoAnim( int anim ) { - if ( ( pm->ps->torsoAnim & ~ANIM_TOGGLEBIT ) == anim ) { - return; - } - if ( pm->ps->torsoTimer > 0 ) { - return; // a high priority animation is running - } - PM_StartTorsoAnim( anim ); -} - -static void PM_ForceLegsAnim( int anim ) { - pm->ps->legsTimer = 0; - PM_StartLegsAnim( anim ); -} - - -/* -================== -PM_ClipVelocity - -Slide off of the impacting surface -================== -*/ -void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce ) { - float backoff; - float change; - 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; - } -} - - -/* -================== -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; - - 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 - if ( pm->waterlevel <= 1 ) { - if ( pml.walking && !(pml.groundTrace.surfaceFlags & SURF_SLICK) ) { - // if getting knocked back, no friction - if ( ! (pm->ps->pm_flags & PMF_TIME_KNOCKBACK) ) { - control = speed < pm_stopspeed ? pm_stopspeed : speed; - drop += control*pm_friction*pml.frametime; - } - } - } - - // apply water friction even if just wading - if ( pm->waterlevel ) { - drop += speed*pm_waterfriction*pm->waterlevel*pml.frametime; - } - - // apply flying friction - if ( pm->ps->powerups[PW_FLIGHT]) { - drop += speed*pm_flightfriction*pml.frametime; - } - - if ( pm->ps->pm_type == PM_SPECTATOR) { - drop += speed*pm_spectatorfriction*pml.frametime; - } - - // scale the velocity - newspeed = speed - drop; - if (newspeed < 0) { - newspeed = 0; - } - newspeed /= speed; - - vel[0] = vel[0] * newspeed; - vel[1] = vel[1] * newspeed; - vel[2] = vel[2] * newspeed; -} - - -/* -============== -PM_Accelerate - -Handles user intended acceleration -============== -*/ -static void PM_Accelerate( vec3_t wishdir, float wishspeed, float accel ) { -#if 1 - // q2 style - 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]; - } -#else - // proper way (avoids strafe jump maxspeed bug), but feels bad - vec3_t wishVelocity; - vec3_t pushDir; - float pushLen; - float canPush; - - VectorScale( wishdir, wishspeed, wishVelocity ); - VectorSubtract( wishVelocity, pm->ps->velocity, pushDir ); - pushLen = VectorNormalize( pushDir ); - - canPush = accel*pml.frametime*wishspeed; - if (canPush > pushLen) { - canPush = pushLen; - } - - VectorMA( pm->ps->velocity, canPush, pushDir, pm->ps->velocity ); -#endif -} - - - -/* -============ -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( cmd->forwardmove * cmd->forwardmove - + cmd->rightmove * cmd->rightmove + cmd->upmove * cmd->upmove ); - scale = (float)pm->ps->speed * max / ( 127.0 * total ); - - return scale; -} - - -/* -================ -PM_SetMovementDir - -Determine the rotation of the legs reletive -to the facing dir -================ -*/ -static void PM_SetMovementDir( void ) { - if ( pm->cmd.forwardmove || pm->cmd.rightmove ) { - if ( pm->cmd.rightmove == 0 && pm->cmd.forwardmove > 0 ) { - pm->ps->movementDir = 0; - } else if ( pm->cmd.rightmove < 0 && pm->cmd.forwardmove > 0 ) { - pm->ps->movementDir = 1; - } else if ( pm->cmd.rightmove < 0 && pm->cmd.forwardmove == 0 ) { - pm->ps->movementDir = 2; - } else if ( pm->cmd.rightmove < 0 && pm->cmd.forwardmove < 0 ) { - pm->ps->movementDir = 3; - } else if ( pm->cmd.rightmove == 0 && pm->cmd.forwardmove < 0 ) { - pm->ps->movementDir = 4; - } else if ( pm->cmd.rightmove > 0 && pm->cmd.forwardmove < 0 ) { - pm->ps->movementDir = 5; - } else if ( pm->cmd.rightmove > 0 && pm->cmd.forwardmove == 0 ) { - pm->ps->movementDir = 6; - } else if ( pm->cmd.rightmove > 0 && pm->cmd.forwardmove > 0 ) { - pm->ps->movementDir = 7; - } - } else { - // if they aren't actively going directly sideways, - // change the animation to the diagonal so they - // don't stop too crooked - if ( pm->ps->movementDir == 2 ) { - pm->ps->movementDir = 1; - } else if ( pm->ps->movementDir == 6 ) { - pm->ps->movementDir = 7; - } - } -} - - -/* -============= -PM_CheckJump -============= -*/ -static qboolean PM_CheckJump( void ) { - if ( pm->ps->pm_flags & PMF_RESPAWNED ) { - return qfalse; // don't allow jump until all buttons are up - } - - if ( pm->cmd.upmove < 10 ) { - // not holding jump - 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; - } - - pml.groundPlane = qfalse; // jumping away - pml.walking = qfalse; - pm->ps->pm_flags |= PMF_JUMP_HELD; - - pm->ps->groundEntityNum = ENTITYNUM_NONE; - pm->ps->velocity[2] = JUMP_VELOCITY; - PM_AddEvent( EV_JUMP ); - - if ( pm->cmd.forwardmove >= 0 ) { - PM_ForceLegsAnim( LEGS_JUMP ); - pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; - } else { - PM_ForceLegsAnim( LEGS_JUMPB ); - pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; - } - - return qtrue; -} - -/* -============= -PM_CheckWaterJump -============= -*/ -static qboolean PM_CheckWaterJump( void ) { - vec3_t spot; - int cont; - vec3_t flatforward; - - if (pm->ps->pm_time) { - return qfalse; - } - - // check for water jump - if ( pm->waterlevel != 2 ) { - return qfalse; - } - - flatforward[0] = pml.forward[0]; - flatforward[1] = pml.forward[1]; - flatforward[2] = 0; - VectorNormalize (flatforward); - - VectorMA (pm->ps->origin, 30, flatforward, spot); - spot[2] += 4; - cont = pm->pointcontents (spot, pm->ps->clientNum ); - if ( !(cont & CONTENTS_SOLID) ) { - return qfalse; - } - - spot[2] += 16; - cont = pm->pointcontents (spot, pm->ps->clientNum ); - if ( cont ) { - return qfalse; - } - - // jump out of water - VectorScale (pml.forward, 200, pm->ps->velocity); - pm->ps->velocity[2] = 350; - - pm->ps->pm_flags |= PMF_TIME_WATERJUMP; - pm->ps->pm_time = 2000; - - return qtrue; -} - -//============================================================================ - - -/* -=================== -PM_WaterJumpMove - -Flying out of the water -=================== -*/ -static void PM_WaterJumpMove( void ) { - // waterjump has no control, but falls - - PM_StepSlideMove( qtrue ); - - pm->ps->velocity[2] -= pm->ps->gravity * pml.frametime; - if (pm->ps->velocity[2] < 0) { - // cancel as soon as we are falling down again - pm->ps->pm_flags &= ~PMF_ALL_TIMES; - pm->ps->pm_time = 0; - } -} - -/* -=================== -PM_WaterMove - -=================== -*/ -static void PM_WaterMove( void ) { - int i; - vec3_t wishvel; - float wishspeed; - vec3_t wishdir; - float scale; - float vel; - - if ( PM_CheckWaterJump() ) { - PM_WaterJumpMove(); - return; - } -#if 0 - // jump = head for surface - if ( pm->cmd.upmove >= 10 ) { - if (pm->ps->velocity[2] > -300) { - if ( pm->watertype == CONTENTS_WATER ) { - pm->ps->velocity[2] = 100; - } else if (pm->watertype == CONTENTS_SLIME) { - pm->ps->velocity[2] = 80; - } else { - pm->ps->velocity[2] = 50; - } - } - } -#endif - PM_Friction (); - - scale = PM_CmdScale( &pm->cmd ); - // - // user intentions - // - if ( !scale ) { - wishvel[0] = 0; - wishvel[1] = 0; - wishvel[2] = -60; // sink towards bottom - } else { - for (i=0 ; i<3 ; i++) - wishvel[i] = scale * pml.forward[i]*pm->cmd.forwardmove + scale * pml.right[i]*pm->cmd.rightmove; - - wishvel[2] += scale * pm->cmd.upmove; - } - - VectorCopy (wishvel, wishdir); - wishspeed = VectorNormalize(wishdir); - - if ( wishspeed > pm->ps->speed * pm_swimScale ) { - wishspeed = pm->ps->speed * pm_swimScale; - } - - PM_Accelerate (wishdir, wishspeed, pm_wateraccelerate); - - // make sure we can go up slopes easily under water - if ( pml.groundPlane && DotProduct( pm->ps->velocity, pml.groundTrace.plane.normal ) < 0 ) { - vel = VectorLength(pm->ps->velocity); - // slide along the ground plane - PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, - pm->ps->velocity, OVERCLIP ); - - VectorNormalize(pm->ps->velocity); - VectorScale(pm->ps->velocity, vel, pm->ps->velocity); - } - - PM_SlideMove( qfalse ); -} - -#ifdef MISSIONPACK -/* -=================== -PM_InvulnerabilityMove - -Only with the invulnerability powerup -=================== -*/ -static void PM_InvulnerabilityMove( void ) { - pm->cmd.forwardmove = 0; - pm->cmd.rightmove = 0; - pm->cmd.upmove = 0; - VectorClear(pm->ps->velocity); -} -#endif - -/* -=================== -PM_FlyMove - -Only with the flight powerup -=================== -*/ -static void PM_FlyMove( void ) { - int i; - vec3_t wishvel; - float wishspeed; - vec3_t wishdir; - float scale; - - // normal slowdown - PM_Friction (); - - scale = PM_CmdScale( &pm->cmd ); - // - // 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; - } - - wishvel[2] += scale * pm->cmd.upmove; - } - - VectorCopy (wishvel, wishdir); - wishspeed = VectorNormalize(wishdir); - - PM_Accelerate (wishdir, wishspeed, pm_flyaccelerate); - - PM_StepSlideMove( qfalse ); -} - - -/* -=================== -PM_AirMove - -=================== -*/ -static void PM_AirMove( void ) { - int i; - vec3_t wishvel; - float fmove, smove; - vec3_t wishdir; - float wishspeed; - float scale; - usercmd_t cmd; - - 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); - - for ( i = 0 ; i < 2 ; i++ ) { - wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; - } - wishvel[2] = 0; - - VectorCopy (wishvel, wishdir); - wishspeed = VectorNormalize(wishdir); - wishspeed *= scale; - - // not on ground, so little effect on velocity - PM_Accelerate (wishdir, wishspeed, pm_airaccelerate); - - // 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 ) { - PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, - pm->ps->velocity, OVERCLIP ); - } - -#if 0 - //ZOID: If we are on the grapple, try stair-stepping - //this allows a player to use the grapple to pull himself - //over a ledge - if (pm->ps->pm_flags & PMF_GRAPPLE_PULL) - PM_StepSlideMove ( qtrue ); - else - PM_SlideMove ( qtrue ); -#endif - - PM_StepSlideMove ( qtrue ); -} - -/* -=================== -PM_GrappleMove - -=================== -*/ -static void PM_GrappleMove( void ) { - vec3_t vel, v; - float vlen; - - VectorScale(pml.forward, -16, v); - VectorAdd(pm->ps->grapplePoint, v, v); - VectorSubtract(v, pm->ps->origin, vel); - vlen = VectorLength(vel); - VectorNormalize( vel ); - - if (vlen <= 100) - VectorScale(vel, 10 * vlen, vel); - else - VectorScale(vel, 800, vel); - - VectorCopy(vel, pm->ps->velocity); - - pml.groundPlane = qfalse; -} - -/* -=================== -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->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; - } - - PM_Friction (); - - fmove = pm->cmd.forwardmove; - smove = pm->cmd.rightmove; - - cmd = pm->cmd; - scale = PM_CmdScale( &cmd ); - - // set the movementDir so clients can rotate the legs for strafing - PM_SetMovementDir(); - - // project moves down to flat plane - pml.forward[2] = 0; - pml.right[2] = 0; - - // project the forward and right directions onto the ground plane - PM_ClipVelocity (pml.forward, pml.groundTrace.plane.normal, pml.forward, OVERCLIP ); - PM_ClipVelocity (pml.right, pml.groundTrace.plane.normal, pml.right, OVERCLIP ); - // - VectorNormalize (pml.forward); - VectorNormalize (pml.right); - - 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; - - // clamp the speed lower if ducking - if ( pm->ps->pm_flags & PMF_DUCKED ) { - if ( wishspeed > pm->ps->speed * pm_duckScale ) { - wishspeed = pm->ps->speed * pm_duckScale; - } - } - - // 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 ( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) { - accelerate = pm_airaccelerate; - } else { - accelerate = pm_accelerate; - } - - 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->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; - } - - PM_StepSlideMove( qfalse ); - - //Com_Printf("velocity2 = %1.1f\n", VectorLength(pm->ps->velocity)); - -} - - -/* -============== -PM_DeadMove -============== -*/ -static void PM_DeadMove( void ) { - float forward; - - if ( !pml.walking ) { - return; - } - - // extra friction - - forward = VectorLength (pm->ps->velocity); - forward -= 20; - if ( forward <= 0 ) { - VectorClear (pm->ps->velocity); - } else { - VectorNormalize (pm->ps->velocity); - VectorScale (pm->ps->velocity, forward, pm->ps->velocity); - } -} - - -/* -=============== -PM_NoclipMove -=============== -*/ -static void PM_NoclipMove( void ) { - float speed, drop, friction, control, newspeed; - int i; - vec3_t wishvel; - float fmove, smove; - vec3_t wishdir; - float wishspeed; - float scale; - - pm->ps->viewheight = DEFAULT_VIEWHEIGHT; - - // friction - - speed = VectorLength (pm->ps->velocity); - if (speed < 1) - { - VectorCopy (vec3_origin, pm->ps->velocity); - } - else - { - drop = 0; - - friction = pm_friction*1.5; // extra friction - control = speed < pm_stopspeed ? pm_stopspeed : speed; - drop += control*friction*pml.frametime; - - // scale the velocity - newspeed = speed - drop; - if (newspeed < 0) - newspeed = 0; - newspeed /= speed; - - VectorScale (pm->ps->velocity, newspeed, pm->ps->velocity); - } - - // accelerate - scale = PM_CmdScale( &pm->cmd ); - - fmove = pm->cmd.forwardmove; - smove = pm->cmd.rightmove; - - for (i=0 ; i<3 ; i++) - wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; - wishvel[2] += pm->cmd.upmove; - - VectorCopy (wishvel, wishdir); - wishspeed = VectorNormalize(wishdir); - wishspeed *= scale; - - PM_Accelerate( wishdir, wishspeed, pm_accelerate ); - - // move - VectorMA (pm->ps->origin, pml.frametime, pm->ps->velocity, pm->ps->origin); -} - -//============================================================================ - -/* -================ -PM_FootstepForSurface - -Returns an event number apropriate for the groundsurface -================ -*/ -static int PM_FootstepForSurface( void ) { - if ( pml.groundTrace.surfaceFlags & SURF_NOSTEPS ) { - return 0; - } - if ( pml.groundTrace.surfaceFlags & SURF_METALSTEPS ) { - return EV_FOOTSTEP_METAL; - } - return EV_FOOTSTEP; -} - - -/* -================= -PM_CrashLand - -Check for hard landings that generate sound events -================= -*/ -static void PM_CrashLand( void ) { - float delta; - float dist; - float vel, acc; - float t; - float a, b, c, den; - - // decide which landing animation to use - if ( pm->ps->pm_flags & PMF_BACKWARDS_JUMP ) { - PM_ForceLegsAnim( LEGS_LANDB ); - } else { - PM_ForceLegsAnim( LEGS_LAND ); - } - - pm->ps->legsTimer = TIMER_LAND; - - // calculate the exact velocity on landing - dist = pm->ps->origin[2] - pml.previous_origin[2]; - vel = pml.previous_velocity[2]; - acc = -pm->ps->gravity; - - a = acc / 2; - b = vel; - c = -dist; - - den = b * b - 4 * a * c; - if ( den < 0 ) { - return; - } - t = (-b - sqrt( den ) ) / ( 2 * a ); - - delta = vel + t * acc; - delta = delta*delta * 0.0001; - - // ducking while falling doubles damage - if ( pm->ps->pm_flags & PMF_DUCKED ) { - delta *= 2; - } - - // never take falling damage if completely underwater - if ( pm->waterlevel == 3 ) { - return; - } - - // reduce falling damage if there is standing water - if ( pm->waterlevel == 2 ) { - delta *= 0.25; - } - if ( pm->waterlevel == 1 ) { - delta *= 0.5; - } - - if ( delta < 1 ) { - return; - } - - // create a local entity event to play the sound - - // SURF_NODAMAGE is used for bounce pads where you don't ever - // want to take damage or play a crunch sound - if ( !(pml.groundTrace.surfaceFlags & SURF_NODAMAGE) ) { - if ( delta > 60 ) { - PM_AddEvent( EV_FALL_FAR ); - } else if ( delta > 40 ) { - // this is a pain grunt, so don't play it if dead - if ( pm->ps->stats[STAT_HEALTH] > 0 ) { - PM_AddEvent( EV_FALL_MEDIUM ); - } - } else if ( delta > 7 ) { - PM_AddEvent( EV_FALL_SHORT ); - } else { - PM_AddEvent( PM_FootstepForSurface() ); - } - } - - // start footstep cycle over - pm->ps->bobCycle = 0; -} - -/* -============= -PM_CheckStuck -============= -*/ -/* -void PM_CheckStuck(void) { - trace_t trace; - - pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, pm->ps->origin, pm->ps->clientNum, pm->tracemask); - if (trace.allsolid) { - //int shit = qtrue; - } -} -*/ - -/* -============= -PM_CorrectAllSolid -============= -*/ -static int PM_CorrectAllSolid( trace_t *trace ) { - int i, j, k; - vec3_t point; - - if ( pm->debugLevel ) { - Com_Printf("%i:allsolid\n", c_pmove); - } - - // jitter around - for (i = -1; i <= 1; i++) { - for (j = -1; j <= 1; j++) { - for (k = -1; k <= 1; k++) { - VectorCopy(pm->ps->origin, point); - point[0] += (float) i; - point[1] += (float) j; - point[2] += (float) k; - pm->trace (trace, point, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); - if ( !trace->allsolid ) { - point[0] = pm->ps->origin[0]; - point[1] = pm->ps->origin[1]; - point[2] = pm->ps->origin[2] - 0.25; - - pm->trace (trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); - pml.groundTrace = *trace; - return qtrue; - } - } - } - } - - pm->ps->groundEntityNum = ENTITYNUM_NONE; - pml.groundPlane = qfalse; - pml.walking = qfalse; - - return qfalse; -} - - -/* -============= -PM_GroundTraceMissed - -The ground trace didn't hit a surface, so we are in freefall -============= -*/ -static void PM_GroundTraceMissed( void ) { - trace_t trace; - vec3_t point; - - 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 ) { - if ( pm->cmd.forwardmove >= 0 ) { - PM_ForceLegsAnim( LEGS_JUMP ); - pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; - } else { - PM_ForceLegsAnim( LEGS_JUMPB ); - pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; - } - } - } - - pm->ps->groundEntityNum = ENTITYNUM_NONE; - pml.groundPlane = qfalse; - pml.walking = qfalse; -} - - -/* -============= -PM_GroundTrace -============= -*/ -static void PM_GroundTrace( void ) { - vec3_t point; - trace_t trace; - - point[0] = pm->ps->origin[0]; - point[1] = pm->ps->origin[1]; - point[2] = pm->ps->origin[2] - 0.25; - - pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); - pml.groundTrace = trace; - - // do something corrective if the trace starts in a solid... - if ( trace.allsolid ) { - if ( !PM_CorrectAllSolid(&trace) ) - return; - } - - // if the trace didn't hit anything, we are in free fall - if ( trace.fraction == 1.0 ) { - PM_GroundTraceMissed(); - pml.groundPlane = qfalse; - pml.walking = qfalse; - return; - } - - // check if getting thrown off the ground - if ( pm->ps->velocity[2] > 0 && DotProduct( pm->ps->velocity, trace.plane.normal ) > 10 ) { - if ( pm->debugLevel ) { - Com_Printf("%i:kickoff\n", c_pmove); - } - // go into jump animation - if ( pm->cmd.forwardmove >= 0 ) { - PM_ForceLegsAnim( LEGS_JUMP ); - pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; - } else { - PM_ForceLegsAnim( LEGS_JUMPB ); - pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; - } - - pm->ps->groundEntityNum = ENTITYNUM_NONE; - pml.groundPlane = qfalse; - pml.walking = qfalse; - return; - } - - // slopes that are too steep will not be considered onground - if ( trace.plane.normal[2] < MIN_WALK_NORMAL ) { - 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; - } - - pml.groundPlane = qtrue; - pml.walking = qtrue; - - // hitting solid ground will end a waterjump - if (pm->ps->pm_flags & PMF_TIME_WATERJUMP) - { - pm->ps->pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND); - pm->ps->pm_time = 0; - } - - if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) { - // just hit the ground - if ( pm->debugLevel ) { - Com_Printf("%i:Land\n", c_pmove); - } - - PM_CrashLand(); - - // don't do landing time if we were just going down a slope - if ( pml.previous_velocity[2] < -200 ) { - // don't allow another jump for a little while - pm->ps->pm_flags |= PMF_TIME_LAND; - pm->ps->pm_time = 250; - } - } - - pm->ps->groundEntityNum = trace.entityNum; - - // don't reset the z velocity for slopes -// pm->ps->velocity[2] = 0; - - PM_AddTouchEnt( trace.entityNum ); -} - - -/* -============= -PM_SetWaterLevel FIXME: avoid this twice? certainly if not moving -============= -*/ -static void PM_SetWaterLevel( void ) { - vec3_t point; - int cont; - int sample1; - int sample2; - - // - // get waterlevel, accounting for ducking - // - pm->waterlevel = 0; - pm->watertype = 0; - - point[0] = pm->ps->origin[0]; - point[1] = pm->ps->origin[1]; - point[2] = pm->ps->origin[2] + MINS_Z + 1; - cont = pm->pointcontents( point, pm->ps->clientNum ); - - if ( cont & MASK_WATER ) { - sample2 = pm->ps->viewheight - MINS_Z; - sample1 = sample2 / 2; - - pm->watertype = cont; - pm->waterlevel = 1; - point[2] = pm->ps->origin[2] + MINS_Z + sample1; - cont = pm->pointcontents (point, pm->ps->clientNum ); - if ( cont & MASK_WATER ) { - pm->waterlevel = 2; - point[2] = pm->ps->origin[2] + MINS_Z + sample2; - cont = pm->pointcontents (point, pm->ps->clientNum ); - if ( cont & MASK_WATER ){ - pm->waterlevel = 3; - } - } - } - -} - -/* -============== -PM_CheckDuck - -Sets mins, maxs, and pm->ps->viewheight -============== -*/ -static void PM_CheckDuck (void) -{ - trace_t trace; - - if ( pm->ps->powerups[PW_INVULNERABILITY] ) { - if ( pm->ps->pm_flags & PMF_INVULEXPAND ) { - // invulnerability sphere has a 42 units radius - VectorSet( pm->mins, -42, -42, -42 ); - VectorSet( pm->maxs, 42, 42, 42 ); - } - else { - VectorSet( pm->mins, -15, -15, MINS_Z ); - VectorSet( pm->maxs, 15, 15, 16 ); - } - pm->ps->pm_flags |= PMF_DUCKED; - pm->ps->viewheight = CROUCH_VIEWHEIGHT; - return; - } - pm->ps->pm_flags &= ~PMF_INVULEXPAND; - - pm->mins[0] = -15; - pm->mins[1] = -15; - - pm->maxs[0] = 15; - pm->maxs[1] = 15; - - pm->mins[2] = MINS_Z; - - if (pm->ps->pm_type == PM_DEAD) - { - pm->maxs[2] = -8; - pm->ps->viewheight = DEAD_VIEWHEIGHT; - return; - } - - if (pm->cmd.upmove < 0) - { // duck - pm->ps->pm_flags |= PMF_DUCKED; - } - else - { // stand up if possible - if (pm->ps->pm_flags & PMF_DUCKED) - { - // try to stand up - pm->maxs[2] = 32; - pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, pm->ps->origin, pm->ps->clientNum, pm->tracemask ); - if (!trace.allsolid) - pm->ps->pm_flags &= ~PMF_DUCKED; - } - } - - if (pm->ps->pm_flags & PMF_DUCKED) - { - pm->maxs[2] = 16; - pm->ps->viewheight = CROUCH_VIEWHEIGHT; - } - else - { - pm->maxs[2] = 32; - pm->ps->viewheight = DEFAULT_VIEWHEIGHT; - } -} - - - -//=================================================================== - - -/* -=============== -PM_Footsteps -=============== -*/ -static void PM_Footsteps( void ) { - float bobmove; - int old; - qboolean footstep; - - // - // 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->groundEntityNum == ENTITYNUM_NONE ) { - - if ( pm->ps->powerups[PW_INVULNERABILITY] ) { - PM_ContinueLegsAnim( LEGS_IDLECR ); - } - // airborne leaves position in cycle intact, but doesn't advance - if ( pm->waterlevel > 1 ) { - PM_ContinueLegsAnim( LEGS_SWIM ); - } - return; - } - - // if not trying to move - if ( !pm->cmd.forwardmove && !pm->cmd.rightmove ) { - if ( pm->xyspeed < 5 ) { - pm->ps->bobCycle = 0; // start at beginning of cycle again - if ( pm->ps->pm_flags & PMF_DUCKED ) { - PM_ContinueLegsAnim( LEGS_IDLECR ); - } else { - PM_ContinueLegsAnim( LEGS_IDLE ); - } - } - return; - } - - - footstep = qfalse; - - if ( pm->ps->pm_flags & PMF_DUCKED ) { - bobmove = 0.5; // ducked characters bob much faster - if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) { - PM_ContinueLegsAnim( LEGS_BACKCR ); - } - else { - PM_ContinueLegsAnim( LEGS_WALKCR ); - } - // ducked characters never play footsteps - /* - } else if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) { - if ( !( pm->cmd.buttons & BUTTON_WALKING ) ) { - bobmove = 0.4; // faster speeds bob faster - footstep = qtrue; - } else { - bobmove = 0.3; - } - PM_ContinueLegsAnim( LEGS_BACK ); - */ - } else { - if ( !( pm->cmd.buttons & BUTTON_WALKING ) ) { - bobmove = 0.4f; // faster speeds bob faster - if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) { - PM_ContinueLegsAnim( LEGS_BACK ); - } - else { - PM_ContinueLegsAnim( LEGS_RUN ); - } - footstep = qtrue; - } else { - bobmove = 0.3f; // walking bobs slow - if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) { - PM_ContinueLegsAnim( LEGS_BACKWALK ); - } - else { - PM_ContinueLegsAnim( LEGS_WALK ); - } - } - } - - // 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->waterlevel == 0 ) { - // on ground will only play sounds if running - if ( footstep && !pm->noFootsteps ) { - PM_AddEvent( PM_FootstepForSurface() ); - } - } else if ( pm->waterlevel == 1 ) { - // splashing - PM_AddEvent( EV_FOOTSPLASH ); - } else if ( pm->waterlevel == 2 ) { - // wading / swimming at surface - PM_AddEvent( EV_SWIM ); - } else if ( pm->waterlevel == 3 ) { - // no sound when completely underwater - - } - } -} - -/* -============== -PM_WaterEvents - -Generate sound events for entering and leaving water -============== -*/ -static void PM_WaterEvents( void ) { // FIXME? - // - // if just entered a water volume, play a sound - // - if (!pml.previous_waterlevel && pm->waterlevel) { - PM_AddEvent( EV_WATER_TOUCH ); - } - - // - // if just completely exited a water volume, play a sound - // - if (pml.previous_waterlevel && !pm->waterlevel) { - PM_AddEvent( EV_WATER_LEAVE ); - } - - // - // check for head just going under water - // - if (pml.previous_waterlevel != 3 && pm->waterlevel == 3) { - PM_AddEvent( EV_WATER_UNDER ); - } - - // - // check for head just coming out of water - // - if (pml.previous_waterlevel == 3 && pm->waterlevel != 3) { - PM_AddEvent( EV_WATER_CLEAR ); - } -} - - -/* -=============== -PM_BeginWeaponChange -=============== -*/ -static void PM_BeginWeaponChange( int weapon ) { - if ( weapon <= WP_NONE || weapon >= WP_NUM_WEAPONS ) { - return; - } - - if ( !( pm->ps->stats[STAT_WEAPONS] & ( 1 << weapon ) ) ) { - return; - } - - if ( pm->ps->weaponstate == WEAPON_DROPPING ) { - return; - } - - PM_AddEvent( EV_CHANGE_WEAPON ); - pm->ps->weaponstate = WEAPON_DROPPING; - pm->ps->weaponTime += 200; - PM_StartTorsoAnim( TORSO_DROP ); -} - - -/* -=============== -PM_FinishWeaponChange -=============== -*/ -static void PM_FinishWeaponChange( void ) { - int weapon; - - weapon = pm->cmd.weapon; - if ( weapon < WP_NONE || weapon >= WP_NUM_WEAPONS ) { - weapon = WP_NONE; - } - - if ( !( pm->ps->stats[STAT_WEAPONS] & ( 1 << weapon ) ) ) { - weapon = WP_NONE; - } - - pm->ps->weapon = weapon; - pm->ps->weaponstate = WEAPON_RAISING; - pm->ps->weaponTime += 250; - PM_StartTorsoAnim( TORSO_RAISE ); -} - - -/* -============== -PM_TorsoAnimation - -============== -*/ -static void PM_TorsoAnimation( void ) { - if ( pm->ps->weaponstate == WEAPON_READY ) { - if ( pm->ps->weapon == WP_GAUNTLET ) { - PM_ContinueTorsoAnim( TORSO_STAND2 ); - } else { - PM_ContinueTorsoAnim( TORSO_STAND ); - } - return; - } -} - - -/* -============== -PM_Weapon - -Generates weapon events and modifes the weapon counter -============== -*/ -static void PM_Weapon( void ) { - int addTime; - - // don't allow attack until all buttons are up - if ( pm->ps->pm_flags & PMF_RESPAWNED ) { - return; - } - - // ignore if spectator - if ( pm->ps->persistant[PERS_TEAM] == TEAM_SPECTATOR ) { - return; - } - - // check for dead player - if ( pm->ps->stats[STAT_HEALTH] <= 0 ) { - pm->ps->weapon = WP_NONE; - return; - } - - // check for item using - if ( pm->cmd.buttons & BUTTON_USE_HOLDABLE ) { - if ( ! ( pm->ps->pm_flags & PMF_USE_ITEM_HELD ) ) { - if ( bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag == HI_MEDKIT - && pm->ps->stats[STAT_HEALTH] >= (pm->ps->stats[STAT_MAX_HEALTH] + 25) ) { - // don't use medkit if at max health - } else { - pm->ps->pm_flags |= PMF_USE_ITEM_HELD; - PM_AddEvent( EV_USE_ITEM0 + bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag ); - pm->ps->stats[STAT_HOLDABLE_ITEM] = 0; - } - return; - } - } else { - pm->ps->pm_flags &= ~PMF_USE_ITEM_HELD; - } - - - // make weapon function - if ( pm->ps->weaponTime > 0 ) { - pm->ps->weaponTime -= pml.msec; - } - - // 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->weaponstate == WEAPON_RAISING ) { - pm->ps->weaponstate = WEAPON_READY; - if ( pm->ps->weapon == WP_GAUNTLET ) { - PM_StartTorsoAnim( TORSO_STAND2 ); - } else { - PM_StartTorsoAnim( TORSO_STAND ); - } - return; - } - - // check for fire - if ( ! (pm->cmd.buttons & BUTTON_ATTACK) ) { - pm->ps->weaponTime = 0; - pm->ps->weaponstate = WEAPON_READY; - return; - } - - // start the animation even if out of ammo - if ( pm->ps->weapon == WP_GAUNTLET ) { - // the guantlet only "fires" when it actually hits something - if ( !pm->gauntletHit ) { - pm->ps->weaponTime = 0; - pm->ps->weaponstate = WEAPON_READY; - return; - } - PM_StartTorsoAnim( TORSO_ATTACK2 ); - } else { - PM_StartTorsoAnim( TORSO_ATTACK ); - } - - pm->ps->weaponstate = WEAPON_FIRING; - - // check for out of ammo - if ( ! pm->ps->ammo[ pm->ps->weapon ] ) { - PM_AddEvent( EV_NOAMMO ); - pm->ps->weaponTime += 500; - return; - } - - // take an ammo away if not infinite - if ( pm->ps->ammo[ pm->ps->weapon ] != -1 ) { - pm->ps->ammo[ pm->ps->weapon ]--; - } - - // fire weapon - PM_AddEvent( EV_FIRE_WEAPON ); - - switch( pm->ps->weapon ) { - default: - case WP_GAUNTLET: - addTime = 400; - break; - case WP_LIGHTNING: - addTime = 50; - break; - case WP_SHOTGUN: - addTime = 1000; - break; - case WP_MACHINEGUN: - addTime = 100; - break; - case WP_GRENADE_LAUNCHER: - addTime = 800; - break; - case WP_ROCKET_LAUNCHER: - addTime = 800; - break; - case WP_PLASMAGUN: - addTime = 100; - break; - case WP_RAILGUN: - addTime = 1500; - break; - case WP_BFG: - addTime = 200; - break; - case WP_GRAPPLING_HOOK: - addTime = 400; - break; -#ifdef MISSIONPACK - case WP_NAILGUN: - addTime = 1000; - break; - case WP_PROX_LAUNCHER: - addTime = 800; - break; - case WP_CHAINGUN: - addTime = 30; - break; -#endif - } - -#ifdef MISSIONPACK - if( bg_itemlist[pm->ps->stats[STAT_PERSISTANT_POWERUP]].giTag == PW_SCOUT ) { - addTime /= 1.5; - } - else - if( bg_itemlist[pm->ps->stats[STAT_PERSISTANT_POWERUP]].giTag == PW_AMMOREGEN ) { - addTime /= 1.3; - } - else -#endif - if ( pm->ps->powerups[PW_HASTE] ) { - addTime /= 1.3; - } - - pm->ps->weaponTime += addTime; -} - -/* -================ -PM_Animate -================ -*/ - -static void PM_Animate( void ) { - if ( pm->cmd.buttons & BUTTON_GESTURE ) { - if ( pm->ps->torsoTimer == 0 ) { - PM_StartTorsoAnim( TORSO_GESTURE ); - pm->ps->torsoTimer = TIMER_GESTURE; - PM_AddEvent( EV_TAUNT ); - } -#ifdef MISSIONPACK - } else if ( pm->cmd.buttons & BUTTON_GETFLAG ) { - if ( pm->ps->torsoTimer == 0 ) { - PM_StartTorsoAnim( TORSO_GETFLAG ); - pm->ps->torsoTimer = 600; //TIMER_GESTURE; - } - } else if ( pm->cmd.buttons & BUTTON_GUARDBASE ) { - if ( pm->ps->torsoTimer == 0 ) { - PM_StartTorsoAnim( TORSO_GUARDBASE ); - pm->ps->torsoTimer = 600; //TIMER_GESTURE; - } - } else if ( pm->cmd.buttons & BUTTON_PATROL ) { - if ( pm->ps->torsoTimer == 0 ) { - PM_StartTorsoAnim( TORSO_PATROL ); - pm->ps->torsoTimer = 600; //TIMER_GESTURE; - } - } else if ( pm->cmd.buttons & BUTTON_FOLLOWME ) { - if ( pm->ps->torsoTimer == 0 ) { - PM_StartTorsoAnim( TORSO_FOLLOWME ); - pm->ps->torsoTimer = 600; //TIMER_GESTURE; - } - } else if ( pm->cmd.buttons & BUTTON_AFFIRMATIVE ) { - if ( pm->ps->torsoTimer == 0 ) { - PM_StartTorsoAnim( TORSO_AFFIRMATIVE); - pm->ps->torsoTimer = 600; //TIMER_GESTURE; - } - } else if ( pm->cmd.buttons & BUTTON_NEGATIVE ) { - if ( pm->ps->torsoTimer == 0 ) { - PM_StartTorsoAnim( TORSO_NEGATIVE ); - pm->ps->torsoTimer = 600; //TIMER_GESTURE; - } -#endif - } -} - - -/* -================ -PM_DropTimers -================ -*/ -static void PM_DropTimers( void ) { - // drop misc timing counter - if ( pm->ps->pm_time ) { - if ( pml.msec >= pm->ps->pm_time ) { - pm->ps->pm_flags &= ~PMF_ALL_TIMES; - pm->ps->pm_time = 0; - } else { - pm->ps->pm_time -= pml.msec; - } - } - - // drop animation counter - if ( pm->ps->legsTimer > 0 ) { - pm->ps->legsTimer -= pml.msec; - if ( pm->ps->legsTimer < 0 ) { - pm->ps->legsTimer = 0; - } - } - - if ( pm->ps->torsoTimer > 0 ) { - pm->ps->torsoTimer -= pml.msec; - if ( pm->ps->torsoTimer < 0 ) { - pm->ps->torsoTimer = 0; - } - } -} - -/* -================ -PM_UpdateViewAngles - -This can be used as another entry point when only the viewangles -are being updated isntead of a full move -================ -*/ -void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd ) { - short temp; - int i; - - if ( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPINTERMISSION) { - return; // no view changes at all - } - - if ( ps->pm_type != PM_SPECTATOR && ps->stats[STAT_HEALTH] <= 0 ) { - return; // no view changes at all - } - - // circularly clamp the angles with deltas - for (i=0 ; i<3 ; i++) { - temp = cmd->angles[i] + ps->delta_angles[i]; - if ( i == PITCH ) { - // don't let the player look up or down more than 90 degrees - if ( temp > 16000 ) { - ps->delta_angles[i] = 16000 - cmd->angles[i]; - temp = 16000; - } else if ( temp < -16000 ) { - ps->delta_angles[i] = -16000 - cmd->angles[i]; - temp = -16000; - } - } - ps->viewangles[i] = SHORT2ANGLE(temp); - } - -} - - -/* -================ -PmoveSingle - -================ -*/ -void trap_SnapVector( float *v ); - -void PmoveSingle (pmove_t *pmove) { - 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; - pm->watertype = 0; - pm->waterlevel = 0; - - if ( pm->ps->stats[STAT_HEALTH] <= 0 ) { - pm->tracemask &= ~CONTENTS_BODY; // corpses can fly through bodies - } - - // make sure walking button is clear if they are running, to avoid - // proxy no-footsteps cheats - if ( abs( pm->cmd.forwardmove ) > 64 || abs( pm->cmd.rightmove ) > 64 ) { - pm->cmd.buttons &= ~BUTTON_WALKING; - } - - // set the talk balloon flag - if ( pm->cmd.buttons & BUTTON_TALK ) { - pm->ps->eFlags |= EF_TALK; - } else { - pm->ps->eFlags &= ~EF_TALK; - } - - // set the firing flag for continuous beam weapons - if ( !(pm->ps->pm_flags & PMF_RESPAWNED) && pm->ps->pm_type != PM_INTERMISSION - && ( pm->cmd.buttons & BUTTON_ATTACK ) && pm->ps->ammo[ pm->ps->weapon ] ) { - pm->ps->eFlags |= EF_FIRING; - } else { - pm->ps->eFlags &= ~EF_FIRING; - } - - // clear the respawned flag if attack and use are cleared - if ( pm->ps->stats[STAT_HEALTH] > 0 && - !( pm->cmd.buttons & (BUTTON_ATTACK | BUTTON_USE_HOLDABLE) ) ) { - pm->ps->pm_flags &= ~PMF_RESPAWNED; - } - - // if talk button is down, dissallow all other input - // this is to prevent any possible intercept proxy from - // adding fake talk balloons - if ( pmove->cmd.buttons & BUTTON_TALK ) { - // keep the talk button set tho for when the cmd.serverTime > 66 msec - // and the same cmd is used multiple times in Pmove - pmove->cmd.buttons = BUTTON_TALK; - pmove->cmd.forwardmove = 0; - pmove->cmd.rightmove = 0; - pmove->cmd.upmove = 0; - } - - // clear all pmove local vars - memset (&pml, 0, sizeof(pml)); - - // determine the time - pml.msec = pmove->cmd.serverTime - pm->ps->commandTime; - if ( pml.msec < 1 ) { - pml.msec = 1; - } else if ( pml.msec > 200 ) { - pml.msec = 200; - } - 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; - - // update the viewangles - PM_UpdateViewAngles( pm->ps, &pm->cmd ); - - 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->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 || pm->ps->pm_type == PM_SPINTERMISSION) { - return; // no movement at all - } - - // set watertype, and waterlevel - PM_SetWaterLevel(); - pml.previous_waterlevel = pmove->waterlevel; - - // set mins, maxs, and viewheight - PM_CheckDuck (); - - // set groundentity - PM_GroundTrace(); - - if ( pm->ps->pm_type == PM_DEAD ) { - PM_DeadMove (); - } - - PM_DropTimers(); - -#ifdef MISSIONPACK - if ( pm->ps->powerups[PW_INVULNERABILITY] ) { - PM_InvulnerabilityMove(); - } else -#endif - if ( pm->ps->powerups[PW_FLIGHT] ) { - // flight powerup doesn't allow jump and has different friction - PM_FlyMove(); - } else if (pm->ps->pm_flags & PMF_GRAPPLE_PULL) { - PM_GrappleMove(); - // We can wiggle a bit - PM_AirMove(); - } else if (pm->ps->pm_flags & PMF_TIME_WATERJUMP) { - PM_WaterJumpMove(); - } else if ( pm->waterlevel > 1 ) { - // swimming - PM_WaterMove(); - } else if ( pml.walking ) { - // walking on ground - PM_WalkMove(); - } else { - // airborne - PM_AirMove(); - } - - PM_Animate(); - - // set groundentity, watertype, and waterlevel - PM_GroundTrace(); - PM_SetWaterLevel(); - - // weapons - PM_Weapon(); - - // torso animation - PM_TorsoAnimation(); - - // footstep events / legs animations - PM_Footsteps(); - - // entering / leaving water splashes - PM_WaterEvents(); - - // snap some parts of playerstate to save network bandwidth - trap_SnapVector( pm->ps->velocity ); -} - - -/* -================ -Pmove - -Can be called by either the server or the client -================ -*/ -void Pmove (pmove_t *pmove) { - int finalTime; - - finalTime = pmove->cmd.serverTime; - - if ( finalTime < pmove->ps->commandTime ) { - return; // should not happen - } - - if ( finalTime > pmove->ps->commandTime + 1000 ) { - pmove->ps->commandTime = finalTime - 1000; - } - - pmove->ps->pmove_framecount = (pmove->ps->pmove_framecount+1) & ((1<ps->commandTime != finalTime ) { - int msec; - - msec = finalTime - pmove->ps->commandTime; - - if ( pmove->pmove_fixed ) { - if ( msec > pmove->pmove_msec ) { - msec = pmove->pmove_msec; - } - } - else { - if ( msec > 66 ) { - msec = 66; - } - } - pmove->cmd.serverTime = pmove->ps->commandTime + msec; - PmoveSingle( pmove ); - - if ( pmove->ps->pm_flags & PMF_JUMP_HELD ) { - pmove->cmd.upmove = 20; - } - } - - //PM_CheckStuck(); - -} - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// bg_pmove.c -- both games player movement code +// takes a playerstate and a usercmd as input and returns a modifed playerstate + +#include "q_shared.h" +#include "bg_public.h" +#include "bg_local.h" + +pmove_t *pm; +pml_t pml; + +// movement parameters +float pm_stopspeed = 100.0f; +float pm_duckScale = 0.25f; +float pm_swimScale = 0.50f; +float pm_wadeScale = 0.70f; + +float pm_accelerate = 10.0f; +float pm_airaccelerate = 1.0f; +float pm_wateraccelerate = 4.0f; +float pm_flyaccelerate = 8.0f; + +float pm_friction = 6.0f; +float pm_waterfriction = 1.0f; +float pm_flightfriction = 3.0f; +float pm_spectatorfriction = 5.0f; + +int c_pmove = 0; + + +/* +=============== +PM_AddEvent + +=============== +*/ +void PM_AddEvent( int newEvent ) { + BG_AddPredictableEventToPlayerstate( newEvent, 0, pm->ps ); +} + +/* +=============== +PM_AddTouchEnt +=============== +*/ +void PM_AddTouchEnt( int entityNum ) { + int i; + + if ( entityNum == ENTITYNUM_WORLD ) { + return; + } + if ( pm->numtouch == MAXTOUCH ) { + return; + } + + // see if it is already added + for ( i = 0 ; i < pm->numtouch ; i++ ) { + if ( pm->touchents[ i ] == entityNum ) { + return; + } + } + + // add it + pm->touchents[pm->numtouch] = entityNum; + pm->numtouch++; +} + +/* +=================== +PM_StartTorsoAnim +=================== +*/ +static void PM_StartTorsoAnim( int anim ) { + if ( pm->ps->pm_type >= PM_DEAD ) { + return; + } + pm->ps->torsoAnim = ( ( pm->ps->torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) + | anim; +} +static void PM_StartLegsAnim( int anim ) { + if ( pm->ps->pm_type >= PM_DEAD ) { + return; + } + if ( pm->ps->legsTimer > 0 ) { + return; // a high priority animation is running + } + pm->ps->legsAnim = ( ( pm->ps->legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) + | anim; +} + +static void PM_ContinueLegsAnim( int anim ) { + if ( ( pm->ps->legsAnim & ~ANIM_TOGGLEBIT ) == anim ) { + return; + } + if ( pm->ps->legsTimer > 0 ) { + return; // a high priority animation is running + } + PM_StartLegsAnim( anim ); +} + +static void PM_ContinueTorsoAnim( int anim ) { + if ( ( pm->ps->torsoAnim & ~ANIM_TOGGLEBIT ) == anim ) { + return; + } + if ( pm->ps->torsoTimer > 0 ) { + return; // a high priority animation is running + } + PM_StartTorsoAnim( anim ); +} + +static void PM_ForceLegsAnim( int anim ) { + pm->ps->legsTimer = 0; + PM_StartLegsAnim( anim ); +} + + +/* +================== +PM_ClipVelocity + +Slide off of the impacting surface +================== +*/ +void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce ) { + float backoff; + float change; + 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; + } +} + + +/* +================== +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; + + 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 + if ( pm->waterlevel <= 1 ) { + if ( pml.walking && !(pml.groundTrace.surfaceFlags & SURF_SLICK) ) { + // if getting knocked back, no friction + if ( ! (pm->ps->pm_flags & PMF_TIME_KNOCKBACK) ) { + control = speed < pm_stopspeed ? pm_stopspeed : speed; + drop += control*pm_friction*pml.frametime; + } + } + } + + // apply water friction even if just wading + if ( pm->waterlevel ) { + drop += speed*pm_waterfriction*pm->waterlevel*pml.frametime; + } + + // apply flying friction + if ( pm->ps->powerups[PW_FLIGHT]) { + drop += speed*pm_flightfriction*pml.frametime; + } + + if ( pm->ps->pm_type == PM_SPECTATOR) { + drop += speed*pm_spectatorfriction*pml.frametime; + } + + // scale the velocity + newspeed = speed - drop; + if (newspeed < 0) { + newspeed = 0; + } + newspeed /= speed; + + vel[0] = vel[0] * newspeed; + vel[1] = vel[1] * newspeed; + vel[2] = vel[2] * newspeed; +} + + +/* +============== +PM_Accelerate + +Handles user intended acceleration +============== +*/ +static void PM_Accelerate( vec3_t wishdir, float wishspeed, float accel ) { +#if 1 + // q2 style + 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]; + } +#else + // proper way (avoids strafe jump maxspeed bug), but feels bad + vec3_t wishVelocity; + vec3_t pushDir; + float pushLen; + float canPush; + + VectorScale( wishdir, wishspeed, wishVelocity ); + VectorSubtract( wishVelocity, pm->ps->velocity, pushDir ); + pushLen = VectorNormalize( pushDir ); + + canPush = accel*pml.frametime*wishspeed; + if (canPush > pushLen) { + canPush = pushLen; + } + + VectorMA( pm->ps->velocity, canPush, pushDir, pm->ps->velocity ); +#endif +} + + + +/* +============ +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( cmd->forwardmove * cmd->forwardmove + + cmd->rightmove * cmd->rightmove + cmd->upmove * cmd->upmove ); + scale = (float)pm->ps->speed * max / ( 127.0 * total ); + + return scale; +} + + +/* +================ +PM_SetMovementDir + +Determine the rotation of the legs reletive +to the facing dir +================ +*/ +static void PM_SetMovementDir( void ) { + if ( pm->cmd.forwardmove || pm->cmd.rightmove ) { + if ( pm->cmd.rightmove == 0 && pm->cmd.forwardmove > 0 ) { + pm->ps->movementDir = 0; + } else if ( pm->cmd.rightmove < 0 && pm->cmd.forwardmove > 0 ) { + pm->ps->movementDir = 1; + } else if ( pm->cmd.rightmove < 0 && pm->cmd.forwardmove == 0 ) { + pm->ps->movementDir = 2; + } else if ( pm->cmd.rightmove < 0 && pm->cmd.forwardmove < 0 ) { + pm->ps->movementDir = 3; + } else if ( pm->cmd.rightmove == 0 && pm->cmd.forwardmove < 0 ) { + pm->ps->movementDir = 4; + } else if ( pm->cmd.rightmove > 0 && pm->cmd.forwardmove < 0 ) { + pm->ps->movementDir = 5; + } else if ( pm->cmd.rightmove > 0 && pm->cmd.forwardmove == 0 ) { + pm->ps->movementDir = 6; + } else if ( pm->cmd.rightmove > 0 && pm->cmd.forwardmove > 0 ) { + pm->ps->movementDir = 7; + } + } else { + // if they aren't actively going directly sideways, + // change the animation to the diagonal so they + // don't stop too crooked + if ( pm->ps->movementDir == 2 ) { + pm->ps->movementDir = 1; + } else if ( pm->ps->movementDir == 6 ) { + pm->ps->movementDir = 7; + } + } +} + + +/* +============= +PM_CheckJump +============= +*/ +static qboolean PM_CheckJump( void ) { + if ( pm->ps->pm_flags & PMF_RESPAWNED ) { + return qfalse; // don't allow jump until all buttons are up + } + + if ( pm->cmd.upmove < 10 ) { + // not holding jump + 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; + } + + pml.groundPlane = qfalse; // jumping away + pml.walking = qfalse; + pm->ps->pm_flags |= PMF_JUMP_HELD; + + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pm->ps->velocity[2] = JUMP_VELOCITY; + PM_AddEvent( EV_JUMP ); + + if ( pm->cmd.forwardmove >= 0 ) { + PM_ForceLegsAnim( LEGS_JUMP ); + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } else { + PM_ForceLegsAnim( LEGS_JUMPB ); + pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; + } + + return qtrue; +} + +/* +============= +PM_CheckWaterJump +============= +*/ +static qboolean PM_CheckWaterJump( void ) { + vec3_t spot; + int cont; + vec3_t flatforward; + + if (pm->ps->pm_time) { + return qfalse; + } + + // check for water jump + if ( pm->waterlevel != 2 ) { + return qfalse; + } + + flatforward[0] = pml.forward[0]; + flatforward[1] = pml.forward[1]; + flatforward[2] = 0; + VectorNormalize (flatforward); + + VectorMA (pm->ps->origin, 30, flatforward, spot); + spot[2] += 4; + cont = pm->pointcontents (spot, pm->ps->clientNum ); + if ( !(cont & CONTENTS_SOLID) ) { + return qfalse; + } + + spot[2] += 16; + cont = pm->pointcontents (spot, pm->ps->clientNum ); + if ( cont ) { + return qfalse; + } + + // jump out of water + VectorScale (pml.forward, 200, pm->ps->velocity); + pm->ps->velocity[2] = 350; + + pm->ps->pm_flags |= PMF_TIME_WATERJUMP; + pm->ps->pm_time = 2000; + + return qtrue; +} + +//============================================================================ + + +/* +=================== +PM_WaterJumpMove + +Flying out of the water +=================== +*/ +static void PM_WaterJumpMove( void ) { + // waterjump has no control, but falls + + PM_StepSlideMove( qtrue ); + + pm->ps->velocity[2] -= pm->ps->gravity * pml.frametime; + if (pm->ps->velocity[2] < 0) { + // cancel as soon as we are falling down again + pm->ps->pm_flags &= ~PMF_ALL_TIMES; + pm->ps->pm_time = 0; + } +} + +/* +=================== +PM_WaterMove + +=================== +*/ +static void PM_WaterMove( void ) { + int i; + vec3_t wishvel; + float wishspeed; + vec3_t wishdir; + float scale; + float vel; + + if ( PM_CheckWaterJump() ) { + PM_WaterJumpMove(); + return; + } +#if 0 + // jump = head for surface + if ( pm->cmd.upmove >= 10 ) { + if (pm->ps->velocity[2] > -300) { + if ( pm->watertype == CONTENTS_WATER ) { + pm->ps->velocity[2] = 100; + } else if (pm->watertype == CONTENTS_SLIME) { + pm->ps->velocity[2] = 80; + } else { + pm->ps->velocity[2] = 50; + } + } + } +#endif + PM_Friction (); + + scale = PM_CmdScale( &pm->cmd ); + // + // user intentions + // + if ( !scale ) { + wishvel[0] = 0; + wishvel[1] = 0; + wishvel[2] = -60; // sink towards bottom + } else { + for (i=0 ; i<3 ; i++) + wishvel[i] = scale * pml.forward[i]*pm->cmd.forwardmove + scale * pml.right[i]*pm->cmd.rightmove; + + wishvel[2] += scale * pm->cmd.upmove; + } + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + + if ( wishspeed > pm->ps->speed * pm_swimScale ) { + wishspeed = pm->ps->speed * pm_swimScale; + } + + PM_Accelerate (wishdir, wishspeed, pm_wateraccelerate); + + // make sure we can go up slopes easily under water + if ( pml.groundPlane && DotProduct( pm->ps->velocity, pml.groundTrace.plane.normal ) < 0 ) { + vel = VectorLength(pm->ps->velocity); + // slide along the ground plane + PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); + + VectorNormalize(pm->ps->velocity); + VectorScale(pm->ps->velocity, vel, pm->ps->velocity); + } + + PM_SlideMove( qfalse ); +} + +#ifdef MISSIONPACK +/* +=================== +PM_InvulnerabilityMove + +Only with the invulnerability powerup +=================== +*/ +static void PM_InvulnerabilityMove( void ) { + pm->cmd.forwardmove = 0; + pm->cmd.rightmove = 0; + pm->cmd.upmove = 0; + VectorClear(pm->ps->velocity); +} +#endif + +/* +=================== +PM_FlyMove + +Only with the flight powerup +=================== +*/ +static void PM_FlyMove( void ) { + int i; + vec3_t wishvel; + float wishspeed; + vec3_t wishdir; + float scale; + + // normal slowdown + PM_Friction (); + + scale = PM_CmdScale( &pm->cmd ); + // + // 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; + } + + wishvel[2] += scale * pm->cmd.upmove; + } + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + + PM_Accelerate (wishdir, wishspeed, pm_flyaccelerate); + + PM_StepSlideMove( qfalse ); +} + + +/* +=================== +PM_AirMove + +=================== +*/ +static void PM_AirMove( void ) { + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + float scale; + usercmd_t cmd; + + 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); + + for ( i = 0 ; i < 2 ; i++ ) { + wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; + } + wishvel[2] = 0; + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + wishspeed *= scale; + + // not on ground, so little effect on velocity + PM_Accelerate (wishdir, wishspeed, pm_airaccelerate); + + // 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 ) { + PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); + } + +#if 0 + //ZOID: If we are on the grapple, try stair-stepping + //this allows a player to use the grapple to pull himself + //over a ledge + if (pm->ps->pm_flags & PMF_GRAPPLE_PULL) + PM_StepSlideMove ( qtrue ); + else + PM_SlideMove ( qtrue ); +#endif + + PM_StepSlideMove ( qtrue ); +} + +/* +=================== +PM_GrappleMove + +=================== +*/ +static void PM_GrappleMove( void ) { + vec3_t vel, v; + float vlen; + + VectorScale(pml.forward, -16, v); + VectorAdd(pm->ps->grapplePoint, v, v); + VectorSubtract(v, pm->ps->origin, vel); + vlen = VectorLength(vel); + VectorNormalize( vel ); + + if (vlen <= 100) + VectorScale(vel, 10 * vlen, vel); + else + VectorScale(vel, 800, vel); + + VectorCopy(vel, pm->ps->velocity); + + pml.groundPlane = qfalse; +} + +/* +=================== +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->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; + } + + PM_Friction (); + + fmove = pm->cmd.forwardmove; + smove = pm->cmd.rightmove; + + cmd = pm->cmd; + scale = PM_CmdScale( &cmd ); + + // set the movementDir so clients can rotate the legs for strafing + PM_SetMovementDir(); + + // project moves down to flat plane + pml.forward[2] = 0; + pml.right[2] = 0; + + // project the forward and right directions onto the ground plane + PM_ClipVelocity (pml.forward, pml.groundTrace.plane.normal, pml.forward, OVERCLIP ); + PM_ClipVelocity (pml.right, pml.groundTrace.plane.normal, pml.right, OVERCLIP ); + // + VectorNormalize (pml.forward); + VectorNormalize (pml.right); + + 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; + + // clamp the speed lower if ducking + if ( pm->ps->pm_flags & PMF_DUCKED ) { + if ( wishspeed > pm->ps->speed * pm_duckScale ) { + wishspeed = pm->ps->speed * pm_duckScale; + } + } + + // 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 ( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) { + accelerate = pm_airaccelerate; + } else { + accelerate = pm_accelerate; + } + + 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->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; + } + + PM_StepSlideMove( qfalse ); + + //Com_Printf("velocity2 = %1.1f\n", VectorLength(pm->ps->velocity)); + +} + + +/* +============== +PM_DeadMove +============== +*/ +static void PM_DeadMove( void ) { + float forward; + + if ( !pml.walking ) { + return; + } + + // extra friction + + forward = VectorLength (pm->ps->velocity); + forward -= 20; + if ( forward <= 0 ) { + VectorClear (pm->ps->velocity); + } else { + VectorNormalize (pm->ps->velocity); + VectorScale (pm->ps->velocity, forward, pm->ps->velocity); + } +} + + +/* +=============== +PM_NoclipMove +=============== +*/ +static void PM_NoclipMove( void ) { + float speed, drop, friction, control, newspeed; + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + float scale; + + pm->ps->viewheight = DEFAULT_VIEWHEIGHT; + + // friction + + speed = VectorLength (pm->ps->velocity); + if (speed < 1) + { + VectorCopy (vec3_origin, pm->ps->velocity); + } + else + { + drop = 0; + + friction = pm_friction*1.5; // extra friction + control = speed < pm_stopspeed ? pm_stopspeed : speed; + drop += control*friction*pml.frametime; + + // scale the velocity + newspeed = speed - drop; + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + + VectorScale (pm->ps->velocity, newspeed, pm->ps->velocity); + } + + // accelerate + scale = PM_CmdScale( &pm->cmd ); + + fmove = pm->cmd.forwardmove; + smove = pm->cmd.rightmove; + + for (i=0 ; i<3 ; i++) + wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; + wishvel[2] += pm->cmd.upmove; + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + wishspeed *= scale; + + PM_Accelerate( wishdir, wishspeed, pm_accelerate ); + + // move + VectorMA (pm->ps->origin, pml.frametime, pm->ps->velocity, pm->ps->origin); +} + +//============================================================================ + +/* +================ +PM_FootstepForSurface + +Returns an event number apropriate for the groundsurface +================ +*/ +static int PM_FootstepForSurface( void ) { + if ( pml.groundTrace.surfaceFlags & SURF_NOSTEPS ) { + return 0; + } + if ( pml.groundTrace.surfaceFlags & SURF_METALSTEPS ) { + return EV_FOOTSTEP_METAL; + } + return EV_FOOTSTEP; +} + + +/* +================= +PM_CrashLand + +Check for hard landings that generate sound events +================= +*/ +static void PM_CrashLand( void ) { + float delta; + float dist; + float vel, acc; + float t; + float a, b, c, den; + + // decide which landing animation to use + if ( pm->ps->pm_flags & PMF_BACKWARDS_JUMP ) { + PM_ForceLegsAnim( LEGS_LANDB ); + } else { + PM_ForceLegsAnim( LEGS_LAND ); + } + + pm->ps->legsTimer = TIMER_LAND; + + // calculate the exact velocity on landing + dist = pm->ps->origin[2] - pml.previous_origin[2]; + vel = pml.previous_velocity[2]; + acc = -pm->ps->gravity; + + a = acc / 2; + b = vel; + c = -dist; + + den = b * b - 4 * a * c; + if ( den < 0 ) { + return; + } + t = (-b - sqrt( den ) ) / ( 2 * a ); + + delta = vel + t * acc; + delta = delta*delta * 0.0001; + + // ducking while falling doubles damage + if ( pm->ps->pm_flags & PMF_DUCKED ) { + delta *= 2; + } + + // never take falling damage if completely underwater + if ( pm->waterlevel == 3 ) { + return; + } + + // reduce falling damage if there is standing water + if ( pm->waterlevel == 2 ) { + delta *= 0.25; + } + if ( pm->waterlevel == 1 ) { + delta *= 0.5; + } + + if ( delta < 1 ) { + return; + } + + // create a local entity event to play the sound + + // SURF_NODAMAGE is used for bounce pads where you don't ever + // want to take damage or play a crunch sound + if ( !(pml.groundTrace.surfaceFlags & SURF_NODAMAGE) ) { + if ( delta > 60 ) { + PM_AddEvent( EV_FALL_FAR ); + } else if ( delta > 40 ) { + // this is a pain grunt, so don't play it if dead + if ( pm->ps->stats[STAT_HEALTH] > 0 ) { + PM_AddEvent( EV_FALL_MEDIUM ); + } + } else if ( delta > 7 ) { + PM_AddEvent( EV_FALL_SHORT ); + } else { + PM_AddEvent( PM_FootstepForSurface() ); + } + } + + // start footstep cycle over + pm->ps->bobCycle = 0; +} + +/* +============= +PM_CheckStuck +============= +*/ +/* +void PM_CheckStuck(void) { + trace_t trace; + + pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, pm->ps->origin, pm->ps->clientNum, pm->tracemask); + if (trace.allsolid) { + //int shit = qtrue; + } +} +*/ + +/* +============= +PM_CorrectAllSolid +============= +*/ +static int PM_CorrectAllSolid( trace_t *trace ) { + int i, j, k; + vec3_t point; + + if ( pm->debugLevel ) { + Com_Printf("%i:allsolid\n", c_pmove); + } + + // jitter around + for (i = -1; i <= 1; i++) { + for (j = -1; j <= 1; j++) { + for (k = -1; k <= 1; k++) { + VectorCopy(pm->ps->origin, point); + point[0] += (float) i; + point[1] += (float) j; + point[2] += (float) k; + pm->trace (trace, point, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); + if ( !trace->allsolid ) { + point[0] = pm->ps->origin[0]; + point[1] = pm->ps->origin[1]; + point[2] = pm->ps->origin[2] - 0.25; + + pm->trace (trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); + pml.groundTrace = *trace; + return qtrue; + } + } + } + } + + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qfalse; + pml.walking = qfalse; + + return qfalse; +} + + +/* +============= +PM_GroundTraceMissed + +The ground trace didn't hit a surface, so we are in freefall +============= +*/ +static void PM_GroundTraceMissed( void ) { + trace_t trace; + vec3_t point; + + 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 ) { + if ( pm->cmd.forwardmove >= 0 ) { + PM_ForceLegsAnim( LEGS_JUMP ); + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } else { + PM_ForceLegsAnim( LEGS_JUMPB ); + pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; + } + } + } + + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qfalse; + pml.walking = qfalse; +} + + +/* +============= +PM_GroundTrace +============= +*/ +static void PM_GroundTrace( void ) { + vec3_t point; + trace_t trace; + + point[0] = pm->ps->origin[0]; + point[1] = pm->ps->origin[1]; + point[2] = pm->ps->origin[2] - 0.25; + + pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); + pml.groundTrace = trace; + + // do something corrective if the trace starts in a solid... + if ( trace.allsolid ) { + if ( !PM_CorrectAllSolid(&trace) ) + return; + } + + // if the trace didn't hit anything, we are in free fall + if ( trace.fraction == 1.0 ) { + PM_GroundTraceMissed(); + pml.groundPlane = qfalse; + pml.walking = qfalse; + return; + } + + // check if getting thrown off the ground + if ( pm->ps->velocity[2] > 0 && DotProduct( pm->ps->velocity, trace.plane.normal ) > 10 ) { + if ( pm->debugLevel ) { + Com_Printf("%i:kickoff\n", c_pmove); + } + // go into jump animation + if ( pm->cmd.forwardmove >= 0 ) { + PM_ForceLegsAnim( LEGS_JUMP ); + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } else { + PM_ForceLegsAnim( LEGS_JUMPB ); + pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; + } + + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qfalse; + pml.walking = qfalse; + return; + } + + // slopes that are too steep will not be considered onground + if ( trace.plane.normal[2] < MIN_WALK_NORMAL ) { + 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; + } + + pml.groundPlane = qtrue; + pml.walking = qtrue; + + // hitting solid ground will end a waterjump + if (pm->ps->pm_flags & PMF_TIME_WATERJUMP) + { + pm->ps->pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND); + pm->ps->pm_time = 0; + } + + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) { + // just hit the ground + if ( pm->debugLevel ) { + Com_Printf("%i:Land\n", c_pmove); + } + + PM_CrashLand(); + + // don't do landing time if we were just going down a slope + if ( pml.previous_velocity[2] < -200 ) { + // don't allow another jump for a little while + pm->ps->pm_flags |= PMF_TIME_LAND; + pm->ps->pm_time = 250; + } + } + + pm->ps->groundEntityNum = trace.entityNum; + + // don't reset the z velocity for slopes +// pm->ps->velocity[2] = 0; + + PM_AddTouchEnt( trace.entityNum ); +} + + +/* +============= +PM_SetWaterLevel FIXME: avoid this twice? certainly if not moving +============= +*/ +static void PM_SetWaterLevel( void ) { + vec3_t point; + int cont; + int sample1; + int sample2; + + // + // get waterlevel, accounting for ducking + // + pm->waterlevel = 0; + pm->watertype = 0; + + point[0] = pm->ps->origin[0]; + point[1] = pm->ps->origin[1]; + point[2] = pm->ps->origin[2] + MINS_Z + 1; + cont = pm->pointcontents( point, pm->ps->clientNum ); + + if ( cont & MASK_WATER ) { + sample2 = pm->ps->viewheight - MINS_Z; + sample1 = sample2 / 2; + + pm->watertype = cont; + pm->waterlevel = 1; + point[2] = pm->ps->origin[2] + MINS_Z + sample1; + cont = pm->pointcontents (point, pm->ps->clientNum ); + if ( cont & MASK_WATER ) { + pm->waterlevel = 2; + point[2] = pm->ps->origin[2] + MINS_Z + sample2; + cont = pm->pointcontents (point, pm->ps->clientNum ); + if ( cont & MASK_WATER ){ + pm->waterlevel = 3; + } + } + } + +} + +/* +============== +PM_CheckDuck + +Sets mins, maxs, and pm->ps->viewheight +============== +*/ +static void PM_CheckDuck (void) +{ + trace_t trace; + + if ( pm->ps->powerups[PW_INVULNERABILITY] ) { + if ( pm->ps->pm_flags & PMF_INVULEXPAND ) { + // invulnerability sphere has a 42 units radius + VectorSet( pm->mins, -42, -42, -42 ); + VectorSet( pm->maxs, 42, 42, 42 ); + } + else { + VectorSet( pm->mins, -15, -15, MINS_Z ); + VectorSet( pm->maxs, 15, 15, 16 ); + } + pm->ps->pm_flags |= PMF_DUCKED; + pm->ps->viewheight = CROUCH_VIEWHEIGHT; + return; + } + pm->ps->pm_flags &= ~PMF_INVULEXPAND; + + pm->mins[0] = -15; + pm->mins[1] = -15; + + pm->maxs[0] = 15; + pm->maxs[1] = 15; + + pm->mins[2] = MINS_Z; + + if (pm->ps->pm_type == PM_DEAD) + { + pm->maxs[2] = -8; + pm->ps->viewheight = DEAD_VIEWHEIGHT; + return; + } + + if (pm->cmd.upmove < 0) + { // duck + pm->ps->pm_flags |= PMF_DUCKED; + } + else + { // stand up if possible + if (pm->ps->pm_flags & PMF_DUCKED) + { + // try to stand up + pm->maxs[2] = 32; + pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, pm->ps->origin, pm->ps->clientNum, pm->tracemask ); + if (!trace.allsolid) + pm->ps->pm_flags &= ~PMF_DUCKED; + } + } + + if (pm->ps->pm_flags & PMF_DUCKED) + { + pm->maxs[2] = 16; + pm->ps->viewheight = CROUCH_VIEWHEIGHT; + } + else + { + pm->maxs[2] = 32; + pm->ps->viewheight = DEFAULT_VIEWHEIGHT; + } +} + + + +//=================================================================== + + +/* +=============== +PM_Footsteps +=============== +*/ +static void PM_Footsteps( void ) { + float bobmove; + int old; + qboolean footstep; + + // + // 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->groundEntityNum == ENTITYNUM_NONE ) { + + if ( pm->ps->powerups[PW_INVULNERABILITY] ) { + PM_ContinueLegsAnim( LEGS_IDLECR ); + } + // airborne leaves position in cycle intact, but doesn't advance + if ( pm->waterlevel > 1 ) { + PM_ContinueLegsAnim( LEGS_SWIM ); + } + return; + } + + // if not trying to move + if ( !pm->cmd.forwardmove && !pm->cmd.rightmove ) { + if ( pm->xyspeed < 5 ) { + pm->ps->bobCycle = 0; // start at beginning of cycle again + if ( pm->ps->pm_flags & PMF_DUCKED ) { + PM_ContinueLegsAnim( LEGS_IDLECR ); + } else { + PM_ContinueLegsAnim( LEGS_IDLE ); + } + } + return; + } + + + footstep = qfalse; + + if ( pm->ps->pm_flags & PMF_DUCKED ) { + bobmove = 0.5; // ducked characters bob much faster + if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) { + PM_ContinueLegsAnim( LEGS_BACKCR ); + } + else { + PM_ContinueLegsAnim( LEGS_WALKCR ); + } + // ducked characters never play footsteps + /* + } else if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) { + if ( !( pm->cmd.buttons & BUTTON_WALKING ) ) { + bobmove = 0.4; // faster speeds bob faster + footstep = qtrue; + } else { + bobmove = 0.3; + } + PM_ContinueLegsAnim( LEGS_BACK ); + */ + } else { + if ( !( pm->cmd.buttons & BUTTON_WALKING ) ) { + bobmove = 0.4f; // faster speeds bob faster + if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) { + PM_ContinueLegsAnim( LEGS_BACK ); + } + else { + PM_ContinueLegsAnim( LEGS_RUN ); + } + footstep = qtrue; + } else { + bobmove = 0.3f; // walking bobs slow + if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) { + PM_ContinueLegsAnim( LEGS_BACKWALK ); + } + else { + PM_ContinueLegsAnim( LEGS_WALK ); + } + } + } + + // 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->waterlevel == 0 ) { + // on ground will only play sounds if running + if ( footstep && !pm->noFootsteps ) { + PM_AddEvent( PM_FootstepForSurface() ); + } + } else if ( pm->waterlevel == 1 ) { + // splashing + PM_AddEvent( EV_FOOTSPLASH ); + } else if ( pm->waterlevel == 2 ) { + // wading / swimming at surface + PM_AddEvent( EV_SWIM ); + } else if ( pm->waterlevel == 3 ) { + // no sound when completely underwater + + } + } +} + +/* +============== +PM_WaterEvents + +Generate sound events for entering and leaving water +============== +*/ +static void PM_WaterEvents( void ) { // FIXME? + // + // if just entered a water volume, play a sound + // + if (!pml.previous_waterlevel && pm->waterlevel) { + PM_AddEvent( EV_WATER_TOUCH ); + } + + // + // if just completely exited a water volume, play a sound + // + if (pml.previous_waterlevel && !pm->waterlevel) { + PM_AddEvent( EV_WATER_LEAVE ); + } + + // + // check for head just going under water + // + if (pml.previous_waterlevel != 3 && pm->waterlevel == 3) { + PM_AddEvent( EV_WATER_UNDER ); + } + + // + // check for head just coming out of water + // + if (pml.previous_waterlevel == 3 && pm->waterlevel != 3) { + PM_AddEvent( EV_WATER_CLEAR ); + } +} + + +/* +=============== +PM_BeginWeaponChange +=============== +*/ +static void PM_BeginWeaponChange( int weapon ) { + if ( weapon <= WP_NONE || weapon >= WP_NUM_WEAPONS ) { + return; + } + + if ( !( pm->ps->stats[STAT_WEAPONS] & ( 1 << weapon ) ) ) { + return; + } + + if ( pm->ps->weaponstate == WEAPON_DROPPING ) { + return; + } + + PM_AddEvent( EV_CHANGE_WEAPON ); + pm->ps->weaponstate = WEAPON_DROPPING; + pm->ps->weaponTime += 200; + PM_StartTorsoAnim( TORSO_DROP ); +} + + +/* +=============== +PM_FinishWeaponChange +=============== +*/ +static void PM_FinishWeaponChange( void ) { + int weapon; + + weapon = pm->cmd.weapon; + if ( weapon < WP_NONE || weapon >= WP_NUM_WEAPONS ) { + weapon = WP_NONE; + } + + if ( !( pm->ps->stats[STAT_WEAPONS] & ( 1 << weapon ) ) ) { + weapon = WP_NONE; + } + + pm->ps->weapon = weapon; + pm->ps->weaponstate = WEAPON_RAISING; + pm->ps->weaponTime += 250; + PM_StartTorsoAnim( TORSO_RAISE ); +} + + +/* +============== +PM_TorsoAnimation + +============== +*/ +static void PM_TorsoAnimation( void ) { + if ( pm->ps->weaponstate == WEAPON_READY ) { + if ( pm->ps->weapon == WP_GAUNTLET ) { + PM_ContinueTorsoAnim( TORSO_STAND2 ); + } else { + PM_ContinueTorsoAnim( TORSO_STAND ); + } + return; + } +} + + +/* +============== +PM_Weapon + +Generates weapon events and modifes the weapon counter +============== +*/ +static void PM_Weapon( void ) { + int addTime; + + // don't allow attack until all buttons are up + if ( pm->ps->pm_flags & PMF_RESPAWNED ) { + return; + } + + // ignore if spectator + if ( pm->ps->persistant[PERS_TEAM] == TEAM_SPECTATOR ) { + return; + } + + // check for dead player + if ( pm->ps->stats[STAT_HEALTH] <= 0 ) { + pm->ps->weapon = WP_NONE; + return; + } + + // check for item using + if ( pm->cmd.buttons & BUTTON_USE_HOLDABLE ) { + if ( ! ( pm->ps->pm_flags & PMF_USE_ITEM_HELD ) ) { + if ( bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag == HI_MEDKIT + && pm->ps->stats[STAT_HEALTH] >= (pm->ps->stats[STAT_MAX_HEALTH] + 25) ) { + // don't use medkit if at max health + } else { + pm->ps->pm_flags |= PMF_USE_ITEM_HELD; + PM_AddEvent( EV_USE_ITEM0 + bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag ); + pm->ps->stats[STAT_HOLDABLE_ITEM] = 0; + } + return; + } + } else { + pm->ps->pm_flags &= ~PMF_USE_ITEM_HELD; + } + + + // make weapon function + if ( pm->ps->weaponTime > 0 ) { + pm->ps->weaponTime -= pml.msec; + } + + // 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->weaponstate == WEAPON_RAISING ) { + pm->ps->weaponstate = WEAPON_READY; + if ( pm->ps->weapon == WP_GAUNTLET ) { + PM_StartTorsoAnim( TORSO_STAND2 ); + } else { + PM_StartTorsoAnim( TORSO_STAND ); + } + return; + } + + // check for fire + if ( ! (pm->cmd.buttons & BUTTON_ATTACK) ) { + pm->ps->weaponTime = 0; + pm->ps->weaponstate = WEAPON_READY; + return; + } + + // start the animation even if out of ammo + if ( pm->ps->weapon == WP_GAUNTLET ) { + // the guantlet only "fires" when it actually hits something + if ( !pm->gauntletHit ) { + pm->ps->weaponTime = 0; + pm->ps->weaponstate = WEAPON_READY; + return; + } + PM_StartTorsoAnim( TORSO_ATTACK2 ); + } else { + PM_StartTorsoAnim( TORSO_ATTACK ); + } + + pm->ps->weaponstate = WEAPON_FIRING; + + // check for out of ammo + if ( ! pm->ps->ammo[ pm->ps->weapon ] ) { + PM_AddEvent( EV_NOAMMO ); + pm->ps->weaponTime += 500; + return; + } + + // take an ammo away if not infinite + if ( pm->ps->ammo[ pm->ps->weapon ] != -1 ) { + pm->ps->ammo[ pm->ps->weapon ]--; + } + + // fire weapon + PM_AddEvent( EV_FIRE_WEAPON ); + + switch( pm->ps->weapon ) { + default: + case WP_GAUNTLET: + addTime = 400; + break; + case WP_LIGHTNING: + addTime = 50; + break; + case WP_SHOTGUN: + addTime = 1000; + break; + case WP_MACHINEGUN: + addTime = 100; + break; + case WP_GRENADE_LAUNCHER: + addTime = 800; + break; + case WP_ROCKET_LAUNCHER: + addTime = 800; + break; + case WP_PLASMAGUN: + addTime = 100; + break; + case WP_RAILGUN: + addTime = 1500; + break; + case WP_BFG: + addTime = 200; + break; + case WP_GRAPPLING_HOOK: + addTime = 400; + break; +#ifdef MISSIONPACK + case WP_NAILGUN: + addTime = 1000; + break; + case WP_PROX_LAUNCHER: + addTime = 800; + break; + case WP_CHAINGUN: + addTime = 30; + break; +#endif + } + +#ifdef MISSIONPACK + if( bg_itemlist[pm->ps->stats[STAT_PERSISTANT_POWERUP]].giTag == PW_SCOUT ) { + addTime /= 1.5; + } + else + if( bg_itemlist[pm->ps->stats[STAT_PERSISTANT_POWERUP]].giTag == PW_AMMOREGEN ) { + addTime /= 1.3; + } + else +#endif + if ( pm->ps->powerups[PW_HASTE] ) { + addTime /= 1.3; + } + + pm->ps->weaponTime += addTime; +} + +/* +================ +PM_Animate +================ +*/ + +static void PM_Animate( void ) { + if ( pm->cmd.buttons & BUTTON_GESTURE ) { + if ( pm->ps->torsoTimer == 0 ) { + PM_StartTorsoAnim( TORSO_GESTURE ); + pm->ps->torsoTimer = TIMER_GESTURE; + PM_AddEvent( EV_TAUNT ); + } +#ifdef MISSIONPACK + } else if ( pm->cmd.buttons & BUTTON_GETFLAG ) { + if ( pm->ps->torsoTimer == 0 ) { + PM_StartTorsoAnim( TORSO_GETFLAG ); + pm->ps->torsoTimer = 600; //TIMER_GESTURE; + } + } else if ( pm->cmd.buttons & BUTTON_GUARDBASE ) { + if ( pm->ps->torsoTimer == 0 ) { + PM_StartTorsoAnim( TORSO_GUARDBASE ); + pm->ps->torsoTimer = 600; //TIMER_GESTURE; + } + } else if ( pm->cmd.buttons & BUTTON_PATROL ) { + if ( pm->ps->torsoTimer == 0 ) { + PM_StartTorsoAnim( TORSO_PATROL ); + pm->ps->torsoTimer = 600; //TIMER_GESTURE; + } + } else if ( pm->cmd.buttons & BUTTON_FOLLOWME ) { + if ( pm->ps->torsoTimer == 0 ) { + PM_StartTorsoAnim( TORSO_FOLLOWME ); + pm->ps->torsoTimer = 600; //TIMER_GESTURE; + } + } else if ( pm->cmd.buttons & BUTTON_AFFIRMATIVE ) { + if ( pm->ps->torsoTimer == 0 ) { + PM_StartTorsoAnim( TORSO_AFFIRMATIVE); + pm->ps->torsoTimer = 600; //TIMER_GESTURE; + } + } else if ( pm->cmd.buttons & BUTTON_NEGATIVE ) { + if ( pm->ps->torsoTimer == 0 ) { + PM_StartTorsoAnim( TORSO_NEGATIVE ); + pm->ps->torsoTimer = 600; //TIMER_GESTURE; + } +#endif + } +} + + +/* +================ +PM_DropTimers +================ +*/ +static void PM_DropTimers( void ) { + // drop misc timing counter + if ( pm->ps->pm_time ) { + if ( pml.msec >= pm->ps->pm_time ) { + pm->ps->pm_flags &= ~PMF_ALL_TIMES; + pm->ps->pm_time = 0; + } else { + pm->ps->pm_time -= pml.msec; + } + } + + // drop animation counter + if ( pm->ps->legsTimer > 0 ) { + pm->ps->legsTimer -= pml.msec; + if ( pm->ps->legsTimer < 0 ) { + pm->ps->legsTimer = 0; + } + } + + if ( pm->ps->torsoTimer > 0 ) { + pm->ps->torsoTimer -= pml.msec; + if ( pm->ps->torsoTimer < 0 ) { + pm->ps->torsoTimer = 0; + } + } +} + +/* +================ +PM_UpdateViewAngles + +This can be used as another entry point when only the viewangles +are being updated isntead of a full move +================ +*/ +void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd ) { + short temp; + int i; + + if ( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPINTERMISSION) { + return; // no view changes at all + } + + if ( ps->pm_type != PM_SPECTATOR && ps->stats[STAT_HEALTH] <= 0 ) { + return; // no view changes at all + } + + // circularly clamp the angles with deltas + for (i=0 ; i<3 ; i++) { + temp = cmd->angles[i] + ps->delta_angles[i]; + if ( i == PITCH ) { + // don't let the player look up or down more than 90 degrees + if ( temp > 16000 ) { + ps->delta_angles[i] = 16000 - cmd->angles[i]; + temp = 16000; + } else if ( temp < -16000 ) { + ps->delta_angles[i] = -16000 - cmd->angles[i]; + temp = -16000; + } + } + ps->viewangles[i] = SHORT2ANGLE(temp); + } + +} + + +/* +================ +PmoveSingle + +================ +*/ +void trap_SnapVector( float *v ); + +void PmoveSingle (pmove_t *pmove) { + 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; + pm->watertype = 0; + pm->waterlevel = 0; + + if ( pm->ps->stats[STAT_HEALTH] <= 0 ) { + pm->tracemask &= ~CONTENTS_BODY; // corpses can fly through bodies + } + + // make sure walking button is clear if they are running, to avoid + // proxy no-footsteps cheats + if ( abs( pm->cmd.forwardmove ) > 64 || abs( pm->cmd.rightmove ) > 64 ) { + pm->cmd.buttons &= ~BUTTON_WALKING; + } + + // set the talk balloon flag + if ( pm->cmd.buttons & BUTTON_TALK ) { + pm->ps->eFlags |= EF_TALK; + } else { + pm->ps->eFlags &= ~EF_TALK; + } + + // set the firing flag for continuous beam weapons + if ( !(pm->ps->pm_flags & PMF_RESPAWNED) && pm->ps->pm_type != PM_INTERMISSION + && ( pm->cmd.buttons & BUTTON_ATTACK ) && pm->ps->ammo[ pm->ps->weapon ] ) { + pm->ps->eFlags |= EF_FIRING; + } else { + pm->ps->eFlags &= ~EF_FIRING; + } + + // clear the respawned flag if attack and use are cleared + if ( pm->ps->stats[STAT_HEALTH] > 0 && + !( pm->cmd.buttons & (BUTTON_ATTACK | BUTTON_USE_HOLDABLE) ) ) { + pm->ps->pm_flags &= ~PMF_RESPAWNED; + } + + // if talk button is down, dissallow all other input + // this is to prevent any possible intercept proxy from + // adding fake talk balloons + if ( pmove->cmd.buttons & BUTTON_TALK ) { + // keep the talk button set tho for when the cmd.serverTime > 66 msec + // and the same cmd is used multiple times in Pmove + pmove->cmd.buttons = BUTTON_TALK; + pmove->cmd.forwardmove = 0; + pmove->cmd.rightmove = 0; + pmove->cmd.upmove = 0; + } + + // clear all pmove local vars + memset (&pml, 0, sizeof(pml)); + + // determine the time + pml.msec = pmove->cmd.serverTime - pm->ps->commandTime; + if ( pml.msec < 1 ) { + pml.msec = 1; + } else if ( pml.msec > 200 ) { + pml.msec = 200; + } + 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; + + // update the viewangles + PM_UpdateViewAngles( pm->ps, &pm->cmd ); + + 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->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 || pm->ps->pm_type == PM_SPINTERMISSION) { + return; // no movement at all + } + + // set watertype, and waterlevel + PM_SetWaterLevel(); + pml.previous_waterlevel = pmove->waterlevel; + + // set mins, maxs, and viewheight + PM_CheckDuck (); + + // set groundentity + PM_GroundTrace(); + + if ( pm->ps->pm_type == PM_DEAD ) { + PM_DeadMove (); + } + + PM_DropTimers(); + +#ifdef MISSIONPACK + if ( pm->ps->powerups[PW_INVULNERABILITY] ) { + PM_InvulnerabilityMove(); + } else +#endif + if ( pm->ps->powerups[PW_FLIGHT] ) { + // flight powerup doesn't allow jump and has different friction + PM_FlyMove(); + } else if (pm->ps->pm_flags & PMF_GRAPPLE_PULL) { + PM_GrappleMove(); + // We can wiggle a bit + PM_AirMove(); + } else if (pm->ps->pm_flags & PMF_TIME_WATERJUMP) { + PM_WaterJumpMove(); + } else if ( pm->waterlevel > 1 ) { + // swimming + PM_WaterMove(); + } else if ( pml.walking ) { + // walking on ground + PM_WalkMove(); + } else { + // airborne + PM_AirMove(); + } + + PM_Animate(); + + // set groundentity, watertype, and waterlevel + PM_GroundTrace(); + PM_SetWaterLevel(); + + // weapons + PM_Weapon(); + + // torso animation + PM_TorsoAnimation(); + + // footstep events / legs animations + PM_Footsteps(); + + // entering / leaving water splashes + PM_WaterEvents(); + + // snap some parts of playerstate to save network bandwidth + trap_SnapVector( pm->ps->velocity ); +} + + +/* +================ +Pmove + +Can be called by either the server or the client +================ +*/ +void Pmove (pmove_t *pmove) { + int finalTime; + + finalTime = pmove->cmd.serverTime; + + if ( finalTime < pmove->ps->commandTime ) { + return; // should not happen + } + + if ( finalTime > pmove->ps->commandTime + 1000 ) { + pmove->ps->commandTime = finalTime - 1000; + } + + pmove->ps->pmove_framecount = (pmove->ps->pmove_framecount+1) & ((1<ps->commandTime != finalTime ) { + int msec; + + msec = finalTime - pmove->ps->commandTime; + + if ( pmove->pmove_fixed ) { + if ( msec > pmove->pmove_msec ) { + msec = pmove->pmove_msec; + } + } + else { + if ( msec > 66 ) { + msec = 66; + } + } + pmove->cmd.serverTime = pmove->ps->commandTime + msec; + PmoveSingle( pmove ); + + if ( pmove->ps->pm_flags & PMF_JUMP_HELD ) { + pmove->cmd.upmove = 20; + } + } + + //PM_CheckStuck(); + +} + diff --git a/code/game/bg_public.h b/code/game/bg_public.h index 0dd59db..e6c24b5 100755 --- a/code/game/bg_public.h +++ b/code/game/bg_public.h @@ -1,738 +1,738 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// bg_public.h -- definitions shared by both the server game and client game modules - -// because games can change separately from the main system version, we need a -// second version that must match between game and cgame - -#define GAME_VERSION "baseq3-1" - -#define DEFAULT_GRAVITY 800 -#define GIB_HEALTH -40 -#define ARMOR_PROTECTION 0.66 - -#define MAX_ITEMS 256 - -#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 - -#define LIGHTNING_RANGE 768 - -#define SCORE_NOT_PRESENT -9999 // for the CS_SCORES[12] when only one player is present - -#define VOTE_TIME 30000 // 30 seconds before vote times out - -#define MINS_Z -24 -#define DEFAULT_VIEWHEIGHT 26 -#define CROUCH_VIEWHEIGHT 12 -#define DEAD_VIEWHEIGHT -16 - -// -// config strings are a general means of communicating variable length strings -// from the server to all connected clients. -// - -// CS_SERVERINFO and CS_SYSTEMINFO are defined in q_shared.h -#define CS_MUSIC 2 -#define CS_MESSAGE 3 // from the map worldspawn's message field -#define CS_MOTD 4 // g_motd string for server message of the day -#define CS_WARMUP 5 // server time when the match will be restarted -#define CS_SCORES1 6 -#define CS_SCORES2 7 -#define CS_VOTE_TIME 8 -#define CS_VOTE_STRING 9 -#define CS_VOTE_YES 10 -#define CS_VOTE_NO 11 - -#define CS_TEAMVOTE_TIME 12 -#define CS_TEAMVOTE_STRING 14 -#define CS_TEAMVOTE_YES 16 -#define CS_TEAMVOTE_NO 18 - -#define CS_GAME_VERSION 20 -#define CS_LEVEL_START_TIME 21 // so the timer only shows the current level -#define CS_INTERMISSION 22 // when 1, fraglimit/timelimit has been hit and intermission will start in a second or two -#define CS_FLAGSTATUS 23 // string indicating flag status in CTF -#define CS_SHADERSTATE 24 -#define CS_BOTINFO 25 - -#define CS_ITEMS 27 // string of 0's and 1's that tell which items are present - -#define CS_MODELS 32 -#define CS_SOUNDS (CS_MODELS+MAX_MODELS) -#define CS_PLAYERS (CS_SOUNDS+MAX_SOUNDS) -#define CS_LOCATIONS (CS_PLAYERS+MAX_CLIENTS) -#define CS_PARTICLES (CS_LOCATIONS+MAX_LOCATIONS) - -#define CS_MAX (CS_PARTICLES+MAX_LOCATIONS) - -#if (CS_MAX) > MAX_CONFIGSTRINGS -#error overflow: (CS_MAX) > MAX_CONFIGSTRINGS -#endif - -typedef enum { - GT_FFA, // free for all - GT_TOURNAMENT, // one on one tournament - GT_SINGLE_PLAYER, // single player ffa - - //-- team games go after this -- - - GT_TEAM, // team deathmatch - GT_CTF, // capture the flag - GT_1FCTF, - GT_OBELISK, - GT_HARVESTER, - GT_MAX_GAME_TYPE -} gametype_t; - -typedef enum { GENDER_MALE, GENDER_FEMALE, GENDER_NEUTER } gender_t; - -/* -=================================================================================== - -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 - PM_SPINTERMISSION // no movement or status bar -} pmtype_t; - -typedef enum { - WEAPON_READY, - WEAPON_RAISING, - WEAPON_DROPPING, - WEAPON_FIRING -} weaponstate_t; - -// pmove->pm_flags -#define PMF_DUCKED 1 -#define PMF_JUMP_HELD 2 -#define PMF_BACKWARDS_JUMP 8 // go into backwards land -#define PMF_BACKWARDS_RUN 16 // coast down to backwards run -#define PMF_TIME_LAND 32 // pm_time is time before rejump -#define PMF_TIME_KNOCKBACK 64 // pm_time is an air-accelerate only time -#define PMF_TIME_WATERJUMP 256 // pm_time is waterjump -#define PMF_RESPAWNED 512 // clear after attack and jump buttons come up -#define PMF_USE_ITEM_HELD 1024 -#define PMF_GRAPPLE_PULL 2048 // pull towards grapple location -#define PMF_FOLLOW 4096 // spectate following another player -#define PMF_SCOREBOARD 8192 // spectate as a scoreboard -#define PMF_INVULEXPAND 16384 // invulnerability sphere set to full size - -#define PMF_ALL_TIMES (PMF_TIME_WATERJUMP|PMF_TIME_LAND|PMF_TIME_KNOCKBACK) - -#define MAXTOUCH 32 -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 - qboolean gauntletHit; // true if a gauntlet attack would actually hit something - - int framecount; - - // results (out) - int numtouch; - int touchents[MAXTOUCH]; - - vec3_t mins, maxs; // bounding box size - - int watertype; - int waterlevel; - - float xyspeed; - - // for fixed msec Pmove - int pmove_fixed; - int pmove_msec; - - // callbacks to test the world - // these will be different functions during game and cgame - void (*trace)( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentMask ); - int (*pointcontents)( const vec3_t point, int passEntityNum ); -} pmove_t; - -// if a full pmove isn't done on the client, you can just update the angles -void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd ); -void Pmove (pmove_t *pmove); - -//=================================================================================== - - -// player_state->stats[] indexes -// NOTE: may not have more than 16 -typedef enum { - STAT_HEALTH, - STAT_HOLDABLE_ITEM, -#ifdef MISSIONPACK - STAT_PERSISTANT_POWERUP, -#endif - 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; - - -// player_state->persistant[] indexes -// these fields are the only part of player_state that isn't -// cleared on respawn -// NOTE: may not have more than 16 -typedef enum { - PERS_SCORE, // !!! MUST NOT CHANGE, SERVER AND GAME BOTH REFERENCE !!! - PERS_HITS, // total points damage inflicted so damage beeps can sound on change - PERS_RANK, // player rank or team rank - PERS_TEAM, // player team - PERS_SPAWN_COUNT, // incremented every respawn - PERS_PLAYEREVENTS, // 16 bits that can be flipped for events - PERS_ATTACKER, // clientnum of last damage inflicter - PERS_ATTACKEE_ARMOR, // health/armor of last person we attacked - PERS_KILLED, // count of the number of times you died - // player awards tracking - PERS_IMPRESSIVE_COUNT, // two railgun hits in a row - PERS_EXCELLENT_COUNT, // two successive kills in a short amount of time - PERS_DEFEND_COUNT, // defend awards - PERS_ASSIST_COUNT, // assist awards - PERS_GAUNTLET_FRAG_COUNT, // kills with the guantlet - PERS_CAPTURES // captures -} persEnum_t; - - -// entityState_t->eFlags -#define EF_DEAD 0x00000001 // don't draw a foe marker over players with EF_DEAD -#ifdef MISSIONPACK -#define EF_TICKING 0x00000002 // used to make players play the prox mine ticking sound -#endif -#define EF_TELEPORT_BIT 0x00000004 // toggled every time the origin abruptly changes -#define EF_AWARD_EXCELLENT 0x00000008 // draw an excellent sprite -#define EF_PLAYER_EVENT 0x00000010 -#define EF_BOUNCE 0x00000010 // for missiles -#define EF_BOUNCE_HALF 0x00000020 // for missiles -#define EF_AWARD_GAUNTLET 0x00000040 // draw a gauntlet sprite -#define EF_NODRAW 0x00000080 // may have an event, but no model (unspawned items) -#define EF_FIRING 0x00000100 // for lightning gun -#define EF_KAMIKAZE 0x00000200 -#define EF_MOVER_STOP 0x00000400 // will push otherwise -#define EF_AWARD_CAP 0x00000800 // draw the capture sprite -#define EF_TALK 0x00001000 // draw a talk balloon -#define EF_CONNECTION 0x00002000 // draw a connection trouble sprite -#define EF_VOTED 0x00004000 // already cast a vote -#define EF_AWARD_IMPRESSIVE 0x00008000 // draw an impressive sprite -#define EF_AWARD_DEFEND 0x00010000 // draw a defend sprite -#define EF_AWARD_ASSIST 0x00020000 // draw a assist sprite -#define EF_AWARD_DENIED 0x00040000 // denied -#define EF_TEAMVOTED 0x00080000 // already cast a team vote - -// NOTE: may not have more than 16 -typedef enum { - PW_NONE, - - PW_QUAD, - PW_BATTLESUIT, - PW_HASTE, - PW_INVIS, - PW_REGEN, - PW_FLIGHT, - - PW_REDFLAG, - PW_BLUEFLAG, - PW_NEUTRALFLAG, - - PW_SCOUT, - PW_GUARD, - PW_DOUBLER, - PW_AMMOREGEN, - PW_INVULNERABILITY, - - PW_NUM_POWERUPS - -} powerup_t; - -typedef enum { - HI_NONE, - - HI_TELEPORTER, - HI_MEDKIT, - HI_KAMIKAZE, - HI_PORTAL, - HI_INVULNERABILITY, - - HI_NUM_HOLDABLE -} holdable_t; - - -typedef enum { - WP_NONE, - - WP_GAUNTLET, - WP_MACHINEGUN, - WP_SHOTGUN, - WP_GRENADE_LAUNCHER, - WP_ROCKET_LAUNCHER, - WP_LIGHTNING, - WP_RAILGUN, - WP_PLASMAGUN, - WP_BFG, - WP_GRAPPLING_HOOK, -#ifdef MISSIONPACK - WP_NAILGUN, - WP_PROX_LAUNCHER, - WP_CHAINGUN, -#endif - - WP_NUM_WEAPONS -} weapon_t; - - -// reward sounds (stored in ps->persistant[PERS_PLAYEREVENTS]) -#define PLAYEREVENT_DENIEDREWARD 0x0001 -#define PLAYEREVENT_GAUNTLETREWARD 0x0002 -#define PLAYEREVENT_HOLYSHIT 0x0004 - -// entityState_t->event values -// entity events are for effects that take place reletive -// to an existing entities origin. Very network efficient. - -// two bits at the top of the entityState->event field -// will be incremented with each change in the event so -// that an identical event started twice in a row can -// be distinguished. And off the value with ~EV_EVENT_BITS -// to retrieve the actual event number -#define EV_EVENT_BIT1 0x00000100 -#define EV_EVENT_BIT2 0x00000200 -#define EV_EVENT_BITS (EV_EVENT_BIT1|EV_EVENT_BIT2) - -#define EVENT_VALID_MSEC 300 - -typedef enum { - 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_PAD, // boing sound at origin, jump sound on player - - EV_JUMP, - EV_WATER_TOUCH, // foot touches - EV_WATER_LEAVE, // foot leaves - EV_WATER_UNDER, // head touches - EV_WATER_CLEAR, // head leaves - - EV_ITEM_PICKUP, // normal item pickups are predictable - EV_GLOBAL_ITEM_PICKUP, // powerup / team sounds are broadcast to everyone - - EV_NOAMMO, - EV_CHANGE_WEAPON, - EV_FIRE_WEAPON, - - EV_USE_ITEM0, - EV_USE_ITEM1, - EV_USE_ITEM2, - EV_USE_ITEM3, - EV_USE_ITEM4, - EV_USE_ITEM5, - EV_USE_ITEM6, - EV_USE_ITEM7, - EV_USE_ITEM8, - EV_USE_ITEM9, - EV_USE_ITEM10, - EV_USE_ITEM11, - EV_USE_ITEM12, - EV_USE_ITEM13, - EV_USE_ITEM14, - EV_USE_ITEM15, - - EV_ITEM_RESPAWN, - EV_ITEM_POP, - EV_PLAYER_TELEPORT_IN, - EV_PLAYER_TELEPORT_OUT, - - EV_GRENADE_BOUNCE, // eventParm will be the soundindex - - EV_GENERAL_SOUND, - EV_GLOBAL_SOUND, // no attenuation - EV_GLOBAL_TEAM_SOUND, - - EV_BULLET_HIT_FLESH, - EV_BULLET_HIT_WALL, - - EV_MISSILE_HIT, - EV_MISSILE_MISS, - EV_MISSILE_MISS_METAL, - EV_RAILTRAIL, - EV_SHOTGUN, - EV_BULLET, // otherEntity is the shooter - - EV_PAIN, - EV_DEATH1, - EV_DEATH2, - EV_DEATH3, - EV_OBITUARY, - - EV_POWERUP_QUAD, - EV_POWERUP_BATTLESUIT, - EV_POWERUP_REGEN, - - EV_GIB_PLAYER, // gib a previously living player - EV_SCOREPLUM, // score plum - -//#ifdef MISSIONPACK - EV_PROXIMITY_MINE_STICK, - EV_PROXIMITY_MINE_TRIGGER, - EV_KAMIKAZE, // kamikaze explodes - EV_OBELISKEXPLODE, // obelisk explodes - EV_OBELISKPAIN, // obelisk is in pain - EV_INVUL_IMPACT, // invulnerability sphere impact - EV_JUICED, // invulnerability juiced effect - EV_LIGHTNINGBOLT, // lightning bolt bounced of invulnerability sphere -//#endif - - EV_DEBUG_LINE, - EV_STOPLOOPINGSOUND, - EV_TAUNT, - EV_TAUNT_YES, - EV_TAUNT_NO, - EV_TAUNT_FOLLOWME, - EV_TAUNT_GETFLAG, - EV_TAUNT_GUARDBASE, - EV_TAUNT_PATROL - -} entity_event_t; - - -typedef enum { - GTS_RED_CAPTURE, - GTS_BLUE_CAPTURE, - GTS_RED_RETURN, - GTS_BLUE_RETURN, - GTS_RED_TAKEN, - GTS_BLUE_TAKEN, - GTS_REDOBELISK_ATTACKED, - GTS_BLUEOBELISK_ATTACKED, - GTS_REDTEAM_SCORED, - GTS_BLUETEAM_SCORED, - GTS_REDTEAM_TOOK_LEAD, - GTS_BLUETEAM_TOOK_LEAD, - GTS_TEAMS_ARE_TIED, - GTS_KAMIKAZE -} global_team_sound_t; - -// animations -typedef enum { - BOTH_DEATH1, - BOTH_DEAD1, - BOTH_DEATH2, - BOTH_DEAD2, - BOTH_DEATH3, - BOTH_DEAD3, - - TORSO_GESTURE, - - TORSO_ATTACK, - TORSO_ATTACK2, - - TORSO_DROP, - TORSO_RAISE, - - TORSO_STAND, - TORSO_STAND2, - - LEGS_WALKCR, - LEGS_WALK, - LEGS_RUN, - LEGS_BACK, - LEGS_SWIM, - - LEGS_JUMP, - LEGS_LAND, - - LEGS_JUMPB, - LEGS_LANDB, - - LEGS_IDLE, - LEGS_IDLECR, - - LEGS_TURN, - - TORSO_GETFLAG, - TORSO_GUARDBASE, - TORSO_PATROL, - TORSO_FOLLOWME, - TORSO_AFFIRMATIVE, - TORSO_NEGATIVE, - - MAX_ANIMATIONS, - - LEGS_BACKCR, - LEGS_BACKWALK, - FLAG_RUN, - FLAG_STAND, - FLAG_STAND2RUN, - - MAX_TOTALANIMATIONS -} animNumber_t; - - -typedef struct animation_s { - int firstFrame; - int numFrames; - int loopFrames; // 0 to numFrames - int frameLerp; // msec between frames - int initialLerp; // msec to get to first frame - int reversed; // true if animation is reversed - int flipflop; // true if animation should flipflop back to base -} animation_t; - - -// flip the togglebit every time an animation -// changes so a restart of the same anim can be detected -#define ANIM_TOGGLEBIT 128 - - -typedef enum { - TEAM_FREE, - TEAM_RED, - TEAM_BLUE, - TEAM_SPECTATOR, - - TEAM_NUM_TEAMS -} team_t; - -// Time between location updates -#define TEAM_LOCATION_UPDATE_TIME 1000 - -// How many players on the overlay -#define TEAM_MAXOVERLAY 32 - -//team task -typedef enum { - TEAMTASK_NONE, - TEAMTASK_OFFENSE, - TEAMTASK_DEFENSE, - TEAMTASK_PATROL, - TEAMTASK_FOLLOW, - TEAMTASK_RETRIEVE, - TEAMTASK_ESCORT, - TEAMTASK_CAMP -} teamtask_t; - -// means of death -typedef enum { - MOD_UNKNOWN, - MOD_SHOTGUN, - MOD_GAUNTLET, - MOD_MACHINEGUN, - MOD_GRENADE, - MOD_GRENADE_SPLASH, - MOD_ROCKET, - MOD_ROCKET_SPLASH, - MOD_PLASMA, - MOD_PLASMA_SPLASH, - MOD_RAILGUN, - MOD_LIGHTNING, - MOD_BFG, - MOD_BFG_SPLASH, - MOD_WATER, - MOD_SLIME, - MOD_LAVA, - MOD_CRUSH, - MOD_TELEFRAG, - MOD_FALLING, - MOD_SUICIDE, - MOD_TARGET_LASER, - MOD_TRIGGER_HURT, -#ifdef MISSIONPACK - MOD_NAIL, - MOD_CHAINGUN, - MOD_PROXIMITY_MINE, - MOD_KAMIKAZE, - MOD_JUICED, -#endif - MOD_GRAPPLE -} meansOfDeath_t; - - -//--------------------------------------------------------- - -// gitem_t->type -typedef enum { - IT_BAD, - IT_WEAPON, // EFX: rotate + upscale + minlight - IT_AMMO, // EFX: rotate - IT_ARMOR, // EFX: rotate + minlight - IT_HEALTH, // EFX: static external sphere + rotating internal - IT_POWERUP, // instant on, timer based - // EFX: rotate + external ring that rotates - IT_HOLDABLE, // single use, holdable item - // EFX: rotate + bob - IT_PERSISTANT_POWERUP, - IT_TEAM -} itemType_t; - -#define MAX_ITEM_MODELS 4 - -typedef struct gitem_s { - char *classname; // spawning name - char *pickup_sound; - char *world_model[MAX_ITEM_MODELS]; - - char *icon; - char *pickup_name; // for printing on pickup - - int quantity; // for ammo how much, or duration of powerup - itemType_t giType; // IT_* flags - - int giTag; - - char *precaches; // string of all models and images this item will use - char *sounds; // string of all sounds this item will use -} gitem_t; - -// included in both the game dll and the client -extern gitem_t bg_itemlist[]; -extern int bg_numItems; - -gitem_t *BG_FindItem( const char *pickupName ); -gitem_t *BG_FindItemForWeapon( weapon_t weapon ); -gitem_t *BG_FindItemForPowerup( powerup_t pw ); -gitem_t *BG_FindItemForHoldable( holdable_t pw ); -#define ITEM_INDEX(x) ((x)-bg_itemlist) - -qboolean BG_CanItemBeGrabbed( int gametype, const entityState_t *ent, const playerState_t *ps ); - - -// g_dmflags->integer flags -#define DF_NO_FALLING 8 -#define DF_FIXED_FOV 16 -#define DF_NO_FOOTSTEPS 32 - -// content masks -#define MASK_ALL (-1) -#define MASK_SOLID (CONTENTS_SOLID) -#define MASK_PLAYERSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_BODY) -#define MASK_DEADSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP) -#define MASK_WATER (CONTENTS_WATER|CONTENTS_LAVA|CONTENTS_SLIME) -#define MASK_OPAQUE (CONTENTS_SOLID|CONTENTS_SLIME|CONTENTS_LAVA) -#define MASK_SHOT (CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_CORPSE) - - -// -// 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_GRAPPLE, // grapple hooked on wall - ET_TEAM, - - 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 BG_EvaluateTrajectory( const trajectory_t *tr, int atTime, vec3_t result ); -void BG_EvaluateTrajectoryDelta( const trajectory_t *tr, int atTime, vec3_t result ); - -void BG_AddPredictableEventToPlayerstate( int newEvent, int eventParm, playerState_t *ps ); - -void BG_TouchJumpPad( playerState_t *ps, entityState_t *jumppad ); - -void BG_PlayerStateToEntityState( playerState_t *ps, entityState_t *s, qboolean snap ); -void BG_PlayerStateToEntityStateExtraPolate( playerState_t *ps, entityState_t *s, int time, qboolean snap ); - -qboolean BG_PlayerTouchesItem( playerState_t *ps, entityState_t *item, int atTime ); - - -#define ARENAS_PER_TIER 4 -#define MAX_ARENAS 1024 -#define MAX_ARENAS_TEXT 8192 - -#define MAX_BOTS 1024 -#define MAX_BOTS_TEXT 8192 - - -// Kamikaze - -// 1st shockwave times -#define KAMI_SHOCKWAVE_STARTTIME 0 -#define KAMI_SHOCKWAVEFADE_STARTTIME 1500 -#define KAMI_SHOCKWAVE_ENDTIME 2000 -// explosion/implosion times -#define KAMI_EXPLODE_STARTTIME 250 -#define KAMI_IMPLODE_STARTTIME 2000 -#define KAMI_IMPLODE_ENDTIME 2250 -// 2nd shockwave times -#define KAMI_SHOCKWAVE2_STARTTIME 2000 -#define KAMI_SHOCKWAVE2FADE_STARTTIME 2500 -#define KAMI_SHOCKWAVE2_ENDTIME 3000 -// radius of the models without scaling -#define KAMI_SHOCKWAVEMODEL_RADIUS 88 -#define KAMI_BOOMSPHEREMODEL_RADIUS 72 -// maximum radius of the models during the effect -#define KAMI_SHOCKWAVE_MAXRADIUS 1320 -#define KAMI_BOOMSPHERE_MAXRADIUS 720 -#define KAMI_SHOCKWAVE2_MAXRADIUS 704 - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// bg_public.h -- definitions shared by both the server game and client game modules + +// because games can change separately from the main system version, we need a +// second version that must match between game and cgame + +#define GAME_VERSION "baseq3-1" + +#define DEFAULT_GRAVITY 800 +#define GIB_HEALTH -40 +#define ARMOR_PROTECTION 0.66 + +#define MAX_ITEMS 256 + +#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 + +#define LIGHTNING_RANGE 768 + +#define SCORE_NOT_PRESENT -9999 // for the CS_SCORES[12] when only one player is present + +#define VOTE_TIME 30000 // 30 seconds before vote times out + +#define MINS_Z -24 +#define DEFAULT_VIEWHEIGHT 26 +#define CROUCH_VIEWHEIGHT 12 +#define DEAD_VIEWHEIGHT -16 + +// +// config strings are a general means of communicating variable length strings +// from the server to all connected clients. +// + +// CS_SERVERINFO and CS_SYSTEMINFO are defined in q_shared.h +#define CS_MUSIC 2 +#define CS_MESSAGE 3 // from the map worldspawn's message field +#define CS_MOTD 4 // g_motd string for server message of the day +#define CS_WARMUP 5 // server time when the match will be restarted +#define CS_SCORES1 6 +#define CS_SCORES2 7 +#define CS_VOTE_TIME 8 +#define CS_VOTE_STRING 9 +#define CS_VOTE_YES 10 +#define CS_VOTE_NO 11 + +#define CS_TEAMVOTE_TIME 12 +#define CS_TEAMVOTE_STRING 14 +#define CS_TEAMVOTE_YES 16 +#define CS_TEAMVOTE_NO 18 + +#define CS_GAME_VERSION 20 +#define CS_LEVEL_START_TIME 21 // so the timer only shows the current level +#define CS_INTERMISSION 22 // when 1, fraglimit/timelimit has been hit and intermission will start in a second or two +#define CS_FLAGSTATUS 23 // string indicating flag status in CTF +#define CS_SHADERSTATE 24 +#define CS_BOTINFO 25 + +#define CS_ITEMS 27 // string of 0's and 1's that tell which items are present + +#define CS_MODELS 32 +#define CS_SOUNDS (CS_MODELS+MAX_MODELS) +#define CS_PLAYERS (CS_SOUNDS+MAX_SOUNDS) +#define CS_LOCATIONS (CS_PLAYERS+MAX_CLIENTS) +#define CS_PARTICLES (CS_LOCATIONS+MAX_LOCATIONS) + +#define CS_MAX (CS_PARTICLES+MAX_LOCATIONS) + +#if (CS_MAX) > MAX_CONFIGSTRINGS +#error overflow: (CS_MAX) > MAX_CONFIGSTRINGS +#endif + +typedef enum { + GT_FFA, // free for all + GT_TOURNAMENT, // one on one tournament + GT_SINGLE_PLAYER, // single player ffa + + //-- team games go after this -- + + GT_TEAM, // team deathmatch + GT_CTF, // capture the flag + GT_1FCTF, + GT_OBELISK, + GT_HARVESTER, + GT_MAX_GAME_TYPE +} gametype_t; + +typedef enum { GENDER_MALE, GENDER_FEMALE, GENDER_NEUTER } gender_t; + +/* +=================================================================================== + +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 + PM_SPINTERMISSION // no movement or status bar +} pmtype_t; + +typedef enum { + WEAPON_READY, + WEAPON_RAISING, + WEAPON_DROPPING, + WEAPON_FIRING +} weaponstate_t; + +// pmove->pm_flags +#define PMF_DUCKED 1 +#define PMF_JUMP_HELD 2 +#define PMF_BACKWARDS_JUMP 8 // go into backwards land +#define PMF_BACKWARDS_RUN 16 // coast down to backwards run +#define PMF_TIME_LAND 32 // pm_time is time before rejump +#define PMF_TIME_KNOCKBACK 64 // pm_time is an air-accelerate only time +#define PMF_TIME_WATERJUMP 256 // pm_time is waterjump +#define PMF_RESPAWNED 512 // clear after attack and jump buttons come up +#define PMF_USE_ITEM_HELD 1024 +#define PMF_GRAPPLE_PULL 2048 // pull towards grapple location +#define PMF_FOLLOW 4096 // spectate following another player +#define PMF_SCOREBOARD 8192 // spectate as a scoreboard +#define PMF_INVULEXPAND 16384 // invulnerability sphere set to full size + +#define PMF_ALL_TIMES (PMF_TIME_WATERJUMP|PMF_TIME_LAND|PMF_TIME_KNOCKBACK) + +#define MAXTOUCH 32 +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 + qboolean gauntletHit; // true if a gauntlet attack would actually hit something + + int framecount; + + // results (out) + int numtouch; + int touchents[MAXTOUCH]; + + vec3_t mins, maxs; // bounding box size + + int watertype; + int waterlevel; + + float xyspeed; + + // for fixed msec Pmove + int pmove_fixed; + int pmove_msec; + + // callbacks to test the world + // these will be different functions during game and cgame + void (*trace)( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentMask ); + int (*pointcontents)( const vec3_t point, int passEntityNum ); +} pmove_t; + +// if a full pmove isn't done on the client, you can just update the angles +void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd ); +void Pmove (pmove_t *pmove); + +//=================================================================================== + + +// player_state->stats[] indexes +// NOTE: may not have more than 16 +typedef enum { + STAT_HEALTH, + STAT_HOLDABLE_ITEM, +#ifdef MISSIONPACK + STAT_PERSISTANT_POWERUP, +#endif + 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; + + +// player_state->persistant[] indexes +// these fields are the only part of player_state that isn't +// cleared on respawn +// NOTE: may not have more than 16 +typedef enum { + PERS_SCORE, // !!! MUST NOT CHANGE, SERVER AND GAME BOTH REFERENCE !!! + PERS_HITS, // total points damage inflicted so damage beeps can sound on change + PERS_RANK, // player rank or team rank + PERS_TEAM, // player team + PERS_SPAWN_COUNT, // incremented every respawn + PERS_PLAYEREVENTS, // 16 bits that can be flipped for events + PERS_ATTACKER, // clientnum of last damage inflicter + PERS_ATTACKEE_ARMOR, // health/armor of last person we attacked + PERS_KILLED, // count of the number of times you died + // player awards tracking + PERS_IMPRESSIVE_COUNT, // two railgun hits in a row + PERS_EXCELLENT_COUNT, // two successive kills in a short amount of time + PERS_DEFEND_COUNT, // defend awards + PERS_ASSIST_COUNT, // assist awards + PERS_GAUNTLET_FRAG_COUNT, // kills with the guantlet + PERS_CAPTURES // captures +} persEnum_t; + + +// entityState_t->eFlags +#define EF_DEAD 0x00000001 // don't draw a foe marker over players with EF_DEAD +#ifdef MISSIONPACK +#define EF_TICKING 0x00000002 // used to make players play the prox mine ticking sound +#endif +#define EF_TELEPORT_BIT 0x00000004 // toggled every time the origin abruptly changes +#define EF_AWARD_EXCELLENT 0x00000008 // draw an excellent sprite +#define EF_PLAYER_EVENT 0x00000010 +#define EF_BOUNCE 0x00000010 // for missiles +#define EF_BOUNCE_HALF 0x00000020 // for missiles +#define EF_AWARD_GAUNTLET 0x00000040 // draw a gauntlet sprite +#define EF_NODRAW 0x00000080 // may have an event, but no model (unspawned items) +#define EF_FIRING 0x00000100 // for lightning gun +#define EF_KAMIKAZE 0x00000200 +#define EF_MOVER_STOP 0x00000400 // will push otherwise +#define EF_AWARD_CAP 0x00000800 // draw the capture sprite +#define EF_TALK 0x00001000 // draw a talk balloon +#define EF_CONNECTION 0x00002000 // draw a connection trouble sprite +#define EF_VOTED 0x00004000 // already cast a vote +#define EF_AWARD_IMPRESSIVE 0x00008000 // draw an impressive sprite +#define EF_AWARD_DEFEND 0x00010000 // draw a defend sprite +#define EF_AWARD_ASSIST 0x00020000 // draw a assist sprite +#define EF_AWARD_DENIED 0x00040000 // denied +#define EF_TEAMVOTED 0x00080000 // already cast a team vote + +// NOTE: may not have more than 16 +typedef enum { + PW_NONE, + + PW_QUAD, + PW_BATTLESUIT, + PW_HASTE, + PW_INVIS, + PW_REGEN, + PW_FLIGHT, + + PW_REDFLAG, + PW_BLUEFLAG, + PW_NEUTRALFLAG, + + PW_SCOUT, + PW_GUARD, + PW_DOUBLER, + PW_AMMOREGEN, + PW_INVULNERABILITY, + + PW_NUM_POWERUPS + +} powerup_t; + +typedef enum { + HI_NONE, + + HI_TELEPORTER, + HI_MEDKIT, + HI_KAMIKAZE, + HI_PORTAL, + HI_INVULNERABILITY, + + HI_NUM_HOLDABLE +} holdable_t; + + +typedef enum { + WP_NONE, + + WP_GAUNTLET, + WP_MACHINEGUN, + WP_SHOTGUN, + WP_GRENADE_LAUNCHER, + WP_ROCKET_LAUNCHER, + WP_LIGHTNING, + WP_RAILGUN, + WP_PLASMAGUN, + WP_BFG, + WP_GRAPPLING_HOOK, +#ifdef MISSIONPACK + WP_NAILGUN, + WP_PROX_LAUNCHER, + WP_CHAINGUN, +#endif + + WP_NUM_WEAPONS +} weapon_t; + + +// reward sounds (stored in ps->persistant[PERS_PLAYEREVENTS]) +#define PLAYEREVENT_DENIEDREWARD 0x0001 +#define PLAYEREVENT_GAUNTLETREWARD 0x0002 +#define PLAYEREVENT_HOLYSHIT 0x0004 + +// entityState_t->event values +// entity events are for effects that take place reletive +// to an existing entities origin. Very network efficient. + +// two bits at the top of the entityState->event field +// will be incremented with each change in the event so +// that an identical event started twice in a row can +// be distinguished. And off the value with ~EV_EVENT_BITS +// to retrieve the actual event number +#define EV_EVENT_BIT1 0x00000100 +#define EV_EVENT_BIT2 0x00000200 +#define EV_EVENT_BITS (EV_EVENT_BIT1|EV_EVENT_BIT2) + +#define EVENT_VALID_MSEC 300 + +typedef enum { + 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_PAD, // boing sound at origin, jump sound on player + + EV_JUMP, + EV_WATER_TOUCH, // foot touches + EV_WATER_LEAVE, // foot leaves + EV_WATER_UNDER, // head touches + EV_WATER_CLEAR, // head leaves + + EV_ITEM_PICKUP, // normal item pickups are predictable + EV_GLOBAL_ITEM_PICKUP, // powerup / team sounds are broadcast to everyone + + EV_NOAMMO, + EV_CHANGE_WEAPON, + EV_FIRE_WEAPON, + + EV_USE_ITEM0, + EV_USE_ITEM1, + EV_USE_ITEM2, + EV_USE_ITEM3, + EV_USE_ITEM4, + EV_USE_ITEM5, + EV_USE_ITEM6, + EV_USE_ITEM7, + EV_USE_ITEM8, + EV_USE_ITEM9, + EV_USE_ITEM10, + EV_USE_ITEM11, + EV_USE_ITEM12, + EV_USE_ITEM13, + EV_USE_ITEM14, + EV_USE_ITEM15, + + EV_ITEM_RESPAWN, + EV_ITEM_POP, + EV_PLAYER_TELEPORT_IN, + EV_PLAYER_TELEPORT_OUT, + + EV_GRENADE_BOUNCE, // eventParm will be the soundindex + + EV_GENERAL_SOUND, + EV_GLOBAL_SOUND, // no attenuation + EV_GLOBAL_TEAM_SOUND, + + EV_BULLET_HIT_FLESH, + EV_BULLET_HIT_WALL, + + EV_MISSILE_HIT, + EV_MISSILE_MISS, + EV_MISSILE_MISS_METAL, + EV_RAILTRAIL, + EV_SHOTGUN, + EV_BULLET, // otherEntity is the shooter + + EV_PAIN, + EV_DEATH1, + EV_DEATH2, + EV_DEATH3, + EV_OBITUARY, + + EV_POWERUP_QUAD, + EV_POWERUP_BATTLESUIT, + EV_POWERUP_REGEN, + + EV_GIB_PLAYER, // gib a previously living player + EV_SCOREPLUM, // score plum + +//#ifdef MISSIONPACK + EV_PROXIMITY_MINE_STICK, + EV_PROXIMITY_MINE_TRIGGER, + EV_KAMIKAZE, // kamikaze explodes + EV_OBELISKEXPLODE, // obelisk explodes + EV_OBELISKPAIN, // obelisk is in pain + EV_INVUL_IMPACT, // invulnerability sphere impact + EV_JUICED, // invulnerability juiced effect + EV_LIGHTNINGBOLT, // lightning bolt bounced of invulnerability sphere +//#endif + + EV_DEBUG_LINE, + EV_STOPLOOPINGSOUND, + EV_TAUNT, + EV_TAUNT_YES, + EV_TAUNT_NO, + EV_TAUNT_FOLLOWME, + EV_TAUNT_GETFLAG, + EV_TAUNT_GUARDBASE, + EV_TAUNT_PATROL + +} entity_event_t; + + +typedef enum { + GTS_RED_CAPTURE, + GTS_BLUE_CAPTURE, + GTS_RED_RETURN, + GTS_BLUE_RETURN, + GTS_RED_TAKEN, + GTS_BLUE_TAKEN, + GTS_REDOBELISK_ATTACKED, + GTS_BLUEOBELISK_ATTACKED, + GTS_REDTEAM_SCORED, + GTS_BLUETEAM_SCORED, + GTS_REDTEAM_TOOK_LEAD, + GTS_BLUETEAM_TOOK_LEAD, + GTS_TEAMS_ARE_TIED, + GTS_KAMIKAZE +} global_team_sound_t; + +// animations +typedef enum { + BOTH_DEATH1, + BOTH_DEAD1, + BOTH_DEATH2, + BOTH_DEAD2, + BOTH_DEATH3, + BOTH_DEAD3, + + TORSO_GESTURE, + + TORSO_ATTACK, + TORSO_ATTACK2, + + TORSO_DROP, + TORSO_RAISE, + + TORSO_STAND, + TORSO_STAND2, + + LEGS_WALKCR, + LEGS_WALK, + LEGS_RUN, + LEGS_BACK, + LEGS_SWIM, + + LEGS_JUMP, + LEGS_LAND, + + LEGS_JUMPB, + LEGS_LANDB, + + LEGS_IDLE, + LEGS_IDLECR, + + LEGS_TURN, + + TORSO_GETFLAG, + TORSO_GUARDBASE, + TORSO_PATROL, + TORSO_FOLLOWME, + TORSO_AFFIRMATIVE, + TORSO_NEGATIVE, + + MAX_ANIMATIONS, + + LEGS_BACKCR, + LEGS_BACKWALK, + FLAG_RUN, + FLAG_STAND, + FLAG_STAND2RUN, + + MAX_TOTALANIMATIONS +} animNumber_t; + + +typedef struct animation_s { + int firstFrame; + int numFrames; + int loopFrames; // 0 to numFrames + int frameLerp; // msec between frames + int initialLerp; // msec to get to first frame + int reversed; // true if animation is reversed + int flipflop; // true if animation should flipflop back to base +} animation_t; + + +// flip the togglebit every time an animation +// changes so a restart of the same anim can be detected +#define ANIM_TOGGLEBIT 128 + + +typedef enum { + TEAM_FREE, + TEAM_RED, + TEAM_BLUE, + TEAM_SPECTATOR, + + TEAM_NUM_TEAMS +} team_t; + +// Time between location updates +#define TEAM_LOCATION_UPDATE_TIME 1000 + +// How many players on the overlay +#define TEAM_MAXOVERLAY 32 + +//team task +typedef enum { + TEAMTASK_NONE, + TEAMTASK_OFFENSE, + TEAMTASK_DEFENSE, + TEAMTASK_PATROL, + TEAMTASK_FOLLOW, + TEAMTASK_RETRIEVE, + TEAMTASK_ESCORT, + TEAMTASK_CAMP +} teamtask_t; + +// means of death +typedef enum { + MOD_UNKNOWN, + MOD_SHOTGUN, + MOD_GAUNTLET, + MOD_MACHINEGUN, + MOD_GRENADE, + MOD_GRENADE_SPLASH, + MOD_ROCKET, + MOD_ROCKET_SPLASH, + MOD_PLASMA, + MOD_PLASMA_SPLASH, + MOD_RAILGUN, + MOD_LIGHTNING, + MOD_BFG, + MOD_BFG_SPLASH, + MOD_WATER, + MOD_SLIME, + MOD_LAVA, + MOD_CRUSH, + MOD_TELEFRAG, + MOD_FALLING, + MOD_SUICIDE, + MOD_TARGET_LASER, + MOD_TRIGGER_HURT, +#ifdef MISSIONPACK + MOD_NAIL, + MOD_CHAINGUN, + MOD_PROXIMITY_MINE, + MOD_KAMIKAZE, + MOD_JUICED, +#endif + MOD_GRAPPLE +} meansOfDeath_t; + + +//--------------------------------------------------------- + +// gitem_t->type +typedef enum { + IT_BAD, + IT_WEAPON, // EFX: rotate + upscale + minlight + IT_AMMO, // EFX: rotate + IT_ARMOR, // EFX: rotate + minlight + IT_HEALTH, // EFX: static external sphere + rotating internal + IT_POWERUP, // instant on, timer based + // EFX: rotate + external ring that rotates + IT_HOLDABLE, // single use, holdable item + // EFX: rotate + bob + IT_PERSISTANT_POWERUP, + IT_TEAM +} itemType_t; + +#define MAX_ITEM_MODELS 4 + +typedef struct gitem_s { + char *classname; // spawning name + char *pickup_sound; + char *world_model[MAX_ITEM_MODELS]; + + char *icon; + char *pickup_name; // for printing on pickup + + int quantity; // for ammo how much, or duration of powerup + itemType_t giType; // IT_* flags + + int giTag; + + char *precaches; // string of all models and images this item will use + char *sounds; // string of all sounds this item will use +} gitem_t; + +// included in both the game dll and the client +extern gitem_t bg_itemlist[]; +extern int bg_numItems; + +gitem_t *BG_FindItem( const char *pickupName ); +gitem_t *BG_FindItemForWeapon( weapon_t weapon ); +gitem_t *BG_FindItemForPowerup( powerup_t pw ); +gitem_t *BG_FindItemForHoldable( holdable_t pw ); +#define ITEM_INDEX(x) ((x)-bg_itemlist) + +qboolean BG_CanItemBeGrabbed( int gametype, const entityState_t *ent, const playerState_t *ps ); + + +// g_dmflags->integer flags +#define DF_NO_FALLING 8 +#define DF_FIXED_FOV 16 +#define DF_NO_FOOTSTEPS 32 + +// content masks +#define MASK_ALL (-1) +#define MASK_SOLID (CONTENTS_SOLID) +#define MASK_PLAYERSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_BODY) +#define MASK_DEADSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP) +#define MASK_WATER (CONTENTS_WATER|CONTENTS_LAVA|CONTENTS_SLIME) +#define MASK_OPAQUE (CONTENTS_SOLID|CONTENTS_SLIME|CONTENTS_LAVA) +#define MASK_SHOT (CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_CORPSE) + + +// +// 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_GRAPPLE, // grapple hooked on wall + ET_TEAM, + + 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 BG_EvaluateTrajectory( const trajectory_t *tr, int atTime, vec3_t result ); +void BG_EvaluateTrajectoryDelta( const trajectory_t *tr, int atTime, vec3_t result ); + +void BG_AddPredictableEventToPlayerstate( int newEvent, int eventParm, playerState_t *ps ); + +void BG_TouchJumpPad( playerState_t *ps, entityState_t *jumppad ); + +void BG_PlayerStateToEntityState( playerState_t *ps, entityState_t *s, qboolean snap ); +void BG_PlayerStateToEntityStateExtraPolate( playerState_t *ps, entityState_t *s, int time, qboolean snap ); + +qboolean BG_PlayerTouchesItem( playerState_t *ps, entityState_t *item, int atTime ); + + +#define ARENAS_PER_TIER 4 +#define MAX_ARENAS 1024 +#define MAX_ARENAS_TEXT 8192 + +#define MAX_BOTS 1024 +#define MAX_BOTS_TEXT 8192 + + +// Kamikaze + +// 1st shockwave times +#define KAMI_SHOCKWAVE_STARTTIME 0 +#define KAMI_SHOCKWAVEFADE_STARTTIME 1500 +#define KAMI_SHOCKWAVE_ENDTIME 2000 +// explosion/implosion times +#define KAMI_EXPLODE_STARTTIME 250 +#define KAMI_IMPLODE_STARTTIME 2000 +#define KAMI_IMPLODE_ENDTIME 2250 +// 2nd shockwave times +#define KAMI_SHOCKWAVE2_STARTTIME 2000 +#define KAMI_SHOCKWAVE2FADE_STARTTIME 2500 +#define KAMI_SHOCKWAVE2_ENDTIME 3000 +// radius of the models without scaling +#define KAMI_SHOCKWAVEMODEL_RADIUS 88 +#define KAMI_BOOMSPHEREMODEL_RADIUS 72 +// maximum radius of the models during the effect +#define KAMI_SHOCKWAVE_MAXRADIUS 1320 +#define KAMI_BOOMSPHERE_MAXRADIUS 720 +#define KAMI_SHOCKWAVE2_MAXRADIUS 704 + diff --git a/code/game/bg_slidemove.c b/code/game/bg_slidemove.c index ed1349f..6c957a5 100755 --- a/code/game/bg_slidemove.c +++ b/code/game/bg_slidemove.c @@ -1,325 +1,325 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// bg_slidemove.c -- part of bg_pmove functionality - -#include "q_shared.h" -#include "bg_public.h" -#include "bg_local.h" - -/* - -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 -qboolean PM_SlideMove( qboolean gravity ) { - 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; - - numbumps = 4; - - VectorCopy (pm->ps->velocity, primal_velocity); - - if ( gravity ) { - VectorCopy( pm->ps->velocity, endVelocity ); - endVelocity[2] -= pm->ps->gravity * pml.frametime; - pm->ps->velocity[2] = ( pm->ps->velocity[2] + endVelocity[2] ) * 0.5; - primal_velocity[2] = endVelocity[2]; - if ( pml.groundPlane ) { - // 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] ); - } else { - numplanes = 0; - } - - // never turn against original velocity - VectorNormalize2( pm->ps->velocity, planes[numplanes] ); - numplanes++; - - for ( bumpcount=0 ; bumpcount < numbumps ; bumpcount++ ) { - - // calculate position we are trying to move to - VectorMA( pm->ps->origin, time_left, pm->ps->velocity, end ); - - // see if we can make it there - pm->trace ( &trace, pm->ps->origin, pm->mins, pm->maxs, end, pm->ps->clientNum, pm->tracemask); - - if (trace.allsolid) { - // entity is completely trapped in another solid - pm->ps->velocity[2] = 0; // don't build up falling damage, but allow sideways acceleration - return qtrue; - } - - if (trace.fraction > 0) { - // actually covered some distance - VectorCopy (trace.endpos, pm->ps->origin); - } - - if (trace.fraction == 1) { - break; // moved the entire distance - } - - // save entity for contact - PM_AddTouchEnt( trace.entityNum ); - - time_left -= time_left * trace.fraction; - - if (numplanes >= MAX_CLIP_PLANES) { - // this shouldn't really happen - VectorClear( pm->ps->velocity ); - 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, pm->ps->velocity, pm->ps->velocity ); - break; - } - } - if ( i < numplanes ) { - continue; - } - VectorCopy (trace.plane.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 tripple plane interaction - VectorClear( pm->ps->velocity ); - return qtrue; - } - } - - // if we have fixed all interactions, try another move - VectorCopy( clipVelocity, pm->ps->velocity ); - VectorCopy( endClipVelocity, endVelocity ); - break; - } - } - - if ( gravity ) { - VectorCopy( endVelocity, pm->ps->velocity ); - } - - // don't change velocity if in a timer (FIXME: is this correct?) - if ( pm->ps->pm_time ) { - VectorCopy( primal_velocity, pm->ps->velocity ); - } - - return ( bumpcount != 0 ); -} - -/* -================== -PM_StepSlideMove - -================== -*/ -void PM_StepSlideMove( qboolean gravity ) { - vec3_t start_o, start_v; - vec3_t down_o, down_v; - trace_t trace; -// float down_dist, up_dist; -// vec3_t delta, delta2; - vec3_t up, down; - float stepSize; - - VectorCopy (pm->ps->origin, start_o); - VectorCopy (pm->ps->velocity, start_v); - - if ( PM_SlideMove( gravity ) == 0 ) { - return; // we got exactly where we wanted to go first try - } - - VectorCopy(start_o, down); - down[2] -= STEPSIZE; - pm->trace (&trace, start_o, pm->mins, pm->maxs, down, pm->ps->clientNum, pm->tracemask); - VectorSet(up, 0, 0, 1); - // never step up when you still have up velocity - if ( pm->ps->velocity[2] > 0 && (trace.fraction == 1.0 || - DotProduct(trace.plane.normal, up) < 0.7)) { - return; - } - - VectorCopy (pm->ps->origin, down_o); - VectorCopy (pm->ps->velocity, down_v); - - VectorCopy (start_o, up); - up[2] += STEPSIZE; - - // test the player position if they were a stepheight higher - pm->trace (&trace, start_o, pm->mins, pm->maxs, up, pm->ps->clientNum, pm->tracemask); - if ( trace.allsolid ) { - if ( pm->debugLevel ) { - Com_Printf("%i:bend can't step\n", c_pmove); - } - return; // can't step up - } - - stepSize = trace.endpos[2] - start_o[2]; - // try slidemove from this position - VectorCopy (trace.endpos, pm->ps->origin); - VectorCopy (start_v, pm->ps->velocity); - - PM_SlideMove( gravity ); - - // push down the final amount - VectorCopy (pm->ps->origin, down); - down[2] -= stepSize; - pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, down, pm->ps->clientNum, pm->tracemask); - if ( !trace.allsolid ) { - VectorCopy (trace.endpos, pm->ps->origin); - } - if ( trace.fraction < 1.0 ) { - PM_ClipVelocity( pm->ps->velocity, trace.plane.normal, pm->ps->velocity, OVERCLIP ); - } - -#if 0 - // if the down trace can trace back to the original position directly, don't step - pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, start_o, pm->ps->clientNum, pm->tracemask); - if ( trace.fraction == 1.0 ) { - // use the original move - VectorCopy (down_o, pm->ps->origin); - VectorCopy (down_v, pm->ps->velocity); - if ( pm->debugLevel ) { - Com_Printf("%i:bend\n", c_pmove); - } - } else -#endif - { - // use the step move - float delta; - - delta = pm->ps->origin[2] - start_o[2]; - if ( delta > 2 ) { - if ( delta < 7 ) { - PM_AddEvent( EV_STEP_4 ); - } else if ( delta < 11 ) { - PM_AddEvent( EV_STEP_8 ); - } else if ( delta < 15 ) { - PM_AddEvent( EV_STEP_12 ); - } else { - PM_AddEvent( EV_STEP_16 ); - } - } - if ( pm->debugLevel ) { - Com_Printf("%i:stepped\n", c_pmove); - } - } -} - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// bg_slidemove.c -- part of bg_pmove functionality + +#include "q_shared.h" +#include "bg_public.h" +#include "bg_local.h" + +/* + +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 +qboolean PM_SlideMove( qboolean gravity ) { + 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; + + numbumps = 4; + + VectorCopy (pm->ps->velocity, primal_velocity); + + if ( gravity ) { + VectorCopy( pm->ps->velocity, endVelocity ); + endVelocity[2] -= pm->ps->gravity * pml.frametime; + pm->ps->velocity[2] = ( pm->ps->velocity[2] + endVelocity[2] ) * 0.5; + primal_velocity[2] = endVelocity[2]; + if ( pml.groundPlane ) { + // 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] ); + } else { + numplanes = 0; + } + + // never turn against original velocity + VectorNormalize2( pm->ps->velocity, planes[numplanes] ); + numplanes++; + + for ( bumpcount=0 ; bumpcount < numbumps ; bumpcount++ ) { + + // calculate position we are trying to move to + VectorMA( pm->ps->origin, time_left, pm->ps->velocity, end ); + + // see if we can make it there + pm->trace ( &trace, pm->ps->origin, pm->mins, pm->maxs, end, pm->ps->clientNum, pm->tracemask); + + if (trace.allsolid) { + // entity is completely trapped in another solid + pm->ps->velocity[2] = 0; // don't build up falling damage, but allow sideways acceleration + return qtrue; + } + + if (trace.fraction > 0) { + // actually covered some distance + VectorCopy (trace.endpos, pm->ps->origin); + } + + if (trace.fraction == 1) { + break; // moved the entire distance + } + + // save entity for contact + PM_AddTouchEnt( trace.entityNum ); + + time_left -= time_left * trace.fraction; + + if (numplanes >= MAX_CLIP_PLANES) { + // this shouldn't really happen + VectorClear( pm->ps->velocity ); + 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, pm->ps->velocity, pm->ps->velocity ); + break; + } + } + if ( i < numplanes ) { + continue; + } + VectorCopy (trace.plane.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 tripple plane interaction + VectorClear( pm->ps->velocity ); + return qtrue; + } + } + + // if we have fixed all interactions, try another move + VectorCopy( clipVelocity, pm->ps->velocity ); + VectorCopy( endClipVelocity, endVelocity ); + break; + } + } + + if ( gravity ) { + VectorCopy( endVelocity, pm->ps->velocity ); + } + + // don't change velocity if in a timer (FIXME: is this correct?) + if ( pm->ps->pm_time ) { + VectorCopy( primal_velocity, pm->ps->velocity ); + } + + return ( bumpcount != 0 ); +} + +/* +================== +PM_StepSlideMove + +================== +*/ +void PM_StepSlideMove( qboolean gravity ) { + vec3_t start_o, start_v; + vec3_t down_o, down_v; + trace_t trace; +// float down_dist, up_dist; +// vec3_t delta, delta2; + vec3_t up, down; + float stepSize; + + VectorCopy (pm->ps->origin, start_o); + VectorCopy (pm->ps->velocity, start_v); + + if ( PM_SlideMove( gravity ) == 0 ) { + return; // we got exactly where we wanted to go first try + } + + VectorCopy(start_o, down); + down[2] -= STEPSIZE; + pm->trace (&trace, start_o, pm->mins, pm->maxs, down, pm->ps->clientNum, pm->tracemask); + VectorSet(up, 0, 0, 1); + // never step up when you still have up velocity + if ( pm->ps->velocity[2] > 0 && (trace.fraction == 1.0 || + DotProduct(trace.plane.normal, up) < 0.7)) { + return; + } + + VectorCopy (pm->ps->origin, down_o); + VectorCopy (pm->ps->velocity, down_v); + + VectorCopy (start_o, up); + up[2] += STEPSIZE; + + // test the player position if they were a stepheight higher + pm->trace (&trace, start_o, pm->mins, pm->maxs, up, pm->ps->clientNum, pm->tracemask); + if ( trace.allsolid ) { + if ( pm->debugLevel ) { + Com_Printf("%i:bend can't step\n", c_pmove); + } + return; // can't step up + } + + stepSize = trace.endpos[2] - start_o[2]; + // try slidemove from this position + VectorCopy (trace.endpos, pm->ps->origin); + VectorCopy (start_v, pm->ps->velocity); + + PM_SlideMove( gravity ); + + // push down the final amount + VectorCopy (pm->ps->origin, down); + down[2] -= stepSize; + pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, down, pm->ps->clientNum, pm->tracemask); + if ( !trace.allsolid ) { + VectorCopy (trace.endpos, pm->ps->origin); + } + if ( trace.fraction < 1.0 ) { + PM_ClipVelocity( pm->ps->velocity, trace.plane.normal, pm->ps->velocity, OVERCLIP ); + } + +#if 0 + // if the down trace can trace back to the original position directly, don't step + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, start_o, pm->ps->clientNum, pm->tracemask); + if ( trace.fraction == 1.0 ) { + // use the original move + VectorCopy (down_o, pm->ps->origin); + VectorCopy (down_v, pm->ps->velocity); + if ( pm->debugLevel ) { + Com_Printf("%i:bend\n", c_pmove); + } + } else +#endif + { + // use the step move + float delta; + + delta = pm->ps->origin[2] - start_o[2]; + if ( delta > 2 ) { + if ( delta < 7 ) { + PM_AddEvent( EV_STEP_4 ); + } else if ( delta < 11 ) { + PM_AddEvent( EV_STEP_8 ); + } else if ( delta < 15 ) { + PM_AddEvent( EV_STEP_12 ); + } else { + PM_AddEvent( EV_STEP_16 ); + } + } + if ( pm->debugLevel ) { + Com_Printf("%i:stepped\n", c_pmove); + } + } +} + diff --git a/code/game/botlib.h b/code/game/botlib.h index a9b45db..687affe 100755 --- a/code/game/botlib.h +++ b/code/game/botlib.h @@ -1,516 +1,516 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -/***************************************************************************** - * name: botlib.h - * - * desc: bot AI library - * - * $Archive: /source/code/game/botai.h $ - * - *****************************************************************************/ - -#define BOTLIB_API_VERSION 2 - -struct aas_clientmove_s; -struct aas_entityinfo_s; -struct aas_areainfo_s; -struct aas_altroutegoal_s; -struct aas_predictroute_s; -struct bot_consolemessage_s; -struct bot_match_s; -struct bot_goal_s; -struct bot_moveresult_s; -struct bot_initmove_s; -struct weaponinfo_s; - -#define BOTFILESBASEFOLDER "botfiles" -//debug line colors -#define LINECOLOR_NONE -1 -#define LINECOLOR_RED 1//0xf2f2f0f0L -#define LINECOLOR_GREEN 2//0xd0d1d2d3L -#define LINECOLOR_BLUE 3//0xf3f3f1f1L -#define LINECOLOR_YELLOW 4//0xdcdddedfL -#define LINECOLOR_ORANGE 5//0xe0e1e2e3L - -//Print types -#define PRT_MESSAGE 1 -#define PRT_WARNING 2 -#define PRT_ERROR 3 -#define PRT_FATAL 4 -#define PRT_EXIT 5 - -//console message types -#define CMS_NORMAL 0 -#define CMS_CHAT 1 - -//botlib error codes -#define BLERR_NOERROR 0 //no error -#define BLERR_LIBRARYNOTSETUP 1 //library not setup -#define BLERR_INVALIDENTITYNUMBER 2 //invalid entity number -#define BLERR_NOAASFILE 3 //no AAS file available -#define BLERR_CANNOTOPENAASFILE 4 //cannot open AAS file -#define BLERR_WRONGAASFILEID 5 //incorrect AAS file id -#define BLERR_WRONGAASFILEVERSION 6 //incorrect AAS file version -#define BLERR_CANNOTREADAASLUMP 7 //cannot read AAS file lump -#define BLERR_CANNOTLOADICHAT 8 //cannot load initial chats -#define BLERR_CANNOTLOADITEMWEIGHTS 9 //cannot load item weights -#define BLERR_CANNOTLOADITEMCONFIG 10 //cannot load item config -#define BLERR_CANNOTLOADWEAPONWEIGHTS 11 //cannot load weapon weights -#define BLERR_CANNOTLOADWEAPONCONFIG 12 //cannot load weapon config - -//action flags -#define ACTION_ATTACK 0x0000001 -#define ACTION_USE 0x0000002 -#define ACTION_RESPAWN 0x0000008 -#define ACTION_JUMP 0x0000010 -#define ACTION_MOVEUP 0x0000020 -#define ACTION_CROUCH 0x0000080 -#define ACTION_MOVEDOWN 0x0000100 -#define ACTION_MOVEFORWARD 0x0000200 -#define ACTION_MOVEBACK 0x0000800 -#define ACTION_MOVELEFT 0x0001000 -#define ACTION_MOVERIGHT 0x0002000 -#define ACTION_DELAYEDJUMP 0x0008000 -#define ACTION_TALK 0x0010000 -#define ACTION_GESTURE 0x0020000 -#define ACTION_WALK 0x0080000 -#define ACTION_AFFIRMATIVE 0x0100000 -#define ACTION_NEGATIVE 0x0200000 -#define ACTION_GETFLAG 0x0800000 -#define ACTION_GUARDBASE 0x1000000 -#define ACTION_PATROL 0x2000000 -#define ACTION_FOLLOWME 0x8000000 - -//the bot input, will be converted to an usercmd_t -typedef struct bot_input_s -{ - float thinktime; //time since last output (in seconds) - vec3_t dir; //movement direction - float speed; //speed in the range [0, 400] - vec3_t viewangles; //the view angles - int actionflags; //one of the ACTION_? flags - int weapon; //weapon to use -} bot_input_t; - -#ifndef BSPTRACE - -#define BSPTRACE - -//bsp_trace_t hit surface -typedef struct bsp_surface_s -{ - char name[16]; - int flags; - int value; -} bsp_surface_t; - -//remove the bsp_trace_s structure definition l8r on -//a trace is returned when a box is swept through the world -typedef struct bsp_trace_s -{ - qboolean allsolid; // if true, plane is not valid - qboolean startsolid; // if true, the initial point was in a solid area - float fraction; // time completed, 1.0 = didn't hit anything - vec3_t endpos; // final position - cplane_t plane; // surface normal at impact - float exp_dist; // expanded plane distance - int sidenum; // number of the brush side hit - bsp_surface_t surface; // the hit point surface - int contents; // contents on other side of surface hit - int ent; // number of entity hit -} bsp_trace_t; - -#endif // BSPTRACE - -//entity state -typedef struct bot_entitystate_s -{ - int type; // entity type - int flags; // entity flags - vec3_t origin; // origin of the entity - vec3_t angles; // angles of the model - vec3_t old_origin; // for lerping - vec3_t mins; // bounding box minimums - vec3_t maxs; // bounding box maximums - int groundent; // ground entity - int solid; // solid type - int modelindex; // model used - int modelindex2; // weapons, CTF flags, etc - int frame; // model frame number - int event; // impulse events -- muzzle flashes, footsteps, etc - int eventParm; // even parameter - int powerups; // bit flags - int weapon; // determines weapon and flash model, etc - int legsAnim; // mask off ANIM_TOGGLEBIT - int torsoAnim; // mask off ANIM_TOGGLEBIT -} bot_entitystate_t; - -//bot AI library exported functions -typedef struct botlib_import_s -{ - //print messages from the bot library - void (QDECL *Print)(int type, char *fmt, ...); - //trace a bbox through the world - void (*Trace)(bsp_trace_t *trace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask); - //trace a bbox against a specific entity - void (*EntityTrace)(bsp_trace_t *trace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int entnum, int contentmask); - //retrieve the contents at the given point - int (*PointContents)(vec3_t point); - //check if the point is in potential visible sight - int (*inPVS)(vec3_t p1, vec3_t p2); - //retrieve the BSP entity data lump - char *(*BSPEntityData)(void); - // - void (*BSPModelMinsMaxsOrigin)(int modelnum, vec3_t angles, vec3_t mins, vec3_t maxs, vec3_t origin); - //send a bot client command - void (*BotClientCommand)(int client, char *command); - //memory allocation - void *(*GetMemory)(int size); // allocate from Zone - void (*FreeMemory)(void *ptr); // free memory from Zone - int (*AvailableMemory)(void); // available Zone memory - void *(*HunkAlloc)(int size); // allocate from hunk - //file system access - int (*FS_FOpenFile)( const char *qpath, fileHandle_t *file, fsMode_t mode ); - int (*FS_Read)( void *buffer, int len, fileHandle_t f ); - int (*FS_Write)( const void *buffer, int len, fileHandle_t f ); - void (*FS_FCloseFile)( fileHandle_t f ); - int (*FS_Seek)( fileHandle_t f, long offset, int origin ); - //debug visualisation stuff - int (*DebugLineCreate)(void); - void (*DebugLineDelete)(int line); - void (*DebugLineShow)(int line, vec3_t start, vec3_t end, int color); - // - int (*DebugPolygonCreate)(int color, int numPoints, vec3_t *points); - void (*DebugPolygonDelete)(int id); -} botlib_import_t; - -typedef struct aas_export_s -{ - //----------------------------------- - // be_aas_entity.h - //----------------------------------- - void (*AAS_EntityInfo)(int entnum, struct aas_entityinfo_s *info); - //----------------------------------- - // be_aas_main.h - //----------------------------------- - int (*AAS_Initialized)(void); - void (*AAS_PresenceTypeBoundingBox)(int presencetype, vec3_t mins, vec3_t maxs); - float (*AAS_Time)(void); - //-------------------------------------------- - // be_aas_sample.c - //-------------------------------------------- - int (*AAS_PointAreaNum)(vec3_t point); - int (*AAS_PointReachabilityAreaIndex)( vec3_t point ); - int (*AAS_TraceAreas)(vec3_t start, vec3_t end, int *areas, vec3_t *points, int maxareas); - int (*AAS_BBoxAreas)(vec3_t absmins, vec3_t absmaxs, int *areas, int maxareas); - int (*AAS_AreaInfo)( int areanum, struct aas_areainfo_s *info ); - //-------------------------------------------- - // be_aas_bspq3.c - //-------------------------------------------- - int (*AAS_PointContents)(vec3_t point); - int (*AAS_NextBSPEntity)(int ent); - int (*AAS_ValueForBSPEpairKey)(int ent, char *key, char *value, int size); - int (*AAS_VectorForBSPEpairKey)(int ent, char *key, vec3_t v); - int (*AAS_FloatForBSPEpairKey)(int ent, char *key, float *value); - int (*AAS_IntForBSPEpairKey)(int ent, char *key, int *value); - //-------------------------------------------- - // be_aas_reach.c - //-------------------------------------------- - int (*AAS_AreaReachability)(int areanum); - //-------------------------------------------- - // be_aas_route.c - //-------------------------------------------- - int (*AAS_AreaTravelTimeToGoalArea)(int areanum, vec3_t origin, int goalareanum, int travelflags); - int (*AAS_EnableRoutingArea)(int areanum, int enable); - int (*AAS_PredictRoute)(struct aas_predictroute_s *route, int areanum, vec3_t origin, - int goalareanum, int travelflags, int maxareas, int maxtime, - int stopevent, int stopcontents, int stoptfl, int stopareanum); - //-------------------------------------------- - // be_aas_altroute.c - //-------------------------------------------- - int (*AAS_AlternativeRouteGoals)(vec3_t start, int startareanum, vec3_t goal, int goalareanum, int travelflags, - struct aas_altroutegoal_s *altroutegoals, int maxaltroutegoals, - int type); - //-------------------------------------------- - // be_aas_move.c - //-------------------------------------------- - int (*AAS_Swimming)(vec3_t origin); - int (*AAS_PredictClientMovement)(struct aas_clientmove_s *move, - int entnum, vec3_t origin, - int presencetype, int onground, - vec3_t velocity, vec3_t cmdmove, - int cmdframes, - int maxframes, float frametime, - int stopevent, int stopareanum, int visualize); -} aas_export_t; - -typedef struct ea_export_s -{ - //ClientCommand elementary actions - void (*EA_Command)(int client, char *command ); - void (*EA_Say)(int client, char *str); - void (*EA_SayTeam)(int client, char *str); - // - void (*EA_Action)(int client, int action); - void (*EA_Gesture)(int client); - void (*EA_Talk)(int client); - void (*EA_Attack)(int client); - void (*EA_Use)(int client); - void (*EA_Respawn)(int client); - void (*EA_MoveUp)(int client); - void (*EA_MoveDown)(int client); - void (*EA_MoveForward)(int client); - void (*EA_MoveBack)(int client); - void (*EA_MoveLeft)(int client); - void (*EA_MoveRight)(int client); - void (*EA_Crouch)(int client); - - void (*EA_SelectWeapon)(int client, int weapon); - void (*EA_Jump)(int client); - void (*EA_DelayedJump)(int client); - void (*EA_Move)(int client, vec3_t dir, float speed); - void (*EA_View)(int client, vec3_t viewangles); - //send regular input to the server - void (*EA_EndRegular)(int client, float thinktime); - void (*EA_GetInput)(int client, float thinktime, bot_input_t *input); - void (*EA_ResetInput)(int client); -} ea_export_t; - -typedef struct ai_export_s -{ - //----------------------------------- - // be_ai_char.h - //----------------------------------- - int (*BotLoadCharacter)(char *charfile, float skill); - void (*BotFreeCharacter)(int character); - float (*Characteristic_Float)(int character, int index); - float (*Characteristic_BFloat)(int character, int index, float min, float max); - int (*Characteristic_Integer)(int character, int index); - int (*Characteristic_BInteger)(int character, int index, int min, int max); - void (*Characteristic_String)(int character, int index, char *buf, int size); - //----------------------------------- - // be_ai_chat.h - //----------------------------------- - int (*BotAllocChatState)(void); - void (*BotFreeChatState)(int handle); - void (*BotQueueConsoleMessage)(int chatstate, int type, char *message); - void (*BotRemoveConsoleMessage)(int chatstate, int handle); - int (*BotNextConsoleMessage)(int chatstate, struct bot_consolemessage_s *cm); - int (*BotNumConsoleMessages)(int chatstate); - void (*BotInitialChat)(int chatstate, char *type, int mcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7); - int (*BotNumInitialChats)(int chatstate, char *type); - int (*BotReplyChat)(int chatstate, char *message, int mcontext, int vcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7); - int (*BotChatLength)(int chatstate); - void (*BotEnterChat)(int chatstate, int client, int sendto); - void (*BotGetChatMessage)(int chatstate, char *buf, int size); - int (*StringContains)(char *str1, char *str2, int casesensitive); - int (*BotFindMatch)(char *str, struct bot_match_s *match, unsigned long int context); - void (*BotMatchVariable)(struct bot_match_s *match, int variable, char *buf, int size); - void (*UnifyWhiteSpaces)(char *string); - void (*BotReplaceSynonyms)(char *string, unsigned long int context); - int (*BotLoadChatFile)(int chatstate, char *chatfile, char *chatname); - void (*BotSetChatGender)(int chatstate, int gender); - void (*BotSetChatName)(int chatstate, char *name, int client); - //----------------------------------- - // be_ai_goal.h - //----------------------------------- - void (*BotResetGoalState)(int goalstate); - void (*BotResetAvoidGoals)(int goalstate); - void (*BotRemoveFromAvoidGoals)(int goalstate, int number); - void (*BotPushGoal)(int goalstate, struct bot_goal_s *goal); - void (*BotPopGoal)(int goalstate); - void (*BotEmptyGoalStack)(int goalstate); - void (*BotDumpAvoidGoals)(int goalstate); - void (*BotDumpGoalStack)(int goalstate); - void (*BotGoalName)(int number, char *name, int size); - int (*BotGetTopGoal)(int goalstate, struct bot_goal_s *goal); - int (*BotGetSecondGoal)(int goalstate, struct bot_goal_s *goal); - int (*BotChooseLTGItem)(int goalstate, vec3_t origin, int *inventory, int travelflags); - int (*BotChooseNBGItem)(int goalstate, vec3_t origin, int *inventory, int travelflags, - struct bot_goal_s *ltg, float maxtime); - int (*BotTouchingGoal)(vec3_t origin, struct bot_goal_s *goal); - int (*BotItemGoalInVisButNotVisible)(int viewer, vec3_t eye, vec3_t viewangles, struct bot_goal_s *goal); - int (*BotGetLevelItemGoal)(int index, char *classname, struct bot_goal_s *goal); - int (*BotGetNextCampSpotGoal)(int num, struct bot_goal_s *goal); - int (*BotGetMapLocationGoal)(char *name, struct bot_goal_s *goal); - float (*BotAvoidGoalTime)(int goalstate, int number); - void (*BotSetAvoidGoalTime)(int goalstate, int number, float avoidtime); - void (*BotInitLevelItems)(void); - void (*BotUpdateEntityItems)(void); - int (*BotLoadItemWeights)(int goalstate, char *filename); - void (*BotFreeItemWeights)(int goalstate); - void (*BotInterbreedGoalFuzzyLogic)(int parent1, int parent2, int child); - void (*BotSaveGoalFuzzyLogic)(int goalstate, char *filename); - void (*BotMutateGoalFuzzyLogic)(int goalstate, float range); - int (*BotAllocGoalState)(int client); - void (*BotFreeGoalState)(int handle); - //----------------------------------- - // be_ai_move.h - //----------------------------------- - void (*BotResetMoveState)(int movestate); - void (*BotMoveToGoal)(struct bot_moveresult_s *result, int movestate, struct bot_goal_s *goal, int travelflags); - int (*BotMoveInDirection)(int movestate, vec3_t dir, float speed, int type); - void (*BotResetAvoidReach)(int movestate); - void (*BotResetLastAvoidReach)(int movestate); - int (*BotReachabilityArea)(vec3_t origin, int testground); - int (*BotMovementViewTarget)(int movestate, struct bot_goal_s *goal, int travelflags, float lookahead, vec3_t target); - int (*BotPredictVisiblePosition)(vec3_t origin, int areanum, struct bot_goal_s *goal, int travelflags, vec3_t target); - int (*BotAllocMoveState)(void); - void (*BotFreeMoveState)(int handle); - void (*BotInitMoveState)(int handle, struct bot_initmove_s *initmove); - void (*BotAddAvoidSpot)(int movestate, vec3_t origin, float radius, int type); - //----------------------------------- - // be_ai_weap.h - //----------------------------------- - int (*BotChooseBestFightWeapon)(int weaponstate, int *inventory); - void (*BotGetWeaponInfo)(int weaponstate, int weapon, struct weaponinfo_s *weaponinfo); - int (*BotLoadWeaponWeights)(int weaponstate, char *filename); - int (*BotAllocWeaponState)(void); - void (*BotFreeWeaponState)(int weaponstate); - void (*BotResetWeaponState)(int weaponstate); - //----------------------------------- - // be_ai_gen.h - //----------------------------------- - int (*GeneticParentsAndChildSelection)(int numranks, float *ranks, int *parent1, int *parent2, int *child); -} ai_export_t; - -//bot AI library imported functions -typedef struct botlib_export_s -{ - //Area Awareness System functions - aas_export_t aas; - //Elementary Action functions - ea_export_t ea; - //AI functions - ai_export_t ai; - //setup the bot library, returns BLERR_ - int (*BotLibSetup)(void); - //shutdown the bot library, returns BLERR_ - int (*BotLibShutdown)(void); - //sets a library variable returns BLERR_ - int (*BotLibVarSet)(char *var_name, char *value); - //gets a library variable returns BLERR_ - int (*BotLibVarGet)(char *var_name, char *value, int size); - - //sets a C-like define returns BLERR_ - int (*PC_AddGlobalDefine)(char *string); - int (*PC_LoadSourceHandle)(const char *filename); - int (*PC_FreeSourceHandle)(int handle); - int (*PC_ReadTokenHandle)(int handle, pc_token_t *pc_token); - int (*PC_SourceFileAndLine)(int handle, char *filename, int *line); - - //start a frame in the bot library - int (*BotLibStartFrame)(float time); - //load a new map in the bot library - int (*BotLibLoadMap)(const char *mapname); - //entity updates - int (*BotLibUpdateEntity)(int ent, bot_entitystate_t *state); - //just for testing - int (*Test)(int parm0, char *parm1, vec3_t parm2, vec3_t parm3); -} botlib_export_t; - -//linking of bot library -botlib_export_t *GetBotLibAPI( int apiVersion, botlib_import_t *import ); - -/* Library variables: - -name: default: module(s): description: - -"basedir" "" l_utils.c base directory -"gamedir" "" l_utils.c game directory -"cddir" "" l_utils.c CD directory - -"log" "0" l_log.c enable/disable creating a log file -"maxclients" "4" be_interface.c maximum number of clients -"maxentities" "1024" be_interface.c maximum number of entities -"bot_developer" "0" be_interface.c bot developer mode - -"phys_friction" "6" be_aas_move.c ground friction -"phys_stopspeed" "100" be_aas_move.c stop speed -"phys_gravity" "800" be_aas_move.c gravity value -"phys_waterfriction" "1" be_aas_move.c water friction -"phys_watergravity" "400" be_aas_move.c gravity in water -"phys_maxvelocity" "320" be_aas_move.c maximum velocity -"phys_maxwalkvelocity" "320" be_aas_move.c maximum walk velocity -"phys_maxcrouchvelocity" "100" be_aas_move.c maximum crouch velocity -"phys_maxswimvelocity" "150" be_aas_move.c maximum swim velocity -"phys_walkaccelerate" "10" be_aas_move.c walk acceleration -"phys_airaccelerate" "1" be_aas_move.c air acceleration -"phys_swimaccelerate" "4" be_aas_move.c swim acceleration -"phys_maxstep" "18" be_aas_move.c maximum step height -"phys_maxsteepness" "0.7" be_aas_move.c maximum floor steepness -"phys_maxbarrier" "32" be_aas_move.c maximum barrier height -"phys_maxwaterjump" "19" be_aas_move.c maximum waterjump height -"phys_jumpvel" "270" be_aas_move.c jump z velocity -"phys_falldelta5" "40" be_aas_move.c -"phys_falldelta10" "60" be_aas_move.c -"rs_waterjump" "400" be_aas_move.c -"rs_teleport" "50" be_aas_move.c -"rs_barrierjump" "100" be_aas_move.c -"rs_startcrouch" "300" be_aas_move.c -"rs_startgrapple" "500" be_aas_move.c -"rs_startwalkoffledge" "70" be_aas_move.c -"rs_startjump" "300" be_aas_move.c -"rs_rocketjump" "500" be_aas_move.c -"rs_bfgjump" "500" be_aas_move.c -"rs_jumppad" "250" be_aas_move.c -"rs_aircontrolledjumppad" "300" be_aas_move.c -"rs_funcbob" "300" be_aas_move.c -"rs_startelevator" "50" be_aas_move.c -"rs_falldamage5" "300" be_aas_move.c -"rs_falldamage10" "500" be_aas_move.c -"rs_maxjumpfallheight" "450" be_aas_move.c - -"max_aaslinks" "4096" be_aas_sample.c maximum links in the AAS -"max_routingcache" "4096" be_aas_route.c maximum routing cache size in KB -"forceclustering" "0" be_aas_main.c force recalculation of clusters -"forcereachability" "0" be_aas_main.c force recalculation of reachabilities -"forcewrite" "0" be_aas_main.c force writing of aas file -"aasoptimize" "0" be_aas_main.c enable aas optimization -"sv_mapChecksum" "0" be_aas_main.c BSP file checksum -"bot_visualizejumppads" "0" be_aas_reach.c visualize jump pads - -"bot_reloadcharacters" "0" - reload bot character files -"ai_gametype" "0" be_ai_goal.c game type -"droppedweight" "1000" be_ai_goal.c additional dropped item weight -"weapindex_rocketlauncher" "5" be_ai_move.c rl weapon index for rocket jumping -"weapindex_bfg10k" "9" be_ai_move.c bfg weapon index for bfg jumping -"weapindex_grapple" "10" be_ai_move.c grapple weapon index for grappling -"entitytypemissile" "3" be_ai_move.c ET_MISSILE -"offhandgrapple" "0" be_ai_move.c enable off hand grapple hook -"cmd_grappleon" "grappleon" be_ai_move.c command to activate off hand grapple -"cmd_grappleoff" "grappleoff" be_ai_move.c command to deactivate off hand grapple -"itemconfig" "items.c" be_ai_goal.c item configuration file -"weaponconfig" "weapons.c" be_ai_weap.c weapon configuration file -"synfile" "syn.c" be_ai_chat.c file with synonyms -"rndfile" "rnd.c" be_ai_chat.c file with random strings -"matchfile" "match.c" be_ai_chat.c file with match strings -"nochat" "0" be_ai_chat.c disable chats -"max_messages" "1024" be_ai_chat.c console message heap size -"max_weaponinfo" "32" be_ai_weap.c maximum number of weapon info -"max_projectileinfo" "32" be_ai_weap.c maximum number of projectile info -"max_iteminfo" "256" be_ai_goal.c maximum number of item info -"max_levelitems" "256" be_ai_goal.c maximum number of level items - -*/ - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +/***************************************************************************** + * name: botlib.h + * + * desc: bot AI library + * + * $Archive: /source/code/game/botai.h $ + * + *****************************************************************************/ + +#define BOTLIB_API_VERSION 2 + +struct aas_clientmove_s; +struct aas_entityinfo_s; +struct aas_areainfo_s; +struct aas_altroutegoal_s; +struct aas_predictroute_s; +struct bot_consolemessage_s; +struct bot_match_s; +struct bot_goal_s; +struct bot_moveresult_s; +struct bot_initmove_s; +struct weaponinfo_s; + +#define BOTFILESBASEFOLDER "botfiles" +//debug line colors +#define LINECOLOR_NONE -1 +#define LINECOLOR_RED 1//0xf2f2f0f0L +#define LINECOLOR_GREEN 2//0xd0d1d2d3L +#define LINECOLOR_BLUE 3//0xf3f3f1f1L +#define LINECOLOR_YELLOW 4//0xdcdddedfL +#define LINECOLOR_ORANGE 5//0xe0e1e2e3L + +//Print types +#define PRT_MESSAGE 1 +#define PRT_WARNING 2 +#define PRT_ERROR 3 +#define PRT_FATAL 4 +#define PRT_EXIT 5 + +//console message types +#define CMS_NORMAL 0 +#define CMS_CHAT 1 + +//botlib error codes +#define BLERR_NOERROR 0 //no error +#define BLERR_LIBRARYNOTSETUP 1 //library not setup +#define BLERR_INVALIDENTITYNUMBER 2 //invalid entity number +#define BLERR_NOAASFILE 3 //no AAS file available +#define BLERR_CANNOTOPENAASFILE 4 //cannot open AAS file +#define BLERR_WRONGAASFILEID 5 //incorrect AAS file id +#define BLERR_WRONGAASFILEVERSION 6 //incorrect AAS file version +#define BLERR_CANNOTREADAASLUMP 7 //cannot read AAS file lump +#define BLERR_CANNOTLOADICHAT 8 //cannot load initial chats +#define BLERR_CANNOTLOADITEMWEIGHTS 9 //cannot load item weights +#define BLERR_CANNOTLOADITEMCONFIG 10 //cannot load item config +#define BLERR_CANNOTLOADWEAPONWEIGHTS 11 //cannot load weapon weights +#define BLERR_CANNOTLOADWEAPONCONFIG 12 //cannot load weapon config + +//action flags +#define ACTION_ATTACK 0x0000001 +#define ACTION_USE 0x0000002 +#define ACTION_RESPAWN 0x0000008 +#define ACTION_JUMP 0x0000010 +#define ACTION_MOVEUP 0x0000020 +#define ACTION_CROUCH 0x0000080 +#define ACTION_MOVEDOWN 0x0000100 +#define ACTION_MOVEFORWARD 0x0000200 +#define ACTION_MOVEBACK 0x0000800 +#define ACTION_MOVELEFT 0x0001000 +#define ACTION_MOVERIGHT 0x0002000 +#define ACTION_DELAYEDJUMP 0x0008000 +#define ACTION_TALK 0x0010000 +#define ACTION_GESTURE 0x0020000 +#define ACTION_WALK 0x0080000 +#define ACTION_AFFIRMATIVE 0x0100000 +#define ACTION_NEGATIVE 0x0200000 +#define ACTION_GETFLAG 0x0800000 +#define ACTION_GUARDBASE 0x1000000 +#define ACTION_PATROL 0x2000000 +#define ACTION_FOLLOWME 0x8000000 + +//the bot input, will be converted to an usercmd_t +typedef struct bot_input_s +{ + float thinktime; //time since last output (in seconds) + vec3_t dir; //movement direction + float speed; //speed in the range [0, 400] + vec3_t viewangles; //the view angles + int actionflags; //one of the ACTION_? flags + int weapon; //weapon to use +} bot_input_t; + +#ifndef BSPTRACE + +#define BSPTRACE + +//bsp_trace_t hit surface +typedef struct bsp_surface_s +{ + char name[16]; + int flags; + int value; +} bsp_surface_t; + +//remove the bsp_trace_s structure definition l8r on +//a trace is returned when a box is swept through the world +typedef struct bsp_trace_s +{ + qboolean allsolid; // if true, plane is not valid + qboolean startsolid; // if true, the initial point was in a solid area + float fraction; // time completed, 1.0 = didn't hit anything + vec3_t endpos; // final position + cplane_t plane; // surface normal at impact + float exp_dist; // expanded plane distance + int sidenum; // number of the brush side hit + bsp_surface_t surface; // the hit point surface + int contents; // contents on other side of surface hit + int ent; // number of entity hit +} bsp_trace_t; + +#endif // BSPTRACE + +//entity state +typedef struct bot_entitystate_s +{ + int type; // entity type + int flags; // entity flags + vec3_t origin; // origin of the entity + vec3_t angles; // angles of the model + vec3_t old_origin; // for lerping + vec3_t mins; // bounding box minimums + vec3_t maxs; // bounding box maximums + int groundent; // ground entity + int solid; // solid type + int modelindex; // model used + int modelindex2; // weapons, CTF flags, etc + int frame; // model frame number + int event; // impulse events -- muzzle flashes, footsteps, etc + int eventParm; // even parameter + int powerups; // bit flags + int weapon; // determines weapon and flash model, etc + int legsAnim; // mask off ANIM_TOGGLEBIT + int torsoAnim; // mask off ANIM_TOGGLEBIT +} bot_entitystate_t; + +//bot AI library exported functions +typedef struct botlib_import_s +{ + //print messages from the bot library + void (QDECL *Print)(int type, char *fmt, ...); + //trace a bbox through the world + void (*Trace)(bsp_trace_t *trace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask); + //trace a bbox against a specific entity + void (*EntityTrace)(bsp_trace_t *trace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int entnum, int contentmask); + //retrieve the contents at the given point + int (*PointContents)(vec3_t point); + //check if the point is in potential visible sight + int (*inPVS)(vec3_t p1, vec3_t p2); + //retrieve the BSP entity data lump + char *(*BSPEntityData)(void); + // + void (*BSPModelMinsMaxsOrigin)(int modelnum, vec3_t angles, vec3_t mins, vec3_t maxs, vec3_t origin); + //send a bot client command + void (*BotClientCommand)(int client, char *command); + //memory allocation + void *(*GetMemory)(int size); // allocate from Zone + void (*FreeMemory)(void *ptr); // free memory from Zone + int (*AvailableMemory)(void); // available Zone memory + void *(*HunkAlloc)(int size); // allocate from hunk + //file system access + int (*FS_FOpenFile)( const char *qpath, fileHandle_t *file, fsMode_t mode ); + int (*FS_Read)( void *buffer, int len, fileHandle_t f ); + int (*FS_Write)( const void *buffer, int len, fileHandle_t f ); + void (*FS_FCloseFile)( fileHandle_t f ); + int (*FS_Seek)( fileHandle_t f, long offset, int origin ); + //debug visualisation stuff + int (*DebugLineCreate)(void); + void (*DebugLineDelete)(int line); + void (*DebugLineShow)(int line, vec3_t start, vec3_t end, int color); + // + int (*DebugPolygonCreate)(int color, int numPoints, vec3_t *points); + void (*DebugPolygonDelete)(int id); +} botlib_import_t; + +typedef struct aas_export_s +{ + //----------------------------------- + // be_aas_entity.h + //----------------------------------- + void (*AAS_EntityInfo)(int entnum, struct aas_entityinfo_s *info); + //----------------------------------- + // be_aas_main.h + //----------------------------------- + int (*AAS_Initialized)(void); + void (*AAS_PresenceTypeBoundingBox)(int presencetype, vec3_t mins, vec3_t maxs); + float (*AAS_Time)(void); + //-------------------------------------------- + // be_aas_sample.c + //-------------------------------------------- + int (*AAS_PointAreaNum)(vec3_t point); + int (*AAS_PointReachabilityAreaIndex)( vec3_t point ); + int (*AAS_TraceAreas)(vec3_t start, vec3_t end, int *areas, vec3_t *points, int maxareas); + int (*AAS_BBoxAreas)(vec3_t absmins, vec3_t absmaxs, int *areas, int maxareas); + int (*AAS_AreaInfo)( int areanum, struct aas_areainfo_s *info ); + //-------------------------------------------- + // be_aas_bspq3.c + //-------------------------------------------- + int (*AAS_PointContents)(vec3_t point); + int (*AAS_NextBSPEntity)(int ent); + int (*AAS_ValueForBSPEpairKey)(int ent, char *key, char *value, int size); + int (*AAS_VectorForBSPEpairKey)(int ent, char *key, vec3_t v); + int (*AAS_FloatForBSPEpairKey)(int ent, char *key, float *value); + int (*AAS_IntForBSPEpairKey)(int ent, char *key, int *value); + //-------------------------------------------- + // be_aas_reach.c + //-------------------------------------------- + int (*AAS_AreaReachability)(int areanum); + //-------------------------------------------- + // be_aas_route.c + //-------------------------------------------- + int (*AAS_AreaTravelTimeToGoalArea)(int areanum, vec3_t origin, int goalareanum, int travelflags); + int (*AAS_EnableRoutingArea)(int areanum, int enable); + int (*AAS_PredictRoute)(struct aas_predictroute_s *route, int areanum, vec3_t origin, + int goalareanum, int travelflags, int maxareas, int maxtime, + int stopevent, int stopcontents, int stoptfl, int stopareanum); + //-------------------------------------------- + // be_aas_altroute.c + //-------------------------------------------- + int (*AAS_AlternativeRouteGoals)(vec3_t start, int startareanum, vec3_t goal, int goalareanum, int travelflags, + struct aas_altroutegoal_s *altroutegoals, int maxaltroutegoals, + int type); + //-------------------------------------------- + // be_aas_move.c + //-------------------------------------------- + int (*AAS_Swimming)(vec3_t origin); + int (*AAS_PredictClientMovement)(struct aas_clientmove_s *move, + int entnum, vec3_t origin, + int presencetype, int onground, + vec3_t velocity, vec3_t cmdmove, + int cmdframes, + int maxframes, float frametime, + int stopevent, int stopareanum, int visualize); +} aas_export_t; + +typedef struct ea_export_s +{ + //ClientCommand elementary actions + void (*EA_Command)(int client, char *command ); + void (*EA_Say)(int client, char *str); + void (*EA_SayTeam)(int client, char *str); + // + void (*EA_Action)(int client, int action); + void (*EA_Gesture)(int client); + void (*EA_Talk)(int client); + void (*EA_Attack)(int client); + void (*EA_Use)(int client); + void (*EA_Respawn)(int client); + void (*EA_MoveUp)(int client); + void (*EA_MoveDown)(int client); + void (*EA_MoveForward)(int client); + void (*EA_MoveBack)(int client); + void (*EA_MoveLeft)(int client); + void (*EA_MoveRight)(int client); + void (*EA_Crouch)(int client); + + void (*EA_SelectWeapon)(int client, int weapon); + void (*EA_Jump)(int client); + void (*EA_DelayedJump)(int client); + void (*EA_Move)(int client, vec3_t dir, float speed); + void (*EA_View)(int client, vec3_t viewangles); + //send regular input to the server + void (*EA_EndRegular)(int client, float thinktime); + void (*EA_GetInput)(int client, float thinktime, bot_input_t *input); + void (*EA_ResetInput)(int client); +} ea_export_t; + +typedef struct ai_export_s +{ + //----------------------------------- + // be_ai_char.h + //----------------------------------- + int (*BotLoadCharacter)(char *charfile, float skill); + void (*BotFreeCharacter)(int character); + float (*Characteristic_Float)(int character, int index); + float (*Characteristic_BFloat)(int character, int index, float min, float max); + int (*Characteristic_Integer)(int character, int index); + int (*Characteristic_BInteger)(int character, int index, int min, int max); + void (*Characteristic_String)(int character, int index, char *buf, int size); + //----------------------------------- + // be_ai_chat.h + //----------------------------------- + int (*BotAllocChatState)(void); + void (*BotFreeChatState)(int handle); + void (*BotQueueConsoleMessage)(int chatstate, int type, char *message); + void (*BotRemoveConsoleMessage)(int chatstate, int handle); + int (*BotNextConsoleMessage)(int chatstate, struct bot_consolemessage_s *cm); + int (*BotNumConsoleMessages)(int chatstate); + void (*BotInitialChat)(int chatstate, char *type, int mcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7); + int (*BotNumInitialChats)(int chatstate, char *type); + int (*BotReplyChat)(int chatstate, char *message, int mcontext, int vcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7); + int (*BotChatLength)(int chatstate); + void (*BotEnterChat)(int chatstate, int client, int sendto); + void (*BotGetChatMessage)(int chatstate, char *buf, int size); + int (*StringContains)(char *str1, char *str2, int casesensitive); + int (*BotFindMatch)(char *str, struct bot_match_s *match, unsigned long int context); + void (*BotMatchVariable)(struct bot_match_s *match, int variable, char *buf, int size); + void (*UnifyWhiteSpaces)(char *string); + void (*BotReplaceSynonyms)(char *string, unsigned long int context); + int (*BotLoadChatFile)(int chatstate, char *chatfile, char *chatname); + void (*BotSetChatGender)(int chatstate, int gender); + void (*BotSetChatName)(int chatstate, char *name, int client); + //----------------------------------- + // be_ai_goal.h + //----------------------------------- + void (*BotResetGoalState)(int goalstate); + void (*BotResetAvoidGoals)(int goalstate); + void (*BotRemoveFromAvoidGoals)(int goalstate, int number); + void (*BotPushGoal)(int goalstate, struct bot_goal_s *goal); + void (*BotPopGoal)(int goalstate); + void (*BotEmptyGoalStack)(int goalstate); + void (*BotDumpAvoidGoals)(int goalstate); + void (*BotDumpGoalStack)(int goalstate); + void (*BotGoalName)(int number, char *name, int size); + int (*BotGetTopGoal)(int goalstate, struct bot_goal_s *goal); + int (*BotGetSecondGoal)(int goalstate, struct bot_goal_s *goal); + int (*BotChooseLTGItem)(int goalstate, vec3_t origin, int *inventory, int travelflags); + int (*BotChooseNBGItem)(int goalstate, vec3_t origin, int *inventory, int travelflags, + struct bot_goal_s *ltg, float maxtime); + int (*BotTouchingGoal)(vec3_t origin, struct bot_goal_s *goal); + int (*BotItemGoalInVisButNotVisible)(int viewer, vec3_t eye, vec3_t viewangles, struct bot_goal_s *goal); + int (*BotGetLevelItemGoal)(int index, char *classname, struct bot_goal_s *goal); + int (*BotGetNextCampSpotGoal)(int num, struct bot_goal_s *goal); + int (*BotGetMapLocationGoal)(char *name, struct bot_goal_s *goal); + float (*BotAvoidGoalTime)(int goalstate, int number); + void (*BotSetAvoidGoalTime)(int goalstate, int number, float avoidtime); + void (*BotInitLevelItems)(void); + void (*BotUpdateEntityItems)(void); + int (*BotLoadItemWeights)(int goalstate, char *filename); + void (*BotFreeItemWeights)(int goalstate); + void (*BotInterbreedGoalFuzzyLogic)(int parent1, int parent2, int child); + void (*BotSaveGoalFuzzyLogic)(int goalstate, char *filename); + void (*BotMutateGoalFuzzyLogic)(int goalstate, float range); + int (*BotAllocGoalState)(int client); + void (*BotFreeGoalState)(int handle); + //----------------------------------- + // be_ai_move.h + //----------------------------------- + void (*BotResetMoveState)(int movestate); + void (*BotMoveToGoal)(struct bot_moveresult_s *result, int movestate, struct bot_goal_s *goal, int travelflags); + int (*BotMoveInDirection)(int movestate, vec3_t dir, float speed, int type); + void (*BotResetAvoidReach)(int movestate); + void (*BotResetLastAvoidReach)(int movestate); + int (*BotReachabilityArea)(vec3_t origin, int testground); + int (*BotMovementViewTarget)(int movestate, struct bot_goal_s *goal, int travelflags, float lookahead, vec3_t target); + int (*BotPredictVisiblePosition)(vec3_t origin, int areanum, struct bot_goal_s *goal, int travelflags, vec3_t target); + int (*BotAllocMoveState)(void); + void (*BotFreeMoveState)(int handle); + void (*BotInitMoveState)(int handle, struct bot_initmove_s *initmove); + void (*BotAddAvoidSpot)(int movestate, vec3_t origin, float radius, int type); + //----------------------------------- + // be_ai_weap.h + //----------------------------------- + int (*BotChooseBestFightWeapon)(int weaponstate, int *inventory); + void (*BotGetWeaponInfo)(int weaponstate, int weapon, struct weaponinfo_s *weaponinfo); + int (*BotLoadWeaponWeights)(int weaponstate, char *filename); + int (*BotAllocWeaponState)(void); + void (*BotFreeWeaponState)(int weaponstate); + void (*BotResetWeaponState)(int weaponstate); + //----------------------------------- + // be_ai_gen.h + //----------------------------------- + int (*GeneticParentsAndChildSelection)(int numranks, float *ranks, int *parent1, int *parent2, int *child); +} ai_export_t; + +//bot AI library imported functions +typedef struct botlib_export_s +{ + //Area Awareness System functions + aas_export_t aas; + //Elementary Action functions + ea_export_t ea; + //AI functions + ai_export_t ai; + //setup the bot library, returns BLERR_ + int (*BotLibSetup)(void); + //shutdown the bot library, returns BLERR_ + int (*BotLibShutdown)(void); + //sets a library variable returns BLERR_ + int (*BotLibVarSet)(char *var_name, char *value); + //gets a library variable returns BLERR_ + int (*BotLibVarGet)(char *var_name, char *value, int size); + + //sets a C-like define returns BLERR_ + int (*PC_AddGlobalDefine)(char *string); + int (*PC_LoadSourceHandle)(const char *filename); + int (*PC_FreeSourceHandle)(int handle); + int (*PC_ReadTokenHandle)(int handle, pc_token_t *pc_token); + int (*PC_SourceFileAndLine)(int handle, char *filename, int *line); + + //start a frame in the bot library + int (*BotLibStartFrame)(float time); + //load a new map in the bot library + int (*BotLibLoadMap)(const char *mapname); + //entity updates + int (*BotLibUpdateEntity)(int ent, bot_entitystate_t *state); + //just for testing + int (*Test)(int parm0, char *parm1, vec3_t parm2, vec3_t parm3); +} botlib_export_t; + +//linking of bot library +botlib_export_t *GetBotLibAPI( int apiVersion, botlib_import_t *import ); + +/* Library variables: + +name: default: module(s): description: + +"basedir" "" l_utils.c base directory +"gamedir" "" l_utils.c game directory +"cddir" "" l_utils.c CD directory + +"log" "0" l_log.c enable/disable creating a log file +"maxclients" "4" be_interface.c maximum number of clients +"maxentities" "1024" be_interface.c maximum number of entities +"bot_developer" "0" be_interface.c bot developer mode + +"phys_friction" "6" be_aas_move.c ground friction +"phys_stopspeed" "100" be_aas_move.c stop speed +"phys_gravity" "800" be_aas_move.c gravity value +"phys_waterfriction" "1" be_aas_move.c water friction +"phys_watergravity" "400" be_aas_move.c gravity in water +"phys_maxvelocity" "320" be_aas_move.c maximum velocity +"phys_maxwalkvelocity" "320" be_aas_move.c maximum walk velocity +"phys_maxcrouchvelocity" "100" be_aas_move.c maximum crouch velocity +"phys_maxswimvelocity" "150" be_aas_move.c maximum swim velocity +"phys_walkaccelerate" "10" be_aas_move.c walk acceleration +"phys_airaccelerate" "1" be_aas_move.c air acceleration +"phys_swimaccelerate" "4" be_aas_move.c swim acceleration +"phys_maxstep" "18" be_aas_move.c maximum step height +"phys_maxsteepness" "0.7" be_aas_move.c maximum floor steepness +"phys_maxbarrier" "32" be_aas_move.c maximum barrier height +"phys_maxwaterjump" "19" be_aas_move.c maximum waterjump height +"phys_jumpvel" "270" be_aas_move.c jump z velocity +"phys_falldelta5" "40" be_aas_move.c +"phys_falldelta10" "60" be_aas_move.c +"rs_waterjump" "400" be_aas_move.c +"rs_teleport" "50" be_aas_move.c +"rs_barrierjump" "100" be_aas_move.c +"rs_startcrouch" "300" be_aas_move.c +"rs_startgrapple" "500" be_aas_move.c +"rs_startwalkoffledge" "70" be_aas_move.c +"rs_startjump" "300" be_aas_move.c +"rs_rocketjump" "500" be_aas_move.c +"rs_bfgjump" "500" be_aas_move.c +"rs_jumppad" "250" be_aas_move.c +"rs_aircontrolledjumppad" "300" be_aas_move.c +"rs_funcbob" "300" be_aas_move.c +"rs_startelevator" "50" be_aas_move.c +"rs_falldamage5" "300" be_aas_move.c +"rs_falldamage10" "500" be_aas_move.c +"rs_maxjumpfallheight" "450" be_aas_move.c + +"max_aaslinks" "4096" be_aas_sample.c maximum links in the AAS +"max_routingcache" "4096" be_aas_route.c maximum routing cache size in KB +"forceclustering" "0" be_aas_main.c force recalculation of clusters +"forcereachability" "0" be_aas_main.c force recalculation of reachabilities +"forcewrite" "0" be_aas_main.c force writing of aas file +"aasoptimize" "0" be_aas_main.c enable aas optimization +"sv_mapChecksum" "0" be_aas_main.c BSP file checksum +"bot_visualizejumppads" "0" be_aas_reach.c visualize jump pads + +"bot_reloadcharacters" "0" - reload bot character files +"ai_gametype" "0" be_ai_goal.c game type +"droppedweight" "1000" be_ai_goal.c additional dropped item weight +"weapindex_rocketlauncher" "5" be_ai_move.c rl weapon index for rocket jumping +"weapindex_bfg10k" "9" be_ai_move.c bfg weapon index for bfg jumping +"weapindex_grapple" "10" be_ai_move.c grapple weapon index for grappling +"entitytypemissile" "3" be_ai_move.c ET_MISSILE +"offhandgrapple" "0" be_ai_move.c enable off hand grapple hook +"cmd_grappleon" "grappleon" be_ai_move.c command to activate off hand grapple +"cmd_grappleoff" "grappleoff" be_ai_move.c command to deactivate off hand grapple +"itemconfig" "items.c" be_ai_goal.c item configuration file +"weaponconfig" "weapons.c" be_ai_weap.c weapon configuration file +"synfile" "syn.c" be_ai_chat.c file with synonyms +"rndfile" "rnd.c" be_ai_chat.c file with random strings +"matchfile" "match.c" be_ai_chat.c file with match strings +"nochat" "0" be_ai_chat.c disable chats +"max_messages" "1024" be_ai_chat.c console message heap size +"max_weaponinfo" "32" be_ai_weap.c maximum number of weapon info +"max_projectileinfo" "32" be_ai_weap.c maximum number of projectile info +"max_iteminfo" "256" be_ai_goal.c maximum number of item info +"max_levelitems" "256" be_ai_goal.c maximum number of level items + +*/ + diff --git a/code/game/chars.h b/code/game/chars.h index c5b1861..ae96f56 100755 --- a/code/game/chars.h +++ b/code/game/chars.h @@ -1,134 +1,134 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -//======================================================== -//======================================================== -//name -#define CHARACTERISTIC_NAME 0 //string -//gender of the bot -#define CHARACTERISTIC_GENDER 1 //string ("male", "female", "it") -//attack skill -// > 0.0 && < 0.2 = don't move -// > 0.3 && < 1.0 = aim at enemy during retreat -// > 0.0 && < 0.4 = only move forward/backward -// >= 0.4 && < 1.0 = circle strafing -// > 0.7 && < 1.0 = random strafe direction change -#define CHARACTERISTIC_ATTACK_SKILL 2 //float [0, 1] -//weapon weight file -#define CHARACTERISTIC_WEAPONWEIGHTS 3 //string -//view angle difference to angle change factor -#define CHARACTERISTIC_VIEW_FACTOR 4 //float <0, 1] -//maximum view angle change -#define CHARACTERISTIC_VIEW_MAXCHANGE 5 //float [1, 360] -//reaction time in seconds -#define CHARACTERISTIC_REACTIONTIME 6 //float [0, 5] -//accuracy when aiming -#define CHARACTERISTIC_AIM_ACCURACY 7 //float [0, 1] -//weapon specific aim accuracy -#define CHARACTERISTIC_AIM_ACCURACY_MACHINEGUN 8 //float [0, 1] -#define CHARACTERISTIC_AIM_ACCURACY_SHOTGUN 9 //float [0, 1] -#define CHARACTERISTIC_AIM_ACCURACY_ROCKETLAUNCHER 10 //float [0, 1] -#define CHARACTERISTIC_AIM_ACCURACY_GRENADELAUNCHER 11 //float [0, 1] -#define CHARACTERISTIC_AIM_ACCURACY_LIGHTNING 12 -#define CHARACTERISTIC_AIM_ACCURACY_PLASMAGUN 13 //float [0, 1] -#define CHARACTERISTIC_AIM_ACCURACY_RAILGUN 14 -#define CHARACTERISTIC_AIM_ACCURACY_BFG10K 15 //float [0, 1] -//skill when aiming -// > 0.0 && < 0.9 = aim is affected by enemy movement -// > 0.4 && <= 0.8 = enemy linear leading -// > 0.8 && <= 1.0 = enemy exact movement leading -// > 0.5 && <= 1.0 = prediction shots when enemy is not visible -// > 0.6 && <= 1.0 = splash damage by shooting nearby geometry -#define CHARACTERISTIC_AIM_SKILL 16 //float [0, 1] -//weapon specific aim skill -#define CHARACTERISTIC_AIM_SKILL_ROCKETLAUNCHER 17 //float [0, 1] -#define CHARACTERISTIC_AIM_SKILL_GRENADELAUNCHER 18 //float [0, 1] -#define CHARACTERISTIC_AIM_SKILL_PLASMAGUN 19 //float [0, 1] -#define CHARACTERISTIC_AIM_SKILL_BFG10K 20 //float [0, 1] -//======================================================== -//chat -//======================================================== -//file with chats -#define CHARACTERISTIC_CHAT_FILE 21 //string -//name of the chat character -#define CHARACTERISTIC_CHAT_NAME 22 //string -//characters per minute type speed -#define CHARACTERISTIC_CHAT_CPM 23 //integer [1, 4000] -//tendency to insult/praise -#define CHARACTERISTIC_CHAT_INSULT 24 //float [0, 1] -//tendency to chat misc -#define CHARACTERISTIC_CHAT_MISC 25 //float [0, 1] -//tendency to chat at start or end of level -#define CHARACTERISTIC_CHAT_STARTENDLEVEL 26 //float [0, 1] -//tendency to chat entering or exiting the game -#define CHARACTERISTIC_CHAT_ENTEREXITGAME 27 //float [0, 1] -//tendency to chat when killed someone -#define CHARACTERISTIC_CHAT_KILL 28 //float [0, 1] -//tendency to chat when died -#define CHARACTERISTIC_CHAT_DEATH 29 //float [0, 1] -//tendency to chat when enemy suicides -#define CHARACTERISTIC_CHAT_ENEMYSUICIDE 30 //float [0, 1] -//tendency to chat when hit while talking -#define CHARACTERISTIC_CHAT_HITTALKING 31 //float [0, 1] -//tendency to chat when bot was hit but didn't dye -#define CHARACTERISTIC_CHAT_HITNODEATH 32 //float [0, 1] -//tendency to chat when bot hit the enemy but enemy didn't dye -#define CHARACTERISTIC_CHAT_HITNOKILL 33 //float [0, 1] -//tendency to randomly chat -#define CHARACTERISTIC_CHAT_RANDOM 34 //float [0, 1] -//tendency to reply -#define CHARACTERISTIC_CHAT_REPLY 35 //float [0, 1] -//======================================================== -//movement -//======================================================== -//tendency to crouch -#define CHARACTERISTIC_CROUCHER 36 //float [0, 1] -//tendency to jump -#define CHARACTERISTIC_JUMPER 37 //float [0, 1] -//tendency to walk -#define CHARACTERISTIC_WALKER 48 //float [0, 1] -//tendency to jump using a weapon -#define CHARACTERISTIC_WEAPONJUMPING 38 //float [0, 1] -//tendency to use the grapple hook when available -#define CHARACTERISTIC_GRAPPLE_USER 39 //float [0, 1] //use this!! -//======================================================== -//goal -//======================================================== -//item weight file -#define CHARACTERISTIC_ITEMWEIGHTS 40 //string -//the aggression of the bot -#define CHARACTERISTIC_AGGRESSION 41 //float [0, 1] -//the self preservation of the bot (rockets near walls etc.) -#define CHARACTERISTIC_SELFPRESERVATION 42 //float [0, 1] -//how likely the bot is to take revenge -#define CHARACTERISTIC_VENGEFULNESS 43 //float [0, 1] //use this!! -//tendency to camp -#define CHARACTERISTIC_CAMPER 44 //float [0, 1] -//======================================================== -//======================================================== -//tendency to get easy frags -#define CHARACTERISTIC_EASY_FRAGGER 45 //float [0, 1] -//how alert the bot is (view distance) -#define CHARACTERISTIC_ALERTNESS 46 //float [0, 1] -//how much the bot fires it's weapon -#define CHARACTERISTIC_FIRETHROTTLE 47 //float [0, 1] - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +//======================================================== +//======================================================== +//name +#define CHARACTERISTIC_NAME 0 //string +//gender of the bot +#define CHARACTERISTIC_GENDER 1 //string ("male", "female", "it") +//attack skill +// > 0.0 && < 0.2 = don't move +// > 0.3 && < 1.0 = aim at enemy during retreat +// > 0.0 && < 0.4 = only move forward/backward +// >= 0.4 && < 1.0 = circle strafing +// > 0.7 && < 1.0 = random strafe direction change +#define CHARACTERISTIC_ATTACK_SKILL 2 //float [0, 1] +//weapon weight file +#define CHARACTERISTIC_WEAPONWEIGHTS 3 //string +//view angle difference to angle change factor +#define CHARACTERISTIC_VIEW_FACTOR 4 //float <0, 1] +//maximum view angle change +#define CHARACTERISTIC_VIEW_MAXCHANGE 5 //float [1, 360] +//reaction time in seconds +#define CHARACTERISTIC_REACTIONTIME 6 //float [0, 5] +//accuracy when aiming +#define CHARACTERISTIC_AIM_ACCURACY 7 //float [0, 1] +//weapon specific aim accuracy +#define CHARACTERISTIC_AIM_ACCURACY_MACHINEGUN 8 //float [0, 1] +#define CHARACTERISTIC_AIM_ACCURACY_SHOTGUN 9 //float [0, 1] +#define CHARACTERISTIC_AIM_ACCURACY_ROCKETLAUNCHER 10 //float [0, 1] +#define CHARACTERISTIC_AIM_ACCURACY_GRENADELAUNCHER 11 //float [0, 1] +#define CHARACTERISTIC_AIM_ACCURACY_LIGHTNING 12 +#define CHARACTERISTIC_AIM_ACCURACY_PLASMAGUN 13 //float [0, 1] +#define CHARACTERISTIC_AIM_ACCURACY_RAILGUN 14 +#define CHARACTERISTIC_AIM_ACCURACY_BFG10K 15 //float [0, 1] +//skill when aiming +// > 0.0 && < 0.9 = aim is affected by enemy movement +// > 0.4 && <= 0.8 = enemy linear leading +// > 0.8 && <= 1.0 = enemy exact movement leading +// > 0.5 && <= 1.0 = prediction shots when enemy is not visible +// > 0.6 && <= 1.0 = splash damage by shooting nearby geometry +#define CHARACTERISTIC_AIM_SKILL 16 //float [0, 1] +//weapon specific aim skill +#define CHARACTERISTIC_AIM_SKILL_ROCKETLAUNCHER 17 //float [0, 1] +#define CHARACTERISTIC_AIM_SKILL_GRENADELAUNCHER 18 //float [0, 1] +#define CHARACTERISTIC_AIM_SKILL_PLASMAGUN 19 //float [0, 1] +#define CHARACTERISTIC_AIM_SKILL_BFG10K 20 //float [0, 1] +//======================================================== +//chat +//======================================================== +//file with chats +#define CHARACTERISTIC_CHAT_FILE 21 //string +//name of the chat character +#define CHARACTERISTIC_CHAT_NAME 22 //string +//characters per minute type speed +#define CHARACTERISTIC_CHAT_CPM 23 //integer [1, 4000] +//tendency to insult/praise +#define CHARACTERISTIC_CHAT_INSULT 24 //float [0, 1] +//tendency to chat misc +#define CHARACTERISTIC_CHAT_MISC 25 //float [0, 1] +//tendency to chat at start or end of level +#define CHARACTERISTIC_CHAT_STARTENDLEVEL 26 //float [0, 1] +//tendency to chat entering or exiting the game +#define CHARACTERISTIC_CHAT_ENTEREXITGAME 27 //float [0, 1] +//tendency to chat when killed someone +#define CHARACTERISTIC_CHAT_KILL 28 //float [0, 1] +//tendency to chat when died +#define CHARACTERISTIC_CHAT_DEATH 29 //float [0, 1] +//tendency to chat when enemy suicides +#define CHARACTERISTIC_CHAT_ENEMYSUICIDE 30 //float [0, 1] +//tendency to chat when hit while talking +#define CHARACTERISTIC_CHAT_HITTALKING 31 //float [0, 1] +//tendency to chat when bot was hit but didn't dye +#define CHARACTERISTIC_CHAT_HITNODEATH 32 //float [0, 1] +//tendency to chat when bot hit the enemy but enemy didn't dye +#define CHARACTERISTIC_CHAT_HITNOKILL 33 //float [0, 1] +//tendency to randomly chat +#define CHARACTERISTIC_CHAT_RANDOM 34 //float [0, 1] +//tendency to reply +#define CHARACTERISTIC_CHAT_REPLY 35 //float [0, 1] +//======================================================== +//movement +//======================================================== +//tendency to crouch +#define CHARACTERISTIC_CROUCHER 36 //float [0, 1] +//tendency to jump +#define CHARACTERISTIC_JUMPER 37 //float [0, 1] +//tendency to walk +#define CHARACTERISTIC_WALKER 48 //float [0, 1] +//tendency to jump using a weapon +#define CHARACTERISTIC_WEAPONJUMPING 38 //float [0, 1] +//tendency to use the grapple hook when available +#define CHARACTERISTIC_GRAPPLE_USER 39 //float [0, 1] //use this!! +//======================================================== +//goal +//======================================================== +//item weight file +#define CHARACTERISTIC_ITEMWEIGHTS 40 //string +//the aggression of the bot +#define CHARACTERISTIC_AGGRESSION 41 //float [0, 1] +//the self preservation of the bot (rockets near walls etc.) +#define CHARACTERISTIC_SELFPRESERVATION 42 //float [0, 1] +//how likely the bot is to take revenge +#define CHARACTERISTIC_VENGEFULNESS 43 //float [0, 1] //use this!! +//tendency to camp +#define CHARACTERISTIC_CAMPER 44 //float [0, 1] +//======================================================== +//======================================================== +//tendency to get easy frags +#define CHARACTERISTIC_EASY_FRAGGER 45 //float [0, 1] +//how alert the bot is (view distance) +#define CHARACTERISTIC_ALERTNESS 46 //float [0, 1] +//how much the bot fires it's weapon +#define CHARACTERISTIC_FIRETHROTTLE 47 //float [0, 1] + diff --git a/code/game/g_active.c b/code/game/g_active.c index 853f5fb..923a5e1 100755 --- a/code/game/g_active.c +++ b/code/game/g_active.c @@ -1,1191 +1,1191 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -#include "g_local.h" - - -/* -=============== -G_DamageFeedback - -Called just before a snapshot is sent to the given player. -Totals up all damage and generates both the player_state_t -damage values to that client for pain blends and kicks, and -global pain sound events for all clients. -=============== -*/ -void P_DamageFeedback( gentity_t *player ) { - gclient_t *client; - float count; - vec3_t angles; - - client = player->client; - if ( client->ps.pm_type == PM_DEAD ) { - return; - } - - // total points of damage shot at the player this frame - count = client->damage_blood + client->damage_armor; - if ( count == 0 ) { - return; // didn't take any damage - } - - if ( count > 255 ) { - count = 255; - } - - // send the information to the client - - // world damage (falling, slime, etc) uses a special code - // to make the blend blob centered instead of positional - if ( client->damage_fromWorld ) { - client->ps.damagePitch = 255; - client->ps.damageYaw = 255; - - client->damage_fromWorld = qfalse; - } else { - vectoangles( client->damage_from, angles ); - client->ps.damagePitch = angles[PITCH]/360.0 * 256; - client->ps.damageYaw = angles[YAW]/360.0 * 256; - } - - // play an apropriate pain sound - if ( (level.time > player->pain_debounce_time) && !(player->flags & FL_GODMODE) ) { - player->pain_debounce_time = level.time + 700; - G_AddEvent( player, EV_PAIN, player->health ); - client->ps.damageEvent++; - } - - - client->ps.damageCount = count; - - // - // clear totals - // - client->damage_blood = 0; - client->damage_armor = 0; - client->damage_knockback = 0; -} - - - -/* -============= -P_WorldEffects - -Check for lava / slime contents and drowning -============= -*/ -void P_WorldEffects( gentity_t *ent ) { - qboolean envirosuit; - int waterlevel; - - if ( ent->client->noclip ) { - ent->client->airOutTime = level.time + 12000; // don't need air - return; - } - - waterlevel = ent->waterlevel; - - envirosuit = ent->client->ps.powerups[PW_BATTLESUIT] > level.time; - - // - // check for drowning - // - if ( waterlevel == 3 ) { - // envirosuit give air - if ( envirosuit ) { - ent->client->airOutTime = level.time + 10000; - } - - // if out of air, start drowning - if ( ent->client->airOutTime < level.time) { - // drown! - ent->client->airOutTime += 1000; - if ( ent->health > 0 ) { - // take more damage the longer underwater - ent->damage += 2; - if (ent->damage > 15) - ent->damage = 15; - - // play a gurp sound instead of a normal pain sound - if (ent->health <= ent->damage) { - G_Sound(ent, CHAN_VOICE, G_SoundIndex("*drown.wav")); - } else if (rand()&1) { - G_Sound(ent, CHAN_VOICE, G_SoundIndex("sound/player/gurp1.wav")); - } else { - G_Sound(ent, CHAN_VOICE, G_SoundIndex("sound/player/gurp2.wav")); - } - - // don't play a normal pain sound - ent->pain_debounce_time = level.time + 200; - - G_Damage (ent, NULL, NULL, NULL, NULL, - ent->damage, DAMAGE_NO_ARMOR, MOD_WATER); - } - } - } else { - ent->client->airOutTime = level.time + 12000; - ent->damage = 2; - } - - // - // check for sizzle damage (move to pmove?) - // - if (waterlevel && - (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) { - if (ent->health > 0 - && ent->pain_debounce_time <= level.time ) { - - if ( envirosuit ) { - G_AddEvent( ent, EV_POWERUP_BATTLESUIT, 0 ); - } else { - if (ent->watertype & CONTENTS_LAVA) { - G_Damage (ent, NULL, NULL, NULL, NULL, - 30*waterlevel, 0, MOD_LAVA); - } - - if (ent->watertype & CONTENTS_SLIME) { - G_Damage (ent, NULL, NULL, NULL, NULL, - 10*waterlevel, 0, MOD_SLIME); - } - } - } - } -} - - - -/* -=============== -G_SetClientSound -=============== -*/ -void G_SetClientSound( gentity_t *ent ) { -#ifdef MISSIONPACK - if( ent->s.eFlags & EF_TICKING ) { - ent->client->ps.loopSound = G_SoundIndex( "sound/weapons/proxmine/wstbtick.wav"); - } - else -#endif - if (ent->waterlevel && (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) { - ent->client->ps.loopSound = level.snd_fry; - } else { - ent->client->ps.loopSound = 0; - } -} - - - -//============================================================== - -/* -============== -ClientImpacts -============== -*/ -void ClientImpacts( gentity_t *ent, pmove_t *pm ) { - int i, j; - trace_t trace; - gentity_t *other; - - memset( &trace, 0, sizeof( trace ) ); - for (i=0 ; inumtouch ; i++) { - for (j=0 ; jtouchents[j] == pm->touchents[i] ) { - break; - } - } - if (j != i) { - continue; // duplicated - } - other = &g_entities[ pm->touchents[i] ]; - - if ( ( ent->r.svFlags & SVF_BOT ) && ( ent->touch ) ) { - ent->touch( ent, other, &trace ); - } - - if ( !other->touch ) { - continue; - } - - other->touch( other, ent, &trace ); - } - -} - -/* -============ -G_TouchTriggers - -Find all trigger entities that ent's current position touches. -Spectators will only interact with teleporters. -============ -*/ -void G_TouchTriggers( gentity_t *ent ) { - int i, num; - int touch[MAX_GENTITIES]; - gentity_t *hit; - trace_t trace; - vec3_t mins, maxs; - static vec3_t range = { 40, 40, 52 }; - - if ( !ent->client ) { - return; - } - - // dead clients don't activate triggers! - if ( ent->client->ps.stats[STAT_HEALTH] <= 0 ) { - return; - } - - VectorSubtract( ent->client->ps.origin, range, mins ); - VectorAdd( ent->client->ps.origin, range, maxs ); - - num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); - - // can't use ent->absmin, because that has a one unit pad - VectorAdd( ent->client->ps.origin, ent->r.mins, mins ); - VectorAdd( ent->client->ps.origin, ent->r.maxs, maxs ); - - for ( i=0 ; itouch && !ent->touch ) { - continue; - } - if ( !( hit->r.contents & CONTENTS_TRIGGER ) ) { - continue; - } - - // ignore most entities if a spectator - if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { - if ( hit->s.eType != ET_TELEPORT_TRIGGER && - // this is ugly but adding a new ET_? type will - // most likely cause network incompatibilities - hit->touch != Touch_DoorTrigger) { - continue; - } - } - - // use seperate code for determining if an item is picked up - // so you don't have to actually contact its bounding box - if ( hit->s.eType == ET_ITEM ) { - if ( !BG_PlayerTouchesItem( &ent->client->ps, &hit->s, level.time ) ) { - continue; - } - } else { - if ( !trap_EntityContact( mins, maxs, hit ) ) { - continue; - } - } - - memset( &trace, 0, sizeof(trace) ); - - if ( hit->touch ) { - hit->touch (hit, ent, &trace); - } - - if ( ( ent->r.svFlags & SVF_BOT ) && ( ent->touch ) ) { - ent->touch( ent, hit, &trace ); - } - } - - // if we didn't touch a jump pad this pmove frame - if ( ent->client->ps.jumppad_frame != ent->client->ps.pmove_framecount ) { - ent->client->ps.jumppad_frame = 0; - ent->client->ps.jumppad_ent = 0; - } -} - -/* -================= -SpectatorThink -================= -*/ -void SpectatorThink( gentity_t *ent, usercmd_t *ucmd ) { - pmove_t pm; - gclient_t *client; - - client = ent->client; - - if ( client->sess.spectatorState != SPECTATOR_FOLLOW ) { - client->ps.pm_type = PM_SPECTATOR; - client->ps.speed = 400; // faster than normal - - // set up for pmove - memset (&pm, 0, sizeof(pm)); - pm.ps = &client->ps; - pm.cmd = *ucmd; - pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; // spectators can fly through bodies - pm.trace = trap_Trace; - pm.pointcontents = trap_PointContents; - - // perform a pmove - Pmove (&pm); - // save results of pmove - VectorCopy( client->ps.origin, ent->s.origin ); - - G_TouchTriggers( ent ); - trap_UnlinkEntity( ent ); - } - - client->oldbuttons = client->buttons; - client->buttons = ucmd->buttons; - - // attack button cycles through spectators - if ( ( client->buttons & BUTTON_ATTACK ) && ! ( client->oldbuttons & BUTTON_ATTACK ) ) { - Cmd_FollowCycle_f( ent, 1 ); - } -} - - - -/* -================= -ClientInactivityTimer - -Returns qfalse if the client is dropped -================= -*/ -qboolean ClientInactivityTimer( gclient_t *client ) { - if ( ! g_inactivity.integer ) { - // give everyone some time, so if the operator sets g_inactivity during - // gameplay, everyone isn't kicked - client->inactivityTime = level.time + 60 * 1000; - client->inactivityWarning = qfalse; - } else if ( client->pers.cmd.forwardmove || - client->pers.cmd.rightmove || - client->pers.cmd.upmove || - (client->pers.cmd.buttons & BUTTON_ATTACK) ) { - client->inactivityTime = level.time + g_inactivity.integer * 1000; - client->inactivityWarning = qfalse; - } else if ( !client->pers.localClient ) { - if ( level.time > client->inactivityTime ) { - trap_DropClient( client - level.clients, "Dropped due to inactivity" ); - return qfalse; - } - if ( level.time > client->inactivityTime - 10000 && !client->inactivityWarning ) { - client->inactivityWarning = qtrue; - trap_SendServerCommand( client - level.clients, "cp \"Ten seconds until inactivity drop!\n\"" ); - } - } - return qtrue; -} - -/* -================== -ClientTimerActions - -Actions that happen once a second -================== -*/ -void ClientTimerActions( gentity_t *ent, int msec ) { - gclient_t *client; -#ifdef MISSIONPACK - int maxHealth; -#endif - - client = ent->client; - client->timeResidual += msec; - - while ( client->timeResidual >= 1000 ) { - client->timeResidual -= 1000; - - // regenerate -#ifdef MISSIONPACK - if( bg_itemlist[client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) { - maxHealth = client->ps.stats[STAT_MAX_HEALTH] / 2; - } - else if ( client->ps.powerups[PW_REGEN] ) { - maxHealth = client->ps.stats[STAT_MAX_HEALTH]; - } - else { - maxHealth = 0; - } - if( maxHealth ) { - if ( ent->health < maxHealth ) { - ent->health += 15; - if ( ent->health > maxHealth * 1.1 ) { - ent->health = maxHealth * 1.1; - } - G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); - } else if ( ent->health < maxHealth * 2) { - ent->health += 5; - if ( ent->health > maxHealth * 2 ) { - ent->health = maxHealth * 2; - } - G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); - } -#else - if ( client->ps.powerups[PW_REGEN] ) { - if ( ent->health < client->ps.stats[STAT_MAX_HEALTH]) { - ent->health += 15; - if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] * 1.1 ) { - ent->health = client->ps.stats[STAT_MAX_HEALTH] * 1.1; - } - G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); - } else if ( ent->health < client->ps.stats[STAT_MAX_HEALTH] * 2) { - ent->health += 5; - if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] * 2 ) { - ent->health = client->ps.stats[STAT_MAX_HEALTH] * 2; - } - G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); - } -#endif - } else { - // count down health when over max - if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] ) { - ent->health--; - } - } - - // count down armor when over max - if ( client->ps.stats[STAT_ARMOR] > client->ps.stats[STAT_MAX_HEALTH] ) { - client->ps.stats[STAT_ARMOR]--; - } - } -#ifdef MISSIONPACK - if( bg_itemlist[client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_AMMOREGEN ) { - int w, max, inc, t, i; - int weapList[]={WP_MACHINEGUN,WP_SHOTGUN,WP_GRENADE_LAUNCHER,WP_ROCKET_LAUNCHER,WP_LIGHTNING,WP_RAILGUN,WP_PLASMAGUN,WP_BFG,WP_NAILGUN,WP_PROX_LAUNCHER,WP_CHAINGUN}; - int weapCount = sizeof(weapList) / sizeof(int); - // - for (i = 0; i < weapCount; i++) { - w = weapList[i]; - - switch(w) { - case WP_MACHINEGUN: max = 50; inc = 4; t = 1000; break; - case WP_SHOTGUN: max = 10; inc = 1; t = 1500; break; - case WP_GRENADE_LAUNCHER: max = 10; inc = 1; t = 2000; break; - case WP_ROCKET_LAUNCHER: max = 10; inc = 1; t = 1750; break; - case WP_LIGHTNING: max = 50; inc = 5; t = 1500; break; - case WP_RAILGUN: max = 10; inc = 1; t = 1750; break; - case WP_PLASMAGUN: max = 50; inc = 5; t = 1500; break; - case WP_BFG: max = 10; inc = 1; t = 4000; break; - case WP_NAILGUN: max = 10; inc = 1; t = 1250; break; - case WP_PROX_LAUNCHER: max = 5; inc = 1; t = 2000; break; - case WP_CHAINGUN: max = 100; inc = 5; t = 1000; break; - default: max = 0; inc = 0; t = 1000; break; - } - client->ammoTimes[w] += msec; - if ( client->ps.ammo[w] >= max ) { - client->ammoTimes[w] = 0; - } - if ( client->ammoTimes[w] >= t ) { - while ( client->ammoTimes[w] >= t ) - client->ammoTimes[w] -= t; - client->ps.ammo[w] += inc; - if ( client->ps.ammo[w] > max ) { - client->ps.ammo[w] = max; - } - } - } - } -#endif -} - -/* -==================== -ClientIntermissionThink -==================== -*/ -void ClientIntermissionThink( gclient_t *client ) { - client->ps.eFlags &= ~EF_TALK; - client->ps.eFlags &= ~EF_FIRING; - - // the level will exit when everyone wants to or after timeouts - - // swap and latch button actions - client->oldbuttons = client->buttons; - client->buttons = client->pers.cmd.buttons; - if ( client->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) & ( client->oldbuttons ^ client->buttons ) ) { - // this used to be an ^1 but once a player says ready, it should stick - client->readyToExit = 1; - } -} - - -/* -================ -ClientEvents - -Events will be passed on to the clients for presentation, -but any server game effects are handled here -================ -*/ -void ClientEvents( gentity_t *ent, int oldEventSequence ) { - int i, j; - int event; - gclient_t *client; - int damage; - vec3_t dir; - vec3_t origin, angles; -// qboolean fired; - gitem_t *item; - gentity_t *drop; - - client = ent->client; - - if ( oldEventSequence < client->ps.eventSequence - MAX_PS_EVENTS ) { - oldEventSequence = client->ps.eventSequence - MAX_PS_EVENTS; - } - for ( i = oldEventSequence ; i < client->ps.eventSequence ; i++ ) { - event = client->ps.events[ i & (MAX_PS_EVENTS-1) ]; - - switch ( event ) { - case EV_FALL_MEDIUM: - case EV_FALL_FAR: - if ( ent->s.eType != ET_PLAYER ) { - break; // not in the player model - } - if ( g_dmflags.integer & DF_NO_FALLING ) { - break; - } - if ( event == EV_FALL_FAR ) { - damage = 10; - } else { - damage = 5; - } - VectorSet (dir, 0, 0, 1); - ent->pain_debounce_time = level.time + 200; // no normal pain sound - G_Damage (ent, NULL, NULL, NULL, NULL, damage, 0, MOD_FALLING); - break; - - case EV_FIRE_WEAPON: - FireWeapon( ent ); - break; - - case EV_USE_ITEM1: // teleporter - // drop flags in CTF - item = NULL; - j = 0; - - if ( ent->client->ps.powerups[ PW_REDFLAG ] ) { - item = BG_FindItemForPowerup( PW_REDFLAG ); - j = PW_REDFLAG; - } else if ( ent->client->ps.powerups[ PW_BLUEFLAG ] ) { - item = BG_FindItemForPowerup( PW_BLUEFLAG ); - j = PW_BLUEFLAG; - } else if ( ent->client->ps.powerups[ PW_NEUTRALFLAG ] ) { - item = BG_FindItemForPowerup( PW_NEUTRALFLAG ); - j = PW_NEUTRALFLAG; - } - - if ( item ) { - drop = Drop_Item( ent, item, 0 ); - // decide how many seconds it has left - drop->count = ( ent->client->ps.powerups[ j ] - level.time ) / 1000; - if ( drop->count < 1 ) { - drop->count = 1; - } - - ent->client->ps.powerups[ j ] = 0; - } - -#ifdef MISSIONPACK - if ( g_gametype.integer == GT_HARVESTER ) { - if ( ent->client->ps.generic1 > 0 ) { - if ( ent->client->sess.sessionTeam == TEAM_RED ) { - item = BG_FindItem( "Blue Cube" ); - } else { - item = BG_FindItem( "Red Cube" ); - } - if ( item ) { - for ( j = 0; j < ent->client->ps.generic1; j++ ) { - drop = Drop_Item( ent, item, 0 ); - if ( ent->client->sess.sessionTeam == TEAM_RED ) { - drop->spawnflags = TEAM_BLUE; - } else { - drop->spawnflags = TEAM_RED; - } - } - } - ent->client->ps.generic1 = 0; - } - } -#endif - SelectSpawnPoint( ent->client->ps.origin, origin, angles ); - TeleportPlayer( ent, origin, angles ); - break; - - case EV_USE_ITEM2: // medkit - ent->health = ent->client->ps.stats[STAT_MAX_HEALTH] + 25; - - break; - -#ifdef MISSIONPACK - case EV_USE_ITEM3: // kamikaze - // make sure the invulnerability is off - ent->client->invulnerabilityTime = 0; - // start the kamikze - G_StartKamikaze( ent ); - break; - - case EV_USE_ITEM4: // portal - if( ent->client->portalID ) { - DropPortalSource( ent ); - } - else { - DropPortalDestination( ent ); - } - break; - case EV_USE_ITEM5: // invulnerability - ent->client->invulnerabilityTime = level.time + 10000; - break; -#endif - - default: - break; - } - } - -} - -#ifdef MISSIONPACK -/* -============== -StuckInOtherClient -============== -*/ -static int StuckInOtherClient(gentity_t *ent) { - int i; - gentity_t *ent2; - - ent2 = &g_entities[0]; - for ( i = 0; i < MAX_CLIENTS; i++, ent2++ ) { - if ( ent2 == ent ) { - continue; - } - if ( !ent2->inuse ) { - continue; - } - if ( !ent2->client ) { - continue; - } - if ( ent2->health <= 0 ) { - continue; - } - // - if (ent2->r.absmin[0] > ent->r.absmax[0]) - continue; - if (ent2->r.absmin[1] > ent->r.absmax[1]) - continue; - if (ent2->r.absmin[2] > ent->r.absmax[2]) - continue; - if (ent2->r.absmax[0] < ent->r.absmin[0]) - continue; - if (ent2->r.absmax[1] < ent->r.absmin[1]) - continue; - if (ent2->r.absmax[2] < ent->r.absmin[2]) - continue; - return qtrue; - } - return qfalse; -} -#endif - -void BotTestSolid(vec3_t origin); - -/* -============== -SendPendingPredictableEvents -============== -*/ -void SendPendingPredictableEvents( playerState_t *ps ) { - gentity_t *t; - int event, seq; - int extEvent, number; - - // if there are still events pending - if ( ps->entityEventSequence < ps->eventSequence ) { - // create a temporary entity for this event which is sent to everyone - // except the client who generated the event - seq = ps->entityEventSequence & (MAX_PS_EVENTS-1); - event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 ); - // set external event to zero before calling BG_PlayerStateToEntityState - extEvent = ps->externalEvent; - ps->externalEvent = 0; - // create temporary entity for event - t = G_TempEntity( ps->origin, event ); - number = t->s.number; - BG_PlayerStateToEntityState( ps, &t->s, qtrue ); - t->s.number = number; - t->s.eType = ET_EVENTS + event; - t->s.eFlags |= EF_PLAYER_EVENT; - t->s.otherEntityNum = ps->clientNum; - // send to everyone except the client who generated the event - t->r.svFlags |= SVF_NOTSINGLECLIENT; - t->r.singleClient = ps->clientNum; - // set back external event - ps->externalEvent = extEvent; - } -} - -/* -============== -ClientThink - -This will be called once for each client frame, which will -usually be a couple times for each server frame on fast clients. - -If "g_synchronousClients 1" is set, this will be called exactly -once for each server frame, which makes for smooth demo recording. -============== -*/ -void ClientThink_real( gentity_t *ent ) { - gclient_t *client; - pmove_t pm; - int oldEventSequence; - int msec; - usercmd_t *ucmd; - - client = ent->client; - - // don't think if the client is not yet connected (and thus not yet spawned in) - if (client->pers.connected != CON_CONNECTED) { - return; - } - // mark the time, so the connection sprite can be removed - ucmd = &ent->client->pers.cmd; - - // sanity check the command time to prevent speedup cheating - if ( ucmd->serverTime > level.time + 200 ) { - ucmd->serverTime = level.time + 200; -// G_Printf("serverTime <<<<<\n" ); - } - if ( ucmd->serverTime < level.time - 1000 ) { - ucmd->serverTime = level.time - 1000; -// G_Printf("serverTime >>>>>\n" ); - } - - msec = ucmd->serverTime - client->ps.commandTime; - // following others may result in bad times, but we still want - // to check for follow toggles - if ( msec < 1 && client->sess.spectatorState != SPECTATOR_FOLLOW ) { - return; - } - if ( msec > 200 ) { - msec = 200; - } - - if ( pmove_msec.integer < 8 ) { - trap_Cvar_Set("pmove_msec", "8"); - } - else if (pmove_msec.integer > 33) { - trap_Cvar_Set("pmove_msec", "33"); - } - - if ( pmove_fixed.integer || client->pers.pmoveFixed ) { - ucmd->serverTime = ((ucmd->serverTime + pmove_msec.integer-1) / pmove_msec.integer) * pmove_msec.integer; - //if (ucmd->serverTime - client->ps.commandTime <= 0) - // return; - } - - // - // check for exiting intermission - // - if ( level.intermissiontime ) { - ClientIntermissionThink( client ); - return; - } - - // spectators don't do much - if ( client->sess.sessionTeam == TEAM_SPECTATOR ) { - if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) { - return; - } - SpectatorThink( ent, ucmd ); - return; - } - - // check for inactivity timer, but never drop the local client of a non-dedicated server - if ( !ClientInactivityTimer( client ) ) { - return; - } - - // clear the rewards if time - if ( level.time > client->rewardTime ) { - client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP ); - } - - 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; - } - - client->ps.gravity = g_gravity.value; - - // set speed - client->ps.speed = g_speed.value; - -#ifdef MISSIONPACK - if( bg_itemlist[client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_SCOUT ) { - client->ps.speed *= 1.5; - } - else -#endif - if ( client->ps.powerups[PW_HASTE] ) { - client->ps.speed *= 1.3; - } - - // Let go of the hook if we aren't firing - if ( client->ps.weapon == WP_GRAPPLING_HOOK && - client->hook && !( ucmd->buttons & BUTTON_ATTACK ) ) { - Weapon_HookFree(client->hook); - } - - // set up for pmove - oldEventSequence = client->ps.eventSequence; - - memset (&pm, 0, sizeof(pm)); - - // check for the hit-scan gauntlet, don't let the action - // go through as an attack unless it actually hits something - if ( client->ps.weapon == WP_GAUNTLET && !( ucmd->buttons & BUTTON_TALK ) && - ( ucmd->buttons & BUTTON_ATTACK ) && client->ps.weaponTime <= 0 ) { - pm.gauntletHit = CheckGauntletAttack( ent ); - } - - if ( ent->flags & FL_FORCE_GESTURE ) { - ent->flags &= ~FL_FORCE_GESTURE; - ent->client->pers.cmd.buttons |= BUTTON_GESTURE; - } - -#ifdef MISSIONPACK - // check for invulnerability expansion before doing the Pmove - if (client->ps.powerups[PW_INVULNERABILITY] ) { - if ( !(client->ps.pm_flags & PMF_INVULEXPAND) ) { - vec3_t mins = { -42, -42, -42 }; - vec3_t maxs = { 42, 42, 42 }; - vec3_t oldmins, oldmaxs; - - VectorCopy (ent->r.mins, oldmins); - VectorCopy (ent->r.maxs, oldmaxs); - // expand - VectorCopy (mins, ent->r.mins); - VectorCopy (maxs, ent->r.maxs); - trap_LinkEntity(ent); - // check if this would get anyone stuck in this player - if ( !StuckInOtherClient(ent) ) { - // set flag so the expanded size will be set in PM_CheckDuck - client->ps.pm_flags |= PMF_INVULEXPAND; - } - // set back - VectorCopy (oldmins, ent->r.mins); - VectorCopy (oldmaxs, ent->r.maxs); - trap_LinkEntity(ent); - } - } -#endif - - pm.ps = &client->ps; - pm.cmd = *ucmd; - if ( pm.ps->pm_type == PM_DEAD ) { - pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; - } - else if ( ent->r.svFlags & SVF_BOT ) { - pm.tracemask = MASK_PLAYERSOLID | CONTENTS_BOTCLIP; - } - else { - pm.tracemask = MASK_PLAYERSOLID; - } - pm.trace = trap_Trace; - pm.pointcontents = trap_PointContents; - pm.debugLevel = g_debugMove.integer; - pm.noFootsteps = ( g_dmflags.integer & DF_NO_FOOTSTEPS ) > 0; - - pm.pmove_fixed = pmove_fixed.integer | client->pers.pmoveFixed; - pm.pmove_msec = pmove_msec.integer; - - VectorCopy( client->ps.origin, client->oldOrigin ); - -#ifdef MISSIONPACK - if (level.intermissionQueued != 0 && g_singlePlayer.integer) { - if ( level.time - level.intermissionQueued >= 1000 ) { - pm.cmd.buttons = 0; - pm.cmd.forwardmove = 0; - pm.cmd.rightmove = 0; - pm.cmd.upmove = 0; - if ( level.time - level.intermissionQueued >= 2000 && level.time - level.intermissionQueued <= 2500 ) { - trap_SendConsoleCommand( EXEC_APPEND, "centerview\n"); - } - ent->client->ps.pm_type = PM_SPINTERMISSION; - } - } - Pmove (&pm); -#else - Pmove (&pm); -#endif - - // save results of pmove - if ( ent->client->ps.eventSequence != oldEventSequence ) { - ent->eventTime = level.time; - } - if (g_smoothClients.integer) { - BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue ); - } - else { - BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); - } - SendPendingPredictableEvents( &ent->client->ps ); - - if ( !( ent->client->ps.eFlags & EF_FIRING ) ) { - client->fireHeld = qfalse; // for grapple - } - - // use the snapped origin for linking so it matches client predicted versions - VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin ); - - VectorCopy (pm.mins, ent->r.mins); - VectorCopy (pm.maxs, ent->r.maxs); - - ent->waterlevel = pm.waterlevel; - ent->watertype = pm.watertype; - - // execute client events - ClientEvents( ent, oldEventSequence ); - - // link entity now, after any personal teleporters have been used - trap_LinkEntity (ent); - if ( !ent->client->noclip ) { - G_TouchTriggers( ent ); - } - - // NOTE: now copy the exact origin over otherwise clients can be snapped into solid - VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); - - //test for solid areas in the AAS file - BotTestAAS(ent->r.currentOrigin); - - // touch other objects - ClientImpacts( ent, &pm ); - - // save results of triggers and client events - if (ent->client->ps.eventSequence != oldEventSequence) { - ent->eventTime = level.time; - } - - // swap and latch button actions - client->oldbuttons = client->buttons; - client->buttons = ucmd->buttons; - client->latched_buttons |= client->buttons & ~client->oldbuttons; - - // check for respawning - if ( client->ps.stats[STAT_HEALTH] <= 0 ) { - // wait for the attack button to be pressed - if ( level.time > client->respawnTime ) { - // forcerespawn is to prevent users from waiting out powerups - if ( g_forcerespawn.integer > 0 && - ( level.time - client->respawnTime ) > g_forcerespawn.integer * 1000 ) { - respawn( ent ); - return; - } - - // pressing attack or use is the normal respawn method - if ( ucmd->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) ) { - respawn( ent ); - } - } - return; - } - - // perform once-a-second actions - ClientTimerActions( ent, msec ); -} - -/* -================== -ClientThink - -A new command has arrived from the client -================== -*/ -void ClientThink( int clientNum ) { - gentity_t *ent; - - ent = g_entities + clientNum; - trap_GetUsercmd( clientNum, &ent->client->pers.cmd ); - - // mark the time we got info, so we can display the - // phone jack if they don't get any for a while - ent->client->lastCmdTime = level.time; - - if ( !(ent->r.svFlags & SVF_BOT) && !g_synchronousClients.integer ) { - ClientThink_real( ent ); - } -} - - -void G_RunClient( gentity_t *ent ) { - if ( !(ent->r.svFlags & SVF_BOT) && !g_synchronousClients.integer ) { - return; - } - ent->client->pers.cmd.serverTime = level.time; - ClientThink_real( ent ); -} - - -/* -================== -SpectatorClientEndFrame - -================== -*/ -void SpectatorClientEndFrame( gentity_t *ent ) { - gclient_t *cl; - - // if we are doing a chase cam or a remote view, grab the latest info - if ( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) { - int clientNum, flags; - - clientNum = ent->client->sess.spectatorClient; - - // team follow1 and team follow2 go to whatever clients are playing - if ( clientNum == -1 ) { - clientNum = level.follow1; - } else if ( clientNum == -2 ) { - clientNum = level.follow2; - } - if ( clientNum >= 0 ) { - cl = &level.clients[ clientNum ]; - if ( cl->pers.connected == CON_CONNECTED && cl->sess.sessionTeam != TEAM_SPECTATOR ) { - flags = (cl->ps.eFlags & ~(EF_VOTED | EF_TEAMVOTED)) | (ent->client->ps.eFlags & (EF_VOTED | EF_TEAMVOTED)); - ent->client->ps = cl->ps; - ent->client->ps.pm_flags |= PMF_FOLLOW; - ent->client->ps.eFlags = flags; - return; - } else { - // drop them to free spectators unless they are dedicated camera followers - if ( ent->client->sess.spectatorClient >= 0 ) { - ent->client->sess.spectatorState = SPECTATOR_FREE; - ClientBegin( ent->client - level.clients ); - } - } - } - } - - if ( ent->client->sess.spectatorState == SPECTATOR_SCOREBOARD ) { - ent->client->ps.pm_flags |= PMF_SCOREBOARD; - } else { - ent->client->ps.pm_flags &= ~PMF_SCOREBOARD; - } -} - -/* -============== -ClientEndFrame - -Called at the end of each server frame for each connected client -A fast client will have multiple ClientThink for each ClientEdFrame, -while a slow client may have multiple ClientEndFrame between ClientThink. -============== -*/ -void ClientEndFrame( gentity_t *ent ) { - int i; - clientPersistant_t *pers; - - if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { - SpectatorClientEndFrame( ent ); - return; - } - - pers = &ent->client->pers; - - // turn off any expired powerups - for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { - if ( ent->client->ps.powerups[ i ] < level.time ) { - ent->client->ps.powerups[ i ] = 0; - } - } - -#ifdef MISSIONPACK - // set powerup for player animation - if( bg_itemlist[ent->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) { - ent->client->ps.powerups[PW_GUARD] = level.time; - } - if( bg_itemlist[ent->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_SCOUT ) { - ent->client->ps.powerups[PW_SCOUT] = level.time; - } - if( bg_itemlist[ent->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_DOUBLER ) { - ent->client->ps.powerups[PW_DOUBLER] = level.time; - } - if( bg_itemlist[ent->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_AMMOREGEN ) { - ent->client->ps.powerups[PW_AMMOREGEN] = level.time; - } - if ( ent->client->invulnerabilityTime > level.time ) { - ent->client->ps.powerups[PW_INVULNERABILITY] = level.time; - } -#endif - - // save network bandwidth -#if 0 - if ( !g_synchronousClients->integer && ent->client->ps.pm_type == PM_NORMAL ) { - // FIXME: this must change eventually for non-sync demo recording - VectorClear( ent->client->ps.viewangles ); - } -#endif - - // - // If the end of unit layout is displayed, don't give - // the player any normal movement attributes - // - if ( level.intermissiontime ) { - return; - } - - // burn from lava, etc - P_WorldEffects (ent); - - // apply all the damage taken this frame - P_DamageFeedback (ent); - - // add the EF_CONNECTION flag if we haven't gotten commands recently - if ( level.time - ent->client->lastCmdTime > 1000 ) { - ent->s.eFlags |= EF_CONNECTION; - } else { - ent->s.eFlags &= ~EF_CONNECTION; - } - - ent->client->ps.stats[STAT_HEALTH] = ent->health; // FIXME: get rid of ent->health... - - G_SetClientSound (ent); - - // set the latest infor - if (g_smoothClients.integer) { - BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue ); - } - else { - BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); - } - SendPendingPredictableEvents( &ent->client->ps ); - - // set the bit for the reachability area the client is currently in -// i = trap_AAS_PointReachabilityAreaIndex( ent->client->ps.origin ); -// ent->client->areabits[i >> 3] |= 1 << (i & 7); -} - - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +#include "g_local.h" + + +/* +=============== +G_DamageFeedback + +Called just before a snapshot is sent to the given player. +Totals up all damage and generates both the player_state_t +damage values to that client for pain blends and kicks, and +global pain sound events for all clients. +=============== +*/ +void P_DamageFeedback( gentity_t *player ) { + gclient_t *client; + float count; + vec3_t angles; + + client = player->client; + if ( client->ps.pm_type == PM_DEAD ) { + return; + } + + // total points of damage shot at the player this frame + count = client->damage_blood + client->damage_armor; + if ( count == 0 ) { + return; // didn't take any damage + } + + if ( count > 255 ) { + count = 255; + } + + // send the information to the client + + // world damage (falling, slime, etc) uses a special code + // to make the blend blob centered instead of positional + if ( client->damage_fromWorld ) { + client->ps.damagePitch = 255; + client->ps.damageYaw = 255; + + client->damage_fromWorld = qfalse; + } else { + vectoangles( client->damage_from, angles ); + client->ps.damagePitch = angles[PITCH]/360.0 * 256; + client->ps.damageYaw = angles[YAW]/360.0 * 256; + } + + // play an apropriate pain sound + if ( (level.time > player->pain_debounce_time) && !(player->flags & FL_GODMODE) ) { + player->pain_debounce_time = level.time + 700; + G_AddEvent( player, EV_PAIN, player->health ); + client->ps.damageEvent++; + } + + + client->ps.damageCount = count; + + // + // clear totals + // + client->damage_blood = 0; + client->damage_armor = 0; + client->damage_knockback = 0; +} + + + +/* +============= +P_WorldEffects + +Check for lava / slime contents and drowning +============= +*/ +void P_WorldEffects( gentity_t *ent ) { + qboolean envirosuit; + int waterlevel; + + if ( ent->client->noclip ) { + ent->client->airOutTime = level.time + 12000; // don't need air + return; + } + + waterlevel = ent->waterlevel; + + envirosuit = ent->client->ps.powerups[PW_BATTLESUIT] > level.time; + + // + // check for drowning + // + if ( waterlevel == 3 ) { + // envirosuit give air + if ( envirosuit ) { + ent->client->airOutTime = level.time + 10000; + } + + // if out of air, start drowning + if ( ent->client->airOutTime < level.time) { + // drown! + ent->client->airOutTime += 1000; + if ( ent->health > 0 ) { + // take more damage the longer underwater + ent->damage += 2; + if (ent->damage > 15) + ent->damage = 15; + + // play a gurp sound instead of a normal pain sound + if (ent->health <= ent->damage) { + G_Sound(ent, CHAN_VOICE, G_SoundIndex("*drown.wav")); + } else if (rand()&1) { + G_Sound(ent, CHAN_VOICE, G_SoundIndex("sound/player/gurp1.wav")); + } else { + G_Sound(ent, CHAN_VOICE, G_SoundIndex("sound/player/gurp2.wav")); + } + + // don't play a normal pain sound + ent->pain_debounce_time = level.time + 200; + + G_Damage (ent, NULL, NULL, NULL, NULL, + ent->damage, DAMAGE_NO_ARMOR, MOD_WATER); + } + } + } else { + ent->client->airOutTime = level.time + 12000; + ent->damage = 2; + } + + // + // check for sizzle damage (move to pmove?) + // + if (waterlevel && + (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) { + if (ent->health > 0 + && ent->pain_debounce_time <= level.time ) { + + if ( envirosuit ) { + G_AddEvent( ent, EV_POWERUP_BATTLESUIT, 0 ); + } else { + if (ent->watertype & CONTENTS_LAVA) { + G_Damage (ent, NULL, NULL, NULL, NULL, + 30*waterlevel, 0, MOD_LAVA); + } + + if (ent->watertype & CONTENTS_SLIME) { + G_Damage (ent, NULL, NULL, NULL, NULL, + 10*waterlevel, 0, MOD_SLIME); + } + } + } + } +} + + + +/* +=============== +G_SetClientSound +=============== +*/ +void G_SetClientSound( gentity_t *ent ) { +#ifdef MISSIONPACK + if( ent->s.eFlags & EF_TICKING ) { + ent->client->ps.loopSound = G_SoundIndex( "sound/weapons/proxmine/wstbtick.wav"); + } + else +#endif + if (ent->waterlevel && (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) { + ent->client->ps.loopSound = level.snd_fry; + } else { + ent->client->ps.loopSound = 0; + } +} + + + +//============================================================== + +/* +============== +ClientImpacts +============== +*/ +void ClientImpacts( gentity_t *ent, pmove_t *pm ) { + int i, j; + trace_t trace; + gentity_t *other; + + memset( &trace, 0, sizeof( trace ) ); + for (i=0 ; inumtouch ; i++) { + for (j=0 ; jtouchents[j] == pm->touchents[i] ) { + break; + } + } + if (j != i) { + continue; // duplicated + } + other = &g_entities[ pm->touchents[i] ]; + + if ( ( ent->r.svFlags & SVF_BOT ) && ( ent->touch ) ) { + ent->touch( ent, other, &trace ); + } + + if ( !other->touch ) { + continue; + } + + other->touch( other, ent, &trace ); + } + +} + +/* +============ +G_TouchTriggers + +Find all trigger entities that ent's current position touches. +Spectators will only interact with teleporters. +============ +*/ +void G_TouchTriggers( gentity_t *ent ) { + int i, num; + int touch[MAX_GENTITIES]; + gentity_t *hit; + trace_t trace; + vec3_t mins, maxs; + static vec3_t range = { 40, 40, 52 }; + + if ( !ent->client ) { + return; + } + + // dead clients don't activate triggers! + if ( ent->client->ps.stats[STAT_HEALTH] <= 0 ) { + return; + } + + VectorSubtract( ent->client->ps.origin, range, mins ); + VectorAdd( ent->client->ps.origin, range, maxs ); + + num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + + // can't use ent->absmin, because that has a one unit pad + VectorAdd( ent->client->ps.origin, ent->r.mins, mins ); + VectorAdd( ent->client->ps.origin, ent->r.maxs, maxs ); + + for ( i=0 ; itouch && !ent->touch ) { + continue; + } + if ( !( hit->r.contents & CONTENTS_TRIGGER ) ) { + continue; + } + + // ignore most entities if a spectator + if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { + if ( hit->s.eType != ET_TELEPORT_TRIGGER && + // this is ugly but adding a new ET_? type will + // most likely cause network incompatibilities + hit->touch != Touch_DoorTrigger) { + continue; + } + } + + // use seperate code for determining if an item is picked up + // so you don't have to actually contact its bounding box + if ( hit->s.eType == ET_ITEM ) { + if ( !BG_PlayerTouchesItem( &ent->client->ps, &hit->s, level.time ) ) { + continue; + } + } else { + if ( !trap_EntityContact( mins, maxs, hit ) ) { + continue; + } + } + + memset( &trace, 0, sizeof(trace) ); + + if ( hit->touch ) { + hit->touch (hit, ent, &trace); + } + + if ( ( ent->r.svFlags & SVF_BOT ) && ( ent->touch ) ) { + ent->touch( ent, hit, &trace ); + } + } + + // if we didn't touch a jump pad this pmove frame + if ( ent->client->ps.jumppad_frame != ent->client->ps.pmove_framecount ) { + ent->client->ps.jumppad_frame = 0; + ent->client->ps.jumppad_ent = 0; + } +} + +/* +================= +SpectatorThink +================= +*/ +void SpectatorThink( gentity_t *ent, usercmd_t *ucmd ) { + pmove_t pm; + gclient_t *client; + + client = ent->client; + + if ( client->sess.spectatorState != SPECTATOR_FOLLOW ) { + client->ps.pm_type = PM_SPECTATOR; + client->ps.speed = 400; // faster than normal + + // set up for pmove + memset (&pm, 0, sizeof(pm)); + pm.ps = &client->ps; + pm.cmd = *ucmd; + pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; // spectators can fly through bodies + pm.trace = trap_Trace; + pm.pointcontents = trap_PointContents; + + // perform a pmove + Pmove (&pm); + // save results of pmove + VectorCopy( client->ps.origin, ent->s.origin ); + + G_TouchTriggers( ent ); + trap_UnlinkEntity( ent ); + } + + client->oldbuttons = client->buttons; + client->buttons = ucmd->buttons; + + // attack button cycles through spectators + if ( ( client->buttons & BUTTON_ATTACK ) && ! ( client->oldbuttons & BUTTON_ATTACK ) ) { + Cmd_FollowCycle_f( ent, 1 ); + } +} + + + +/* +================= +ClientInactivityTimer + +Returns qfalse if the client is dropped +================= +*/ +qboolean ClientInactivityTimer( gclient_t *client ) { + if ( ! g_inactivity.integer ) { + // give everyone some time, so if the operator sets g_inactivity during + // gameplay, everyone isn't kicked + client->inactivityTime = level.time + 60 * 1000; + client->inactivityWarning = qfalse; + } else if ( client->pers.cmd.forwardmove || + client->pers.cmd.rightmove || + client->pers.cmd.upmove || + (client->pers.cmd.buttons & BUTTON_ATTACK) ) { + client->inactivityTime = level.time + g_inactivity.integer * 1000; + client->inactivityWarning = qfalse; + } else if ( !client->pers.localClient ) { + if ( level.time > client->inactivityTime ) { + trap_DropClient( client - level.clients, "Dropped due to inactivity" ); + return qfalse; + } + if ( level.time > client->inactivityTime - 10000 && !client->inactivityWarning ) { + client->inactivityWarning = qtrue; + trap_SendServerCommand( client - level.clients, "cp \"Ten seconds until inactivity drop!\n\"" ); + } + } + return qtrue; +} + +/* +================== +ClientTimerActions + +Actions that happen once a second +================== +*/ +void ClientTimerActions( gentity_t *ent, int msec ) { + gclient_t *client; +#ifdef MISSIONPACK + int maxHealth; +#endif + + client = ent->client; + client->timeResidual += msec; + + while ( client->timeResidual >= 1000 ) { + client->timeResidual -= 1000; + + // regenerate +#ifdef MISSIONPACK + if( bg_itemlist[client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) { + maxHealth = client->ps.stats[STAT_MAX_HEALTH] / 2; + } + else if ( client->ps.powerups[PW_REGEN] ) { + maxHealth = client->ps.stats[STAT_MAX_HEALTH]; + } + else { + maxHealth = 0; + } + if( maxHealth ) { + if ( ent->health < maxHealth ) { + ent->health += 15; + if ( ent->health > maxHealth * 1.1 ) { + ent->health = maxHealth * 1.1; + } + G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); + } else if ( ent->health < maxHealth * 2) { + ent->health += 5; + if ( ent->health > maxHealth * 2 ) { + ent->health = maxHealth * 2; + } + G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); + } +#else + if ( client->ps.powerups[PW_REGEN] ) { + if ( ent->health < client->ps.stats[STAT_MAX_HEALTH]) { + ent->health += 15; + if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] * 1.1 ) { + ent->health = client->ps.stats[STAT_MAX_HEALTH] * 1.1; + } + G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); + } else if ( ent->health < client->ps.stats[STAT_MAX_HEALTH] * 2) { + ent->health += 5; + if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] * 2 ) { + ent->health = client->ps.stats[STAT_MAX_HEALTH] * 2; + } + G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); + } +#endif + } else { + // count down health when over max + if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] ) { + ent->health--; + } + } + + // count down armor when over max + if ( client->ps.stats[STAT_ARMOR] > client->ps.stats[STAT_MAX_HEALTH] ) { + client->ps.stats[STAT_ARMOR]--; + } + } +#ifdef MISSIONPACK + if( bg_itemlist[client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_AMMOREGEN ) { + int w, max, inc, t, i; + int weapList[]={WP_MACHINEGUN,WP_SHOTGUN,WP_GRENADE_LAUNCHER,WP_ROCKET_LAUNCHER,WP_LIGHTNING,WP_RAILGUN,WP_PLASMAGUN,WP_BFG,WP_NAILGUN,WP_PROX_LAUNCHER,WP_CHAINGUN}; + int weapCount = sizeof(weapList) / sizeof(int); + // + for (i = 0; i < weapCount; i++) { + w = weapList[i]; + + switch(w) { + case WP_MACHINEGUN: max = 50; inc = 4; t = 1000; break; + case WP_SHOTGUN: max = 10; inc = 1; t = 1500; break; + case WP_GRENADE_LAUNCHER: max = 10; inc = 1; t = 2000; break; + case WP_ROCKET_LAUNCHER: max = 10; inc = 1; t = 1750; break; + case WP_LIGHTNING: max = 50; inc = 5; t = 1500; break; + case WP_RAILGUN: max = 10; inc = 1; t = 1750; break; + case WP_PLASMAGUN: max = 50; inc = 5; t = 1500; break; + case WP_BFG: max = 10; inc = 1; t = 4000; break; + case WP_NAILGUN: max = 10; inc = 1; t = 1250; break; + case WP_PROX_LAUNCHER: max = 5; inc = 1; t = 2000; break; + case WP_CHAINGUN: max = 100; inc = 5; t = 1000; break; + default: max = 0; inc = 0; t = 1000; break; + } + client->ammoTimes[w] += msec; + if ( client->ps.ammo[w] >= max ) { + client->ammoTimes[w] = 0; + } + if ( client->ammoTimes[w] >= t ) { + while ( client->ammoTimes[w] >= t ) + client->ammoTimes[w] -= t; + client->ps.ammo[w] += inc; + if ( client->ps.ammo[w] > max ) { + client->ps.ammo[w] = max; + } + } + } + } +#endif +} + +/* +==================== +ClientIntermissionThink +==================== +*/ +void ClientIntermissionThink( gclient_t *client ) { + client->ps.eFlags &= ~EF_TALK; + client->ps.eFlags &= ~EF_FIRING; + + // the level will exit when everyone wants to or after timeouts + + // swap and latch button actions + client->oldbuttons = client->buttons; + client->buttons = client->pers.cmd.buttons; + if ( client->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) & ( client->oldbuttons ^ client->buttons ) ) { + // this used to be an ^1 but once a player says ready, it should stick + client->readyToExit = 1; + } +} + + +/* +================ +ClientEvents + +Events will be passed on to the clients for presentation, +but any server game effects are handled here +================ +*/ +void ClientEvents( gentity_t *ent, int oldEventSequence ) { + int i, j; + int event; + gclient_t *client; + int damage; + vec3_t dir; + vec3_t origin, angles; +// qboolean fired; + gitem_t *item; + gentity_t *drop; + + client = ent->client; + + if ( oldEventSequence < client->ps.eventSequence - MAX_PS_EVENTS ) { + oldEventSequence = client->ps.eventSequence - MAX_PS_EVENTS; + } + for ( i = oldEventSequence ; i < client->ps.eventSequence ; i++ ) { + event = client->ps.events[ i & (MAX_PS_EVENTS-1) ]; + + switch ( event ) { + case EV_FALL_MEDIUM: + case EV_FALL_FAR: + if ( ent->s.eType != ET_PLAYER ) { + break; // not in the player model + } + if ( g_dmflags.integer & DF_NO_FALLING ) { + break; + } + if ( event == EV_FALL_FAR ) { + damage = 10; + } else { + damage = 5; + } + VectorSet (dir, 0, 0, 1); + ent->pain_debounce_time = level.time + 200; // no normal pain sound + G_Damage (ent, NULL, NULL, NULL, NULL, damage, 0, MOD_FALLING); + break; + + case EV_FIRE_WEAPON: + FireWeapon( ent ); + break; + + case EV_USE_ITEM1: // teleporter + // drop flags in CTF + item = NULL; + j = 0; + + if ( ent->client->ps.powerups[ PW_REDFLAG ] ) { + item = BG_FindItemForPowerup( PW_REDFLAG ); + j = PW_REDFLAG; + } else if ( ent->client->ps.powerups[ PW_BLUEFLAG ] ) { + item = BG_FindItemForPowerup( PW_BLUEFLAG ); + j = PW_BLUEFLAG; + } else if ( ent->client->ps.powerups[ PW_NEUTRALFLAG ] ) { + item = BG_FindItemForPowerup( PW_NEUTRALFLAG ); + j = PW_NEUTRALFLAG; + } + + if ( item ) { + drop = Drop_Item( ent, item, 0 ); + // decide how many seconds it has left + drop->count = ( ent->client->ps.powerups[ j ] - level.time ) / 1000; + if ( drop->count < 1 ) { + drop->count = 1; + } + + ent->client->ps.powerups[ j ] = 0; + } + +#ifdef MISSIONPACK + if ( g_gametype.integer == GT_HARVESTER ) { + if ( ent->client->ps.generic1 > 0 ) { + if ( ent->client->sess.sessionTeam == TEAM_RED ) { + item = BG_FindItem( "Blue Cube" ); + } else { + item = BG_FindItem( "Red Cube" ); + } + if ( item ) { + for ( j = 0; j < ent->client->ps.generic1; j++ ) { + drop = Drop_Item( ent, item, 0 ); + if ( ent->client->sess.sessionTeam == TEAM_RED ) { + drop->spawnflags = TEAM_BLUE; + } else { + drop->spawnflags = TEAM_RED; + } + } + } + ent->client->ps.generic1 = 0; + } + } +#endif + SelectSpawnPoint( ent->client->ps.origin, origin, angles ); + TeleportPlayer( ent, origin, angles ); + break; + + case EV_USE_ITEM2: // medkit + ent->health = ent->client->ps.stats[STAT_MAX_HEALTH] + 25; + + break; + +#ifdef MISSIONPACK + case EV_USE_ITEM3: // kamikaze + // make sure the invulnerability is off + ent->client->invulnerabilityTime = 0; + // start the kamikze + G_StartKamikaze( ent ); + break; + + case EV_USE_ITEM4: // portal + if( ent->client->portalID ) { + DropPortalSource( ent ); + } + else { + DropPortalDestination( ent ); + } + break; + case EV_USE_ITEM5: // invulnerability + ent->client->invulnerabilityTime = level.time + 10000; + break; +#endif + + default: + break; + } + } + +} + +#ifdef MISSIONPACK +/* +============== +StuckInOtherClient +============== +*/ +static int StuckInOtherClient(gentity_t *ent) { + int i; + gentity_t *ent2; + + ent2 = &g_entities[0]; + for ( i = 0; i < MAX_CLIENTS; i++, ent2++ ) { + if ( ent2 == ent ) { + continue; + } + if ( !ent2->inuse ) { + continue; + } + if ( !ent2->client ) { + continue; + } + if ( ent2->health <= 0 ) { + continue; + } + // + if (ent2->r.absmin[0] > ent->r.absmax[0]) + continue; + if (ent2->r.absmin[1] > ent->r.absmax[1]) + continue; + if (ent2->r.absmin[2] > ent->r.absmax[2]) + continue; + if (ent2->r.absmax[0] < ent->r.absmin[0]) + continue; + if (ent2->r.absmax[1] < ent->r.absmin[1]) + continue; + if (ent2->r.absmax[2] < ent->r.absmin[2]) + continue; + return qtrue; + } + return qfalse; +} +#endif + +void BotTestSolid(vec3_t origin); + +/* +============== +SendPendingPredictableEvents +============== +*/ +void SendPendingPredictableEvents( playerState_t *ps ) { + gentity_t *t; + int event, seq; + int extEvent, number; + + // if there are still events pending + if ( ps->entityEventSequence < ps->eventSequence ) { + // create a temporary entity for this event which is sent to everyone + // except the client who generated the event + seq = ps->entityEventSequence & (MAX_PS_EVENTS-1); + event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 ); + // set external event to zero before calling BG_PlayerStateToEntityState + extEvent = ps->externalEvent; + ps->externalEvent = 0; + // create temporary entity for event + t = G_TempEntity( ps->origin, event ); + number = t->s.number; + BG_PlayerStateToEntityState( ps, &t->s, qtrue ); + t->s.number = number; + t->s.eType = ET_EVENTS + event; + t->s.eFlags |= EF_PLAYER_EVENT; + t->s.otherEntityNum = ps->clientNum; + // send to everyone except the client who generated the event + t->r.svFlags |= SVF_NOTSINGLECLIENT; + t->r.singleClient = ps->clientNum; + // set back external event + ps->externalEvent = extEvent; + } +} + +/* +============== +ClientThink + +This will be called once for each client frame, which will +usually be a couple times for each server frame on fast clients. + +If "g_synchronousClients 1" is set, this will be called exactly +once for each server frame, which makes for smooth demo recording. +============== +*/ +void ClientThink_real( gentity_t *ent ) { + gclient_t *client; + pmove_t pm; + int oldEventSequence; + int msec; + usercmd_t *ucmd; + + client = ent->client; + + // don't think if the client is not yet connected (and thus not yet spawned in) + if (client->pers.connected != CON_CONNECTED) { + return; + } + // mark the time, so the connection sprite can be removed + ucmd = &ent->client->pers.cmd; + + // sanity check the command time to prevent speedup cheating + if ( ucmd->serverTime > level.time + 200 ) { + ucmd->serverTime = level.time + 200; +// G_Printf("serverTime <<<<<\n" ); + } + if ( ucmd->serverTime < level.time - 1000 ) { + ucmd->serverTime = level.time - 1000; +// G_Printf("serverTime >>>>>\n" ); + } + + msec = ucmd->serverTime - client->ps.commandTime; + // following others may result in bad times, but we still want + // to check for follow toggles + if ( msec < 1 && client->sess.spectatorState != SPECTATOR_FOLLOW ) { + return; + } + if ( msec > 200 ) { + msec = 200; + } + + if ( pmove_msec.integer < 8 ) { + trap_Cvar_Set("pmove_msec", "8"); + } + else if (pmove_msec.integer > 33) { + trap_Cvar_Set("pmove_msec", "33"); + } + + if ( pmove_fixed.integer || client->pers.pmoveFixed ) { + ucmd->serverTime = ((ucmd->serverTime + pmove_msec.integer-1) / pmove_msec.integer) * pmove_msec.integer; + //if (ucmd->serverTime - client->ps.commandTime <= 0) + // return; + } + + // + // check for exiting intermission + // + if ( level.intermissiontime ) { + ClientIntermissionThink( client ); + return; + } + + // spectators don't do much + if ( client->sess.sessionTeam == TEAM_SPECTATOR ) { + if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) { + return; + } + SpectatorThink( ent, ucmd ); + return; + } + + // check for inactivity timer, but never drop the local client of a non-dedicated server + if ( !ClientInactivityTimer( client ) ) { + return; + } + + // clear the rewards if time + if ( level.time > client->rewardTime ) { + client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP ); + } + + 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; + } + + client->ps.gravity = g_gravity.value; + + // set speed + client->ps.speed = g_speed.value; + +#ifdef MISSIONPACK + if( bg_itemlist[client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_SCOUT ) { + client->ps.speed *= 1.5; + } + else +#endif + if ( client->ps.powerups[PW_HASTE] ) { + client->ps.speed *= 1.3; + } + + // Let go of the hook if we aren't firing + if ( client->ps.weapon == WP_GRAPPLING_HOOK && + client->hook && !( ucmd->buttons & BUTTON_ATTACK ) ) { + Weapon_HookFree(client->hook); + } + + // set up for pmove + oldEventSequence = client->ps.eventSequence; + + memset (&pm, 0, sizeof(pm)); + + // check for the hit-scan gauntlet, don't let the action + // go through as an attack unless it actually hits something + if ( client->ps.weapon == WP_GAUNTLET && !( ucmd->buttons & BUTTON_TALK ) && + ( ucmd->buttons & BUTTON_ATTACK ) && client->ps.weaponTime <= 0 ) { + pm.gauntletHit = CheckGauntletAttack( ent ); + } + + if ( ent->flags & FL_FORCE_GESTURE ) { + ent->flags &= ~FL_FORCE_GESTURE; + ent->client->pers.cmd.buttons |= BUTTON_GESTURE; + } + +#ifdef MISSIONPACK + // check for invulnerability expansion before doing the Pmove + if (client->ps.powerups[PW_INVULNERABILITY] ) { + if ( !(client->ps.pm_flags & PMF_INVULEXPAND) ) { + vec3_t mins = { -42, -42, -42 }; + vec3_t maxs = { 42, 42, 42 }; + vec3_t oldmins, oldmaxs; + + VectorCopy (ent->r.mins, oldmins); + VectorCopy (ent->r.maxs, oldmaxs); + // expand + VectorCopy (mins, ent->r.mins); + VectorCopy (maxs, ent->r.maxs); + trap_LinkEntity(ent); + // check if this would get anyone stuck in this player + if ( !StuckInOtherClient(ent) ) { + // set flag so the expanded size will be set in PM_CheckDuck + client->ps.pm_flags |= PMF_INVULEXPAND; + } + // set back + VectorCopy (oldmins, ent->r.mins); + VectorCopy (oldmaxs, ent->r.maxs); + trap_LinkEntity(ent); + } + } +#endif + + pm.ps = &client->ps; + pm.cmd = *ucmd; + if ( pm.ps->pm_type == PM_DEAD ) { + pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; + } + else if ( ent->r.svFlags & SVF_BOT ) { + pm.tracemask = MASK_PLAYERSOLID | CONTENTS_BOTCLIP; + } + else { + pm.tracemask = MASK_PLAYERSOLID; + } + pm.trace = trap_Trace; + pm.pointcontents = trap_PointContents; + pm.debugLevel = g_debugMove.integer; + pm.noFootsteps = ( g_dmflags.integer & DF_NO_FOOTSTEPS ) > 0; + + pm.pmove_fixed = pmove_fixed.integer | client->pers.pmoveFixed; + pm.pmove_msec = pmove_msec.integer; + + VectorCopy( client->ps.origin, client->oldOrigin ); + +#ifdef MISSIONPACK + if (level.intermissionQueued != 0 && g_singlePlayer.integer) { + if ( level.time - level.intermissionQueued >= 1000 ) { + pm.cmd.buttons = 0; + pm.cmd.forwardmove = 0; + pm.cmd.rightmove = 0; + pm.cmd.upmove = 0; + if ( level.time - level.intermissionQueued >= 2000 && level.time - level.intermissionQueued <= 2500 ) { + trap_SendConsoleCommand( EXEC_APPEND, "centerview\n"); + } + ent->client->ps.pm_type = PM_SPINTERMISSION; + } + } + Pmove (&pm); +#else + Pmove (&pm); +#endif + + // save results of pmove + if ( ent->client->ps.eventSequence != oldEventSequence ) { + ent->eventTime = level.time; + } + if (g_smoothClients.integer) { + BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue ); + } + else { + BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); + } + SendPendingPredictableEvents( &ent->client->ps ); + + if ( !( ent->client->ps.eFlags & EF_FIRING ) ) { + client->fireHeld = qfalse; // for grapple + } + + // use the snapped origin for linking so it matches client predicted versions + VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin ); + + VectorCopy (pm.mins, ent->r.mins); + VectorCopy (pm.maxs, ent->r.maxs); + + ent->waterlevel = pm.waterlevel; + ent->watertype = pm.watertype; + + // execute client events + ClientEvents( ent, oldEventSequence ); + + // link entity now, after any personal teleporters have been used + trap_LinkEntity (ent); + if ( !ent->client->noclip ) { + G_TouchTriggers( ent ); + } + + // NOTE: now copy the exact origin over otherwise clients can be snapped into solid + VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); + + //test for solid areas in the AAS file + BotTestAAS(ent->r.currentOrigin); + + // touch other objects + ClientImpacts( ent, &pm ); + + // save results of triggers and client events + if (ent->client->ps.eventSequence != oldEventSequence) { + ent->eventTime = level.time; + } + + // swap and latch button actions + client->oldbuttons = client->buttons; + client->buttons = ucmd->buttons; + client->latched_buttons |= client->buttons & ~client->oldbuttons; + + // check for respawning + if ( client->ps.stats[STAT_HEALTH] <= 0 ) { + // wait for the attack button to be pressed + if ( level.time > client->respawnTime ) { + // forcerespawn is to prevent users from waiting out powerups + if ( g_forcerespawn.integer > 0 && + ( level.time - client->respawnTime ) > g_forcerespawn.integer * 1000 ) { + respawn( ent ); + return; + } + + // pressing attack or use is the normal respawn method + if ( ucmd->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) ) { + respawn( ent ); + } + } + return; + } + + // perform once-a-second actions + ClientTimerActions( ent, msec ); +} + +/* +================== +ClientThink + +A new command has arrived from the client +================== +*/ +void ClientThink( int clientNum ) { + gentity_t *ent; + + ent = g_entities + clientNum; + trap_GetUsercmd( clientNum, &ent->client->pers.cmd ); + + // mark the time we got info, so we can display the + // phone jack if they don't get any for a while + ent->client->lastCmdTime = level.time; + + if ( !(ent->r.svFlags & SVF_BOT) && !g_synchronousClients.integer ) { + ClientThink_real( ent ); + } +} + + +void G_RunClient( gentity_t *ent ) { + if ( !(ent->r.svFlags & SVF_BOT) && !g_synchronousClients.integer ) { + return; + } + ent->client->pers.cmd.serverTime = level.time; + ClientThink_real( ent ); +} + + +/* +================== +SpectatorClientEndFrame + +================== +*/ +void SpectatorClientEndFrame( gentity_t *ent ) { + gclient_t *cl; + + // if we are doing a chase cam or a remote view, grab the latest info + if ( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) { + int clientNum, flags; + + clientNum = ent->client->sess.spectatorClient; + + // team follow1 and team follow2 go to whatever clients are playing + if ( clientNum == -1 ) { + clientNum = level.follow1; + } else if ( clientNum == -2 ) { + clientNum = level.follow2; + } + if ( clientNum >= 0 ) { + cl = &level.clients[ clientNum ]; + if ( cl->pers.connected == CON_CONNECTED && cl->sess.sessionTeam != TEAM_SPECTATOR ) { + flags = (cl->ps.eFlags & ~(EF_VOTED | EF_TEAMVOTED)) | (ent->client->ps.eFlags & (EF_VOTED | EF_TEAMVOTED)); + ent->client->ps = cl->ps; + ent->client->ps.pm_flags |= PMF_FOLLOW; + ent->client->ps.eFlags = flags; + return; + } else { + // drop them to free spectators unless they are dedicated camera followers + if ( ent->client->sess.spectatorClient >= 0 ) { + ent->client->sess.spectatorState = SPECTATOR_FREE; + ClientBegin( ent->client - level.clients ); + } + } + } + } + + if ( ent->client->sess.spectatorState == SPECTATOR_SCOREBOARD ) { + ent->client->ps.pm_flags |= PMF_SCOREBOARD; + } else { + ent->client->ps.pm_flags &= ~PMF_SCOREBOARD; + } +} + +/* +============== +ClientEndFrame + +Called at the end of each server frame for each connected client +A fast client will have multiple ClientThink for each ClientEdFrame, +while a slow client may have multiple ClientEndFrame between ClientThink. +============== +*/ +void ClientEndFrame( gentity_t *ent ) { + int i; + clientPersistant_t *pers; + + if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { + SpectatorClientEndFrame( ent ); + return; + } + + pers = &ent->client->pers; + + // turn off any expired powerups + for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { + if ( ent->client->ps.powerups[ i ] < level.time ) { + ent->client->ps.powerups[ i ] = 0; + } + } + +#ifdef MISSIONPACK + // set powerup for player animation + if( bg_itemlist[ent->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) { + ent->client->ps.powerups[PW_GUARD] = level.time; + } + if( bg_itemlist[ent->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_SCOUT ) { + ent->client->ps.powerups[PW_SCOUT] = level.time; + } + if( bg_itemlist[ent->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_DOUBLER ) { + ent->client->ps.powerups[PW_DOUBLER] = level.time; + } + if( bg_itemlist[ent->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_AMMOREGEN ) { + ent->client->ps.powerups[PW_AMMOREGEN] = level.time; + } + if ( ent->client->invulnerabilityTime > level.time ) { + ent->client->ps.powerups[PW_INVULNERABILITY] = level.time; + } +#endif + + // save network bandwidth +#if 0 + if ( !g_synchronousClients->integer && ent->client->ps.pm_type == PM_NORMAL ) { + // FIXME: this must change eventually for non-sync demo recording + VectorClear( ent->client->ps.viewangles ); + } +#endif + + // + // If the end of unit layout is displayed, don't give + // the player any normal movement attributes + // + if ( level.intermissiontime ) { + return; + } + + // burn from lava, etc + P_WorldEffects (ent); + + // apply all the damage taken this frame + P_DamageFeedback (ent); + + // add the EF_CONNECTION flag if we haven't gotten commands recently + if ( level.time - ent->client->lastCmdTime > 1000 ) { + ent->s.eFlags |= EF_CONNECTION; + } else { + ent->s.eFlags &= ~EF_CONNECTION; + } + + ent->client->ps.stats[STAT_HEALTH] = ent->health; // FIXME: get rid of ent->health... + + G_SetClientSound (ent); + + // set the latest infor + if (g_smoothClients.integer) { + BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue ); + } + else { + BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); + } + SendPendingPredictableEvents( &ent->client->ps ); + + // set the bit for the reachability area the client is currently in +// i = trap_AAS_PointReachabilityAreaIndex( ent->client->ps.origin ); +// ent->client->areabits[i >> 3] |= 1 << (i & 7); +} + + diff --git a/code/game/g_arenas.c b/code/game/g_arenas.c index 1e6476b..d9cf4d5 100755 --- a/code/game/g_arenas.c +++ b/code/game/g_arenas.c @@ -1,376 +1,376 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// -// g_arenas.c -// - -#include "g_local.h" - - -gentity_t *podium1; -gentity_t *podium2; -gentity_t *podium3; - - -/* -================== -UpdateTournamentInfo -================== -*/ -void UpdateTournamentInfo( void ) { - int i; - gentity_t *player; - int playerClientNum; - int n, accuracy, perfect, msglen; - int buflen; -#ifdef MISSIONPACK // bk001205 - int score1, score2; - qboolean won; -#endif - char buf[32]; - char msg[MAX_STRING_CHARS]; - - // find the real player - player = NULL; - for (i = 0; i < level.maxclients; i++ ) { - player = &g_entities[i]; - if ( !player->inuse ) { - continue; - } - if ( !( player->r.svFlags & SVF_BOT ) ) { - break; - } - } - // this should never happen! - if ( !player || i == level.maxclients ) { - return; - } - playerClientNum = i; - - CalculateRanks(); - - if ( level.clients[playerClientNum].sess.sessionTeam == TEAM_SPECTATOR ) { -#ifdef MISSIONPACK - Com_sprintf( msg, sizeof(msg), "postgame %i %i 0 0 0 0 0 0 0 0 0 0 0", level.numNonSpectatorClients, playerClientNum ); -#else - Com_sprintf( msg, sizeof(msg), "postgame %i %i 0 0 0 0 0 0", level.numNonSpectatorClients, playerClientNum ); -#endif - } - else { - if( player->client->accuracy_shots ) { - accuracy = player->client->accuracy_hits * 100 / player->client->accuracy_shots; - } - else { - accuracy = 0; - } -#ifdef MISSIONPACK - won = qfalse; - if (g_gametype.integer >= GT_CTF) { - score1 = level.teamScores[TEAM_RED]; - score2 = level.teamScores[TEAM_BLUE]; - if (level.clients[playerClientNum].sess.sessionTeam == TEAM_RED) { - won = (level.teamScores[TEAM_RED] > level.teamScores[TEAM_BLUE]); - } else { - won = (level.teamScores[TEAM_BLUE] > level.teamScores[TEAM_RED]); - } - } else { - if (&level.clients[playerClientNum] == &level.clients[ level.sortedClients[0] ]) { - won = qtrue; - score1 = level.clients[ level.sortedClients[0] ].ps.persistant[PERS_SCORE]; - score2 = level.clients[ level.sortedClients[1] ].ps.persistant[PERS_SCORE]; - } else { - score2 = level.clients[ level.sortedClients[0] ].ps.persistant[PERS_SCORE]; - score1 = level.clients[ level.sortedClients[1] ].ps.persistant[PERS_SCORE]; - } - } - if (won && player->client->ps.persistant[PERS_KILLED] == 0) { - perfect = 1; - } else { - perfect = 0; - } - Com_sprintf( msg, sizeof(msg), "postgame %i %i %i %i %i %i %i %i %i %i %i %i %i %i", level.numNonSpectatorClients, playerClientNum, accuracy, - player->client->ps.persistant[PERS_IMPRESSIVE_COUNT], player->client->ps.persistant[PERS_EXCELLENT_COUNT],player->client->ps.persistant[PERS_DEFEND_COUNT], - player->client->ps.persistant[PERS_ASSIST_COUNT], player->client->ps.persistant[PERS_GAUNTLET_FRAG_COUNT], player->client->ps.persistant[PERS_SCORE], - perfect, score1, score2, level.time, player->client->ps.persistant[PERS_CAPTURES] ); - -#else - perfect = ( level.clients[playerClientNum].ps.persistant[PERS_RANK] == 0 && player->client->ps.persistant[PERS_KILLED] == 0 ) ? 1 : 0; - Com_sprintf( msg, sizeof(msg), "postgame %i %i %i %i %i %i %i %i", level.numNonSpectatorClients, playerClientNum, accuracy, - player->client->ps.persistant[PERS_IMPRESSIVE_COUNT], player->client->ps.persistant[PERS_EXCELLENT_COUNT], - player->client->ps.persistant[PERS_GAUNTLET_FRAG_COUNT], player->client->ps.persistant[PERS_SCORE], - perfect ); -#endif - } - - msglen = strlen( msg ); - for( i = 0; i < level.numNonSpectatorClients; i++ ) { - n = level.sortedClients[i]; - Com_sprintf( buf, sizeof(buf), " %i %i %i", n, level.clients[n].ps.persistant[PERS_RANK], level.clients[n].ps.persistant[PERS_SCORE] ); - buflen = strlen( buf ); - if( msglen + buflen + 1 >= sizeof(msg) ) { - break; - } - strcat( msg, buf ); - } - trap_SendConsoleCommand( EXEC_APPEND, msg ); -} - - -static gentity_t *SpawnModelOnVictoryPad( gentity_t *pad, vec3_t offset, gentity_t *ent, int place ) { - gentity_t *body; - vec3_t vec; - vec3_t f, r, u; - - body = G_Spawn(); - if ( !body ) { - G_Printf( S_COLOR_RED "ERROR: out of gentities\n" ); - return NULL; - } - - body->classname = ent->client->pers.netname; - body->client = ent->client; - body->s = ent->s; - body->s.eType = ET_PLAYER; // could be ET_INVISIBLE - body->s.eFlags = 0; // clear EF_TALK, etc - body->s.powerups = 0; // clear powerups - body->s.loopSound = 0; // clear lava burning - body->s.number = body - g_entities; - body->timestamp = level.time; - body->physicsObject = qtrue; - body->physicsBounce = 0; // don't bounce - body->s.event = 0; - body->s.pos.trType = TR_STATIONARY; - body->s.groundEntityNum = ENTITYNUM_WORLD; - body->s.legsAnim = LEGS_IDLE; - body->s.torsoAnim = TORSO_STAND; - if( body->s.weapon == WP_NONE ) { - body->s.weapon = WP_MACHINEGUN; - } - if( body->s.weapon == WP_GAUNTLET) { - body->s.torsoAnim = TORSO_STAND2; - } - body->s.event = 0; - body->r.svFlags = ent->r.svFlags; - VectorCopy (ent->r.mins, body->r.mins); - VectorCopy (ent->r.maxs, body->r.maxs); - VectorCopy (ent->r.absmin, body->r.absmin); - VectorCopy (ent->r.absmax, body->r.absmax); - body->clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP; - body->r.contents = CONTENTS_BODY; - body->r.ownerNum = ent->r.ownerNum; - body->takedamage = qfalse; - - VectorSubtract( level.intermission_origin, pad->r.currentOrigin, vec ); - vectoangles( vec, body->s.apos.trBase ); - body->s.apos.trBase[PITCH] = 0; - body->s.apos.trBase[ROLL] = 0; - - AngleVectors( body->s.apos.trBase, f, r, u ); - VectorMA( pad->r.currentOrigin, offset[0], f, vec ); - VectorMA( vec, offset[1], r, vec ); - VectorMA( vec, offset[2], u, vec ); - - G_SetOrigin( body, vec ); - - trap_LinkEntity (body); - - body->count = place; - - return body; -} - - -static void CelebrateStop( gentity_t *player ) { - int anim; - - if( player->s.weapon == WP_GAUNTLET) { - anim = TORSO_STAND2; - } - else { - anim = TORSO_STAND; - } - player->s.torsoAnim = ( ( player->s.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; -} - - -#define TIMER_GESTURE (34*66+50) -static void CelebrateStart( gentity_t *player ) { - player->s.torsoAnim = ( ( player->s.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | TORSO_GESTURE; - player->nextthink = level.time + TIMER_GESTURE; - player->think = CelebrateStop; - - /* - player->client->ps.events[player->client->ps.eventSequence & (MAX_PS_EVENTS-1)] = EV_TAUNT; - player->client->ps.eventParms[player->client->ps.eventSequence & (MAX_PS_EVENTS-1)] = 0; - player->client->ps.eventSequence++; - */ - G_AddEvent(player, EV_TAUNT, 0); -} - - -static vec3_t offsetFirst = {0, 0, 74}; -static vec3_t offsetSecond = {-10, 60, 54}; -static vec3_t offsetThird = {-19, -60, 45}; - -static void PodiumPlacementThink( gentity_t *podium ) { - vec3_t vec; - vec3_t origin; - vec3_t f, r, u; - - podium->nextthink = level.time + 100; - - AngleVectors( level.intermission_angle, vec, NULL, NULL ); - VectorMA( level.intermission_origin, trap_Cvar_VariableIntegerValue( "g_podiumDist" ), vec, origin ); - origin[2] -= trap_Cvar_VariableIntegerValue( "g_podiumDrop" ); - G_SetOrigin( podium, origin ); - - if( podium1 ) { - VectorSubtract( level.intermission_origin, podium->r.currentOrigin, vec ); - vectoangles( vec, podium1->s.apos.trBase ); - podium1->s.apos.trBase[PITCH] = 0; - podium1->s.apos.trBase[ROLL] = 0; - - AngleVectors( podium1->s.apos.trBase, f, r, u ); - VectorMA( podium->r.currentOrigin, offsetFirst[0], f, vec ); - VectorMA( vec, offsetFirst[1], r, vec ); - VectorMA( vec, offsetFirst[2], u, vec ); - - G_SetOrigin( podium1, vec ); - } - - if( podium2 ) { - VectorSubtract( level.intermission_origin, podium->r.currentOrigin, vec ); - vectoangles( vec, podium2->s.apos.trBase ); - podium2->s.apos.trBase[PITCH] = 0; - podium2->s.apos.trBase[ROLL] = 0; - - AngleVectors( podium2->s.apos.trBase, f, r, u ); - VectorMA( podium->r.currentOrigin, offsetSecond[0], f, vec ); - VectorMA( vec, offsetSecond[1], r, vec ); - VectorMA( vec, offsetSecond[2], u, vec ); - - G_SetOrigin( podium2, vec ); - } - - if( podium3 ) { - VectorSubtract( level.intermission_origin, podium->r.currentOrigin, vec ); - vectoangles( vec, podium3->s.apos.trBase ); - podium3->s.apos.trBase[PITCH] = 0; - podium3->s.apos.trBase[ROLL] = 0; - - AngleVectors( podium3->s.apos.trBase, f, r, u ); - VectorMA( podium->r.currentOrigin, offsetThird[0], f, vec ); - VectorMA( vec, offsetThird[1], r, vec ); - VectorMA( vec, offsetThird[2], u, vec ); - - G_SetOrigin( podium3, vec ); - } -} - - -static gentity_t *SpawnPodium( void ) { - gentity_t *podium; - vec3_t vec; - vec3_t origin; - - podium = G_Spawn(); - if ( !podium ) { - return NULL; - } - - podium->classname = "podium"; - podium->s.eType = ET_GENERAL; - podium->s.number = podium - g_entities; - podium->clipmask = CONTENTS_SOLID; - podium->r.contents = CONTENTS_SOLID; - podium->s.modelindex = G_ModelIndex( SP_PODIUM_MODEL ); - - AngleVectors( level.intermission_angle, vec, NULL, NULL ); - VectorMA( level.intermission_origin, trap_Cvar_VariableIntegerValue( "g_podiumDist" ), vec, origin ); - origin[2] -= trap_Cvar_VariableIntegerValue( "g_podiumDrop" ); - G_SetOrigin( podium, origin ); - - VectorSubtract( level.intermission_origin, podium->r.currentOrigin, vec ); - podium->s.apos.trBase[YAW] = vectoyaw( vec ); - trap_LinkEntity (podium); - - podium->think = PodiumPlacementThink; - podium->nextthink = level.time + 100; - return podium; -} - - -/* -================== -SpawnModelsOnVictoryPads -================== -*/ -void SpawnModelsOnVictoryPads( void ) { - gentity_t *player; - gentity_t *podium; - - podium1 = NULL; - podium2 = NULL; - podium3 = NULL; - - podium = SpawnPodium(); - - player = SpawnModelOnVictoryPad( podium, offsetFirst, &g_entities[level.sortedClients[0]], - level.clients[ level.sortedClients[0] ].ps.persistant[PERS_RANK] &~ RANK_TIED_FLAG ); - if ( player ) { - player->nextthink = level.time + 2000; - player->think = CelebrateStart; - podium1 = player; - } - - player = SpawnModelOnVictoryPad( podium, offsetSecond, &g_entities[level.sortedClients[1]], - level.clients[ level.sortedClients[1] ].ps.persistant[PERS_RANK] &~ RANK_TIED_FLAG ); - if ( player ) { - podium2 = player; - } - - if ( level.numNonSpectatorClients > 2 ) { - player = SpawnModelOnVictoryPad( podium, offsetThird, &g_entities[level.sortedClients[2]], - level.clients[ level.sortedClients[2] ].ps.persistant[PERS_RANK] &~ RANK_TIED_FLAG ); - if ( player ) { - podium3 = player; - } - } -} - - -/* -=============== -Svcmd_AbortPodium_f -=============== -*/ -void Svcmd_AbortPodium_f( void ) { - if( g_gametype.integer != GT_SINGLE_PLAYER ) { - return; - } - - if( podium1 ) { - podium1->nextthink = level.time; - podium1->think = CelebrateStop; - } -} +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// +// g_arenas.c +// + +#include "g_local.h" + + +gentity_t *podium1; +gentity_t *podium2; +gentity_t *podium3; + + +/* +================== +UpdateTournamentInfo +================== +*/ +void UpdateTournamentInfo( void ) { + int i; + gentity_t *player; + int playerClientNum; + int n, accuracy, perfect, msglen; + int buflen; +#ifdef MISSIONPACK // bk001205 + int score1, score2; + qboolean won; +#endif + char buf[32]; + char msg[MAX_STRING_CHARS]; + + // find the real player + player = NULL; + for (i = 0; i < level.maxclients; i++ ) { + player = &g_entities[i]; + if ( !player->inuse ) { + continue; + } + if ( !( player->r.svFlags & SVF_BOT ) ) { + break; + } + } + // this should never happen! + if ( !player || i == level.maxclients ) { + return; + } + playerClientNum = i; + + CalculateRanks(); + + if ( level.clients[playerClientNum].sess.sessionTeam == TEAM_SPECTATOR ) { +#ifdef MISSIONPACK + Com_sprintf( msg, sizeof(msg), "postgame %i %i 0 0 0 0 0 0 0 0 0 0 0", level.numNonSpectatorClients, playerClientNum ); +#else + Com_sprintf( msg, sizeof(msg), "postgame %i %i 0 0 0 0 0 0", level.numNonSpectatorClients, playerClientNum ); +#endif + } + else { + if( player->client->accuracy_shots ) { + accuracy = player->client->accuracy_hits * 100 / player->client->accuracy_shots; + } + else { + accuracy = 0; + } +#ifdef MISSIONPACK + won = qfalse; + if (g_gametype.integer >= GT_CTF) { + score1 = level.teamScores[TEAM_RED]; + score2 = level.teamScores[TEAM_BLUE]; + if (level.clients[playerClientNum].sess.sessionTeam == TEAM_RED) { + won = (level.teamScores[TEAM_RED] > level.teamScores[TEAM_BLUE]); + } else { + won = (level.teamScores[TEAM_BLUE] > level.teamScores[TEAM_RED]); + } + } else { + if (&level.clients[playerClientNum] == &level.clients[ level.sortedClients[0] ]) { + won = qtrue; + score1 = level.clients[ level.sortedClients[0] ].ps.persistant[PERS_SCORE]; + score2 = level.clients[ level.sortedClients[1] ].ps.persistant[PERS_SCORE]; + } else { + score2 = level.clients[ level.sortedClients[0] ].ps.persistant[PERS_SCORE]; + score1 = level.clients[ level.sortedClients[1] ].ps.persistant[PERS_SCORE]; + } + } + if (won && player->client->ps.persistant[PERS_KILLED] == 0) { + perfect = 1; + } else { + perfect = 0; + } + Com_sprintf( msg, sizeof(msg), "postgame %i %i %i %i %i %i %i %i %i %i %i %i %i %i", level.numNonSpectatorClients, playerClientNum, accuracy, + player->client->ps.persistant[PERS_IMPRESSIVE_COUNT], player->client->ps.persistant[PERS_EXCELLENT_COUNT],player->client->ps.persistant[PERS_DEFEND_COUNT], + player->client->ps.persistant[PERS_ASSIST_COUNT], player->client->ps.persistant[PERS_GAUNTLET_FRAG_COUNT], player->client->ps.persistant[PERS_SCORE], + perfect, score1, score2, level.time, player->client->ps.persistant[PERS_CAPTURES] ); + +#else + perfect = ( level.clients[playerClientNum].ps.persistant[PERS_RANK] == 0 && player->client->ps.persistant[PERS_KILLED] == 0 ) ? 1 : 0; + Com_sprintf( msg, sizeof(msg), "postgame %i %i %i %i %i %i %i %i", level.numNonSpectatorClients, playerClientNum, accuracy, + player->client->ps.persistant[PERS_IMPRESSIVE_COUNT], player->client->ps.persistant[PERS_EXCELLENT_COUNT], + player->client->ps.persistant[PERS_GAUNTLET_FRAG_COUNT], player->client->ps.persistant[PERS_SCORE], + perfect ); +#endif + } + + msglen = strlen( msg ); + for( i = 0; i < level.numNonSpectatorClients; i++ ) { + n = level.sortedClients[i]; + Com_sprintf( buf, sizeof(buf), " %i %i %i", n, level.clients[n].ps.persistant[PERS_RANK], level.clients[n].ps.persistant[PERS_SCORE] ); + buflen = strlen( buf ); + if( msglen + buflen + 1 >= sizeof(msg) ) { + break; + } + strcat( msg, buf ); + } + trap_SendConsoleCommand( EXEC_APPEND, msg ); +} + + +static gentity_t *SpawnModelOnVictoryPad( gentity_t *pad, vec3_t offset, gentity_t *ent, int place ) { + gentity_t *body; + vec3_t vec; + vec3_t f, r, u; + + body = G_Spawn(); + if ( !body ) { + G_Printf( S_COLOR_RED "ERROR: out of gentities\n" ); + return NULL; + } + + body->classname = ent->client->pers.netname; + body->client = ent->client; + body->s = ent->s; + body->s.eType = ET_PLAYER; // could be ET_INVISIBLE + body->s.eFlags = 0; // clear EF_TALK, etc + body->s.powerups = 0; // clear powerups + body->s.loopSound = 0; // clear lava burning + body->s.number = body - g_entities; + body->timestamp = level.time; + body->physicsObject = qtrue; + body->physicsBounce = 0; // don't bounce + body->s.event = 0; + body->s.pos.trType = TR_STATIONARY; + body->s.groundEntityNum = ENTITYNUM_WORLD; + body->s.legsAnim = LEGS_IDLE; + body->s.torsoAnim = TORSO_STAND; + if( body->s.weapon == WP_NONE ) { + body->s.weapon = WP_MACHINEGUN; + } + if( body->s.weapon == WP_GAUNTLET) { + body->s.torsoAnim = TORSO_STAND2; + } + body->s.event = 0; + body->r.svFlags = ent->r.svFlags; + VectorCopy (ent->r.mins, body->r.mins); + VectorCopy (ent->r.maxs, body->r.maxs); + VectorCopy (ent->r.absmin, body->r.absmin); + VectorCopy (ent->r.absmax, body->r.absmax); + body->clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP; + body->r.contents = CONTENTS_BODY; + body->r.ownerNum = ent->r.ownerNum; + body->takedamage = qfalse; + + VectorSubtract( level.intermission_origin, pad->r.currentOrigin, vec ); + vectoangles( vec, body->s.apos.trBase ); + body->s.apos.trBase[PITCH] = 0; + body->s.apos.trBase[ROLL] = 0; + + AngleVectors( body->s.apos.trBase, f, r, u ); + VectorMA( pad->r.currentOrigin, offset[0], f, vec ); + VectorMA( vec, offset[1], r, vec ); + VectorMA( vec, offset[2], u, vec ); + + G_SetOrigin( body, vec ); + + trap_LinkEntity (body); + + body->count = place; + + return body; +} + + +static void CelebrateStop( gentity_t *player ) { + int anim; + + if( player->s.weapon == WP_GAUNTLET) { + anim = TORSO_STAND2; + } + else { + anim = TORSO_STAND; + } + player->s.torsoAnim = ( ( player->s.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; +} + + +#define TIMER_GESTURE (34*66+50) +static void CelebrateStart( gentity_t *player ) { + player->s.torsoAnim = ( ( player->s.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | TORSO_GESTURE; + player->nextthink = level.time + TIMER_GESTURE; + player->think = CelebrateStop; + + /* + player->client->ps.events[player->client->ps.eventSequence & (MAX_PS_EVENTS-1)] = EV_TAUNT; + player->client->ps.eventParms[player->client->ps.eventSequence & (MAX_PS_EVENTS-1)] = 0; + player->client->ps.eventSequence++; + */ + G_AddEvent(player, EV_TAUNT, 0); +} + + +static vec3_t offsetFirst = {0, 0, 74}; +static vec3_t offsetSecond = {-10, 60, 54}; +static vec3_t offsetThird = {-19, -60, 45}; + +static void PodiumPlacementThink( gentity_t *podium ) { + vec3_t vec; + vec3_t origin; + vec3_t f, r, u; + + podium->nextthink = level.time + 100; + + AngleVectors( level.intermission_angle, vec, NULL, NULL ); + VectorMA( level.intermission_origin, trap_Cvar_VariableIntegerValue( "g_podiumDist" ), vec, origin ); + origin[2] -= trap_Cvar_VariableIntegerValue( "g_podiumDrop" ); + G_SetOrigin( podium, origin ); + + if( podium1 ) { + VectorSubtract( level.intermission_origin, podium->r.currentOrigin, vec ); + vectoangles( vec, podium1->s.apos.trBase ); + podium1->s.apos.trBase[PITCH] = 0; + podium1->s.apos.trBase[ROLL] = 0; + + AngleVectors( podium1->s.apos.trBase, f, r, u ); + VectorMA( podium->r.currentOrigin, offsetFirst[0], f, vec ); + VectorMA( vec, offsetFirst[1], r, vec ); + VectorMA( vec, offsetFirst[2], u, vec ); + + G_SetOrigin( podium1, vec ); + } + + if( podium2 ) { + VectorSubtract( level.intermission_origin, podium->r.currentOrigin, vec ); + vectoangles( vec, podium2->s.apos.trBase ); + podium2->s.apos.trBase[PITCH] = 0; + podium2->s.apos.trBase[ROLL] = 0; + + AngleVectors( podium2->s.apos.trBase, f, r, u ); + VectorMA( podium->r.currentOrigin, offsetSecond[0], f, vec ); + VectorMA( vec, offsetSecond[1], r, vec ); + VectorMA( vec, offsetSecond[2], u, vec ); + + G_SetOrigin( podium2, vec ); + } + + if( podium3 ) { + VectorSubtract( level.intermission_origin, podium->r.currentOrigin, vec ); + vectoangles( vec, podium3->s.apos.trBase ); + podium3->s.apos.trBase[PITCH] = 0; + podium3->s.apos.trBase[ROLL] = 0; + + AngleVectors( podium3->s.apos.trBase, f, r, u ); + VectorMA( podium->r.currentOrigin, offsetThird[0], f, vec ); + VectorMA( vec, offsetThird[1], r, vec ); + VectorMA( vec, offsetThird[2], u, vec ); + + G_SetOrigin( podium3, vec ); + } +} + + +static gentity_t *SpawnPodium( void ) { + gentity_t *podium; + vec3_t vec; + vec3_t origin; + + podium = G_Spawn(); + if ( !podium ) { + return NULL; + } + + podium->classname = "podium"; + podium->s.eType = ET_GENERAL; + podium->s.number = podium - g_entities; + podium->clipmask = CONTENTS_SOLID; + podium->r.contents = CONTENTS_SOLID; + podium->s.modelindex = G_ModelIndex( SP_PODIUM_MODEL ); + + AngleVectors( level.intermission_angle, vec, NULL, NULL ); + VectorMA( level.intermission_origin, trap_Cvar_VariableIntegerValue( "g_podiumDist" ), vec, origin ); + origin[2] -= trap_Cvar_VariableIntegerValue( "g_podiumDrop" ); + G_SetOrigin( podium, origin ); + + VectorSubtract( level.intermission_origin, podium->r.currentOrigin, vec ); + podium->s.apos.trBase[YAW] = vectoyaw( vec ); + trap_LinkEntity (podium); + + podium->think = PodiumPlacementThink; + podium->nextthink = level.time + 100; + return podium; +} + + +/* +================== +SpawnModelsOnVictoryPads +================== +*/ +void SpawnModelsOnVictoryPads( void ) { + gentity_t *player; + gentity_t *podium; + + podium1 = NULL; + podium2 = NULL; + podium3 = NULL; + + podium = SpawnPodium(); + + player = SpawnModelOnVictoryPad( podium, offsetFirst, &g_entities[level.sortedClients[0]], + level.clients[ level.sortedClients[0] ].ps.persistant[PERS_RANK] &~ RANK_TIED_FLAG ); + if ( player ) { + player->nextthink = level.time + 2000; + player->think = CelebrateStart; + podium1 = player; + } + + player = SpawnModelOnVictoryPad( podium, offsetSecond, &g_entities[level.sortedClients[1]], + level.clients[ level.sortedClients[1] ].ps.persistant[PERS_RANK] &~ RANK_TIED_FLAG ); + if ( player ) { + podium2 = player; + } + + if ( level.numNonSpectatorClients > 2 ) { + player = SpawnModelOnVictoryPad( podium, offsetThird, &g_entities[level.sortedClients[2]], + level.clients[ level.sortedClients[2] ].ps.persistant[PERS_RANK] &~ RANK_TIED_FLAG ); + if ( player ) { + podium3 = player; + } + } +} + + +/* +=============== +Svcmd_AbortPodium_f +=============== +*/ +void Svcmd_AbortPodium_f( void ) { + if( g_gametype.integer != GT_SINGLE_PLAYER ) { + return; + } + + if( podium1 ) { + podium1->nextthink = level.time; + podium1->think = CelebrateStop; + } +} diff --git a/code/game/g_bot.c b/code/game/g_bot.c index e42013d..1987cfa 100755 --- a/code/game/g_bot.c +++ b/code/game/g_bot.c @@ -1,1017 +1,1017 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// g_bot.c - -#include "g_local.h" - - -static int g_numBots; -static char *g_botInfos[MAX_BOTS]; - - -int g_numArenas; -static char *g_arenaInfos[MAX_ARENAS]; - - -#define BOT_BEGIN_DELAY_BASE 2000 -#define BOT_BEGIN_DELAY_INCREMENT 1500 - -#define BOT_SPAWN_QUEUE_DEPTH 16 - -typedef struct { - int clientNum; - int spawnTime; -} botSpawnQueue_t; - -//static int botBeginDelay = 0; // bk001206 - unused, init -static botSpawnQueue_t botSpawnQueue[BOT_SPAWN_QUEUE_DEPTH]; - -vmCvar_t bot_minplayers; - -extern gentity_t *podium1; -extern gentity_t *podium2; -extern gentity_t *podium3; - -float trap_Cvar_VariableValue( const char *var_name ) { - char buf[128]; - - trap_Cvar_VariableStringBuffer(var_name, buf, sizeof(buf)); - return atof(buf); -} - - - -/* -=============== -G_ParseInfos -=============== -*/ -int G_ParseInfos( char *buf, int max, char *infos[] ) { - char *token; - int count; - char key[MAX_TOKEN_CHARS]; - char info[MAX_INFO_STRING]; - - count = 0; - - while ( 1 ) { - token = COM_Parse( &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( &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( &buf, qfalse ); - if ( !token[0] ) { - strcpy( token, "" ); - } - Info_SetValueForKey( info, key, token ); - } - //NOTE: extra space for arena number - infos[count] = G_Alloc(strlen(info) + strlen("\\num\\") + strlen(va("%d", MAX_ARENAS)) + 1); - if (infos[count]) { - strcpy(infos[count], info); - count++; - } - } - return count; -} - -/* -=============== -G_LoadArenasFromFile -=============== -*/ -static void G_LoadArenasFromFile( char *filename ) { - int len; - fileHandle_t f; - char buf[MAX_ARENAS_TEXT]; - - len = trap_FS_FOpenFile( filename, &f, FS_READ ); - if ( !f ) { - trap_Printf( va( S_COLOR_RED "file not found: %s\n", filename ) ); - return; - } - if ( len >= MAX_ARENAS_TEXT ) { - trap_Printf( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_ARENAS_TEXT ) ); - trap_FS_FCloseFile( f ); - return; - } - - trap_FS_Read( buf, len, f ); - buf[len] = 0; - trap_FS_FCloseFile( f ); - - g_numArenas += G_ParseInfos( buf, MAX_ARENAS - g_numArenas, &g_arenaInfos[g_numArenas] ); -} - -/* -=============== -G_LoadArenas -=============== -*/ -static void G_LoadArenas( void ) { - int numdirs; - vmCvar_t arenasFile; - char filename[128]; - char dirlist[1024]; - char* dirptr; - int i, n; - int dirlen; - - g_numArenas = 0; - - trap_Cvar_Register( &arenasFile, "g_arenasFile", "", CVAR_INIT|CVAR_ROM ); - if( *arenasFile.string ) { - G_LoadArenasFromFile(arenasFile.string); - } - else { - G_LoadArenasFromFile("scripts/arenas.txt"); - } - - // get all arenas from .arena files - numdirs = trap_FS_GetFileList("scripts", ".arena", dirlist, 1024 ); - dirptr = dirlist; - for (i = 0; i < numdirs; i++, dirptr += dirlen+1) { - dirlen = strlen(dirptr); - strcpy(filename, "scripts/"); - strcat(filename, dirptr); - G_LoadArenasFromFile(filename); - } - trap_Printf( va( "%i arenas parsed\n", g_numArenas ) ); - - for( n = 0; n < g_numArenas; n++ ) { - Info_SetValueForKey( g_arenaInfos[n], "num", va( "%i", n ) ); - } -} - - -/* -=============== -G_GetArenaInfoByNumber -=============== -*/ -const char *G_GetArenaInfoByMap( const char *map ) { - int n; - - for( n = 0; n < g_numArenas; n++ ) { - if( Q_stricmp( Info_ValueForKey( g_arenaInfos[n], "map" ), map ) == 0 ) { - return g_arenaInfos[n]; - } - } - - return NULL; -} - - -/* -================= -PlayerIntroSound -================= -*/ -static void PlayerIntroSound( const char *modelAndSkin ) { - char model[MAX_QPATH]; - char *skin; - - Q_strncpyz( model, modelAndSkin, sizeof(model) ); - skin = Q_strrchr( model, '/' ); - if ( skin ) { - *skin++ = '\0'; - } - else { - skin = model; - } - - if( Q_stricmp( skin, "default" ) == 0 ) { - skin = model; - } - - trap_SendConsoleCommand( EXEC_APPEND, va( "play sound/player/announce/%s.wav\n", skin ) ); -} - -/* -=============== -G_AddRandomBot -=============== -*/ -void G_AddRandomBot( int team ) { - int i, n, num; - float skill; - char *value, netname[36], *teamstr; - gclient_t *cl; - - num = 0; - for ( n = 0; n < g_numBots ; n++ ) { - value = Info_ValueForKey( g_botInfos[n], "name" ); - // - for ( i=0 ; i< g_maxclients.integer ; i++ ) { - cl = level.clients + i; - if ( cl->pers.connected != CON_CONNECTED ) { - continue; - } - if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) { - continue; - } - if ( team >= 0 && cl->sess.sessionTeam != team ) { - continue; - } - if ( !Q_stricmp( value, cl->pers.netname ) ) { - break; - } - } - if (i >= g_maxclients.integer) { - num++; - } - } - num = random() * num; - for ( n = 0; n < g_numBots ; n++ ) { - value = Info_ValueForKey( g_botInfos[n], "name" ); - // - for ( i=0 ; i< g_maxclients.integer ; i++ ) { - cl = level.clients + i; - if ( cl->pers.connected != CON_CONNECTED ) { - continue; - } - if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) { - continue; - } - if ( team >= 0 && cl->sess.sessionTeam != team ) { - continue; - } - if ( !Q_stricmp( value, cl->pers.netname ) ) { - break; - } - } - if (i >= g_maxclients.integer) { - num--; - if (num <= 0) { - skill = trap_Cvar_VariableValue( "g_spSkill" ); - if (team == TEAM_RED) teamstr = "red"; - else if (team == TEAM_BLUE) teamstr = "blue"; - else teamstr = ""; - strncpy(netname, value, sizeof(netname)-1); - netname[sizeof(netname)-1] = '\0'; - Q_CleanStr(netname); - trap_SendConsoleCommand( EXEC_INSERT, va("addbot %s %f %s %i\n", netname, skill, teamstr, 0) ); - return; - } - } - } -} - -/* -=============== -G_RemoveRandomBot -=============== -*/ -int G_RemoveRandomBot( int team ) { - int i; - char netname[36]; - gclient_t *cl; - - for ( i=0 ; i< g_maxclients.integer ; i++ ) { - cl = level.clients + i; - if ( cl->pers.connected != CON_CONNECTED ) { - continue; - } - if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) { - continue; - } - if ( team >= 0 && cl->sess.sessionTeam != team ) { - continue; - } - strcpy(netname, cl->pers.netname); - Q_CleanStr(netname); - trap_SendConsoleCommand( EXEC_INSERT, va("kick %s\n", netname) ); - return qtrue; - } - return qfalse; -} - -/* -=============== -G_CountHumanPlayers -=============== -*/ -int G_CountHumanPlayers( int team ) { - int i, num; - gclient_t *cl; - - num = 0; - for ( i=0 ; i< g_maxclients.integer ; i++ ) { - cl = level.clients + i; - if ( cl->pers.connected != CON_CONNECTED ) { - continue; - } - if ( g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT ) { - continue; - } - if ( team >= 0 && cl->sess.sessionTeam != team ) { - continue; - } - num++; - } - return num; -} - -/* -=============== -G_CountBotPlayers -=============== -*/ -int G_CountBotPlayers( int team ) { - int i, n, num; - gclient_t *cl; - - num = 0; - for ( i=0 ; i< g_maxclients.integer ; i++ ) { - cl = level.clients + i; - if ( cl->pers.connected != CON_CONNECTED ) { - continue; - } - if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) { - continue; - } - if ( team >= 0 && cl->sess.sessionTeam != team ) { - continue; - } - num++; - } - for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) { - if( !botSpawnQueue[n].spawnTime ) { - continue; - } - if ( botSpawnQueue[n].spawnTime > level.time ) { - continue; - } - num++; - } - return num; -} - -/* -=============== -G_CheckMinimumPlayers -=============== -*/ -void G_CheckMinimumPlayers( void ) { - int minplayers; - int humanplayers, botplayers; - static int checkminimumplayers_time; - - if (level.intermissiontime) return; - //only check once each 10 seconds - if (checkminimumplayers_time > level.time - 10000) { - return; - } - checkminimumplayers_time = level.time; - trap_Cvar_Update(&bot_minplayers); - minplayers = bot_minplayers.integer; - if (minplayers <= 0) return; - - if (g_gametype.integer >= GT_TEAM) { - if (minplayers >= g_maxclients.integer / 2) { - minplayers = (g_maxclients.integer / 2) -1; - } - - humanplayers = G_CountHumanPlayers( TEAM_RED ); - botplayers = G_CountBotPlayers( TEAM_RED ); - // - if (humanplayers + botplayers < minplayers) { - G_AddRandomBot( TEAM_RED ); - } else if (humanplayers + botplayers > minplayers && botplayers) { - G_RemoveRandomBot( TEAM_RED ); - } - // - humanplayers = G_CountHumanPlayers( TEAM_BLUE ); - botplayers = G_CountBotPlayers( TEAM_BLUE ); - // - if (humanplayers + botplayers < minplayers) { - G_AddRandomBot( TEAM_BLUE ); - } else if (humanplayers + botplayers > minplayers && botplayers) { - G_RemoveRandomBot( TEAM_BLUE ); - } - } - else if (g_gametype.integer == GT_TOURNAMENT ) { - if (minplayers >= g_maxclients.integer) { - minplayers = g_maxclients.integer-1; - } - humanplayers = G_CountHumanPlayers( -1 ); - botplayers = G_CountBotPlayers( -1 ); - // - if (humanplayers + botplayers < minplayers) { - G_AddRandomBot( TEAM_FREE ); - } else if (humanplayers + botplayers > minplayers && botplayers) { - // try to remove spectators first - if (!G_RemoveRandomBot( TEAM_SPECTATOR )) { - // just remove the bot that is playing - G_RemoveRandomBot( -1 ); - } - } - } - else if (g_gametype.integer == GT_FFA) { - if (minplayers >= g_maxclients.integer) { - minplayers = g_maxclients.integer-1; - } - humanplayers = G_CountHumanPlayers( TEAM_FREE ); - botplayers = G_CountBotPlayers( TEAM_FREE ); - // - if (humanplayers + botplayers < minplayers) { - G_AddRandomBot( TEAM_FREE ); - } else if (humanplayers + botplayers > minplayers && botplayers) { - G_RemoveRandomBot( TEAM_FREE ); - } - } -} - -/* -=============== -G_CheckBotSpawn -=============== -*/ -void G_CheckBotSpawn( void ) { - int n; - char userinfo[MAX_INFO_VALUE]; - - G_CheckMinimumPlayers(); - - for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) { - if( !botSpawnQueue[n].spawnTime ) { - continue; - } - if ( botSpawnQueue[n].spawnTime > level.time ) { - continue; - } - ClientBegin( botSpawnQueue[n].clientNum ); - botSpawnQueue[n].spawnTime = 0; - - if( g_gametype.integer == GT_SINGLE_PLAYER ) { - trap_GetUserinfo( botSpawnQueue[n].clientNum, userinfo, sizeof(userinfo) ); - PlayerIntroSound( Info_ValueForKey (userinfo, "model") ); - } - } -} - - -/* -=============== -AddBotToSpawnQueue -=============== -*/ -static void AddBotToSpawnQueue( int clientNum, int delay ) { - int n; - - for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) { - if( !botSpawnQueue[n].spawnTime ) { - botSpawnQueue[n].spawnTime = level.time + delay; - botSpawnQueue[n].clientNum = clientNum; - return; - } - } - - G_Printf( S_COLOR_YELLOW "Unable to delay spawn\n" ); - ClientBegin( clientNum ); -} - - -/* -=============== -G_RemoveQueuedBotBegin - -Called on client disconnect to make sure the delayed spawn -doesn't happen on a freed index -=============== -*/ -void G_RemoveQueuedBotBegin( int clientNum ) { - int n; - - for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) { - if( botSpawnQueue[n].clientNum == clientNum ) { - botSpawnQueue[n].spawnTime = 0; - return; - } - } -} - - -/* -=============== -G_BotConnect -=============== -*/ -qboolean G_BotConnect( int clientNum, qboolean restart ) { - bot_settings_t settings; - char userinfo[MAX_INFO_STRING]; - - trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) ); - - Q_strncpyz( settings.characterfile, Info_ValueForKey( userinfo, "characterfile" ), sizeof(settings.characterfile) ); - settings.skill = atof( Info_ValueForKey( userinfo, "skill" ) ); - Q_strncpyz( settings.team, Info_ValueForKey( userinfo, "team" ), sizeof(settings.team) ); - - if (!BotAISetupClient( clientNum, &settings, restart )) { - trap_DropClient( clientNum, "BotAISetupClient failed" ); - return qfalse; - } - - return qtrue; -} - - -/* -=============== -G_AddBot -=============== -*/ -static void G_AddBot( const char *name, float skill, const char *team, int delay, char *altname) { - int clientNum; - char *botinfo; - gentity_t *bot; - char *key; - char *s; - char *botname; - char *model; - char *headmodel; - char userinfo[MAX_INFO_STRING]; - - // get the botinfo from bots.txt - botinfo = G_GetBotInfoByName( name ); - if ( !botinfo ) { - G_Printf( S_COLOR_RED "Error: Bot '%s' not defined\n", name ); - return; - } - - // create the bot's userinfo - userinfo[0] = '\0'; - - botname = Info_ValueForKey( botinfo, "funname" ); - if( !botname[0] ) { - botname = Info_ValueForKey( botinfo, "name" ); - } - // check for an alternative name - if (altname && altname[0]) { - botname = altname; - } - Info_SetValueForKey( userinfo, "name", botname ); - Info_SetValueForKey( userinfo, "rate", "25000" ); - Info_SetValueForKey( userinfo, "snaps", "20" ); - Info_SetValueForKey( userinfo, "skill", va("%1.2f", skill) ); - - if ( skill >= 1 && skill < 2 ) { - Info_SetValueForKey( userinfo, "handicap", "50" ); - } - else if ( skill >= 2 && skill < 3 ) { - Info_SetValueForKey( userinfo, "handicap", "70" ); - } - else if ( skill >= 3 && skill < 4 ) { - Info_SetValueForKey( userinfo, "handicap", "90" ); - } - - key = "model"; - model = Info_ValueForKey( botinfo, key ); - if ( !*model ) { - model = "visor/default"; - } - Info_SetValueForKey( userinfo, key, model ); - key = "team_model"; - Info_SetValueForKey( userinfo, key, model ); - - key = "headmodel"; - headmodel = Info_ValueForKey( botinfo, key ); - if ( !*headmodel ) { - headmodel = model; - } - Info_SetValueForKey( userinfo, key, headmodel ); - key = "team_headmodel"; - Info_SetValueForKey( userinfo, key, headmodel ); - - key = "gender"; - s = Info_ValueForKey( botinfo, key ); - if ( !*s ) { - s = "male"; - } - Info_SetValueForKey( userinfo, "sex", s ); - - key = "color1"; - s = Info_ValueForKey( botinfo, key ); - if ( !*s ) { - s = "4"; - } - Info_SetValueForKey( userinfo, key, s ); - - key = "color2"; - s = Info_ValueForKey( botinfo, key ); - if ( !*s ) { - s = "5"; - } - Info_SetValueForKey( userinfo, key, s ); - - s = Info_ValueForKey(botinfo, "aifile"); - if (!*s ) { - trap_Printf( S_COLOR_RED "Error: bot has no aifile specified\n" ); - return; - } - - // have the server allocate a client slot - clientNum = trap_BotAllocateClient(); - if ( clientNum == -1 ) { - G_Printf( S_COLOR_RED "Unable to add bot. All player slots are in use.\n" ); - G_Printf( S_COLOR_RED "Start server with more 'open' slots (or check setting of sv_maxclients cvar).\n" ); - return; - } - - // initialize the bot settings - if( !team || !*team ) { - if( g_gametype.integer >= GT_TEAM ) { - if( PickTeam(clientNum) == TEAM_RED) { - team = "red"; - } - else { - team = "blue"; - } - } - else { - team = "red"; - } - } - Info_SetValueForKey( userinfo, "characterfile", Info_ValueForKey( botinfo, "aifile" ) ); - Info_SetValueForKey( userinfo, "skill", va( "%5.2f", skill ) ); - Info_SetValueForKey( userinfo, "team", team ); - - bot = &g_entities[ clientNum ]; - bot->r.svFlags |= SVF_BOT; - bot->inuse = qtrue; - - // register the userinfo - trap_SetUserinfo( clientNum, userinfo ); - - // have it connect to the game as a normal client - if ( ClientConnect( clientNum, qtrue, qtrue ) ) { - return; - } - - if( delay == 0 ) { - ClientBegin( clientNum ); - return; - } - - AddBotToSpawnQueue( clientNum, delay ); -} - - -/* -=============== -Svcmd_AddBot_f -=============== -*/ -void Svcmd_AddBot_f( void ) { - float skill; - int delay; - char name[MAX_TOKEN_CHARS]; - char altname[MAX_TOKEN_CHARS]; - char string[MAX_TOKEN_CHARS]; - char team[MAX_TOKEN_CHARS]; - - // are bots enabled? - if ( !trap_Cvar_VariableIntegerValue( "bot_enable" ) ) { - return; - } - - // name - trap_Argv( 1, name, sizeof( name ) ); - if ( !name[0] ) { - trap_Printf( "Usage: Addbot [skill 1-5] [team] [msec delay] [altname]\n" ); - return; - } - - // skill - trap_Argv( 2, string, sizeof( string ) ); - if ( !string[0] ) { - skill = 4; - } - else { - skill = atof( string ); - } - - // team - trap_Argv( 3, team, sizeof( team ) ); - - // delay - trap_Argv( 4, string, sizeof( string ) ); - if ( !string[0] ) { - delay = 0; - } - else { - delay = atoi( string ); - } - - // alternative name - trap_Argv( 5, altname, sizeof( altname ) ); - - G_AddBot( name, skill, team, delay, altname ); - - // if this was issued during gameplay and we are playing locally, - // go ahead and load the bot's media immediately - if ( level.time - level.startTime > 1000 && - trap_Cvar_VariableIntegerValue( "cl_running" ) ) { - trap_SendServerCommand( -1, "loaddefered\n" ); // FIXME: spelled wrong, but not changing for demo - } -} - -/* -=============== -Svcmd_BotList_f -=============== -*/ -void Svcmd_BotList_f( void ) { - int i; - char name[MAX_TOKEN_CHARS]; - char funname[MAX_TOKEN_CHARS]; - char model[MAX_TOKEN_CHARS]; - char aifile[MAX_TOKEN_CHARS]; - - trap_Printf("^1name model aifile funname\n"); - for (i = 0; i < g_numBots; i++) { - strcpy(name, Info_ValueForKey( g_botInfos[i], "name" )); - if ( !*name ) { - strcpy(name, "UnnamedPlayer"); - } - strcpy(funname, Info_ValueForKey( g_botInfos[i], "funname" )); - if ( !*funname ) { - strcpy(funname, ""); - } - strcpy(model, Info_ValueForKey( g_botInfos[i], "model" )); - if ( !*model ) { - strcpy(model, "visor/default"); - } - strcpy(aifile, Info_ValueForKey( g_botInfos[i], "aifile")); - if (!*aifile ) { - strcpy(aifile, "bots/default_c.c"); - } - trap_Printf(va("%-16s %-16s %-20s %-20s\n", name, model, aifile, funname)); - } -} - - -/* -=============== -G_SpawnBots -=============== -*/ -static void G_SpawnBots( char *botList, int baseDelay ) { - char *bot; - char *p; - float skill; - int delay; - char bots[MAX_INFO_VALUE]; - - podium1 = NULL; - podium2 = NULL; - podium3 = NULL; - - skill = trap_Cvar_VariableValue( "g_spSkill" ); - if( skill < 1 ) { - trap_Cvar_Set( "g_spSkill", "1" ); - skill = 1; - } - else if ( skill > 5 ) { - trap_Cvar_Set( "g_spSkill", "5" ); - skill = 5; - } - - Q_strncpyz( bots, botList, sizeof(bots) ); - p = &bots[0]; - delay = baseDelay; - while( *p ) { - //skip spaces - while( *p && *p == ' ' ) { - p++; - } - if( !p ) { - break; - } - - // mark start of bot name - bot = p; - - // skip until space of null - while( *p && *p != ' ' ) { - p++; - } - if( *p ) { - *p++ = 0; - } - - // we must add the bot this way, calling G_AddBot directly at this stage - // does "Bad Things" - trap_SendConsoleCommand( EXEC_INSERT, va("addbot %s %f free %i\n", bot, skill, delay) ); - - delay += BOT_BEGIN_DELAY_INCREMENT; - } -} - - -/* -=============== -G_LoadBotsFromFile -=============== -*/ -static void G_LoadBotsFromFile( char *filename ) { - int len; - fileHandle_t f; - char buf[MAX_BOTS_TEXT]; - - len = trap_FS_FOpenFile( filename, &f, FS_READ ); - if ( !f ) { - trap_Printf( va( S_COLOR_RED "file not found: %s\n", filename ) ); - return; - } - if ( len >= MAX_BOTS_TEXT ) { - trap_Printf( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_BOTS_TEXT ) ); - trap_FS_FCloseFile( f ); - return; - } - - trap_FS_Read( buf, len, f ); - buf[len] = 0; - trap_FS_FCloseFile( f ); - - g_numBots += G_ParseInfos( buf, MAX_BOTS - g_numBots, &g_botInfos[g_numBots] ); -} - -/* -=============== -G_LoadBots -=============== -*/ -static void G_LoadBots( void ) { - vmCvar_t botsFile; - int numdirs; - char filename[128]; - char dirlist[1024]; - char* dirptr; - int i; - int dirlen; - - if ( !trap_Cvar_VariableIntegerValue( "bot_enable" ) ) { - return; - } - - g_numBots = 0; - - trap_Cvar_Register( &botsFile, "g_botsFile", "", CVAR_INIT|CVAR_ROM ); - if( *botsFile.string ) { - G_LoadBotsFromFile(botsFile.string); - } - else { - G_LoadBotsFromFile("scripts/bots.txt"); - } - - // get all bots from .bot files - numdirs = trap_FS_GetFileList("scripts", ".bot", dirlist, 1024 ); - dirptr = dirlist; - for (i = 0; i < numdirs; i++, dirptr += dirlen+1) { - dirlen = strlen(dirptr); - strcpy(filename, "scripts/"); - strcat(filename, dirptr); - G_LoadBotsFromFile(filename); - } - trap_Printf( va( "%i bots parsed\n", g_numBots ) ); -} - - - -/* -=============== -G_GetBotInfoByNumber -=============== -*/ -char *G_GetBotInfoByNumber( int num ) { - if( num < 0 || num >= g_numBots ) { - trap_Printf( va( S_COLOR_RED "Invalid bot number: %i\n", num ) ); - return NULL; - } - return g_botInfos[num]; -} - - -/* -=============== -G_GetBotInfoByName -=============== -*/ -char *G_GetBotInfoByName( const char *name ) { - int n; - char *value; - - for ( n = 0; n < g_numBots ; n++ ) { - value = Info_ValueForKey( g_botInfos[n], "name" ); - if ( !Q_stricmp( value, name ) ) { - return g_botInfos[n]; - } - } - - return NULL; -} - -/* -=============== -G_InitBots -=============== -*/ -void G_InitBots( qboolean restart ) { - int fragLimit; - int timeLimit; - const char *arenainfo; - char *strValue; - int basedelay; - char map[MAX_QPATH]; - char serverinfo[MAX_INFO_STRING]; - - G_LoadBots(); - G_LoadArenas(); - - trap_Cvar_Register( &bot_minplayers, "bot_minplayers", "0", CVAR_SERVERINFO ); - - if( g_gametype.integer == GT_SINGLE_PLAYER ) { - trap_GetServerinfo( serverinfo, sizeof(serverinfo) ); - Q_strncpyz( map, Info_ValueForKey( serverinfo, "mapname" ), sizeof(map) ); - arenainfo = G_GetArenaInfoByMap( map ); - if ( !arenainfo ) { - return; - } - - strValue = Info_ValueForKey( arenainfo, "fraglimit" ); - fragLimit = atoi( strValue ); - if ( fragLimit ) { - trap_Cvar_Set( "fraglimit", strValue ); - } - else { - trap_Cvar_Set( "fraglimit", "0" ); - } - - strValue = Info_ValueForKey( arenainfo, "timelimit" ); - timeLimit = atoi( strValue ); - if ( timeLimit ) { - trap_Cvar_Set( "timelimit", strValue ); - } - else { - trap_Cvar_Set( "timelimit", "0" ); - } - - if ( !fragLimit && !timeLimit ) { - trap_Cvar_Set( "fraglimit", "10" ); - trap_Cvar_Set( "timelimit", "0" ); - } - - basedelay = BOT_BEGIN_DELAY_BASE; - strValue = Info_ValueForKey( arenainfo, "special" ); - if( Q_stricmp( strValue, "training" ) == 0 ) { - basedelay += 10000; - } - - if( !restart ) { - G_SpawnBots( Info_ValueForKey( arenainfo, "bots" ), basedelay ); - } - } -} +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// g_bot.c + +#include "g_local.h" + + +static int g_numBots; +static char *g_botInfos[MAX_BOTS]; + + +int g_numArenas; +static char *g_arenaInfos[MAX_ARENAS]; + + +#define BOT_BEGIN_DELAY_BASE 2000 +#define BOT_BEGIN_DELAY_INCREMENT 1500 + +#define BOT_SPAWN_QUEUE_DEPTH 16 + +typedef struct { + int clientNum; + int spawnTime; +} botSpawnQueue_t; + +//static int botBeginDelay = 0; // bk001206 - unused, init +static botSpawnQueue_t botSpawnQueue[BOT_SPAWN_QUEUE_DEPTH]; + +vmCvar_t bot_minplayers; + +extern gentity_t *podium1; +extern gentity_t *podium2; +extern gentity_t *podium3; + +float trap_Cvar_VariableValue( const char *var_name ) { + char buf[128]; + + trap_Cvar_VariableStringBuffer(var_name, buf, sizeof(buf)); + return atof(buf); +} + + + +/* +=============== +G_ParseInfos +=============== +*/ +int G_ParseInfos( char *buf, int max, char *infos[] ) { + char *token; + int count; + char key[MAX_TOKEN_CHARS]; + char info[MAX_INFO_STRING]; + + count = 0; + + while ( 1 ) { + token = COM_Parse( &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( &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( &buf, qfalse ); + if ( !token[0] ) { + strcpy( token, "" ); + } + Info_SetValueForKey( info, key, token ); + } + //NOTE: extra space for arena number + infos[count] = G_Alloc(strlen(info) + strlen("\\num\\") + strlen(va("%d", MAX_ARENAS)) + 1); + if (infos[count]) { + strcpy(infos[count], info); + count++; + } + } + return count; +} + +/* +=============== +G_LoadArenasFromFile +=============== +*/ +static void G_LoadArenasFromFile( char *filename ) { + int len; + fileHandle_t f; + char buf[MAX_ARENAS_TEXT]; + + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( !f ) { + trap_Printf( va( S_COLOR_RED "file not found: %s\n", filename ) ); + return; + } + if ( len >= MAX_ARENAS_TEXT ) { + trap_Printf( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_ARENAS_TEXT ) ); + trap_FS_FCloseFile( f ); + return; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + + g_numArenas += G_ParseInfos( buf, MAX_ARENAS - g_numArenas, &g_arenaInfos[g_numArenas] ); +} + +/* +=============== +G_LoadArenas +=============== +*/ +static void G_LoadArenas( void ) { + int numdirs; + vmCvar_t arenasFile; + char filename[128]; + char dirlist[1024]; + char* dirptr; + int i, n; + int dirlen; + + g_numArenas = 0; + + trap_Cvar_Register( &arenasFile, "g_arenasFile", "", CVAR_INIT|CVAR_ROM ); + if( *arenasFile.string ) { + G_LoadArenasFromFile(arenasFile.string); + } + else { + G_LoadArenasFromFile("scripts/arenas.txt"); + } + + // get all arenas from .arena files + numdirs = trap_FS_GetFileList("scripts", ".arena", dirlist, 1024 ); + dirptr = dirlist; + for (i = 0; i < numdirs; i++, dirptr += dirlen+1) { + dirlen = strlen(dirptr); + strcpy(filename, "scripts/"); + strcat(filename, dirptr); + G_LoadArenasFromFile(filename); + } + trap_Printf( va( "%i arenas parsed\n", g_numArenas ) ); + + for( n = 0; n < g_numArenas; n++ ) { + Info_SetValueForKey( g_arenaInfos[n], "num", va( "%i", n ) ); + } +} + + +/* +=============== +G_GetArenaInfoByNumber +=============== +*/ +const char *G_GetArenaInfoByMap( const char *map ) { + int n; + + for( n = 0; n < g_numArenas; n++ ) { + if( Q_stricmp( Info_ValueForKey( g_arenaInfos[n], "map" ), map ) == 0 ) { + return g_arenaInfos[n]; + } + } + + return NULL; +} + + +/* +================= +PlayerIntroSound +================= +*/ +static void PlayerIntroSound( const char *modelAndSkin ) { + char model[MAX_QPATH]; + char *skin; + + Q_strncpyz( model, modelAndSkin, sizeof(model) ); + skin = Q_strrchr( model, '/' ); + if ( skin ) { + *skin++ = '\0'; + } + else { + skin = model; + } + + if( Q_stricmp( skin, "default" ) == 0 ) { + skin = model; + } + + trap_SendConsoleCommand( EXEC_APPEND, va( "play sound/player/announce/%s.wav\n", skin ) ); +} + +/* +=============== +G_AddRandomBot +=============== +*/ +void G_AddRandomBot( int team ) { + int i, n, num; + float skill; + char *value, netname[36], *teamstr; + gclient_t *cl; + + num = 0; + for ( n = 0; n < g_numBots ; n++ ) { + value = Info_ValueForKey( g_botInfos[n], "name" ); + // + for ( i=0 ; i< g_maxclients.integer ; i++ ) { + cl = level.clients + i; + if ( cl->pers.connected != CON_CONNECTED ) { + continue; + } + if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) { + continue; + } + if ( team >= 0 && cl->sess.sessionTeam != team ) { + continue; + } + if ( !Q_stricmp( value, cl->pers.netname ) ) { + break; + } + } + if (i >= g_maxclients.integer) { + num++; + } + } + num = random() * num; + for ( n = 0; n < g_numBots ; n++ ) { + value = Info_ValueForKey( g_botInfos[n], "name" ); + // + for ( i=0 ; i< g_maxclients.integer ; i++ ) { + cl = level.clients + i; + if ( cl->pers.connected != CON_CONNECTED ) { + continue; + } + if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) { + continue; + } + if ( team >= 0 && cl->sess.sessionTeam != team ) { + continue; + } + if ( !Q_stricmp( value, cl->pers.netname ) ) { + break; + } + } + if (i >= g_maxclients.integer) { + num--; + if (num <= 0) { + skill = trap_Cvar_VariableValue( "g_spSkill" ); + if (team == TEAM_RED) teamstr = "red"; + else if (team == TEAM_BLUE) teamstr = "blue"; + else teamstr = ""; + strncpy(netname, value, sizeof(netname)-1); + netname[sizeof(netname)-1] = '\0'; + Q_CleanStr(netname); + trap_SendConsoleCommand( EXEC_INSERT, va("addbot %s %f %s %i\n", netname, skill, teamstr, 0) ); + return; + } + } + } +} + +/* +=============== +G_RemoveRandomBot +=============== +*/ +int G_RemoveRandomBot( int team ) { + int i; + char netname[36]; + gclient_t *cl; + + for ( i=0 ; i< g_maxclients.integer ; i++ ) { + cl = level.clients + i; + if ( cl->pers.connected != CON_CONNECTED ) { + continue; + } + if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) { + continue; + } + if ( team >= 0 && cl->sess.sessionTeam != team ) { + continue; + } + strcpy(netname, cl->pers.netname); + Q_CleanStr(netname); + trap_SendConsoleCommand( EXEC_INSERT, va("kick %s\n", netname) ); + return qtrue; + } + return qfalse; +} + +/* +=============== +G_CountHumanPlayers +=============== +*/ +int G_CountHumanPlayers( int team ) { + int i, num; + gclient_t *cl; + + num = 0; + for ( i=0 ; i< g_maxclients.integer ; i++ ) { + cl = level.clients + i; + if ( cl->pers.connected != CON_CONNECTED ) { + continue; + } + if ( g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT ) { + continue; + } + if ( team >= 0 && cl->sess.sessionTeam != team ) { + continue; + } + num++; + } + return num; +} + +/* +=============== +G_CountBotPlayers +=============== +*/ +int G_CountBotPlayers( int team ) { + int i, n, num; + gclient_t *cl; + + num = 0; + for ( i=0 ; i< g_maxclients.integer ; i++ ) { + cl = level.clients + i; + if ( cl->pers.connected != CON_CONNECTED ) { + continue; + } + if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) { + continue; + } + if ( team >= 0 && cl->sess.sessionTeam != team ) { + continue; + } + num++; + } + for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) { + if( !botSpawnQueue[n].spawnTime ) { + continue; + } + if ( botSpawnQueue[n].spawnTime > level.time ) { + continue; + } + num++; + } + return num; +} + +/* +=============== +G_CheckMinimumPlayers +=============== +*/ +void G_CheckMinimumPlayers( void ) { + int minplayers; + int humanplayers, botplayers; + static int checkminimumplayers_time; + + if (level.intermissiontime) return; + //only check once each 10 seconds + if (checkminimumplayers_time > level.time - 10000) { + return; + } + checkminimumplayers_time = level.time; + trap_Cvar_Update(&bot_minplayers); + minplayers = bot_minplayers.integer; + if (minplayers <= 0) return; + + if (g_gametype.integer >= GT_TEAM) { + if (minplayers >= g_maxclients.integer / 2) { + minplayers = (g_maxclients.integer / 2) -1; + } + + humanplayers = G_CountHumanPlayers( TEAM_RED ); + botplayers = G_CountBotPlayers( TEAM_RED ); + // + if (humanplayers + botplayers < minplayers) { + G_AddRandomBot( TEAM_RED ); + } else if (humanplayers + botplayers > minplayers && botplayers) { + G_RemoveRandomBot( TEAM_RED ); + } + // + humanplayers = G_CountHumanPlayers( TEAM_BLUE ); + botplayers = G_CountBotPlayers( TEAM_BLUE ); + // + if (humanplayers + botplayers < minplayers) { + G_AddRandomBot( TEAM_BLUE ); + } else if (humanplayers + botplayers > minplayers && botplayers) { + G_RemoveRandomBot( TEAM_BLUE ); + } + } + else if (g_gametype.integer == GT_TOURNAMENT ) { + if (minplayers >= g_maxclients.integer) { + minplayers = g_maxclients.integer-1; + } + humanplayers = G_CountHumanPlayers( -1 ); + botplayers = G_CountBotPlayers( -1 ); + // + if (humanplayers + botplayers < minplayers) { + G_AddRandomBot( TEAM_FREE ); + } else if (humanplayers + botplayers > minplayers && botplayers) { + // try to remove spectators first + if (!G_RemoveRandomBot( TEAM_SPECTATOR )) { + // just remove the bot that is playing + G_RemoveRandomBot( -1 ); + } + } + } + else if (g_gametype.integer == GT_FFA) { + if (minplayers >= g_maxclients.integer) { + minplayers = g_maxclients.integer-1; + } + humanplayers = G_CountHumanPlayers( TEAM_FREE ); + botplayers = G_CountBotPlayers( TEAM_FREE ); + // + if (humanplayers + botplayers < minplayers) { + G_AddRandomBot( TEAM_FREE ); + } else if (humanplayers + botplayers > minplayers && botplayers) { + G_RemoveRandomBot( TEAM_FREE ); + } + } +} + +/* +=============== +G_CheckBotSpawn +=============== +*/ +void G_CheckBotSpawn( void ) { + int n; + char userinfo[MAX_INFO_VALUE]; + + G_CheckMinimumPlayers(); + + for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) { + if( !botSpawnQueue[n].spawnTime ) { + continue; + } + if ( botSpawnQueue[n].spawnTime > level.time ) { + continue; + } + ClientBegin( botSpawnQueue[n].clientNum ); + botSpawnQueue[n].spawnTime = 0; + + if( g_gametype.integer == GT_SINGLE_PLAYER ) { + trap_GetUserinfo( botSpawnQueue[n].clientNum, userinfo, sizeof(userinfo) ); + PlayerIntroSound( Info_ValueForKey (userinfo, "model") ); + } + } +} + + +/* +=============== +AddBotToSpawnQueue +=============== +*/ +static void AddBotToSpawnQueue( int clientNum, int delay ) { + int n; + + for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) { + if( !botSpawnQueue[n].spawnTime ) { + botSpawnQueue[n].spawnTime = level.time + delay; + botSpawnQueue[n].clientNum = clientNum; + return; + } + } + + G_Printf( S_COLOR_YELLOW "Unable to delay spawn\n" ); + ClientBegin( clientNum ); +} + + +/* +=============== +G_RemoveQueuedBotBegin + +Called on client disconnect to make sure the delayed spawn +doesn't happen on a freed index +=============== +*/ +void G_RemoveQueuedBotBegin( int clientNum ) { + int n; + + for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) { + if( botSpawnQueue[n].clientNum == clientNum ) { + botSpawnQueue[n].spawnTime = 0; + return; + } + } +} + + +/* +=============== +G_BotConnect +=============== +*/ +qboolean G_BotConnect( int clientNum, qboolean restart ) { + bot_settings_t settings; + char userinfo[MAX_INFO_STRING]; + + trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) ); + + Q_strncpyz( settings.characterfile, Info_ValueForKey( userinfo, "characterfile" ), sizeof(settings.characterfile) ); + settings.skill = atof( Info_ValueForKey( userinfo, "skill" ) ); + Q_strncpyz( settings.team, Info_ValueForKey( userinfo, "team" ), sizeof(settings.team) ); + + if (!BotAISetupClient( clientNum, &settings, restart )) { + trap_DropClient( clientNum, "BotAISetupClient failed" ); + return qfalse; + } + + return qtrue; +} + + +/* +=============== +G_AddBot +=============== +*/ +static void G_AddBot( const char *name, float skill, const char *team, int delay, char *altname) { + int clientNum; + char *botinfo; + gentity_t *bot; + char *key; + char *s; + char *botname; + char *model; + char *headmodel; + char userinfo[MAX_INFO_STRING]; + + // get the botinfo from bots.txt + botinfo = G_GetBotInfoByName( name ); + if ( !botinfo ) { + G_Printf( S_COLOR_RED "Error: Bot '%s' not defined\n", name ); + return; + } + + // create the bot's userinfo + userinfo[0] = '\0'; + + botname = Info_ValueForKey( botinfo, "funname" ); + if( !botname[0] ) { + botname = Info_ValueForKey( botinfo, "name" ); + } + // check for an alternative name + if (altname && altname[0]) { + botname = altname; + } + Info_SetValueForKey( userinfo, "name", botname ); + Info_SetValueForKey( userinfo, "rate", "25000" ); + Info_SetValueForKey( userinfo, "snaps", "20" ); + Info_SetValueForKey( userinfo, "skill", va("%1.2f", skill) ); + + if ( skill >= 1 && skill < 2 ) { + Info_SetValueForKey( userinfo, "handicap", "50" ); + } + else if ( skill >= 2 && skill < 3 ) { + Info_SetValueForKey( userinfo, "handicap", "70" ); + } + else if ( skill >= 3 && skill < 4 ) { + Info_SetValueForKey( userinfo, "handicap", "90" ); + } + + key = "model"; + model = Info_ValueForKey( botinfo, key ); + if ( !*model ) { + model = "visor/default"; + } + Info_SetValueForKey( userinfo, key, model ); + key = "team_model"; + Info_SetValueForKey( userinfo, key, model ); + + key = "headmodel"; + headmodel = Info_ValueForKey( botinfo, key ); + if ( !*headmodel ) { + headmodel = model; + } + Info_SetValueForKey( userinfo, key, headmodel ); + key = "team_headmodel"; + Info_SetValueForKey( userinfo, key, headmodel ); + + key = "gender"; + s = Info_ValueForKey( botinfo, key ); + if ( !*s ) { + s = "male"; + } + Info_SetValueForKey( userinfo, "sex", s ); + + key = "color1"; + s = Info_ValueForKey( botinfo, key ); + if ( !*s ) { + s = "4"; + } + Info_SetValueForKey( userinfo, key, s ); + + key = "color2"; + s = Info_ValueForKey( botinfo, key ); + if ( !*s ) { + s = "5"; + } + Info_SetValueForKey( userinfo, key, s ); + + s = Info_ValueForKey(botinfo, "aifile"); + if (!*s ) { + trap_Printf( S_COLOR_RED "Error: bot has no aifile specified\n" ); + return; + } + + // have the server allocate a client slot + clientNum = trap_BotAllocateClient(); + if ( clientNum == -1 ) { + G_Printf( S_COLOR_RED "Unable to add bot. All player slots are in use.\n" ); + G_Printf( S_COLOR_RED "Start server with more 'open' slots (or check setting of sv_maxclients cvar).\n" ); + return; + } + + // initialize the bot settings + if( !team || !*team ) { + if( g_gametype.integer >= GT_TEAM ) { + if( PickTeam(clientNum) == TEAM_RED) { + team = "red"; + } + else { + team = "blue"; + } + } + else { + team = "red"; + } + } + Info_SetValueForKey( userinfo, "characterfile", Info_ValueForKey( botinfo, "aifile" ) ); + Info_SetValueForKey( userinfo, "skill", va( "%5.2f", skill ) ); + Info_SetValueForKey( userinfo, "team", team ); + + bot = &g_entities[ clientNum ]; + bot->r.svFlags |= SVF_BOT; + bot->inuse = qtrue; + + // register the userinfo + trap_SetUserinfo( clientNum, userinfo ); + + // have it connect to the game as a normal client + if ( ClientConnect( clientNum, qtrue, qtrue ) ) { + return; + } + + if( delay == 0 ) { + ClientBegin( clientNum ); + return; + } + + AddBotToSpawnQueue( clientNum, delay ); +} + + +/* +=============== +Svcmd_AddBot_f +=============== +*/ +void Svcmd_AddBot_f( void ) { + float skill; + int delay; + char name[MAX_TOKEN_CHARS]; + char altname[MAX_TOKEN_CHARS]; + char string[MAX_TOKEN_CHARS]; + char team[MAX_TOKEN_CHARS]; + + // are bots enabled? + if ( !trap_Cvar_VariableIntegerValue( "bot_enable" ) ) { + return; + } + + // name + trap_Argv( 1, name, sizeof( name ) ); + if ( !name[0] ) { + trap_Printf( "Usage: Addbot [skill 1-5] [team] [msec delay] [altname]\n" ); + return; + } + + // skill + trap_Argv( 2, string, sizeof( string ) ); + if ( !string[0] ) { + skill = 4; + } + else { + skill = atof( string ); + } + + // team + trap_Argv( 3, team, sizeof( team ) ); + + // delay + trap_Argv( 4, string, sizeof( string ) ); + if ( !string[0] ) { + delay = 0; + } + else { + delay = atoi( string ); + } + + // alternative name + trap_Argv( 5, altname, sizeof( altname ) ); + + G_AddBot( name, skill, team, delay, altname ); + + // if this was issued during gameplay and we are playing locally, + // go ahead and load the bot's media immediately + if ( level.time - level.startTime > 1000 && + trap_Cvar_VariableIntegerValue( "cl_running" ) ) { + trap_SendServerCommand( -1, "loaddefered\n" ); // FIXME: spelled wrong, but not changing for demo + } +} + +/* +=============== +Svcmd_BotList_f +=============== +*/ +void Svcmd_BotList_f( void ) { + int i; + char name[MAX_TOKEN_CHARS]; + char funname[MAX_TOKEN_CHARS]; + char model[MAX_TOKEN_CHARS]; + char aifile[MAX_TOKEN_CHARS]; + + trap_Printf("^1name model aifile funname\n"); + for (i = 0; i < g_numBots; i++) { + strcpy(name, Info_ValueForKey( g_botInfos[i], "name" )); + if ( !*name ) { + strcpy(name, "UnnamedPlayer"); + } + strcpy(funname, Info_ValueForKey( g_botInfos[i], "funname" )); + if ( !*funname ) { + strcpy(funname, ""); + } + strcpy(model, Info_ValueForKey( g_botInfos[i], "model" )); + if ( !*model ) { + strcpy(model, "visor/default"); + } + strcpy(aifile, Info_ValueForKey( g_botInfos[i], "aifile")); + if (!*aifile ) { + strcpy(aifile, "bots/default_c.c"); + } + trap_Printf(va("%-16s %-16s %-20s %-20s\n", name, model, aifile, funname)); + } +} + + +/* +=============== +G_SpawnBots +=============== +*/ +static void G_SpawnBots( char *botList, int baseDelay ) { + char *bot; + char *p; + float skill; + int delay; + char bots[MAX_INFO_VALUE]; + + podium1 = NULL; + podium2 = NULL; + podium3 = NULL; + + skill = trap_Cvar_VariableValue( "g_spSkill" ); + if( skill < 1 ) { + trap_Cvar_Set( "g_spSkill", "1" ); + skill = 1; + } + else if ( skill > 5 ) { + trap_Cvar_Set( "g_spSkill", "5" ); + skill = 5; + } + + Q_strncpyz( bots, botList, sizeof(bots) ); + p = &bots[0]; + delay = baseDelay; + while( *p ) { + //skip spaces + while( *p && *p == ' ' ) { + p++; + } + if( !p ) { + break; + } + + // mark start of bot name + bot = p; + + // skip until space of null + while( *p && *p != ' ' ) { + p++; + } + if( *p ) { + *p++ = 0; + } + + // we must add the bot this way, calling G_AddBot directly at this stage + // does "Bad Things" + trap_SendConsoleCommand( EXEC_INSERT, va("addbot %s %f free %i\n", bot, skill, delay) ); + + delay += BOT_BEGIN_DELAY_INCREMENT; + } +} + + +/* +=============== +G_LoadBotsFromFile +=============== +*/ +static void G_LoadBotsFromFile( char *filename ) { + int len; + fileHandle_t f; + char buf[MAX_BOTS_TEXT]; + + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( !f ) { + trap_Printf( va( S_COLOR_RED "file not found: %s\n", filename ) ); + return; + } + if ( len >= MAX_BOTS_TEXT ) { + trap_Printf( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_BOTS_TEXT ) ); + trap_FS_FCloseFile( f ); + return; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + + g_numBots += G_ParseInfos( buf, MAX_BOTS - g_numBots, &g_botInfos[g_numBots] ); +} + +/* +=============== +G_LoadBots +=============== +*/ +static void G_LoadBots( void ) { + vmCvar_t botsFile; + int numdirs; + char filename[128]; + char dirlist[1024]; + char* dirptr; + int i; + int dirlen; + + if ( !trap_Cvar_VariableIntegerValue( "bot_enable" ) ) { + return; + } + + g_numBots = 0; + + trap_Cvar_Register( &botsFile, "g_botsFile", "", CVAR_INIT|CVAR_ROM ); + if( *botsFile.string ) { + G_LoadBotsFromFile(botsFile.string); + } + else { + G_LoadBotsFromFile("scripts/bots.txt"); + } + + // get all bots from .bot files + numdirs = trap_FS_GetFileList("scripts", ".bot", dirlist, 1024 ); + dirptr = dirlist; + for (i = 0; i < numdirs; i++, dirptr += dirlen+1) { + dirlen = strlen(dirptr); + strcpy(filename, "scripts/"); + strcat(filename, dirptr); + G_LoadBotsFromFile(filename); + } + trap_Printf( va( "%i bots parsed\n", g_numBots ) ); +} + + + +/* +=============== +G_GetBotInfoByNumber +=============== +*/ +char *G_GetBotInfoByNumber( int num ) { + if( num < 0 || num >= g_numBots ) { + trap_Printf( va( S_COLOR_RED "Invalid bot number: %i\n", num ) ); + return NULL; + } + return g_botInfos[num]; +} + + +/* +=============== +G_GetBotInfoByName +=============== +*/ +char *G_GetBotInfoByName( const char *name ) { + int n; + char *value; + + for ( n = 0; n < g_numBots ; n++ ) { + value = Info_ValueForKey( g_botInfos[n], "name" ); + if ( !Q_stricmp( value, name ) ) { + return g_botInfos[n]; + } + } + + return NULL; +} + +/* +=============== +G_InitBots +=============== +*/ +void G_InitBots( qboolean restart ) { + int fragLimit; + int timeLimit; + const char *arenainfo; + char *strValue; + int basedelay; + char map[MAX_QPATH]; + char serverinfo[MAX_INFO_STRING]; + + G_LoadBots(); + G_LoadArenas(); + + trap_Cvar_Register( &bot_minplayers, "bot_minplayers", "0", CVAR_SERVERINFO ); + + if( g_gametype.integer == GT_SINGLE_PLAYER ) { + trap_GetServerinfo( serverinfo, sizeof(serverinfo) ); + Q_strncpyz( map, Info_ValueForKey( serverinfo, "mapname" ), sizeof(map) ); + arenainfo = G_GetArenaInfoByMap( map ); + if ( !arenainfo ) { + return; + } + + strValue = Info_ValueForKey( arenainfo, "fraglimit" ); + fragLimit = atoi( strValue ); + if ( fragLimit ) { + trap_Cvar_Set( "fraglimit", strValue ); + } + else { + trap_Cvar_Set( "fraglimit", "0" ); + } + + strValue = Info_ValueForKey( arenainfo, "timelimit" ); + timeLimit = atoi( strValue ); + if ( timeLimit ) { + trap_Cvar_Set( "timelimit", strValue ); + } + else { + trap_Cvar_Set( "timelimit", "0" ); + } + + if ( !fragLimit && !timeLimit ) { + trap_Cvar_Set( "fraglimit", "10" ); + trap_Cvar_Set( "timelimit", "0" ); + } + + basedelay = BOT_BEGIN_DELAY_BASE; + strValue = Info_ValueForKey( arenainfo, "special" ); + if( Q_stricmp( strValue, "training" ) == 0 ) { + basedelay += 10000; + } + + if( !restart ) { + G_SpawnBots( Info_ValueForKey( arenainfo, "bots" ), basedelay ); + } + } +} diff --git a/code/game/g_client.c b/code/game/g_client.c index 5a584d4..355077b 100755 --- a/code/game/g_client.c +++ b/code/game/g_client.c @@ -1,1344 +1,1344 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -#include "g_local.h" - -// g_client.c -- client functions that don't happen every frame - -static vec3_t playerMins = {-15, -15, -24}; -static vec3_t playerMaxs = {15, 15, 32}; - -/*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) initial -potential spawning position for deathmatch games. -The first time a player enters the game, they will be at an 'initial' spot. -Targets will be fired when someone spawns in on them. -"nobots" will prevent bots from using this spot. -"nohumans" will prevent non-bots from using this spot. -*/ -void SP_info_player_deathmatch( gentity_t *ent ) { - int i; - - G_SpawnInt( "nobots", "0", &i); - if ( i ) { - ent->flags |= FL_NO_BOTS; - } - G_SpawnInt( "nohumans", "0", &i ); - if ( i ) { - ent->flags |= FL_NO_HUMANS; - } -} - -/*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32) -equivelant to info_player_deathmatch -*/ -void SP_info_player_start(gentity_t *ent) { - ent->classname = "info_player_deathmatch"; - SP_info_player_deathmatch( ent ); -} - -/*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32) -The intermission will be viewed from this point. Target an info_notnull for the view direction. -*/ -void SP_info_player_intermission( gentity_t *ent ) { - -} - - - -/* -======================================================================= - - SelectSpawnPoint - -======================================================================= -*/ - -/* -================ -SpotWouldTelefrag - -================ -*/ -qboolean SpotWouldTelefrag( gentity_t *spot ) { - int i, num; - int touch[MAX_GENTITIES]; - gentity_t *hit; - vec3_t mins, maxs; - - VectorAdd( spot->s.origin, playerMins, mins ); - VectorAdd( spot->s.origin, playerMaxs, maxs ); - num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); - - for (i=0 ; iclient && hit->client->ps.stats[STAT_HEALTH] > 0 ) { - if ( hit->client) { - return qtrue; - } - - } - - return qfalse; -} - -/* -================ -SelectNearestDeathmatchSpawnPoint - -Find the spot that we DON'T want to use -================ -*/ -#define MAX_SPAWN_POINTS 128 -gentity_t *SelectNearestDeathmatchSpawnPoint( vec3_t from ) { - gentity_t *spot; - vec3_t delta; - float dist, nearestDist; - gentity_t *nearestSpot; - - nearestDist = 999999; - nearestSpot = NULL; - spot = NULL; - - while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { - - VectorSubtract( spot->s.origin, from, delta ); - dist = VectorLength( delta ); - if ( dist < nearestDist ) { - nearestDist = dist; - nearestSpot = spot; - } - } - - return nearestSpot; -} - - -/* -================ -SelectRandomDeathmatchSpawnPoint - -go to a random point that doesn't telefrag -================ -*/ -#define MAX_SPAWN_POINTS 128 -gentity_t *SelectRandomDeathmatchSpawnPoint( void ) { - gentity_t *spot; - int count; - int selection; - gentity_t *spots[MAX_SPAWN_POINTS]; - - count = 0; - spot = NULL; - - while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { - if ( SpotWouldTelefrag( spot ) ) { - continue; - } - spots[ count ] = spot; - count++; - } - - if ( !count ) { // no spots that won't telefrag - return G_Find( NULL, FOFS(classname), "info_player_deathmatch"); - } - - selection = rand() % count; - return spots[ selection ]; -} - -/* -=========== -SelectRandomFurthestSpawnPoint - -Chooses a player start, deathmatch start, etc -============ -*/ -gentity_t *SelectRandomFurthestSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles ) { - gentity_t *spot; - vec3_t delta; - float dist; - float list_dist[64]; - gentity_t *list_spot[64]; - int numSpots, rnd, i, j; - - numSpots = 0; - spot = NULL; - - while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { - if ( SpotWouldTelefrag( spot ) ) { - continue; - } - VectorSubtract( spot->s.origin, avoidPoint, delta ); - dist = VectorLength( delta ); - for (i = 0; i < numSpots; i++) { - if ( dist > list_dist[i] ) { - if ( numSpots >= 64 ) - numSpots = 64-1; - for (j = numSpots; j > i; j--) { - list_dist[j] = list_dist[j-1]; - list_spot[j] = list_spot[j-1]; - } - list_dist[i] = dist; - list_spot[i] = spot; - numSpots++; - if (numSpots > 64) - numSpots = 64; - break; - } - } - if (i >= numSpots && numSpots < 64) { - list_dist[numSpots] = dist; - list_spot[numSpots] = spot; - numSpots++; - } - } - if (!numSpots) { - spot = G_Find( NULL, FOFS(classname), "info_player_deathmatch"); - if (!spot) - G_Error( "Couldn't find a spawn point" ); - VectorCopy (spot->s.origin, origin); - origin[2] += 9; - VectorCopy (spot->s.angles, angles); - return spot; - } - - // select a random spot from the spawn points furthest away - rnd = random() * (numSpots / 2); - - VectorCopy (list_spot[rnd]->s.origin, origin); - origin[2] += 9; - VectorCopy (list_spot[rnd]->s.angles, angles); - - return list_spot[rnd]; -} - -/* -=========== -SelectSpawnPoint - -Chooses a player start, deathmatch start, etc -============ -*/ -gentity_t *SelectSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles ) { - return SelectRandomFurthestSpawnPoint( avoidPoint, origin, angles ); - - /* - gentity_t *spot; - gentity_t *nearestSpot; - - nearestSpot = SelectNearestDeathmatchSpawnPoint( avoidPoint ); - - spot = SelectRandomDeathmatchSpawnPoint ( ); - if ( spot == nearestSpot ) { - // roll again if it would be real close to point of death - spot = SelectRandomDeathmatchSpawnPoint ( ); - if ( spot == nearestSpot ) { - // last try - spot = SelectRandomDeathmatchSpawnPoint ( ); - } - } - - // find a single player start spot - if (!spot) { - G_Error( "Couldn't find a spawn point" ); - } - - VectorCopy (spot->s.origin, origin); - origin[2] += 9; - VectorCopy (spot->s.angles, angles); - - return spot; - */ -} - -/* -=========== -SelectInitialSpawnPoint - -Try to find a spawn point marked 'initial', otherwise -use normal spawn selection. -============ -*/ -gentity_t *SelectInitialSpawnPoint( vec3_t origin, vec3_t angles ) { - gentity_t *spot; - - spot = NULL; - while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { - if ( spot->spawnflags & 1 ) { - break; - } - } - - if ( !spot || SpotWouldTelefrag( spot ) ) { - return SelectSpawnPoint( vec3_origin, origin, angles ); - } - - VectorCopy (spot->s.origin, origin); - origin[2] += 9; - VectorCopy (spot->s.angles, angles); - - return spot; -} - -/* -=========== -SelectSpectatorSpawnPoint - -============ -*/ -gentity_t *SelectSpectatorSpawnPoint( vec3_t origin, vec3_t angles ) { - FindIntermissionPoint(); - - VectorCopy( level.intermission_origin, origin ); - VectorCopy( level.intermission_angle, angles ); - - return NULL; -} - -/* -======================================================================= - -BODYQUE - -======================================================================= -*/ - -/* -=============== -InitBodyQue -=============== -*/ -void InitBodyQue (void) { - int i; - gentity_t *ent; - - level.bodyQueIndex = 0; - for (i=0; iclassname = "bodyque"; - ent->neverFree = qtrue; - level.bodyQue[i] = ent; - } -} - -/* -============= -BodySink - -After sitting around for five seconds, fall into the ground and dissapear -============= -*/ -void BodySink( gentity_t *ent ) { - if ( level.time - ent->timestamp > 6500 ) { - // the body ques are never actually freed, they are just unlinked - trap_UnlinkEntity( ent ); - ent->physicsObject = qfalse; - return; - } - ent->nextthink = level.time + 100; - ent->s.pos.trBase[2] -= 1; -} - -/* -============= -CopyToBodyQue - -A player is respawning, so make an entity that looks -just like the existing corpse to leave behind. -============= -*/ -void CopyToBodyQue( gentity_t *ent ) { -#ifdef MISSIONPACK - gentity_t *e; - int i; -#endif - gentity_t *body; - int contents; - - trap_UnlinkEntity (ent); - - // if client is in a nodrop area, don't leave the body - contents = trap_PointContents( ent->s.origin, -1 ); - if ( contents & CONTENTS_NODROP ) { - return; - } - - // grab a body que and cycle to the next one - body = level.bodyQue[ level.bodyQueIndex ]; - level.bodyQueIndex = (level.bodyQueIndex + 1) % BODY_QUEUE_SIZE; - - trap_UnlinkEntity (body); - - body->s = ent->s; - body->s.eFlags = EF_DEAD; // clear EF_TALK, etc -#ifdef MISSIONPACK - if ( ent->s.eFlags & EF_KAMIKAZE ) { - body->s.eFlags |= EF_KAMIKAZE; - - // check if there is a kamikaze timer around for this owner - for (i = 0; i < MAX_GENTITIES; i++) { - e = &g_entities[i]; - if (!e->inuse) - continue; - if (e->activator != ent) - continue; - if (strcmp(e->classname, "kamikaze timer")) - continue; - e->activator = body; - break; - } - } -#endif - body->s.powerups = 0; // clear powerups - body->s.loopSound = 0; // clear lava burning - body->s.number = body - g_entities; - body->timestamp = level.time; - body->physicsObject = qtrue; - body->physicsBounce = 0; // don't bounce - if ( body->s.groundEntityNum == ENTITYNUM_NONE ) { - body->s.pos.trType = TR_GRAVITY; - body->s.pos.trTime = level.time; - VectorCopy( ent->client->ps.velocity, body->s.pos.trDelta ); - } else { - body->s.pos.trType = TR_STATIONARY; - } - body->s.event = 0; - - // change the animation to the last-frame only, so the sequence - // doesn't repeat anew for the body - switch ( body->s.legsAnim & ~ANIM_TOGGLEBIT ) { - case BOTH_DEATH1: - case BOTH_DEAD1: - body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD1; - break; - case BOTH_DEATH2: - case BOTH_DEAD2: - body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD2; - break; - case BOTH_DEATH3: - case BOTH_DEAD3: - default: - body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD3; - break; - } - - body->r.svFlags = ent->r.svFlags; - VectorCopy (ent->r.mins, body->r.mins); - VectorCopy (ent->r.maxs, body->r.maxs); - VectorCopy (ent->r.absmin, body->r.absmin); - VectorCopy (ent->r.absmax, body->r.absmax); - - body->clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP; - body->r.contents = CONTENTS_CORPSE; - body->r.ownerNum = ent->s.number; - - body->nextthink = level.time + 5000; - body->think = BodySink; - - body->die = body_die; - - // don't take more damage if already gibbed - if ( ent->health <= GIB_HEALTH ) { - body->takedamage = qfalse; - } else { - body->takedamage = qtrue; - } - - - VectorCopy ( body->s.pos.trBase, body->r.currentOrigin ); - trap_LinkEntity (body); -} - -//====================================================================== - - -/* -================== -SetClientViewAngle - -================== -*/ -void SetClientViewAngle( gentity_t *ent, vec3_t angle ) { - int i; - - // set the delta angle - for (i=0 ; i<3 ; i++) { - int cmdAngle; - - cmdAngle = ANGLE2SHORT(angle[i]); - ent->client->ps.delta_angles[i] = cmdAngle - ent->client->pers.cmd.angles[i]; - } - VectorCopy( angle, ent->s.angles ); - VectorCopy (ent->s.angles, ent->client->ps.viewangles); -} - -/* -================ -respawn -================ -*/ -void respawn( gentity_t *ent ) { - gentity_t *tent; - - CopyToBodyQue (ent); - ClientSpawn(ent); - - // add a teleportation effect - tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_IN ); - tent->s.clientNum = ent->s.clientNum; -} - -/* -================ -TeamCount - -Returns number of players on a team -================ -*/ -team_t TeamCount( int ignoreClientNum, int team ) { - int i; - int count = 0; - - for ( i = 0 ; i < level.maxclients ; i++ ) { - if ( i == ignoreClientNum ) { - continue; - } - if ( level.clients[i].pers.connected == CON_DISCONNECTED ) { - continue; - } - if ( level.clients[i].sess.sessionTeam == team ) { - count++; - } - } - - return count; -} - -/* -================ -TeamLeader - -Returns the client number of the team leader -================ -*/ -int TeamLeader( int team ) { - int i; - - for ( i = 0 ; i < level.maxclients ; i++ ) { - if ( level.clients[i].pers.connected == CON_DISCONNECTED ) { - continue; - } - if ( level.clients[i].sess.sessionTeam == team ) { - if ( level.clients[i].sess.teamLeader ) - return i; - } - } - - return -1; -} - - -/* -================ -PickTeam - -================ -*/ -team_t PickTeam( int ignoreClientNum ) { - int counts[TEAM_NUM_TEAMS]; - - counts[TEAM_BLUE] = TeamCount( ignoreClientNum, TEAM_BLUE ); - counts[TEAM_RED] = TeamCount( ignoreClientNum, TEAM_RED ); - - if ( counts[TEAM_BLUE] > counts[TEAM_RED] ) { - return TEAM_RED; - } - if ( counts[TEAM_RED] > counts[TEAM_BLUE] ) { - return TEAM_BLUE; - } - // equal team count, so join the team with the lowest score - if ( level.teamScores[TEAM_BLUE] > level.teamScores[TEAM_RED] ) { - return TEAM_RED; - } - return TEAM_BLUE; -} - -/* -=========== -ForceClientSkin - -Forces a client's skin (for teamplay) -=========== -*/ -/* -static void ForceClientSkin( gclient_t *client, char *model, const char *skin ) { - char *p; - - if ((p = Q_strrchr(model, '/')) != 0) { - *p = 0; - } - - Q_strcat(model, MAX_QPATH, "/"); - Q_strcat(model, MAX_QPATH, skin); -} -*/ - -/* -=========== -ClientCheckName -============ -*/ -static void ClientCleanName( const char *in, char *out, int outSize ) { - int len, colorlessLen; - char ch; - char *p; - int spaces; - - //save room for trailing null byte - outSize--; - - len = 0; - colorlessLen = 0; - p = out; - *p = 0; - spaces = 0; - - while( 1 ) { - ch = *in++; - if( !ch ) { - break; - } - - // don't allow leading spaces - if( !*p && ch == ' ' ) { - continue; - } - - // check colors - if( ch == Q_COLOR_ESCAPE ) { - // solo trailing carat is not a color prefix - if( !*in ) { - break; - } - - // don't allow black in a name, period - if( ColorIndex(*in) == 0 ) { - in++; - continue; - } - - // make sure room in dest for both chars - if( len > outSize - 2 ) { - break; - } - - *out++ = ch; - *out++ = *in++; - len += 2; - continue; - } - - // don't allow too many consecutive spaces - if( ch == ' ' ) { - spaces++; - if( spaces > 3 ) { - continue; - } - } - else { - spaces = 0; - } - - if( len > outSize - 1 ) { - break; - } - - *out++ = ch; - colorlessLen++; - len++; - } - *out = 0; - - // don't allow empty names - if( *p == 0 || colorlessLen == 0 ) { - Q_strncpyz( p, "UnnamedPlayer", outSize ); - } -} - - -/* -=========== -ClientUserInfoChanged - -Called from ClientConnect when the player first connects and -directly by the server system when the player updates a userinfo variable. - -The game can override any of the settings and call trap_SetUserinfo -if desired. -============ -*/ -void ClientUserinfoChanged( int clientNum ) { - gentity_t *ent; - int teamTask, teamLeader, team, health; - char *s; - char model[MAX_QPATH]; - char headModel[MAX_QPATH]; - char oldname[MAX_STRING_CHARS]; - gclient_t *client; - char c1[MAX_INFO_STRING]; - char c2[MAX_INFO_STRING]; - char redTeam[MAX_INFO_STRING]; - char blueTeam[MAX_INFO_STRING]; - char userinfo[MAX_INFO_STRING]; - - ent = g_entities + clientNum; - client = ent->client; - - trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); - - // check for malformed or illegal info strings - if ( !Info_Validate(userinfo) ) { - strcpy (userinfo, "\\name\\badinfo"); - } - - // check for local client - s = Info_ValueForKey( userinfo, "ip" ); - if ( !strcmp( s, "localhost" ) ) { - client->pers.localClient = qtrue; - } - - // check the item prediction - s = Info_ValueForKey( userinfo, "cg_predictItems" ); - if ( !atoi( s ) ) { - client->pers.predictItemPickup = qfalse; - } else { - client->pers.predictItemPickup = qtrue; - } - - // set name - Q_strncpyz ( oldname, client->pers.netname, sizeof( oldname ) ); - s = Info_ValueForKey (userinfo, "name"); - ClientCleanName( s, client->pers.netname, sizeof(client->pers.netname) ); - - if ( client->sess.sessionTeam == TEAM_SPECTATOR ) { - if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) { - Q_strncpyz( client->pers.netname, "scoreboard", sizeof(client->pers.netname) ); - } - } - - if ( client->pers.connected == CON_CONNECTED ) { - if ( strcmp( oldname, client->pers.netname ) ) { - trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " renamed to %s\n\"", oldname, - client->pers.netname) ); - } - } - - // set max health -#ifdef MISSIONPACK - if (client->ps.powerups[PW_GUARD]) { - client->pers.maxHealth = 200; - } else { - health = atoi( Info_ValueForKey( userinfo, "handicap" ) ); - client->pers.maxHealth = health; - if ( client->pers.maxHealth < 1 || client->pers.maxHealth > 100 ) { - client->pers.maxHealth = 100; - } - } -#else - health = atoi( Info_ValueForKey( userinfo, "handicap" ) ); - client->pers.maxHealth = health; - if ( client->pers.maxHealth < 1 || client->pers.maxHealth > 100 ) { - client->pers.maxHealth = 100; - } -#endif - client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; - - // set model - if( g_gametype.integer >= GT_TEAM ) { - Q_strncpyz( model, Info_ValueForKey (userinfo, "team_model"), sizeof( model ) ); - Q_strncpyz( headModel, Info_ValueForKey (userinfo, "team_headmodel"), sizeof( headModel ) ); - } else { - Q_strncpyz( model, Info_ValueForKey (userinfo, "model"), sizeof( model ) ); - Q_strncpyz( headModel, Info_ValueForKey (userinfo, "headmodel"), sizeof( headModel ) ); - } - - // bots set their team a few frames later - if (g_gametype.integer >= GT_TEAM && g_entities[clientNum].r.svFlags & SVF_BOT) { - s = Info_ValueForKey( userinfo, "team" ); - if ( !Q_stricmp( s, "red" ) || !Q_stricmp( s, "r" ) ) { - team = TEAM_RED; - } else if ( !Q_stricmp( s, "blue" ) || !Q_stricmp( s, "b" ) ) { - team = TEAM_BLUE; - } else { - // pick the team with the least number of players - team = PickTeam( clientNum ); - } - } - else { - team = client->sess.sessionTeam; - } - -/* NOTE: all client side now - - // team - switch( team ) { - case TEAM_RED: - ForceClientSkin(client, model, "red"); -// ForceClientSkin(client, headModel, "red"); - break; - case TEAM_BLUE: - ForceClientSkin(client, model, "blue"); -// ForceClientSkin(client, headModel, "blue"); - break; - } - // don't ever use a default skin in teamplay, it would just waste memory - // however bots will always join a team but they spawn in as spectator - if ( g_gametype.integer >= GT_TEAM && team == TEAM_SPECTATOR) { - ForceClientSkin(client, model, "red"); -// ForceClientSkin(client, headModel, "red"); - } -*/ - -#ifdef MISSIONPACK - if (g_gametype.integer >= GT_TEAM) { - client->pers.teamInfo = qtrue; - } else { - s = Info_ValueForKey( userinfo, "teamoverlay" ); - if ( ! *s || atoi( s ) != 0 ) { - client->pers.teamInfo = qtrue; - } else { - client->pers.teamInfo = qfalse; - } - } -#else - // teamInfo - s = Info_ValueForKey( userinfo, "teamoverlay" ); - if ( ! *s || atoi( s ) != 0 ) { - client->pers.teamInfo = qtrue; - } else { - client->pers.teamInfo = qfalse; - } -#endif - /* - s = Info_ValueForKey( userinfo, "cg_pmove_fixed" ); - if ( !*s || atoi( s ) == 0 ) { - client->pers.pmoveFixed = qfalse; - } - else { - client->pers.pmoveFixed = qtrue; - } - */ - - // team task (0 = none, 1 = offence, 2 = defence) - teamTask = atoi(Info_ValueForKey(userinfo, "teamtask")); - // team Leader (1 = leader, 0 is normal player) - teamLeader = client->sess.teamLeader; - - // colors - strcpy(c1, Info_ValueForKey( userinfo, "color1" )); - strcpy(c2, Info_ValueForKey( userinfo, "color2" )); - - strcpy(redTeam, Info_ValueForKey( userinfo, "g_redteam" )); - strcpy(blueTeam, Info_ValueForKey( userinfo, "g_blueteam" )); - - // send over a subset of the userinfo keys so other clients can - // print scoreboards, display models, and play custom sounds - if ( ent->r.svFlags & SVF_BOT ) { - s = va("n\\%s\\t\\%i\\model\\%s\\hmodel\\%s\\c1\\%s\\c2\\%s\\hc\\%i\\w\\%i\\l\\%i\\skill\\%s\\tt\\%d\\tl\\%d", - client->pers.netname, team, model, headModel, c1, c2, - client->pers.maxHealth, client->sess.wins, client->sess.losses, - Info_ValueForKey( userinfo, "skill" ), teamTask, teamLeader ); - } else { - s = va("n\\%s\\t\\%i\\model\\%s\\hmodel\\%s\\g_redteam\\%s\\g_blueteam\\%s\\c1\\%s\\c2\\%s\\hc\\%i\\w\\%i\\l\\%i\\tt\\%d\\tl\\%d", - client->pers.netname, client->sess.sessionTeam, model, headModel, redTeam, blueTeam, c1, c2, - client->pers.maxHealth, client->sess.wins, client->sess.losses, teamTask, teamLeader); - } - - trap_SetConfigstring( CS_PLAYERS+clientNum, s ); - - // this is not the userinfo, more like the configstring actually - G_LogPrintf( "ClientUserinfoChanged: %i %s\n", clientNum, s ); -} - - -/* -=========== -ClientConnect - -Called when a player begins connecting to the server. -Called again for every map change or tournement restart. - -The session information will be valid after exit. - -Return NULL if the client should be allowed, otherwise return -a string with the reason for denial. - -Otherwise, the client will be sent the current gamestate -and will eventually get to ClientBegin. - -firstTime will be qtrue the very first time a client connects -to the server machine, but qfalse on map changes and tournement -restarts. -============ -*/ -char *ClientConnect( int clientNum, qboolean firstTime, qboolean isBot ) { - char *value; -// char *areabits; - gclient_t *client; - char userinfo[MAX_INFO_STRING]; - gentity_t *ent; - - ent = &g_entities[ clientNum ]; - - trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); - - // IP filtering - // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=500 - // recommanding PB based IP / GUID banning, the builtin system is pretty limited - // check to see if they are on the banned IP list - value = Info_ValueForKey (userinfo, "ip"); - if ( G_FilterPacket( value ) ) { - return "You are banned from this server."; - } - - // we don't check password for bots and local client - // NOTE: local client <-> "ip" "localhost" - // this means this client is not running in our current process - if ( !( ent->r.svFlags & SVF_BOT ) && (strcmp(value, "localhost") != 0)) { - // check for a password - value = Info_ValueForKey (userinfo, "password"); - if ( g_password.string[0] && Q_stricmp( g_password.string, "none" ) && - strcmp( g_password.string, value) != 0) { - return "Invalid password"; - } - } - - // they can connect - ent->client = level.clients + clientNum; - client = ent->client; - -// areabits = client->areabits; - - memset( client, 0, sizeof(*client) ); - - client->pers.connected = CON_CONNECTING; - - // read or initialize the session data - if ( firstTime || level.newSession ) { - G_InitSessionData( client, userinfo ); - } - G_ReadSessionData( client ); - - if( isBot ) { - ent->r.svFlags |= SVF_BOT; - ent->inuse = qtrue; - if( !G_BotConnect( clientNum, !firstTime ) ) { - return "BotConnectfailed"; - } - } - - // get and distribute relevent paramters - G_LogPrintf( "ClientConnect: %i\n", clientNum ); - ClientUserinfoChanged( clientNum ); - - // don't do the "xxx connected" messages if they were caried over from previous level - if ( firstTime ) { - trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " connected\n\"", client->pers.netname) ); - } - - if ( g_gametype.integer >= GT_TEAM && - client->sess.sessionTeam != TEAM_SPECTATOR ) { - BroadcastTeamChange( client, -1 ); - } - - // count current clients and rank for scoreboard - CalculateRanks(); - - // for statistics -// client->areabits = areabits; -// if ( !client->areabits ) -// client->areabits = G_Alloc( (trap_AAS_PointReachabilityAreaIndex( NULL ) + 7) / 8 ); - - 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 ) { - gentity_t *ent; - gclient_t *client; - gentity_t *tent; - int flags; - - ent = g_entities + clientNum; - - client = level.clients + clientNum; - - if ( ent->r.linked ) { - trap_UnlinkEntity( ent ); - } - G_InitGentity( ent ); - ent->touch = 0; - ent->pain = 0; - ent->client = client; - - client->pers.connected = CON_CONNECTED; - client->pers.enterTime = level.time; - client->pers.teamState.state = TEAM_BEGIN; - - // save eflags around this, because changing teams will - // cause this to happen with a valid entity, and we - // want to make sure the teleport bit is set right - // so the viewpoint doesn't interpolate through the - // world to the new position - flags = client->ps.eFlags; - memset( &client->ps, 0, sizeof( client->ps ) ); - client->ps.eFlags = flags; - - // locate ent at a spawn point - ClientSpawn( ent ); - - if ( client->sess.sessionTeam != TEAM_SPECTATOR ) { - // send event - tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_IN ); - tent->s.clientNum = ent->s.clientNum; - - if ( g_gametype.integer != GT_TOURNAMENT ) { - trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " entered the game\n\"", client->pers.netname) ); - } - } - G_LogPrintf( "ClientBegin: %i\n", clientNum ); - - // count current clients and rank for scoreboard - CalculateRanks(); -} - -/* -=========== -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 -============ -*/ -void ClientSpawn(gentity_t *ent) { - int index; - vec3_t spawn_origin, spawn_angles; - gclient_t *client; - int i; - clientPersistant_t saved; - clientSession_t savedSess; - int persistant[MAX_PERSISTANT]; - gentity_t *spawnPoint; - int flags; - int savedPing; -// char *savedAreaBits; - int accuracy_hits, accuracy_shots; - int eventSequence; - char userinfo[MAX_INFO_STRING]; - - index = ent - g_entities; - client = ent->client; - - // find a spawn point - // do it before setting health back up, so farthest - // ranging doesn't count this client - if ( client->sess.sessionTeam == TEAM_SPECTATOR ) { - spawnPoint = SelectSpectatorSpawnPoint ( - spawn_origin, spawn_angles); - } else if (g_gametype.integer >= GT_CTF ) { - // all base oriented team games use the CTF spawn points - spawnPoint = SelectCTFSpawnPoint ( - client->sess.sessionTeam, - client->pers.teamState.state, - spawn_origin, spawn_angles); - } else { - do { - // the first spawn should be at a good looking spot - if ( !client->pers.initialSpawn && client->pers.localClient ) { - client->pers.initialSpawn = qtrue; - spawnPoint = SelectInitialSpawnPoint( spawn_origin, spawn_angles ); - } else { - // don't spawn near existing origin if possible - spawnPoint = SelectSpawnPoint ( - client->ps.origin, - spawn_origin, spawn_angles); - } - - // Tim needs to prevent bots from spawning at the initial point - // on q3dm0... - if ( ( spawnPoint->flags & FL_NO_BOTS ) && ( ent->r.svFlags & SVF_BOT ) ) { - continue; // try again - } - // just to be symetric, we have a nohumans option... - if ( ( spawnPoint->flags & FL_NO_HUMANS ) && !( ent->r.svFlags & SVF_BOT ) ) { - continue; // try again - } - - break; - - } while ( 1 ); - } - client->pers.teamState.state = TEAM_ACTIVE; - - // always clear the kamikaze flag - ent->s.eFlags &= ~EF_KAMIKAZE; - - // toggle the teleport bit so the client knows to not lerp - // and never clear the voted flag - flags = ent->client->ps.eFlags & (EF_TELEPORT_BIT | EF_VOTED | EF_TEAMVOTED); - flags ^= EF_TELEPORT_BIT; - - // clear everything but the persistant data - - saved = client->pers; - savedSess = client->sess; - savedPing = client->ps.ping; -// savedAreaBits = client->areabits; - accuracy_hits = client->accuracy_hits; - accuracy_shots = client->accuracy_shots; - for ( i = 0 ; i < MAX_PERSISTANT ; i++ ) { - persistant[i] = client->ps.persistant[i]; - } - eventSequence = client->ps.eventSequence; - - memset (client, 0, sizeof(*client)); // bk FIXME: Com_Memset? - - client->pers = saved; - client->sess = savedSess; - client->ps.ping = savedPing; -// client->areabits = savedAreaBits; - client->accuracy_hits = accuracy_hits; - client->accuracy_shots = accuracy_shots; - client->lastkilled_client = -1; - - for ( i = 0 ; i < MAX_PERSISTANT ; i++ ) { - client->ps.persistant[i] = persistant[i]; - } - client->ps.eventSequence = eventSequence; - // increment the spawncount so the client will detect the respawn - client->ps.persistant[PERS_SPAWN_COUNT]++; - client->ps.persistant[PERS_TEAM] = client->sess.sessionTeam; - - client->airOutTime = level.time + 12000; - - trap_GetUserinfo( index, userinfo, sizeof(userinfo) ); - // set max health - client->pers.maxHealth = atoi( Info_ValueForKey( userinfo, "handicap" ) ); - if ( client->pers.maxHealth < 1 || client->pers.maxHealth > 100 ) { - client->pers.maxHealth = 100; - } - // clear entity values - client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; - client->ps.eFlags = flags; - - ent->s.groundEntityNum = ENTITYNUM_NONE; - ent->client = &level.clients[index]; - ent->takedamage = qtrue; - ent->inuse = qtrue; - ent->classname = "player"; - ent->r.contents = CONTENTS_BODY; - ent->clipmask = MASK_PLAYERSOLID; - ent->die = player_die; - ent->waterlevel = 0; - ent->watertype = 0; - ent->flags = 0; - - VectorCopy (playerMins, ent->r.mins); - VectorCopy (playerMaxs, ent->r.maxs); - - client->ps.clientNum = index; - - client->ps.stats[STAT_WEAPONS] = ( 1 << WP_MACHINEGUN ); - if ( g_gametype.integer == GT_TEAM ) { - client->ps.ammo[WP_MACHINEGUN] = 50; - } else { - client->ps.ammo[WP_MACHINEGUN] = 100; - } - - client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_GAUNTLET ); - client->ps.ammo[WP_GAUNTLET] = -1; - client->ps.ammo[WP_GRAPPLING_HOOK] = -1; - - // health will count down towards max_health - ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] + 25; - - G_SetOrigin( ent, spawn_origin ); - VectorCopy( spawn_origin, client->ps.origin ); - - // the respawned flag will be cleared after the attack and jump keys come up - client->ps.pm_flags |= PMF_RESPAWNED; - - trap_GetUsercmd( client - level.clients, &ent->client->pers.cmd ); - SetClientViewAngle( ent, spawn_angles ); - - if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { - - } else { - G_KillBox( ent ); - trap_LinkEntity (ent); - - // force the base weapon up - client->ps.weapon = WP_MACHINEGUN; - client->ps.weaponstate = WEAPON_READY; - - } - - // don't allow full run speed for a bit - client->ps.pm_flags |= PMF_TIME_KNOCKBACK; - client->ps.pm_time = 100; - - client->respawnTime = level.time; - client->inactivityTime = level.time + g_inactivity.integer * 1000; - client->latched_buttons = 0; - - // set default animations - client->ps.torsoAnim = TORSO_STAND; - client->ps.legsAnim = LEGS_IDLE; - - if ( level.intermissiontime ) { - MoveClientToIntermission( ent ); - } else { - // fire the targets of the spawn point - G_UseTargets( spawnPoint, ent ); - - // select the highest weapon number available, after any - // spawn given items have fired - client->ps.weapon = 1; - for ( i = WP_NUM_WEAPONS - 1 ; i > 0 ; i-- ) { - if ( client->ps.stats[STAT_WEAPONS] & ( 1 << i ) ) { - client->ps.weapon = i; - break; - } - } - } - - // run a client frame to drop exactly to the floor, - // initialize animations and other things - client->ps.commandTime = level.time - 100; - ent->client->pers.cmd.serverTime = level.time; - ClientThink( ent-g_entities ); - - // positively link the client, even if the command times are weird - if ( ent->client->sess.sessionTeam != TEAM_SPECTATOR ) { - BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); - VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); - trap_LinkEntity( ent ); - } - - // run the presend to set anything else - ClientEndFrame( ent ); - - // clear entity state values - BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); -} - - -/* -=========== -ClientDisconnect - -Called when a player drops from the server. -Will not be called between levels. - -This should NOT be called directly by any game logic, -call trap_DropClient(), which will call this and do -server system housekeeping. -============ -*/ -void ClientDisconnect( int clientNum ) { - gentity_t *ent; - gentity_t *tent; - int i; - - // cleanup if we are kicking a bot that - // hasn't spawned yet - G_RemoveQueuedBotBegin( clientNum ); - - ent = g_entities + clientNum; - if ( !ent->client ) { - return; - } - - // stop any following clients - for ( i = 0 ; i < level.maxclients ; i++ ) { - if ( level.clients[i].sess.sessionTeam == TEAM_SPECTATOR - && level.clients[i].sess.spectatorState == SPECTATOR_FOLLOW - && level.clients[i].sess.spectatorClient == clientNum ) { - StopFollowing( &g_entities[i] ); - } - } - - // send effect if they were completely connected - if ( ent->client->pers.connected == CON_CONNECTED - && ent->client->sess.sessionTeam != TEAM_SPECTATOR ) { - tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_OUT ); - tent->s.clientNum = ent->s.clientNum; - - // They don't get to take powerups with them! - // Especially important for stuff like CTF flags - TossClientItems( ent ); -#ifdef MISSIONPACK - TossClientPersistantPowerups( ent ); - if( g_gametype.integer == GT_HARVESTER ) { - TossClientCubes( ent ); - } -#endif - - } - - G_LogPrintf( "ClientDisconnect: %i\n", clientNum ); - - // if we are playing in tourney mode and losing, give a win to the other player - if ( (g_gametype.integer == GT_TOURNAMENT ) - && !level.intermissiontime - && !level.warmupTime && level.sortedClients[1] == clientNum ) { - level.clients[ level.sortedClients[0] ].sess.wins++; - ClientUserinfoChanged( level.sortedClients[0] ); - } - - trap_UnlinkEntity (ent); - ent->s.modelindex = 0; - ent->inuse = qfalse; - ent->classname = "disconnected"; - ent->client->pers.connected = CON_DISCONNECTED; - ent->client->ps.persistant[PERS_TEAM] = TEAM_FREE; - ent->client->sess.sessionTeam = TEAM_FREE; - - trap_SetConfigstring( CS_PLAYERS + clientNum, ""); - - CalculateRanks(); - - if ( ent->r.svFlags & SVF_BOT ) { - BotAIShutdownClient( clientNum, qfalse ); - } -} - - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +#include "g_local.h" + +// g_client.c -- client functions that don't happen every frame + +static vec3_t playerMins = {-15, -15, -24}; +static vec3_t playerMaxs = {15, 15, 32}; + +/*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) initial +potential spawning position for deathmatch games. +The first time a player enters the game, they will be at an 'initial' spot. +Targets will be fired when someone spawns in on them. +"nobots" will prevent bots from using this spot. +"nohumans" will prevent non-bots from using this spot. +*/ +void SP_info_player_deathmatch( gentity_t *ent ) { + int i; + + G_SpawnInt( "nobots", "0", &i); + if ( i ) { + ent->flags |= FL_NO_BOTS; + } + G_SpawnInt( "nohumans", "0", &i ); + if ( i ) { + ent->flags |= FL_NO_HUMANS; + } +} + +/*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32) +equivelant to info_player_deathmatch +*/ +void SP_info_player_start(gentity_t *ent) { + ent->classname = "info_player_deathmatch"; + SP_info_player_deathmatch( ent ); +} + +/*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32) +The intermission will be viewed from this point. Target an info_notnull for the view direction. +*/ +void SP_info_player_intermission( gentity_t *ent ) { + +} + + + +/* +======================================================================= + + SelectSpawnPoint + +======================================================================= +*/ + +/* +================ +SpotWouldTelefrag + +================ +*/ +qboolean SpotWouldTelefrag( gentity_t *spot ) { + int i, num; + int touch[MAX_GENTITIES]; + gentity_t *hit; + vec3_t mins, maxs; + + VectorAdd( spot->s.origin, playerMins, mins ); + VectorAdd( spot->s.origin, playerMaxs, maxs ); + num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + + for (i=0 ; iclient && hit->client->ps.stats[STAT_HEALTH] > 0 ) { + if ( hit->client) { + return qtrue; + } + + } + + return qfalse; +} + +/* +================ +SelectNearestDeathmatchSpawnPoint + +Find the spot that we DON'T want to use +================ +*/ +#define MAX_SPAWN_POINTS 128 +gentity_t *SelectNearestDeathmatchSpawnPoint( vec3_t from ) { + gentity_t *spot; + vec3_t delta; + float dist, nearestDist; + gentity_t *nearestSpot; + + nearestDist = 999999; + nearestSpot = NULL; + spot = NULL; + + while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { + + VectorSubtract( spot->s.origin, from, delta ); + dist = VectorLength( delta ); + if ( dist < nearestDist ) { + nearestDist = dist; + nearestSpot = spot; + } + } + + return nearestSpot; +} + + +/* +================ +SelectRandomDeathmatchSpawnPoint + +go to a random point that doesn't telefrag +================ +*/ +#define MAX_SPAWN_POINTS 128 +gentity_t *SelectRandomDeathmatchSpawnPoint( void ) { + gentity_t *spot; + int count; + int selection; + gentity_t *spots[MAX_SPAWN_POINTS]; + + count = 0; + spot = NULL; + + while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { + if ( SpotWouldTelefrag( spot ) ) { + continue; + } + spots[ count ] = spot; + count++; + } + + if ( !count ) { // no spots that won't telefrag + return G_Find( NULL, FOFS(classname), "info_player_deathmatch"); + } + + selection = rand() % count; + return spots[ selection ]; +} + +/* +=========== +SelectRandomFurthestSpawnPoint + +Chooses a player start, deathmatch start, etc +============ +*/ +gentity_t *SelectRandomFurthestSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles ) { + gentity_t *spot; + vec3_t delta; + float dist; + float list_dist[64]; + gentity_t *list_spot[64]; + int numSpots, rnd, i, j; + + numSpots = 0; + spot = NULL; + + while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { + if ( SpotWouldTelefrag( spot ) ) { + continue; + } + VectorSubtract( spot->s.origin, avoidPoint, delta ); + dist = VectorLength( delta ); + for (i = 0; i < numSpots; i++) { + if ( dist > list_dist[i] ) { + if ( numSpots >= 64 ) + numSpots = 64-1; + for (j = numSpots; j > i; j--) { + list_dist[j] = list_dist[j-1]; + list_spot[j] = list_spot[j-1]; + } + list_dist[i] = dist; + list_spot[i] = spot; + numSpots++; + if (numSpots > 64) + numSpots = 64; + break; + } + } + if (i >= numSpots && numSpots < 64) { + list_dist[numSpots] = dist; + list_spot[numSpots] = spot; + numSpots++; + } + } + if (!numSpots) { + spot = G_Find( NULL, FOFS(classname), "info_player_deathmatch"); + if (!spot) + G_Error( "Couldn't find a spawn point" ); + VectorCopy (spot->s.origin, origin); + origin[2] += 9; + VectorCopy (spot->s.angles, angles); + return spot; + } + + // select a random spot from the spawn points furthest away + rnd = random() * (numSpots / 2); + + VectorCopy (list_spot[rnd]->s.origin, origin); + origin[2] += 9; + VectorCopy (list_spot[rnd]->s.angles, angles); + + return list_spot[rnd]; +} + +/* +=========== +SelectSpawnPoint + +Chooses a player start, deathmatch start, etc +============ +*/ +gentity_t *SelectSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles ) { + return SelectRandomFurthestSpawnPoint( avoidPoint, origin, angles ); + + /* + gentity_t *spot; + gentity_t *nearestSpot; + + nearestSpot = SelectNearestDeathmatchSpawnPoint( avoidPoint ); + + spot = SelectRandomDeathmatchSpawnPoint ( ); + if ( spot == nearestSpot ) { + // roll again if it would be real close to point of death + spot = SelectRandomDeathmatchSpawnPoint ( ); + if ( spot == nearestSpot ) { + // last try + spot = SelectRandomDeathmatchSpawnPoint ( ); + } + } + + // find a single player start spot + if (!spot) { + G_Error( "Couldn't find a spawn point" ); + } + + VectorCopy (spot->s.origin, origin); + origin[2] += 9; + VectorCopy (spot->s.angles, angles); + + return spot; + */ +} + +/* +=========== +SelectInitialSpawnPoint + +Try to find a spawn point marked 'initial', otherwise +use normal spawn selection. +============ +*/ +gentity_t *SelectInitialSpawnPoint( vec3_t origin, vec3_t angles ) { + gentity_t *spot; + + spot = NULL; + while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { + if ( spot->spawnflags & 1 ) { + break; + } + } + + if ( !spot || SpotWouldTelefrag( spot ) ) { + return SelectSpawnPoint( vec3_origin, origin, angles ); + } + + VectorCopy (spot->s.origin, origin); + origin[2] += 9; + VectorCopy (spot->s.angles, angles); + + return spot; +} + +/* +=========== +SelectSpectatorSpawnPoint + +============ +*/ +gentity_t *SelectSpectatorSpawnPoint( vec3_t origin, vec3_t angles ) { + FindIntermissionPoint(); + + VectorCopy( level.intermission_origin, origin ); + VectorCopy( level.intermission_angle, angles ); + + return NULL; +} + +/* +======================================================================= + +BODYQUE + +======================================================================= +*/ + +/* +=============== +InitBodyQue +=============== +*/ +void InitBodyQue (void) { + int i; + gentity_t *ent; + + level.bodyQueIndex = 0; + for (i=0; iclassname = "bodyque"; + ent->neverFree = qtrue; + level.bodyQue[i] = ent; + } +} + +/* +============= +BodySink + +After sitting around for five seconds, fall into the ground and dissapear +============= +*/ +void BodySink( gentity_t *ent ) { + if ( level.time - ent->timestamp > 6500 ) { + // the body ques are never actually freed, they are just unlinked + trap_UnlinkEntity( ent ); + ent->physicsObject = qfalse; + return; + } + ent->nextthink = level.time + 100; + ent->s.pos.trBase[2] -= 1; +} + +/* +============= +CopyToBodyQue + +A player is respawning, so make an entity that looks +just like the existing corpse to leave behind. +============= +*/ +void CopyToBodyQue( gentity_t *ent ) { +#ifdef MISSIONPACK + gentity_t *e; + int i; +#endif + gentity_t *body; + int contents; + + trap_UnlinkEntity (ent); + + // if client is in a nodrop area, don't leave the body + contents = trap_PointContents( ent->s.origin, -1 ); + if ( contents & CONTENTS_NODROP ) { + return; + } + + // grab a body que and cycle to the next one + body = level.bodyQue[ level.bodyQueIndex ]; + level.bodyQueIndex = (level.bodyQueIndex + 1) % BODY_QUEUE_SIZE; + + trap_UnlinkEntity (body); + + body->s = ent->s; + body->s.eFlags = EF_DEAD; // clear EF_TALK, etc +#ifdef MISSIONPACK + if ( ent->s.eFlags & EF_KAMIKAZE ) { + body->s.eFlags |= EF_KAMIKAZE; + + // check if there is a kamikaze timer around for this owner + for (i = 0; i < MAX_GENTITIES; i++) { + e = &g_entities[i]; + if (!e->inuse) + continue; + if (e->activator != ent) + continue; + if (strcmp(e->classname, "kamikaze timer")) + continue; + e->activator = body; + break; + } + } +#endif + body->s.powerups = 0; // clear powerups + body->s.loopSound = 0; // clear lava burning + body->s.number = body - g_entities; + body->timestamp = level.time; + body->physicsObject = qtrue; + body->physicsBounce = 0; // don't bounce + if ( body->s.groundEntityNum == ENTITYNUM_NONE ) { + body->s.pos.trType = TR_GRAVITY; + body->s.pos.trTime = level.time; + VectorCopy( ent->client->ps.velocity, body->s.pos.trDelta ); + } else { + body->s.pos.trType = TR_STATIONARY; + } + body->s.event = 0; + + // change the animation to the last-frame only, so the sequence + // doesn't repeat anew for the body + switch ( body->s.legsAnim & ~ANIM_TOGGLEBIT ) { + case BOTH_DEATH1: + case BOTH_DEAD1: + body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD1; + break; + case BOTH_DEATH2: + case BOTH_DEAD2: + body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD2; + break; + case BOTH_DEATH3: + case BOTH_DEAD3: + default: + body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD3; + break; + } + + body->r.svFlags = ent->r.svFlags; + VectorCopy (ent->r.mins, body->r.mins); + VectorCopy (ent->r.maxs, body->r.maxs); + VectorCopy (ent->r.absmin, body->r.absmin); + VectorCopy (ent->r.absmax, body->r.absmax); + + body->clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP; + body->r.contents = CONTENTS_CORPSE; + body->r.ownerNum = ent->s.number; + + body->nextthink = level.time + 5000; + body->think = BodySink; + + body->die = body_die; + + // don't take more damage if already gibbed + if ( ent->health <= GIB_HEALTH ) { + body->takedamage = qfalse; + } else { + body->takedamage = qtrue; + } + + + VectorCopy ( body->s.pos.trBase, body->r.currentOrigin ); + trap_LinkEntity (body); +} + +//====================================================================== + + +/* +================== +SetClientViewAngle + +================== +*/ +void SetClientViewAngle( gentity_t *ent, vec3_t angle ) { + int i; + + // set the delta angle + for (i=0 ; i<3 ; i++) { + int cmdAngle; + + cmdAngle = ANGLE2SHORT(angle[i]); + ent->client->ps.delta_angles[i] = cmdAngle - ent->client->pers.cmd.angles[i]; + } + VectorCopy( angle, ent->s.angles ); + VectorCopy (ent->s.angles, ent->client->ps.viewangles); +} + +/* +================ +respawn +================ +*/ +void respawn( gentity_t *ent ) { + gentity_t *tent; + + CopyToBodyQue (ent); + ClientSpawn(ent); + + // add a teleportation effect + tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_IN ); + tent->s.clientNum = ent->s.clientNum; +} + +/* +================ +TeamCount + +Returns number of players on a team +================ +*/ +team_t TeamCount( int ignoreClientNum, int team ) { + int i; + int count = 0; + + for ( i = 0 ; i < level.maxclients ; i++ ) { + if ( i == ignoreClientNum ) { + continue; + } + if ( level.clients[i].pers.connected == CON_DISCONNECTED ) { + continue; + } + if ( level.clients[i].sess.sessionTeam == team ) { + count++; + } + } + + return count; +} + +/* +================ +TeamLeader + +Returns the client number of the team leader +================ +*/ +int TeamLeader( int team ) { + int i; + + for ( i = 0 ; i < level.maxclients ; i++ ) { + if ( level.clients[i].pers.connected == CON_DISCONNECTED ) { + continue; + } + if ( level.clients[i].sess.sessionTeam == team ) { + if ( level.clients[i].sess.teamLeader ) + return i; + } + } + + return -1; +} + + +/* +================ +PickTeam + +================ +*/ +team_t PickTeam( int ignoreClientNum ) { + int counts[TEAM_NUM_TEAMS]; + + counts[TEAM_BLUE] = TeamCount( ignoreClientNum, TEAM_BLUE ); + counts[TEAM_RED] = TeamCount( ignoreClientNum, TEAM_RED ); + + if ( counts[TEAM_BLUE] > counts[TEAM_RED] ) { + return TEAM_RED; + } + if ( counts[TEAM_RED] > counts[TEAM_BLUE] ) { + return TEAM_BLUE; + } + // equal team count, so join the team with the lowest score + if ( level.teamScores[TEAM_BLUE] > level.teamScores[TEAM_RED] ) { + return TEAM_RED; + } + return TEAM_BLUE; +} + +/* +=========== +ForceClientSkin + +Forces a client's skin (for teamplay) +=========== +*/ +/* +static void ForceClientSkin( gclient_t *client, char *model, const char *skin ) { + char *p; + + if ((p = Q_strrchr(model, '/')) != 0) { + *p = 0; + } + + Q_strcat(model, MAX_QPATH, "/"); + Q_strcat(model, MAX_QPATH, skin); +} +*/ + +/* +=========== +ClientCheckName +============ +*/ +static void ClientCleanName( const char *in, char *out, int outSize ) { + int len, colorlessLen; + char ch; + char *p; + int spaces; + + //save room for trailing null byte + outSize--; + + len = 0; + colorlessLen = 0; + p = out; + *p = 0; + spaces = 0; + + while( 1 ) { + ch = *in++; + if( !ch ) { + break; + } + + // don't allow leading spaces + if( !*p && ch == ' ' ) { + continue; + } + + // check colors + if( ch == Q_COLOR_ESCAPE ) { + // solo trailing carat is not a color prefix + if( !*in ) { + break; + } + + // don't allow black in a name, period + if( ColorIndex(*in) == 0 ) { + in++; + continue; + } + + // make sure room in dest for both chars + if( len > outSize - 2 ) { + break; + } + + *out++ = ch; + *out++ = *in++; + len += 2; + continue; + } + + // don't allow too many consecutive spaces + if( ch == ' ' ) { + spaces++; + if( spaces > 3 ) { + continue; + } + } + else { + spaces = 0; + } + + if( len > outSize - 1 ) { + break; + } + + *out++ = ch; + colorlessLen++; + len++; + } + *out = 0; + + // don't allow empty names + if( *p == 0 || colorlessLen == 0 ) { + Q_strncpyz( p, "UnnamedPlayer", outSize ); + } +} + + +/* +=========== +ClientUserInfoChanged + +Called from ClientConnect when the player first connects and +directly by the server system when the player updates a userinfo variable. + +The game can override any of the settings and call trap_SetUserinfo +if desired. +============ +*/ +void ClientUserinfoChanged( int clientNum ) { + gentity_t *ent; + int teamTask, teamLeader, team, health; + char *s; + char model[MAX_QPATH]; + char headModel[MAX_QPATH]; + char oldname[MAX_STRING_CHARS]; + gclient_t *client; + char c1[MAX_INFO_STRING]; + char c2[MAX_INFO_STRING]; + char redTeam[MAX_INFO_STRING]; + char blueTeam[MAX_INFO_STRING]; + char userinfo[MAX_INFO_STRING]; + + ent = g_entities + clientNum; + client = ent->client; + + trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); + + // check for malformed or illegal info strings + if ( !Info_Validate(userinfo) ) { + strcpy (userinfo, "\\name\\badinfo"); + } + + // check for local client + s = Info_ValueForKey( userinfo, "ip" ); + if ( !strcmp( s, "localhost" ) ) { + client->pers.localClient = qtrue; + } + + // check the item prediction + s = Info_ValueForKey( userinfo, "cg_predictItems" ); + if ( !atoi( s ) ) { + client->pers.predictItemPickup = qfalse; + } else { + client->pers.predictItemPickup = qtrue; + } + + // set name + Q_strncpyz ( oldname, client->pers.netname, sizeof( oldname ) ); + s = Info_ValueForKey (userinfo, "name"); + ClientCleanName( s, client->pers.netname, sizeof(client->pers.netname) ); + + if ( client->sess.sessionTeam == TEAM_SPECTATOR ) { + if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) { + Q_strncpyz( client->pers.netname, "scoreboard", sizeof(client->pers.netname) ); + } + } + + if ( client->pers.connected == CON_CONNECTED ) { + if ( strcmp( oldname, client->pers.netname ) ) { + trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " renamed to %s\n\"", oldname, + client->pers.netname) ); + } + } + + // set max health +#ifdef MISSIONPACK + if (client->ps.powerups[PW_GUARD]) { + client->pers.maxHealth = 200; + } else { + health = atoi( Info_ValueForKey( userinfo, "handicap" ) ); + client->pers.maxHealth = health; + if ( client->pers.maxHealth < 1 || client->pers.maxHealth > 100 ) { + client->pers.maxHealth = 100; + } + } +#else + health = atoi( Info_ValueForKey( userinfo, "handicap" ) ); + client->pers.maxHealth = health; + if ( client->pers.maxHealth < 1 || client->pers.maxHealth > 100 ) { + client->pers.maxHealth = 100; + } +#endif + client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; + + // set model + if( g_gametype.integer >= GT_TEAM ) { + Q_strncpyz( model, Info_ValueForKey (userinfo, "team_model"), sizeof( model ) ); + Q_strncpyz( headModel, Info_ValueForKey (userinfo, "team_headmodel"), sizeof( headModel ) ); + } else { + Q_strncpyz( model, Info_ValueForKey (userinfo, "model"), sizeof( model ) ); + Q_strncpyz( headModel, Info_ValueForKey (userinfo, "headmodel"), sizeof( headModel ) ); + } + + // bots set their team a few frames later + if (g_gametype.integer >= GT_TEAM && g_entities[clientNum].r.svFlags & SVF_BOT) { + s = Info_ValueForKey( userinfo, "team" ); + if ( !Q_stricmp( s, "red" ) || !Q_stricmp( s, "r" ) ) { + team = TEAM_RED; + } else if ( !Q_stricmp( s, "blue" ) || !Q_stricmp( s, "b" ) ) { + team = TEAM_BLUE; + } else { + // pick the team with the least number of players + team = PickTeam( clientNum ); + } + } + else { + team = client->sess.sessionTeam; + } + +/* NOTE: all client side now + + // team + switch( team ) { + case TEAM_RED: + ForceClientSkin(client, model, "red"); +// ForceClientSkin(client, headModel, "red"); + break; + case TEAM_BLUE: + ForceClientSkin(client, model, "blue"); +// ForceClientSkin(client, headModel, "blue"); + break; + } + // don't ever use a default skin in teamplay, it would just waste memory + // however bots will always join a team but they spawn in as spectator + if ( g_gametype.integer >= GT_TEAM && team == TEAM_SPECTATOR) { + ForceClientSkin(client, model, "red"); +// ForceClientSkin(client, headModel, "red"); + } +*/ + +#ifdef MISSIONPACK + if (g_gametype.integer >= GT_TEAM) { + client->pers.teamInfo = qtrue; + } else { + s = Info_ValueForKey( userinfo, "teamoverlay" ); + if ( ! *s || atoi( s ) != 0 ) { + client->pers.teamInfo = qtrue; + } else { + client->pers.teamInfo = qfalse; + } + } +#else + // teamInfo + s = Info_ValueForKey( userinfo, "teamoverlay" ); + if ( ! *s || atoi( s ) != 0 ) { + client->pers.teamInfo = qtrue; + } else { + client->pers.teamInfo = qfalse; + } +#endif + /* + s = Info_ValueForKey( userinfo, "cg_pmove_fixed" ); + if ( !*s || atoi( s ) == 0 ) { + client->pers.pmoveFixed = qfalse; + } + else { + client->pers.pmoveFixed = qtrue; + } + */ + + // team task (0 = none, 1 = offence, 2 = defence) + teamTask = atoi(Info_ValueForKey(userinfo, "teamtask")); + // team Leader (1 = leader, 0 is normal player) + teamLeader = client->sess.teamLeader; + + // colors + strcpy(c1, Info_ValueForKey( userinfo, "color1" )); + strcpy(c2, Info_ValueForKey( userinfo, "color2" )); + + strcpy(redTeam, Info_ValueForKey( userinfo, "g_redteam" )); + strcpy(blueTeam, Info_ValueForKey( userinfo, "g_blueteam" )); + + // send over a subset of the userinfo keys so other clients can + // print scoreboards, display models, and play custom sounds + if ( ent->r.svFlags & SVF_BOT ) { + s = va("n\\%s\\t\\%i\\model\\%s\\hmodel\\%s\\c1\\%s\\c2\\%s\\hc\\%i\\w\\%i\\l\\%i\\skill\\%s\\tt\\%d\\tl\\%d", + client->pers.netname, team, model, headModel, c1, c2, + client->pers.maxHealth, client->sess.wins, client->sess.losses, + Info_ValueForKey( userinfo, "skill" ), teamTask, teamLeader ); + } else { + s = va("n\\%s\\t\\%i\\model\\%s\\hmodel\\%s\\g_redteam\\%s\\g_blueteam\\%s\\c1\\%s\\c2\\%s\\hc\\%i\\w\\%i\\l\\%i\\tt\\%d\\tl\\%d", + client->pers.netname, client->sess.sessionTeam, model, headModel, redTeam, blueTeam, c1, c2, + client->pers.maxHealth, client->sess.wins, client->sess.losses, teamTask, teamLeader); + } + + trap_SetConfigstring( CS_PLAYERS+clientNum, s ); + + // this is not the userinfo, more like the configstring actually + G_LogPrintf( "ClientUserinfoChanged: %i %s\n", clientNum, s ); +} + + +/* +=========== +ClientConnect + +Called when a player begins connecting to the server. +Called again for every map change or tournement restart. + +The session information will be valid after exit. + +Return NULL if the client should be allowed, otherwise return +a string with the reason for denial. + +Otherwise, the client will be sent the current gamestate +and will eventually get to ClientBegin. + +firstTime will be qtrue the very first time a client connects +to the server machine, but qfalse on map changes and tournement +restarts. +============ +*/ +char *ClientConnect( int clientNum, qboolean firstTime, qboolean isBot ) { + char *value; +// char *areabits; + gclient_t *client; + char userinfo[MAX_INFO_STRING]; + gentity_t *ent; + + ent = &g_entities[ clientNum ]; + + trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); + + // IP filtering + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=500 + // recommanding PB based IP / GUID banning, the builtin system is pretty limited + // check to see if they are on the banned IP list + value = Info_ValueForKey (userinfo, "ip"); + if ( G_FilterPacket( value ) ) { + return "You are banned from this server."; + } + + // we don't check password for bots and local client + // NOTE: local client <-> "ip" "localhost" + // this means this client is not running in our current process + if ( !( ent->r.svFlags & SVF_BOT ) && (strcmp(value, "localhost") != 0)) { + // check for a password + value = Info_ValueForKey (userinfo, "password"); + if ( g_password.string[0] && Q_stricmp( g_password.string, "none" ) && + strcmp( g_password.string, value) != 0) { + return "Invalid password"; + } + } + + // they can connect + ent->client = level.clients + clientNum; + client = ent->client; + +// areabits = client->areabits; + + memset( client, 0, sizeof(*client) ); + + client->pers.connected = CON_CONNECTING; + + // read or initialize the session data + if ( firstTime || level.newSession ) { + G_InitSessionData( client, userinfo ); + } + G_ReadSessionData( client ); + + if( isBot ) { + ent->r.svFlags |= SVF_BOT; + ent->inuse = qtrue; + if( !G_BotConnect( clientNum, !firstTime ) ) { + return "BotConnectfailed"; + } + } + + // get and distribute relevent paramters + G_LogPrintf( "ClientConnect: %i\n", clientNum ); + ClientUserinfoChanged( clientNum ); + + // don't do the "xxx connected" messages if they were caried over from previous level + if ( firstTime ) { + trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " connected\n\"", client->pers.netname) ); + } + + if ( g_gametype.integer >= GT_TEAM && + client->sess.sessionTeam != TEAM_SPECTATOR ) { + BroadcastTeamChange( client, -1 ); + } + + // count current clients and rank for scoreboard + CalculateRanks(); + + // for statistics +// client->areabits = areabits; +// if ( !client->areabits ) +// client->areabits = G_Alloc( (trap_AAS_PointReachabilityAreaIndex( NULL ) + 7) / 8 ); + + 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 ) { + gentity_t *ent; + gclient_t *client; + gentity_t *tent; + int flags; + + ent = g_entities + clientNum; + + client = level.clients + clientNum; + + if ( ent->r.linked ) { + trap_UnlinkEntity( ent ); + } + G_InitGentity( ent ); + ent->touch = 0; + ent->pain = 0; + ent->client = client; + + client->pers.connected = CON_CONNECTED; + client->pers.enterTime = level.time; + client->pers.teamState.state = TEAM_BEGIN; + + // save eflags around this, because changing teams will + // cause this to happen with a valid entity, and we + // want to make sure the teleport bit is set right + // so the viewpoint doesn't interpolate through the + // world to the new position + flags = client->ps.eFlags; + memset( &client->ps, 0, sizeof( client->ps ) ); + client->ps.eFlags = flags; + + // locate ent at a spawn point + ClientSpawn( ent ); + + if ( client->sess.sessionTeam != TEAM_SPECTATOR ) { + // send event + tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_IN ); + tent->s.clientNum = ent->s.clientNum; + + if ( g_gametype.integer != GT_TOURNAMENT ) { + trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " entered the game\n\"", client->pers.netname) ); + } + } + G_LogPrintf( "ClientBegin: %i\n", clientNum ); + + // count current clients and rank for scoreboard + CalculateRanks(); +} + +/* +=========== +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 +============ +*/ +void ClientSpawn(gentity_t *ent) { + int index; + vec3_t spawn_origin, spawn_angles; + gclient_t *client; + int i; + clientPersistant_t saved; + clientSession_t savedSess; + int persistant[MAX_PERSISTANT]; + gentity_t *spawnPoint; + int flags; + int savedPing; +// char *savedAreaBits; + int accuracy_hits, accuracy_shots; + int eventSequence; + char userinfo[MAX_INFO_STRING]; + + index = ent - g_entities; + client = ent->client; + + // find a spawn point + // do it before setting health back up, so farthest + // ranging doesn't count this client + if ( client->sess.sessionTeam == TEAM_SPECTATOR ) { + spawnPoint = SelectSpectatorSpawnPoint ( + spawn_origin, spawn_angles); + } else if (g_gametype.integer >= GT_CTF ) { + // all base oriented team games use the CTF spawn points + spawnPoint = SelectCTFSpawnPoint ( + client->sess.sessionTeam, + client->pers.teamState.state, + spawn_origin, spawn_angles); + } else { + do { + // the first spawn should be at a good looking spot + if ( !client->pers.initialSpawn && client->pers.localClient ) { + client->pers.initialSpawn = qtrue; + spawnPoint = SelectInitialSpawnPoint( spawn_origin, spawn_angles ); + } else { + // don't spawn near existing origin if possible + spawnPoint = SelectSpawnPoint ( + client->ps.origin, + spawn_origin, spawn_angles); + } + + // Tim needs to prevent bots from spawning at the initial point + // on q3dm0... + if ( ( spawnPoint->flags & FL_NO_BOTS ) && ( ent->r.svFlags & SVF_BOT ) ) { + continue; // try again + } + // just to be symetric, we have a nohumans option... + if ( ( spawnPoint->flags & FL_NO_HUMANS ) && !( ent->r.svFlags & SVF_BOT ) ) { + continue; // try again + } + + break; + + } while ( 1 ); + } + client->pers.teamState.state = TEAM_ACTIVE; + + // always clear the kamikaze flag + ent->s.eFlags &= ~EF_KAMIKAZE; + + // toggle the teleport bit so the client knows to not lerp + // and never clear the voted flag + flags = ent->client->ps.eFlags & (EF_TELEPORT_BIT | EF_VOTED | EF_TEAMVOTED); + flags ^= EF_TELEPORT_BIT; + + // clear everything but the persistant data + + saved = client->pers; + savedSess = client->sess; + savedPing = client->ps.ping; +// savedAreaBits = client->areabits; + accuracy_hits = client->accuracy_hits; + accuracy_shots = client->accuracy_shots; + for ( i = 0 ; i < MAX_PERSISTANT ; i++ ) { + persistant[i] = client->ps.persistant[i]; + } + eventSequence = client->ps.eventSequence; + + memset (client, 0, sizeof(*client)); // bk FIXME: Com_Memset? + + client->pers = saved; + client->sess = savedSess; + client->ps.ping = savedPing; +// client->areabits = savedAreaBits; + client->accuracy_hits = accuracy_hits; + client->accuracy_shots = accuracy_shots; + client->lastkilled_client = -1; + + for ( i = 0 ; i < MAX_PERSISTANT ; i++ ) { + client->ps.persistant[i] = persistant[i]; + } + client->ps.eventSequence = eventSequence; + // increment the spawncount so the client will detect the respawn + client->ps.persistant[PERS_SPAWN_COUNT]++; + client->ps.persistant[PERS_TEAM] = client->sess.sessionTeam; + + client->airOutTime = level.time + 12000; + + trap_GetUserinfo( index, userinfo, sizeof(userinfo) ); + // set max health + client->pers.maxHealth = atoi( Info_ValueForKey( userinfo, "handicap" ) ); + if ( client->pers.maxHealth < 1 || client->pers.maxHealth > 100 ) { + client->pers.maxHealth = 100; + } + // clear entity values + client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; + client->ps.eFlags = flags; + + ent->s.groundEntityNum = ENTITYNUM_NONE; + ent->client = &level.clients[index]; + ent->takedamage = qtrue; + ent->inuse = qtrue; + ent->classname = "player"; + ent->r.contents = CONTENTS_BODY; + ent->clipmask = MASK_PLAYERSOLID; + ent->die = player_die; + ent->waterlevel = 0; + ent->watertype = 0; + ent->flags = 0; + + VectorCopy (playerMins, ent->r.mins); + VectorCopy (playerMaxs, ent->r.maxs); + + client->ps.clientNum = index; + + client->ps.stats[STAT_WEAPONS] = ( 1 << WP_MACHINEGUN ); + if ( g_gametype.integer == GT_TEAM ) { + client->ps.ammo[WP_MACHINEGUN] = 50; + } else { + client->ps.ammo[WP_MACHINEGUN] = 100; + } + + client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_GAUNTLET ); + client->ps.ammo[WP_GAUNTLET] = -1; + client->ps.ammo[WP_GRAPPLING_HOOK] = -1; + + // health will count down towards max_health + ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] + 25; + + G_SetOrigin( ent, spawn_origin ); + VectorCopy( spawn_origin, client->ps.origin ); + + // the respawned flag will be cleared after the attack and jump keys come up + client->ps.pm_flags |= PMF_RESPAWNED; + + trap_GetUsercmd( client - level.clients, &ent->client->pers.cmd ); + SetClientViewAngle( ent, spawn_angles ); + + if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { + + } else { + G_KillBox( ent ); + trap_LinkEntity (ent); + + // force the base weapon up + client->ps.weapon = WP_MACHINEGUN; + client->ps.weaponstate = WEAPON_READY; + + } + + // don't allow full run speed for a bit + client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + client->ps.pm_time = 100; + + client->respawnTime = level.time; + client->inactivityTime = level.time + g_inactivity.integer * 1000; + client->latched_buttons = 0; + + // set default animations + client->ps.torsoAnim = TORSO_STAND; + client->ps.legsAnim = LEGS_IDLE; + + if ( level.intermissiontime ) { + MoveClientToIntermission( ent ); + } else { + // fire the targets of the spawn point + G_UseTargets( spawnPoint, ent ); + + // select the highest weapon number available, after any + // spawn given items have fired + client->ps.weapon = 1; + for ( i = WP_NUM_WEAPONS - 1 ; i > 0 ; i-- ) { + if ( client->ps.stats[STAT_WEAPONS] & ( 1 << i ) ) { + client->ps.weapon = i; + break; + } + } + } + + // run a client frame to drop exactly to the floor, + // initialize animations and other things + client->ps.commandTime = level.time - 100; + ent->client->pers.cmd.serverTime = level.time; + ClientThink( ent-g_entities ); + + // positively link the client, even if the command times are weird + if ( ent->client->sess.sessionTeam != TEAM_SPECTATOR ) { + BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); + VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); + trap_LinkEntity( ent ); + } + + // run the presend to set anything else + ClientEndFrame( ent ); + + // clear entity state values + BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); +} + + +/* +=========== +ClientDisconnect + +Called when a player drops from the server. +Will not be called between levels. + +This should NOT be called directly by any game logic, +call trap_DropClient(), which will call this and do +server system housekeeping. +============ +*/ +void ClientDisconnect( int clientNum ) { + gentity_t *ent; + gentity_t *tent; + int i; + + // cleanup if we are kicking a bot that + // hasn't spawned yet + G_RemoveQueuedBotBegin( clientNum ); + + ent = g_entities + clientNum; + if ( !ent->client ) { + return; + } + + // stop any following clients + for ( i = 0 ; i < level.maxclients ; i++ ) { + if ( level.clients[i].sess.sessionTeam == TEAM_SPECTATOR + && level.clients[i].sess.spectatorState == SPECTATOR_FOLLOW + && level.clients[i].sess.spectatorClient == clientNum ) { + StopFollowing( &g_entities[i] ); + } + } + + // send effect if they were completely connected + if ( ent->client->pers.connected == CON_CONNECTED + && ent->client->sess.sessionTeam != TEAM_SPECTATOR ) { + tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_OUT ); + tent->s.clientNum = ent->s.clientNum; + + // They don't get to take powerups with them! + // Especially important for stuff like CTF flags + TossClientItems( ent ); +#ifdef MISSIONPACK + TossClientPersistantPowerups( ent ); + if( g_gametype.integer == GT_HARVESTER ) { + TossClientCubes( ent ); + } +#endif + + } + + G_LogPrintf( "ClientDisconnect: %i\n", clientNum ); + + // if we are playing in tourney mode and losing, give a win to the other player + if ( (g_gametype.integer == GT_TOURNAMENT ) + && !level.intermissiontime + && !level.warmupTime && level.sortedClients[1] == clientNum ) { + level.clients[ level.sortedClients[0] ].sess.wins++; + ClientUserinfoChanged( level.sortedClients[0] ); + } + + trap_UnlinkEntity (ent); + ent->s.modelindex = 0; + ent->inuse = qfalse; + ent->classname = "disconnected"; + ent->client->pers.connected = CON_DISCONNECTED; + ent->client->ps.persistant[PERS_TEAM] = TEAM_FREE; + ent->client->sess.sessionTeam = TEAM_FREE; + + trap_SetConfigstring( CS_PLAYERS + clientNum, ""); + + CalculateRanks(); + + if ( ent->r.svFlags & SVF_BOT ) { + BotAIShutdownClient( clientNum, qfalse ); + } +} + + diff --git a/code/game/g_cmds.c b/code/game/g_cmds.c index e72c80e..8773676 100755 --- a/code/game/g_cmds.c +++ b/code/game/g_cmds.c @@ -1,1701 +1,1701 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -#include "g_local.h" - -#include "../../ui/menudef.h" // for the voice chats - -/* -================== -DeathmatchScoreboardMessage - -================== -*/ -void DeathmatchScoreboardMessage( gentity_t *ent ) { - char entry[1024]; - char string[1400]; - int stringlength; - int i, j; - gclient_t *cl; - int numSorted, scoreFlags, accuracy, perfect; - - // send the latest information on all clients - string[0] = 0; - stringlength = 0; - scoreFlags = 0; - - numSorted = level.numConnectedClients; - - for (i=0 ; i < numSorted ; i++) { - int ping; - - cl = &level.clients[level.sortedClients[i]]; - - if ( cl->pers.connected == CON_CONNECTING ) { - ping = -1; - } else { - ping = cl->ps.ping < 999 ? cl->ps.ping : 999; - } - - if( cl->accuracy_shots ) { - accuracy = cl->accuracy_hits * 100 / cl->accuracy_shots; - } - else { - accuracy = 0; - } - perfect = ( cl->ps.persistant[PERS_RANK] == 0 && cl->ps.persistant[PERS_KILLED] == 0 ) ? 1 : 0; - - Com_sprintf (entry, sizeof(entry), - " %i %i %i %i %i %i %i %i %i %i %i %i %i %i", level.sortedClients[i], - cl->ps.persistant[PERS_SCORE], ping, (level.time - cl->pers.enterTime)/60000, - scoreFlags, g_entities[level.sortedClients[i]].s.powerups, accuracy, - cl->ps.persistant[PERS_IMPRESSIVE_COUNT], - cl->ps.persistant[PERS_EXCELLENT_COUNT], - cl->ps.persistant[PERS_GAUNTLET_FRAG_COUNT], - cl->ps.persistant[PERS_DEFEND_COUNT], - cl->ps.persistant[PERS_ASSIST_COUNT], - perfect, - cl->ps.persistant[PERS_CAPTURES]); - j = strlen(entry); - if (stringlength + j > 1024) - break; - strcpy (string + stringlength, entry); - stringlength += j; - } - - trap_SendServerCommand( ent-g_entities, va("scores %i %i %i%s", i, - level.teamScores[TEAM_RED], level.teamScores[TEAM_BLUE], - string ) ); -} - - -/* -================== -Cmd_Score_f - -Request current scoreboard information -================== -*/ -void Cmd_Score_f( gentity_t *ent ) { - DeathmatchScoreboardMessage( ent ); -} - - - -/* -================== -CheatsOk -================== -*/ -qboolean CheatsOk( gentity_t *ent ) { - if ( !g_cheats.integer ) { - trap_SendServerCommand( ent-g_entities, va("print \"Cheats are not enabled on this server.\n\"")); - return qfalse; - } - if ( ent->health <= 0 ) { - trap_SendServerCommand( ent-g_entities, va("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[MAX_STRING_CHARS]; - - len = 0; - c = trap_Argc(); - for ( i = start ; i < c ; i++ ) { - trap_Argv( i, arg, sizeof( arg ) ); - tlen = strlen( arg ); - if ( len + tlen >= MAX_STRING_CHARS - 1 ) { - break; - } - memcpy( line + len, arg, tlen ); - len += tlen; - if ( i != c - 1 ) { - line[len] = ' '; - len++; - } - } - - line[len] = 0; - - return line; -} - -/* -================== -SanitizeString - -Remove case and control characters -================== -*/ -void SanitizeString( char *in, char *out ) { - while ( *in ) { - if ( *in == 27 ) { - in += 2; // skip color code - continue; - } - if ( *in < 32 ) { - in++; - continue; - } - *out++ = tolower( *in++ ); - } - - *out = 0; -} - -/* -================== -ClientNumberFromString - -Returns a player number for either a number or name string -Returns -1 if invalid -================== -*/ -int ClientNumberFromString( gentity_t *to, char *s ) { - gclient_t *cl; - int idnum; - char s2[MAX_STRING_CHARS]; - char n2[MAX_STRING_CHARS]; - - // numeric values are just slot numbers - if (s[0] >= '0' && s[0] <= '9') { - idnum = atoi( s ); - if ( idnum < 0 || idnum >= level.maxclients ) { - trap_SendServerCommand( to-g_entities, va("print \"Bad client slot: %i\n\"", idnum)); - return -1; - } - - cl = &level.clients[idnum]; - if ( cl->pers.connected != CON_CONNECTED ) { - trap_SendServerCommand( to-g_entities, va("print \"Client %i is not active\n\"", idnum)); - return -1; - } - return idnum; - } - - // check for a name match - SanitizeString( s, s2 ); - for ( idnum=0,cl=level.clients ; idnum < level.maxclients ; idnum++,cl++ ) { - if ( cl->pers.connected != CON_CONNECTED ) { - continue; - } - SanitizeString( cl->pers.netname, n2 ); - if ( !strcmp( n2, s2 ) ) { - return idnum; - } - } - - trap_SendServerCommand( to-g_entities, va("print \"User %s is not on the server\n\"", s)); - return -1; -} - -/* -================== -Cmd_Give_f - -Give items to a client -================== -*/ -void Cmd_Give_f (gentity_t *ent) -{ - char *name; - gitem_t *it; - int i; - qboolean give_all; - gentity_t *it_ent; - trace_t trace; - - 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, "health") == 0) - { - ent->health = ent->client->ps.stats[STAT_MAX_HEALTH]; - if (!give_all) - return; - } - - if (give_all || Q_stricmp(name, "weapons") == 0) - { - ent->client->ps.stats[STAT_WEAPONS] = (1 << WP_NUM_WEAPONS) - 1 - - ( 1 << WP_GRAPPLING_HOOK ) - ( 1 << WP_NONE ); - if (!give_all) - return; - } - - if (give_all || Q_stricmp(name, "ammo") == 0) - { - for ( i = 0 ; i < MAX_WEAPONS ; i++ ) { - ent->client->ps.ammo[i] = 999; - } - if (!give_all) - return; - } - - if (give_all || Q_stricmp(name, "armor") == 0) - { - ent->client->ps.stats[STAT_ARMOR] = 200; - - if (!give_all) - return; - } - - if (Q_stricmp(name, "excellent") == 0) { - ent->client->ps.persistant[PERS_EXCELLENT_COUNT]++; - return; - } - if (Q_stricmp(name, "impressive") == 0) { - ent->client->ps.persistant[PERS_IMPRESSIVE_COUNT]++; - return; - } - if (Q_stricmp(name, "gauntletaward") == 0) { - ent->client->ps.persistant[PERS_GAUNTLET_FRAG_COUNT]++; - return; - } - if (Q_stricmp(name, "defend") == 0) { - ent->client->ps.persistant[PERS_DEFEND_COUNT]++; - return; - } - if (Q_stricmp(name, "assist") == 0) { - ent->client->ps.persistant[PERS_ASSIST_COUNT]++; - return; - } - - // spawn a specific item right on the player - if ( !give_all ) { - it = BG_FindItem (name); - if (!it) { - return; - } - - it_ent = G_Spawn(); - VectorCopy( ent->r.currentOrigin, it_ent->s.origin ); - it_ent->classname = it->classname; - G_SpawnItem (it_ent, it); - FinishSpawningItem(it_ent ); - memset( &trace, 0, sizeof( trace ) ); - Touch_Item (it_ent, ent, &trace); - if (it_ent->inuse) { - G_FreeEntity( it_ent ); - } - } -} - - -/* -================== -Cmd_God_f - -Sets client to godmode - -argv(0) god -================== -*/ -void Cmd_God_f (gentity_t *ent) -{ - char *msg; - - if ( !CheatsOk( ent ) ) { - return; - } - - ent->flags ^= FL_GODMODE; - if (!(ent->flags & FL_GODMODE) ) - msg = "godmode OFF\n"; - else - msg = "godmode ON\n"; - - trap_SendServerCommand( ent-g_entities, va("print \"%s\"", msg)); -} - - -/* -================== -Cmd_Notarget_f - -Sets client to notarget - -argv(0) notarget -================== -*/ -void Cmd_Notarget_f( gentity_t *ent ) { - char *msg; - - if ( !CheatsOk( ent ) ) { - return; - } - - ent->flags ^= FL_NOTARGET; - if (!(ent->flags & FL_NOTARGET) ) - msg = "notarget OFF\n"; - else - msg = "notarget ON\n"; - - trap_SendServerCommand( ent-g_entities, va("print \"%s\"", msg)); -} - - -/* -================== -Cmd_Noclip_f - -argv(0) noclip -================== -*/ -void Cmd_Noclip_f( gentity_t *ent ) { - char *msg; - - if ( !CheatsOk( ent ) ) { - return; - } - - if ( ent->client->noclip ) { - msg = "noclip OFF\n"; - } else { - msg = "noclip ON\n"; - } - ent->client->noclip = !ent->client->noclip; - - trap_SendServerCommand( ent-g_entities, va("print \"%s\"", msg)); -} - - -/* -================== -Cmd_LevelShot_f - -This is just to help generate the level pictures -for the menus. It goes to the intermission immediately -and sends over a command to the client to resize the view, -hide the scoreboard, and take a special screenshot -================== -*/ -void Cmd_LevelShot_f( gentity_t *ent ) { - if ( !CheatsOk( ent ) ) { - return; - } - - // doesn't work in single player - if ( g_gametype.integer != 0 ) { - trap_SendServerCommand( ent-g_entities, - "print \"Must be in g_gametype 0 for levelshot\n\"" ); - return; - } - - BeginIntermission(); - trap_SendServerCommand( ent-g_entities, "clientLevelShot" ); -} - - -/* -================== -Cmd_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_TeamTask_f( gentity_t *ent ) { - char userinfo[MAX_INFO_STRING]; - char arg[MAX_TOKEN_CHARS]; - int task; - int client = ent->client - level.clients; - - if ( trap_Argc() != 2 ) { - return; - } - trap_Argv( 1, arg, sizeof( arg ) ); - task = atoi( arg ); - - trap_GetUserinfo(client, userinfo, sizeof(userinfo)); - Info_SetValueForKey(userinfo, "teamtask", va("%d", task)); - trap_SetUserinfo(client, userinfo); - ClientUserinfoChanged(client); -} - - - -/* -================= -Cmd_Kill_f -================= -*/ -void Cmd_Kill_f( gentity_t *ent ) { - if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { - return; - } - if (ent->health <= 0) { - return; - } - ent->flags &= ~FL_GODMODE; - ent->client->ps.stats[STAT_HEALTH] = ent->health = -999; - player_die (ent, ent, ent, 100000, MOD_SUICIDE); -} - -/* -================= -BroadCastTeamChange - -Let everyone know about a team change -================= -*/ -void BroadcastTeamChange( gclient_t *client, int oldTeam ) -{ - if ( client->sess.sessionTeam == TEAM_RED ) { - trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " joined the red team.\n\"", - client->pers.netname) ); - } else if ( client->sess.sessionTeam == TEAM_BLUE ) { - trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " joined the blue team.\n\"", - client->pers.netname)); - } else if ( client->sess.sessionTeam == TEAM_SPECTATOR && oldTeam != TEAM_SPECTATOR ) { - trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " joined the spectators.\n\"", - client->pers.netname)); - } else if ( client->sess.sessionTeam == TEAM_FREE ) { - trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " joined the battle.\n\"", - client->pers.netname)); - } -} - -/* -================= -SetTeam -================= -*/ -void SetTeam( gentity_t *ent, char *s ) { - int team, oldTeam; - gclient_t *client; - int clientNum; - spectatorState_t specState; - int specClient; - int teamLeader; - - // - // see what change is requested - // - client = ent->client; - - clientNum = client - level.clients; - specClient = 0; - specState = SPECTATOR_NOT; - if ( !Q_stricmp( s, "scoreboard" ) || !Q_stricmp( s, "score" ) ) { - team = TEAM_SPECTATOR; - specState = SPECTATOR_SCOREBOARD; - } else if ( !Q_stricmp( s, "follow1" ) ) { - team = TEAM_SPECTATOR; - specState = SPECTATOR_FOLLOW; - specClient = -1; - } else if ( !Q_stricmp( s, "follow2" ) ) { - team = TEAM_SPECTATOR; - specState = SPECTATOR_FOLLOW; - specClient = -2; - } else if ( !Q_stricmp( s, "spectator" ) || !Q_stricmp( s, "s" ) ) { - team = TEAM_SPECTATOR; - specState = SPECTATOR_FREE; - } else if ( g_gametype.integer >= GT_TEAM ) { - // if running a team game, assign player to one of the teams - specState = SPECTATOR_NOT; - if ( !Q_stricmp( s, "red" ) || !Q_stricmp( s, "r" ) ) { - team = TEAM_RED; - } else if ( !Q_stricmp( s, "blue" ) || !Q_stricmp( s, "b" ) ) { - team = TEAM_BLUE; - } else { - // pick the team with the least number of players - team = PickTeam( clientNum ); - } - - if ( g_teamForceBalance.integer ) { - int counts[TEAM_NUM_TEAMS]; - - counts[TEAM_BLUE] = TeamCount( ent->client->ps.clientNum, TEAM_BLUE ); - counts[TEAM_RED] = TeamCount( ent->client->ps.clientNum, TEAM_RED ); - - // We allow a spread of two - if ( team == TEAM_RED && counts[TEAM_RED] - counts[TEAM_BLUE] > 1 ) { - trap_SendServerCommand( ent->client->ps.clientNum, - "cp \"Red team has too many players.\n\"" ); - return; // ignore the request - } - if ( team == TEAM_BLUE && counts[TEAM_BLUE] - counts[TEAM_RED] > 1 ) { - trap_SendServerCommand( ent->client->ps.clientNum, - "cp \"Blue team has too many players.\n\"" ); - return; // ignore the request - } - - // It's ok, the team we are switching to has less or same number of players - } - - } else { - // force them to spectators if there aren't any spots free - team = TEAM_FREE; - } - - // override decision if limiting the players - if ( (g_gametype.integer == GT_TOURNAMENT) - && level.numNonSpectatorClients >= 2 ) { - team = TEAM_SPECTATOR; - } else if ( g_maxGameClients.integer > 0 && - level.numNonSpectatorClients >= g_maxGameClients.integer ) { - team = TEAM_SPECTATOR; - } - - // - // decide if we will allow the change - // - oldTeam = client->sess.sessionTeam; - if ( team == oldTeam && team != TEAM_SPECTATOR ) { - return; - } - - // - // execute the team change - // - - // if the player was dead leave the body - if ( client->ps.stats[STAT_HEALTH] <= 0 ) { - CopyToBodyQue(ent); - } - - // he starts at 'base' - client->pers.teamState.state = TEAM_BEGIN; - if ( oldTeam != TEAM_SPECTATOR ) { - // Kill him (makes sure he loses flags, etc) - ent->flags &= ~FL_GODMODE; - ent->client->ps.stats[STAT_HEALTH] = ent->health = 0; - player_die (ent, ent, ent, 100000, MOD_SUICIDE); - - } - // they go to the end of the line for tournements - if ( team == TEAM_SPECTATOR ) { - client->sess.spectatorTime = level.time; - } - - client->sess.sessionTeam = team; - client->sess.spectatorState = specState; - client->sess.spectatorClient = specClient; - - client->sess.teamLeader = qfalse; - if ( team == TEAM_RED || team == TEAM_BLUE ) { - teamLeader = TeamLeader( team ); - // if there is no team leader or the team leader is a bot and this client is not a bot - if ( teamLeader == -1 || ( !(g_entities[clientNum].r.svFlags & SVF_BOT) && (g_entities[teamLeader].r.svFlags & SVF_BOT) ) ) { - SetLeader( team, clientNum ); - } - } - // make sure there is a team leader on the team the player came from - if ( oldTeam == TEAM_RED || oldTeam == TEAM_BLUE ) { - CheckTeamLeader( oldTeam ); - } - - BroadcastTeamChange( client, oldTeam ); - - // get and distribute relevent paramters - ClientUserinfoChanged( clientNum ); - - ClientBegin( clientNum ); -} - -/* -================= -StopFollowing - -If the client being followed leaves the game, or you just want to drop -to free floating spectator mode -================= -*/ -void StopFollowing( gentity_t *ent ) { - ent->client->ps.persistant[ PERS_TEAM ] = TEAM_SPECTATOR; - ent->client->sess.sessionTeam = TEAM_SPECTATOR; - ent->client->sess.spectatorState = SPECTATOR_FREE; - ent->client->ps.pm_flags &= ~PMF_FOLLOW; - ent->r.svFlags &= ~SVF_BOT; - ent->client->ps.clientNum = ent - g_entities; -} - -/* -================= -Cmd_Team_f -================= -*/ -void Cmd_Team_f( gentity_t *ent ) { - int oldTeam; - char s[MAX_TOKEN_CHARS]; - - if ( trap_Argc() != 2 ) { - oldTeam = ent->client->sess.sessionTeam; - switch ( oldTeam ) { - case TEAM_BLUE: - trap_SendServerCommand( ent-g_entities, "print \"Blue team\n\"" ); - break; - case TEAM_RED: - trap_SendServerCommand( ent-g_entities, "print \"Red team\n\"" ); - break; - case TEAM_FREE: - trap_SendServerCommand( ent-g_entities, "print \"Free team\n\"" ); - break; - case TEAM_SPECTATOR: - trap_SendServerCommand( ent-g_entities, "print \"Spectator team\n\"" ); - break; - } - return; - } - - if ( ent->client->switchTeamTime > level.time ) { - trap_SendServerCommand( ent-g_entities, "print \"May not switch teams more than once per 5 seconds.\n\"" ); - return; - } - - // if they are playing a tournement game, count as a loss - if ( (g_gametype.integer == GT_TOURNAMENT ) - && ent->client->sess.sessionTeam == TEAM_FREE ) { - ent->client->sess.losses++; - } - - trap_Argv( 1, s, sizeof( s ) ); - - SetTeam( ent, s ); - - ent->client->switchTeamTime = level.time + 5000; -} - - -/* -================= -Cmd_Follow_f -================= -*/ -void Cmd_Follow_f( gentity_t *ent ) { - int i; - char arg[MAX_TOKEN_CHARS]; - - if ( trap_Argc() != 2 ) { - if ( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) { - StopFollowing( ent ); - } - return; - } - - trap_Argv( 1, arg, sizeof( arg ) ); - i = ClientNumberFromString( ent, arg ); - if ( i == -1 ) { - return; - } - - // can't follow self - if ( &level.clients[ i ] == ent->client ) { - return; - } - - // can't follow another spectator - if ( level.clients[ i ].sess.sessionTeam == TEAM_SPECTATOR ) { - return; - } - - // if they are playing a tournement game, count as a loss - if ( (g_gametype.integer == GT_TOURNAMENT ) - && ent->client->sess.sessionTeam == TEAM_FREE ) { - ent->client->sess.losses++; - } - - // first set them to spectator - if ( ent->client->sess.sessionTeam != TEAM_SPECTATOR ) { - SetTeam( ent, "spectator" ); - } - - ent->client->sess.spectatorState = SPECTATOR_FOLLOW; - ent->client->sess.spectatorClient = i; -} - -/* -================= -Cmd_FollowCycle_f -================= -*/ -void Cmd_FollowCycle_f( gentity_t *ent, int dir ) { - int clientnum; - int original; - - // if they are playing a tournement game, count as a loss - if ( (g_gametype.integer == GT_TOURNAMENT ) - && ent->client->sess.sessionTeam == TEAM_FREE ) { - ent->client->sess.losses++; - } - // first set them to spectator - if ( ent->client->sess.spectatorState == SPECTATOR_NOT ) { - SetTeam( ent, "spectator" ); - } - - if ( dir != 1 && dir != -1 ) { - G_Error( "Cmd_FollowCycle_f: bad dir %i", dir ); - } - - clientnum = ent->client->sess.spectatorClient; - original = clientnum; - do { - clientnum += dir; - if ( clientnum >= level.maxclients ) { - clientnum = 0; - } - if ( clientnum < 0 ) { - clientnum = level.maxclients - 1; - } - - // can only follow connected clients - if ( level.clients[ clientnum ].pers.connected != CON_CONNECTED ) { - continue; - } - - // can't follow another spectator - if ( level.clients[ clientnum ].sess.sessionTeam == TEAM_SPECTATOR ) { - continue; - } - - // this is good, we can use it - ent->client->sess.spectatorClient = clientnum; - ent->client->sess.spectatorState = SPECTATOR_FOLLOW; - return; - } while ( clientnum != original ); - - // leave it where it was -} - - -/* -================== -G_Say -================== -*/ - -static void G_SayTo( gentity_t *ent, gentity_t *other, int mode, int color, const char *name, const char *message ) { - if (!other) { - return; - } - if (!other->inuse) { - return; - } - if (!other->client) { - return; - } - if ( other->client->pers.connected != CON_CONNECTED ) { - return; - } - if ( mode == SAY_TEAM && !OnSameTeam(ent, other) ) { - return; - } - // no chatting to players in tournements - if ( (g_gametype.integer == GT_TOURNAMENT ) - && other->client->sess.sessionTeam == TEAM_FREE - && ent->client->sess.sessionTeam != TEAM_FREE ) { - return; - } - - trap_SendServerCommand( other-g_entities, va("%s \"%s%c%c%s\"", - mode == SAY_TEAM ? "tchat" : "chat", - name, Q_COLOR_ESCAPE, color, message)); -} - -#define EC "\x19" - -void G_Say( gentity_t *ent, gentity_t *target, int mode, const char *chatText ) { - int j; - gentity_t *other; - int color; - char name[64]; - // don't let text be too long for malicious reasons - char text[MAX_SAY_TEXT]; - char location[64]; - - if ( g_gametype.integer < GT_TEAM && mode == SAY_TEAM ) { - mode = SAY_ALL; - } - - switch ( mode ) { - default: - case SAY_ALL: - G_LogPrintf( "say: %s: %s\n", ent->client->pers.netname, chatText ); - Com_sprintf (name, sizeof(name), "%s%c%c"EC": ", ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); - color = COLOR_GREEN; - break; - case SAY_TEAM: - G_LogPrintf( "sayteam: %s: %s\n", ent->client->pers.netname, chatText ); - if (Team_GetLocationMsg(ent, location, sizeof(location))) - Com_sprintf (name, sizeof(name), EC"(%s%c%c"EC") (%s)"EC": ", - ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE, location); - else - Com_sprintf (name, sizeof(name), EC"(%s%c%c"EC")"EC": ", - ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); - color = COLOR_CYAN; - break; - case SAY_TELL: - if (target && g_gametype.integer >= GT_TEAM && - target->client->sess.sessionTeam == ent->client->sess.sessionTeam && - Team_GetLocationMsg(ent, location, sizeof(location))) - Com_sprintf (name, sizeof(name), EC"[%s%c%c"EC"] (%s)"EC": ", ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE, location ); - else - Com_sprintf (name, sizeof(name), EC"[%s%c%c"EC"]"EC": ", ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); - color = COLOR_MAGENTA; - break; - } - - Q_strncpyz( text, chatText, sizeof(text) ); - - if ( target ) { - G_SayTo( ent, target, mode, color, name, text ); - return; - } - - // echo the text to the console - if ( g_dedicated.integer ) { - G_Printf( "%s%s\n", name, text); - } - - // send it to all the apropriate clients - for (j = 0; j < level.maxclients; j++) { - other = &g_entities[j]; - G_SayTo( ent, other, mode, color, name, text ); - } -} - - -/* -================== -Cmd_Say_f -================== -*/ -static void Cmd_Say_f( gentity_t *ent, int mode, qboolean arg0 ) { - char *p; - - if ( trap_Argc () < 2 && !arg0 ) { - return; - } - - if (arg0) - { - p = ConcatArgs( 0 ); - } - else - { - p = ConcatArgs( 1 ); - } - - G_Say( ent, NULL, mode, p ); -} - -/* -================== -Cmd_Tell_f -================== -*/ -static void Cmd_Tell_f( gentity_t *ent ) { - int targetNum; - gentity_t *target; - char *p; - char arg[MAX_TOKEN_CHARS]; - - if ( trap_Argc () < 2 ) { - return; - } - - trap_Argv( 1, arg, sizeof( arg ) ); - targetNum = atoi( arg ); - if ( targetNum < 0 || targetNum >= level.maxclients ) { - return; - } - - target = &g_entities[targetNum]; - if ( !target || !target->inuse || !target->client ) { - return; - } - - p = ConcatArgs( 2 ); - - G_LogPrintf( "tell: %s to %s: %s\n", ent->client->pers.netname, target->client->pers.netname, p ); - G_Say( ent, target, SAY_TELL, p ); - // don't tell to the player self if it was already directed to this player - // also don't send the chat back to a bot - if ( ent != target && !(ent->r.svFlags & SVF_BOT)) { - G_Say( ent, ent, SAY_TELL, p ); - } -} - - -static void G_VoiceTo( gentity_t *ent, gentity_t *other, int mode, const char *id, qboolean voiceonly ) { - int color; - char *cmd; - - if (!other) { - return; - } - if (!other->inuse) { - return; - } - if (!other->client) { - return; - } - if ( mode == SAY_TEAM && !OnSameTeam(ent, other) ) { - return; - } - // no chatting to players in tournements - if ( (g_gametype.integer == GT_TOURNAMENT )) { - return; - } - - if (mode == SAY_TEAM) { - color = COLOR_CYAN; - cmd = "vtchat"; - } - else if (mode == SAY_TELL) { - color = COLOR_MAGENTA; - cmd = "vtell"; - } - else { - color = COLOR_GREEN; - cmd = "vchat"; - } - - trap_SendServerCommand( other-g_entities, va("%s %d %d %d %s", cmd, voiceonly, ent->s.number, color, id)); -} - -void G_Voice( gentity_t *ent, gentity_t *target, int mode, const char *id, qboolean voiceonly ) { - int j; - gentity_t *other; - - if ( g_gametype.integer < GT_TEAM && mode == SAY_TEAM ) { - mode = SAY_ALL; - } - - if ( target ) { - G_VoiceTo( ent, target, mode, id, voiceonly ); - return; - } - - // echo the text to the console - if ( g_dedicated.integer ) { - G_Printf( "voice: %s %s\n", ent->client->pers.netname, id); - } - - // send it to all the apropriate clients - for (j = 0; j < level.maxclients; j++) { - other = &g_entities[j]; - G_VoiceTo( ent, other, mode, id, voiceonly ); - } -} - -/* -================== -Cmd_Voice_f -================== -*/ -static void Cmd_Voice_f( gentity_t *ent, int mode, qboolean arg0, qboolean voiceonly ) { - char *p; - - if ( trap_Argc () < 2 && !arg0 ) { - return; - } - - if (arg0) - { - p = ConcatArgs( 0 ); - } - else - { - p = ConcatArgs( 1 ); - } - - G_Voice( ent, NULL, mode, p, voiceonly ); -} - -/* -================== -Cmd_VoiceTell_f -================== -*/ -static void Cmd_VoiceTell_f( gentity_t *ent, qboolean voiceonly ) { - int targetNum; - gentity_t *target; - char *id; - char arg[MAX_TOKEN_CHARS]; - - if ( trap_Argc () < 2 ) { - return; - } - - trap_Argv( 1, arg, sizeof( arg ) ); - targetNum = atoi( arg ); - if ( targetNum < 0 || targetNum >= level.maxclients ) { - return; - } - - target = &g_entities[targetNum]; - if ( !target || !target->inuse || !target->client ) { - return; - } - - id = ConcatArgs( 2 ); - - G_LogPrintf( "vtell: %s to %s: %s\n", ent->client->pers.netname, target->client->pers.netname, id ); - G_Voice( ent, target, SAY_TELL, id, voiceonly ); - // don't tell to the player self if it was already directed to this player - // also don't send the chat back to a bot - if ( ent != target && !(ent->r.svFlags & SVF_BOT)) { - G_Voice( ent, ent, SAY_TELL, id, voiceonly ); - } -} - - -/* -================== -Cmd_VoiceTaunt_f -================== -*/ -static void Cmd_VoiceTaunt_f( gentity_t *ent ) { - gentity_t *who; - int i; - - if (!ent->client) { - return; - } - - // insult someone who just killed you - if (ent->enemy && ent->enemy->client && ent->enemy->client->lastkilled_client == ent->s.number) { - // i am a dead corpse - if (!(ent->enemy->r.svFlags & SVF_BOT)) { - G_Voice( ent, ent->enemy, SAY_TELL, VOICECHAT_DEATHINSULT, qfalse ); - } - if (!(ent->r.svFlags & SVF_BOT)) { - G_Voice( ent, ent, SAY_TELL, VOICECHAT_DEATHINSULT, qfalse ); - } - ent->enemy = NULL; - return; - } - // insult someone you just killed - if (ent->client->lastkilled_client >= 0 && ent->client->lastkilled_client != ent->s.number) { - who = g_entities + ent->client->lastkilled_client; - if (who->client) { - // who is the person I just killed - if (who->client->lasthurt_mod == MOD_GAUNTLET) { - if (!(who->r.svFlags & SVF_BOT)) { - G_Voice( ent, who, SAY_TELL, VOICECHAT_KILLGAUNTLET, qfalse ); // and I killed them with a gauntlet - } - if (!(ent->r.svFlags & SVF_BOT)) { - G_Voice( ent, ent, SAY_TELL, VOICECHAT_KILLGAUNTLET, qfalse ); - } - } else { - if (!(who->r.svFlags & SVF_BOT)) { - G_Voice( ent, who, SAY_TELL, VOICECHAT_KILLINSULT, qfalse ); // and I killed them with something else - } - if (!(ent->r.svFlags & SVF_BOT)) { - G_Voice( ent, ent, SAY_TELL, VOICECHAT_KILLINSULT, qfalse ); - } - } - ent->client->lastkilled_client = -1; - return; - } - } - - if (g_gametype.integer >= GT_TEAM) { - // praise a team mate who just got a reward - for(i = 0; i < MAX_CLIENTS; i++) { - who = g_entities + i; - if (who->client && who != ent && who->client->sess.sessionTeam == ent->client->sess.sessionTeam) { - if (who->client->rewardTime > level.time) { - if (!(who->r.svFlags & SVF_BOT)) { - G_Voice( ent, who, SAY_TELL, VOICECHAT_PRAISE, qfalse ); - } - if (!(ent->r.svFlags & SVF_BOT)) { - G_Voice( ent, ent, SAY_TELL, VOICECHAT_PRAISE, qfalse ); - } - return; - } - } - } - } - - // just say something - G_Voice( ent, NULL, SAY_ALL, VOICECHAT_TAUNT, qfalse ); -} - - - -static char *gc_orders[] = { - "hold your position", - "hold this position", - "come here", - "cover me", - "guard location", - "search and destroy", - "report" -}; - -void Cmd_GameCommand_f( gentity_t *ent ) { - int player; - int order; - char str[MAX_TOKEN_CHARS]; - - trap_Argv( 1, str, sizeof( str ) ); - player = atoi( str ); - trap_Argv( 2, str, sizeof( str ) ); - order = atoi( str ); - - if ( player < 0 || player >= MAX_CLIENTS ) { - return; - } - if ( order < 0 || order > sizeof(gc_orders)/sizeof(char *) ) { - return; - } - G_Say( ent, &g_entities[player], SAY_TELL, gc_orders[order] ); - G_Say( ent, ent, SAY_TELL, gc_orders[order] ); -} - -/* -================== -Cmd_Where_f -================== -*/ -void Cmd_Where_f( gentity_t *ent ) { - trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", vtos( ent->s.origin ) ) ); -} - -static const char *gameNames[] = { - "Free For All", - "Tournament", - "Single Player", - "Team Deathmatch", - "Capture the Flag", - "One Flag CTF", - "Overload", - "Harvester" -}; - -/* -================== -Cmd_CallVote_f -================== -*/ -void Cmd_CallVote_f( gentity_t *ent ) { - int i; - char arg1[MAX_STRING_TOKENS]; - char arg2[MAX_STRING_TOKENS]; - - if ( !g_allowVote.integer ) { - trap_SendServerCommand( ent-g_entities, "print \"Voting not allowed here.\n\"" ); - return; - } - - if ( level.voteTime ) { - trap_SendServerCommand( ent-g_entities, "print \"A vote is already in progress.\n\"" ); - return; - } - if ( ent->client->pers.voteCount >= MAX_VOTE_COUNT ) { - trap_SendServerCommand( ent-g_entities, "print \"You have called the maximum number of votes.\n\"" ); - return; - } - if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { - trap_SendServerCommand( ent-g_entities, "print \"Not allowed to call a vote as spectator.\n\"" ); - return; - } - - // make sure it is a valid command to vote on - trap_Argv( 1, arg1, sizeof( arg1 ) ); - trap_Argv( 2, arg2, sizeof( arg2 ) ); - - if( strchr( arg1, ';' ) || strchr( arg2, ';' ) ) { - trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string.\n\"" ); - return; - } - - if ( !Q_stricmp( arg1, "map_restart" ) ) { - } else if ( !Q_stricmp( arg1, "nextmap" ) ) { - } else if ( !Q_stricmp( arg1, "map" ) ) { - } else if ( !Q_stricmp( arg1, "g_gametype" ) ) { - } else if ( !Q_stricmp( arg1, "kick" ) ) { - } else if ( !Q_stricmp( arg1, "clientkick" ) ) { - } else if ( !Q_stricmp( arg1, "g_doWarmup" ) ) { - } else if ( !Q_stricmp( arg1, "timelimit" ) ) { - } else if ( !Q_stricmp( arg1, "fraglimit" ) ) { - } else { - trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string.\n\"" ); - trap_SendServerCommand( ent-g_entities, "print \"Vote commands are: map_restart, nextmap, map , g_gametype , kick , clientkick , g_doWarmup, timelimit