From 952c5c128f9efaea89d41d882c4ea3ade7df4591 Mon Sep 17 00:00:00 2001 From: zakk Date: Fri, 26 Aug 2005 04:48:05 +0000 Subject: Itsa me, quake3io! git-svn-id: svn://svn.icculus.org/quake3/trunk@2 edf5b092-35ff-0310-97b2-ce42778d08ea --- code/Construct | 431 + code/Makefile | 3 + code/botlib/aasfile.h | 267 + code/botlib/be_aas_bsp.h | 89 + code/botlib/be_aas_bspq3.c | 487 + code/botlib/be_aas_cluster.c | 1545 +++ code/botlib/be_aas_cluster.h | 38 + code/botlib/be_aas_debug.c | 777 ++ code/botlib/be_aas_debug.h | 62 + code/botlib/be_aas_def.h | 306 + code/botlib/be_aas_entity.c | 437 + code/botlib/be_aas_entity.h | 63 + code/botlib/be_aas_file.c | 582 + code/botlib/be_aas_file.h | 42 + code/botlib/be_aas_funcs.h | 47 + code/botlib/be_aas_main.c | 429 + code/botlib/be_aas_main.h | 61 + code/botlib/be_aas_move.c | 1101 ++ code/botlib/be_aas_move.h | 71 + code/botlib/be_aas_optimize.c | 312 + code/botlib/be_aas_optimize.h | 33 + code/botlib/be_aas_reach.c | 4547 +++++++ code/botlib/be_aas_reach.h | 68 + code/botlib/be_aas_route.c | 2209 ++++ code/botlib/be_aas_route.h | 67 + code/botlib/be_aas_routealt.c | 240 + code/botlib/be_aas_routealt.h | 40 + code/botlib/be_aas_sample.c | 1394 +++ code/botlib/be_aas_sample.h | 69 + code/botlib/be_ai_char.c | 790 ++ code/botlib/be_ai_chat.c | 3017 +++++ code/botlib/be_ai_gen.c | 134 + code/botlib/be_ai_goal.c | 1821 +++ code/botlib/be_ai_move.c | 3610 ++++++ code/botlib/be_ai_weap.c | 543 + code/botlib/be_ai_weight.c | 912 ++ code/botlib/be_ai_weight.h | 83 + code/botlib/be_ea.c | 508 + code/botlib/be_interface.c | 881 ++ code/botlib/be_interface.h | 57 + code/botlib/botlib.vcproj | 1559 +++ code/botlib/l_crc.c | 151 + code/botlib/l_crc.h | 29 + code/botlib/l_libvar.c | 294 + code/botlib/l_libvar.h | 63 + code/botlib/l_log.c | 169 + code/botlib/l_log.h | 46 + code/botlib/l_memory.c | 463 + code/botlib/l_memory.h | 76 + code/botlib/l_precomp.c | 3228 +++++ code/botlib/l_precomp.h | 180 + code/botlib/l_script.c | 1433 +++ code/botlib/l_script.h | 247 + code/botlib/l_struct.c | 462 + code/botlib/l_struct.h | 75 + code/botlib/l_utils.h | 35 + code/botlib/lcc.mak | 55 + code/botlib/linux-i386.mak | 92 + code/bspc/Conscript | 75 + code/bspc/Makefile | 114 + code/bspc/_files.c | 63 + code/bspc/aas_areamerging.c | 390 + code/bspc/aas_areamerging.h | 24 + code/bspc/aas_cfg.c | 252 + code/bspc/aas_cfg.h | 73 + code/bspc/aas_create.c | 1142 ++ code/bspc/aas_create.h | 136 + code/bspc/aas_edgemelting.c | 108 + code/bspc/aas_edgemelting.h | 24 + code/bspc/aas_facemerging.c | 282 + code/bspc/aas_facemerging.h | 24 + code/bspc/aas_file.c | 549 + code/bspc/aas_file.h | 25 + code/bspc/aas_gsubdiv.c | 656 ++ code/bspc/aas_gsubdiv.h | 25 + code/bspc/aas_map.c | 849 ++ code/bspc/aas_map.h | 23 + code/bspc/aas_prunenodes.c | 89 + code/bspc/aas_prunenodes.h | 24 + code/bspc/aas_store.c | 1082 ++ code/bspc/aas_store.h | 107 + code/bspc/aasfile.h | 252 + code/bspc/be_aas_bspc.c | 292 + code/bspc/be_aas_bspc.h | 23 + code/bspc/brushbsp.c | 1871 +++ code/bspc/bspc.c | 991 ++ code/bspc/bspc.sln | 28 + code/bspc/bspc.vcproj | 1381 +++ code/bspc/cfgq3.c | 84 + code/bspc/csg.c | 1005 ++ code/bspc/faces.c | 978 ++ code/bspc/gldraw.c | 232 + code/bspc/glfile.c | 149 + code/bspc/l_bsp_ent.c | 180 + code/bspc/l_bsp_ent.h | 58 + code/bspc/l_bsp_hl.c | 888 ++ code/bspc/l_bsp_hl.h | 314 + code/bspc/l_bsp_q1.c | 620 + code/bspc/l_bsp_q1.h | 275 + code/bspc/l_bsp_q2.c | 1134 ++ code/bspc/l_bsp_q2.h | 98 + code/bspc/l_bsp_q3.c | 824 ++ code/bspc/l_bsp_q3.h | 81 + code/bspc/l_bsp_sin.c | 1186 ++ code/bspc/l_bsp_sin.h | 106 + code/bspc/l_cmd.c | 1230 ++ code/bspc/l_cmd.h | 157 + code/bspc/l_log.c | 215 + code/bspc/l_log.h | 42 + code/bspc/l_math.c | 289 + code/bspc/l_math.h | 93 + code/bspc/l_mem.c | 441 + code/bspc/l_mem.h | 51 + code/bspc/l_poly.c | 1411 +++ code/bspc/l_poly.h | 120 + code/bspc/l_qfiles.c | 663 ++ code/bspc/l_qfiles.h | 91 + code/bspc/l_threads.c | 1510 +++ code/bspc/l_threads.h | 45 + code/bspc/l_utils.c | 259 + code/bspc/l_utils.h | 79 + code/bspc/lcc.mak | 61 + code/bspc/leakfile.c | 101 + code/bspc/linux-i386.mak | 109 + code/bspc/map.c | 1267 ++ code/bspc/map_hl.c | 1114 ++ code/bspc/map_q1.c | 1174 ++ code/bspc/map_q2.c | 1162 ++ code/bspc/map_q3.c | 681 ++ code/bspc/map_sin.c | 1211 ++ code/bspc/nodraw.c | 47 + code/bspc/portals.c | 1297 ++ code/bspc/prtfile.c | 287 + code/bspc/q2files.h | 487 + code/bspc/q3files.h | 374 + code/bspc/qbsp.h | 477 + code/bspc/qfiles.h | 487 + code/bspc/sinfiles.h | 365 + code/bspc/tetrahedron.c | 1389 +++ code/bspc/tetrahedron.h | 24 + code/bspc/textures.c | 228 + code/bspc/tree.c | 283 + code/bspc/writebsp.c | 595 + code/cgame.lnt | 25 + code/cgame/Conscript | 138 + code/cgame/cg_consolecmds.c | 578 + code/cgame/cg_draw.c | 2659 +++++ code/cgame/cg_drawtools.c | 824 ++ code/cgame/cg_effects.c | 718 ++ code/cgame/cg_ents.c | 1037 ++ code/cgame/cg_event.c | 1205 ++ code/cgame/cg_info.c | 297 + code/cgame/cg_local.h | 1669 +++ code/cgame/cg_localents.c | 886 ++ code/cgame/cg_main.c | 1997 ++++ code/cgame/cg_marks.c | 2274 ++++ code/cgame/cg_newdraw.c | 1852 +++ code/cgame/cg_particles.c | 2018 ++++ code/cgame/cg_players.c | 2619 ++++ code/cgame/cg_playerstate.c | 526 + code/cgame/cg_predict.c | 628 + code/cgame/cg_public.h | 238 + code/cgame/cg_scoreboard.c | 534 + code/cgame/cg_servercmds.c | 1098 ++ code/cgame/cg_snapshot.c | 403 + code/cgame/cg_syscalls.asm | 106 + code/cgame/cg_syscalls.c | 445 + code/cgame/cg_view.c | 876 ++ code/cgame/cg_weapons.c | 2277 ++++ code/cgame/cgame.bat | 63 + code/cgame/cgame.def | 3 + code/cgame/cgame.plg | 124 + code/cgame/cgame.q3asm | 26 + code/cgame/cgame.sh | 36 + code/cgame/cgame.vcproj | 1221 ++ code/cgame/cgame_ta.bat | 65 + code/cgame/cgame_ta.q3asm | 28 + code/cgame/cgame_ta.sh | 38 + code/cgame/tr_types.h | 229 + code/clean.bat | 61 + code/client/cl_cgame.c | 1029 ++ code/client/cl_cin.c | 1740 +++ code/client/cl_console.c | 786 ++ code/client/cl_input.c | 901 ++ code/client/cl_keys.c | 1252 ++ code/client/cl_main.c | 3324 ++++++ code/client/cl_net_chan.c | 167 + code/client/cl_parse.c | 655 + code/client/cl_scrn.c | 547 + code/client/cl_ui.c | 1200 ++ code/client/client.h | 519 + code/client/keys.h | 57 + code/client/snd_adpcm.c | 330 + code/client/snd_dma.c | 1636 +++ code/client/snd_local.h | 204 + code/client/snd_mem.c | 404 + code/client/snd_mix.c | 681 ++ code/client/snd_public.h | 72 + code/client/snd_wavelet.c | 253 + code/game.lnt | 35 + code/game/Conscript | 140 + code/game/ai_chat.c | 1226 ++ code/game/ai_chat.h | 61 + code/game/ai_cmd.c | 1992 ++++ code/game/ai_cmd.h | 37 + code/game/ai_dmnet.c | 2610 ++++ code/game/ai_dmnet.h | 61 + code/game/ai_dmq3.c | 5461 +++++++++ code/game/ai_dmq3.h | 206 + code/game/ai_main.c | 1695 +++ code/game/ai_main.h | 299 + code/game/ai_team.c | 2080 ++++ code/game/ai_team.h | 39 + code/game/ai_vcmd.c | 550 + code/game/ai_vcmd.h | 36 + code/game/be_aas.h | 221 + code/game/be_ai_char.h | 48 + code/game/be_ai_chat.h | 113 + code/game/be_ai_gen.h | 33 + code/game/be_ai_goal.h | 118 + code/game/be_ai_move.h | 142 + code/game/be_ai_weap.h | 104 + code/game/be_ea.h | 66 + code/game/bg_lib.c | 1324 +++ code/game/bg_lib.h | 91 + code/game/bg_local.h | 83 + code/game/bg_misc.c | 1604 +++ code/game/bg_pmove.c | 2069 ++++ code/game/bg_public.h | 738 ++ code/game/bg_slidemove.c | 325 + code/game/botlib.h | 516 + code/game/chars.h | 134 + code/game/g_active.c | 1191 ++ code/game/g_arenas.c | 376 + code/game/g_bot.c | 1017 ++ code/game/g_client.c | 1344 +++ code/game/g_cmds.c | 1701 +++ code/game/g_combat.c | 1195 ++ code/game/g_items.c | 1010 ++ code/game/g_local.h | 971 ++ code/game/g_main.c | 1832 +++ code/game/g_mem.c | 61 + code/game/g_misc.c | 482 + code/game/g_missile.c | 808 ++ code/game/g_mover.c | 1612 +++ code/game/g_public.h | 429 + code/game/g_rankings.c | 1135 ++ code/game/g_rankings.h | 396 + code/game/g_session.c | 193 + code/game/g_spawn.c | 643 + code/game/g_svcmds.c | 508 + code/game/g_syscalls.asm | 225 + code/game/g_syscalls.c | 790 ++ code/game/g_target.c | 467 + code/game/g_team.c | 1483 +++ code/game/g_team.h | 88 + code/game/g_trigger.c | 465 + code/game/g_utils.c | 666 ++ code/game/g_weapon.c | 1145 ++ code/game/game.bat | 85 + code/game/game.def | 3 + code/game/game.q3asm | 35 + code/game/game.sh | 48 + code/game/game.vcproj | 2132 ++++ code/game/game_ta.bat | 86 + code/game/game_ta.q3asm | 35 + code/game/game_ta.sh | 48 + code/game/inv.h | 166 + code/game/match.h | 134 + code/game/q_math.c | 1308 ++ code/game/q_shared.c | 1258 ++ code/game/q_shared.h | 1432 +++ code/game/surfaceflags.h | 80 + code/game/syn.h | 34 + code/installdebug.bat | 6 + code/installrelease.bat | 14 + code/installvms.bat | 7 + code/jpeg-6/README | 385 + code/jpeg-6/jcapimin.c | 228 + code/jpeg-6/jcapistd.c | 161 + code/jpeg-6/jccoefct.c | 448 + code/jpeg-6/jccolor.c | 459 + code/jpeg-6/jcdctmgr.c | 391 + code/jpeg-6/jchuff.c | 846 ++ code/jpeg-6/jchuff.h | 34 + code/jpeg-6/jcinit.c | 72 + code/jpeg-6/jcmainct.c | 296 + code/jpeg-6/jcmarker.c | 639 + code/jpeg-6/jcmaster.c | 578 + code/jpeg-6/jcomapi.c | 94 + code/jpeg-6/jconfig.h | 41 + code/jpeg-6/jcparam.c | 575 + code/jpeg-6/jcphuff.c | 829 ++ code/jpeg-6/jcprepct.c | 371 + code/jpeg-6/jcsample.c | 519 + code/jpeg-6/jctrans.c | 371 + code/jpeg-6/jdapimin.c | 398 + code/jpeg-6/jdapistd.c | 275 + code/jpeg-6/jdatadst.c | 151 + code/jpeg-6/jdatasrc.c | 204 + code/jpeg-6/jdcoefct.c | 725 ++ code/jpeg-6/jdcolor.c | 367 + code/jpeg-6/jdct.h | 176 + code/jpeg-6/jddctmgr.c | 270 + code/jpeg-6/jdhuff.c | 574 + code/jpeg-6/jdhuff.h | 202 + code/jpeg-6/jdinput.c | 381 + code/jpeg-6/jdmainct.c | 520 + code/jpeg-6/jdmarker.c | 1052 ++ code/jpeg-6/jdmaster.c | 557 + code/jpeg-6/jdmerge.c | 400 + code/jpeg-6/jdphuff.c | 642 + code/jpeg-6/jdpostct.c | 290 + code/jpeg-6/jdsample.c | 478 + code/jpeg-6/jdtrans.c | 122 + code/jpeg-6/jerror.c | 232 + code/jpeg-6/jerror.h | 273 + code/jpeg-6/jfdctflt.c | 168 + code/jpeg-6/jfdctfst.c | 224 + code/jpeg-6/jfdctint.c | 283 + code/jpeg-6/jidctflt.c | 241 + code/jpeg-6/jidctfst.c | 367 + code/jpeg-6/jidctint.c | 388 + code/jpeg-6/jidctred.c | 397 + code/jpeg-6/jinclude.h | 116 + code/jpeg-6/jload.c | 145 + code/jpeg-6/jmemansi.c | 167 + code/jpeg-6/jmemdos.c | 634 + code/jpeg-6/jmemmgr.c | 1115 ++ code/jpeg-6/jmemname.c | 271 + code/jpeg-6/jmemnobs.c | 105 + code/jpeg-6/jmemsys.h | 182 + code/jpeg-6/jmorecfg.h | 348 + code/jpeg-6/jpegint.h | 388 + code/jpeg-6/jpeglib.h | 1051 ++ code/jpeg-6/jpegtran.c | 370 + code/jpeg-6/jquant1.c | 856 ++ code/jpeg-6/jquant2.c | 1310 ++ code/jpeg-6/jutils.c | 175 + code/jpeg-6/jversion.h | 14 + code/macosx/BuildRelease | 17 + code/macosx/CGMouseDeltaFix.h | 27 + code/macosx/CGMouseDeltaFix.m | 131 + code/macosx/CGPrivateAPI.h | 185 + code/macosx/GenerateQGL.pl | 146 + code/macosx/Performance.rtf | 114 + code/macosx/Q3Controller.h | 40 + code/macosx/Q3Controller.m | 435 + code/macosx/Quake3.icns | Bin 0 -> 35224 bytes code/macosx/Quake3.nib/classes.nib | 18 + code/macosx/Quake3.nib/info.nib | 19 + code/macosx/Quake3.nib/objects.nib | Bin 0 -> 765 bytes code/macosx/Quake3.pbproj/apple.pbxuser | 563 + code/macosx/Quake3.pbproj/project.pbxproj | 11802 +++++++++++++++++++ code/macosx/RecordDemo.zsh | 9 + code/macosx/banner.jpg | Bin 0 -> 55841 bytes code/macosx/botlib.log | 196 + code/macosx/macosx_display.h | 38 + code/macosx/macosx_display.m | 373 + code/macosx/macosx_glimp.h | 37 + code/macosx/macosx_glimp.m | 1114 ++ code/macosx/macosx_glsmp_mutex.m | 176 + code/macosx/macosx_glsmp_null.m | 46 + code/macosx/macosx_glsmp_ports.m | 425 + code/macosx/macosx_input.m | 916 ++ code/macosx/macosx_local.h | 129 + code/macosx/macosx_qgl.h | 5095 ++++++++ code/macosx/macosx_sndcore.m | 325 + code/macosx/macosx_snddma.m | 205 + code/macosx/macosx_sys.m | 537 + code/macosx/macosx_timers.h | 56 + code/macosx/macosx_timers.m | 75 + code/macosx/timedemo.zsh | 26 + code/null/mac_net.c | 65 + code/null/null_client.c | 91 + code/null/null_glimp.c | 56 + code/null/null_input.c | 35 + code/null/null_main.c | 116 + code/null/null_net.c | 64 + code/null/null_snddma.c | 60 + code/opts.lnt | 26 + code/q3_ui/Conscript | 93 + code/q3_ui/compile.bat | 2 + code/q3_ui/keycodes.h | 163 + code/q3_ui/q3_ui.bat | 100 + code/q3_ui/q3_ui.q3asm | 43 + code/q3_ui/q3_ui.sh | 55 + code/q3_ui/q3_ui.vcproj | 2008 ++++ code/q3_ui/ui.def | 3 + code/q3_ui/ui.q3asm | 45 + code/q3_ui/ui_addbots.c | 412 + code/q3_ui/ui_atoms.c | 1267 ++ code/q3_ui/ui_cdkey.c | 291 + code/q3_ui/ui_cinematics.c | 350 + code/q3_ui/ui_confirm.c | 293 + code/q3_ui/ui_connect.c | 282 + code/q3_ui/ui_controls2.c | 1667 +++ code/q3_ui/ui_credits.c | 129 + code/q3_ui/ui_demo2.c | 291 + code/q3_ui/ui_display.c | 265 + code/q3_ui/ui_gameinfo.c | 820 ++ code/q3_ui/ui_ingame.c | 349 + code/q3_ui/ui_loadconfig.c | 274 + code/q3_ui/ui_local.h | 800 ++ code/q3_ui/ui_login.c | 208 + code/q3_ui/ui_main.c | 249 + code/q3_ui/ui_menu.c | 419 + code/q3_ui/ui_mfield.c | 439 + code/q3_ui/ui_mods.c | 283 + code/q3_ui/ui_network.c | 281 + code/q3_ui/ui_options.c | 229 + code/q3_ui/ui_playermodel.c | 731 ++ code/q3_ui/ui_players.c | 1248 ++ code/q3_ui/ui_playersettings.c | 513 + code/q3_ui/ui_preferences.c | 419 + code/q3_ui/ui_qmenu.c | 1746 +++ code/q3_ui/ui_rankings.c | 420 + code/q3_ui/ui_rankstatus.c | 209 + code/q3_ui/ui_removebots.c | 342 + code/q3_ui/ui_saveconfig.c | 212 + code/q3_ui/ui_serverinfo.c | 272 + code/q3_ui/ui_servers2.c | 1640 +++ code/q3_ui/ui_setup.c | 327 + code/q3_ui/ui_signup.c | 286 + code/q3_ui/ui_sound.c | 316 + code/q3_ui/ui_sparena.c | 50 + code/q3_ui/ui_specifyleague.c | 333 + code/q3_ui/ui_specifyserver.c | 213 + code/q3_ui/ui_splevel.c | 1008 ++ code/q3_ui/ui_sppostgame.c | 644 + code/q3_ui/ui_spreset.c | 194 + code/q3_ui/ui_spskill.c | 329 + code/q3_ui/ui_startserver.c | 1968 ++++ code/q3_ui/ui_team.c | 210 + code/q3_ui/ui_teamorders.c | 449 + code/q3_ui/ui_video.c | 1070 ++ code/qcommon/cm_load.c | 839 ++ code/qcommon/cm_local.h | 194 + code/qcommon/cm_patch.c | 1771 +++ code/qcommon/cm_patch.h | 103 + code/qcommon/cm_polylib.c | 737 ++ code/qcommon/cm_polylib.h | 68 + code/qcommon/cm_public.h | 76 + code/qcommon/cm_test.c | 478 + code/qcommon/cm_trace.c | 1471 +++ code/qcommon/cmd.c | 715 ++ code/qcommon/cmd.c.save | 636 + code/qcommon/common.c | 3317 ++++++ code/qcommon/cvar.c | 906 ++ code/qcommon/files.c | 3419 ++++++ code/qcommon/huffman.c | 437 + code/qcommon/md4.c | 299 + code/qcommon/msg.c | 1757 +++ code/qcommon/net_chan.c | 742 ++ code/qcommon/qcommon.h | 1067 ++ code/qcommon/qfiles.h | 488 + code/qcommon/unzip.c | 4299 +++++++ code/qcommon/unzip.h | 336 + code/qcommon/vm.c | 835 ++ code/qcommon/vm_interpreted.c | 889 ++ code/qcommon/vm_local.h | 180 + code/qcommon/vm_ppc.c | 1479 +++ code/qcommon/vm_ppc_new.c | 2119 ++++ code/qcommon/vm_x86.c | 1196 ++ code/quake3.sln | 145 + code/quake3.vcproj | 3463 ++++++ code/renderer.lnt | 27 + code/renderer/qgl.h | 572 + code/renderer/qgl_linked.h | 357 + code/renderer/ref_trin.def | 2 + code/renderer/renderer.vcproj | 6112 ++++++++++ code/renderer/tr_animation.c | 171 + code/renderer/tr_backend.c | 1143 ++ code/renderer/tr_bsp.c | 1862 +++ code/renderer/tr_cmds.c | 447 + code/renderer/tr_curve.c | 624 + code/renderer/tr_flares.c | 447 + code/renderer/tr_font.c | 542 + code/renderer/tr_image.c | 2520 ++++ code/renderer/tr_init.c | 1217 ++ code/renderer/tr_light.c | 395 + code/renderer/tr_local.h | 1609 +++ code/renderer/tr_main.c | 1485 +++ code/renderer/tr_marks.c | 443 + code/renderer/tr_mesh.c | 397 + code/renderer/tr_model.c | 700 ++ code/renderer/tr_noise.c | 95 + code/renderer/tr_public.h | 167 + code/renderer/tr_scene.c | 409 + code/renderer/tr_shade.c | 1361 +++ code/renderer/tr_shade_calc.c | 1205 ++ code/renderer/tr_shader.c | 3013 +++++ code/renderer/tr_shadows.c | 341 + code/renderer/tr_sky.c | 845 ++ code/renderer/tr_surface.c | 1215 ++ code/renderer/tr_world.c | 668 ++ code/run.bat | 1 + code/runrelease.bat | 1 + code/server/server.h | 404 + code/server/sv_bot.c | 635 + code/server/sv_ccmds.c | 762 ++ code/server/sv_client.c | 1531 +++ code/server/sv_game.c | 981 ++ code/server/sv_init.c | 695 ++ code/server/sv_main.c | 855 ++ code/server/sv_net_chan.c | 207 + code/server/sv_rankings.c | 1537 +++ code/server/sv_snapshot.c | 682 ++ code/server/sv_world.c | 691 ++ code/splines/Splines.vcproj | 296 + code/splines/math_angles.cpp | 150 + code/splines/math_angles.h | 195 + code/splines/math_matrix.cpp | 134 + code/splines/math_matrix.h | 223 + code/splines/math_quaternion.cpp | 78 + code/splines/math_quaternion.h | 190 + code/splines/math_vector.cpp | 144 + code/splines/math_vector.h | 574 + code/splines/q_parse.cpp | 535 + code/splines/q_shared.cpp | 976 ++ code/splines/q_shared.h | 810 ++ code/splines/q_shared.hpp | 810 ++ code/splines/splines.cpp | 1250 ++ code/splines/splines.h | 1075 ++ code/splines/util_list.h | 346 + code/splines/util_str.cpp | 620 + code/splines/util_str.h | 817 ++ code/ui/Conscript | 62 + code/ui/compile.bat | 2 + code/ui/keycodes.h | 163 + code/ui/ui.bat | 33 + code/ui/ui.def | 3 + code/ui/ui.q3asm | 12 + code/ui/ui.vcproj | 1008 ++ code/ui/ui_atoms.c | 520 + code/ui/ui_gameinfo.c | 324 + code/ui/ui_local.h | 1136 ++ code/ui/ui_main.c | 5997 ++++++++++ code/ui/ui_players.c | 1378 +++ code/ui/ui_public.h | 191 + code/ui/ui_shared.c | 5785 +++++++++ code/ui/ui_shared.h | 450 + code/ui/ui_syscalls.asm | 101 + code/ui/ui_syscalls.c | 401 + code/ui/ui_util.c | 29 + code/unix/ChangeLog | 2981 +++++ code/unix/Cons_gcc.pm | 47 + code/unix/Conscript-client | 265 + code/unix/Conscript-dedicated | 115 + code/unix/Conscript-pk3 | 135 + code/unix/Conscript-sdk | 122 + code/unix/Conscript-setup | 33 + code/unix/LinuxSupport/CHANGES-1.32.txt | 150 + code/unix/LinuxSupport/INSTALL | 52 + code/unix/LinuxSupport/index.html | 283 + code/unix/LinuxSupport/udp_wide_README.txt | 20 + code/unix/LinuxSupport/udp_wide_broadcast.patch | 57 + code/unix/Makefile | 2119 ++++ code/unix/Makefile.Game | 285 + code/unix/Quake3.kdelnk | 12 + code/unix/README.EULA | 184 + code/unix/README.Linux | 352 + code/unix/README.Q3Test | 306 + code/unix/build_setup.sh | 125 + code/unix/cons | 6828 +++++++++++ code/unix/extract_ver.pl | 9 + code/unix/ftol.nasm | 151 + code/unix/linux_common.c | 344 + code/unix/linux_glimp.c | 1780 +++ code/unix/linux_joystick.c | 207 + code/unix/linux_local.h | 49 + code/unix/linux_qgl.c | 4153 +++++++ code/unix/linux_signals.c | 61 + code/unix/linux_snd.c | 293 + code/unix/matha.s | 425 + code/unix/pcons-2.3.1 | 7911 +++++++++++++ code/unix/q3test.spec.sh | 52 + code/unix/qasm.h | 480 + code/unix/quake3.gif | Bin 0 -> 1378 bytes code/unix/quake3.xpm | 161 + code/unix/run-target.sh | 12 + code/unix/snapvector.nasm | 95 + code/unix/snd_mixa.s | 217 + code/unix/sys_dosa.s | 115 + code/unix/unix_glw.h | 38 + code/unix/unix_main.c | 1273 ++ code/unix/unix_net.c | 616 + code/unix/unix_shared.c | 435 + code/unix/vm_x86.c | 29 + code/unix/vm_x86a.s | 462 + code/win32/background.bmp | Bin 0 -> 197688 bytes code/win32/clear.bmp | Bin 0 -> 5174 bytes code/win32/glw_win.h | 51 + code/win32/icon2.ico | Bin 0 -> 766 bytes code/win32/mod-sdk-setup/GameSource.VCT | Bin 0 -> 628735 bytes .../mod-sdk-setup/QIIIA Game Source License.doc | Bin 0 -> 34304 bytes code/win32/mod-sdk-setup/bin/lcc.exe | Bin 0 -> 135223 bytes code/win32/mod-sdk-setup/bin/q3asm.exe | Bin 0 -> 69632 bytes code/win32/mod-sdk-setup/bin/q3cpp.exe | Bin 0 -> 159801 bytes code/win32/mod-sdk-setup/bin/q3rcc.exe | Bin 0 -> 847929 bytes code/win32/qe3.ico | Bin 0 -> 766 bytes code/win32/resource.h | 44 + code/win32/win_gamma.c | 214 + code/win32/win_glimp.c | 1658 +++ code/win32/win_input.c | 1149 ++ code/win32/win_local.h | 95 + code/win32/win_main.c | 1253 ++ code/win32/win_net.c | 1031 ++ code/win32/win_qgl.c | 4374 +++++++ code/win32/win_shared.c | 306 + code/win32/win_snd.c | 388 + code/win32/win_syscon.c | 596 + code/win32/win_wndproc.c | 456 + code/win32/winquake.rc | 71 + 614 files changed, 397076 insertions(+) create mode 100755 code/Construct create mode 100755 code/Makefile create mode 100755 code/botlib/aasfile.h create mode 100755 code/botlib/be_aas_bsp.h create mode 100755 code/botlib/be_aas_bspq3.c create mode 100755 code/botlib/be_aas_cluster.c create mode 100755 code/botlib/be_aas_cluster.h create mode 100755 code/botlib/be_aas_debug.c create mode 100755 code/botlib/be_aas_debug.h create mode 100755 code/botlib/be_aas_def.h create mode 100755 code/botlib/be_aas_entity.c create mode 100755 code/botlib/be_aas_entity.h create mode 100755 code/botlib/be_aas_file.c create mode 100755 code/botlib/be_aas_file.h create mode 100755 code/botlib/be_aas_funcs.h create mode 100755 code/botlib/be_aas_main.c create mode 100755 code/botlib/be_aas_main.h create mode 100755 code/botlib/be_aas_move.c create mode 100755 code/botlib/be_aas_move.h create mode 100755 code/botlib/be_aas_optimize.c create mode 100755 code/botlib/be_aas_optimize.h create mode 100755 code/botlib/be_aas_reach.c create mode 100755 code/botlib/be_aas_reach.h create mode 100755 code/botlib/be_aas_route.c create mode 100755 code/botlib/be_aas_route.h create mode 100755 code/botlib/be_aas_routealt.c create mode 100755 code/botlib/be_aas_routealt.h create mode 100755 code/botlib/be_aas_sample.c create mode 100755 code/botlib/be_aas_sample.h create mode 100755 code/botlib/be_ai_char.c create mode 100755 code/botlib/be_ai_chat.c create mode 100755 code/botlib/be_ai_gen.c create mode 100755 code/botlib/be_ai_goal.c create mode 100755 code/botlib/be_ai_move.c create mode 100755 code/botlib/be_ai_weap.c create mode 100755 code/botlib/be_ai_weight.c create mode 100755 code/botlib/be_ai_weight.h create mode 100755 code/botlib/be_ea.c create mode 100755 code/botlib/be_interface.c create mode 100755 code/botlib/be_interface.h create mode 100755 code/botlib/botlib.vcproj create mode 100755 code/botlib/l_crc.c create mode 100755 code/botlib/l_crc.h create mode 100755 code/botlib/l_libvar.c create mode 100755 code/botlib/l_libvar.h create mode 100755 code/botlib/l_log.c create mode 100755 code/botlib/l_log.h create mode 100755 code/botlib/l_memory.c create mode 100755 code/botlib/l_memory.h create mode 100755 code/botlib/l_precomp.c create mode 100755 code/botlib/l_precomp.h create mode 100755 code/botlib/l_script.c create mode 100755 code/botlib/l_script.h create mode 100755 code/botlib/l_struct.c create mode 100755 code/botlib/l_struct.h create mode 100755 code/botlib/l_utils.h create mode 100755 code/botlib/lcc.mak create mode 100755 code/botlib/linux-i386.mak create mode 100755 code/bspc/Conscript create mode 100755 code/bspc/Makefile create mode 100755 code/bspc/_files.c create mode 100755 code/bspc/aas_areamerging.c create mode 100755 code/bspc/aas_areamerging.h create mode 100755 code/bspc/aas_cfg.c create mode 100755 code/bspc/aas_cfg.h create mode 100755 code/bspc/aas_create.c create mode 100755 code/bspc/aas_create.h create mode 100755 code/bspc/aas_edgemelting.c create mode 100755 code/bspc/aas_edgemelting.h create mode 100755 code/bspc/aas_facemerging.c create mode 100755 code/bspc/aas_facemerging.h create mode 100755 code/bspc/aas_file.c create mode 100755 code/bspc/aas_file.h create mode 100755 code/bspc/aas_gsubdiv.c create mode 100755 code/bspc/aas_gsubdiv.h create mode 100755 code/bspc/aas_map.c create mode 100755 code/bspc/aas_map.h create mode 100755 code/bspc/aas_prunenodes.c create mode 100755 code/bspc/aas_prunenodes.h create mode 100755 code/bspc/aas_store.c create mode 100755 code/bspc/aas_store.h create mode 100755 code/bspc/aasfile.h create mode 100755 code/bspc/be_aas_bspc.c create mode 100755 code/bspc/be_aas_bspc.h create mode 100755 code/bspc/brushbsp.c create mode 100755 code/bspc/bspc.c create mode 100755 code/bspc/bspc.sln create mode 100755 code/bspc/bspc.vcproj create mode 100755 code/bspc/cfgq3.c create mode 100755 code/bspc/csg.c create mode 100755 code/bspc/faces.c create mode 100755 code/bspc/gldraw.c create mode 100755 code/bspc/glfile.c create mode 100755 code/bspc/l_bsp_ent.c create mode 100755 code/bspc/l_bsp_ent.h create mode 100755 code/bspc/l_bsp_hl.c create mode 100755 code/bspc/l_bsp_hl.h create mode 100755 code/bspc/l_bsp_q1.c create mode 100755 code/bspc/l_bsp_q1.h create mode 100755 code/bspc/l_bsp_q2.c create mode 100755 code/bspc/l_bsp_q2.h create mode 100755 code/bspc/l_bsp_q3.c create mode 100755 code/bspc/l_bsp_q3.h create mode 100755 code/bspc/l_bsp_sin.c create mode 100755 code/bspc/l_bsp_sin.h create mode 100755 code/bspc/l_cmd.c create mode 100755 code/bspc/l_cmd.h create mode 100755 code/bspc/l_log.c create mode 100755 code/bspc/l_log.h create mode 100755 code/bspc/l_math.c create mode 100755 code/bspc/l_math.h create mode 100755 code/bspc/l_mem.c create mode 100755 code/bspc/l_mem.h create mode 100755 code/bspc/l_poly.c create mode 100755 code/bspc/l_poly.h create mode 100755 code/bspc/l_qfiles.c create mode 100755 code/bspc/l_qfiles.h create mode 100755 code/bspc/l_threads.c create mode 100755 code/bspc/l_threads.h create mode 100755 code/bspc/l_utils.c create mode 100755 code/bspc/l_utils.h create mode 100755 code/bspc/lcc.mak create mode 100755 code/bspc/leakfile.c create mode 100755 code/bspc/linux-i386.mak create mode 100755 code/bspc/map.c create mode 100755 code/bspc/map_hl.c create mode 100755 code/bspc/map_q1.c create mode 100755 code/bspc/map_q2.c create mode 100755 code/bspc/map_q3.c create mode 100755 code/bspc/map_sin.c create mode 100755 code/bspc/nodraw.c create mode 100755 code/bspc/portals.c create mode 100755 code/bspc/prtfile.c create mode 100755 code/bspc/q2files.h create mode 100755 code/bspc/q3files.h create mode 100755 code/bspc/qbsp.h create mode 100755 code/bspc/qfiles.h create mode 100755 code/bspc/sinfiles.h create mode 100755 code/bspc/tetrahedron.c create mode 100755 code/bspc/tetrahedron.h create mode 100755 code/bspc/textures.c create mode 100755 code/bspc/tree.c create mode 100755 code/bspc/writebsp.c create mode 100755 code/cgame.lnt create mode 100755 code/cgame/Conscript create mode 100755 code/cgame/cg_consolecmds.c create mode 100755 code/cgame/cg_draw.c create mode 100755 code/cgame/cg_drawtools.c create mode 100755 code/cgame/cg_effects.c create mode 100755 code/cgame/cg_ents.c create mode 100755 code/cgame/cg_event.c create mode 100755 code/cgame/cg_info.c create mode 100755 code/cgame/cg_local.h create mode 100755 code/cgame/cg_localents.c create mode 100755 code/cgame/cg_main.c create mode 100755 code/cgame/cg_marks.c create mode 100755 code/cgame/cg_newdraw.c create mode 100755 code/cgame/cg_particles.c create mode 100755 code/cgame/cg_players.c create mode 100755 code/cgame/cg_playerstate.c create mode 100755 code/cgame/cg_predict.c create mode 100755 code/cgame/cg_public.h create mode 100755 code/cgame/cg_scoreboard.c create mode 100755 code/cgame/cg_servercmds.c create mode 100755 code/cgame/cg_snapshot.c create mode 100755 code/cgame/cg_syscalls.asm create mode 100755 code/cgame/cg_syscalls.c create mode 100755 code/cgame/cg_view.c create mode 100755 code/cgame/cg_weapons.c create mode 100755 code/cgame/cgame.bat create mode 100755 code/cgame/cgame.def create mode 100755 code/cgame/cgame.plg create mode 100755 code/cgame/cgame.q3asm create mode 100755 code/cgame/cgame.sh create mode 100755 code/cgame/cgame.vcproj create mode 100755 code/cgame/cgame_ta.bat create mode 100755 code/cgame/cgame_ta.q3asm create mode 100755 code/cgame/cgame_ta.sh create mode 100755 code/cgame/tr_types.h create mode 100755 code/clean.bat create mode 100755 code/client/cl_cgame.c create mode 100755 code/client/cl_cin.c create mode 100755 code/client/cl_console.c create mode 100755 code/client/cl_input.c create mode 100755 code/client/cl_keys.c create mode 100755 code/client/cl_main.c create mode 100755 code/client/cl_net_chan.c create mode 100755 code/client/cl_parse.c create mode 100755 code/client/cl_scrn.c create mode 100755 code/client/cl_ui.c create mode 100755 code/client/client.h create mode 100755 code/client/keys.h create mode 100755 code/client/snd_adpcm.c create mode 100755 code/client/snd_dma.c create mode 100755 code/client/snd_local.h create mode 100755 code/client/snd_mem.c create mode 100755 code/client/snd_mix.c create mode 100755 code/client/snd_public.h create mode 100755 code/client/snd_wavelet.c create mode 100755 code/game.lnt create mode 100755 code/game/Conscript create mode 100755 code/game/ai_chat.c create mode 100755 code/game/ai_chat.h create mode 100755 code/game/ai_cmd.c create mode 100755 code/game/ai_cmd.h create mode 100755 code/game/ai_dmnet.c create mode 100755 code/game/ai_dmnet.h create mode 100755 code/game/ai_dmq3.c create mode 100755 code/game/ai_dmq3.h create mode 100755 code/game/ai_main.c create mode 100755 code/game/ai_main.h create mode 100755 code/game/ai_team.c create mode 100755 code/game/ai_team.h create mode 100755 code/game/ai_vcmd.c create mode 100755 code/game/ai_vcmd.h create mode 100755 code/game/be_aas.h create mode 100755 code/game/be_ai_char.h create mode 100755 code/game/be_ai_chat.h create mode 100755 code/game/be_ai_gen.h create mode 100755 code/game/be_ai_goal.h create mode 100755 code/game/be_ai_move.h create mode 100755 code/game/be_ai_weap.h create mode 100755 code/game/be_ea.h create mode 100755 code/game/bg_lib.c create mode 100755 code/game/bg_lib.h create mode 100755 code/game/bg_local.h create mode 100755 code/game/bg_misc.c create mode 100755 code/game/bg_pmove.c create mode 100755 code/game/bg_public.h create mode 100755 code/game/bg_slidemove.c create mode 100755 code/game/botlib.h create mode 100755 code/game/chars.h create mode 100755 code/game/g_active.c create mode 100755 code/game/g_arenas.c create mode 100755 code/game/g_bot.c create mode 100755 code/game/g_client.c create mode 100755 code/game/g_cmds.c create mode 100755 code/game/g_combat.c create mode 100755 code/game/g_items.c create mode 100755 code/game/g_local.h create mode 100755 code/game/g_main.c create mode 100755 code/game/g_mem.c create mode 100755 code/game/g_misc.c create mode 100755 code/game/g_missile.c create mode 100755 code/game/g_mover.c create mode 100755 code/game/g_public.h create mode 100755 code/game/g_rankings.c create mode 100755 code/game/g_rankings.h create mode 100755 code/game/g_session.c create mode 100755 code/game/g_spawn.c create mode 100755 code/game/g_svcmds.c create mode 100755 code/game/g_syscalls.asm create mode 100755 code/game/g_syscalls.c create mode 100755 code/game/g_target.c create mode 100755 code/game/g_team.c create mode 100755 code/game/g_team.h create mode 100755 code/game/g_trigger.c create mode 100755 code/game/g_utils.c create mode 100755 code/game/g_weapon.c create mode 100755 code/game/game.bat create mode 100755 code/game/game.def create mode 100755 code/game/game.q3asm create mode 100755 code/game/game.sh create mode 100755 code/game/game.vcproj create mode 100755 code/game/game_ta.bat create mode 100755 code/game/game_ta.q3asm create mode 100755 code/game/game_ta.sh create mode 100755 code/game/inv.h create mode 100755 code/game/match.h create mode 100755 code/game/q_math.c create mode 100755 code/game/q_shared.c create mode 100755 code/game/q_shared.h create mode 100755 code/game/surfaceflags.h create mode 100755 code/game/syn.h create mode 100755 code/installdebug.bat create mode 100755 code/installrelease.bat create mode 100755 code/installvms.bat create mode 100755 code/jpeg-6/README create mode 100755 code/jpeg-6/jcapimin.c create mode 100755 code/jpeg-6/jcapistd.c create mode 100755 code/jpeg-6/jccoefct.c create mode 100755 code/jpeg-6/jccolor.c create mode 100755 code/jpeg-6/jcdctmgr.c create mode 100755 code/jpeg-6/jchuff.c create mode 100755 code/jpeg-6/jchuff.h create mode 100755 code/jpeg-6/jcinit.c create mode 100755 code/jpeg-6/jcmainct.c create mode 100755 code/jpeg-6/jcmarker.c create mode 100755 code/jpeg-6/jcmaster.c create mode 100755 code/jpeg-6/jcomapi.c create mode 100755 code/jpeg-6/jconfig.h create mode 100755 code/jpeg-6/jcparam.c create mode 100755 code/jpeg-6/jcphuff.c create mode 100755 code/jpeg-6/jcprepct.c create mode 100755 code/jpeg-6/jcsample.c create mode 100755 code/jpeg-6/jctrans.c create mode 100755 code/jpeg-6/jdapimin.c create mode 100755 code/jpeg-6/jdapistd.c create mode 100755 code/jpeg-6/jdatadst.c create mode 100755 code/jpeg-6/jdatasrc.c create mode 100755 code/jpeg-6/jdcoefct.c create mode 100755 code/jpeg-6/jdcolor.c create mode 100755 code/jpeg-6/jdct.h create mode 100755 code/jpeg-6/jddctmgr.c create mode 100755 code/jpeg-6/jdhuff.c create mode 100755 code/jpeg-6/jdhuff.h create mode 100755 code/jpeg-6/jdinput.c create mode 100755 code/jpeg-6/jdmainct.c create mode 100755 code/jpeg-6/jdmarker.c create mode 100755 code/jpeg-6/jdmaster.c create mode 100755 code/jpeg-6/jdmerge.c create mode 100755 code/jpeg-6/jdphuff.c create mode 100755 code/jpeg-6/jdpostct.c create mode 100755 code/jpeg-6/jdsample.c create mode 100755 code/jpeg-6/jdtrans.c create mode 100755 code/jpeg-6/jerror.c create mode 100755 code/jpeg-6/jerror.h create mode 100755 code/jpeg-6/jfdctflt.c create mode 100755 code/jpeg-6/jfdctfst.c create mode 100755 code/jpeg-6/jfdctint.c create mode 100755 code/jpeg-6/jidctflt.c create mode 100755 code/jpeg-6/jidctfst.c create mode 100755 code/jpeg-6/jidctint.c create mode 100755 code/jpeg-6/jidctred.c create mode 100755 code/jpeg-6/jinclude.h create mode 100755 code/jpeg-6/jload.c create mode 100755 code/jpeg-6/jmemansi.c create mode 100755 code/jpeg-6/jmemdos.c create mode 100755 code/jpeg-6/jmemmgr.c create mode 100755 code/jpeg-6/jmemname.c create mode 100755 code/jpeg-6/jmemnobs.c create mode 100755 code/jpeg-6/jmemsys.h create mode 100755 code/jpeg-6/jmorecfg.h create mode 100755 code/jpeg-6/jpegint.h create mode 100755 code/jpeg-6/jpeglib.h create mode 100755 code/jpeg-6/jpegtran.c create mode 100755 code/jpeg-6/jquant1.c create mode 100755 code/jpeg-6/jquant2.c create mode 100755 code/jpeg-6/jutils.c create mode 100755 code/jpeg-6/jversion.h create mode 100755 code/macosx/BuildRelease create mode 100755 code/macosx/CGMouseDeltaFix.h create mode 100755 code/macosx/CGMouseDeltaFix.m create mode 100755 code/macosx/CGPrivateAPI.h create mode 100755 code/macosx/GenerateQGL.pl create mode 100755 code/macosx/Performance.rtf create mode 100755 code/macosx/Q3Controller.h create mode 100755 code/macosx/Q3Controller.m create mode 100755 code/macosx/Quake3.icns create mode 100755 code/macosx/Quake3.nib/classes.nib create mode 100755 code/macosx/Quake3.nib/info.nib create mode 100755 code/macosx/Quake3.nib/objects.nib create mode 100755 code/macosx/Quake3.pbproj/apple.pbxuser create mode 100755 code/macosx/Quake3.pbproj/project.pbxproj create mode 100755 code/macosx/RecordDemo.zsh create mode 100755 code/macosx/banner.jpg create mode 100755 code/macosx/botlib.log create mode 100755 code/macosx/macosx_display.h create mode 100755 code/macosx/macosx_display.m create mode 100755 code/macosx/macosx_glimp.h create mode 100755 code/macosx/macosx_glimp.m create mode 100755 code/macosx/macosx_glsmp_mutex.m create mode 100755 code/macosx/macosx_glsmp_null.m create mode 100755 code/macosx/macosx_glsmp_ports.m create mode 100755 code/macosx/macosx_input.m create mode 100755 code/macosx/macosx_local.h create mode 100755 code/macosx/macosx_qgl.h create mode 100755 code/macosx/macosx_sndcore.m create mode 100755 code/macosx/macosx_snddma.m create mode 100755 code/macosx/macosx_sys.m create mode 100755 code/macosx/macosx_timers.h create mode 100755 code/macosx/macosx_timers.m create mode 100755 code/macosx/timedemo.zsh create mode 100755 code/null/mac_net.c create mode 100755 code/null/null_client.c create mode 100755 code/null/null_glimp.c create mode 100755 code/null/null_input.c create mode 100755 code/null/null_main.c create mode 100755 code/null/null_net.c create mode 100755 code/null/null_snddma.c create mode 100755 code/opts.lnt create mode 100755 code/q3_ui/Conscript create mode 100755 code/q3_ui/compile.bat create mode 100755 code/q3_ui/keycodes.h create mode 100755 code/q3_ui/q3_ui.bat create mode 100755 code/q3_ui/q3_ui.q3asm create mode 100755 code/q3_ui/q3_ui.sh create mode 100755 code/q3_ui/q3_ui.vcproj create mode 100755 code/q3_ui/ui.def create mode 100755 code/q3_ui/ui.q3asm create mode 100755 code/q3_ui/ui_addbots.c create mode 100755 code/q3_ui/ui_atoms.c create mode 100755 code/q3_ui/ui_cdkey.c create mode 100755 code/q3_ui/ui_cinematics.c create mode 100755 code/q3_ui/ui_confirm.c create mode 100755 code/q3_ui/ui_connect.c create mode 100755 code/q3_ui/ui_controls2.c create mode 100755 code/q3_ui/ui_credits.c create mode 100755 code/q3_ui/ui_demo2.c create mode 100755 code/q3_ui/ui_display.c create mode 100755 code/q3_ui/ui_gameinfo.c create mode 100755 code/q3_ui/ui_ingame.c create mode 100755 code/q3_ui/ui_loadconfig.c create mode 100755 code/q3_ui/ui_local.h create mode 100755 code/q3_ui/ui_login.c create mode 100755 code/q3_ui/ui_main.c create mode 100755 code/q3_ui/ui_menu.c create mode 100755 code/q3_ui/ui_mfield.c create mode 100755 code/q3_ui/ui_mods.c create mode 100755 code/q3_ui/ui_network.c create mode 100755 code/q3_ui/ui_options.c create mode 100755 code/q3_ui/ui_playermodel.c create mode 100755 code/q3_ui/ui_players.c create mode 100755 code/q3_ui/ui_playersettings.c create mode 100755 code/q3_ui/ui_preferences.c create mode 100755 code/q3_ui/ui_qmenu.c create mode 100755 code/q3_ui/ui_rankings.c create mode 100755 code/q3_ui/ui_rankstatus.c create mode 100755 code/q3_ui/ui_removebots.c create mode 100755 code/q3_ui/ui_saveconfig.c create mode 100755 code/q3_ui/ui_serverinfo.c create mode 100755 code/q3_ui/ui_servers2.c create mode 100755 code/q3_ui/ui_setup.c create mode 100755 code/q3_ui/ui_signup.c create mode 100755 code/q3_ui/ui_sound.c create mode 100755 code/q3_ui/ui_sparena.c create mode 100755 code/q3_ui/ui_specifyleague.c create mode 100755 code/q3_ui/ui_specifyserver.c create mode 100755 code/q3_ui/ui_splevel.c create mode 100755 code/q3_ui/ui_sppostgame.c create mode 100755 code/q3_ui/ui_spreset.c create mode 100755 code/q3_ui/ui_spskill.c create mode 100755 code/q3_ui/ui_startserver.c create mode 100755 code/q3_ui/ui_team.c create mode 100755 code/q3_ui/ui_teamorders.c create mode 100755 code/q3_ui/ui_video.c create mode 100755 code/qcommon/cm_load.c create mode 100755 code/qcommon/cm_local.h create mode 100755 code/qcommon/cm_patch.c create mode 100755 code/qcommon/cm_patch.h create mode 100755 code/qcommon/cm_polylib.c create mode 100755 code/qcommon/cm_polylib.h create mode 100755 code/qcommon/cm_public.h create mode 100755 code/qcommon/cm_test.c create mode 100755 code/qcommon/cm_trace.c create mode 100755 code/qcommon/cmd.c create mode 100755 code/qcommon/cmd.c.save create mode 100755 code/qcommon/common.c create mode 100755 code/qcommon/cvar.c create mode 100755 code/qcommon/files.c create mode 100755 code/qcommon/huffman.c create mode 100755 code/qcommon/md4.c create mode 100755 code/qcommon/msg.c create mode 100755 code/qcommon/net_chan.c create mode 100755 code/qcommon/qcommon.h create mode 100755 code/qcommon/qfiles.h create mode 100755 code/qcommon/unzip.c create mode 100755 code/qcommon/unzip.h create mode 100755 code/qcommon/vm.c create mode 100755 code/qcommon/vm_interpreted.c create mode 100755 code/qcommon/vm_local.h create mode 100755 code/qcommon/vm_ppc.c create mode 100755 code/qcommon/vm_ppc_new.c create mode 100755 code/qcommon/vm_x86.c create mode 100755 code/quake3.sln create mode 100755 code/quake3.vcproj create mode 100755 code/renderer.lnt create mode 100755 code/renderer/qgl.h create mode 100755 code/renderer/qgl_linked.h create mode 100755 code/renderer/ref_trin.def create mode 100755 code/renderer/renderer.vcproj create mode 100755 code/renderer/tr_animation.c create mode 100755 code/renderer/tr_backend.c create mode 100755 code/renderer/tr_bsp.c create mode 100755 code/renderer/tr_cmds.c create mode 100755 code/renderer/tr_curve.c create mode 100755 code/renderer/tr_flares.c create mode 100755 code/renderer/tr_font.c create mode 100755 code/renderer/tr_image.c create mode 100755 code/renderer/tr_init.c create mode 100755 code/renderer/tr_light.c create mode 100755 code/renderer/tr_local.h create mode 100755 code/renderer/tr_main.c create mode 100755 code/renderer/tr_marks.c create mode 100755 code/renderer/tr_mesh.c create mode 100755 code/renderer/tr_model.c create mode 100755 code/renderer/tr_noise.c create mode 100755 code/renderer/tr_public.h create mode 100755 code/renderer/tr_scene.c create mode 100755 code/renderer/tr_shade.c create mode 100755 code/renderer/tr_shade_calc.c create mode 100755 code/renderer/tr_shader.c create mode 100755 code/renderer/tr_shadows.c create mode 100755 code/renderer/tr_sky.c create mode 100755 code/renderer/tr_surface.c create mode 100755 code/renderer/tr_world.c create mode 100755 code/run.bat create mode 100755 code/runrelease.bat create mode 100755 code/server/server.h create mode 100755 code/server/sv_bot.c create mode 100755 code/server/sv_ccmds.c create mode 100755 code/server/sv_client.c create mode 100755 code/server/sv_game.c create mode 100755 code/server/sv_init.c create mode 100755 code/server/sv_main.c create mode 100755 code/server/sv_net_chan.c create mode 100755 code/server/sv_rankings.c create mode 100755 code/server/sv_snapshot.c create mode 100755 code/server/sv_world.c create mode 100755 code/splines/Splines.vcproj create mode 100755 code/splines/math_angles.cpp create mode 100755 code/splines/math_angles.h create mode 100755 code/splines/math_matrix.cpp create mode 100755 code/splines/math_matrix.h create mode 100755 code/splines/math_quaternion.cpp create mode 100755 code/splines/math_quaternion.h create mode 100755 code/splines/math_vector.cpp create mode 100755 code/splines/math_vector.h create mode 100755 code/splines/q_parse.cpp create mode 100755 code/splines/q_shared.cpp create mode 100755 code/splines/q_shared.h create mode 100755 code/splines/q_shared.hpp create mode 100755 code/splines/splines.cpp create mode 100755 code/splines/splines.h create mode 100755 code/splines/util_list.h create mode 100755 code/splines/util_str.cpp create mode 100755 code/splines/util_str.h create mode 100755 code/ui/Conscript create mode 100755 code/ui/compile.bat create mode 100755 code/ui/keycodes.h create mode 100755 code/ui/ui.bat create mode 100755 code/ui/ui.def create mode 100755 code/ui/ui.q3asm create mode 100755 code/ui/ui.vcproj create mode 100755 code/ui/ui_atoms.c create mode 100755 code/ui/ui_gameinfo.c create mode 100755 code/ui/ui_local.h create mode 100755 code/ui/ui_main.c create mode 100755 code/ui/ui_players.c create mode 100755 code/ui/ui_public.h create mode 100755 code/ui/ui_shared.c create mode 100755 code/ui/ui_shared.h create mode 100755 code/ui/ui_syscalls.asm create mode 100755 code/ui/ui_syscalls.c create mode 100755 code/ui/ui_util.c create mode 100755 code/unix/ChangeLog create mode 100755 code/unix/Cons_gcc.pm create mode 100755 code/unix/Conscript-client create mode 100755 code/unix/Conscript-dedicated create mode 100755 code/unix/Conscript-pk3 create mode 100755 code/unix/Conscript-sdk create mode 100755 code/unix/Conscript-setup create mode 100755 code/unix/LinuxSupport/CHANGES-1.32.txt create mode 100755 code/unix/LinuxSupport/INSTALL create mode 100755 code/unix/LinuxSupport/index.html create mode 100755 code/unix/LinuxSupport/udp_wide_README.txt create mode 100755 code/unix/LinuxSupport/udp_wide_broadcast.patch create mode 100755 code/unix/Makefile create mode 100755 code/unix/Makefile.Game create mode 100755 code/unix/Quake3.kdelnk create mode 100755 code/unix/README.EULA create mode 100755 code/unix/README.Linux create mode 100755 code/unix/README.Q3Test create mode 100755 code/unix/build_setup.sh create mode 100755 code/unix/cons create mode 100755 code/unix/extract_ver.pl create mode 100755 code/unix/ftol.nasm create mode 100755 code/unix/linux_common.c create mode 100755 code/unix/linux_glimp.c create mode 100755 code/unix/linux_joystick.c create mode 100755 code/unix/linux_local.h create mode 100755 code/unix/linux_qgl.c create mode 100755 code/unix/linux_signals.c create mode 100755 code/unix/linux_snd.c create mode 100755 code/unix/matha.s create mode 100755 code/unix/pcons-2.3.1 create mode 100755 code/unix/q3test.spec.sh create mode 100755 code/unix/qasm.h create mode 100755 code/unix/quake3.gif create mode 100755 code/unix/quake3.xpm create mode 100755 code/unix/run-target.sh create mode 100755 code/unix/snapvector.nasm create mode 100755 code/unix/snd_mixa.s create mode 100755 code/unix/sys_dosa.s create mode 100755 code/unix/unix_glw.h create mode 100755 code/unix/unix_main.c create mode 100755 code/unix/unix_net.c create mode 100755 code/unix/unix_shared.c create mode 100755 code/unix/vm_x86.c create mode 100755 code/unix/vm_x86a.s create mode 100755 code/win32/background.bmp create mode 100755 code/win32/clear.bmp create mode 100755 code/win32/glw_win.h create mode 100755 code/win32/icon2.ico create mode 100755 code/win32/mod-sdk-setup/GameSource.VCT create mode 100755 code/win32/mod-sdk-setup/QIIIA Game Source License.doc create mode 100755 code/win32/mod-sdk-setup/bin/lcc.exe create mode 100755 code/win32/mod-sdk-setup/bin/q3asm.exe create mode 100755 code/win32/mod-sdk-setup/bin/q3cpp.exe create mode 100755 code/win32/mod-sdk-setup/bin/q3rcc.exe create mode 100755 code/win32/qe3.ico create mode 100755 code/win32/resource.h create mode 100755 code/win32/win_gamma.c create mode 100755 code/win32/win_glimp.c create mode 100755 code/win32/win_input.c create mode 100755 code/win32/win_local.h create mode 100755 code/win32/win_main.c create mode 100755 code/win32/win_net.c create mode 100755 code/win32/win_qgl.c create mode 100755 code/win32/win_shared.c create mode 100755 code/win32/win_snd.c create mode 100755 code/win32/win_syscon.c create mode 100755 code/win32/win_wndproc.c create mode 100755 code/win32/winquake.rc (limited to 'code') diff --git a/code/Construct b/code/Construct new file mode 100755 index 0000000..ad9bc3c --- /dev/null +++ b/code/Construct @@ -0,0 +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 +" +; diff --git a/code/Makefile b/code/Makefile new file mode 100755 index 0000000..673b3ae --- /dev/null +++ b/code/Makefile @@ -0,0 +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 diff --git a/code/botlib/aasfile.h b/code/botlib/aasfile.h new file mode 100755 index 0000000..5b37fbe --- /dev/null +++ b/code/botlib/aasfile.h @@ -0,0 +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 +*/ diff --git a/code/botlib/be_aas_bsp.h b/code/botlib/be_aas_bsp.h new file mode 100755 index 0000000..d55353c --- /dev/null +++ b/code/botlib/be_aas_bsp.h @@ -0,0 +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); + diff --git a/code/botlib/be_aas_bspq3.c b/code/botlib/be_aas_bspq3.c new file mode 100755 index 0000000..a5e0d44 --- /dev/null +++ b/code/botlib/be_aas_bspq3.c @@ -0,0 +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 diff --git a/code/botlib/be_aas_cluster.c b/code/botlib/be_aas_cluster.c new file mode 100755 index 0000000..2fd569b --- /dev/null +++ b/code/botlib/be_aas_cluster.c @@ -0,0 +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 diff --git a/code/botlib/be_aas_cluster.h b/code/botlib/be_aas_cluster.h new file mode 100755 index 0000000..3f52a46 --- /dev/null +++ b/code/botlib/be_aas_cluster.h @@ -0,0 +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 + diff --git a/code/botlib/be_aas_debug.c b/code/botlib/be_aas_debug.c new file mode 100755 index 0000000..6271677 --- /dev/null +++ b/code/botlib/be_aas_debug.c @@ -0,0 +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); +} diff --git a/code/botlib/be_aas_debug.h b/code/botlib/be_aas_debug.h new file mode 100755 index 0000000..f0147c1 --- /dev/null +++ b/code/botlib/be_aas_debug.h @@ -0,0 +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); + diff --git a/code/botlib/be_aas_def.h b/code/botlib/be_aas_def.h new file mode 100755 index 0000000..22ebbc5 --- /dev/null +++ b/code/botlib/be_aas_def.h @@ -0,0 +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 diff --git a/code/botlib/be_aas_entity.c b/code/botlib/be_aas_entity.c new file mode 100755 index 0000000..e3ed85e --- /dev/null +++ b/code/botlib/be_aas_entity.c @@ -0,0 +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 diff --git a/code/botlib/be_aas_entity.h b/code/botlib/be_aas_entity.h new file mode 100755 index 0000000..f5286dd --- /dev/null +++ b/code/botlib/be_aas_entity.h @@ -0,0 +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); + diff --git a/code/botlib/be_aas_file.c b/code/botlib/be_aas_file.c new file mode 100755 index 0000000..35147fc --- /dev/null +++ b/code/botlib/be_aas_file.c @@ -0,0 +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 diff --git a/code/botlib/be_aas_file.h b/code/botlib/be_aas_file.h new file mode 100755 index 0000000..87a38d0 --- /dev/null +++ b/code/botlib/be_aas_file.h @@ -0,0 +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 + diff --git a/code/botlib/be_aas_funcs.h b/code/botlib/be_aas_funcs.h new file mode 100755 index 0000000..4dd37e9 --- /dev/null +++ b/code/botlib/be_aas_funcs.h @@ -0,0 +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 diff --git a/code/botlib/be_aas_main.c b/code/botlib/be_aas_main.c new file mode 100755 index 0000000..45a9dc3 --- /dev/null +++ b/code/botlib/be_aas_main.c @@ -0,0 +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 diff --git a/code/botlib/be_aas_main.h b/code/botlib/be_aas_main.h new file mode 100755 index 0000000..13564a9 --- /dev/null +++ b/code/botlib/be_aas_main.h @@ -0,0 +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 ); diff --git a/code/botlib/be_aas_move.c b/code/botlib/be_aas_move.c new file mode 100755 index 0000000..d8b6cfd --- /dev/null +++ b/code/botlib/be_aas_move.c @@ -0,0 +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 diff --git a/code/botlib/be_aas_move.h b/code/botlib/be_aas_move.h new file mode 100755 index 0000000..5b491a4 --- /dev/null +++ b/code/botlib/be_aas_move.h @@ -0,0 +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); diff --git a/code/botlib/be_aas_optimize.c b/code/botlib/be_aas_optimize.c new file mode 100755 index 0000000..f420fd1 --- /dev/null +++ b/code/botlib/be_aas_optimize.c @@ -0,0 +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 diff --git a/code/botlib/be_aas_optimize.h b/code/botlib/be_aas_optimize.h new file mode 100755 index 0000000..15e44e6 --- /dev/null +++ b/code/botlib/be_aas_optimize.h @@ -0,0 +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); + diff --git a/code/botlib/be_aas_reach.c b/code/botlib/be_aas_reach.c new file mode 100755 index 0000000..465da80 --- /dev/null +++ b/code/botlib/be_aas_reach.c @@ -0,0 +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 diff --git a/code/botlib/be_aas_reach.h b/code/botlib/be_aas_reach.h new file mode 100755 index 0000000..e4b5ef2 --- /dev/null +++ b/code/botlib/be_aas_reach.h @@ -0,0 +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); diff --git a/code/botlib/be_aas_route.c b/code/botlib/be_aas_route.c new file mode 100755 index 0000000..441984d --- /dev/null +++ b/code/botlib/be_aas_route.c @@ -0,0 +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 diff --git a/code/botlib/be_aas_route.h b/code/botlib/be_aas_route.h new file mode 100755 index 0000000..8b92ac1 --- /dev/null +++ b/code/botlib/be_aas_route.h @@ -0,0 +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); + + diff --git a/code/botlib/be_aas_routealt.c b/code/botlib/be_aas_routealt.c new file mode 100755 index 0000000..fd7cb8d --- /dev/null +++ b/code/botlib/be_aas_routealt.c @@ -0,0 +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 diff --git a/code/botlib/be_aas_routealt.h b/code/botlib/be_aas_routealt.h new file mode 100755 index 0000000..da4ef67 --- /dev/null +++ b/code/botlib/be_aas_routealt.h @@ -0,0 +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); diff --git a/code/botlib/be_aas_sample.c b/code/botlib/be_aas_sample.c new file mode 100755 index 0000000..c0adb47 --- /dev/null +++ b/code/botlib/be_aas_sample.c @@ -0,0 +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 diff --git a/code/botlib/be_aas_sample.h b/code/botlib/be_aas_sample.h new file mode 100755 index 0000000..6c2fc7e --- /dev/null +++ b/code/botlib/be_aas_sample.h @@ -0,0 +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); + diff --git a/code/botlib/be_ai_char.c b/code/botlib/be_ai_char.c new file mode 100755 index 0000000..042adb4 --- /dev/null +++ b/code/botlib/be_ai_char.c @@ -0,0 +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 + diff --git a/code/botlib/be_ai_chat.c b/code/botlib/be_ai_chat.c new file mode 100755 index 0000000..9483e9b --- /dev/null +++ b/code/botlib/be_ai_chat.c @@ -0,0 +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 diff --git a/code/botlib/be_ai_gen.c b/code/botlib/be_ai_gen.c new file mode 100755 index 0000000..510d6b0 --- /dev/null +++ b/code/botlib/be_ai_gen.c @@ -0,0 +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 diff --git a/code/botlib/be_ai_goal.c b/code/botlib/be_ai_goal.c new file mode 100755 index 0000000..2735c57 --- /dev/null +++ b/code/botlib/be_ai_goal.c @@ -0,0 +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 diff --git a/code/botlib/be_ai_move.c b/code/botlib/be_ai_move.c new file mode 100755 index 0000000..210d4d9 --- /dev/null +++ b/code/botlib/be_ai_move.c @@ -0,0 +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 + + diff --git a/code/botlib/be_ai_weap.c b/code/botlib/be_ai_weap.c new file mode 100755 index 0000000..435292d --- /dev/null +++ b/code/botlib/be_ai_weap.c @@ -0,0 +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 + diff --git a/code/botlib/be_ai_weight.c b/code/botlib/be_ai_weight.c new file mode 100755 index 0000000..c501593 --- /dev/null +++ b/code/botlib/be_ai_weight.c @@ -0,0 +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 diff --git a/code/botlib/be_ai_weight.h b/code/botlib/be_ai_weight.h new file mode 100755 index 0000000..4fc2f6b --- /dev/null +++ b/code/botlib/be_ai_weight.h @@ -0,0 +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); diff --git a/code/botlib/be_ea.c b/code/botlib/be_ea.c new file mode 100755 index 0000000..2c57099 --- /dev/null +++ b/code/botlib/be_ea.c @@ -0,0 +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 diff --git a/code/botlib/be_interface.c b/code/botlib/be_interface.c new file mode 100755 index 0000000..b5a022b --- /dev/null +++ b/code/botlib/be_interface.c @@ -0,0 +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; +} diff --git a/code/botlib/be_interface.h b/code/botlib/be_interface.h new file mode 100755 index 0000000..a9f6347 --- /dev/null +++ b/code/botlib/be_interface.h @@ -0,0 +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); + diff --git a/code/botlib/botlib.vcproj b/code/botlib/botlib.vcproj new file mode 100755 index 0000000..e544a14 --- /dev/null +++ b/code/botlib/botlib.vcproj @@ -0,0 +1,1559 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/botlib/l_crc.c b/code/botlib/l_crc.c new file mode 100755 index 0000000..9a945f5 --- /dev/null +++ b/code/botlib/l_crc.c @@ -0,0 +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 diff --git a/code/botlib/l_crc.h b/code/botlib/l_crc.h new file mode 100755 index 0000000..3f37cd4 --- /dev/null +++ b/code/botlib/l_crc.h @@ -0,0 +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); diff --git a/code/botlib/l_libvar.c b/code/botlib/l_libvar.c new file mode 100755 index 0000000..d7ac798 --- /dev/null +++ b/code/botlib/l_libvar.c @@ -0,0 +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 diff --git a/code/botlib/l_libvar.h b/code/botlib/l_libvar.h new file mode 100755 index 0000000..79fc65e --- /dev/null +++ b/code/botlib/l_libvar.h @@ -0,0 +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); + diff --git a/code/botlib/l_log.c b/code/botlib/l_log.c new file mode 100755 index 0000000..d532c8c --- /dev/null +++ b/code/botlib/l_log.c @@ -0,0 +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 + diff --git a/code/botlib/l_log.h b/code/botlib/l_log.h new file mode 100755 index 0000000..2d07f9e --- /dev/null +++ b/code/botlib/l_log.h @@ -0,0 +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); + diff --git a/code/botlib/l_memory.c b/code/botlib/l_memory.c new file mode 100755 index 0000000..68928ab --- /dev/null +++ b/code/botlib/l_memory.c @@ -0,0 +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 diff --git a/code/botlib/l_memory.h b/code/botlib/l_memory.h new file mode 100755 index 0000000..d0cfbb4 --- /dev/null +++ b/code/botlib/l_memory.h @@ -0,0 +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); diff --git a/code/botlib/l_precomp.c b/code/botlib/l_precomp.c new file mode 100755 index 0000000..4fa708b --- /dev/null +++ b/code/botlib/l_precomp.c @@ -0,0 +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 + diff --git a/code/botlib/l_precomp.h b/code/botlib/l_precomp.h new file mode 100755 index 0000000..b2da94f --- /dev/null +++ b/code/botlib/l_precomp.h @@ -0,0 +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); diff --git a/code/botlib/l_script.c b/code/botlib/l_script.c new file mode 100755 index 0000000..2f1e4fa --- /dev/null +++ b/code/botlib/l_script.c @@ -0,0 +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 diff --git a/code/botlib/l_script.h b/code/botlib/l_script.h new file mode 100755 index 0000000..2317907 --- /dev/null +++ b/code/botlib/l_script.h @@ -0,0 +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, ...); + + diff --git a/code/botlib/l_struct.c b/code/botlib/l_struct.c new file mode 100755 index 0000000..dea3b20 --- /dev/null +++ b/code/botlib/l_struct.c @@ -0,0 +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 + diff --git a/code/botlib/l_struct.h b/code/botlib/l_struct.h new file mode 100755 index 0000000..66ac035 --- /dev/null +++ b/code/botlib/l_struct.h @@ -0,0 +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); + + diff --git a/code/botlib/l_utils.h b/code/botlib/l_utils.h new file mode 100755 index 0000000..04bcc6a --- /dev/null +++ b/code/botlib/l_utils.h @@ -0,0 +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) diff --git a/code/botlib/lcc.mak b/code/botlib/lcc.mak new file mode 100755 index 0000000..6de559e --- /dev/null +++ b/code/botlib/lcc.mak @@ -0,0 +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) $< + diff --git a/code/botlib/linux-i386.mak b/code/botlib/linux-i386.mak new file mode 100755 index 0000000..db6ee20 --- /dev/null +++ b/code/botlib/linux-i386.mak @@ -0,0 +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" +# + diff --git a/code/bspc/Conscript b/code/bspc/Conscript new file mode 100755 index 0000000..08487ab --- /dev/null +++ b/code/bspc/Conscript @@ -0,0 +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'; diff --git a/code/bspc/Makefile b/code/bspc/Makefile new file mode 100755 index 0000000..2f16169 --- /dev/null +++ b/code/bspc/Makefile @@ -0,0 +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" +# + diff --git a/code/bspc/_files.c b/code/bspc/_files.c new file mode 100755 index 0000000..8f4fcff --- /dev/null +++ b/code/bspc/_files.c @@ -0,0 +1,63 @@ +//=========================================================================== +// +// Name: _files.c +// Function: +// Programmer: Mr Elusive +// Last update: 1999-12-02 +// Tab Size: 4 +//=========================================================================== + +/* + +aas_areamerging.c //AAS area merging +aas_cfg.c //AAS configuration for different games +aas_create.c //AAS creating +aas_edgemelting.c //AAS edge melting +aas_facemerging.c //AAS face merging +aas_file.c //AAS file writing +aas_gsubdiv.c //AAS gravitational and ladder subdivision +aas_map.c //AAS map brush creation +aas_prunenodes.c //AAS node pruning +aas_store.c //AAS file storing + +map.c //map file loading and writing +map_hl.c //Half-Life map loading +map_q1.c //Quake1 map loading +map_q2.c //Quake2 map loading +map_q3.c //Quake3 map loading +map_sin.c //Sin map loading +tree.c //BSP tree management + node pruning (*) +brushbsp.c //brush bsp creation (*) +portals.c //BSP portal creation and leaf filling (*) +csg.c //Constructive Solid Geometry brush chopping (*) +leakfile.c //leak file writing (*) +textures.c //Quake2 BSP textures (*) + +l_bsp_ent.c //BSP entity parsing +l_bsp_hl.c //Half-Life BSP loading and writing +l_bsp_q1.c //Quake1 BSP loading and writing +l_bsp_q2.c //Quake2 BSP loading and writing +l_bsp_q3.c //Quake2 BSP loading and writing +l_bsp_sin.c //Sin BSP loading and writing +l_cmd.c //cmd library +l_log.c //log file library +l_math.c //math library +l_mem.c //memory management library +l_poly.c //polygon (winding) library +l_script.c //script file parsing library +l_threads.c //multi-threading library +l_utils.c //utility library +l_qfiles.c //loading of quake files + +gldraw.c //GL drawing (*) +glfile.c //GL file writing (*) +nodraw.c //no draw module (*) + +bspc.c //BSPC Win32 console version +winbspc.c //WinBSPC Win32 GUI version +win32_terminal.c //Win32 terminal output +win32_qfiles.c //Win32 game file management (also .pak .sin) +win32_font.c //Win32 fonts +win32_folder.c //Win32 folder dialogs + +*/ diff --git a/code/bspc/aas_areamerging.c b/code/bspc/aas_areamerging.c new file mode 100755 index 0000000..c5f82d2 --- /dev/null +++ b/code/bspc/aas_areamerging.c @@ -0,0 +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 diff --git a/code/bspc/aas_areamerging.h b/code/bspc/aas_areamerging.h new file mode 100755 index 0000000..c0b39a4 --- /dev/null +++ b/code/bspc/aas_areamerging.h @@ -0,0 +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); + diff --git a/code/bspc/aas_cfg.c b/code/bspc/aas_cfg.c new file mode 100755 index 0000000..3aeb569 --- /dev/null +++ b/code/bspc/aas_cfg.c @@ -0,0 +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 diff --git a/code/bspc/aas_cfg.h b/code/bspc/aas_cfg.h new file mode 100755 index 0000000..25537c6 --- /dev/null +++ b/code/bspc/aas_cfg.h @@ -0,0 +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); diff --git a/code/bspc/aas_create.c b/code/bspc/aas_create.c new file mode 100755 index 0000000..914b25b --- /dev/null +++ b/code/bspc/aas_create.c @@ -0,0 +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 diff --git a/code/bspc/aas_create.h b/code/bspc/aas_create.h new file mode 100755 index 0000000..cc5693c --- /dev/null +++ b/code/bspc/aas_create.h @@ -0,0 +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); diff --git a/code/bspc/aas_edgemelting.c b/code/bspc/aas_edgemelting.c new file mode 100755 index 0000000..e84f48b --- /dev/null +++ b/code/bspc/aas_edgemelting.c @@ -0,0 +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 + diff --git a/code/bspc/aas_edgemelting.h b/code/bspc/aas_edgemelting.h new file mode 100755 index 0000000..4c03e97 --- /dev/null +++ b/code/bspc/aas_edgemelting.h @@ -0,0 +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); + diff --git a/code/bspc/aas_facemerging.c b/code/bspc/aas_facemerging.c new file mode 100755 index 0000000..bf170de --- /dev/null +++ b/code/bspc/aas_facemerging.c @@ -0,0 +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 diff --git a/code/bspc/aas_facemerging.h b/code/bspc/aas_facemerging.h new file mode 100755 index 0000000..5a81735 --- /dev/null +++ b/code/bspc/aas_facemerging.h @@ -0,0 +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); diff --git a/code/bspc/aas_file.c b/code/bspc/aas_file.c new file mode 100755 index 0000000..9f41639 --- /dev/null +++ b/code/bspc/aas_file.c @@ -0,0 +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 + diff --git a/code/bspc/aas_file.h b/code/bspc/aas_file.h new file mode 100755 index 0000000..5176461 --- /dev/null +++ b/code/bspc/aas_file.h @@ -0,0 +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); + diff --git a/code/bspc/aas_gsubdiv.c b/code/bspc/aas_gsubdiv.c new file mode 100755 index 0000000..d9ba597 --- /dev/null +++ b/code/bspc/aas_gsubdiv.c @@ -0,0 +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 diff --git a/code/bspc/aas_gsubdiv.h b/code/bspc/aas_gsubdiv.h new file mode 100755 index 0000000..0156a9a --- /dev/null +++ b/code/bspc/aas_gsubdiv.h @@ -0,0 +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); diff --git a/code/bspc/aas_map.c b/code/bspc/aas_map.c new file mode 100755 index 0000000..da16d1b --- /dev/null +++ b/code/bspc/aas_map.c @@ -0,0 +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 diff --git a/code/bspc/aas_map.h b/code/bspc/aas_map.h new file mode 100755 index 0000000..601cdfb --- /dev/null +++ b/code/bspc/aas_map.h @@ -0,0 +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); diff --git a/code/bspc/aas_prunenodes.c b/code/bspc/aas_prunenodes.c new file mode 100755 index 0000000..a03e9d2 --- /dev/null +++ b/code/bspc/aas_prunenodes.c @@ -0,0 +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 diff --git a/code/bspc/aas_prunenodes.h b/code/bspc/aas_prunenodes.h new file mode 100755 index 0000000..36989ad --- /dev/null +++ b/code/bspc/aas_prunenodes.h @@ -0,0 +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); + diff --git a/code/bspc/aas_store.c b/code/bspc/aas_store.c new file mode 100755 index 0000000..4836d51 --- /dev/null +++ b/code/bspc/aas_store.c @@ -0,0 +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 diff --git a/code/bspc/aas_store.h b/code/bspc/aas_store.h new file mode 100755 index 0000000..26957e4 --- /dev/null +++ b/code/bspc/aas_store.h @@ -0,0 +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); diff --git a/code/bspc/aasfile.h b/code/bspc/aasfile.h new file mode 100755 index 0000000..7ed8677 --- /dev/null +++ b/code/bspc/aasfile.h @@ -0,0 +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 +*/ diff --git a/code/bspc/be_aas_bspc.c b/code/bspc/be_aas_bspc.c new file mode 100755 index 0000000..127549c --- /dev/null +++ b/code/bspc/be_aas_bspc.c @@ -0,0 +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 diff --git a/code/bspc/be_aas_bspc.h b/code/bspc/be_aas_bspc.h new file mode 100755 index 0000000..72e488a --- /dev/null +++ b/code/bspc/be_aas_bspc.h @@ -0,0 +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); diff --git a/code/bspc/brushbsp.c b/code/bspc/brushbsp.c new file mode 100755 index 0000000..22cb0dd --- /dev/null +++ b/code/bspc/brushbsp.c @@ -0,0 +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 + diff --git a/code/bspc/bspc.c b/code/bspc/bspc.c new file mode 100755 index 0000000..4f58b53 --- /dev/null +++ b/code/bspc/bspc.c @@ -0,0 +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 + diff --git a/code/bspc/bspc.sln b/code/bspc/bspc.sln new file mode 100755 index 0000000..79dc8ae --- /dev/null +++ b/code/bspc/bspc.sln @@ -0,0 +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 diff --git a/code/bspc/bspc.vcproj b/code/bspc/bspc.vcproj new file mode 100755 index 0000000..6171e1a --- /dev/null +++ b/code/bspc/bspc.vcproj @@ -0,0 +1,1381 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/bspc/cfgq3.c b/code/bspc/cfgq3.c new file mode 100755 index 0000000..588509c --- /dev/null +++ b/code/bspc/cfgq3.c @@ -0,0 +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 diff --git a/code/bspc/csg.c b/code/bspc/csg.c new file mode 100755 index 0000000..c141075 --- /dev/null +++ b/code/bspc/csg.c @@ -0,0 +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 diff --git a/code/bspc/faces.c b/code/bspc/faces.c new file mode 100755 index 0000000..bcd3ef9 --- /dev/null +++ b/code/bspc/faces.c @@ -0,0 +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); +} diff --git a/code/bspc/gldraw.c b/code/bspc/gldraw.c new file mode 100755 index 0000000..9f7d6f7 --- /dev/null +++ b/code/bspc/gldraw.c @@ -0,0 +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; +} diff --git a/code/bspc/glfile.c b/code/bspc/glfile.c new file mode 100755 index 0000000..179a836 --- /dev/null +++ b/code/bspc/glfile.c @@ -0,0 +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); +} + diff --git a/code/bspc/l_bsp_ent.c b/code/bspc/l_bsp_ent.c new file mode 100755 index 0000000..8260179 --- /dev/null +++ b/code/bspc/l_bsp_ent.c @@ -0,0 +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; +} + + diff --git a/code/bspc/l_bsp_ent.h b/code/bspc/l_bsp_ent.h new file mode 100755 index 0000000..05dd162 --- /dev/null +++ b/code/bspc/l_bsp_ent.h @@ -0,0 +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); + diff --git a/code/bspc/l_bsp_hl.c b/code/bspc/l_bsp_hl.c new file mode 100755 index 0000000..5b922b5 --- /dev/null +++ b/code/bspc/l_bsp_hl.c @@ -0,0 +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; +} //*/ diff --git a/code/bspc/l_bsp_hl.h b/code/bspc/l_bsp_hl.h new file mode 100755 index 0000000..0089b1e --- /dev/null +++ b/code/bspc/l_bsp_hl.h @@ -0,0 +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 diff --git a/code/bspc/l_bsp_q1.c b/code/bspc/l_bsp_q1.c new file mode 100755 index 0000000..c68b5c8 --- /dev/null +++ b/code/bspc/l_bsp_q1.c @@ -0,0 +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 diff --git a/code/bspc/l_bsp_q1.h b/code/bspc/l_bsp_q1.h new file mode 100755 index 0000000..1c2cd92 --- /dev/null +++ b/code/bspc/l_bsp_q1.h @@ -0,0 +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 diff --git a/code/bspc/l_bsp_q2.c b/code/bspc/l_bsp_q2.c new file mode 100755 index 0000000..d5e9844 --- /dev/null +++ b/code/bspc/l_bsp_q2.c @@ -0,0 +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 + diff --git a/code/bspc/l_bsp_q2.h b/code/bspc/l_bsp_q2.h new file mode 100755 index 0000000..8d937a8 --- /dev/null +++ b/code/bspc/l_bsp_q2.h @@ -0,0 +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); + diff --git a/code/bspc/l_bsp_q3.c b/code/bspc/l_bsp_q3.c new file mode 100755 index 0000000..19a3941 --- /dev/null +++ b/code/bspc/l_bsp_q3.c @@ -0,0 +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 + + diff --git a/code/bspc/l_bsp_q3.h b/code/bspc/l_bsp_q3.h new file mode 100755 index 0000000..6ac175c --- /dev/null +++ b/code/bspc/l_bsp_q3.h @@ -0,0 +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); diff --git a/code/bspc/l_bsp_sin.c b/code/bspc/l_bsp_sin.c new file mode 100755 index 0000000..de51528 --- /dev/null +++ b/code/bspc/l_bsp_sin.c @@ -0,0 +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); +} diff --git a/code/bspc/l_bsp_sin.h b/code/bspc/l_bsp_sin.h new file mode 100755 index 0000000..93cb69f --- /dev/null +++ b/code/bspc/l_bsp_sin.h @@ -0,0 +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); + diff --git a/code/bspc/l_cmd.c b/code/bspc/l_cmd.c new file mode 100755 index 0000000..35fef7b --- /dev/null +++ b/code/bspc/l_cmd.c @@ -0,0 +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 diff --git a/code/bspc/l_cmd.h b/code/bspc/l_cmd.h new file mode 100755 index 0000000..b5497b4 --- /dev/null +++ b/code/bspc/l_cmd.h @@ -0,0 +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 + diff --git a/code/bspc/l_log.c b/code/bspc/l_log.c new file mode 100755 index 0000000..b685ed3 --- /dev/null +++ b/code/bspc/l_log.c @@ -0,0 +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 + diff --git a/code/bspc/l_log.h b/code/bspc/l_log.h new file mode 100755 index 0000000..eff7bf6 --- /dev/null +++ b/code/bspc/l_log.h @@ -0,0 +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 diff --git a/code/bspc/l_math.c b/code/bspc/l_math.c new file mode 100755 index 0000000..caf3419 --- /dev/null +++ b/code/bspc/l_math.c @@ -0,0 +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; + } +} diff --git a/code/bspc/l_math.h b/code/bspc/l_math.h new file mode 100755 index 0000000..1df7b61 --- /dev/null +++ b/code/bspc/l_math.h @@ -0,0 +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 diff --git a/code/bspc/l_mem.c b/code/bspc/l_mem.c new file mode 100755 index 0000000..831543e --- /dev/null +++ b/code/bspc/l_mem.c @@ -0,0 +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 diff --git a/code/bspc/l_mem.h b/code/bspc/l_mem.h new file mode 100755 index 0000000..d517743 --- /dev/null +++ b/code/bspc/l_mem.h @@ -0,0 +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); + diff --git a/code/bspc/l_poly.c b/code/bspc/l_poly.c new file mode 100755 index 0000000..fea2bef --- /dev/null +++ b/code/bspc/l_poly.c @@ -0,0 +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 diff --git a/code/bspc/l_poly.h b/code/bspc/l_poly.h new file mode 100755 index 0000000..a14b834 --- /dev/null +++ b/code/bspc/l_poly.h @@ -0,0 +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 + diff --git a/code/bspc/l_qfiles.c b/code/bspc/l_qfiles.c new file mode 100755 index 0000000..32cb6de --- /dev/null +++ b/code/bspc/l_qfiles.c @@ -0,0 +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 diff --git a/code/bspc/l_qfiles.h b/code/bspc/l_qfiles.h new file mode 100755 index 0000000..308c608 --- /dev/null +++ b/code/bspc/l_qfiles.h @@ -0,0 +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); diff --git a/code/bspc/l_threads.c b/code/bspc/l_threads.c new file mode 100755 index 0000000..bc4b998 --- /dev/null +++ b/code/bspc/l_threads.c @@ -0,0 +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 diff --git a/code/bspc/l_threads.h b/code/bspc/l_threads.h new file mode 100755 index 0000000..2c6d8df --- /dev/null +++ b/code/bspc/l_threads.h @@ -0,0 +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); + diff --git a/code/bspc/l_utils.c b/code/bspc/l_utils.c new file mode 100755 index 0000000..f9d6d5f --- /dev/null +++ b/code/bspc/l_utils.c @@ -0,0 +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 diff --git a/code/bspc/l_utils.h b/code/bspc/l_utils.h new file mode 100755 index 0000000..2ef9161 --- /dev/null +++ b/code/bspc/l_utils.h @@ -0,0 +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 + + + diff --git a/code/bspc/lcc.mak b/code/bspc/lcc.mak new file mode 100755 index 0000000..d74adce --- /dev/null +++ b/code/bspc/lcc.mak @@ -0,0 +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) $< + diff --git a/code/bspc/leakfile.c b/code/bspc/leakfile.c new file mode 100755 index 0000000..7118759 --- /dev/null +++ b/code/bspc/leakfile.c @@ -0,0 +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); +} + diff --git a/code/bspc/linux-i386.mak b/code/bspc/linux-i386.mak new file mode 100755 index 0000000..00a261d --- /dev/null +++ b/code/bspc/linux-i386.mak @@ -0,0 +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" +# + diff --git a/code/bspc/map.c b/code/bspc/map.c new file mode 100755 index 0000000..613c234 --- /dev/null +++ b/code/bspc/map.c @@ -0,0 +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 diff --git a/code/bspc/map_hl.c b/code/bspc/map_hl.c new file mode 100755 index 0000000..7e6037b --- /dev/null +++ b/code/bspc/map_hl.c @@ -0,0 +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 diff --git a/code/bspc/map_q1.c b/code/bspc/map_q1.c new file mode 100755 index 0000000..772a358 --- /dev/null +++ b/code/bspc/map_q1.c @@ -0,0 +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 diff --git a/code/bspc/map_q2.c b/code/bspc/map_q2.c new file mode 100755 index 0000000..0f15216 --- /dev/null +++ b/code/bspc/map_q2.c @@ -0,0 +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 + diff --git a/code/bspc/map_q3.c b/code/bspc/map_q3.c new file mode 100755 index 0000000..750b304 --- /dev/null +++ b/code/bspc/map_q3.c @@ -0,0 +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 + diff --git a/code/bspc/map_sin.c b/code/bspc/map_sin.c new file mode 100755 index 0000000..7794b8c --- /dev/null +++ b/code/bspc/map_sin.c @@ -0,0 +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 diff --git a/code/bspc/nodraw.c b/code/bspc/nodraw.c new file mode 100755 index 0000000..b5442dc --- /dev/null +++ b/code/bspc/nodraw.c @@ -0,0 +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) +{ +} diff --git a/code/bspc/portals.c b/code/bspc/portals.c new file mode 100755 index 0000000..3d3389d --- /dev/null +++ b/code/bspc/portals.c @@ -0,0 +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 + diff --git a/code/bspc/prtfile.c b/code/bspc/prtfile.c new file mode 100755 index 0000000..69834aa --- /dev/null +++ b/code/bspc/prtfile.c @@ -0,0 +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); +} + diff --git a/code/bspc/q2files.h b/code/bspc/q2files.h new file mode 100755 index 0000000..5ffea6e --- /dev/null +++ b/code/bspc/q2files.h @@ -0,0 +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; diff --git a/code/bspc/q3files.h b/code/bspc/q3files.h new file mode 100755 index 0000000..b251cc0 --- /dev/null +++ b/code/bspc/q3files.h @@ -0,0 +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 diff --git a/code/bspc/qbsp.h b/code/bspc/qbsp.h new file mode 100755 index 0000000..7ebf9d4 --- /dev/null +++ b/code/bspc/qbsp.h @@ -0,0 +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); diff --git a/code/bspc/qfiles.h b/code/bspc/qfiles.h new file mode 100755 index 0000000..5ffea6e --- /dev/null +++ b/code/bspc/qfiles.h @@ -0,0 +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; diff --git a/code/bspc/sinfiles.h b/code/bspc/sinfiles.h new file mode 100755 index 0000000..31091c5 --- /dev/null +++ b/code/bspc/sinfiles.h @@ -0,0 +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; diff --git a/code/bspc/tetrahedron.c b/code/bspc/tetrahedron.c new file mode 100755 index 0000000..36c10af --- /dev/null +++ b/code/bspc/tetrahedron.c @@ -0,0 +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 diff --git a/code/bspc/tetrahedron.h b/code/bspc/tetrahedron.h new file mode 100755 index 0000000..89003f1 --- /dev/null +++ b/code/bspc/tetrahedron.h @@ -0,0 +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); + diff --git a/code/bspc/textures.c b/code/bspc/textures.c new file mode 100755 index 0000000..ad6a8f5 --- /dev/null +++ b/code/bspc/textures.c @@ -0,0 +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 diff --git a/code/bspc/tree.c b/code/bspc/tree.c new file mode 100755 index 0000000..3d2ee9c --- /dev/null +++ b/code/bspc/tree.c @@ -0,0 +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 diff --git a/code/bspc/writebsp.c b/code/bspc/writebsp.c new file mode 100755 index 0000000..38e9980 --- /dev/null +++ b/code/bspc/writebsp.c @@ -0,0 +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++; +} + diff --git a/code/cgame.lnt b/code/cgame.lnt new file mode 100755 index 0000000..e1801aa --- /dev/null +++ b/code/cgame.lnt @@ -0,0 +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 diff --git a/code/cgame/Conscript b/code/cgame/Conscript new file mode 100755 index 0000000..887a2a1 --- /dev/null +++ b/code/cgame/Conscript @@ -0,0 +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'; + } +} diff --git a/code/cgame/cg_consolecmds.c b/code/cgame/cg_consolecmds.c new file mode 100755 index 0000000..6f13a22 --- /dev/null +++ b/code/cgame/cg_consolecmds.c @@ -0,0 +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 +} diff --git a/code/cgame/cg_draw.c b/code/cgame/cg_draw.c new file mode 100755 index 0000000..e29c10f --- /dev/null +++ b/code/cgame/cg_draw.c @@ -0,0 +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(); +} + + + diff --git a/code/cgame/cg_drawtools.c b/code/cgame/cg_drawtools.c new file mode 100755 index 0000000..9c27606 --- /dev/null +++ b/code/cgame/cg_drawtools.c @@ -0,0 +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 diff --git a/code/cgame/cg_effects.c b/code/cgame/cg_effects.c new file mode 100755 index 0000000..1ea447d --- /dev/null +++ b/code/cgame/cg_effects.c @@ -0,0 +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 ); +} + diff --git a/code/cgame/cg_ents.c b/code/cgame/cg_ents.c new file mode 100755 index 0000000..5855089 --- /dev/null +++ b/code/cgame/cg_ents.c @@ -0,0 +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 ); + } +} + diff --git a/code/cgame/cg_event.c b/code/cgame/cg_event.c new file mode 100755 index 0000000..86167c6 --- /dev/null +++ b/code/cgame/cg_event.c @@ -0,0 +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 ); +} + diff --git a/code/cgame/cg_info.c b/code/cgame/cg_info.c new file mode 100755 index 0000000..04124d1 --- /dev/null +++ b/code/cgame/cg_info.c @@ -0,0 +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; + } + } +} + diff --git a/code/cgame/cg_local.h b/code/cgame/cg_local.h new file mode 100755 index 0000000..4dd5c9d --- /dev/null +++ b/code/cgame/cg_local.h @@ -0,0 +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 ); + + diff --git a/code/cgame/cg_localents.c b/code/cgame/cg_localents.c new file mode 100755 index 0000000..534b147 --- /dev/null +++ b/code/cgame/cg_localents.c @@ -0,0 +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 + } + } +} + + + + diff --git a/code/cgame/cg_main.c b/code/cgame/cg_main.c new file mode 100755 index 0000000..aa9ec27 --- /dev/null +++ b/code/cgame/cg_main.c @@ -0,0 +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 + diff --git a/code/cgame/cg_marks.c b/code/cgame/cg_marks.c new file mode 100755 index 0000000..4c648d2 --- /dev/null +++ b/code/cgame/cg_marks.c @@ -0,0 +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; +} + diff --git a/code/cgame/cg_newdraw.c b/code/cgame/cg_newdraw.c new file mode 100755 index 0000000..28335fe --- /dev/null +++ b/code/cgame/cg_newdraw.c @@ -0,0 +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; + } +} + diff --git a/code/cgame/cg_particles.c b/code/cgame/cg_particles.c new file mode 100755 index 0000000..b3ce322 --- /dev/null +++ b/code/cgame/cg_particles.c @@ -0,0 +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; +} diff --git a/code/cgame/cg_players.c b/code/cgame/cg_players.c new file mode 100755 index 0000000..2fd7b73 --- /dev/null +++ b/code/cgame/cg_players.c @@ -0,0 +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 ); + } +} + diff --git a/code/cgame/cg_playerstate.c b/code/cgame/cg_playerstate.c new file mode 100755 index 0000000..1e0d61a --- /dev/null +++ b/code/cgame/cg_playerstate.c @@ -0,0 +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; + } +} + diff --git a/code/cgame/cg_predict.c b/code/cgame/cg_predict.c new file mode 100755 index 0000000..fec2cfb --- /dev/null +++ b/code/cgame/cg_predict.c @@ -0,0 +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; + } + } +} + + diff --git a/code/cgame/cg_public.h b/code/cgame/cg_public.h new file mode 100755 index 0000000..8514143 --- /dev/null +++ b/code/cgame/cg_public.h @@ -0,0 +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; + +//---------------------------------------------- diff --git a/code/cgame/cg_scoreboard.c b/code/cgame/cg_scoreboard.c new file mode 100755 index 0000000..25966c9 --- /dev/null +++ b/code/cgame/cg_scoreboard.c @@ -0,0 +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; + } + } + + +} + diff --git a/code/cgame/cg_servercmds.c b/code/cgame/cg_servercmds.c new file mode 100755 index 0000000..d12cd97 --- /dev/null +++ b/code/cgame/cg_servercmds.c @@ -0,0 +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(); + } + } +} diff --git a/code/cgame/cg_snapshot.c b/code/cgame/cg_snapshot.c new file mode 100755 index 0000000..add49f0 --- /dev/null +++ b/code/cgame/cg_snapshot.c @@ -0,0 +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" ); + } + +} + diff --git a/code/cgame/cg_syscalls.asm b/code/cgame/cg_syscalls.asm new file mode 100755 index 0000000..8363fe3 --- /dev/null +++ b/code/cgame/cg_syscalls.asm @@ -0,0 +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 + diff --git a/code/cgame/cg_syscalls.c b/code/cgame/cg_syscalls.c new file mode 100755 index 0000000..a7bf659 --- /dev/null +++ b/code/cgame/cg_syscalls.c @@ -0,0 +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 ); +} diff --git a/code/cgame/cg_view.c b/code/cgame/cg_view.c new file mode 100755 index 0000000..83d5141 --- /dev/null +++ b/code/cgame/cg_view.c @@ -0,0 +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 ); + } + + +} + diff --git a/code/cgame/cg_weapons.c b/code/cgame/cg_weapons.c new file mode 100755 index 0000000..9ac1ca9 --- /dev/null +++ b/code/cgame/cg_weapons.c @@ -0,0 +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 ); + } + +} diff --git a/code/cgame/cgame.bat b/code/cgame/cgame.bat new file mode 100755 index 0000000..6e13523 --- /dev/null +++ b/code/cgame/cgame.bat @@ -0,0 +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 .. diff --git a/code/cgame/cgame.def b/code/cgame/cgame.def new file mode 100755 index 0000000..01861ba --- /dev/null +++ b/code/cgame/cgame.def @@ -0,0 +1,3 @@ +EXPORTS + vmMain + dllEntry diff --git a/code/cgame/cgame.plg b/code/cgame/cgame.plg new file mode 100755 index 0000000..b7c80ee --- /dev/null +++ b/code/cgame/cgame.plg @@ -0,0 +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) +
+ + diff --git a/code/cgame/cgame.q3asm b/code/cgame/cgame.q3asm new file mode 100755 index 0000000..7923f73 --- /dev/null +++ b/code/cgame/cgame.q3asm @@ -0,0 +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 diff --git a/code/cgame/cgame.sh b/code/cgame/cgame.sh new file mode 100755 index 0000000..62f2570 --- /dev/null +++ b/code/cgame/cgame.sh @@ -0,0 +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 .. diff --git a/code/cgame/cgame.vcproj b/code/cgame/cgame.vcproj new file mode 100755 index 0000000..4dbdfd0 --- /dev/null +++ b/code/cgame/cgame.vcproj @@ -0,0 +1,1221 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/cgame/cgame_ta.bat b/code/cgame/cgame_ta.bat new file mode 100755 index 0000000..266e017 --- /dev/null +++ b/code/cgame/cgame_ta.bat @@ -0,0 +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 .. diff --git a/code/cgame/cgame_ta.q3asm b/code/cgame/cgame_ta.q3asm new file mode 100755 index 0000000..8b14ed9 --- /dev/null +++ b/code/cgame/cgame_ta.q3asm @@ -0,0 +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 diff --git a/code/cgame/cgame_ta.sh b/code/cgame/cgame_ta.sh new file mode 100755 index 0000000..5f87eb5 --- /dev/null +++ b/code/cgame/cgame_ta.sh @@ -0,0 +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 .. diff --git a/code/cgame/tr_types.h b/code/cgame/tr_types.h new file mode 100755 index 0000000..ec2e054 --- /dev/null +++ b/code/cgame/tr_types.h @@ -0,0 +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 diff --git a/code/clean.bat b/code/clean.bat new file mode 100755 index 0000000..3daad51 --- /dev/null +++ b/code/clean.bat @@ -0,0 +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 diff --git a/code/client/cl_cgame.c b/code/client/cl_cgame.c new file mode 100755 index 0000000..0b259d3 --- /dev/null +++ b/code/client/cl_cgame.c @@ -0,0 +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 + } + } + +} + + + diff --git a/code/client/cl_cin.c b/code/client/cl_cin.c new file mode 100755 index 0000000..7929624 --- /dev/null +++ b/code/client/cl_cin.c @@ -0,0 +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--; + } + } +} + diff --git a/code/client/cl_console.c b/code/client/cl_console.c new file mode 100755 index 0000000..50df0e2 --- /dev/null +++ b/code/client/cl_console.c @@ -0,0 +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; +} diff --git a/code/client/cl_input.c b/code/client/cl_input.c new file mode 100755 index 0000000..f450ee7 --- /dev/null +++ b/code/client/cl_input.c @@ -0,0 +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); +} diff --git a/code/client/cl_keys.c b/code/client/cl_keys.c new file mode 100755 index 0000000..7dcd7d8 --- /dev/null +++ b/code/client/cl_keys.c @@ -0,0 +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; + } +} + diff --git a/code/client/cl_main.c b/code/client/cl_main.c new file mode 100755 index 0000000..89d3b48 --- /dev/null +++ b/code/client/cl_main.c @@ -0,0 +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; +} + + diff --git a/code/client/cl_net_chan.c b/code/client/cl_net_chan.c new file mode 100755 index 0000000..d1e0b08 --- /dev/null +++ b/code/client/cl_net_chan.c @@ -0,0 +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; +} diff --git a/code/client/cl_parse.c b/code/client/cl_parse.c new file mode 100755 index 0000000..9de3588 --- /dev/null +++ b/code/client/cl_parse.c @@ -0,0 +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; + } + } +} + + diff --git a/code/client/cl_scrn.c b/code/client/cl_scrn.c new file mode 100755 index 0000000..c4ccb40 --- /dev/null +++ b/code/client/cl_scrn.c @@ -0,0 +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; +} + diff --git a/code/client/cl_ui.c b/code/client/cl_ui.c new file mode 100755 index 0000000..80040a8 --- /dev/null +++ b/code/client/cl_ui.c @@ -0,0 +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 ); +} diff --git a/code/client/client.h b/code/client/client.h new file mode 100755 index 0000000..09e02a8 --- /dev/null +++ b/code/client/client.h @@ -0,0 +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 ); diff --git a/code/client/keys.h b/code/client/keys.h new file mode 100755 index 0000000..bc598f4 --- /dev/null +++ b/code/client/keys.h @@ -0,0 +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); diff --git a/code/client/snd_adpcm.c b/code/client/snd_adpcm.c new file mode 100755 index 0000000..bc52ad4 --- /dev/null +++ b/code/client/snd_adpcm.c @@ -0,0 +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; + } +} diff --git a/code/client/snd_dma.c b/code/client/snd_dma.c new file mode 100755 index 0000000..6ecff18 --- /dev/null +++ b/code/client/snd_dma.c @@ -0,0 +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; +} diff --git a/code/client/snd_local.h b/code/client/snd_local.h new file mode 100755 index 0000000..5b0c97a --- /dev/null +++ b/code/client/snd_local.h @@ -0,0 +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; + diff --git a/code/client/snd_mem.c b/code/client/snd_mem.c new file mode 100755 index 0000000..0e400d5 --- /dev/null +++ b/code/client/snd_mem.c @@ -0,0 +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); +} diff --git a/code/client/snd_mix.c b/code/client/snd_mix.c new file mode 100755 index 0000000..ec983b9 --- /dev/null +++ b/code/client/snd_mix.c @@ -0,0 +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; + } +} diff --git a/code/client/snd_public.h b/code/client/snd_public.h new file mode 100755 index 0000000..438fc53 --- /dev/null +++ b/code/client/snd_public.h @@ -0,0 +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 ); diff --git a/code/client/snd_wavelet.c b/code/client/snd_wavelet.c new file mode 100755 index 0000000..9b07ee1 --- /dev/null +++ b/code/client/snd_wavelet.c @@ -0,0 +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 '#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 new file mode 100755 index 0000000..b5485e9 --- /dev/null +++ b/code/game/ai_chat.c @@ -0,0 +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); + } +} diff --git a/code/game/ai_chat.h b/code/game/ai_chat.h new file mode 100755 index 0000000..98711c4 --- /dev/null +++ b/code/game/ai_chat.h @@ -0,0 +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); + diff --git a/code/game/ai_cmd.c b/code/game/ai_cmd.c new file mode 100755 index 0000000..802d7d8 --- /dev/null +++ b/code/game/ai_cmd.c @@ -0,0 +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; +} diff --git a/code/game/ai_cmd.h b/code/game/ai_cmd.h new file mode 100755 index 0000000..511c57a --- /dev/null +++ b/code/game/ai_cmd.h @@ -0,0 +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); + diff --git a/code/game/ai_dmnet.c b/code/game/ai_dmnet.c new file mode 100755 index 0000000..3bf3a8a --- /dev/null +++ b/code/game/ai_dmnet.c @@ -0,0 +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; +} + diff --git a/code/game/ai_dmnet.h b/code/game/ai_dmnet.h new file mode 100755 index 0000000..a5c4c9a --- /dev/null +++ b/code/game/ai_dmnet.h @@ -0,0 +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); + diff --git a/code/game/ai_dmq3.c b/code/game/ai_dmq3.c new file mode 100755 index 0000000..ed7f4c1 --- /dev/null +++ b/code/game/ai_dmq3.c @@ -0,0 +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; +} diff --git a/code/game/ai_dmq3.h b/code/game/ai_dmq3.h new file mode 100755 index 0000000..b7ab19d --- /dev/null +++ b/code/game/ai_dmq3.h @@ -0,0 +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 diff --git a/code/game/ai_main.c b/code/game/ai_main.c new file mode 100755 index 0000000..d13696a --- /dev/null +++ b/code/game/ai_main.c @@ -0,0 +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; +} + diff --git a/code/game/ai_main.h b/code/game/ai_main.h new file mode 100755 index 0000000..91e8895 --- /dev/null +++ b/code/game/ai_main.h @@ -0,0 +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); diff --git a/code/game/ai_team.c b/code/game/ai_team.c new file mode 100755 index 0000000..9215702 --- /dev/null +++ b/code/game/ai_team.c @@ -0,0 +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 + } +} + diff --git a/code/game/ai_team.h b/code/game/ai_team.h new file mode 100755 index 0000000..fbe4a9e --- /dev/null +++ b/code/game/ai_team.h @@ -0,0 +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); + + diff --git a/code/game/ai_vcmd.c b/code/game/ai_vcmd.c new file mode 100755 index 0000000..812fe8f --- /dev/null +++ b/code/game/ai_vcmd.c @@ -0,0 +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; +} diff --git a/code/game/ai_vcmd.h b/code/game/ai_vcmd.h new file mode 100755 index 0000000..0eef6ac --- /dev/null +++ b/code/game/ai_vcmd.h @@ -0,0 +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); + + diff --git a/code/game/be_aas.h b/code/game/be_aas.h new file mode 100755 index 0000000..24f982c --- /dev/null +++ b/code/game/be_aas.h @@ -0,0 +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; diff --git a/code/game/be_ai_char.h b/code/game/be_ai_char.h new file mode 100755 index 0000000..89f1e52 --- /dev/null +++ b/code/game/be_ai_char.h @@ -0,0 +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); diff --git a/code/game/be_ai_chat.h b/code/game/be_ai_chat.h new file mode 100755 index 0000000..8d3d7a4 --- /dev/null +++ b/code/game/be_ai_chat.h @@ -0,0 +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); + diff --git a/code/game/be_ai_gen.h b/code/game/be_ai_gen.h new file mode 100755 index 0000000..94528ed --- /dev/null +++ b/code/game/be_ai_gen.h @@ -0,0 +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); diff --git a/code/game/be_ai_goal.h b/code/game/be_ai_goal.h new file mode 100755 index 0000000..1fdddf3 --- /dev/null +++ b/code/game/be_ai_goal.h @@ -0,0 +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); diff --git a/code/game/be_ai_move.h b/code/game/be_ai_move.h new file mode 100755 index 0000000..1f0c756 --- /dev/null +++ b/code/game/be_ai_move.h @@ -0,0 +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); + diff --git a/code/game/be_ai_weap.h b/code/game/be_ai_weap.h new file mode 100755 index 0000000..6dbb5a2 --- /dev/null +++ b/code/game/be_ai_weap.h @@ -0,0 +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); diff --git a/code/game/be_ea.h b/code/game/be_ea.h new file mode 100755 index 0000000..01cc7e5 --- /dev/null +++ b/code/game/be_ea.h @@ -0,0 +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); diff --git a/code/game/bg_lib.c b/code/game/bg_lib.c new file mode 100755 index 0000000..a5ee53e --- /dev/null +++ b/code/game/bg_lib.c @@ -0,0 +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 diff --git a/code/game/bg_lib.h b/code/game/bg_lib.h new file mode 100755 index 0000000..57333c0 --- /dev/null +++ b/code/game/bg_lib.h @@ -0,0 +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 ); + diff --git a/code/game/bg_local.h b/code/game/bg_local.h new file mode 100755 index 0000000..223c688 --- /dev/null +++ b/code/game/bg_local.h @@ -0,0 +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 ); + + diff --git a/code/game/bg_misc.c b/code/game/bg_misc.c new file mode 100755 index 0000000..f729f06 --- /dev/null +++ b/code/game/bg_misc.c @@ -0,0 +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; +} diff --git a/code/game/bg_pmove.c b/code/game/bg_pmove.c new file mode 100755 index 0000000..3711869 --- /dev/null +++ b/code/game/bg_pmove.c @@ -0,0 +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(); + +} + diff --git a/code/game/bg_public.h b/code/game/bg_public.h new file mode 100755 index 0000000..0dd59db --- /dev/null +++ b/code/game/bg_public.h @@ -0,0 +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 + diff --git a/code/game/bg_slidemove.c b/code/game/bg_slidemove.c new file mode 100755 index 0000000..ed1349f --- /dev/null +++ b/code/game/bg_slidemove.c @@ -0,0 +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); + } + } +} + diff --git a/code/game/botlib.h b/code/game/botlib.h new file mode 100755 index 0000000..a9b45db --- /dev/null +++ b/code/game/botlib.h @@ -0,0 +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 + +*/ + diff --git a/code/game/chars.h b/code/game/chars.h new file mode 100755 index 0000000..c5b1861 --- /dev/null +++ b/code/game/chars.h @@ -0,0 +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] + diff --git a/code/game/g_active.c b/code/game/g_active.c new file mode 100755 index 0000000..853f5fb --- /dev/null +++ b/code/game/g_active.c @@ -0,0 +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); +} + + diff --git a/code/game/g_arenas.c b/code/game/g_arenas.c new file mode 100755 index 0000000..1e6476b --- /dev/null +++ b/code/game/g_arenas.c @@ -0,0 +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; + } +} diff --git a/code/game/g_bot.c b/code/game/g_bot.c new file mode 100755 index 0000000..e42013d --- /dev/null +++ b/code/game/g_bot.c @@ -0,0 +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 ); + } + } +} diff --git a/code/game/g_client.c b/code/game/g_client.c new file mode 100755 index 0000000..5a584d4 --- /dev/null +++ b/code/game/g_client.c @@ -0,0 +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 ); + } +} + + diff --git a/code/game/g_cmds.c b/code/game/g_cmds.c new file mode 100755 index 0000000..e72c80e --- /dev/null +++ b/code/game/g_cmds.c @@ -0,0 +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